Kubernetes Pod 反复重启:区分 CrashLoopBackOff、探针失败与 OOM

3 次浏览1 条回复

适用于 Kubernetes 1.28+、kubectl 1.28+。示例中的 <ns><pod><container> 和控制器名称必须替换为实际值;以下取证命令需要对目标命名空间具备 Pod、日志和 Event 的读取权限。CrashLoopBackOff 只是 kubelet 对重复失败施加退避后的等待状态,不是根因。先保留现场,不要一开始就删除 Pod;删除后,上一容器实例的日志通常无法再从原 Pod 获取。

1. 固定对象、时间线与失败容器

date -u
kubectl version --client
kubectl -n <ns> get pod <pod> -o wide
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> get pod <pod> -o jsonpath='{range .status.initContainerStatuses[*]}init {.name}{"\trestarts="}{.restartCount}{"\twaiting="}{.state.waiting.reason}{"\tlast="}{.lastState.terminated.reason}{"\texit="}{.lastState.terminated.exitCode}{"\n"}{end}{range .status.containerStatuses[*]}app {.name}{"\trestarts="}{.restartCount}{"\twaiting="}{.state.waiting.reason}{"\tlast="}{.lastState.terminated.reason}{"\texit="}{.lastState.terminated.exitCode}{"\n"}{end}'

先确认失败的是 init container、主容器还是 sidecar。restartCount 是当前 Pod 内该容器的累计值;控制器替换 Pod 后会重新计数,因此必须同时记录 Pod 名称、UID 和创建时间。describe 底部的 Event 有助于定位拉取镜像、挂载、探针和调度问题,但 Event 有保留期限,不能作为唯一证据。

2. 同时读取当前与上一实例日志

kubectl -n <ns> logs <pod> -c <container> --timestamps --tail=200
kubectl -n <ns> logs <pod> -c <container> --previous --timestamps --tail=200
kubectl -n <ns> get events \
  --field-selector involvedObject.kind=Pod,involvedObject.name=<pod> \
  --sort-by=.metadata.creationTimestamp

--previous 只有在同一 Pod 中存在上一容器实例时才有内容。若应用启动后立刻退出,上一实例日志通常比当前日志更关键。先按 UTC 时间对齐应用日志、容器终止原因和 Event;不要只凭最后一行异常判断根因。

3. 区分应用退出、探针重启与 OOM

  • lastState.terminated.reason=Error:结合退出码和应用文档检查参数、配置、依赖与权限。退出码 1 只表示程序报告失败,不能进一步推断原因。
  • Event 先出现 liveness probe failed,随后出现 Killing:重点检查探针的路径、端口、协议、超时和执行成本。readiness 探针失败只会把 Pod 移出就绪端点,本身不会重启容器;startup 探针成功前,liveness 与 readiness 不会开始执行。
  • reason=OOMKilled:核对内存限制、应用峰值和节点侧记录。退出码 137 表示进程收到 SIGKILL,并不单独证明是 OOM,仍需与 OOMKilled、Event 或节点日志相互印证。

可继续做只读检查:

kubectl -n <ns> get pod <pod> -o jsonpath='{range .spec.containers[*]}{.name}{"\trequests="}{.resources.requests}{"\tlimits="}{.resources.limits}{"\n"}{end}'
kubectl -n <ns> top pod <pod> --containers

kubectl top 依赖 Metrics Server,只显示近期采样,无法还原已经结束的内存峰值;命令无数据时不能据此排除 OOM。不要仅通过放宽探针或内存限制来掩盖持续增长的资源使用。

4. 核对控制器与最近变更

kubectl -n <ns> get pod <pod> -o jsonpath='{.metadata.uid}{"\n"}{range .metadata.ownerReferences[*]}{.kind}{"/"}{.name}{"\n"}{end}{range .spec.containers[*]}{.name}{"\timage="}{.image}{"\n"}{end}'
kubectl -n <ns> rollout history deployment/<deployment>

Pod 的直接所有者可能是 ReplicaSet、StatefulSet、DaemonSet 或 Job,不要假定一定存在 Deployment。将首次失败时间与镜像、配置、Secret、探针及资源限制的变更时间对应;环境变量形式的配置不会因源对象更新而自动刷新到既有容器。检查清单时注意输出中可能包含敏感值,避免把完整 Pod YAML 或 Secret 内容粘贴到公开记录。

5. 修复后的闭环

在允许的变更窗口按实际控制器发布修复,不要把手工修改单个 Pod 当作持久修复。随后至少验证:

kubectl -n <ns> get pod <pod>
kubectl -n <ns> describe pod <pod>
kubectl -n <ns> logs <pod> -c <container> --timestamps --tail=100
kubectl -n <ns> rollout status deployment/<deployment> --timeout=5m

rollout status 仅适用于实际由 Deployment 管理的工作负载。验证窗口应超过原来的典型崩溃周期及探针启动时间:Pod 持续 Ready,容器 restartCount 不再增长,Event 与日志无新增失败,并且从真实服务入口完成一次无副作用的健康检查。只有这些条件同时满足,才能认为重启循环已经闭环。

补充一个 OOM 分支:还要区分容器触及内存限制、节点级 OOM 与 kubelet 因内存压力驱逐;三者都可能表现为工作负载中断,但修复方向不同。Kubernetes 1.28+ 可先记录 Pod 所在节点、QoS、终止信息与节点条件:

kubectl -n <ns> get pod <pod> -o jsonpath='{.spec.nodeName}{"\tqos="}{.status.qosClass}{"\tphase="}{.status.phase}{"\treason="}{.status.reason}{"\tmessage="}{.status.message}{"\n"}'
kubectl -n <ns> get pod <pod> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\tlastReason="}{.lastState.terminated.reason}{"\texit="}{.lastState.terminated.exitCode}{"\tfinished="}{.lastState.terminated.finishedAt}{"\n"}{end}'
kubectl get node <node> -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{"\treason="}{.reason}{"\tlastTransition="}{.lastTransitionTime}{"\n"}{end}'

Pod 的 status.reason=Evicted 或 Event 中的 eviction 信息更偏向 kubelet 驱逐;容器 lastReason=OOMKilled 说明终止与 OOM 相关,但仅凭这一字段仍不能判断是容器内存上限还是节点全局内存不足。若具备节点日志读取权限,且节点由 systemd 托管,可在对应 UTC 时间窗核对内核与 kubelet 日志:

sudo journalctl -k --since '<UTC-start>' --until '<UTC-end>' --no-pager | grep -Ei 'out of memory|oom-kill|killed process'
sudo journalctl -u kubelet --since '<UTC-start>' --until '<UTC-end>' --no-pager | grep -Ei 'evict|memorypressure|oom'

修复后除了观察 restartCount,还应确认节点 MemoryPressure 未持续为 True、没有新增驱逐事件,并在至少一个原故障周期内观察内存使用趋势。若问题来自节点压力,单纯提高容器 limit 反而可能扩大影响范围。