存活、就绪和启动探针

存活、就绪和启动探针

Kubernetes 允许你定义**探针(Probe)**来持续监控 Pod 中容器的健康状况。 探针是由 kubelet 对容器周期性执行的诊断。 为执行诊断,kubelet 或是在容器内执行代码,或是发起一个网络请求。

根据探针的结果,Kubernetes 可以重启不健康的容器,或者停止向尚未就绪的容器发送流量。

探针的类型

kubelet 可以选择对运行中的容器执行三种探针,并对探针的结果作出响应; 每种探针有不同的用途:

启动探针

启动探针(Startup Probe)用于检查容器内的应用是否已经启动。 如果配置了启动探针,Kubernetes 将在启动探针成功之前不执行存活探针或就绪探针, 从而为应用留出足够的时间完成初始化。

启动探针仅在启动时执行,不像存活探针和就绪探针那样周期性地运行。 如果启动探针失败,kubelet 将杀死容器,容器随后将依据其 重启策略进行处理。

存活探针

存活探针(Liveness Probe)决定何时重启容器。 例如,存活探针可以捕获死锁——即应用在运行但无法取得进展。 在此类情况下重启容器,有助于提高应用的可用性,即使应用本身存在缺陷。

如果某容器的存活探针失败次数超过配置的容忍次数,kubelet 将重启该容器。 存活探针不会等待就绪探针成功。如果你希望在执行存活探针之前先等待, 可以定义 initialDelaySeconds,或者使用启动探针

注意:

存活探针是从应用故障中恢复的有效手段,但应谨慎使用。 存活探针必须经过仔细配置,确保其真正能够指示不可恢复的应用故障,例如死锁。

错误地实现存活探针可能导致级联故障。这会引发容器在高负载下被重启、 客户端请求因应用可伸缩性下降而失败,以及由于某些 Pod 失败导致剩余 Pod 工作负载增加等问题。 请理解存活探针和就绪探针之间的区别,并明确何时为你的应用使用它们。

就绪探针

就绪探针(Readiness Probe)决定容器何时准备好接受流量。 这种探针在等待应用执行耗时的初始任务时非常有用, 例如建立网络连接、加载文件和预热缓存等。 就绪探针在容器生命周期的后期也很有用,例如从临时故障或过载中恢复时。

如果就绪探针返回失败状态, EndpointSlice 控制器会将 Pod 的 IP 地址从与该 Pod 匹配的所有 Service 的 EndpointSlice 中移除。

就绪探针在容器的整个生命周期内持续运行。

说明:

如果你希望在 Pod 被删除时能够腾空请求,并不一定需要就绪探针; 当 Pod 被删除时,EndpointSlice 中对应的端点会更新其状况: 端点的 ready 状况会被设为 false,从而负载均衡器不会将常规流量发送给该 Pod。 关于 kubelet 如何处理 Pod 删除的更多信息, 参阅 Pod 终止

何时使用各类探针

何时使用启动探针?

对于包含启动时间较长的容器的 Pod,启动探针非常有用。 你不必设置较长的存活探针时间间隔, 而是为容器启动阶段单独配置探针参数,允许使用比存活探针时间间隔更长的时间。

如果你的容器通常需要超过 \( initialDelaySeconds + failureThreshold \times periodSeconds \) 的时间启动, 你应该指定一个启动探针,对存活探针所使用的同一端点执行检查。 periodSeconds 的默认值是 10 秒。 你应将其 failureThreshold 设置得足够高,以便容器能够启动, 同时不必更改存活探针的默认值。这种设置有助于防范死锁状况。

何时使用存活探针?

如果你的容器中的进程在遇到问题或变得不健康时能够自行崩溃, 那么你不一定需要存活探针;kubelet 将根据 Pod 的 restartPolicy 自动执行正确的操作。

如果你希望容器在探针失败时被杀死并重新启动, 那么请指定一个存活探针,并将 restartPolicy 设置为 AlwaysOnFailure

存活探针的一种常见模式是使用与就绪探针相同的低成本 HTTP 端点, 但设置较高的 failureThreshold。 这样可以确保在容器被强制杀死之前,Pod 会有一段时间被观察为未就绪状态。

何时使用就绪探针?

