解决步骤
下面这条排查路径,从单 Pod 到集群级,按性价比从高到低排列。每一步都附可直接复制执行的命令和 YAML 片段。
Step 1:拿到容器退出日志
第一步永远先看日志,别瞎猜。
kubectl logs -n --previous
--previous 标志会拉取上一次容器的日志——因为当前容器已经重启过,日志很可能已经被覆盖。如果连 --previous 都拉不到任何输出,说明容器在入口点(ENTRYPOINT)执行之前就已经崩溃了,问题大概率出在镜像或挂载上。
日志量太大时,配合 tail 和 grep 过滤:
kubectl logs -n --previous --tail=100 | grep -i error
Exit Code 对照表(这块算是高频检索型内容,建议收藏):
| Exit Code |
含义 |
典型原因 |
| 0 |
正常退出 |
通常是应用主动退出,可能是任务完成式 Pod |
| 1 |
通用错误 |
应用层异常、未捕获异常 |
| 126 |
命令不可执行 |
权限问题、文件不是可执行格式 |
| 127 |
命令/文件不存在 |
路径错误、依赖缺失、脚本未 chmod +x |
| 137 |
SIGKILL(信号 9) |
OOMKilled、节点资源紧张被 kubelet 驱逐 |
| 139 |
SIGSEGV |
段错误,内存越界访问,通常是 native crash |
补充几个生产中常见的"非主流" Exit Code:
- 134 (SIGABRT):通常来自 Go runtime panic、C++ 的 abort()、断言失败
- 143 (SIGTERM):被 kubelet 优雅终止,多见于滚动升级或缩容时未等完 terminationGracePeriodSeconds
- 255:SSH 或某些 daemon 应用的特殊退出码,需结合日志判断
Step 2:检查资源限制是否触达
内存溢出(OOMKilled)是 CrashLoopBackOff 的高频原因,没有之一。查看上一轮终止状态:
kubectl describe pod | grep -A 5 "Last State"
典型输出:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Exit Code 137 = SIGKILL,几乎可以肯定是被 OOM Killer 干掉或被 kubelet 主动驱逐。对照 Pod Spec 检查:
resources:
limits:
memory: "256Mi" # 若应用实际需 512Mi,这里就不足
requests:
memory: "128Mi"
内存排查的几个进阶点:
1. JVM 堆内存陷阱:JVM 默认最大堆内存可能达到容器内存上限的 50% 甚至更高,加上 Metaspace、线程栈、DirectBuffer,实际占用很容易撑爆 limits。务必显式设置 -Xmx。
2. 内存泄漏:limits 设得合理但仍 OOM,可能是应用内存泄漏。配合 kubectl top pod 看趋势曲线。
3. CPU throttle 引发"假崩溃":CPU limits 限得太狠,长时间 throttle 会让健康检查超时,看起来像应用挂了。
补充命令(持续监控内存):
kubectl top pod -n --containers
Step 3:验证依赖服务可达性
应用启动时依赖数据库、Redis、消息队列或其他 Service 时,必须确认网络策略和 DNS 解析正常。一个非常实用的调试手法:
kubectl debug -it --image=nicolaka/netshoot -- bash
# 在调试容器内执行
nslookup
curl -v http://:/health
wget -qO- http://:/health
nicolaka/netshoot 镜像预装了 dig、nslookup、curl、tcpdump 等工具,比 busybox 好用太多,强烈推荐。
如果 DNS 解析失败,先看 CoreDNS:
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
配置建议:在应用启动参数中加上明确的启动超时时间(如 STARTUP_TIMEOUT=60s),避免依赖服务还没就绪时,应用傻乎乎地直接退出。
Step 4:确认镜像与仓库认证
镜像拉取失败时,describe pod 输出里通常会带 Failed to pull image 事件,伴随 ErrImagePull 或 ImagePullBackOff 状态。
验证镜像是否真的存在:
crictl images | grep
验证私有仓库认证:
kubectl get secret -n | grep docker-registry
kubectl describe pod | grep "ImagePullSecrets"
私有仓库认证排查清单(这几条几乎踩遍了所有坑):
- Secret 类型必须是 kubernetes.io/dockerconfigjson
- imagePullSecrets 必须在 Pod 所在 namespace 下,且名字匹配
- 凭证过期要重新 docker login 并更新 Secret
- 使用 ECR / ACR / GCR 等云厂商仓库时,需配置对应的 IAM 角色或 Workload Identity
- 镜像架构不匹配(比如 ARM 节点拉了 amd64 镜像)也会触发拉取失败
Step 5:定位启动命令 / 健康检查 / Init 容器配置错误
command 和 args 字段覆盖镜像默认入口点时,最容易出现语法或路径错误:
spec:
containers:
- name: api
command: ["/app/start.sh"] # 若脚本不存在,容器立即退出(Exit 127)
args: ["--config", "/etc/config.yaml"]
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:8080"]
initialDelaySeconds: 10
periodSeconds: 5
健康检查 initialDelaySeconds 设得过短,会让应用还没启动完就被判失败,逐步调大该值验证。
启动命令排查要点:
- 确认 workingDir 与命令路径匹配
- 启动脚本必须有可执行权限(容器里 chmod +x,或 Dockerfile 里 RUN chmod +x)
- 配置文件路径在 Pod 内要真实存在(用 kubectl exec 进去看一眼)
Init 容器排查(容易被遗漏的高频场景):
Init 容器失败会导致主容器根本不会启动,但状态可能表现为 Init:CrashLoopBackOff 或 Init:Error,很多新手会误以为是主容器的问题。
# 查看 Init 容器日志
kubectl logs -c --previous
# 跳过 Init 容器调试主容器
kubectl debug -it --copy-to= --container=main -- sh
常见 Init 容器失败原因:等待下游服务就绪的脚本超时、数据库迁移失败、配置渲染(envsubst、helm template)报错。
Step 6:大规模集群下的批量排查
当多个不相关 Pod 同时进入 CrashLoopBackOff 时,就要跳出单 Pod 视角,进入集群级排查。集群级故障的典型特征:
- 多个 namespace 的 Pod 同步出现问题
- Pod 重启时间高度同步(精确到同一分钟)
- 伴随节点负载异常或网络丢包
# 1. 看节点资源是否枯竭
kubectl top nodes
kubectl describe node | grep -A 10 "Allocated resources"
# 2. 看 DNS / 网络插件
kubectl get pods -n kube-system -l k8s-app=kube-dns -o wide
kubectl get pods -n kube-system -l k8s.cilium.io/cilium-children=true -o wide # Cilium
# 3. etcd 健康
kubectl exec -n kube-system etcd- -- etcdctl endpoint health
kubectl exec -n kube-system etcd- -- etcdctl endpoint status --write-out=table
近两年常见的集群级 CrashLoopBackOff 诱因,值得单独点名:
1. 节点 NotReady 驱逐链式反应:某个节点 NotReady 后,Pod 被驱逐到其他节点,但其他节点也资源紧张,引发连锁重启。用 kubectl get nodes -o wide 看 Ready 状态,关注 kubectl describe node 里的 MemoryPressure、DiskPressure、PIDPressure。
2. containerd / dockerd hang:容器运行时本身僵死,新 Pod 起不来,老 Pod 退出后无法清理。这时 SSH 到节点上 systemctl status containerd 或 crictl info 看是否响应。
3. kube-controller-manager 异常:Deployment、ReplicaSet 控制器出问题会导致 Pod 反复创建/删除,看起来像 CrashLoopBackOff。看 kubectl logs -n kube-system kube-controller-manager-。
4. CNI 插件异常:Cilium、Calico 自身崩溃,新 Pod 分配不到网络,初始化失败退出。
用 eBPF 工具替代盲排查(2026 年的更优解)
传统 kubectl exec + tcpdump 的方式在大规模集群里效率太低,建议上专业的可观测性工具:
| 工具 |
用途 |
适用场景 |
| Pixie (px.dev) |
自动 eBPF 抓包 + 应用协议解析 |
HTTP/gRPC/MySQL/Redis 流量可视化,无需修改代码 |
| Cilium Hubble |
基于 eBPF 的网络可观测性 |
服务间调用链、DNS 解析失败、DNS 延迟、丢包定位 |
| BCC / bpftrace |
自定义 eBPF 探针 |
深度内核级问题,需要懂 BPF 脚本 |
| LitmusChaos |
混沌工程 |
主动注入故障验证 Pod 韧性 |
| Inspektor Gadget |
Kubernetes 专属 eBPF 工具集 |
排查 TCP 重传、DNS 延迟、文件访问等 |
例:用 Pixie 看 Pod 的 DNS 查询情况:
px dnsLookupSummary --pod
例:用 Cilium Hubble 看流量:
hubble observe --namespace --follow
Sidecar 容器自身 CrashLoopBackOff(Istio / Linkerd 时代的高频坑)
如果你用 Service Mesh,Istio Envoy、Linkerd Proxy 这种 Sidecar 自身 CrashLoopBackOff 会让主容器永远卡在 "not ready",而主容器本身其实没毛病。
排查要点:
# 查看 Sidecar 容器日志
kubectl logs -c istio-proxy --previous
# 查看 Sidecar 启动配置
kubectl get pod -o jsonpath='{.spec.containers.name}'
Kubernetes 1.28+ 引入了 Sidecar Container 的 Lifecycle 改进(1.30+ 进一步成熟),Sidecar 现在可以独立启动/停止,与主容器解耦。如果你的集群版本较新,可以用 kubectl describe pod 里的 containerStatuses.ready 字段分别查看每个 sidecar 的就绪状态。
Gateway API 迁移期的特殊坑
如果你正在从 Ingress 迁移到 Gateway API,新部署的 Gateway / HTTPRoute 资源可能因为 CRD 未安装、GatewayClass 不匹配导致关联 Pod(通常是 Envoy Gateway、Contour)起不来。检查:
kubectl get gatewayclass
kubectl describe gateway -n