Kubernetes Pod CrashLoopBackOff 故障排查实战

一、故障现象

在 Kubernetes 集群中,Pod 频繁重启是最常见的故障之一。当 Pod 进入 CrashLoopBackOff 状态时,表示容器启动后很快退出,Kubernetes 不断尝试重启但始终失败。

典型表现

1
2
3
4
$ kubectl get pods -n <namespace>
NAME READY STATUS RESTARTS AGE
my-app-6d8f9c7b5-x2k4m 0/1 CrashLoopBackOff 15 45m
my-app-6d8f9c7b5-j7n3p 0/1 CrashLoopBackOff 12 45m

状态码含义

状态 含义
CrashLoopBackOff 容器反复崩溃,Kubernetes 采用指数退避策略重启
Error 容器退出码非 0
OOMKilled 容器因内存超限被杀死
Completed 容器正常退出(退出码 0)

二、排查流程

步骤 1:查看 Pod 详细信息

1
2
3
4
5
# 查看 Pod 详细状态
kubectl describe pod <pod-name> -n <namespace>

# 查看 Pod 事件日志
kubectl get events --field-selector involvedObject.name=<pod-name> -n <namespace>

关键信息点:

  • Last State:上次退出状态和退出码
  • Reason:退出原因(Error、OOMKilled、Completed 等)
  • Exit Code:退出码(0=正常,1-255=异常)
  • Events:调度、拉取镜像、启动等事件

步骤 2:查看容器日志

1
2
3
4
5
6
7
8
# 查看当前容器日志
kubectl logs <pod-name> -n <namespace>

# 查看之前容器的日志(重启前的日志)
kubectl logs <pod-name> -n <namespace> --previous

# 多容器 Pod 指定容器
kubectl logs <pod-name> -c <container-name> -n <namespace>

步骤 3:检查退出码

常见退出码含义:

退出码 含义 可能原因
0 正常退出 应用主动退出
1 应用程序错误 代码异常、配置错误
126 命令不可执行 权限问题、文件不存在
127 命令未找到 PATH 配置错误、依赖缺失
137 SIGKILL (128+9) OOMKilled 或手动杀死
143 SIGTERM (128+15) 优雅终止超时
255 退出状态超出范围 严重错误

步骤 4:检查资源限制

1
2
3
4
5
6
# 查看 Pod 资源配置
kubectl get pod <pod-name> -n <namespace> -o yaml

# 查看节点资源使用情况
kubectl top pod <pod-name> -n <namespace>
kubectl top node

检查要点:

  • resources.limits.memory 是否过低
  • resources.requests.memory 是否合理
  • 节点内存是否充足

步骤 5:检查探针配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 不合理的探针配置示例
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5 # 太短,应用未启动完成
periodSeconds: 10
timeoutSeconds: 1 # 太短,响应慢时被误杀

readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

建议配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30 # 给应用足够启动时间
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3

readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3

三、常见故障场景与解决方案

场景 1:应用启动失败

现象:

1
2
3
Last State:     Terminated
Reason: Error
Exit Code: 1

可能原因:

  • 配置文件缺失或格式错误
  • 数据库连接失败
  • 端口被占用
  • 依赖服务不可用

排查命令:

1
2
3
4
5
# 查看日志中的错误信息
kubectl logs <pod-name> -n <namespace> --previous | grep -i error

# 进入容器调试(如果镜像有 shell)
kubectl run -it --rm debug --image=<pod-image> --restart=Never -n <namespace> -- /bin/sh

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 添加启动等待或重试逻辑
spec:
containers:
- name: app
command: ["/bin/sh", "-c"]
args:
- |
echo "Waiting for database..."
until nc -z db-host 5432; do
sleep 2
done
echo "Database ready, starting app..."
exec /app/start.sh

场景 2:内存溢出 (OOMKilled)

现象:

1
2
3
Last State:     Terminated
Reason: OOMKilled
Exit Code: 137

排查命令:

1
2
3
4
5
# 查看 Pod 内存使用历史
kubectl describe pod <pod-name> -n <namespace> | grep -A 5 "Last State"

# 查看节点内存压力
kubectl describe node <node-name> | grep -A 10 "Allocated resources"

解决方案:

1
2
3
4
5
6
7
8
9
10
spec:
containers:
- name: app
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi" # 适当提高限制
cpu: "500m"

应用层优化:

  • Java 应用:调整 JVM 堆大小 -Xmx512m
  • Node.js 应用:设置 NODE_OPTIONS="--max-old-space-size=512"
  • Python 应用:检查内存泄漏,使用生成器替代列表

场景 3:健康检查失败

现象:

1
2
3
4
5
6
Last State:     Terminated
Reason: Error
Exit Code: 143
Events:
Type Reason Message
Warning Unhealthy Liveness probe failed: HTTP probe failed with statuscode: 503

排查命令:

1
2
3
4
5
# 手动测试健康检查端点
kubectl exec <pod-name> -n <namespace> -- curl -v http://localhost:8080/health

# 查看应用启动时间
kubectl logs <pod-name> -n <namespace> | grep -i "started\|ready"

解决方案:

1
2
3
4
5
6
7
8
9
10
# 调整探针参数
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 增加启动等待时间
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3 # 允许失败 3 次
successThreshold: 1

场景 4:镜像拉取失败

现象:

