使用健康探针提升 Kubernetes 可靠性

KubernetesBeginner
立即练习

简介

Running 只表示容器进程存在,并不能证明应用已经准备好接收流量,也不能证明它具备自我恢复能力。Kubernetes 探针可以将应用自身的健康检查转化为控制器和 Service 的实际行为。

你将添加全部三种探针,观察就绪状态如何将 Pod 从 Service 端点中移除,修复该问题,并添加优雅关闭钩子。

请明确区分各探针的职责:启动探针用于保护初始化缓慢的应用;就绪探针决定 Pod 是否接收来自 Service 的流量;存活探针决定 kubelet 何时应重启卡住的容器。让一个探针承担所有职责,往往会导致不必要的重启,或让流量过早进入尚未准备好的应用。

部署带探针的应用

在本步骤中,部署 NGINX,并针对其命名为 http 的端口对 / 路径配置启动、就绪和存活检查。

由于三个探针都位于容器配置中,清单文件会更长。应用清单前,建议按以下顺序阅读:Deployment 和标签、容器及命名端口,然后是启动、就绪和存活探针。本实验中的三个检查使用同一个 HTTP 端点,但失败后的处理方式各不相同。

部分单行映射和列表使用 YAML 的紧凑「流式」写法,例如 {app: healthy-web}。花括号表示映射,方括号表示列表;它们与多行 YAML 写法含义相同。这里只在整个值仍然易于阅读时使用这种写法。

启动探针的阈值最多允许 24 秒(12 × 2),超过后 Kubernetes 才会判定启动失败。启动成功后,就绪探针每 3 秒检查一次,存活探针每 5 秒检查一次。

cd /home/labex/project/application-health
cat <<'EOF' > healthy-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: healthy-web
  namespace: practice-health
spec:
  replicas: 2
  selector:
    matchLabels: {app: healthy-web}
  template:
    metadata:
      labels: {app: healthy-web}
    spec:
      containers:
        - name: web
          image: nginx:1.27-alpine
          imagePullPolicy: IfNotPresent
          ports:
            - {name: http, containerPort: 80, protocol: TCP}
          startupProbe:
            httpGet: {path: /, port: http}
            failureThreshold: 12
            periodSeconds: 2
          readinessProbe:
            httpGet: {path: /, port: http}
            periodSeconds: 3
          livenessProbe:
            httpGet: {path: /, port: http}
            periodSeconds: 5
            failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
  name: healthy-web
  namespace: practice-health
spec:
  selector: {app: healthy-web}
  ports:
    - {name: http, port: 80, targetPort: http, protocol: TCP}
EOF

创建命名空间并应用清单。rollout status 会等待两个副本都满足就绪条件,而不仅仅是等待进程启动:

kubectl create namespace practice-health
kubectl apply -f healthy-web.yaml
kubectl rollout status deployment/healthy-web -n practice-health --timeout=60s

启动探针保护缓慢的初始化过程;就绪探针控制 Pod 是否可以接收流量;存活探针则允许在发生无法恢复的故障后重启容器。

如果发布顺利完成,你掌握的信息就不再只是「容器正在运行」:两个 Pod 都已经成功响应了就绪检查请求。

发布过程输出的最后一行是第一个健康状态证据:

Waiting for deployment "healthy-web" rollout to finish: 0 of 2 updated replicas are available...
Waiting for deployment "healthy-web" rollout to finish: 1 of 2 updated replicas are available...
deployment "healthy-web" successfully rolled out

中间的输出属于正常现象。Kubernetes 会随着每个 Pod 通过启动和就绪检查而报告进度;只有最后一行表示两个副本都已可用。

将就绪状态关联到 Service 端点

在本步骤中,检查 Pod 的就绪条件和 EndpointSlice。

先查看熟悉的 Pod 列表,然后将输出范围缩小到就绪条件和重启次数。自定义表格可以清楚地区分「未就绪」和「正在重启」:

kubectl get pods -n practice-health -l app=healthy-web
kubectl get pods -n practice-health -l app=healthy-web -o custom-columns='POD:.metadata.name,READY_CONDITION:.status.conditions[?(@.type=="Ready")].status,RESTARTS:.status.containerStatuses[0].restartCount'
kubectl get endpointslices -n practice-health -l kubernetes.io/service-name=healthy-web -o jsonpath='{range .items[].endpoints[*]}{.addresses[0]} ready={.conditions.ready}{"\n"}{end}'

两个地址都应显示 ready=true。就绪检查失败时,容器仍会继续运行,但其地址会从 Service 的正常路由中移除。

EndpointSlice 连接了 Service 选择器与实际的 Pod 地址。即使某个 Pod 未就绪,Service 仍然存在;真正阻止正常流量发送到该 Pod 的,是端点上的就绪条件。

典型的健康状态输出如下:

POD                            READY_CONDITION   RESTARTS
healthy-web-...                True              0
healthy-web-...                True              0
10.244.104.11 ready=true
10.244.104.12 ready=true

Pod 名称和 IP 地址会有所不同。需要关注的是它们之间的对应关系:两个 Ready=True 的 Pod 对应两个 ready=true 的端点,并且两个容器都没有发生重启。

观察就绪检查失败

在本步骤中,只将就绪检查路径改为一个不存在的 URL,以此主动制造故障。

