目录
使用Ingress Controller代理k8s内部应用的流程
Ingress介绍
Ingress官网定义:Ingress可以把进入到集群内部的请求转发到集群中的一些服务上,从而可以把服务映射到集群外部。Ingress 能把集群内Service 配置成外网能够访问的 URL,流量负载均衡,提供基于域名访问的虚拟主机等。
Ingress简单的理解就是你原来需要改Nginx配置,然后配置各种域名对应哪个 Service,现在把这个动作抽象出来,变成一个 Ingress 对象,你可以用 yaml 创建,每次不要去改Nginx 了,直接改yaml然后创建/更新就行了;那么问题来了:”Nginx 该怎么处理?”
Ingress Controller 这东西就是解决 “Nginx 的处理方式” 的;Ingress Controller 通过与 Kubernetes API 交互,动态的去感知集群中Ingress规则变化,然后读取他,按照他自己模板生成一段 Nginx 配置,再写到 Nginx Pod 里,最后 reload 一下
Ingress Controller介绍
Ingress Controller是一个七层负载均衡调度器,客户端的请求先到达这个七层负载均衡调度器,由七层负载均衡器在反向代理到后端pod,常见的七层负载均衡器有nginx、traefik,以我们熟悉的nginx为例,假如请求到达nginx,会通过upstream反向代理到后端pod应用,但是后端pod的ip地址是一直在变化的,因此在后端pod前需要加一个service,这个service只是起到分组的作用,那么我们upstream只需要填写service地址即可
Ingress和Ingress Controller总结
Ingress Controller 可以理解为控制器,它通过不断的跟 Kubernetes API 交互,实时获取后端Service、Pod的变化,比如新增、删除等,结合Ingress 定义的规则生成配置,然后动态更新上边的 Nginx 或者trafik负载均衡器,并刷新使配置生效,来达到服务自动发现的作用。
Ingress 则是定义规则,通过它定义某个域名的请求过来之后转发到集群中指定的 Service。它可以通过 Yaml 文件定义,可以给一个或多个 Service 定义一个或多个 Ingress 规则。
使用Ingress Controller代理k8s内部应用的流程
- 部署Ingress controller,我们ingress controller使用的是nginx
- 创建Pod应用,可以通过控制器创建pod
- 创建Service,用来分组pod
- 创建Ingress http,测试通过http访问应用
- 创建Ingress https,测试通过https访问应用
客户端通过七层调度器访问后端pod的方式
使用七层负载均衡调度器ingress controller时,当客户端访问kubernetes集群内部的应用时,数据包走向如下图流程所示:
Ingress实现业务灰度发布
常见的版本发布方法
蓝绿部署
蓝绿部署,是采用两个分开的集群对软件版本进行升级的一种方式。它的部署模型中包括一个蓝色集群 A 和一个绿色集群 B,在没有新版本上线的情况下,两个集群上运行的版本是一致的,同时对外提供服务。
系统升级时,蓝绿部署的流程是:
首先,从负载均衡器列表中删除集群 A,让集群 B 单独提供服务。
然后,在集群 A 上部署新版本。
接下来,集群 A 升级完毕后,把负载均衡列表全部指向 A,并删除集群 B,由 A 单独提供服务。
在集群 B 上部署完新版本后,再把它添加回负载均衡列表中。
这样,我们就完成了两个集群上所有机器的版本升级。
从上述的部署流程中,也能发现,蓝绿部署它的优点在于发布策略简单、对于用户来说无感知,可以实现升级平滑过渡。但它的缺点也很明显:需要准备正常业务使用资源的两倍以上服务器,需要投入较大的资源成本。当然对于不差钱、追求服务稳定性的公司而言,较为推荐这种部署模式。
红黑部署
红黑部署是Netflix采用的部署手段,Netflix的主要基础设施是在AWS上,它与蓝绿部署类似,红黑部署也是通过两个集群完成软件版本的升级。
当前提供服务的所有机器都运行在红色集群 A 中,当需要发布新版本的时候,具体流程是这样的:
- 先在云上申请一个黑色集群 B,在 B 上部署新版本的服务;
- 等到 B 升级完成后,我们一次性地把负载均衡全部指向 B;
- 把 A 集群从负载均衡列表中删除,并释放集群 A 中所有机器。
这样就完成了一个版本的升级。可以看到,与蓝绿部署相比,红黑部署只不过是充分利用了云计算的弹性伸缩优势,从而获得了两个收益:一是,简化了流程;二是,避免了在升级的过程中,由于只有一半的服务器提供服务,而可能导致的系统过载问题。
灰度发布
灰度发布,也被叫作金丝雀发布。与蓝绿部署、红黑部署不同的是,灰度发布属于增量发布方法。也就是说,服务升级的过程中,新旧版本会同时为用户提供服务。
灰度发布的具体流程是这样的:在集群的一小部分机器上部署新版本,给一部分用户使用,以测试新版本的功能和性能;确认没有问题之后,再对整个集群进行升级。简单地说,灰度发布就是把部署好的服务分批次、逐步暴露给越来越多的用户,直到最终完全上线。
之所以叫作灰度发布,是因为它介于黑与白之间,并不是版本之间的直接切换,而是一个平滑过渡的过程。
AB Test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度,而我们平常所说的金丝雀部署也就是灰度发布的一种方式。
之所以又被叫作金丝雀发布,是因为金丝雀对瓦斯极其敏感,17 世纪时英国矿井工人会携带金丝雀下井,以便及时发现危险。这就与灰色发布过程中,先发布给一部分用户来测试相似,因而得名。
对于灰度发布来说,它的优点在于如果前期出问题影响范围很小,相对用户体验也少;可以做到及时发现、及时调整问题,影响范围可控。但是采取这种模式对自动化以及运维监控能力的要求非常高。
滚动发布
滚动发布是指每次只升级一个或多个服务,升级完成后加入生产环境,不断执行这个过程,直到集群中的全部旧版本升级新版本。
- 红色:正在更新的实例
- 蓝色:更新完成并加入集群的实例
- 绿色:正在运行的实例
这种部署方式相对于蓝绿部署,更加节约资源——它不需要运行两个集群、两倍的实例数。我们可以部分部署,例如每次只取出集群的20%进行升级,比较节约资源,但同时缺点也很明显:采用滚动发布方式部署时,没有一个确定OK的环境。如果使用蓝绿部署,我们能够清晰地知道老版本是OK的,而使用滚动发布,我们无法确定。并且一旦发布过程出现问题,需要回滚,回滚过程非常困难。
在实际工作当中,升级过程中需要保持服务的连续性、稳定,对外界无感知是几个基本的要求。在生产上选择哪种部署方法最合适?这取决于哪种方法最适合你的业务和技术需求。
部署Ingress Controller
选择合适的版本,本文使用的是Kubernetes 1.23版本,Ingress Controller版本选择为v1.6.4
如果有需要yaml文件的可以评论区@我
# 部署Ingress Controller
[root@master ingress-nginx]# cd
[root@master ~]# kubectl apply -f deploy.yaml
namespace/ingress-nginx created
serviceaccount/ingress-nginx created
serviceaccount/ingress-nginx-admission created
role.rbac.authorization.k8s.io/ingress-nginx created
role.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrole.rbac.authorization.k8s.io/ingress-nginx created
clusterrole.rbac.authorization.k8s.io/ingress-nginx-admission created
rolebinding.rbac.authorization.k8s.io/ingress-nginx created
rolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx created
clusterrolebinding.rbac.authorization.k8s.io/ingress-nginx-admission created
configmap/ingress-nginx-controller created
service/ingress-nginx-controller created
service/ingress-nginx-controller-admission created
deployment.apps/ingress-nginx-controller created
job.batch/ingress-nginx-admission-create created
job.batch/ingress-nginx-admission-patch created
ingressclass.networking.k8s.io/nginx created
validatingwebhookconfiguration.admissionregistration.k8s.io/ingress-nginx-admission created
部署两个版本的服务
V1版本
[root@master ~]# cat nginx_v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v1
spec:
replicas: 1
selector:
matchLabels:
app: nginx
version: v1
template:
metadata:
labels:
app: nginx
version: v1
spec:
containers:
- name: nginx
image: "openresty/openresty:centos"
imagePullPolicy: IfNotPresent
ports:
- name: http
protocol: TCP
containerPort: 80
volumeMounts:
- mountPath: /usr/local/openresty/nginx/conf/nginx.conf
name: config
subPath: nginx.conf
volumes:
- name: config
configMap:
name: nginx-v1
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app: nginx
version: v1
name: nginx-v1
data:
nginx.conf: |-
worker_processes 1;
events {
accept_mutex on;
multi_accept on;
use epoll;
worker_connections 1024;
}
http {
ignore_invalid_headers off;
server {
listen 80;
location / {
access_by_lua '
local header_str = ngx.say("nginx-v1")
';
}
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v1
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
name: http
selector:
app: nginx
version: v1
[root@master ~]# kubectl apply -f nginx_v1.yaml
deployment.apps/nginx-v1 created
configmap/nginx-v1 created
service/nginx-v1 created
V2版本
[root@master ~]# cat nginx_v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v2
spec:
replicas: 1
selector:
matchLabels:
app: nginx
version: v2
template:
metadata:
labels:
app: nginx
version: v2
spec:
containers:
- name: nginx
image: "openresty/openresty:centos"
imagePullPolicy: IfNotPresent
ports:
- name: http
protocol: TCP
containerPort: 80
volumeMounts:
- mountPath: /usr/local/openresty/nginx/conf/nginx.conf
name: config
subPath: nginx.conf
volumes:
- name: config
configMap:
name: nginx-v2
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
app: nginx
version: v2
name: nginx-v2
data:
nginx.conf: |-
worker_processes 1;
events {
accept_mutex on;
multi_accept on;
use epoll;
worker_connections 1024;
}
http {
ignore_invalid_headers off;
server {
listen 80;
location / {
access_by_lua '
local header_str = ngx.say("nginx-v2")
';
}
}
}
---
apiVersion: v1
kind: Service
metadata:
name: nginx-v2
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
name: http
selector:
app: nginx
version: v2
[root@master ~]# kubectl apply -f nginx_v2.yaml
deployment.apps/nginx-v2 created
configmap/nginx-v2 created
service/nginx-v2 created
创建Ingress指向V1版本
[root@master ~]# cat nginx_ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v1
port:
number: 80
[root@master ~]# kubectl apply -f nginx_ingress.yaml
ingress.networking.k8s.io/nginx created
访问测试
curl -H "Host: canary.example.com" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP
[root@master ~]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.1.139.131 <pending> 80:31357/TCP,443:30170/TCP 82s
ingress-nginx-controller-admission ClusterIP 10.1.212.45 <none> 443/TCP 82s
[root@master ~]# curl -H "Host: canary.example.com" http://10.1.139.131
nginx-v1
或者使用以下方法进行测试
[root@master ~]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.1.139.131 <pending> 80:31357/TCP,443:30170/TCP 4m27s
ingress-nginx-controller-admission ClusterIP 10.1.212.45 <none> 443/TCP 4m27s
[root@master ~]# kubectl describe svc -n ingress-nginx ingress-nginx-controller
Name: ingress-nginx-controller
Namespace: ingress-nginx
Labels: app.kubernetes.io/component=controller
app.kubernetes.io/instance=ingress-nginx
app.kubernetes.io/name=ingress-nginx
app.kubernetes.io/part-of=ingress-nginx
app.kubernetes.io/version=1.6.4
Annotations: <none>
Selector: app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx
Type: LoadBalancer
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.1.139.131
IPs: 10.1.139.131
Port: http 80/TCP
TargetPort: http/TCP
NodePort: http 31357/TCP
Endpoints: 192.168.207.166:80
Port: https 443/TCP
TargetPort: https/TCP
NodePort: https 30170/TCP
Endpoints: 192.168.207.166:443
Session Affinity: None
External Traffic Policy: Local
HealthCheck NodePort: 31952
Events: <none>
# 根据查到的Endpoints,找一台机器写入hosts解析
192.168.207.166 canary.example.com
# 在浏览器访问canary.example.com域名
基于 Header 的流量切分
创建 Canary Ingress,指定 v2 版本的后端服务,且加上一些 annotation,实现仅将带有名为 Region 且值为 cd 或 sz 的请求头的请求转发给当前 Canary Ingress,模拟灰度新版本给成都和深圳地域的用户。
[root@master ~]# cat nginx_canary.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: "Region"
nginx.ingress.kubernetes.io/canary-by-header-pattern: "cd|sz"
name: nginx-canary
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v2
port:
number: 80
[root@master ~]# kubectl apply -f nginx_canary.yaml
ingress.networking.k8s.io/nginx-canary created
访问测试
curl -H "Host: canary.example.com" -H "Region: cd" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP
[root@master ~]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.1.139.131 <pending> 80:31357/TCP,443:30170/TCP 12m
ingress-nginx-controller-admission ClusterIP 10.1.212.45 <none> 443/TCP 12m
# 可以看到,只有 header Region 为 cd 或 sz 的请求才由 v2 版本服务响应
[root@master ~]# curl -H "Host: canary.example.com" -H "Region: cd" 10.1.139.131
nginx-v2
[root@master ~]# curl -H "Host: canary.example.com" -H "Region: sz" 10.1.139.131
nginx-v2
[root@master ~]# curl -H "Host: canary.example.com" -H "Region: zz" 10.1.139.131
nginx-v1
[root@master ~]# curl -H "Host: canary.example.com" -H "Region: bj" 10.1.139.131
nginx-v1
基于 Cookie 的流量切分
与前面 Header 类似,不过使用 Cookie 就无法自定义 value 了,这里以模拟灰度成都地域用户为例,仅将带有名为 user_from_cd 的 cookie 的请求转发给当前 Canary Ingress 。先删除前面基于 Header 的流量切分的 Canary Ingress,然后创建下面新的 Canary Ingress。
[root@master ~]# cat nginx_canary.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: "user_from_cd"
name: nginx-canary
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v2
port:
number: 80
[root@master ~]# kubectl apply -f nginx_canary.yaml
ingress.networking.k8s.io/nginx-canary configured
访问测试
curl -s -H "Host: canary.example.com" --cookie "user_from_cd=always" http://EXTERNAL-IP # EXTERNAL-IP 替换为 Nginx Ingress 自身对外暴露的 IP
[root@master ~]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.1.139.131 <pending> 80:31357/TCP,443:30170/TCP 15m
ingress-nginx-controller-admission ClusterIP 10.1.212.45 <none> 443/TCP 15m
# 可以看到,只有 cookie user_from_cd 为 always 的请求才由 v2 版本的服务响应
[root@master ~]# curl -s -H "Host: canary.example.com" --cookie "user_from_cd=always" 10.1.139.131
nginx-v2
[root@master ~]# curl -s -H "Host: canary.example.com" --cookie "user_from_cd=never" 10.1.139.131
nginx-v1
基于服务权重的流量切分
基于服务权重的 Canary Ingress 就简单了,直接定义需要导入的流量比例,这里以导入 10% 流量到 v2 版本为例 (如果有,先删除之前的 Canary Ingress)
[root@master ~]# cat nginx_canary.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: nginx-canary
spec:
rules:
- host: canary.example.com
http:
paths:
- path: / #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
pathType: Prefix
backend: #配置后端服务
service:
name: nginx-v2
port:
number: 80
[root@master ~]# kubectl apply -f nginx_canary.yaml
ingress.networking.k8s.io/nginx-canary configured
访问测试
for i in {1..10}; do curl -H "Host: canary.example.com" http://EXTERNAL-IP; done;
[root@master ~]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.1.139.131 <pending> 80:31357/TCP,443:30170/TCP 22m
ingress-nginx-controller-admission ClusterIP 10.1.212.45 <none> 443/TCP 22m
# 可以看到,大概只有十分之一的几率由 v2 版本的服务响应,符合 10% 服务权重的设置,可以多访问几次试试
[root@master ~]# for i in {1..10}; do curl -H "Host: canary.example.com" http://10.1.139.131; done;
nginx-v2
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1
nginx-v1