ingress是什么
Ingress 提供从集群外部到集群内服务的 HTTP 和 HTTPS 路由,是对集群中服务的外部访问进行管理的 API 对象, 流量路由 Ingress 资源所定义的规则来控制。 通过配置,Ingress 可为 Service 提供外部可访问的 URL、对其流量作负载均衡、 终止 SSL/TLS,以及基于名称的虚拟托管等能力。 Ingress 控制器 负责完成 Ingress 的工作,具体实现上通常会使用某个负载均衡器, 不过也可以配置边缘路由器或其他前端来帮助处理流量。
简单得理解就是能利用nginx、haproxy啥的负载均衡器暴露集群内服务的工具
可以说Ingress是为了弥补NodePort在流量路由方面的不足而生的。使用NodePort,只能将流量路由到一个具体的Service,并且必须使用Service的端口号来访问该服务。但是,使用Ingress,就可以使用自定义域名、路径和其他HTTP头来定义路由规则,以便将流量路由到不同的Service。
常见的三种负载均衡方案
NodePort:
- NodePort 人如其名,直接使用宿主机的 Port 进行暴露。
- 缺点是 端口随机(通常30000+)且有限,使用不便利,维护成本高。
- 唯一的优点是 原生支持,不需要引入额外的组件。但如果想实现高可用和负载均衡,也需要在上层再挂一个 LB。
LoadBalancer:
- LB 与 服务 一对一 创建,这种模式 最灵活、性能最高、安全性最好。
- 但是对 IP 资源消耗较大,每个云厂商实现不同。适合做四层负载。
Ingress:
- 一个流量入口对应多个 SVC,类似 Nginx,拥有处理七层流量的能力,一般可根据域名和路径等策略进行路由和相关处理。
- 支持限流、TLS、黑名单等诸多功能,用户体验与 网关 相似,适合做七层负载。
ingress 的工作原理
主架构
Ingress Nginx解决方案由如下模块组成:
- Ingress控制器(Nginx Proxy、选主、控制器逻辑)
- 业务定义的Ingress对象
- 后端service和POD endpoints
Ingress在部署的时候可以通过部署多个replica提供HA,多个replica通过选主程序确定一个leader;之后leader controller读取Kubernetes定义的所有Ingress以及对应的后端Service以及Endpoints,通过分析集群的这些输入信息生产一个反应系统当前运行状态的model;controller使用这个model生成Nginx的配置文件,最后realod Nginx来使这些配置生效;后续Ingress Nginx可以通过域名和路径将外部用户请求proxy到内部的endpoints上面去。
控制面
Ingress在部署之后,最重要的任务就是根据集群关键元素的状态生成Nginx的配置文件,然后进行动态管理,如下图所示:
在Ingress Ngixn的架构里面,控制面最关键的一个数据结构是Nginx model。当前,通过获取Ingress、Service、Endpoints、Secrets、Configmaps等Kubernetes Object,进行分析,得出一个反应当前系统运行状态的model;这个model由controller loop进行状态比较,比较之后生成Nginx的配置文件。
Controller在分析对比model的状态的时候:
- 如果model没有变化,则不进行任何Nginx操作
- 如果model变化的只有service的endpoints,则将endpoints变化交给Lua Handler进行处理,而不需要生成新的Nginx配置文件并reload Nginx
- 如果存在endpoints之外的变化,则需要生成新的Nginx配置文件并且reload Nginx
mode的如下一些变化会导致Nginx reload:
- 有新建的Ingress
- 已有的Ingress中添加了TLS部分
- 导致不止Upstream配置变化的Ingress annotation的更新
- Ingress中添加或者删除一个path
- 删除Ingress、Service或者Secret
- 一些出错的Service或者Secret之类状态变正常了
- 更新Secret
新生成的配置文件存在在Configmap里面,然后被各个Nginx POD引用。在生成配置的时候,会参考如下的一些基本规则和操作:
- 按照Ingress规则的时间戳,先处理时间戳较早的规则
- 如果多个Ingress定义来一样的host和path,则使用时间戳较早的规则
- 如果多个Ingress包含同样host的TLS配置,则使用时间戳较早的规则
- 如果多个Ingress包含影响Server block的annotation,则使用时间戳较早的规则
- 构建Nginx Server列表
- 构建NGINX Upstreams列表
- 如果多个Ingress定义来同一个host的不同path,这些path定义会被合并
- Annotations对Ingress里面的所有path生效
- 不同的Ingress可以定义不同的annotation并且不相互干扰
数据面流程
在Ingress Nginx部署的时候,会部署多个POD实例,然后Ingress Nginx还会部署一个NodePort Service,最后将部分部署了Ingress Nginx的node挂载到Vendor的LB上面,作为接入层。
在公有云Kubernetes部署的服务需要通过一系列的中间路径最终对外提供服务,从最外层一直到最末端的路径如下:
在上述路径中,需要具体说明的关键点如下:
- external ELB承载DNS域名并且进行HTTPS卸载
- Kong用于根据域名或者路径进行路由后端选择或者实施访问限制,Kong的路由目的可能是部署在ECS上的服务,也有可能是部署在Kubernetes上的服务
- Kubernetes集群部署了内部和外部两个ELB,分别对应内部和外部服务,外部ELB接受Kong转过来的外部请求,内部ELB接受来自内部VM或者POD的请求
- POD如果请求部署在Kubernetes的服务,可以选择使用内部域名,也可以选择使用Kubernetes的cluster service;但是对于某些使用gRPC协议的服务,使用ingress nginx或者cluster service都无法进行很好的负载均衡,需要自行制定服务发现和负载均衡
- 所有外部的请求域名类似于xxx.test.com,而内部请求的域名则类似于internal-xxx.test.com
- Kubernetes的ELB挂载nodes ig的worker节点,这些节点都部署了ingress nginx daemonset,用于接入对于集群中服务的访问请求
- 使用ingress或者cluster service方式访问POD中的服务,不可避免存在跨worker节点的traffic relay
Nginx是如何连接后端的
在配置Ingress的时候,我们只配置了后端服务的cluser service的名字,之后Nginx进行请求转发的时候,并不是直接连接cluser service的IP。因为在此之际,controller会根据cluser service的名字找到对应的所有endpoints,然后Nginx会直接向这些endpoints POD转发请求,更具体的信息可以在这里找到:github.com/kubernetes-…
这样的做主要原因还是在于直接连接后端POD可以给予ingress更及时的响应能力和更完善的proxy功能支持,增强Ingress的掌控能力,这个文章《Ingress Controller: Forget about the service》也进行了详尽的分析。
2.5 Ingress Nginx Controller是如何做HA的 在Ingress Controller启动的时候,会通过configmap “ingress-controller-leader-nginx”进行选主,leader controller会lock这个configmap:
kubectl logs -n ingress-nginx nginx-ingress-controller-2nqlg | grep leader
I0424 08:29:13.641151 8 leaderelection.go:242] attempting to acquire leader lease ingress-nginx/ingress-controller-leader-nginx...
I0424 08:29:14.295593 8 leaderelection.go:252] successfully acquired lease ingress-nginx/ingress-controller-leader-nginx
I0424 08:29:14.295656 8 status.go:86] new leader elected: nginx-ingress-controller-2nqlg
在configmap被leader lock之后,configmap里面会记录如下信息:
主流的Ingress Controller
在 Kubernetes 中,有很多不同的 Ingress 控制器可以选择,例如 Nginx、Traefik、HAProxy、Envoy 等等。不同的控制器可能会提供不同的功能、性能和可靠性,可以根据实际需求来选择合适的控制器。Kubernetes生态系统中有许多不同的Ingress控制器可供选择,其中比较主流的有:
- Nginx Ingress Controller:基于Nginx的Ingress控制器,提供了广泛的功能和配置选项。
- Traefik Ingress Controller:Traefik是一个流行的反向代理和负载均衡器,Traefik Ingress Controller提供了灵活的配置选项和自动发现服务的功能。
- Istio Ingress Gateway:Istio是一种服务网格,它提供了基于Envoy代理的Ingress Gateway来管理入站和出站流量。
- Contour Ingress Controller:基于Envoy代理的Ingress控制器,具有高度可扩展性和灵活的路由规则。
- Kong Ingress Controller:Kong是一个API网关,提供了可扩展的路由和服务管理功能。
- Ambassador API Gateway:Ambassador是一个Kubernetes-native API Gateway,提供了自动化的服务发现和路由管理功能。
可以根据实际需要选择适当的Ingress控制器,并进行相应的配置和部署。
控制器的部署方案
Ingress控制器通常建议部署在 Kubernetes 集群内部。这样可以确保 Ingress 控制器与 Kubernetes API Server 之间的网络延迟较低,并且可以通过 Kubernetes Service 来管理 Ingress 控制器的负载均衡和高可用性。在 Kubernetes 集群内部部署 Ingress 控制器通常有两种方式:
- 部署一个独立的 Ingress 控制器 Pod:可以通过将 Ingress 控制器部署为一个独立的 Pod,使用 Kubernetes Service 对其进行负载均衡和暴露服务。
- 部署一个 DaemonSet 类型的 Ingress 控制器:可以通过部署一个 DaemonSet 类型的 Ingress 控制器,使每个节点上都运行一个 Ingress 控制器 Pod,并通过 Kubernetes Service 对其进行负载均衡和暴露服务。
无论是哪种方式,Ingress 控制器都应该被部署在 Kubernetes 集群内部,以便与 Kubernetes API Server 进行通信,并与 Kubernetes 资源(如 Service、Pod、Endpoints 等)进行交互。
Ingress 资源 配置
让我们看看创建一个 Ingress,需要的基本参数。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-wildcard-host
spec:
rules:
- host: "foo.bar.com"
http:
paths:
- pathType: Prefix
path: "/bar"
backend:
service:
name: service1
port:
number: 80
- host: "*.foo.com"
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: service2
port:
number: 80
可以看到,通常我们会通过使用不用的域名和路径,来访问不同的后端服务。所以配置文件中我们填写了 host、path 用来进行规则匹配,backend.service 则是指明了转发给哪个K8S服务。
所以 我们可以把 Ingress 理解成集成在 K8S 中的网关,每当我们 创建/修改 Ingress 资源时,就是对网关的配置进行修改。
图中 Kubernetes Ingress 即为 IngressNginx,为K8S官方开发支持。而与之相邻的 NginxIngress 则是 NGINX 官方锁开发支持。
如无特殊说明,本文中所提及的 IngressNginx 均指 Kubernetes Ingress。
最终,我们选择了 IngressNGINX 方案。
IngressNginx 优点:
- 基本等同于传统 Nginx 的方案,优势是性能、简单、稳定可靠 和 低成本。
- 社区默认支持的 IngressController。
IngressNginx 缺点:
-
由于Nginx的模式是 Watch K8S 集群的相关资源事件,当发现有更新时,会重新生成配置文件,并进行 Reload。然而 Reload 的成本会随着配置文件的大小增长,当配置文件过大,热更新配置可能会有几秒的延迟,这期间会有问题。当然,也有方案进行解决,例如在集群中部署多个 IngressNginx 分摊压力,但会增加维护成本。
-
另一个缺点就是可扩展性,当我们想对它进行功能扩展时,只能使用 Lua 的方式,这个成本也是很高的。当然,原生支持的功能基本足够使用。
IngressNGINX 架构
Ingress 一般由 Ingress资源对象、IngressController 和 GW 三部分组成(对于 IngressNGINX来说 GW 就是 NGINX)。
IngressController 实际上就是一种适配器模式,把原本毫无关系的 Ingress 和 NGINX 集成起来。IC 作为适配器,使 NGINX 拥有了感知 K8S 集群资源变化的能力。
IngressNGINX 模型原理
IN POD由一个容器组成,该容器又包括以下内容:
- IC进程,它根据Ingress和集群中创建的其他资源配置NGINX。
- NGINX Master进程,它是负责控制NGINX的管理进程。
- NGINX Worker进程,它负责处理客户端通信,并对后端应用程序的流量进行负载均衡。
下面是一个模块图,它显示了这些流程如何在一起交互,以及如何与一些外部流程/实体交互:
下面的编号列表用花括号描述了每个连接的类型:
-
(HTTP)Prometheus通过IC公开的HTTP端点获取IC和NGINX指标。默认值为:9113/metrics。注意:Prometheus不是IC所需要的,端点可以关闭。
-
(HTTPS)IC读取Kubernetes API以获取集群中资源的最新版本,并写入API以更新已处理资源的状态并发出事件。
-
(HTTP)Kubelet探测IC就绪探针(默认值为:8081/nginx-ready),以考虑IC-Pod就绪。
-
(文件I/O)当IC启动时,它从文件系统中读取配置生成所需的配置模板。模板位于容器的 /etc/nginxtemplate/ 目录中,扩展名为 .tmpl。
-
(文件I/O)IC将日志写入容器运行时收集的stdout和stderr。
-
(文件I/O)IC根据集群中创建的资源生成NGINX配置,并将其写入文件系统的 /etc/nginx 目录中。配置文件的扩展名为 .conf。
-
(文件I/O)IC将TLS证书和密钥从入口和其他资源中引用的任何TLS机密写入文件系统。
-
(HTTP)IC通过UNIX:/var/lib/NGINX/nginx-status.sock UNIX套接字获取NGINX指标,并将其转换为#1中使用的普罗米修斯格式。
-
(HTTP)为考虑配置重新加载成功,IC确保至少有一个NGINX Worker具有新配置。为此,IC通过UNIX:/var/lib/nginx/nginx-config-version.sock UNIX套接字检查特定端点。
-
(N/A)为了启动NGINX,IC运行NGINX命令,该命令启动NGINX主机。
-
(信号)为了重新加载NGINX,IC 运行 nginx-s reload 命令,该命令验证配置并将重新加载信号发送给NGINX主机。
-
(信号)为了关闭NGINX,IC执行nginx-s quit命令,该命令将优美的关闭信号发送给NGINX主机。
-
(文件I/O)NGINX主服务器将日志发送到它的stdout和stderr,这两个日志由容器运行时收集。
-
(文件I/O)NGINX主机在启动或重新加载时读取配置中引用的TLS证书和密钥。
-
(文件I/O)NGINX主机在启动时或重新加载期间读取配置文件。
-
(信号)NGINX主程序控制NGINX Worker的生命周期,它使用新配置创建 Worker,并使用旧配置关闭Worker。
-
(文件I/O)NGINX工作人员将日志写入容器运行时收集的stdout和stderr。
-
(UDP)NGINX Worker通过UNIX套接字/var/lib/NGINX/nginx-syslog.sock通过Syslog协议将HTTP上游服务器响应延迟日志发送到IC。反过来,IC分析并将日志转换为普罗米修斯度量。
-
(HTTP、HTTPS、TCP、UDP)客户端向端口80和443以及GlobalConfiguration资源公开的任何其他端口上的任何NGINX工作端口发送通信量,并从其接收通信量。
-
(HTTP、HTTPS、TCP、UDP)NGINX工作器向后端发送通信量,并从后端接收通信量。
-
(HTTP)Admin 可以通过NGINX工作器使用端口8080连接到NGINX stub_status。注意:默认情况下,NGINX只允许来自本地主机的连接。
创建一个 Ingress 的处理流程
核心步骤:
1. 用户 创建 Ingress
-
IC 感知到 Ingress 资源发生变化
-
IC 重新生成配置文件
-
IC 触发 NGINX Reload
7.2 NGINX 读取配置文件
何时进行 NGINX Reload
通常,Kubernetes 控制器使用SyncLoop模式来检查控制器中所需的状态是否更新或需要更改。为此,我们需要使用集群中的不同对象来构建模型,特别是Ingresses, Services, Endpoints, Secrets, 和 Configmaps 来生成一个反映集群状态的时间点配置文件。
为了从集群中获得这个对象,我们使用了 Kubernetes Informer。当添加、修改或删除一个新对象时,该informer允许对使用回调对单个更改的更改作出反应。不幸的是,没有办法知道某个特定的更改是否会影响最终的配置文件。因此,对于每一个变化,必须根据集群的状态从零开始重建一个新的模型,并与当前的模型进行比较。 如果新模型等于当前模型,那么我们就避免生成新的 NGINX 配置并触发重新加载。否则,将检查差异是否仅限于 Endpoints。如果是这样,那么就使用 HTTP POST 请求将 Endpoints 的新列表发送给在 NGINX 中运行的 Lua 处理程序,同样避免生成新的 NGINX 配置并触发重新加载。如果运行和新模型之间的差异不仅仅是 Endpoints,那么将基于新模型创建一个新的 NGINX 配置,替换当前模型并触发Reload。
该模型的一个用途是避免不必要的重载,当状态没有变化时,并检测定义中的冲突。NGINX 配置的最终表示是从一个 Go 模板生成的,该模板使用新模型作为模板所需变量的输入。
一些需要 Reload 的场景(括号中表示 NGINX配置文件需要做出的改动):
- 创建新的 Ingress 资源(添加 server)。
- TLS 部分添加到现有的 Ingress(增加 tls 配置)。
- 从 Ingress 添加/删除路径(增加/删除 location)。
- 删除 Ingress、Svc、Secret 等(需要删除 server 等)。
需要注意的是,NGINX 使用 balancer_by_lua_block 模块进行负载均衡,这个lua模块可以在不 Reload 的情况下,动态更新upstream上游节点。换句话说,如果只是 Endpoint 有更新,则无需进行 Reload。
下面是 NGINX 的配置文件 nginx.conf。
nginx.conf
server ``{`` ``server_name test-msc.qihoo.net ; ``listen 80 ;`` ``listen 443 ssl http2 ; ...... ``location / ``{ ``// 一些和 K8S 资源相关的标识`` ``set $namespace ``"nlzx-chy"``;`` ``set $ingress_name ``"qc-safety-brain-test-msc"``;`` ``set $service_name ``"qc-safety-brain-test-frontend"``;`` ``set $service_port ``"80"``;`` ``set $location_path ``"/"``;`` ``set $global_rate_limit_exceeding n; ``// 最终转发到 upstream_balancer 上游`` ``proxy_pass http``:``//upstream_balancer; ...... upstream upstream_balancer ``{`` ``// 使用 balancer_by_lua_block lua模块进行负载均衡`` ``balancer_by_lua_block ``{`` ``tcp_udp_balancer.balance()`` ``}``} |
|---|
NGINX Reload 原理
我们在前面提了很多次 NGINX Reload,接下来讲一下 NGINX Reload 的原理。让我们看看 NG 是如何在不影响流量的情况下,进行配置重载。
首先我们看一下 NG 的进程模型,NG 是多进程模型。Master 作为父进程,负责管理所有子进程,不处理流量。Worker作为子进程,负责处理流量。
Master 和 Worker 之间主要通过 signal 进行通信。
进程模型抽象:
早期 NGINX 的模型是,Master 负责 Listen,然后在 fork 时把 socket 传递给子进程,每个子进程进行 Accept 取客户端链接。但多个进程 Accept 同一个 Socket 有惊群问题,需要使用互斥锁处理,会对性能有一定影响。
而现在的 ReusePort 模型socket 的 listen 也交给子进程,这样每个子进程都持有自己独有的 socket,就可以不用使用互斥锁,直接交给内核进行负载均衡,性能更佳。提升数据如下:
通常我们需要使用 nginx -s reload 指令进行 Reload,在现在的模型下,这个指令原理如下:
使用nginx -s reload进行平滑重启。nginx启动时会通过参数-s 发现目前要进行信号处理而不是启动nginx服务,然后他会查看nginx的pid文件,pid文件中保存有master的进程号,然后向master进行发送相应的信号,reload对应的是HUP信号,所以nginx –s reload 跟kill -1 一样。Master收到HUP信号后的处理流程如下:
-
master解析新的配置文件。
-
master fork出新的worker进程,此时新的worker会和旧的worker共存。
-
master向旧的worker发送QUIT命令。
-
旧的worker会关闭监听端口,不再接受新的网络请求,并等待所有正在处理的请求完成后,退出。
-
此时只有新的worker存在,nginx完成了重启。
NGINX 是 IO 密集型系统,关于 IO 密集型系统的各种性能优化,这里就不展开讲了。
IngressController 高可用
至此,我们对 IngressController 的原理有了一定的了解。最后我们聊一下 IC 本身的高可用,即 IC 本身的多副本部署架构。
我们使用 DaemonSet + NodeSelector(ingress=on) 方式,只在 Master 节点上部署 IC。
在 IC 的上层使用了 LVS,把 IC 挂载到 lvs 后面,对多个 IC 进行负载均衡。
优点:IC 和 Master 组件集成在一起,不需要额外的机器部署,成本低。
缺点:当 IC 接入的流量压力过大时,可能会争抢集群组件的资源,影响集群稳定性。
后期规划
由于 IngressNginx 是比较成熟的软件,几乎不用担心他的稳定性、性能 和 功能完善度。所以可优化的层面主要是部署架构:
- 和 Master 分离开,单独机器部署,避免影响到集群基础组件运行。
- 在一个集群中部署多个 IC,分担流量压力。
按照目前集群对 Ingress 的使用规模来看,还没有达到这个地步,可以持续观察着,在遇到瓶颈时再进行变动,以节省成本。