前言

在 Kubernetes 集群中,Pod 是运行应用的基本单元。然而,Pod 中的容器可能会因为各种原因变得不健康,例如进程崩溃、死锁、响应缓慢或依赖服务故障。Kubernetes 提供了一套强大的自愈能力,能够自动发现并处理这些故障。默认情况下,K8s 通过监控容器进程的退出状态码来决定是否重启容器。但这种机制过于简单,无法检测到“进程依然运行但服务不可用”的场景(如 HTTP 返回 500 错误)。为了解决这一问题,Kubernetes 引入了Liveness 探测Readiness 探测两种健康检查机制,帮助用户精细控制容器的生命周期和流量接入。本文将系统介绍这两种探测的工作原理、配置方法,以及它们在副本更新和滚动发布中的实际应用。


一、K8s 中默认的健康检查机制

Kubernetes 默认的健康检查依赖于容器主进程的生命周期。每个容器启动时都会运行一个进程(由 Dockerfile 的 CMD 或 ENTRYPOINT 指定)。如果该进程以非零状态码退出,Kubernetes 就会认为容器发生故障,并根据 Pod 的 restartPolicy 策略执行重启。

下面通过一个简单示例演示这一过程。创建一个 Pod 配置文件 healthcheck-pod.yml

apiVersion: v1
kind: Pod
metadata:
  name: pod-healthcheck
  labels:
    test: healthcheck-test
spec:
  restartPolicy: OnFailure
  containers:
  - name: healthcheck
    image: busybox
    args:
    - /bin/sh
    - -c
    - sleep 10; exit 1   # 容器启动10秒后主动退出,返回非零状态码

执行创建并查看 Pod 状态:

[root@master pod]# kubectl apply -f healthcheck-pod.yml
[root@master pod]# kubectl get pod
NAME               READY   STATUS             RESTARTS   AGE
pod-healthcheck    0/1     CrashLoopBackOff   5          8m

可以看到,Pod 反复重启,状态变为 CrashLoopBackOff。这是因为容器每次启动 10 秒后都会退出(exit 1),Kubernetes 认为容器故障,不断尝试重启。

这种默认机制虽然有效,但局限性明显:许多应用程序在发生内部错误(如线程死锁、内存泄漏、响应超时)时,主进程并不会退出,此时 Kubernetes 无法感知故障,也不会采取任何自愈动作。因此,我们需要更精细的健康检查手段。


二、Liveness 探测:检测并重启不健康的容器

Liveness 探测用于判断容器是否处于存活状态。如果探测失败,Kubernetes 会认为容器已“死亡”,并根据 restartPolicy 杀掉并重启该容器。它非常适合检测那些进程仍在运行但已无法正常工作的场景(如死锁、无限循环)。

2.1 Liveness 探测示例

下面是一个使用 exec 方式进行 Liveness 探测的 Pod 配置文件 healthcheck-liveness-pod.yml

apiVersion: v1
kind: Pod
metadata:
  name: pod-liveness
  labels:
    test: liveness-test
spec:
  restartPolicy: OnFailure
  containers:
  - name: liveness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/check; sleep 30; rm -rf /tmp/check; sleep 600
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/check
      initialDelaySeconds: 10
      periodSeconds: 5

说明

  • 容器启动后首先创建 /tmp/check 文件,30 秒后删除,随后保持运行 600 秒。

  • livenessProbe 定义了探测方式:每隔 5 秒执行一次 cat /tmp/check

  • initialDelaySeconds: 10 表示容器启动后等待 10 秒才开始第一次探测,避免启动过程中的误判。

  • 如果 cat 命令返回 0(文件存在),探测成功;如果返回非 0(文件不存在),探测失败。连续失败 3 次后,Kubernetes 将杀掉容器并重启。

执行该 Pod:

[root@master pod]# kubectl apply -f healthcheck-liveness-pod.yml
[root@master pod]# kubectl describe pod pod-liveness

在事件日志中可以看到,前 30 秒探测成功,之后 /tmp/check 被删除,探测开始失败,最终触发容器重启:

[root@master pod]# kubectl get pod
NAME            READY   STATUS             RESTARTS   AGE
pod-liveness    0/1     CrashLoopBackOff   6          12m

2.2 Liveness 探测的常用配置参数

参数 说明 默认值
initialDelaySeconds 容器启动后延迟多久开始探测 0
periodSeconds 探测间隔时间(秒) 10
timeoutSeconds 探测超时时间(秒) 1
successThreshold 连续成功多少次才认为成功 1
failureThreshold 连续失败多少次才认为失败 3

三、Readiness 探测:控制流量接入

Readiness 探测用于判断容器是否已经准备好接收业务流量。与 Liveness 探测不同,Readiness 探测失败不会重启容器,而是将 Pod 的 IP 从对应 Service 的 Endpoints 列表中移除,使其不再接收请求。一旦探测恢复成功,Pod 会被重新加入负载均衡。

Readiness 探测的配置语法与 Liveness 探测完全一致,只是将 livenessProbe 替换为 readinessProbe

3.1 Readiness 探测示例

healthcheck-readiness.yml

apiVersion: v1
kind: Pod
metadata:
  name: pod-readiness
  labels:
    test: readiness-test
spec:
  restartPolicy: OnFailure
  containers:
  - name: readiness
    image: busybox
    args:
    - /bin/sh
    - -c
    - touch /tmp/check; sleep 30; rm -rf /tmp/check; sleep 600
    readinessProbe:
      exec:
        command:
        - cat
        - /tmp/check
      initialDelaySeconds: 10
      periodSeconds: 5

运行该 Pod 并观察 READY 状态变化:

[root@master pod]# kubectl apply -f healthcheck-readiness.yml
[root@master pod]# kubectl get pod -w

  • 刚创建时,READY 列为 0/1(未就绪)。

  • 10 秒后首次探测成功,READY 变为 1/1

  • 30 秒后 /tmp/check 被删除,连续 3 次探测失败后,READY 再次变为 0/1

通过 kubectl describe 可以看到 Readiness probe failed 的警告事件。

3.2 Liveness 与 Readiness 的比较

探测类型 探测失败后的行为 主要用途
Liveness 探测 重启容器 修复死锁、内存泄漏等“进程假死”问题
Readiness 探测 从 Service 的 Endpoints 中移除 Pod 避免流量发送到未就绪或正在初始化的 Pod

两者相互独立,可以单独使用,也可以同时配置。


四、健康检查在副本更新中的应用

在多副本场景下,更新 Deployment 时,新创建的 Pod 通常需要一个“准备时间”,例如加载缓存、建立数据库连接等。如果在新 Pod 尚未就绪时就将流量导入,会导致请求失败。此时,Readiness 探测可以完美解决这个问题。

下面是一个结合了 Deployment 和 Service 的完整示例,其中定义了基于 HTTP 的 Readiness 探测:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myweb
spec:
  replicas: 3
  selector:
    matchLabels:
      run: web
  template:
    metadata:
      labels:
        run: web
    spec:
      containers:
      - name: web
        image: httpd
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            scheme: HTTP
            path: /index.html
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    run: web
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 80

说明

  • readinessProbe 使用 httpGet 方式,请求 http://<Pod-IP>:80/index.html

  • 如果 HTTP 返回码在 200~400 之间,认为探测成功;否则失败。

  • 容器启动后等待 10 秒开始探测。只有探测成功后,Pod 才会被加入到 web-service 的 Endpoints 中开始接收流量。

  • 探测会持续进行,如果后续连续失败 3 次,Pod 会再次从 Service 中移除,直到下次探测成功。


五、健康检查在滚动更新中的应用

滚动更新是 Kubernetes 实现零停机发布的核心机制。但在实际更新过程中,新版本 Pod 可能因为配置错误、依赖缺失等导致永远无法就绪。如果没有 Readiness 探测,Kubernetes 会认为新 Pod 是健康的(因为进程未退出),从而逐渐替换掉所有旧 Pod,最终造成服务全面不可用。

Readiness 探测可以在滚动更新中充当“安全闸”:只有通过探测的新 Pod 才会被允许接收流量,并且只有在新 Pod 就绪后,才会继续销毁旧 Pod。这保证了即使新版本有问题,集群中仍有大部分旧版本实例在提供服务。

5.1 模拟更新失败场景

appv1.yml(正常版本):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
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/check; sleep 30000
        readinessProbe:
          exec:
            command:
            - cat
            - /tmp/check
          initialDelaySeconds: 10
          periodSeconds: 5

appv2.yml(有缺陷的版本,无法创建 /tmp/check 文件):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
spec:
  replicas: 10
  selector:
    matchLabels:
      run: app
  template:
    metadata:
      labels:
        run: app
    spec:
      containers:
      - name: app
        image: busybox
        args:
        - /bin/sh
        - -c
        - sleep 30000   # 永远不会创建 /tmp/check
        readinessProbe:
          exec:
            command:
            - cat
            - /tmp/check
          initialDelaySeconds: 10
          periodSeconds: 5

执行步骤:

[root@master check]# kubectl apply -f appv1.yml
[root@master check]# kubectl get deploy
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
app-deployment   10/10   10           10          1m

[root@master check]# kubectl apply -f appv2.yml   # 尝试更新到有问题的版本
[root@master check]# kubectl get deploy
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
app-deployment   8/10    5            8           4m

观察输出:

  • READY 显示为 8/10:只有 8 个 Pod 处于就绪状态(全部是旧版本)。

  • UP-TO-DATE 显示为 5:有 5 个新版本 Pod 被创建,但它们始终无法通过 Readiness 探测。

  • AVAILABLE 显示为 8:当前可用的 Pod 数量为 8。

查看 Pod:

[root@master check]# kubectl get pod
NAME                            READY   STATUS        RESTARTS   AGE
app-deployment-78cc7bd6d6-4dt8t 0/1     ContainerCreating   0     13s
...(5个新Pod处于ContainerCreating或Running但0/1)
app-deployment-85b79dd7bb-6wpwx 1/1     Running             0     4m12s
...(8个旧Pod保持Running)
app-deployment-85b79dd7bb-czj7x 1/1     Terminating         0     4m12s
...(2个旧Pod正在被销毁)

由于新 Pod 无法就绪,滚动更新被卡住,业务流量仍然全部由旧 Pod 处理,避免了服务中断。这正是 Readiness 探测的保护作用。

5.2 控制滚动更新速率:maxSurge 与 maxUnavailable

为什么上述例子中会出现 13 个 Pod(8 旧 + 5 新)?这是因为 Deployment 的滚动更新策略中包含了两个关键参数:

  • maxSurge:滚动更新过程中,允许超出期望副本数的最大 Pod 数量。可以是绝对值(如 2)或百分比(向上取整)。默认 25%。

  • maxUnavailable:滚动更新过程中,允许不可用 Pod 的最大数量(相对于期望副本数)。可以是绝对值或百分比(向下取整)。默认 25%。

在本例中,期望副本数 replicas=10

  • maxSurge 默认 25% ⇒ roundUp(10 * 0.25) = 3 ⇒ 允许最多 10+3 = 13 个 Pod。

  • maxUnavailable 默认 25% ⇒ roundDown(10 * 0.25) = 2 ⇒ 允许最多 2 个 Pod 不可用 ⇒ 至少保持 8 个可用 Pod。

因此,Kubernetes 会先创建 3 个新 Pod(达到上限 13),然后销毁 2 个旧 Pod(使可用数降到 8)。由于新 Pod 始终无法就绪,可用数量无法超过 8,因此永远不会销毁更多旧 Pod,最终停留在 8 旧 + 5 新的状态。

5.3 回滚失败更新

当发现更新出现问题时,可以快速回滚到上一个正常版本:

[root@master check]# kubectl rollout history deployment app-deployment
[root@master check]# kubectl rollout undo deployment app-deployment --to-revision=1
[root@master check]# kubectl get deploy
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
app-deployment   10/10   10           10          10m

回滚后,所有 Pod 恢复到旧版本,服务恢复正常。

5.4 自定义 maxSurge 和 maxUnavailable

用户可以在 Deployment 的 spec.strategy.rollingUpdate 中自定义这两个参数,例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
spec:
  strategy:
    rollingUpdate:
      maxSurge: 35%
      maxUnavailable: 35%
  # ... 其余配置

合理设置这两个值可以在更新速度和稳定性之间取得平衡。


结尾

Kubernetes 的健康检查机制(Liveness 探测和 Readiness 探测)为容器化应用提供了远超“进程存活”级别的可观测性和自愈能力。通过合理配置,我们可以:

  • 利用 Liveness 探测让 K8s 自动重启陷入死锁或异常状态的容器。

  • 利用 Readiness 探测控制 Pod 何时接收流量,避免请求发送到未就绪的实例。

  • 在滚动更新中,Readiness 探测能够阻止有问题的版本替换全部旧实例,保障业务连续性。

  • 通过 maxSurge 和 maxUnavailable 调整滚动更新的速率,实现更精细的发布控制。

在实际生产环境中,强烈建议为每个关键服务配置合适的 Readiness 探测和 Liveness 探测,它们是保障服务高可用和发布安全的重要基石。希望本文能帮助你深入理解并熟练运用 Kubernetes 的健康检查机制。

更多推荐