Kubernetes Pod频繁重启是什么原因?事件日志与健康检查分析

监控告警显示某个 Deployment 的 Pod RESTARTS 计数一路往上走,业务时断时续,但 kubectl get pods 里 STATUS 却写着奇怪的 CrashLoopBackOff。Pod 频繁重启是 K8s 里最常见的应用层故障,也是最容易被误判的一类——因为容器已经退出了,现场只剩下一次 veteran 记录,看 kubectl logs 又只能看到最新一次启动的日志。本文讲清楚如何正确读取 Pod 的事件与前一次运行的日志,如何通过退出码快速区分 OOMKilled、健康检查失败与应用自身崩溃,以及各类型问题对应的处理动作。

一、先读状态:Pod STATUS 字段的含义

kubectl get pods 输出的 STATUS 列是第一手线索,不同值直接指向不同方向。

STATUS含义优先怀疑方向
CrashLoopBackOff容器反复启动后反复退出,K8s 正在按退避时间等待重启应用启动失败、OOM、配置缺失
Error容器已退出且退出码非 0,重启策略未触发或已达上限应用崩溃且 restartPolicy 为 OnFailure/Never
OOMKilled容器因超出内存限制被内核 killlimits.memory 设置过小或应用内存泄漏
ImagePullBackOff / ErrImagePull镜像拉取失败镜像名错、tag 不存在、私有仓库认证失败
CreateContainerConfigError容器配置的 ConfigMap/Secret 不存在挂载了不存在的 key 或对象
Running 但 READY 为 0/1容器在跑但未通过就绪检查readinessProbe 配置不当、依赖未就绪
Evicted被 kubelet 因节点资源压力驱逐节点磁盘/内存不足
Init:Error / Init:CrashLoopBackOff初始化容器失败initContainer 逻辑失败或镜像问题
# 1. 看所有 Pod 的状态与重启次数
kubectl get pods -A -o wide
kubectl get pods -n <命名空间> --sort-by=.status.containerStatuses[0].restartCount

# 2. 实时观察状态变化(-w 相当于 watch)
kubectl get pods -n <命名空间> -w

# 3. 直接取出所有 Pod 的重启次数
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.status.containerStatuses[0].restartCount}{"\n"}{end}' | sort -k3 -rn

# 4. 只看某个 Pod 的完整状态树
kubectl get pod  -n  -o yaml | sed -n '/^status:/,$p'

二、核心命令:describe 里的三段现场

kubectl describe pod 是解决 Pod 重启问题最核心的命令,重点看三段:容器状态(Last State)、事件(Events)、以及容器相互 ID 与镜像。

# 核心命令
kubectl describe pod  -n <命名空间>

# 只提取容器状态部分(含上一次退出的退出码与原因)
kubectl get pod  -n  -o jsonpath='{range .status.containerStatuses[*]}{"容器: "}{.name}{"\n"}{"  就绪: "}{.ready}{"\n"}{"  重启次数: "}{.restartCount}{"\n"}{"  当前状态: "}{.state.running.startedAt}{.state.waiting.reason}{.state.terminated.reason}{"\n"}{"  上次状态: "}{.lastState.terminated.reason}{"\n"}{"  退出码: "}{.lastState.terminated.exitCode}{"\n"}{"  退出时间: "}{.lastState.terminated.finishedAt}{"\n"}{end}'

# 只看 init 容器状态
kubectl get pod  -n  -o jsonpath='{.status.initContainerStatuses}'

# 拉取与该 Pod 相关的事件(Events 字段可能已滚动丢失,用这条兜底)
kubectl get events -n  --field-selector involvedObject.name= --sort-by=.lastTimestamp

# 查看 Deployment 级别的事件(副本级问题在这里)
kubectl describe deployment  -n 

Events 段落里最常见的几类条目及其含义:

  • Liveness probe failed: HTTP probe failed with statuscode: 500 → 存活探针失败,kubelet 会杀掉容器。
  • Readiness probe failed: Get "http://...": dial tcp ... connection refused → 就绪探针失败,只摘流量,不会导致重启。
  • Back-off restarting failed container → 已经失败多次,正在退避。
  • OOMKilled 出现在 Last State 的 Reason 里 → 内存超限。
  • Failed to pull image "...": rpc error: code = NotFound → 镜像或 tag 不存在。
  • Error: container's runAsUser breaks non-root policy → 安全策略冲突。

三、用退出码快速定位:这是最快的一步

容器退出码是判断故障类型最直接的证据。注意 K8s 记录的退出码含义:

退出码含义常见原因处理方向
0正常退出命令执行完就结束(如 shell 脚本、一次性任务)改容器命令使其常驻,或改用 Job
1应用通用错误代码异常、配置解析失败看 --previous 日志
2命令用法错误启动脚本参数写错检查 command/args
125容器无法运行docker/containerd 层面执行失败检查镜像与运行时
126命令无法执行权限不足、脚本无可执行位chmod +x 或调整 fsGroup
127命令不存在镜像精简后缺少 sh/curl 等换镜像或改用 exec 探针
137 (128+9)被 SIGKILL 杀死OOMKilled,或主动 kill调大 memory limit 或查泄漏
139 (128+11)段错误 SIGSEGV程序内存访问越界升级版本或回退
143 (128+15)被 SIGTERM 终止正常停止流程(滚动更新、缩容)属正常现象
# 查看"上一次"运行的日志 —— 这是排查重启问题最关键的命令
kubectl logs  -n  --previous

