试想一个场景
现在有一个正常运行的多副本应用,要对应用进行更新(比如更新image),Kubernetes会启动新副本来代理旧副本,然后发生了以下事件:
1、正常情况下新副本需要10s左右来完成准备工作,在此之前无法响应更新后的业务请求。
2、但由于人为配置错误,副本始终无法完成准备工作(比如无法连接后端数据库)
如果在没有配置Health Check的情况下,容器是可以正常运行的,只是无法提供正常服务,当所有的旧副本被新副本更新完成替代后,这个环境是不可用的,无法提供服务,如果发生在生产环境中,后果很严重。
相反,如果配置了Health Check的Readiness探测,只有通过这个探测才会被添加到Service中,如果没有通过探测,则旧副本继续运行,不影响生产业务。
模拟多副本应用的滚动更新故障
[root@node1 ~]# vim app.v1.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
selector:
matchLabels:
run: app
replicas: 10
template:
metadata:
labels:
run: app
spec:
containers:
- name: app
image: busybox
args:
- /bin/sh
- -c
- sleep 10; touch /tmp/healthy; sleep 300000
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
因为一会要模拟滚动更新,所以复制一个文件进行小改动
[root@node1 ~]# scp app.v1.yml app.v2.yml
[root@node1 ~]# vim app.v2.yml
# 修改args部分
args:
- /bin/sh
- -c
- sleep 300000
先运行v1的正确的pod
[root@node1 ~]# kubectl apply -f app.v1.yml --record
查看10个Pod
[root@node1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
app-688f655f7b-2clr7 1/1 Running 0 4m52s
app-688f655f7b-2zdkc 1/1 Running 0 4m52s
app-688f655f7b-74f9d 1/1 Running 0 4m52s
app-688f655f7b-9zrdj 1/1 Running 0 4m52s
app-688f655f7b-bbh8p 1/1 Running 0 4m52s
app-688f655f7b-gh5xn 1/1 Running 0 4m52s
app-688f655f7b-htsd2 1/1 Running 0 4m52s
app-688f655f7b-jx6rx 1/1 Running 0 4m52s
app-688f655f7b-ml99l 1/1 Running 0 4m52s
app-688f655f7b-tf22x 1/1 Running 0 4m52s
这个运行是没有问题的。现在使用v2进行滚动更新,v2的健康检查是肯定会失败的。
[root@node1 ~]# kubectl apply -f app.v2.yml --record
deployment.apps/app configured
查看Pod更新,准备删除2个旧副本,运行5个新副本
[root@node1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
app-647855b8dc-7lnjs 0/1 ContainerCreating 0 12s
app-647855b8dc-c5cjd 0/1 ContainerCreating 0 12s
app-647855b8dc-jfqqs 0/1 ContainerCreating 0 12s
app-647855b8dc-kjcph 0/1 ContainerCreating 0 12s
app-647855b8dc-zctt7 0/1 ContainerCreating 0 12s
app-688f655f7b-2clr7 1/1 Running 0 7m29s
app-688f655f7b-2zdkc 1/1 Running 0 7m29s
app-688f655f7b-74f9d 1/1 Running 0 7m29s
app-688f655f7b-9zrdj 1/1 Running 0 7m29s
app-688f655f7b-bbh8p 1/1 Running 0 7m29s
app-688f655f7b-gh5xn 1/1 Running 0 7m29s
app-688f655f7b-htsd2 1/1 Terminating 0 7m29s
app-688f655f7b-jx6rx 1/1 Running 0 7m29s
app-688f655f7b-ml99l 1/1 Running 0 7m29s
app-688f655f7b-tf22x 1/1 Terminating 0 7m29s
查看Deployment
[root@node1 ~]# kubectl get deployments app
NAME READY UP-TO-DATE AVAILABLE AGE
app 8/10 5 8 8m16s
一段时间后,一共13个Pod在运行,其中5个新副本的READY不是正常的
[root@node1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
app-647855b8dc-7lnjs 0/1 Running 0 2m31s
app-647855b8dc-c5cjd 0/1 Running 0 2m31s
app-647855b8dc-jfqqs 0/1 Running 0 2m31s
app-647855b8dc-kjcph 0/1 Running 0 2m31s
app-647855b8dc-zctt7 0/1 Running 0 2m31s
app-688f655f7b-2clr7 1/1 Running 0 9m48s
app-688f655f7b-2zdkc 1/1 Running 0 9m48s
app-688f655f7b-74f9d 1/1 Running 0 9m48s
app-688f655f7b-9zrdj 1/1 Running 0 9m48s
app-688f655f7b-bbh8p 1/1 Running 0 9m48s
app-688f655f7b-gh5xn 1/1 Running 0 9m48s
app-688f655f7b-jx6rx 1/1 Running 0 9m48s
app-688f655f7b-ml99l 1/1 Running 0 9m48s
证明新的副本一直没有通过Readiness的探测
这就很好的预防了没有健康检查时,滚动更新之后影响生产业务的麻烦,当然是用回滚也是可以预防的。
滚动更新副本更换的比例设置
在上面的滚动更新中发现在启动5个新副本的同时,停掉了2个旧副本,导致了后面运行了13个副本,5个新的,8个旧的。这个数字是怎么计算的。
之前在使用kubectl describe pod xxx
的时候有看到过的两个参数maxSurge
和maxUnavailable
[root@node1 ~]# kubectl describe deployments app | grep max
RollingUpdateStrategy: 25% max unavailable, 25% max surge
maxSurge
这个参数控制滚动更新过程中副本总数超过期望值(DESIRED)的上限。maxSurge
可以是具体的整数(比如:3),也可以是百分比,向上取整。默认为25%
在上面的例子中,期望值为10,副本总数的最大值为:
10+10*25%=13,所以即使更新失败后的总数也是13
maxUnavailable
这个参数控制滚动更新过程中,不可用的副本占期望值的最大比例,也就是当滚动更新时,首先停掉几个旧副本。maxUnavailable
可以是具体的整数(比如 3),也可以是百分百,向下取整。maxUnavailable
默认值为 25%。
上面的例子中,期望值为10,可用的副本数最少为:
10-10*25%=8,所以失败后依然看到8个Pod在提供服务
总结 maxSurge
值越大,更新时创建的新副本数量就越多;maxUnavailable
值越大,更新时销毁的旧副本数量就越多。
理想情况下,这个案例滚动更新的过程应该是这样的:
- 首先创建 3 个新副本使副本总数达到 13 个。
- 然后销毁 2 个旧副本使可用的副本数降到 8 个。
- 当这 2 个旧副本成功销毁后,可再创建 2 个新副本,使副本总数保持为 13 个。
- 当新副本通过 Readiness 探测后,会使可用副本数增加,超过 8。
- 进而可以继续销毁更多的旧副本,使可用副本数回到 8。
- 旧副本的销毁使副本总数低于 13,这样就允许创建更多的新副本。
- 这个过程会持续进行,最终所有的旧副本都会被新副本替换,滚动更新完成。
而实际情况是在第 4 步就卡住了,新副本无法通过 Readiness 探测。这个过程可以在 kubectl describe deployment app
的日志部分查看。
最后更新失败之后,可以通过kubectl rollout undo
回滚到上一个版本
应用maxSurge和maxUnavailable
因为直接使用上面使用过的文件,所以将创建的Pod先删除
[root@node1 ~]# kubectl delete -f app.v1.yml
deployment.apps "app" deleted
修改v2文件添加maxSurge和maxUnavailable选项,因为v2会作为滚动更新的版本来使用
[root@node1 ~]# vim app.v2.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
strategy:
rollingUpdate:
maxSurge: 35%
maxUnavailable: 35%
selector:
matchLabels:
run: app
replicas: 10
template:
metadata:
labels:
run: app
spec:
containers:
- name: app
image: busybox
args:
- /bin/sh
- -c
- sleep 10; touch /tmp/healthy; sleep 300000
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
运行v1的Pod
[root@node1 ~]# kubectl apply -f app.v1.yml
deployment.apps/app created
等待Pod创建好之后,查看
[root@node1 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
app-688f655f7b-27cpx 1/1 Running 0 30s
app-688f655f7b-2hhqq 1/1 Running 0 30s
app-688f655f7b-448z4 1/1 Running 0 30s
app-688f655f7b-6fgh2 1/1 Running 0 30s
app-688f655f7b-kz8vb 1/1 Running 0 30s
app-688f655f7b-szz2w 1/1 Running 0 30s
app-688f655f7b-tx87l 1/1 Running 0 30s
app-688f655f7b-vqqtq 1/1 Running 0 30s
app-688f655f7b-vvd7x 1/1 Running 0 30s
app-688f655f7b-zkhlr 1/1 Running 0 30s
进行v2的滚动更新
[root@node1 ~]# kubectl apply -f app.v2.yml
deployment.apps/app configured
使用v2更新,v2还是之前不会更新成功的文件
[root@node1 ~]# kubectl apply -f app.v2.yml
deployment.apps/app configured
主要是为了看出怎么样计算的百分比
[root@node1 ~]# kubectl describe deployment app
...
Replicas: 10 desired | 7 updated | 14 total | 7 available | 7 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 35% max unavailable, 35% max surge.
...
从上面的信息看出10个可用期望值,更新时,先直接启动7个新副本,保持总量新旧副本总量在14,也就是当启动了7个新副本,旧副本需要停掉3个,还剩7个,加上更新的7个就是14个。
如果要想直接根据期望值和更新时的新旧副本总量,来确定,这两个值的百分比的话 需要自己去根据期望值副本一步步推算,也是个繁琐的过程