官方参考文档
玩过 Docker Swarm
的应该都知道,有一种功能叫自愈功能
,当集群检测到节点或服务故障时回进行自动故障转移,从而保障业务的可用性。而 K8s 集群相对于其他集群体系,其自愈能力更加强大,这也是 K8s 容器编排引擎的一重要特性。自愈从某种角度上来讲,其实现了以下几几种功能特性:
K8s 有三种探针,分别是:存活(Liveness)
、就绪(Readiness)
和启动(Startup)
存活(Liveness):kubelet 使用存活探针来确定什么时候要重启容器。 例如,存活探针可以探测到应用死锁(应用程序在运行,但是无法继续执行后面的步骤)情况。 重启这种状态下的容器有助于提高应用的可用性,即使其中存在缺陷。
就绪(Readiness):kubelet 使用就绪探针可以知道容器何时准备好接受请求流量。当一个 Pod 内的所有容器都就绪时,才能认为该 Pod 就绪。 这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。 若 Pod 尚未就绪,会被从 Service 的负载均衡器中剔除。
启动(Startup):kubelet 使用启动探针来了解应用容器何时启动。 如果配置了这类探针,你就可以控制容器在启动成功后再进行存活性和就绪态检查, 确保这些存活、就绪探针不会影响应用的启动。 启动探针可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。
K8s 探针有三种探测方式,分别是:ExecAction
、HTTPGetAction
和 TCPSocketAction
。
注意,这三种探测方式只能同时使用一种,不能两种或三种同时使用。
探针探测的结果有以下值:
了解 Docker 的都知道,每个容器启动时都会执行一个进程,该进程由 Dockerfile 的 CMD 或 ENTRYPOINT 指定。如果进程退出或返回状态码为非零时,则认为容器发生故障,这个时候 K8s 就会根据 restartPolicy
重启容器。restartPolicy
有三种重启策略:
Always
Pod 中容器不论如何停止都将自动重启;
OnFailure
Pod 中容器非正常停止会自动重启,正常停止不会重启;
Never
Pod 中容器不论以任何方式停止,都不会自动重启。
K8s 的 restartPolicy
默认为 Always
。如果探针超过失败重试的次数,则 Pod 就会根据 restartPolicy 策略进行选择是否重启。
1、创建一个 Pod
apiVersion: v1
kind: Pod
metadata:
labels:
test: healthcheck
name: healthcheck
spec:
restartPolicy: OnFailure
containers:
- name: healthcheck
image: busybox
args:
- /bin/sh
- -c
- sleep 10;exit 1
2、观察 Pod 状态
这个容器启动 10s 后会发生故障,接下来执行 kubectl apply
创建 Pod,Pod Name 为 healthcheck。过几分钟后看看 Pod 的状态:
可看到已经重启了三次,该案例中容器进程返回值为非零,k8s 则认为容器发生故障,需要重启。但可能有些情况下发生了故障进程不退出的现象,如访问 Web 服务器时返回 500 错误代码,这可能是系统负荷较大或资源锁死,此时 httpd 进程并没有异常退出,这种情况下可直接重启容器。而 K8s 的 Liveness 机制刚好能解决此类问题(即出现此类问题会直接 Kill 掉容器并重新启动)。
Liveness 探针让用户可以自定义判断容器是否健康的条件,如果探测失败,K8s 就会重启容器,如下案例:
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
在这个配置文件中,可以看到 Pod 中只有一个 Container
。 periodSeconds
字段指定了 kubelet 应该每 5 秒执行一次存活探测。 initialDelaySeconds
字段告诉 kubelet 在执行第一次探测前应该等待 5 秒(即 5 秒后才开始启动探针)。 kubelet 在容器内执行命令 cat /tmp/healthy
来进行探测。 如果命令执行成功并且返回值为 0,kubelet 就会认为这个容器是健康存活的。 如果这个命令返回非 0 值,kubelet 会杀死这个容器并重新启动它。当容器启动时,会执行如下的命令:
/bin/sh -c "touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600"
这个容器生命的前 30 秒,/tmp/healthy
文件是存在的。 所以在这最开始的 30 秒内,执行命令 cat /tmp/healthy
会返回成功代码。 30 秒之后,执行命令 cat /tmp/healthy
就会返回失败代码。
执行 kubectl apply
创建 Pod 。
kubectl apply -f liveness.yml
查看 Pod 启动日志:
kubectl describe pod liveness-exec
前 30s /tmp/healthy
文件是存在的:
35s 后检测到/tmp/healthy
文件已经不存在:
再过十几秒后容器被重启:
除了可以自定义判断容器是否健康的条件外,用户也可以通过 Readiness
探针告诉 K8s 什么时候可以重启容器实现自愈,Readiness
探针则告诉 K8s 什么时候可以将容器加入到 Service 负载均衡池中,对外提供服务。就绪探针的配置和存活探针的配置相似。 唯一区别就是要使用 readinessProbe
字段,而不是 livenessProbe
字段。
apiVersion: v1
kind: Pod
metadata:
labels:
test: readiness
name: readiness-exec
spec:
containers:
- name: liveness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 600
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
看看启动日志,与 Liveness
探针类似
kubectl describe pod readiness-exec
Probe 有很多配置字段,可以使用这些字段精确地控制启动、存活和就绪检测的行为:
initialDelaySeconds
:容器启动后要等待多少秒后才启动启动、存活和就绪探针, 默认是 0 秒,最小值是 0。periodSeconds
:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。timeoutSeconds
:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。successThreshold
:探针在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。failureThreshold
:当探测失败时,Kubernetes 的重试次数。 对存活探测而言,放弃就意味着重新启动容器。 对就绪探测而言,放弃意味着 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。注意:存活探针 不等待 就绪性探针成功。如果要在执行存活探针之前等待就绪性探针,应该使用 initialDelaySeconds
或 startupProbe
。
Startup 是 k8s 1.16+
版本后新加的探测方式,用于判断容器内应用程序是否已经启动,如果同时配置了 startuprobe
、Livenessprobe
、Readinessprobe
,K8s 就会先禁用其他的探针,而首先使用 startuprobe
探测直到它成功为止,成功后将不再进行探测。
startupProbe 探针与另两种区别?
如果三个探针同时存在,先执行startupProbe探针,其他两个探针将会被暂时禁用,直到pod满足startupProbe探针配置的条件,其他2个探针启动,如果不满足按照规则重启容器。
另外两种探针在容器启动后,会按照配置,直到容器消亡才停止探测,而startupProbe探针只是在容器启动后按照配置满足一次后,不在进行后续的探测。
那 Startup 存在的意义又是什么呢?
试想一个问题,如果启动一个服务需要 1 分钟,如果没有 Startup
的情况下,且假设我们配置了 livenessProbe
探针,具体如下:
livenessProbe:
httpGet:
path: /test
prot: 80
failureThreshold: 6
initialDelay:40
periodSeconds: 5
我们设置了失败重试次数为 6,每 5 秒探测一次,加上我们探测前等待的 40 秒,总时间就是:40 + 6 x 5 = 70s,这样 Pod 确实能够启动起来。那问题又来了,如果这个配置用于生产配置上,将会导致我们比较晚的收到服务不可用的情况,也就是说我的服务可能在 5s 时已经不可用,但是由于我们探测机制(有 6 次失败重试的机会),我们在至少 6 x 5 = 30s 才会发现服务不可用的情况,这在生产上一般是不被允许的。那该如何解决类似这样的问题呢?答案是 startupProbe
。
注意上面这个案例,如果
failureThreshold
设置为 1 或 2,那这个服务是永远起不起来的,因为你把initialDelay
的时间算上也不足 1 分钟,因为我们说启动至少需要 1 分钟,那超过失败重试次数后,就会根据 Pod 的restartPolicy
来重启容器。
接着我们引入 startupProbe
,继续优化:
livenessProbe:
httpGet:
path: /test
prot: 80
failureThreshold: 1
initialDelay:5
periodSeconds: 5
startupProbe:
httpGet:
path: /test
prot: 80
failureThreshold: 60
initialDelay:5
periodSeconds: 5
前面说了,startupProbe
会在探测成功后停止探测,而其他两种探针则是随着 Pod 的生命周期一直在探测的。因此上面的配置功能就是:K8s 部署服务后会在 5s 后启动 startupProbe
,在 5 x 60 = 300s 的时间内只要成功探测到服务则停止探测(否则需接受 Pod 的重启策略进行重启),并启用 livenessProbe
探针,此时livenessProbe
探针就会伴随着 Pod 的生命周期每 5s 检测一次,且我们只设置了 1 次失败重试的机会,这就意味着我们只需要在 1 x 5 = 5s 时间就可以发现服务不可用。因此,你配置了 startupProbe
,那其他2种探针如果配置了initialDelaySeconds
,建议时间就不要给太长。这就是 Startup 探针存在的意义。