# 带上时间戳,判断崩溃发生的时间点
kubectl logs  -n  --previous --timestamps

# 多容器时必须指定容器名
kubectl logs  -n  -c <容器名> --previous

# 查看 init 容器日志
kubectl logs  -n  -c init-

# 查看当前日志最后若干行
kubectl logs  -n  --tail=200 -f

# Pod 一直起不来时,用它临时打开一个能常驻的容器去看文件系统
kubectl debug -it  -n  --image=busybox:1.36 --target=<容器名>

# 宿主机层面确认 OOM(节点内存不足导致的 Container 级 OOM 在这里能看到)
dmesg -T | grep -iE 'oom|killed process' | tail -20
journalctl -k --since "1 hour ago" | grep -i oom

四、OOMKilled:内存超限排查

OOMKilled 的判定很干脆:describe pod 里 Last State 的 Reason 显示 OOMKilled,Exit Code 显示 137。但要区分两种:

  • 容器级 OOM:容器用量超过了 resources.limits.memory,cgroup 直接杀掉容器内进程。K8s 会记录 OOMKilled。
  • 节点级 OOM:宿主机整体内存耗尽,内核 OOM Killer 挑一个进程杀掉,不一定是超 limit 的那个。dmesg 里能看到 Killed process。
# 1. 确认当前配置的资源限制
kubectl get pod  -n  -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{"  requests: "}{.resources.requests}{"\n"}{"  limits: "}{.resources.limits}{"\n"}{end}'

# 2. 查看实际用量(需要 metrics-server)
kubectl top pod  -n  --containers
kubectl top pod -A --sort-by=memory | head -20

# 3. 调整 Deployment 的内存限制
kubectl set resources deployment  \
  --requests=memory=512Mi,cpu=500m \
  --limits=memory=1Gi,cpu=1000m -n 

# 4. 观察滚动更新是否完成
kubectl rollout status deployment/ -n 

# 5. 查看该 Pod 的 QoS 等级(决定资源紧张时被驱逐的顺序)
kubectl get pod  -n  -o jsonpath='{.status.qosClass}'
# Guaranteed: requests == limits(且每项都设)→ 最难被杀
# Burstable: 部分设置或不相等
# BestEffort: 完全没设 → 最先被杀

判断标准:把 memory limit 设为应用在峰值时 RSS 用量的 1.3-1.5 倍。设得太小会频繁 OOMKilled,设得太大则节点资源超卖、其他 Pod 受害。Java 应用尤其要注意 -Xmx 与容器 limit 的关系:JVM 只认 -Xmx,如果 -Xmx 接近 limit,元空间、线程栈、堆外内存的开销容易把总用量顶到 limit 之上,典型的建议是 -Xmx 不超过 limit 的 75%。

五、健康检查(探针)配置不当

探针是 Pod 频繁重启的第二大原因,而且最容易误伤。必须分清三种探针的作用——很多人把 readiness 配置错了却说"探针导致重启"。

探针失败后果是否会重启容器典型用途
livenessProbe(存活探针)kill 容器并按 restartPolicy 重启会检测死锁、进程假死
readinessProbe(就绪探针)从 Service Endpoint 摘除,停止转发流量不会检测依赖未就绪、预热未完成
startupProbe(启动探针)阻塞 liveness/readiness;失败则 kill 重启会慢启动应用(如大型 Java)

关键参数与默认值(默认值经常是踩坑根源):

  • initialDelaySeconds:容器启动后多久开始探测,默认 0。慢启动应用必须设大,否则刚起来就被判死。
  • periodSeconds:探测间隔,默认 10 秒。
  • timeoutSeconds:单次探测超时时间,默认 1 秒。接口偶发慢就判失败,非常容易误伤,建议放宽到 3-5 秒。
  • failureThreshold:连续失败多少次才认定为失败,默认 3。
  • successThreshold:失败后连续成功多少次才算恢复,默认 1。
# 错误的典型写法:delay 0 + timeout 1s,慢启动 Java 必然被打死
# livenessProbe:
#   httpGet: { path: /health, port: 8080 }

# 推荐写法:给足启动时间,三个探针分工明确
# readinessProbe:
#   httpGet: { path: /ready, port: 8080 }
#   initialDelaySeconds: 10
#   periodSeconds: 5
#   timeoutSeconds: 3
#   failureThreshold: 3
# livenessProbe:
#   httpGet: { path: /healthz, port: 8080 }
#   initialDelaySeconds: 60
#   periodSeconds: 10
#   timeoutSeconds: 5
#   failureThreshold: 5
# startupProbe:
#   httpGet: { path: /healthz, port: 8080 }
#   failureThreshold: 30
#   periodSeconds: 10

