K8s 探针超时设短了反而触发重启:CrashLoopBackOff 的排查路径
本文摘要:liveness 探针默认超时 1 秒,启动阶段健康检查持续失败,反复触发容器重启。需从集群事件定位失败原因,实测健康接口耗时后再选择修复方案。
一、问题与结论
一个部署在 K8s 上的 Python 服务,启动后需要约 25 秒完成数据库连接池初始化和缓存预热。应用本身运行正常,但 Pod 始终处于CrashLoopBackOff状态,重启次数持续增长。
根因是livenessProbe未显式设置timeoutSeconds,走的是1 秒默认值。健康接口/health首次调用会触发 25 秒初始化,kubelet 等待 1 秒后判定超时失败。连续 3 次失败后容器被 SIGKILL,重启后初始化从头开始——形成死循环。
结论:单纯调大timeoutSeconds是掩盖症状。正确做法是引入startupProbe,在时间维度上隔离启动阶段与运行阶段,让livenessProbe保持对真正运行期故障的检测灵敏度。
二、排查与选择依据
探针失败发生在 kubelet 层面,不一定写入应用日志,直接看应用日志容易遗漏根因。排查顺序应为"Kubernetes 事件 → 探针配置 → 应用响应时间",从外部机制反推内部行为,而非从日志倒推。
Step 1:确认 Pod 状态。
kubectl get pods-lapp=slow-start-appStep 2:查看 Events,寻找Unhealthy事件。
kubectl describe pod<pod-name>如果看到以下事件,说明是探针超时导致的重启:
Warning Unhealthy Liveness probe failed: Get "http://10.0.0.5:8080/health": context deadline exceeded Normal Killing Container app failed liveness probe, will be restartedcontext deadline exceeded看起来像网络问题,实际是探针超时。区分Liveness probe failed(触发容器重启)和Readiness probe failed(仅影响流量路由)至关重要——后者不会导致CrashLoopBackOff。
Step 3:确认探针参数是否被显式设置。
kubectl get pod<pod-name>-ojsonpath='{.spec.containers[0].livenessProbe}'如果timeoutSeconds为1或缺失,基本可确认根因。
Step 4:查看上一个容器的日志,确认是否在初始化阶段被杀。
kubectl logs<pod-name>--previous日志可能刚打到初始化开始就被截断——容器已被 SIGKILL,应用侧不会收到优雅关闭信号。
Step 5:从 Pod 内部实测健康接口的真实响应时间。
kubectlexec-it<pod-name>--curl-w"\ntime: %{time_total}s\n"http://localhost:8080/health如果time_total超过 1 秒,矛盾得到验证。
替代方案与取舍
| 方案 | 选择条件 | 代价 | 不适用场景 |
|---|---|---|---|
调大timeoutSeconds至 5 秒 | 健康接口响应在 1-5 秒波动,无渐进恶化趋势 | 真正 hang 死时,kubelet 需多等 5 秒才能判定失败 | 启动时间超过 5 秒的应用 |
引入startupProbe(本文方案) | 启动初始化时间长且不稳定(缓存预热、连接池建立) | Pod 从创建到 Ready 变长,Service 有一段时间无可用后端 | 启动时间极短(< 2 秒)且稳定,仅增加配置复杂度 |
拆分轻量/healthz端点做 liveness | 健康检查逻辑本身较重(需查数据库) | 需修改应用代码,增加端点 | 所有探针指向同一端点的简单应用 |
不应使用本文方案的情况:如果应用启动时间短且稳定,startupProbe只会增加配置维护成本,直接设好timeoutSeconds即可。
三、关键原理
探针参数是一个整体,不能孤立调整:
最大容忍时间 = periodSeconds × failureThreshold默认值下10 × 3 = 30秒——容器启动后 30 秒内 liveness 探针判定不健康并杀容器。而timeoutSeconds默认仅1 秒,如果健康接口首次调用需 25 秒,无论探测几次,每次都会在 1 秒内超时。
startupProbe的工作逻辑:
- 容器启动后,只有 startupProbe 在运行,liveness/readiness 被暂停
- startupProbe 成功后,liveness/readiness 才开始执行
- 总等待时间 =
failureThreshold × periodSeconds(如12 × 5 = 60秒) - 启动阶段的慢响应不会触发容器重启
容器被杀后,重启间隔逐次翻倍:10 秒、20 秒、40 秒……最长 5 分钟。这就是CrashLoopBackOff状态的来源——kubelet 主动退避,避免反复杀容器消耗资源。
四、可运行示例
环境:Kubernetes v1.28+,Python 3.12 + Flask 3.1。
模拟一个启动后需要 25 秒才能让/health在 1 秒内响应的服务:
# app.pyimporttimefromflaskimportFlask,jsonify app=Flask(__name__)startup_complete=False@app.before_requestdefcheck_startup():globalstartup_completeifnotstartup_complete:time.sleep(25)startup_complete=TruereturnNone@app.route('/health')defhealth():returnjsonify({"status":"ok"}),200if__name__=='__main__':app.run(host='0.0.0.0',port=8080)构建镜像:
FROM python:3.12-slim WORKDIR /app RUN pip install flask==3.1.0 COPY app.py . EXPOSE 8080 CMD ["python", "app.py"]错误配置——不设timeoutSeconds,无startupProbe:
# bad-probe.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:slow-start-appspec:replicas:1selector:matchLabels:app:slow-start-apptemplate:metadata:labels:app:slow-start-appspec:containers:-name:appimage:your-registry/slow-start-app:latestports:-containerPort:8080livenessProbe:httpGet:path:/healthport:8080# timeoutSeconds 走默认 1,periodSeconds 走默认 10,failureThreshold 走默认 3部署并观察:
kubectl apply-fbad-probe.yaml kubectl get pods-lapp=slow-start-app-w预期输出(基于官方文档行为推演,未在真实集群验证):
NAME READY RESTARTS STATUS slow-start-app-7f8b9c-xxxxx 0/1 3 (25s ago) CrashLoopBackOff正确配置——增加startupProbe,显式设置所有探针参数:
# good-probe.yamlapiVersion:apps/v1kind:Deploymentmetadata:name:slow-start-appspec:replicas:1selector:matchLabels:app:slow-start-apptemplate:metadata:labels:app:slow-start-appspec:containers:-name:appimage:your-registry/slow-start-app:latestports:-containerPort:8080startupProbe:httpGet:path:/healthport:8080timeoutSeconds:30periodSeconds:5failureThreshold:12successThreshold:1livenessProbe:httpGet:path:/healthport:8080timeoutSeconds:3periodSeconds:10failureThreshold:3successThreshold:1readinessProbe:httpGet:path:/healthport:8080timeoutSeconds:3periodSeconds:10failureThreshold:2successThreshold:1验证:
kubectl delete deployment slow-start-app kubectl apply-fgood-probe.yaml kubectl get pods-lapp=slow-start-app-w实际输出(基于文档推演,未在真实集群验证):
NAME READY RESTARTS STATUS slow-start-app-6d4a2f-xxxxx 1/1 0 Running五、验证结果与边界
常见失败及原因:
| 现象 | 原因 | 修复 |
|---|---|---|
startupProbe已配置但仍被重启 | 集群版本 < v1.18,该功能未默认启用 | 升级集群(v1.20+ 稳定) |
| startupProbe 成功后短暂 Ready 又 Unhealthy | livenessProbe 的timeoutSeconds仍过小,偶发慢请求导致失败 | 调至 3-5 秒,覆盖偶发波动 |
Events 中只有Readiness probe failed | readinessProbe 不触发容器重启,仅影响流量路由 | 检查应用是否真正不健康,而非探针超时 |
边界条件:
- 本文所有 Pod 输出均为基于官方文档行为的推演,未在真实集群验证
startupProbe从 v1.16 进入 Alpha,v1.18 Beta 并默认启用,v1.20 起稳定,集群 ≥ v1.18 均可直接使用- kubelet 自身负载高时,探针执行可能被延迟,排查时需要排除
terminationGracePeriodSeconds默认 30 秒,应用收到 SIGTERM 后关闭慢时,重启可能与新探针周期重叠
思考
startupProbe总容忍时间设得过大时,真正启动失败的应用要等多久才能被发现,是否需要额外监控兜底?多副本部署中每个 Pod 启动需较长时间,Service 期间无可用后端,是否需在 Ingress 层增加缓冲?
参考资料
Kubernetes 官方文档 — Configure Liveness, Readiness and Startup Probes
Kubernetes API Reference — Pod v1 Probe
Kubernetes 官方文档 — Pod Lifecycle 与 RestartPolicy