强大的自愈能力是Kubernetes这类容器编排引擎的⼀个重要特性。自愈的默认实现方式是自动重启发生故障的容器。除此之外,用户还可以利用Liveness和Readiness探测机制设置更精细的健康检查,进而实现:
(1)零停机部署。
(2)避免部署无效的镜像。
(3)更加安全的滚动升级。

1.1 默认的健康检查

默认情况下,每个容器启动时都会执行一个进程,由Dockerfile中的CMD或ENTRYPOINT指定。如果进程退出时的返回码不为0,则认为容器发生了故障,K8S会根据重启策略(restartPolicy)重启容器。
vi healthcheck.yaml

apiVersion: v1kind: Podmetadata:name: healthcheck-demolabels:test: healthcheckspec:restartPolicy: OnFailurecontainers:- name: healthcheckimage: busyboximagePullPolicy: IfNotPresentargs:- /bin/sh- -c- sleep 10; exit 1

Pod的restartPolicy设置为OnFailure,默认为Always。sleep10;exit1模拟容器启动10秒后发生故障。
执行kubectlapply创建Pod,命名为healthcheck。

[root@k8s-master ~]# kubectl get pod healthcheck-demoNAME READY STATUS RESTARTS AGEhealthcheck-demo 0/1 CrashLoopBackOff 3101s

可看到容器当前已经重启了3次。
在上面的例子中,容器进程返回值非零,必须等到进程退出后的返回值是非零才会触发重启策略,不能直接监测容器是否是健康。

K8S中有没有更好的机制能够实现智能一点的健康检查呢?答案就是使用Liveness与Readinesss。

1.2 Liveness探测

Liveness的参数:

initialDelaySeconds:容器启动后第一次执行探测是需要等待多少秒,看运行的服务而定。
periodSeconds:执行探测的频率,默认是10秒,最小1秒。
timeoutSeconds:探测超时时间,默认1秒,最小1秒。
successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功,默认是1,对于liveness必须是1,最小值是1。
failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败。默认是3。最小值是1.

vi liveness-demo.yml

apiVersion: v1kind: Podmetadata:labels:test: livenessname: liveness-demospec:containers:- name: livenessimage: busyboxargs:- /bin/sh- -c- touch /tmp/healthy; sleep 30; rm -rf/tmp/healthy; sleep 10livenessProbe:exec:command:- cat- /tmp/healthyinitialDelaySeconds: 10periodSeconds: 5

这里启动pod后会创建文件夹 /tmp/healthy,30秒后删除,在我们的设置中,如果 /tmp/healthy 存在,则认为容器处于正常状态,否则认为发生故障。

需要注意的就是livenessProbe部分的定义了:

(1)探测方法:通过cat命令查看/tmp/healthy是否存在;如果返回值为0,则探测成功;否则,探测失败;

(2)initialDelaySeconds: 10 => 容器启动10秒之后开始执行liveness探测;

(3)periodSeconds: 5 => 每5秒执行一次liveness探测;如果连续执行3次探测都失败,那么就会杀掉并重启容器;

下面快速地验证一下:

[root@k8s-master ~]# kubectl describe pod liveness-demodeployment.apps/httpd configuredNormal Pulling71s (x4 over 4m29s)kubelet, k8s-node2Pulling image "busybox"Normal Pulled 44s (x4 over 4m27s)kubelet, k8s-node2Successfully pulled image "busybox"Normal Created44s (x4 over 4m27s)kubelet, k8s-node2Created container livenessNormal Started44s (x4 over 4m27s)kubelet, k8s-node2Started container livenessWarningBackOff2s (x7 over 2m49s) kubelet, k8s-node2Back-off restarting failed container[root@k8s-master ~]# kubectl get pod liveness-demoNAMEREADY STATUSRESTARTS AGEliveness-demo 1/1 Running 45m46s

1.3 Readiness探测

用户通过Liveness探测可以告诉Kubernetes什么时候通过重启容器实现自愈;Readiness探测则是告诉Kubernetes什么时候可以将容器加入到Service负载均衡池中,对外提供服务。

vi readiness-demo.yml

apiVersion: v1kind: Podmetadata:labels:test: readinessname: readiness-demospec:containers:- name: readinessimage: busyboxargs:- /bin/sh- -c- touch /tmp/healthy; sleep 30; rm -rf/tmp/healthy; sleep 10readinessProbe:exec:command:- cat- /tmp/healthyinitialDelaySeconds: 10periodSeconds: 5

这个配置文件只是将前面例⼦中的liveness替换为了readiness。

[root@k8s-master ~]# kubectl get pod readiness-demoNAME READY STATUSRESTARTS AGEreadiness-demo 1/1 Running 1107sEvents:Type Reason AgeFromMessage---- ------ ---- -----------Normal Scheduled<unknown>default-scheduler Successfully assigned default/readiness-demo to k8s-node2Normal Pulling61s (x3 over 3m13s)kubelet, k8s-node2Pulling image "busybox"Normal Pulled 44s (x3 over 2m51s)kubelet, k8s-node2Successfully pulled image "busybox"Normal Created44s (x3 over 2m51s)kubelet, k8s-node2Created container readinessNormal Started44s (x3 over 2m51s)kubelet, k8s-node2Started container readinessWarningBackOff4s (x2 over 73s) kubelet, k8s-node2Back-off restarting failed containerWarningUnhealthy4s kubelet, k8s-node2Readiness probe failed: OCI runtime exec failed: exec failed: unable to sta