这是一次受控故障实验。JSON Patch 只替换一个嵌套字段,因此启动和存活探针保持不变。NGINX 对 /not-ready 返回 HTTP 404,而就绪检查会将其判定为失败。

kubectl patch deployment healthy-web -n practice-health --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/not-ready"}]'
kubectl rollout status deployment/healthy-web -n practice-health --timeout=20s || true
kubectl get pods -n practice-health -l app=healthy-web
kubectl get endpointslices -n practice-health -l kubernetes.io/service-name=healthy-web -o jsonpath='{range .items[].endpoints[*]}{.addresses[0]} ready={.conditions.ready}{"\n"}{end}'

|| true 会将预期中的发布超时标记为成功,使 Shell 能够继续执行。新容器会运行起来,但就绪检查失败,因此 Deployment 无法达到可用状态。

本次测试得到的状态类似于:

error: timed out waiting for the condition
NAME                           READY   STATUS    RESTARTS
healthy-web-old-...            1/1     Running   0
healthy-web-old-...            1/1     Running   0
healthy-web-new-...            0/1     Running   0
...
10.244.104.17 ready=false

新的 Pod 处于 Running 状态但未就绪,也没有发生重启。两个旧的就绪 Pod 仍然保留,这是因为 Deployment 的滚动更新会保护服务可用性:当新版本无法就绪时,Kubernetes 不会继续替换更多健康副本。因此,发布超时同时证明了就绪行为和控制器的安全机制。在 EndpointSlice 输出中,新地址显示为 ready=false,所以 Service 的正常路由仍然只会使用健康端点。

修复就绪检查并验证流量

在本步骤中,恢复就绪检查路径,并等待 Service 端点重新可用。

使用上一步操作的反向补丁,这样实验中只改变一个变量。现在等待发布状态会成功,因为替换后的 Pod 能够对 / 返回 HTTP 200。

kubectl run health-client 会创建一个临时的独立 Pod,用于发起请求。--image 用于选择 BusyBox,--image-pull-policy=IfNotPresent 用于复用缓存中的镜像,--restart=Never 表示创建 Pod 而不是控制器。--rm -i 会保持命令附着运行,并在命令退出时删除 Pod;第二个 -- 用于分隔 kubectl 选项和容器内部执行的 wget 命令。

在 wget -qO- 中,-q 会隐藏进度消息,-O- 会将响应正文写入标准输出;管道随后将正文传给 head,只显示开头几行。URL 使用 Service 名称 healthy-web,集群 DNS 会在该命名空间内解析此名称。

kubectl patch deployment healthy-web -n practice-health --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/readinessProbe/httpGet/path","value":"/"}]'
kubectl rollout status deployment/healthy-web -n practice-health --timeout=60s
kubectl run health-client -n practice-health --image=busybox:1.36 --image-pull-policy=IfNotPresent --restart=Never --rm -i -- wget -qO- http://healthy-web | head

临时客户端使用集群 DNS。限制显示的 HTML 内容可以让验证结果更易读,同时成功的响应也证明就绪状态与 Service 路由保持一致。

响应开头应包含熟悉的 NGINX HTML:

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

在发布成功后看到 HTML,证明了完整的请求路径:DNS 成功解析 Service,Service 选择了一个就绪端点,而 NGINX 成功处理了请求。

完成后删除临时 Pod,避免在命名空间中留下诊断客户端。

添加优雅终止机制

在本步骤中,添加 preStop 钩子和终止宽限期。该钩子会短暂延迟关闭,使端点移除操作有时间完成后,进程才退出。

请分别阅读两个 JSON Patch 操作。第一个操作将 Pod 的终止时间预算设置为 20 秒;第二个操作为容器索引 0 添加一个持续 5 秒的休眠钩子。终止开始时,Kubernetes 会启动该钩子,同时剩余的宽限期仍在倒计时。

kubectl patch deployment healthy-web -n practice-health --type=json -p='[
  {"op":"add","path":"/spec/template/spec/terminationGracePeriodSeconds","value":20},
  {"op":"add","path":"/spec/template/spec/containers/0/lifecycle","value":{"preStop":{"sleep":{"seconds":5}}}}
]'
kubectl rollout status deployment/healthy-web -n practice-health --timeout=60s
kubectl get deployment healthy-web -n practice-health -o jsonpath='grace={.spec.template.spec.terminationGracePeriodSeconds}s preStop={.spec.template.spec.containers[0].lifecycle.preStop.sleep.seconds}s{"\n"}'

JSON 补丁只修改指定的两个字段,因此探针和其他容器设置保持不变。优雅终止机制与就绪探针相互配合:前者让 Kubernetes 有时间在 Pod 关闭完成前将其从流量中移除,后者则控制 Pod 是否可以接收流量。

最后的 JSONPath 输出应为 grace=20s preStop=5s。5 秒的钩子运行时间在 20 秒预算之内,还会为 NGINX 正常退出留下充足时间。

由于新的 preStop 钩子会有意增加 5 秒的关闭时间,发布过程中可能会出现 1 old replicas are pending termination 等提示。最终输出仍应为:

deployment "healthy-web" successfully rolled out
grace=20s preStop=5s

总结

你已经将启动、就绪和存活检查与 Kubernetes 的实际行为关联起来,观察了就绪失败如何在不停止容器的情况下移除端点,恢复了流量,并添加了优雅终止机制。这些机制让健康状态不仅可见,还能够直接驱动系统行为。