如果你希望仅在探针成功时才开始向 Pod 发送流量,请指定就绪探针。 就绪探针可以与存活探针相同,但规约中存在就绪探针意味着 Pod 启动时不接收任何流量, 只有在探针开始成功后才开始接收流量。

你也可以使用就绪探针让容器在维护期间将自身下线, 方法是检查一个专用于就绪检查的端点,与存活探针所使用的端点不同。

当你的应用对后端服务有强依赖时,你可以同时实现存活探针和就绪探针。 当应用自身健康时存活探针通过,但就绪探针还会检查所需的每个后端服务是否可用。 这有助于避免将流量导向只能以错误信息响应的 Pod。

对于在启动期间需要加载大量数据、配置文件或执行迁移操作的容器, 请考虑使用启动探针。 不过,如果你希望区分应用已经失败和应用仍在处理启动数据这两种情况, 可能更适合使用就绪探针。

检查机制

使用探针检查容器有四种不同的方法。每个探针必须恰好定义这四种机制中的一种:

exec
在容器内执行指定的命令。如果命令以状态码 0 退出,则认为诊断成功。
grpc
使用 gRPC 执行远程过程调用。 目标应实现 gRPC 健康检查。 如果响应的 statusSERVING,则认为诊断成功。 更多细节参阅 gRPC 探针
httpGet
针对 Pod IP 地址上指定端口和路径执行 HTTP GET 请求。 如果响应的状态码大于等于 200 且小于 400,则认为诊断成功。 更多细节参阅 HTTP 探针
tcpSocket
针对 Pod IP 地址上指定端口执行 TCP 检查。 如果该端口是开放的,则认为诊断成功。 如果远程系统(容器)在打开连接后立即将其关闭,这也算作健康。 更多细节参阅 TCP 探针

注意:

与其他机制不同,exec 探针的实现涉及每次执行时创建/派生多个进程。 因此,在 Pod 密度较高、initialDelaySecondsperiodSeconds 间隔较短的集群中, 为任何探针配置 exec 机制都可能给节点的 CPU 使用带来额外开销。 在这种场景下,请考虑使用替代的探针机制来避免这种开销。

探针结果

kubelet 评估每次探针执行的结果,并据此采取相应措施。每个探针的结果有以下三种之一:

Success
容器通过了诊断。
Failure
容器未通过诊断。对于存活探针和启动探针, kubelet 会杀死容器,容器随后依据其重启策略进行处理。 对于就绪探针,kubelet 会将容器标记为未就绪, Pod 将停止从与之匹配的 Service 接收流量。
Unknown
诊断失败(不应采取任何行动,kubelet 将继续执行进一步的检查)。

如果容器未提供某种特定探针,kubelet 始终将其结果视为 Success。 对于就绪探针,在初始延迟之前,结果被视为 Failure

配置字段

探针 有若干字段,可用来更精确地控制启动、存活和就绪检查的行为。例如:

apiVersion: v1
kind: Pod
metadata:
  name: probe-example
spec:
  containers:
  - name: app
    image: registry.k8s.io/e2e-test-images/agnhost:2.40
    ports:
    - containerPort: 8080
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      failureThreshold: 30
      periodSeconds: 10
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 5
      timeoutSeconds: 3
      failureThreshold: 3
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      periodSeconds: 5
initialDelaySeconds
容器启动后到启动、存活或就绪探针开始执行之间的秒数。 如果定义了启动探针,则存活探针和就绪探针的延迟在启动探针成功之前不会开始计时。 在某些较早的 Kubernetes 版本中,如果 periodSeconds 被设置为高于 initialDelaySeconds 的值, initialDelaySeconds 可能会被忽略。 然而在当前版本中,initialDelaySeconds 始终被遵守, 探针只有在初始延迟之后才会启动。默认为 0 秒。最小值为 0。
periodSeconds
执行探针的频率(以秒为单位)。默认为 10 秒。最小值为 1。 当容器未就绪时,就绪探针可能在 periodSeconds 配置的间隔之外的时刻执行。 这是为了让 Pod 更快地变为就绪状态。
timeoutSeconds
探针超时时间(以秒为单位)。默认为 1 秒。最小值为 1。
successThreshold
探针在失败之后被视为成功所需的最小连续成功次数。默认值为 1。 对于存活探针和启动探针,该值必须为 1。最小值为 1。
failureThreshold
探针连续失败 failureThreshold 次之后,Kubernetes 认为整体检查已失败: 容器就绪/未健康/不存活。默认为 3。最小值为 1。 对于启动探针或存活探针的情形,如果至少有 failureThreshold 个探针失败, Kubernetes 会将容器视为不健康,并触发该特定容器的重启。 kubelet 会遵从该容器的 terminationGracePeriodSeconds 设置。 对于失败的就绪探针,kubelet 会继续运行检查失败的容器并继续运行更多探针; 由于检查失败,kubelet 会将 Pod 的 Ready 状况设置为 false
terminationGracePeriodSeconds
为 kubelet 配置一个宽限期,用于在触发关闭失败容器与强制容器运行时停止该容器之间等待。 默认值为继承 Pod 层面的 terminationGracePeriodSeconds 值(如果未指定则为 30 秒), 最小值为 1。 更多细节参阅探针层面的 terminationGracePeriodSeconds