# 在线 kubectl 修改探针配置
kubectl edit deployment  -n 

# 手工验证探针路径是否真的可用(进容器里测)
kubectl exec -it  -n  -- curl -sv http://127.0.0.1:8080/healthz

# 镜像里没有 curl 时用 wget 或 /dev/tcp
kubectl exec -it  -n  -- wget -qO- http://127.0.0.1:8080/healthz

# 镜像极其精简(distroless)时的替代方案:改用 tcpSocket 或 exec 探针
# exec:
#   command: ["cat", "/tmp/healthy"]

还要注意探针接口本身的复杂度。如果 /healthz 里做了数据库查询、缓存连通性检查,一次数据库抖动就会导致全部 Pod 被判死重启,形成雪崩。正确的做法是:liveness 探针只检查进程自身能否响应(极简),把依赖检查放到 readiness 探针里。

5.3 重启策略与退避机制

K8s 的 restartPolicy 默认为 Always,容器退出后会被 kube-controller-manager 不断重启,重启间隔按指数退避:10s → 20s → 40s → 80s → 160s → 300s(上限)。这就是 CrashLoopBackOff 里 "BackOff" 的来源。成功运行一段时间后重置。

# 查看当前重启策略
kubectl get pod  -n  -o jsonpath='{.spec.restartPolicy}'
# Always(默认,Deployment 只能是 Always) / OnFailure / Never
# Job/CronJob 通常配置为 OnFailure 或 Never

# 临时让 Pod 停下来方便排查(把副本数缩到 0)
kubectl scale deployment  --replicas=0 -n 
# 排查完恢复
kubectl scale deployment  --replicas=3 -n 

# 重建所有 Pod(配置已修好的情况下最快恢复手段)
kubectl rollout restart deployment  -n 

# 回退到上一个版本(配置改错导致重启的标准止损方式)
kubectl rollout undo deployment/ -n 
kubectl rollout history deployment/ -n 

六、标准化排查步骤清单

  1. kubectl get pods 看 STATUS 与 RESTARTS,确认是 CrashLoopBackOff、OOMKilled 还是 ImagePullBackOff。
  2. kubectl describe pod,重点读 Last State(Reason + Exit Code)和 Events。
  3. 取上一次运行的日志:kubectl logs --previous,这是唯一能拿到崩溃现场的途径。
  4. 按退出码判断方向:137→内存超限、127→命令不存在、1→应用异常、0→命令执行完就结束。
  5. 若读 kubelet:确认为 137 后区分容器级 OOM 与节点级 OOM,后者查 dmesg | grep oom 和节点 free -h。
  6. 若探针失败:确认是 liveness 还是 readiness(只有 liveness 会重启),检查 initialDelaySeconds 与 timeoutSeconds。
  7. 若探针路径无效:进容器 curl 手工验证,镜像无 curl 时改 tcpSocket 或 exec。
  8. 模拟器 CronJob/Job 场景确认 restartPolicy 是否为 OnFailure/Never。
  9. 确认镜像拉取与配置:ImagePullSecrets、ConfigMap/Secret 是否存在且 key 正确。
  10. 需要止损时:kubectl rollout undo 回退版本,或 scale --replicas=0 暂停。
  11. 修复后验证:kubectl rollout status 观察至少 10 分钟,确认 RESTARTS 不再增长。

小结

Pod 频繁重启的排查有一条铁律:永远先看 kubectl describe pod 的 Last State(尤其是退出码)和 kubectl logs --previous,而不是先看当前日志。当前日志属于已经重启成功的新进程,几乎不含崩溃信息。三大数据因占比大致是:应用自身错误或依赖不可用(约四成)、内存超限 OOMKilled(约三成)、探针配置不当(约两成)。

避坑建议:

  • 不要把依赖检查放进 livenessProbe,数据库抖动会导致全部 Pod 同时重启,比故障本身更严重。
  • 区分 readiness 和 liveness:readiness 失败只是摘流量,不会重启;看错方向会白排查半天。
  • timeoutSeconds 默认 1 秒太激进,生产环境建议 3-5 秒以上。
  • 慢启动应用必须用 startupProbe,或者把 liveness 的 initialDelaySeconds 设到足够长。
  • 核心业务设置 Guaranteed QoS(requests == limits),降低节点资源紧张时被优先杀掉的概率。
  • Java 应用注意 -Xmx 与 limits 的比例,建议 -Xmx 不超过 limit 的 75%,给堆外内存留空间。
  • 善用 kubectl rollout undo,改了配置导致重启时,回退比现场 debug 成本低得多。
  • 给 RESTARTS 增长配告警,而不是等业务报障:5 分钟内重启次数超过 3 次即触发。

延伸阅读:如果整个节点上的 Pod 一起异常、伴随节点 NotReady,问题已不在 Pod 层,而应转到节点状态、kubelet 与 CNI 网络的排查。