[TOC]
11 K8S pod 实现代码升级和回滚
11.1 传统架构代码升级
传统情况下一般在虚拟机进行代码升级与回滚,在公司内部都会采用 gitlab ,gitlab 主要是让开发将代码提交,所以这个 gitlab 的安全一定要做好,别到时候代码丢了也不能泄露。
有了 gitlab 之后我们做代码部署一般使用 Jenkins,有了 Jenkins 之后后期进行代码部署:
- 先执行代码 clone
- 代码 clone 之后就会通过 maven 进行编译
- 将编译好的包拷贝至虚拟机的(scp、ansible)一个新目录
- 拷贝过去之后如果需要对代码进行替换,切换软连接重启服务(将虚拟机下线,主要是担心用户的访问会转发到虚拟机上,从而导致虚拟机报错)
- 停掉对应服务
- 下线之后再将代码换掉,替换代码有两种方法:
- 将之前存储代码的目录清空,或者将之前存储代码的目录全删完然后在将目录替换
- 或者通过软连接的方式进行替换
- 启动服务,其实这时候就会使用的我们上一步加载的新代码
- 测试,因为服务起来之后为了确保能够正常访问所以需要提前测试(通过 curl 命令或者简单的 API 进行测试)
- 测试通过上线
- 然后在负载均衡中将需要升级的主机从负载均衡里面摘掉,然后升级完成在加回负载均衡,并做灰度发布,这样的话我在升级过程中是不影响用户访问的。如果在以此类推升级其他服务器
- 然后灰度发布,如上图有两个版本是 v1 有一个本版是新发布的 v2 ,即使有用户访问有问题也只能说影响那百分之 30 左右的用户,如果这时候 v2 版本 有问题就直接回滚为 v1 ,如果 v2 没有问题那我们直接将其他版本升级到 v2
- 没有问题之后部署到负载均衡
- 最后用户就能直接访问
11.2 K8S 架构代码升级
执行滚动更新:https://kubernetes.io/zh/docs/tutorials/kubernetes-basics/update/update-intro/
用户希望应用程序始终可用,而开发人员则需要每天多次部署它们的新版本。在 Kubernetes 中,这些是通过滚动更新(Rolling Updates)完成的。 滚动更新 允许通过使用新的实例逐步更新 Pod 实例,零停机进行 Deployment 更新。新的 Pod 将在具有可用资源的节点上进行调度。
在前面的模块中,我们将应用程序扩展为运行多个实例。这是在不影响应用程序可用性的情况下执行更新的要求。默认情况下,更新期间不可用的 pod 的最大值和可以创建的新 pod 数都是 1。这两个选项都可以配置为(pod)数字或百分比。 在 Kubernetes 中,更新是经过版本控制的,任何 Deployment 更新都可以恢复到以前的(稳定)版本。
在 K8S 中进行代码升级相对来说就比较简单,K8S 也是通过滚动升级来实现代码更新的,一般都是 3 个 pod 跑一个业务。
在升级过程中 svc 会把请求逐渐的转向新的。如上图这三个 pod 是一个服务,这时候我们想做升级的话(v1 到 v2),K8S 中他会先启动一个新的将其升级为 V2 版本,等 v2 版本起来之后将三个 pod 中的其中某一个删除,并且 svc 会将删除掉的 pod 的请求往 v2 pod 进行转发。
当这个删除掉老的 v1 之后会在启动一个 v2 的 pod ,并且这时在将一个 v1 版本的删除掉,然后在启 v2 pod。
所以通过这种方式来保证我们在代码升级中是可用的,不会说将 pod 全部删除而导致用户无法访问,从而实现了零停机进行控制器的版本更新。
滚动更新允许以下操作:
- 将应用程序从一个环境提升到另一个环境(通过容器镜像更新)
- 回滚到以前的版本
- 持续集成和持续交付应用程序,无需停机
11.3 Deployment 中代码更新和回滚
在指定的 deployment 中通过 kubect set image 指定新版本的 镜像:tag来实现更新代码的目的。
构建三个不同版本的 nginx 镜像,第一次使用 v1 版本, 后组逐渐升级到 v2 与 v3 , 测试镜像版本升级与回滚操作
deployment 控制器支持两种更新策略:默认为滚动更新
1.滚动更新(rolling update):
滚动更新是默认的更新策略,滚动更新是基于新版本镜像创建新版本 pod ,然后删除一部分旧版本pod, 然后再创建新版本 pod,再删除一部分旧版本 pod ,直到就版本 pod 删除完成, 滚动更新优势是在升级过程当中不会导致服务不可用,缺点是升级过程中会导致两个版本在短时间内会并存。
具体升级过程是在执行更新操作后 k8s 会再创建一个新版本的 ReplicaSet 控制器,在删除旧版本的 ReplicaSet 控制器下的 pod 的同时会在新版本的 ReplicaSet 控制器下创建新的 pod ,直到旧版本的 pod 全部被删除完后再把就版本的 ReplicaSet 控制器也回收掉。
在执行滚动更新的同时,为了保证服务的可用性,当前控制器内不可用的 pod (pod需要拉取镜像执行创建和执行探针探测期间是不可用的) 不能超出一定范围,因为需要至少保留一定数量的 pod 以保证服务可以被客户端正常访问,可以通过以下参数指定:
# kubectl explain deployment.spec.strategy
deployment.spec.strategy.rollingUpdate.maxSurge # 指定在升级期间pod总数可以超出定义好的期望的pod数的个数或者百分比,默认为 25% ,如果设置为 10%,假如当前是 100个 pod,那么升级时最多将创建 110 个 pod 即额外有 10% 的 pod 临时会超出当前(replicas )指定的副本数限制。
deployment.spec.strategy.rollingUpdate.maxUnavailable # 指定在升级期间最大不可用的 pod 数,可以是整数或者当前pod的百分比,默认是 25% ,假如当前是 100 个 pod , 那么升级时最多可以有 25个 (25%) pod 不可用,即还要 75个 (75%) pod是可用的。
# 注意:以上两个值不能同时为 0,如果 maxUnavailable 最大不可用 pod 为 0, maxSurge超出pod数也为0, 那么将会导致pod无法进行滚动更新。
2.重建更新(recreate):
先删除现有的 pod ,然后基于新版本的镜像重建,优势是同时只有一个版本在线,不会产生多版本在线问题,缺点是 pod 删除后到 pod 重建成功中间的时间会导致服务无法访问,因此较少使用。
deployment 会创建一个 replicaset ,而这个 replicaset 会维持这个 v1 版本的 pod 3副本
当执行 set image 的时候 deployment 会在创建一个新的 replicaset ,而这个 replicaset 维护的是 v2 版本的镜像 pod 3副本
11.3.1 升级镜像到指定版本
1.先编写一个测试的 yaml 文件
[11:09:32 root@k8s-master update]#vim nginx.yaml
apiVersion: v1
kind: Namespace
metadata:
name: web
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-dm
namespace: web
spec:
replicas: 3
selector:
matchLabels:
name: nginx
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx-containers
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
limits:
cpu: 1
memory: "512Mi"
requests:
cpu: 500m
memory: "512Mi"
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: web
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 31180
selector:
name: nginx
2.创建
[11:09:34 root@k8s-master update]#kubectl apply -f nginx.yaml
[11:31:55 root@k8s-master update]#kubectl get pod -n web
NAME READY STATUS RESTARTS AGE
nginx-dm-d5c788d6c-fqzpq 1/1 Running 0 5s
nginx-dm-d5c788d6c-hr7l6 1/1 Running 0 5s
nginx-dm-d5c788d6c-ps62g 1/1 Running 0 5s
[11:19:04 root@k8s-master update]#kubectl get svc -n web
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc NodePort 172.30.76.104 <none> 80:31180/TCP 9m47s
3.浏览器访问
我们可以看到这里采用的是官方的 nginx 1.21.3 的版本
4.我们在现在的终端中监控当前 web namespace 的 pod 状态
[15:23:43 root@k8s-master update]#kubectl get pod -n web -w
NAME READY STATUS RESTARTS AGE
nginx-dm-d5c788d6c-fqzpq 1/1 Running 0 5s
nginx-dm-d5c788d6c-hr7l6 1/1 Running 0 5s
nginx-dm-d5c788d6c-ps62g 1/1 Running 0 5s
# 当前的控制器 ID 是 d5c788d6c
5.更新为 nginx:1.16.1 版本
更新过程中 K8S 会先更新一部分,然后在更新一部分,知道滚动更新完
[15:02:30 root@k8s-master ~]#kubectl set image --record -n web deployment/nginx-dm nginx-containers=nginx:1.16.1
kubectl set image # 设置更新镜像
-n web deployment/nginx-dm # 对 web namespace 下 nginx-dm deployment 进行更新
nginx-containers # 对该容器的镜像进行更新
hub.zhangguiyuan.com/baseimage/nginx:v1test # 容器更新的镜像
--record: #该选项可以记录命令,我们可以很方便的查看每次 revision 的变化
6.当执行完上面的上面的这条命令之后会发现原有的d5c788d6c
这个控制器将会被替换
[15:23:43 root@k8s-master update]#kubectl get pod -n web -w
NAME READY STATUS RESTARTS AGE
nginx-dm-d5c788d6c-fqzpq 1/1 Running 0 13m
nginx-dm-d5c788d6c-hr7l6 1/1 Running 0 13m
nginx-dm-d5c788d6c-wwf4p 1/1 Running 0 13m
nginx-dm-7495fb94fb-9k48l 0/1 Pending 0 0s
nginx-dm-7495fb94fb-9k48l 0/1 Pending 0 0s
nginx-dm-7495fb94fb-9k48l 0/1 ContainerCreating 0 0s
nginx-dm-7495fb94fb-9k48l 1/1 Running 0 2s
nginx-dm-d5c788d6c-wwf4p 1/1 Terminating 0 14m
nginx-dm-7495fb94fb-4wmh4 0/1 Pending 0 0s
nginx-dm-7495fb94fb-4wmh4 0/1 Pending 0 0s
nginx-dm-7495fb94fb-4wmh4 0/1 ContainerCreating 0 0s
nginx-dm-d5c788d6c-wwf4p 0/1 Terminating 0 14m
nginx-dm-7495fb94fb-4wmh4 1/1 Running 0 2s
nginx-dm-d5c788d6c-fqzpq 1/1 Terminating 0 14m
nginx-dm-7495fb94fb-bg2fr 0/1 Pending 0 0s
nginx-dm-7495fb94fb-bg2fr 0/1 Pending 0 0s
nginx-dm-7495fb94fb-bg2fr 0/1 ContainerCreating 0 0s
nginx-dm-d5c788d6c-wwf4p 0/1 Terminating 0 14m
nginx-dm-d5c788d6c-wwf4p 0/1 Terminating 0 14m
nginx-dm-d5c788d6c-fqzpq 0/1 Terminating 0 14m
nginx-dm-d5c788d6c-fqzpq 0/1 Terminating 0 14m
nginx-dm-d5c788d6c-fqzpq 0/1 Terminating 0 14m
nginx-dm-7495fb94fb-bg2fr 1/1 Running 0 28s
nginx-dm-d5c788d6c-hr7l6 1/1 Terminating 0 14m
nginx-dm-d5c788d6c-hr7l6 0/1 Terminating 0 14m
nginx-dm-d5c788d6c-hr7l6 0/1 Terminating 0 14m
nginx-dm-d5c788d6c-hr7l6 0/1 Terminating 0 14m
# 从而现在运行的是 88rbh qfkcl b2jxr 这 3 个 pod
[15:26:55 root@k8s-master update]#kubectl get pod -n web
NAME READY STATUS RESTARTS AGE
nginx-dm-7495fb94fb-88rbh 1/1 Running 0 3m1s
nginx-dm-7495fb94fb-b2jxr 1/1 Running 0 2m57s
nginx-dm-7495fb94fb-qfkcl 1/1 Running 0 2m59s
# 当前的控制器是 7495fb94fb
7.通过浏览器 F12 访问 nginx 版本已经替换为了 1.16.1
11.3.2 RS 验证
我们都知道每一个 RS 控制器其实他管理的就是对应的镜像信息
1.通过查看 RS 我们会发现他有两个
[15:46:22 root@k8s-master update]#kubectl get rs -n web
NAME DESIRED CURRENT READY AGE
nginx-dm-7495fb94fb 3 3 3 10m
nginx-dm-d5c788d6c 0 0 0 13m
2.也能通过 rollout history
查看历史版本
[15:56:53 root@k8s-master update]#kubectl rollout history -n web deployment nginx-dm
deployment.apps/nginx-dm
REVISION CHANGE-CAUSE
1 <none>
2 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.16.1 --record=true --namespace=web
REVISION: #标识历史版本,现有 1、2 版本
CHANGE-CAUSE: #因为在创建的时候添加 record 参数,所以查看每次 revision 的变化,如果我们在创建的时候没有加 record 这里就会显示为 none
kubectl rollout history: #查看控制器历史版本
deployment nginx-dm: #查看 deployment控制器的nginx 这个的历史版本
3.我们可以看到现在只有两个 rs ,如果我这里在升级一个镜像,只要镜像版本不一样他就会在多一个 RS
[15:45:55 root@k8s-master ~]#kubectl set image --record -n web deployment/nginx-dm nginx-containers=nginx:1.18.0
# 这里我将容器镜像换成了 1.18.0
4.验证发现这次的 RS 就有 3 个
因为每次更新镜像发生变化的话就会创建一个新的 RS 控制器
[15:59:09 root@k8s-master update]#kubectl rollout history -n web deployment nginx-dm
deployment.apps/nginx-dm
REVISION CHANGE-CAUSE
1 <none>
2 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.16.1 --record=true --namespace=web
3 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.18.0 --record=true --namespace=web
5.查看 pod 对应的 RS id 也变为了 64d9ccc4f7
[16:04:46 root@k8s-master update]#kubectl get pod -n web
NAME READY STATUS RESTARTS AGE
nginx-dm-64d9ccc4f7-k6xt7 1/1 Running 0 2m7s
nginx-dm-64d9ccc4f7-np87g 1/1 Running 0 2m36s
nginx-dm-64d9ccc4f7-tg6wf 1/1 Running 0 2m4s
6.浏览器访问,版本更新为了 1.18.0
所以每次我们更新完镜像之后都会有 RS 控制器被创建出来,而这个 RS 控制器也可以用来做版本控制、版本回滚,由此引出了下面的镜像回滚
11.3.3 镜像回滚
假如现在我们的版本出现了问题,就可以使用下面的这条命令滚回到之前的旧状态。它默认会回滚到之前的这么一个老旧版本上,而且这个回滚不再需要重新构建镜像
1.执行下面命令实现回滚
[16:04:52 root@k8s-master update]# kubectl rollout undo -n web deployment nginx-dm
2.会发现他已经从 64d9ccc4f7 的 RS 回滚到了 7495fb94fb
[16:10:38 root@k8s-master update]#kubectl get pod -n web -w
NAME READY STATUS RESTARTS AGE
nginx-dm-64d9ccc4f7-k6xt7 0/1 Terminating 0 7m55s
nginx-dm-64d9ccc4f7-np87g 0/1 Terminating 0 8m24s
nginx-dm-7495fb94fb-jm2s4 1/1 Running 0 13s
nginx-dm-7495fb94fb-lcl88 1/1 Running 0 11s
nginx-dm-7495fb94fb-qm9pc 1/1 Running 0 9s
3、通过kubectl get rs
查看我们的 RS 已经回滚至以前的旧版本 rs中
[16:15:32 root@k8s-master update]#kubectl get rs -n web
NAME DESIRED CURRENT READY AGE
nginx-dm-64d9ccc4f7 0 0 0 13m
nginx-dm-7495fb94fb 3 3 3 29m # 回滚至该 RS 中
nginx-dm-d5c788d6c 0 0 0 31m
4.查看 rollout history
,现在已经更新到 4 版本,由于镜像采用的是 nginx 1.16.1 所以 RS 中的 2 版本也替换到了 4 版本,只要镜像一样就会修改 version id
[16:15:38 root@k8s-master update]#kubectl rollout history -n web deployment nginx-dm
deployment.apps/nginx-dm
REVISION CHANGE-CAUSE
1 <none>
3 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.18.0 --record=true --namespace=web
4 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.16.1 --record=true --namespace=web
4.浏览器访问镜像版本已经回滚
5.如果这里我在执行一次镜像回滚,就会回滚到 nginx:1.18.0 的镜像版本中,因为 1.18.0 的 version id 为 3
[16:24:44 root@k8s-master update]#kubectl rollout undo -n web deployment nginx-dm
6.浏览器访问
7.查看 rollout history
已经替换到了 nginx:1.18.0
[16:30:16 root@k8s-master update]#kubectl rollout history -n web deployment nginx-dm
deployment.apps/nginx-dm
REVISION CHANGE-CAUSE
1 <none>
4 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.16.1 --record=true --namespace=web
5 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.18.0 --record=true --namespace=web
所以说 undo 只能够在最近的两个版本之间进行回滚
11.3.4 跨版本回滚
因为在上面的案例中我们可以看到 undo 只能够在最近的两个版本之间进行回滚,但是如果我们像跨版本回滚就可以通过 --to-revision
参数来实现
如果当我们升级一个版本,但是更新后的版本可能有问题,这时候还想通过更新来解决由此更新到了第三个版本,可是当我们更新到了第三个版本之后发现依旧有问题而且时间也不足以更新到第 4 个版本,这时候我们只能回滚到最初的那个 v1 版本
1.查看当前版本 revision=5
[16:38:59 root@k8s-master update]#kubectl rollout history -n web deployment nginx-dm
deployment.apps/nginx-dm
REVISION CHANGE-CAUSE
1 <none>
4 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.16.1 --record=true --namespace=web
5 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.18.0 --record=true --namespace=web
2.这里我想回滚至 revision=1
的版本
[16:39:08 root@k8s-master update]#kubectl rollout undo -n web deployment nginx-dm --to-revision=1
# --to-revision=1 # 指定回滚至 1 版本
3.查看 rs
[16:41:45 root@k8s-master update]#kubectl rollout history -n web deployment nginx-dm
deployment.apps/nginx-dm
REVISION CHANGE-CAUSE
4 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.16.1 --record=true --namespace=web
5 kubectl set image deployment/nginx-dm nginx-containers=nginx:1.18.0 --record=true --namespace=web
6 <none> # 就回到了最初始化的版本中
4.nginx 版本为该 RS 最初的那个
11.4 K8S 中实现灰度发布
我们在 11.3 的几个案例中可以看到 K8S 默认一次性将所有的 pod 都实现了更新,那么当我们想通过一部分发布一部分不发布,就可以采用下面案例
在 K8S 中有两种方式实现灰度发布:
- 在升级的时候暂停更新,先升级第一部分,K8S 会计算出来当前的 deployment 中有几个 pod 然后基于 pod 总数的 25% 标准进行更新,如果说新发布的 pod 版本有问题就将其回滚,如果新发布的没有问题,将其剩下的 pod 全部升级
11.4.1 基于暂停实现灰度发布
这里采用的镜像是 nginx:1.16.1 和 nginx:1.18.0 ,通过这两个镜像来实现类似于灰度发布的效果,我想从 1.16.1 升级到 1.18.0 但是我还不想一次性升级完毕,而是只升级一部分
1.编写 yaml
[17:03:37 root@k8s-master update]#vim nginx.yaml
apiVersion: v1
kind: Namespace
metadata:
name: web
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-dm
namespace: web
spec:
replicas: 6
selector:
matchLabels:
name: nginx
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx-containers
image: nginx:1.16.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
limits:
cpu: 1
memory: "512Mi"
requests:
cpu: 500m
memory: "512Mi"
---
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: web
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 31180
selector:
name: nginx
2.运行
[17:04:30 root@k8s-master update]#kubectl apply -f nginx.yaml
[17:23:16 root@k8s-master update]#kubectl get deployments.apps -n web
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-dm 6/6 6 6 16m
# 并且我们可以看到当前的 rs 都是 7495fb94fb
[17:29:18 root@k8s-master ~]#kubectl get po -n web
NAME READY STATUS RESTARTS AGE
nginx-dm-7495fb94fb-gng7x 1/1 Running 0 23m
nginx-dm-7495fb94fb-g6vjr 1/1 Running 0 23m
nginx-dm-7495fb94fb-j85sd 1/1 Running 0 23m
nginx-dm-7495fb94fb-l4psn 1/1 Running 0 23m
nginx-dm-7495fb94fb-mbq8z 1/1 Running 0 23m
nginx-dm-7495fb94fb-w46jn 1/1 Running 0 23m
3.浏览器验证 nginx 当前是 1.16.1
3.现在 nginx:1.16.1 的 pod 创建好了,那么我再升级至 1.18.0
在升级的时候我们基于暂停来实现灰度发布
# 镜像升级至 1.18.0
[17:23:31 root@k8s-master update]#kubectl set image deployment/nginx-dm nginx-containers=nginx:1.18.0 --record=true --namespace=web
deployment.apps/nginx-dm image updated
# 这条命令在执行完 set image 之后立即执行暂停
[17:29:09 root@k8s-master update]#kubectl rollout pause -n web deployment nginx-dm
deployment.apps/nginx-dm paused
4.查看当前 pod ,就会发现有所替换
[17:29:10 root@k8s-master update]#kubectl get pod -n web
NAME READY STATUS RESTARTS AGE
nginx-dm-64d9ccc4f7-dzvjj 1/1 Running 0 116m
nginx-dm-64d9ccc4f7-gng7x 1/1 Running 0 116m
nginx-dm-64d9ccc4f7-xnpl8 0/1 Pending 0 116m
nginx-dm-7495fb94fb-g6vjr 1/1 Running 0 138m
nginx-dm-7495fb94fb-j85sd 1/1 Running 0 138m
nginx-dm-7495fb94fb-l4psn 1/1 Running 0 138m
nginx-dm-7495fb94fb-mbq8z 1/1 Running 0 138m
nginx-dm-7495fb94fb-w46jn 1/1 Running 0 138m
# 并且可以看到现在一共起了 8 个 pod ,5 个旧的 7495fb94fb ,然后三个新的 64d9ccc4f7
5.通过 curl 命令获取请求报文信息,我们会看到有 4 个请求会被调度给 nginx:1.16.1 有两个请求会被调度到 nginx:1.18.0
如果说访问的时候发现业务代码有问题了那我们就赶紧 undo
,如果说发布的没有问题那我们就直接取消暂停
6.取消暂停
[21:00:13 root@k8s-master ~]#kubectl rollout resume -n web deployment nginx-dm
7.可以看到取消暂停之后他的 rs 就变为了64d9ccc4f7
,就是将剩下的那些版本都替换为了我们需要发布的版本
[21:01:47 root@k8s-master ~]#kubectl get pod -n web
NAME READY STATUS RESTARTS AGE
nginx-dm-64d9ccc4f7-2vzgf 1/1 Running 0 19s
nginx-dm-64d9ccc4f7-5lh2q 1/1 Running 0 34s
nginx-dm-64d9ccc4f7-dzvjj 1/1 Running 0 3h32m
nginx-dm-64d9ccc4f7-gng7x 1/1 Running 0 3h32m
nginx-dm-64d9ccc4f7-szswr 1/1 Running 0 34s
nginx-dm-64d9ccc4f7-xnpl8 1/1 Running 0 3h32m
11.4.2 通过 SVC 实现灰度发布
除了暂停更新和恢复更新的另外一种灰度发布方法,我们在升级的时候先写一个 yaml 文件,用新的 yaml 文件来部署我们的 pod ,通过这种方式来控制我们的 pod 副本
实现机制也是 pod 两个版本需要同时在线,用户的访问既能访问到旧版本也能访问到新版本。
让两个版本并行运行一段时间如果没有问题我就直接替换为新的版本
1.编写 namespace 文件
[21:19:51 root@k8s-master update]#vim ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: web
[21:20:22 root@k8s-master update]#kubectl apply -f ns.yaml
2.编写测试 yaml 文件,这个 yaml 文件采用的是 nginx:1.16.1 得镜像,作为旧版本
[21:16:00 root@k8s-master update]#vim nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-dm
namespace: web
spec:
replicas: 6
selector:
matchLabels:
name: nginx # 老版本和新版本公共标签
template:
metadata:
labels:
name: nginx
spec:
containers:
- name: nginx-containers
image: nginx:1.16.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
limits:
cpu: 1
memory: "512Mi"
requests:
cpu: 500m
memory: "512Mi"
[21:20:55 root@k8s-master update]#kubectl apply -f nginx.yaml
3.编写 svc yaml
[21:16:00 root@k8s-master update]#vim svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: web
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 31180
selector:
name: nginx # 匹配两个 deployment 中的标签
[21:21:49 root@k8s-master update]#kubectl apply -f svc.yaml
4.编写 nginx-v2 yaml 文件,这个作为需要灰度发布的版本,这里nginx:1.18.0
[21:30:49 root@k8s-master update]#vim nginx-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v2
namespace: web
spec:
replicas: 1
selector:
matchLabels:
version: v2 # 用于区分我们的发布版本
name: nginx # 老版本和新版本公共标签
template:
metadata:
labels:
version: v2 # 用于区分我们的发布版本
name: nginx
spec:
containers:
- name: nginx-containers
image: nginx:1.18.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
limits:
cpu: 1
memory: "512Mi"
requests:
cpu: 500m
memory: "512Mi"
[21:35:32 root@k8s-master update]#kubectl apply -f nginx-v2.yaml
查看 ep
[21:49:18 root@k8s-master update]#kubectl get ep -n web
NAME ENDPOINTS AGE
nginx-svc 10.10.113.185:80,10.10.113.186:80,10.10.169.156:80 + 4 more... 27m
# 会发现一共有 7 个 EP
5.客户端编写循环访问验证
可以看到当前访问的 nginx 版本 6 次为 nginx/1.16.1 ,一次为 nginx/1.18.0
[21:29:46 root@k8s-node ~]#while true ; do curl -I http://10.0.0.131:31180/ | grep nginx ;sleep 1 ; done
如果后期我们发布的例如 nginx/1.18.0 版本没有问题,那么我们就修改 svc,添加 v2 标签,并注释掉老版本和新版本公共标签
6.修改 svc yaml 文件
[21:37:45 root@k8s-master update]#vim svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: web
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
nodePort: 31180
selector:
# name: nginx
version: v2
[21:41:27 root@k8s-master update]#kubectl apply -f svc.yaml
现在只有一个 ep 能够满足,因为现在 svc 只能够匹配 version: v2 标签的 pod
[21:50:35 root@k8s-master update]#kubectl get ep -n web
NAME ENDPOINTS AGE
nginx-svc 10.10.169.156:80 28m
7.再次通过客户端访问,可以看到现在全部调度到了 nginx/1.18.0
[21:37:57 root@k8s-node ~]#while true ; do curl -I http://10.0.0.131:31180/ | grep nginx ;sleep 1 ; done
8.如果说我们替换的代码没有问题,那么我们现在就可以修改新版本的 yaml 文件,将 pod 副本数改为对应的发布情况即可
[21:50:37 root@k8s-master update]#vim nginx-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-v2
namespace: web
spec:
replicas: 6 # 将副本数改为 6
发布
[21:57:49 root@k8s-master update]#kubectl apply -f nginx-v2.yaml
发布完成之后在将 nginx/1.16.1 版本 delete 即可
[21:58:15 root@k8s-master update]#kubectl delete -f nginx.yaml
以上就是通过控制 svc 实现了灰度发布