Health Check 在 业务生产中滚动更新(rolling update)的应用场景
我们准备一个deployment资源的yaml文件
# cat myapp-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mytest
spec:
replicas: 10 # 这里准备10个数量的pod
selector:
matchLabels:
app: mytest
template:
metadata:
labels:
app: mytest
spec:
containers:
- name: mytest
image: busybox
args:
- /bin/sh
- -c
- sleep 10; touch /tmp/healthy; sleep 30000
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
运行这个配置
[root@master1 ~]# kubectl apply -f myapp-v1.yaml --record
deployment.apps/mytest created
[root@master1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mytest-d9f48585b-2lmh2 1/1 Running 0 3m22s
mytest-d9f48585b-5lh9l 1/1 Running 0 3m22s
mytest-d9f48585b-cwb8l 1/1 Running 0 3m22s
mytest-d9f48585b-f6tzc 1/1 Running 0 3m22s
mytest-d9f48585b-hb665 1/1 Running 0 3m22s
mytest-d9f48585b-hmqrw 1/1 Running 0 3m22s
mytest-d9f48585b-jm8bm 1/1 Running 0 3m22s
mytest-d9f48585b-kxm2m 1/1 Running 0 3m22s
mytest-d9f48585b-lqpr9 1/1 Running 0 3m22s
mytest-d9f48585b-pk75z 1/1 Running 0 3m22s
接着我们来准备更新这个服务,并且人为模拟版本故障来进行观察,新准备一个配置myapp-v2.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# cat myapp-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mytest
spec:
strategy:
rollingUpdate:
maxSurge: 35% # 滚动更新的副本总数最大值(以10的基数为例):10 + 10 * 35% = 13.5 --> 14
maxUnavailable: 35% # 可用副本数最大值(默认值两个都是25%): 10 - 10 * 35% = 6.5 --> 7
replicas: 10
selector:
matchLabels:
app: mytest
template:
metadata:
labels:
app: mytest
spec:
containers:
- name: mytest
image: busybox
args:
- /bin/sh
- -c
- sleep 30000 # 可见这里并没有生成/tmp/healthy这个文件,所以下面的检测必然失败
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
很明显这里因为我们更新的这个v2版本里面不会生成/tmp/healthy文件,那么自动是无法通过Readiness 检测的,详情如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
[root@master1 ~]# kubectl apply -f myapp-v2.yaml
deployment.apps/mytest configured
[root@master1 ~]# kubectl get deployment mytest
NAME READY UP-TO-DATE AVAILABLE AGE
mytest 7/10 7 7 28m
# READY 现在正在运行的只有7个pod
# UP-TO-DATE 表示当前已经完成更新的副本数:即 7 个新副本
# AVAILABLE 表示当前处于 READY 状态的副本数
[root@master1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mytest-5ddcd59f46-69mjv 0/1 Running 0 26m
mytest-5ddcd59f46-7f4jn 0/1 Running 0 26m
mytest-5ddcd59f46-946m8 0/1 Running 0 26m
mytest-5ddcd59f46-9l9kc 0/1 Running 0 26m
mytest-5ddcd59f46-q4cjj 0/1 Running 0 26m
mytest-5ddcd59f46-r8rms 0/1 Running 0 26m
mytest-5ddcd59f46-szhpc 0/1 Running 0 26m
mytest-8658bfffb7-dzt6d 1/1 Running 0 29m
mytest-8658bfffb7-gmh2m 1/1 Running 0 29m
mytest-8658bfffb7-gqp46 1/1 Running 0 29m
mytest-8658bfffb7-gxqvq 1/1 Running 0 29m
mytest-8658bfffb7-n2rpl 1/1 Running 0 29m
mytest-8658bfffb7-r8dvc 1/1 Running 0 29m
mytest-8658bfffb7-rsl6j 1/1 Running 0 29m
# 上面可以看到,由于 Readiness 检测一直没通过,所以新版本的pod都是Not ready状态的,这样就保证了错误的业务代码不会被外界请求到
[root@master1 ~]# kubectl describe deployment mytest
# 下面截取一些这里需要的关键信息
......
Replicas: 10 desired | 7 updated | 14 total | 7 available | 7 unavailable
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 5m55s deployment-controller Scaled up replica set mytest-d9f48585b to 10
Normal ScalingReplicaSet 3m53s deployment-controller Scaled up replica set mytest-7657789bc7 to 4 # 启动4个新版本的pod
Normal ScalingReplicaSet 3m53s deployment-controller Scaled down replica set mytest-d9f48585b to 7 # 将旧版本pod数量降至7
Normal ScalingReplicaSet 3m53s deployment-controller Scaled up replica set mytest-7657789bc7 to 7 # 新增3个启动至7个新版本
综合上面的分析,我们很真实的模拟一次K8s上次错误的代码上线流程,所幸的是这里有Health Check的Readiness检测帮我们屏蔽了有错误的副本,不至于被外面的流量请求到,同时保留了大部分旧版本的pod,因此整个服务的业务并没有因这此更新失败而受到影响。
接下来我们详细分析下滚动更新的原理,为什么上面服务新版本创建的pod数量是7个,同时只销毁了3个旧版本的pod呢?
原因就在于这段配置:
我们不显式配置这段的话,默认值均是25%
1
2
3
4
strategy:
rollingUpdate:
maxSurge: 35%
maxUnavailable: 35%
滚动更新通过参数maxSurge和maxUnavailable来控制pod副本数量的更新替换。
maxSurge
这个参数控制滚动更新过程中pod副本总数超过设定总副本数量的上限。maxSurge 可以是具体的整数(比如 3),也可以是百分比,向上取整。maxSurge 默认值为 25%
在上面测试的例子里面,pod的总副本数量是10,那么在更新过程中,总副本数量的上限大最值计划公式为:
10 + 10 * 35% = 13.5 —> 14
我们查看下更新deployment的描述信息:
Replicas: 10 desired | 7 updated | 14 total | 7 available | 7 unavailable
旧版本available 的数量7个 + 新版本unavailable`的数量7个 = 总数量 14 total
maxUnavailable
这个参数控制滚动更新过程中不可用的pod副本总数量的值,同样,maxUnavailable 可以是具体的整数(比如 3),也可以是百分百,向下取整。maxUnavailable 默认值为 25%。
在上面测试的例子里面,pod的总副本数量是10,那么要保证正常可用的pod副本数量为:
10 - 10 * 35% = 6.5 —> 7
所以我们在上面查看的描述信息里,7 available 正常可用的pod数量值就为7
maxSurge 值越大,初始创建的新副本数量就越多;maxUnavailable 值越大,初始销毁的旧副本数量就越多。
正常更新理想情况下,我们这次版本发布案例滚动更新的过程是:
- 首先创建4个新版本的pod,使副本总数量达到14个
- 然后再销毁3个旧版本的pod,使可用的副本数量降为7个
- 当这3个旧版本的pod被 成功销毁后,可再创建3个新版本的pod,使总的副本数量保持为14个
- 当新版本的pod通过Readiness 检测后,会使可用的pod副本数量增加超过7个
- 然后可以继续销毁更多的旧版本的pod,使整体可用的pod数量回到7个
- 随着旧版本的pod销毁,使pod副本总数量低于14个,这样就可以继续创建更多的新版本的pod
- 这个新增销毁流程会持续地进行,最终所有旧版本的pod会被新版本的pod逐渐替换,整个滚动更新完成
而我们这里的实际情况是在第4步就卡住了,新版本的pod数量无法能过Readiness 检测。上面的描述信息最后面的事件部分的日志也详细说明了这一切:
1
2
3
4
5
6
7
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 5m55s deployment-controller Scaled up replica set mytest-d9f48585b to 10
Normal ScalingReplicaSet 3m53s deployment-controller Scaled up replica set mytest-7657789bc7 to 4 # 启动4个新版本的pod
Normal ScalingReplicaSet 3m53s deployment-controller Scaled down replica set mytest-d9f48585b to 7 # 将旧版本pod数量降至7
Normal ScalingReplicaSet 3m53s deployment-controller Scaled up replica set mytest-7657789bc7 to 7 # 新增3个启动至7个新版本
这里按正常的生产处理流程,在获取足够的新版本错误信息提交给开发分析后,我们可以通过kubectl rollout undo 来回滚到上一个正常的服务版本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 先查看下要回滚版本号前面的数字,这里为1
[root@master1 ~]# kubectl rollout history deployment mytest
deployment.apps/mytest
REVISION CHANGE-CAUSE
1 kubectl apply --filename=myapp-v1.yaml --record=true
2 kubectl apply --filename=myapp-v2.yaml --record=true
[root@master1 ~]# kubectl rollout undo deployment mytest --to-revision=1
deployment.apps/mytest rolled back
[root@master1 ~]# kubectl get deployment mytest
NAME READY UP-TO-DATE AVAILABLE AGE
mytest 10/10 10 10 34m
[root@master1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mytest-8658bfffb7-74mmf 1/1 Running 0 113s
mytest-8658bfffb7-dzt6d 1/1 Running 0 34m
mytest-8658bfffb7-gmh2m 1/1 Running 0 34m
mytest-8658bfffb7-gqp46 1/1 Running 0 34m
mytest-8658bfffb7-gxqvq 1/1 Running 0 34m
mytest-8658bfffb7-n2rpl 1/1 Running 0 34m
mytest-8658bfffb7-r8dvc 1/1 Running 0 34m
mytest-8658bfffb7-rpp2c 1/1 Running 0 113s
mytest-8658bfffb7-rsl6j 1/1 Running 0 34m
mytest-8658bfffb7-zn4zx 1/1 Running 0 113s
OK,到这里为止,我们真实的模拟了一次有问题的版本发布及回滚,并且可以看到,在这整个过程中,虽然出现了问题,但我们的业务依然是没有受到任何影响的,这就是K8s的魅力所在。