(1)刚被创建时,其READY状态为不可用;
(2)15秒(initialDelaySeconds + periodSeconds = 10 + 5 = 15)之后,第一次进行Readiness探测成功,其READY状态变为可用。
(3)30秒之后,/tmp/healthy被删除,连续3次Readiness探测均失败后,其READY状态又变为了不可用。

与Liveness的对比

Liveness与Readiness都是K8S的Health Check机制,Liveness探测是重启容器,而Readiness探测则是将容器设置为不可用,不让其再接受Service转发的请求。
Liveness与Readiness是独立执行的,二者无依赖,可以单独使用也可以同时使用。

1.4 Health Check在Scale Up中的应用

对于多副本应用,当执行ScaleUp操作时,新副本会作为backend被添加到Service的负载均衡中,与已有副本⼀起处理客户的请求。考虑到应用启动通常都需要⼀个准备阶段,比如加载缓存数据、连接数据库等,从容器启动到真正能够提供服务是需要⼀段时间的。我们可以通过Readiness探测判断容器是否就绪,避免将请求发送到还没有准备好的backend。

apiVersion: apps/v1kind: Deploymentmetadata:name: webspec:replicas: 3selector:matchLabels:run: webtemplate:metadata:labels:run: webspec:containers:- name: webimage: myhttpdports:- containerPort: 8080readinessProbe:httpGet:path: /healthyport: 8080initialDelaySeconds: 10periodSeconds: 5---apiVersion: v1kind: Servicemetadata:name: web-svcspec:selector:run: webports:- protocol: TCPport: 8080targetPort: 8080

重点关注readinessProbe部分。这里我们使用了不同于exec的另⼀种探测方法httpGet。
Kubernetes对于该方法探测成功的判断条件是http请求的返回代码在200〜400之间。
上⾯配置的作用是:
(1)容器启动10秒之后开始探测。
(2)如果http://[container_ip]:8080/healthy返回代码不是200〜 400,表示容器没有就绪,不接收Serviceweb-svc的请求。
(3)每隔5秒探测⼀次。
(4)直到返回代码为200〜400,表明容器已经就绪,然后将其加入
到web-svc的负载均衡中,开始处理客户请求。
(5)探测会继续以5秒的间隔执行,如果连续发生3次失败,容器又
会从负载均衡中移除,直到下次探测成功重新加入。

1.5 Health Check在Rolling Update中的应用

假设现在有一个正常运行的多副本应用,我们要对其进行滚动更新即Rolling Update,K8S会逐步用新Pod替换旧Pod,结果就有可能发生这样的一个场景:
(1)正常情况下新副本需要10秒钟完成准备工作,在此之前无法响应业务请求。
(2)当所有旧副本被替换之后,而新的Pod由于人为配置错误一直无法启动,因此整个应用将无法处理请求,无法对外提供服务,后果很严重!

如果正确配置了HealthCheck,新副本只有通过了Readiness探测才会被添加到Service;如果没有通过探测,现有副本不会被全部替换,业务仍然正常进行。

因此,Readiness探测还提供了用于避免滚动更新中出现这种情况的一些解决办法,比如maxSurge和maxUnavailable两个参数,用来控制副本替换的数量。

继续以上面的YAML配置文件为例,重点关注strategy部分:

apiVersion: apps/v1kind: Deploymentmetadata:name: webspec:strategy:rollingupdate:maxSurge: 25%maxUnavailable: 25%replicas: 3selector:matchLabels:run: webtemplate:metadata:labels:run: webspec:containers:- name: webimage: myhttpdports:- containerPort: 8080readinessProbe:httpGet:path: /healthyport: 8080initialDelaySeconds: 10periodSeconds: 5---apiVersion: v1kind: Servicemetadata:name: web-svcspec:selector:run: webports:- protocol: TCPport: 8080targetPort: 8080

(1)maxSurge:25%=>控制滚动更新过程中副本总数超过预期(这里预期是10个副本replicas:10)的上限,可以是数值也可以是百分比,然后向上取整。这里写的百分比,默认值是25%;
如果预期副本数为10,那么副本总数的最大值为RoundUp(10+1025%)=13个。
(2)maxUnavailable:25%=>控制滚动更新过程中不可用的副本(这里预期是10个副本replicas:10)占预期的最大比例,可以是数值也可以是百分比,然后向下取整,同样地默认值也是25%;
如果预期副本总数为10,那么可用的副本数至少要为10-roundDown(10
25%)=10-2=8个。
综上看来,maxSurge的值越大,初始创建的新副本数量就越多;maxUnavaliable值越大,初始销毁的旧副本数量就越多;