1
2
3
4
Events:
Type Reason Message
Warning Failed Error: ImagePullBackOff
Warning Failed Failed to pull image "myregistry/app:v1.0": unauthorized

排查命令:

1
2
3
4
5
# 查看 Pod 事件
kubectl describe pod <pod-name> -n <namespace>

# 检查 ImagePullSecrets
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.imagePullSecrets}'

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建镜像拉取密钥
kubectl create secret docker-registry regcred \
--docker-server=<registry-url> \
--docker-username=<username> \
--docker-password=<password> \
-n <namespace>

# 在 Pod 中引用
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: <registry-url>/app:v1.0

场景 5:配置缺失或错误

现象:

1
2
3
Error: config file "/etc/app/config.yaml" not found

Error: required environment variable DB_HOST not set

排查命令:

1
2
3
4
5
# 查看 Pod 配置
kubectl get pod <pod-name> -n <namespace> -o yaml

# 检查 ConfigMap 和 Secret
kubectl get configmap,secret -n <namespace>

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 正确挂载 ConfigMap
spec:
containers:
- name: app
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: db_host
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secret
key: db_password
volumeMounts:
- name: config-volume
mountPath: /etc/app
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config

场景 6:权限问题

现象:

1
2
3
Error: permission denied: /var/log/app.log

Exit Code: 126

排查命令:

1
2
3
# 查看 Pod 安全上下文
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.securityContext}'
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.containers[0].securityContext}'

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
volumeMounts:
- name: log-volume
mountPath: /var/log/app
volumes:
- name: log-volume
emptyDir: {}

场景 7:Init Container 失败

现象:

1
2
3
4
5
Status:     Pending
Init Containers:
init-db:
State: Waiting
Reason: CrashLoopBackOff

排查命令:

1
2
3
4
5
# 查看 Init Container 日志
kubectl logs <pod-name> -c <init-container-name> -n <namespace>

# 查看 Init Container 状态
kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Init Containers"

解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
spec:
initContainers:
- name: init-db
image: busybox
command: ['sh', '-c']
args:
- |
until nc -z db-host 5432; do
echo "Waiting for database..."
sleep 2
done
echo "Database is ready!"

四、调试技巧

技巧 1:临时禁用重启策略

1
2
3
4
# 临时修改 Pod 重启策略为 Never(用于调试)
kubectl get pod <pod-name> -n <namespace> -o yaml | \
sed 's/restartPolicy: Always/restartPolicy: Never/' | \
kubectl replace --force -f -

技巧 2:使用调试容器

1
2
3
4
5
# 使用 ephemeral container 调试(Kubernetes 1.23+)
kubectl debug -it <pod-name> -n <namespace> --image=busybox --target=<container-name>

# 或创建临时调试 Pod
kubectl run debug-pod --rm -it --image=busybox --restart=Never -n <namespace> -- /bin/sh

技巧 3:增加日志详细程度

1
2
3
4
5
6
# 在应用配置中增加日志级别
env:
- name: LOG_LEVEL
value: "debug"
- name: DEBUG
value: "true"

技巧 4:使用 kubectl debug 命令

1
2
# Kubernetes 1.20+ 支持
kubectl debug pod/<pod-name> -it --image=ubuntu --target=<container-name>

五、预防措施

1. 完善的健康检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 区分 liveness 和 readiness
livenessProbe:
# 检测应用是否存活,失败则重启
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10

readinessProbe:
# 检测应用是否就绪,失败则从 Service 移除
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

2. 合理的资源配额

1
2
3
4
5
6
7
8
# 基于实际使用情况设置
resources:
requests:
memory: "256Mi" # 保证的最小资源
cpu: "100m"
limits:
memory: "512Mi" # 允许的最大资源
cpu: "200m"

3. 优雅关闭处理

1
2
3
4
5
6
7
8
9
# 设置终止宽限期
spec:
terminationGracePeriodSeconds: 30 # 默认 30 秒
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"] # 等待流量排空

4. 配置变更管理

1
2
3
4
5
6
7
# 使用 ConfigMap 热更新
kubectl create configmap app-config --from-file=config.yaml

# 在 Deployment 中引用
envFrom:
- configMapRef:
name: app-config

5. 监控告警配置

1
2
3
4
5
6
7
8
9
10
11
# Prometheus 告警规则
groups:
- name: kubernetes-apps
rules:
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 * 5 > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.pod }} is crash looping"

六、排查清单

遇到 CrashLoopBackOff 时,按以下顺序排查:

  • kubectl describe pod 查看事件和退出码
  • kubectl logs --previous 查看崩溃前日志
  • 检查退出码含义(1/126/127/137/143)
  • 验证资源配置(requests/limits)
  • 检查健康探针配置(initialDelaySeconds)
  • 验证 ConfigMap/Secret 是否正确挂载
  • 检查依赖服务是否可用
  • 验证镜像拉取权限
  • 检查存储卷权限和空间
  • 查看节点资源压力情况

七、总结

Pod CrashLoopBackOff 是 Kubernetes 中最常见的故障之一,排查关键在于:

  1. 快速定位:通过 describelogs --previous 获取第一手信息
  2. 理解退出码:不同退出码指向不同问题根源
  3. 系统性排查:从应用到基础设施逐层检查
  4. 预防为主:合理的资源配置、健康检查、优雅关闭

掌握这套排查方法,可以快速解决 90% 以上的 Pod 重启问题。


参考文档: