搞懂 K8s 健康检查:探针机制与最佳实践
前言
在 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 的健康检查机制。
更多推荐
所有评论(0)