K8s Liveness/Readiness/Startup 探针机制

时间:2022-12-27 15:55:06

K8s Liveness/Readiness/Startup 探针机制

官方参考文档



前言

玩过 Docker Swarm 的应该都知道,有一种功能叫自愈功能,当集群检测到节点或服务故障时回进行自动故障转移,从而保障业务的可用性。而 K8s 集群相对于其他集群体系,其自愈能力更加强大,这也是 K8s 容器编排引擎的一重要特性。自愈从某种角度上来讲,其实现了以下几几种功能特性:

  • 零停机部署;
  • 避免无效镜像;
  • 实现滚动升级与回退。

K8s 有三种探针,分别是:存活(Liveness)就绪(Readiness)启动(Startup)

  • 存活(Liveness):kubelet 使用存活探针来确定什么时候要重启容器。 例如,存活探针可以探测到应用死锁(应用程序在运行,但是无法继续执行后面的步骤)情况。 重启这种状态下的容器有助于提高应用的可用性,即使其中存在缺陷。

  • 就绪(Readiness):kubelet 使用就绪探针可以知道容器何时准备好接受请求流量。当一个 Pod 内的所有容器都就绪时,才能认为该 Pod 就绪。 这种信号的一个用途就是控制哪个 Pod 作为 Service 的后端。 若 Pod 尚未就绪,会被从 Service 的负载均衡器中剔除。

  • 启动(Startup):kubelet 使用启动探针来了解应用容器何时启动。 如果配置了这类探针,你就可以控制容器在启动成功后再进行存活性和就绪态检查, 确保这些存活、就绪探针不会影响应用的启动。 启动探针可以用于对慢启动容器进行存活性检测,避免它们在启动运行之前就被杀掉。

K8s 探针有三种探测方式,分别是:ExecActionHTTPGetActionTCPSocketAction

注意,这三种探测方式只能同时使用一种,不能两种或三种同时使用。

  • ExecAction:在容器中执行指定的命令,如果执行成功,退出码为0则探测成功。
  • HTTPGetAction:通过容器的IP地址、端口号及路径调用HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器健康。
  • TCPSocketAction:通过容器的IP地址和端口号执行TCP检查,如果能够建立TCP连接,则表明容器健康。

探针探测的结果有以下值:

  • Success:表示通过检测。
  • Failure:表示未通过检测。
  • Unknown:表示检测没有正常进行。

一、默认健康检测

1.1 restartPolicy

了解 Docker 的都知道,每个容器启动时都会执行一个进程,该进程由 Dockerfile 的 CMD 或 ENTRYPOINT 指定。如果进程退出或返回状态码为非零时,则认为容器发生故障,这个时候 K8s 就会根据 restartPolicy 重启容器。restartPolicy 有三种重启策略:

  • Always

    Pod 中容器不论如何停止都将自动重启;

  • OnFailure
    Pod 中容器非正常停止会自动重启,正常停止不会重启;

  • Never
    Pod 中容器不论以任何方式停止,都不会自动重启。

K8s 的 restartPolicy 默认为 Always。如果探针超过失败重试的次数,则 Pod 就会根据 restartPolicy 策略进行选择是否重启。

1.2 测试案例

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 Liveness/Readiness/Startup 探针机制

可看到已经重启了三次,该案例中容器进程返回值为非零,k8s 则认为容器发生故障,需要重启。但可能有些情况下发生了故障进程不退出的现象,如访问 Web 服务器时返回 500 错误代码,这可能是系统负荷较大或资源锁死,此时 httpd 进程并没有异常退出,这种情况下可直接重启容器。而 K8s 的 Liveness 机制刚好能解决此类问题(即出现此类问题会直接 Kill 掉容器并重新启动)。

二、Liveness

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 中只有一个 ContainerperiodSeconds 字段指定了 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 文件是存在的:

K8s Liveness/Readiness/Startup 探针机制

35s 后检测到/tmp/healthy 文件已经不存在:

K8s Liveness/Readiness/Startup 探针机制

再过十几秒后容器被重启:

K8s Liveness/Readiness/Startup 探针机制

三、Readiness

除了可以自定义判断容器是否健康的条件外,用户也可以通过 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

K8s Liveness/Readiness/Startup 探针机制

Probe 有很多配置字段,可以使用这些字段精确地控制启动、存活和就绪检测的行为:

  • initialDelaySeconds:容器启动后要等待多少秒后才启动启动、存活和就绪探针, 默认是 0 秒,最小值是 0。
  • periodSeconds:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。
  • timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
  • successThreshold:探针在失败后,被视为成功的最小连续成功数。默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。
  • failureThreshold:当探测失败时,Kubernetes 的重试次数。 对存活探测而言,放弃就意味着重新启动容器。 对就绪探测而言,放弃意味着 Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。

注意:存活探针 不等待 就绪性探针成功。如果要在执行存活探针之前等待就绪性探针,应该使用 initialDelaySecondsstartupProbe

四、Startup

Startup 是 k8s 1.16+ 版本后新加的探测方式,用于判断容器内应用程序是否已经启动,如果同时配置了 startuprobeLivenessprobeReadinessprobe,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 探针存在的意义。