一、Ingress和Ingress控制器
1.1 为什么需要Ingress资源
Kubernetes上的NodePort和LoadBalancer类型的Service资源能够把集群内部服务暴露给集群外部客户端进行访问。但是由于生产环境中业务多为分布式,暗含复杂的调用关系,且业务数量不止一个,由此会带来如下问题:
- 如何管理端口
当需要对外暴露的服务量比较多的时候,NodePort/LoadBalancer在每个节点上开启的端口数量会极其庞大,而且难以维护(NodePort默认使用30000~32767端口)。这时候引出的思考问题是 “能不能使用 Nginx 啥的只监听一个端口(eg:80),然后按照域名向后转发?”这思路可以,简单的实现就是使用 DaemonSet 在每个Node节点上监听80端口,然后写好规则,因为 Nginx 外面绑定了宿主机 80 端口(就像 NodePort那样),本身又在集群内,那么向后直接转发到相应 Service IP 就行了。
- 域名分配和动态更新问题
由上面的思路,采用 Nginx 似乎已经解决了问题,但是其实这里面有一个弊端:每次有新服务加入怎么改 Nginx 配置?难道要手动改或者来个 Rolling Update 前端 Nginx Pod 么?这种人工介入的方式显然不那么理想。
通过如上介绍,使用NodePort和LoadBalancer类型的Service除了有这些问题之外,加上其自带的跃点现象,增加网络延迟,更增加使用云服务维护成本,同时自动化也做得不够理想。因此,Kubernetes为这种需求提供了一种更为高级的流量管理方式,使用Ingress资源以Kubernetes标准的资源格式定义流量调度、路由等规则,通过Ingress控制器监视API Server上Ingress资源的变动,并将其体现在自身的配置文件中。这样一来,流量由Ingress控制器(基于Ingress的定义)直接到达相关Service的后端端点(Pod对象),该转发过程不再经由Service进行,从而节省了由kube-proxy产生的流量代理(转发)开销。同时Ingress仍然借助Service资源完成服务发现。
从本质上说,Ingress资源基于HTTP虚拟主机或URL路径的流量转发规则,把需要暴露给集群外部的Service对象映射为Ingress控制器上的一个个虚拟主机或某个虚拟主机上的路径(见下图)。
1.2 为什么需要Ingress-Controller
如果说Service本质上就是一个由 kube-proxy 控制的四层负载均衡,在TCP/IP协议栈上转发流量,则Ingress就是工作在七层之上的另一种形式的Service,它同样会代理一些后端的Pod,也定义一些路由规则说明流量该如何转发,只不过这些规则使用的是HTTP/HTTPS协议。
Service 本身只是一些 iptables/ipvs规则,真正配置、应用这些规则的实际上是节点里的 kube-proxy 组件。如果没有 kube-proxy,Service 定义得再完善也没有用。同样的,Ingress 也只是一些 HTTP 路由规则的集合,相当于一份静态的描述文件,真正要把这些规则在集群里实施运行,还需要有另外一个东西,这就是 Ingress Controller(Ingress控制器),它的作用就相当于 Service 的 kube-proxy,能够读取、应用 Ingress 规则,处理、调度流量。
事实上,Ingress控制器本身就是一类以代理HTTP/HTTPS协议为主要功能的代理程序,它可以由任何具有反向代理功能的服务程序实现,比如Ingress-Nginx(使用较多)、Haproxy、Traefik、Gloo、Contour等。Kubernetes支持同时部署多个Ingress控制器。但为了避免一个Ingress资源被多个Ingress控制器重复加载,需要在Ingress资源指定加载该资源的Ingress控制器。
下图来自 Nginx 官网,比较清楚地展示了 Ingress Controller 在 Kubernetes 集群里的地位。
1.3 为什么要有Ingress-Class
有了Ingress和Ingress控制器就一定能完美管控集群的进出流量呢?未必。随着 Ingress 在实践中的大量应用,很多用户发现这种用法会带来一些问题,比如:
- 由于某些原因,项目组需要引入不同的 Ingress Controller,但 Kubernetes 不允许这样做;
- Ingress 规则过多,都交给一个 Ingress Controller 处理会让它不堪重负;
- 多个 Ingress 对象没有很好的逻辑分组方式,管理和维护成本很高;
- 集群里有不同的租户,他们对 Ingress 的需求差异很大甚至有冲突,无法部署在同一个 Ingress Controller 上。
因此,Kubernetes 就又提出了一个 Ingress Class 的概念,让它在 Ingress 和 Ingress Controller 中间,作为流量规则和控制器的协调人,解除了 Ingress 和 Ingress Controller 的强绑定关系,用它来定义不同的业务逻辑分组,简化 Ingress 规则的复杂度。比如,我们可以用 Class A 处理博客流量、Class B 处理短视频流量、Class C 处理购物流量。
基于此,这些 Ingress 和 Ingress Controller 彼此独立,不会发生冲突,所以上面的那些问题也就随着 Ingress Class 的引入迎刃而解了。
二、Ingress的工作原理
(1)Ingress-Controller通过和Kubernetes API Server交互,动态的去感知集群中Ingress规则变化。
(2)然后读取它,按照自定义的规则(即哪个域名对应哪个Service),生成一段Nginx配置。
(3)再写到Ingress-Controller的pod里,这个Ingress-Controller的Pod里运行着一个Nginx服务,控制器会把生成的Nginx配置写入 /etc/nginx.conf文件中。
(4)然后Reload一下使配置生效,以此达到域名区分配置和动态更新的作用。
说白了,Ingress控制器解决了之前手动改Nginx的问题,自动生成Nginx配置,再写入Pod中,再Reload使之生效。
三、部署Ingress-Controller
以ingress-nginx为例,参考https://github.com/kubernetes/ingress-nginx/deploy
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/cloud/deploy.yaml
在新创建的名称空间ingress-nginx中,前两个Pod(ingress-nginx-admission)是额外补充进来的,用于检查/填充资源规范。等到它们状态为Completed后就会运行ingress-nginx-controller,等到它状态为Running时表示ingress-nginx-controller运行成功。
同时,在ingress-nginx名称空间下还会生成若干Service对象。
查询ingressclass资源对象,发现部署ingress-nginx时会自动生成一个名为nginx的对象。后续创建Ingress资源时,如果系统上有多个Ingress控制器,究竟该被哪一个解析和使用?可以使用ingressclass去定义。
问题在于,ingress控制器该如何加入流量?一种方式是共享节点的网络名称空间(hostNetwork: true),另一种方式是通过NodePort类型的Service(暴露端口)接入进来,集群外部加一个LoadBalancer。如果没有LoadBalancer,则在ingress-nginx手动关联一个外部ip地址。
kubectl get svc ingress-nginx-controller -n ingress-nginx -o yaml > ingress-nginx-controller.yaml
打开 ingress-nginx-controller.yaml,添加一个外部IP地址(externalIPs)的同时设置外部流量处理策略为Cluster。
apply此配置文件,再查看此Service对象的信息,发现多了个外部IP地址(192.168.131.160)。但这种配置方式是有缺陷的,由于这个IP地址写死在某个节点上,一旦该节点宕机,则不能通过此入口访问服务。理想的做法是将外部IP地址通过keepalived流动在多个节点上。虽然这个外部IP地址指定在某个节点上,但因外部流量策略设置为Cluster,所以外部访问流量都能访问进来,将请求调度到集群内所有可用的端点。如果一定要这么配置,建议最好将外部IP地址配置在Pod所在的节点,这样不会产生跃点,一定程度上减轻对性能的负面影响。
访问该外部IP地址,结果说明流量已到达Nginx。
四、Ingress资源类型
4.1 基于URL路径进行流量分发
在同一个FQDN(Fully qualified domain name,完整域名)通过不同的URI(Uniform Resource Identifier,统一资源标识符)完成不同应用间的流量分发,无需为每个应用配置专用的域名。简单地说就是基于单个虚拟主机接收多个应用的流量,将流量分发至同一个应用下的不同子应用,每个子应用都有其专用的Service暴露服务。例如,对www.ilinux.io/api的请求统统转发至API Service资源,将对www.ilinux.io/wap的请求转发至WAP Service资源。
4.2 基于主机名的虚拟主机
为每个应用设置一个独立的主机名,基于这些主机名完成不同应用间的流量转发。每一个主机名对应一个Service对象。
类似于Nginx的虚拟主机,每个FQDN对应于Ingress控制器上的一个虚拟主机的定义。将多个FQDN解析至同一个IP地址,之后根据主机头信息进行转发。
4.3 TLS类型的Ingress资源
此类型以HTTPS协议发布Service资源,基于一个含有私钥和证书的Secret对象即可配置TLS协议的Ingress资源,目前来说,Ingress资源仅支持单TLS端口,并且还会卸载TLS会话。在Ingress资源中引用此Secret即可让Ingress控制器加载并配置为HTTPS服务。
简单地说就是客户端与Ingress建立TLS连接,而后Ingress卸载TLS会话后以明文通信形式与后端Service建立通信。
五、Ingress资源配置
5.1 基础准备
#语法
kubectl create ingress NAME --rule=host/path=service:port[,tls[=secret]]
常用选项
• --annotation=[]:提供注解,格式为"annotation=value"
• --rule=[]:代理规则(指定虚拟主机),格式为"host/path=service:port[,tls=secretname]"
• --class=’’:此Ingress适配的Ingress Class
准备环境:两个Service(demoapp10和demoapp11)
#部署demoappv1.0
kubectl create deploy demoapp10 --image=ikubernetes/demoapp:v1.0 --replicas=2
kubectl create svc clusterip demoapp10 --tcp=80:80
#部署demoappv1.1
kubectl create deploy demoapp11 --image=ikubernetes/demoapp:v1.1 --replicas=2
kubectl create svc clusterip demoapp11 --tcp=80:80
5.2 基于URL路径进行流量分发创建Ingress资源
基于URI方式代理不同应用的请求时,后端应用的URI若与代理时使用的URI不同,则需要启用URL Rewrite实现URI的重写。Ingress-Nginx支持使用”annotation nginx.ingress.kubernetes.io/rewrite-target”注解进行。
Eg:对于发往demoapp.wxd.com的请求,将”路径/v10”代理至service/demoapp10,将”路径/v11”代理至service/demoapp11。此为精准匹配。
kubectl create ingress demoapp --rule="demoapp.wxd.com/v10=demoapp10:80"--rule="demoapp.wxd.com/v11=demoapp11:80" --class=nginx --annotation nginx.ingress.kubernetes.io/rewrite-target="/"
此时进入之前部署的Ingress控制器,查看Nginx配置文件,会发现之前定义的ingress中定义的虚拟主机规则已被解析在Nginx配置文件当中。
kubectl exec -it ingress-nginx-controller-79d66f886c-f8q96 -n ingress-nginx -- /bin/sh
less nginx.conf
在物理机的C:\Windows\System32\drivers\etc\hosts文件添加如下解析:
192.168.131.160 demoapp.wxd.com
测试结果显示,访问/v10路径,对应的demoapp版本是v1.0,ServerIP即为deployment对象demoapp10对应的后端端点,负载均衡功能亦能实现。访问/v11路径的效果与之类似。这说明已经将clusterip类型的Service基于同一虚拟主机给发布出去了。
注意:这里的192.168.113.252为Ingress Nginx这个Pod的IP地址,因为使用了代理。
如果要使用URI前缀匹配,而非精准匹配,则创建Ingress命令如下:
kubectl create ingress demoapp --rule='demoapp.wxd.com/v10(/|$)(.*)=demoapp10:80' --rule='demoapp.wxd.com/v11(/|$)(.*)=demoapp11:80' --class=nginx --annotation nginx.ingress.kubernetes.io/rewrite-target='/$2'
此时在/v10或者/v11路径后面添加个/hostname,发现也能正常访问。之前是不行的。
查看Pod日志,也能看到当访问/v11路径时,结果是把/v11路径删除,直接访问/hostname路径,说明路径重写正常完成,以/v11路径为前缀的访问能正常实现。
5.3 基于虚拟主机创建Ingress资源
需要事先准备多个域名,且确保这些域名的解析能够到达Ingress控制器。
Eg:对demoapp10.wxd.com的请求代理至service/demoapp10,对demoapp11.wxd.com的请求代理至service/demoapp11
#这里的*表示起始于根的所有路径
kubectl create ingress demoapp --rule='demoapp10.wxd.com/*=demoapp10:80' --rule='demoapp11.wxd.com/*=demoapp11:80' --class=nginx
此时,进入Ingress控制器的Pod内部,查看Nginx配置文件,发现已有关于demoapp10和demoapp11两个虚拟主机的定义。下图为demoapp10.wxd.com域名对应的虚拟主机的配置,demoapp11.wxd.com与之类似。
在Windows机器的hosts文件添加如下解析:
192.168.131.160 demoapp10.wxd.com demoapp11.wxd.com
现实中对于开放到外部的服务应该有个DNS服务器来解析域名,这里使用hosts文件来完成此功能。需要确保外部的DNS服务器的主机名解析能够对应到Ingress Nginx这个Service的外部可用IP地址上或LoadBalancer的IP地址上。
5.4 基于TLS创建Ingress资源
#生成私钥
umask 077;openssl genrsa -out wxd.key 4096
#使用已有私钥创建证书签署请求。主体信息由选项subj指定,其中CN的值被API Server识别为用户名,O的值被识别为用户组
openssl req -new -x509 -key wxd.key -out wxd.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=services.wxd.com
#创建tls类型的Secret对象
kubectl create secret tls tls-wxd --cert=./wxd.crt --key=./wxd.key
#创建常规的代理主机规则(Ingress资源),同时将该主机定义为tls类型
kubectl create ingress tls-demo --rule='demoapp.wxd.com/*=demoapp10:80,tls=tls-wxd' --class=nginx
此时查看tls-demo这个Ingress的详细信息,可以看到在rules的基础上多了个tls选项,指明将demoapp.wxd.com这个主机名配置为tls格式,使用的Secret对象名为tls-wxd。
注意:启用tls后,该域名下的所有URI默认将http请求强制跳转至https。若不希望使用该功能,可使用如下注解选项:
--annotation nginx.ingress.kubernetes.io/ssl-redirect=false
通过curl -I命令可以看到,这里做了永久重定向(308代码),转发到https://demoapp.wxd.com。
由于这里用到自签证书,不够安全,这里访问https://demoapp.wxd.com就要加-k选项。
六、基于Ingress Nginx进行灰度发布
6.1 介绍
Ingress Nginx支持配置Ingress Annotations来实现不同场景下的灰度发布和测试,它能够满足金丝雀发布、蓝绿部署等不同业务场景。这样不论后端Pod数量多少,都可以做到流量的精准分配。
这里有两种使用场景:
- 场景1:不区分用户的类别,直接基于比例做金丝雀发布。
假设线上已运行了一套对外提供7层服务的 Service B,此时修复了 Service B 的部分问题,需灰度上线新版本 Service B'。但不期望直接替换原有的 Service B,需先切换10%的流量至新版本,待运行一段时间足够稳定后再逐渐加大新版本流量比例直至完全替换旧版本,最终平滑下线旧版本。示意图如下:
- 场景2:灰度发布新版本到部分用户
人为的在Nginx上做流量特性区分,有满足相关特性的请求就发给金丝雀,其余的都视为常规请求。假设线上已运行了一套对外提供7层服务的 Service A,此时需上线开发的新版本 Service A',但不期望直接替换原有的 Service A,仅灰度部分用户,待运行一段时间足够稳定后再逐渐全量上线新版本,平滑下线旧版本。针对此场景可使用 Nginx Ingress 基于 Header 或 Cookie 进行流量切分的策略来发布,业务使用 Header 或 Cookie 来标识不同类型的用户,并通过配置 Ingress 来实现让带有指定 Header 或 Cookie 的请求被转发到新版本,其它请求仍然转发到旧版本,从而将新版本灰度给部分用户。示意图如下:
通过给 Ingress 资源指定 Nginx Ingress 所支持的 annotation 可实现金丝雀发布。需给服务创建2个 Ingress,其中1个为常规 Ingress,另1个为含有 nginx.ingress.kubernetes.io/canary: "true"这个固定的 annotation 的 Ingress,称为 Canary Ingress。Canary Ingress 一般代表新版本的服务,结合另外针对流量切分策略的 annotation 一起配置即可实现多种场景的金丝雀发布。
Ingress Nginx Annotations支持的金丝雀规则(流量切分策略,即将指定的流量发布到金丝雀版本)定义如下:
- nginx.ingress.kubernetes.io/canary-by-header
基于该Annotations指定的Request Header(请求头)进行流量切分,适用于灰度发布及A/B测试。在请求报文中,若存在此Header且值为always时,请求将会发送到金丝雀版本。若存在此Header且值为never时,请求将不会发送至金丝雀版本。对于任何其它值,将忽略该Annotations指定的Header,并通过优先级请求与其它金丝雀规则进行优先级比较。
- nginx.ingress.kubernetes.io/canary-by-header-value
它可以作为canary-by-header的补充,可指定请求头为自定义值,包含但不限于always或never。请求头由前一个Annotations(canary-by-header)进行指定。当请求报文中存在指定的请求头,且请求头的值命中指定的自定义值时,请求将会转发给该 Ingress 定义的金丝雀版本,如果是其它值则忽略该 Annotation。
- nginx.ingress.kubernetes.io/canary-by-header-pattern
与canary-by-header-value类似,只不过此Annotation基于正则表达式匹配Request Header的值。若此Annotation与canary-by-header-value同时存在,则此Annotation会被忽略。
- nginx.ingress.kubernetes.io/canary-weight
基于服务权重进行流量切分,权重范围0~100%,按比例将请求路由到Canary Ingress对应的后端服务。
- nginx.ingress.kubernetes.io/canary-by-cookie
基于cookie的流量切分,使用方式与canary-by-header类似。
当同时定义了这些规则时,规则遵照以下优先顺序(优先级由高到低):canary-by-header -> canary-by-cookie -> canary-weight
6.2 应用示例
先准备两个应用对应的Service,分别为demoapp-v10(旧版本)和demoapp-v11(新版本)
cd learning-k8s/ingress-canary-demo
vim demoapp-v10.yml
apiVersion: v1
kind: Service
metadata:
labels:
app: demoapp10
name: demoapp-v10
spec:
internalTrafficPolicy: Cluster
ports:
- name: 80-80
port: 80
protocol: TCP
targetPort: 80
selector:
app: demoapp10
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
demoapp-v11的配置与之类似。
kubectl apply -f demoapp-v10.yml -f demoapp-v11.yml
下面的配置文件创建了一个Ingress对象,将demoapp-v10(旧版本)通过域名demoapp.magedu.com发布出去。
vim 01-ingress-demoapp.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demoapp
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: demoapp.magedu.com
http:
paths:
- backend:
service:
name: demoapp-v10
port:
number: 80
path: /
pathType: Prefix
kubectl apply -f 01-ingress-demoapp.yaml
以下示例定义了一个新的Ingress对象,基于特定的请求头X-Canary做流量分发。当请求头X-Canary的值为always时就将流量转发给demoapp-v11(新版本)。
vim 02-canary-by-header.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
name: demoapp-canary-by-header
spec:
rules:
- host: demoapp.magedu.com
http:
paths:
- backend:
service:
name: demoapp-v11
port:
number: 80
path: /
pathType: Prefix
kubectl apply -f 02-canary-by-header.yaml
执行curl -H "X-Canary:always" demoapp.magedu.com命令,发现请求已经转给demoapp-v11(新版本),版本变成了demoapp v1.1(原来是demoapp v1.0)。当X-Canary的标头的值为never或其它值时,请求转到正常的老版本demoapp-v10。
以下示例在新的Ingress对象中自定义请求头名称为IsVIP,当它的值为false时,就当成金丝雀发布,将流量转给demoapp-v11,否则流量就转向demoapp-v10。
vim 03-canary-by-header-value.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "IsVIP"
nginx.ingress.kubernetes.io/canary-by-header-value: "false"
name: demoapp-canary-by-header-value
spec:
rules:
- host: demoapp.magedu.com
http:
paths:
- backend:
service:
name: demoapp-v11
port:
number: 80
path: /
pathType: Prefix
kubectl apply -f 03-canary-by-header-value.yaml
执行curl -H "IsVIP:false" demoapp.magedu.com命令,发现请求转给demoapp-v11。
以下示例在原来的基础上用正则表达式进行匹配。当请求头名称为Username,值为以大写VIP或小写vip加_作为前缀时,请求就转给demoapp-v11。
vim 04-canary-by-header-pattern.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-header: "Username"
nginx.ingress.kubernetes.io/canary-by-header-pattern: "(vip|VIP)_.*"
name: demoapp-canary-by-header-pattern
spec:
rules:
- host: demoapp.magedu.com
http:
paths:
- backend:
service:
name: demoapp-v11
port:
number: 80
path: /
pathType: Prefix
kubectl apply -f 04-canary-by-header-pattern.yaml
以下示例基于权重发布金丝雀流量。这里将10%的流量发给demoapp-v11(新版本),将90%的流量发给demoapp-v10旧版本。
vim 05-canary-by-weight.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
name: demoapp-canary-by-weight
spec:
rules:
- host: demoapp.magedu.com
http:
paths:
- backend:
service:
name: demoapp-v11
port:
number: 80
path: /
pathType: Prefix
kubectl apply -f 05-canary-by-weight.yaml
当访问的数量足够多时,通过测算可得,到达demoapp-v11的流量数占总数的10%。当新版本运行一段时间发现无问题时,可以不断调整权重大小。当调整为100(即100%)时,效果等同于蓝绿部署,即一次性将请求全部发往新版本。
以下示例基于cookie发布。这里指定cookie为vip_user。当有此cookie,且值为always时,即为金丝雀发布,就将请求转发给demoapp-v11。
vim 06-canary-by-cookie.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-by-cookie: "vip_user"
name: demoapp-canary-by-cookie
spec:
rules:
- host: demoapp.magedu.com
http:
paths:
- backend:
service:
name: demoapp-v11
port:
number: 80
path: /
pathType: Prefix
kubectl apply -f 06-canary-by-cookie.yaml
执行curl -b "vip_user=always" demoapp.magedu.com命令,发现请求已转给demoapp-v11。
标签:Ingress,Kubernetes,nginx,ingress,canary,demoapp,io From: https://blog.51cto.com/u_15796303/6788265