注意:

错误地实现就绪探针可能导致容器内的进程数不断增长, 若不加以控制,可能造成资源耗尽。

探针层面的 terminationGracePeriodSeconds

特性状态: Kubernetes v1.28 [stable]

在 1.25 及以上版本中,用户可以将探针层面的 terminationGracePeriodSeconds 作为探针规约的一部分来设置。 当 Pod 层面和探针层面的 terminationGracePeriodSeconds 都被设置时, kubelet 将使用探针层面的值。

在设置 terminationGracePeriodSeconds 时,请注意以下几点:

  • 如果 Pod 上存在探针层面的 terminationGracePeriodSeconds 字段, kubelet 始终会遵守该字段。
  • 如果你的现有 Pod 设置了 terminationGracePeriodSeconds 字段, 并且你不再希望使用按探针配置的终止宽限期,你必须删除这些现有的 Pod。

例如:

spec:
  terminationGracePeriodSeconds: 3600  # pod-level
  containers:
  - name: test
    image: ...

    ports:
    - name: liveness-port
      containerPort: 8080

    livenessProbe:
      httpGet:
        path: /healthz
        port: liveness-port
      failureThreshold: 1
      periodSeconds: 60
      # Override pod-level terminationGracePeriodSeconds
      terminationGracePeriodSeconds: 60

探针层面的 terminationGracePeriodSeconds 不能为就绪探针设置。 API 服务器将拒绝这种设置。

探针机制详解

HTTP 探针

HTTP 探针 可以在 httpGet 上设置以下额外字段:

  • host:要连接的主机名,默认为 Pod IP。你可能更希望在 httpHeaders 中设置 Host
  • scheme:用于连接主机的协议(HTTP 或 HTTPS)。默认为 "HTTP"。
  • path:HTTP 服务器上要访问的路径。默认为 "/"。
  • httpHeaders:请求中要设置的自定义标头。HTTP 允许重复的标头。
  • port:要在容器上访问的端口的名称或编号。编号必须在 1 到 65535 之间。

对于 HTTP 探针,kubelet 向指定的端口和路径发送 HTTP 请求来执行检查。 kubelet 会将探针发送到 Pod 的 IP 地址, 除非地址被 httpGet 中的可选 host 字段覆盖。 如果 scheme 字段被设置为 HTTPS,kubelet 会发送 HTTPS 请求并跳过证书验证。 在大多数场景下,你不需要设置 host 字段。 下面是一个需要设置的场景:假设容器监听 127.0.0.1,且 Pod 的 hostNetwork 字段为 true。 此时 httpGet 下的 host 应被设置为 127.0.0.1。 如果你的 Pod 依赖虚拟主机(这可能是更常见的情况), 你不应使用 host,而是应该在 httpHeaders 中设置 Host 标头。

