Health Check 在 业务生产中滚动更新(rolling update)的应用场景
我们准备一个deployment资源的yaml文件

  1. # cat myapp-v1.yaml
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: mytest
  6. spec:
  7. replicas: 10 # 这里准备10个数量的pod
  8. selector:
  9. matchLabels:
  10. app: mytest
  11. template:
  12. metadata:
  13. labels:
  14. app: mytest
  15. spec:
  16. containers:
  17. - name: mytest
  18. image: busybox
  19. args:
  20. - /bin/sh
  21. - -c
  22. - sleep 10; touch /tmp/healthy; sleep 30000
  23. readinessProbe:
  24. exec:
  25. command:
  26. - cat
  27. - /tmp/healthy
  28. initialDelaySeconds: 10
  29. periodSeconds: 5

运行这个配置

  1. [root@master1 ~]# kubectl apply -f myapp-v1.yaml --record
  2. deployment.apps/mytest created
  3. [root@master1 ~]# kubectl get pod
  4. NAME READY STATUS RESTARTS AGE
  5. mytest-d9f48585b-2lmh2 1/1 Running 0 3m22s
  6. mytest-d9f48585b-5lh9l 1/1 Running 0 3m22s
  7. mytest-d9f48585b-cwb8l 1/1 Running 0 3m22s
  8. mytest-d9f48585b-f6tzc 1/1 Running 0 3m22s
  9. mytest-d9f48585b-hb665 1/1 Running 0 3m22s
  10. mytest-d9f48585b-hmqrw 1/1 Running 0 3m22s
  11. mytest-d9f48585b-jm8bm 1/1 Running 0 3m22s
  12. mytest-d9f48585b-kxm2m 1/1 Running 0 3m22s
  13. mytest-d9f48585b-lqpr9 1/1 Running 0 3m22s
  14. 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 值越大,初始销毁的旧副本数量就越多。
正常更新理想情况下,我们这次版本发布案例滚动更新的过程是:

  1. 首先创建4个新版本的pod,使副本总数量达到14个
  2. 然后再销毁3个旧版本的pod,使可用的副本数量降为7个
  3. 当这3个旧版本的pod被 成功销毁后,可再创建3个新版本的pod,使总的副本数量保持为14个
  4. 当新版本的pod通过Readiness 检测后,会使可用的pod副本数量增加超过7个
  5. 然后可以继续销毁更多的旧版本的pod,使整体可用的pod数量回到7个
  6. 随着旧版本的pod销毁,使pod副本总数量低于14个,这样就可以继续创建更多的新版本的pod
  7. 这个新增销毁流程会持续地进行,最终所有旧版本的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的魅力所在。