强大的自愈能力是Kubernetes这类容器编排引擎的一个重要特性。
自愈的默认实现方式是自动重启发生故障的容器。
除此之外,还可以利用Liveness和Readiness探测机制设置更精细的健康检查。
默认的健康检查
每个容器启动时都会执行一个进程,此进程由Dockerfile的CMD或ENTRYPOINT指定。如果进程退出时返回码非零,则认为容器发生故障,Kubernetes就会根据restartPolicy重启容器。
下面模拟一个容器发生故障的场景
apiVersion: v1
kind: Pod
metadata:
name: healthcheck
labels:
test: healthcheck
spec:
restartPolicy: OnFailure
containers:
- name: healthcheck
image: busybox
args:
- /bin/sh
- -c
- sleep 10; exit 1
Pod的restartPolicy设置为OnFailure,默认为Always。
sleep 10; exit 1模拟容器启动10秒后发生故障(返回值非0)。
可以看到容器已经重启2次
Liveness探测
在上面的例子中,容器进程返回值非零,Kubernetes则认为容器发生故障,需要重启。
有不少情况是发生了故障,但进程并不会退出。此时默认的健康检查策略已经不能满足需求。这就需要自定义判断容器是否健康的条件。
下面举例说明,创建Pod
apiVersion: v1
kind: Pod
metadata:
name: liveness
labels:
test: liveness
spec:
restartPolicy: OnFailure
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
启动进程首先创建文件/tmp/healthy,30秒后删除。
现在的设定是:如果/tmp/healthy文件存在,则认为容器处于正常状态,反之则发生故障。
livenessProbe
定义Liveness探测方法:
(1)通过cat命令检查/tmp/healthy文件是否存在。如果命令执行成功,返回值为零,本次Liveness探测成功;否则探测失败。
(2)initialDelaySeconds:指定容器启动10之后开始执行Liveness探测,
(3)periodSeconds:指定每5秒执行一次Liveness探测。Kubernetes如果连续执行3次Liveness探测均失败,则会杀掉并重启容器。
一段时间后通过kubectl describe pod liveness
查看pod信息
关注日志部分内容
最后两条表明执行了健康检查和杀死容器(重启)
这时查看pod,发现已经重启了6次
Readiness探测
Liveness探测可以告诉Kubernetes什么时候通过重启容器实现自愈;
Readiness探测则是告诉Kubernetes什么时候可以将容器加入到Service负载均衡池中,对外提供服务(即容器准备就绪)。
Readiness探测的配置语法与Liveness探测完全一样,这里只是将前面例子中的liveness替换为了readiness
apiVersion: v1
kind: Pod
metadata:
name: readiness
labels:
test: readiness
spec:
restartPolicy: OnFailure
containers:
- name: readiness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
apply后,观察pod的ready状态变化
(1)刚被创建时,READY状态为不可用。(因为10s后才开始探测)
(2)10秒后开始探测并成功返回,设置READY为可用。
(3)30秒后,/tmp/healthy被删除,连续3次Readiness探测均失败后,READY被设置为不可用。
通过kubectl describe pod readiness也可以看到Readiness探测失败的日志
注意:Readiness探测失败不会重启容器,而是将服务设置为不可用
总结
(1)Liveness探测和Readiness探测是两种Health Check机制,如果不配置,Kubernetes将对两种探测采取相同的默认行为,即通过判断容器启动进程的返回值是否为零来判断探测是否成功。
(2)两种探测的配置方法完全一样,支持的配置参数也一样。不同之处在于探测失败后的行为:Liveness探测是重启容器;Readiness探测则是将容器设置为不可用,不接收Service转发的请求。
(3)Liveness探测和Readiness探测是独立执行的,二者之间没有依赖,所以可以单独使用,也可以同时使用。
Health Check在滚动更新中的应用
Health Check一个重要的应用场景是滚动更新
现有一个正常运行的多副本应用,接下来对应用进行更新(比如使用更高版本的image),Kubernetes会启动新副本,然后发生了如下事件:
(1)正常情况下新副本需要10秒钟完成准备工作,在此之前无法响应业务请求。
(2)由于人为配置错误,副本始终无法完成准备工作(比如无法连接后端数据库)
如果没有配置Health Check,因为新副本本身没有异常退出,默认的Health Check机制会认为容器已经就绪,进而会逐步用新副本替换现有副本,其结果就是:当所有旧副本都被替换后,整个应用将无法对外提供服务。
如果正确配置了Health Check,新副本只有通过了Readiness探测才会被添加到Service;如果没有通过探测,现有副本不会被全部替换,业务仍然正常进行。
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 10
selector:
matchLabels:
run: app
template:
metadata:
labels:
run: app
spec:
containers:
- name: app
image: busybox
args:
- /bin/sh
- -c
- sleep 10; touch /tmp/healthy; sleep 30000
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
一段时间后。deployment和pod准备就绪
接下来滚动更新,修改配置文件
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 10
selector:
matchLabels:
run: app
template:
metadata:
labels:
run: app
spec:
containers:
- name: app
image: busybox
args:
- /bin/sh
- -c
- sleep 3000
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
由于新副本中不存在/tmp/healthy,因此是无法通过Readiness探测的,也就是说,这时的更新时是有问题的
关注kubectl get pod
输出:
(1)前5个Pod是新副本,目前处于NOT READY状态。
(2)旧副本从最初10个减少到8个。
再来看kubectl get deployment app
的输出:
(1)期望10个READY的副本。
(2)UP-TO-DATE 5表示最新副本的数量。
(3)AVAILABLE 8表示当前处于READY状态的副本数。
(4)当前副本的总数为13,即8个旧副本+5个新副本。
新副本始终都无法通过Readiness探测,所以这个状态会一直保持下去。
上面模拟了一个滚动更新失败的场景。不过幸运的是:Health Check帮我们屏蔽了有缺陷的副本,同时保留了大部分旧副本,业务没有因更新失败受到影响。
为什么新创建的副本数是5个,同时只销毁了2个旧副本?原因是:滚动更新通过参数maxSurge和maxUnavailable来控制副本替换的数量。
1.maxSurge
此参数控制滚动更新过程中副本总数超过DESIRED的上限。
maxSurge默认值为25%。在上面的例子中,DESIRED为10,那么副本总数的最大值为roundUp(10+10*25%)=13,所以CURRENT就是13。
2.maxUnavailable
此参数控制滚动更新过程中,不可用的副本相占DESIRED的最大比例。
maxUnavailable默认值为25%。在上面的例子中,DESIRED为10,那么可用的副本数至少要为10 - roundDown(10* 25%)= 8,所以AVAILABLE是8。
maxSurge值越大,初始创建的新副本数量就越多;maxUnavailable值越大,初始销毁的旧副本数量就越多。
这里也可以理解滚动更新的过程:
(1)创建3个新副本使副本总数达到13个。
(2)销毁2个旧副本使可用的副本数降到8个。
(3)当2个旧副本成功销毁后,再创建2个新副本,使副本总数保持为13个。
(4)当新副本通过Readiness探测后,会使可用副本数增加,超过8。
(5)进而可以继续销毁更多的旧副本,使可用副本数回到8。
(6)旧副本的销毁使副本总数低于13,这样就允许创建更多的新副本。
(7)这个过程会持续进行,最终所有的旧副本都会被新副本替换,滚动更新完成。
如果滚动更新失败,可以通过kubectl rollout undo
回滚到上一个版本
也可以自定义maxSurge和maxUnavailable