对于 HTTP 探针,除了必需的 Host 标头之外,kubelet 还会发送两个请求标头:

  • User-Agent,默认值为 kube-probe/1.36, 其中 1.36 是 kubelet 的版本。
  • Accept,默认值为 */*

你可以通过为探针定义 httpHeaders 来覆盖这些标头。例如:

livenessProbe:
  httpGet:
    httpHeaders:
      - name: Accept
        value: application/json

startupProbe:
  httpGet:
    httpHeaders:
      - name: User-Agent
        value: MyUserAgent

你也可以通过将其设置为空值来移除这两个标头。

livenessProbe:
  httpGet:
    httpHeaders:
      - name: Accept
        value: ""

startupProbe:
  httpGet:
    httpHeaders:
      - name: User-Agent
        value: ""

重定向处理

当 kubelet 使用 HTTP 探测容器时,只有当重定向目标是同一主机时才会跟随重定向。 这包括将协议从 HTTP 更改为 HTTPS 的重定向, 即使探针被配置为 scheme: HTTP

如果重定向到不同的主机名,kubelet 不会跟随该重定向。 此时,kubelet 会将探针视为成功,并记录一个 ProbeWarning 事件。

如果 kubelet 跟随重定向并累计收到 11 次或以上的重定向, 探针被视为成功并记录一个 ProbeWarning 事件。例如:

Events:
  Type     Reason        Age                     From               Message
  ----     ------        ----                    ----               -------
  Normal   Scheduled     29m                     default-scheduler  Successfully assigned default/httpbin-7b8bc9cb85-bjzwn to daocloud
  Normal   Pulling       29m                     kubelet            Pulling image "docker.io/kennethreitz/httpbin"
  Normal   Pulled        24m                     kubelet            Successfully pulled image "docker.io/kennethreitz/httpbin" in 5m12.402735213s
  Normal   Created       24m                     kubelet            Created container httpbin
  Normal   Started       24m                     kubelet            Started container httpbin
 Warning  ProbeWarning  4m11s (x1197 over 24m)  kubelet            Readiness probe warning: Probe terminated redirects

注意:

处理 httpGet 探针时,kubelet 在读取响应主体超过 10KiB 后会停止读取。 探针的成功与否仅由响应状态码决定,状态码可以在响应标头中找到。

如果你探测的端点返回的响应主体大于 10KiB, kubelet 仍会根据状态码将探针标记为成功, 但在达到 10KiB 限制后会关闭连接。 这种突然的关闭可能导致 connection reset by peerbroken pipe errors 等错误出现在你的应用日志中,而这些错误可能难以与真正的网络问题区分。

为了让 httpGet 探针更可靠,强烈建议使用专用的健康检查端点, 让其返回较小的响应主体。 如果你必须使用一个负载较大的现有端点,可以考虑改用 exec 探针执行一次 HEAD 请求。

TCP 探针

对于 TCP 探针,kubelet 在节点上而不是在 Pod 中建立探测连接, 这意味着你不能在 host 参数中使用服务名称,因为 kubelet 无法解析它。

gRPC 探针

特性状态: Kubernetes v1.27 [stable]

如果你的应用实现了 gRPC 健康检查协议, 你可以配置 Kubernetes 使用该协议来执行应用启动、存活或就绪检查。

下面是一个清单示例:

apiVersion: v1
kind: Pod
metadata:
  name: etcd-with-grpc
spec:
  containers:
  - name: etcd
    image: registry.k8s.io/etcd:3.5.1-0
    command: [ "/usr/local/bin/etcd", "--data-dir",  "/var/lib/etcd", "--listen-client-urls", "http://0.0.0.0:2379", "--advertise-client-urls", "http://127.0.0.1:2379", "--log-level", "debug"]
    ports:
    - containerPort: 2379
    livenessProbe:
      grpc:
        port: 2379
      initialDelaySeconds: 10

要使用 gRPC 探针,必须配置 port。 如果你想区分不同类型的探针和针对不同特性的探针,可以使用 service 字段。 你可以将 service 设置为 liveness, 并让你的 gRPC 健康检查端点对此请求作出与设置 servicereadiness 时不同的响应。 这使你能够使用同一端点完成不同种类的容器健康检查, 而不必监听两个不同的端口。 如果你想指定自己的自定义服务名称并同时指定一种探针类型, Kubernetes 项目建议你使用将两者连接起来的名称。 例如:myservice-liveness(使用 - 作为分隔符)。

说明:

与 HTTP 或 TCP 探针不同,你不能通过名称指定健康检查端口, 也不能配置自定义主机名。

配置问题(例如:端口或服务不正确、未实现健康检查协议) 被视为探针失败,类似于 HTTP 和 TCP 探针。

接下来