排查 Kubernetes Pod 和容器问题

KubernetesBeginner
立即练习

简介

Pod 状态显示为 Pending 或 CrashLoopBackOff 时,这只是表象,并不代表根本原因。有效的故障排查应按照一致的顺序收集证据:对象配置、条件、事件、各容器状态、日志以及运行中检查。

你将诊断两个预先准备好的故障并修复它们,随后检查一个运行正常的多容器 Pod。目标是掌握一套可复用的排查方法,而不是死记某个修复方案。

处理每个故障时都使用相同的顺序:先查看整体情况,锁定一个对象,读取其当前状态和事件,再检查容器级证据,最后修复负责管理它的控制器。这样可以避免随意修改,也能帮助你说明修复为何有效。

从 Pod 状态入手

在本步骤中,你将识别哪些工作负载运行正常,哪些需要进一步调查。先查看整个命名空间的情况。-o wide 会保留熟悉的表格格式,同时增加 Pod IP 和分配节点等有用列。

cd /home/labex/project/pod-troubleshooting
kubectl get deployments,pods -n practice-debug -o wide

正常的 Pod 应显示为 2/2 Running。其中一个 Deployment Pod 应出现镜像拉取问题,而 worker Pod 则会反复重启。

测试环境中的典型结果如下所示(Pod 后缀、创建时间和重启次数会有所不同):

NAME                                 READY   STATUS             RESTARTS   AGE
pod/broken-image-58758b4d69-c7j7g    0/1     ImagePullBackOff   0          33s
pod/crashy-worker-5557cfb974-xwpst   0/1     Error              2          33s
pod/web-with-sidecar                 2/2     Running            0          33s

如果 worker 当前显示为 Error 而不是 CrashLoopBackOff,不必担心。它会短暂运行后退出,并在 Kubernetes 再次启动它之前等待一段时间,因此你观察时看到的状态可能会变化。不断增加的 RESTARTS 值更能说明该容器正在反复失败。

不必读取完整的 Pod YAML,直接查看容器级别的等待原因:

kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[*].ready,WAITING_REASON:.status.containerStatuses[*].state.waiting.reason,RESTARTS:.status.containerStatuses[*].restartCount'

[*] 表示「列表中的每一项」,因此同一张表也适用于多容器 Pod。先利用这次查看结果,决定下一步执行哪条诊断命令。

例如,WAITING_REASON=ImagePullBackOff 表明问题可能出在镜像配置或镜像仓库访问上。worker 可能在最近一次尝试处于 Terminated 状态时没有等待原因;因此,这张表只是排查起点,并不能完成全部诊断。

此时不要进行修复。镜像故障和反复重启的 worker 是两种不同的症状,需要收集不同的证据。好的第一条命令应该在不预设原因的前提下缩小排查范围。

诊断镜像拉取失败

在本步骤中,你将从状态和事件入手,追踪镜像拉取失败的原因。先保存自动生成的 Pod 名称,确保后续命令都针对同一个对象:

IMAGE_POD=$(kubectl get pods -n practice-debug -l app=broken-image -o jsonpath='{.items[0].metadata.name}')
echo "$IMAGE_POD"
kubectl describe pod "$IMAGE_POD" -n practice-debug

从底部向上阅读输出。Events 部分应显示 Kubernetes 无法拉取 nginx:not-a-real-tag。直接确认配置的镜像:

kubectl get pod "$IMAGE_POD" -n practice-debug -o jsonpath='{.spec.containers[0].image}{"\n"}'
kubectl events -n practice-debug --for "pod/${IMAGE_POD}"

事件说明失败的操作,而 JSONPath 查询则能确认导致问题的目标镜像。两者结合使用,可以避免把临时的镜像仓库问题误判为永久无效的标签。

在测试环境中,关键行类似如下:

Image:          nginx:not-a-real-tag
State:          Waiting
  Reason:       ErrImagePull
Failed to pull image "nginx:not-a-real-tag": ... manifest unknown
Error: ImagePullBackOff

ErrImagePull 表示一次镜像拉取尝试失败。ImagePullBackOff 表示 Kubernetes 现在会延迟一段时间后再重试同一操作。manifest unknown 消息与无效标签共同将症状和原因联系起来。

应修复 Deployment,而不是直接编辑生成的 Pod。kubectl set image 会修改 Pod 模板,因此 Deployment 控制器会替换失败的 Pod:

kubectl set image deployment/broken-image web=nginx:1.27-alpine -n practice-debug
kubectl rollout status deployment/broken-image -n practice-debug --timeout=60s

命令成功结束时,应显示:

deployment.apps/broken-image image updated
deployment "broken-image" successfully rolled out

旧 Pod 是可随时替换的临时对象;Deployment 模板才是持久的期望状态。滚动更新完成后,应出现新的 Pod 名称,并且 Deployment 应报告有一个可用副本。

读取当前日志和先前日志

在本步骤中,你将诊断一个启动后立即退出的容器。先找到它的 Pod,并检查重启信息:

WORKER_POD=$(kubectl get pods -n practice-debug -l app=crashy-worker -o jsonpath='{.items[0].metadata.name}')
kubectl get pod "$WORKER_POD" -n practice-debug
kubectl describe pod "$WORKER_POD" -n practice-debug

对于反复重启的容器,普通的 kubectl logs 会读取当前这次运行的日志。--previous 会要求容器运行时返回紧接当前运行之前、已经终止的那次运行的日志:

kubectl logs "$WORKER_POD" -n practice-debug -c worker
kubectl logs "$WORKER_POD" -n practice-debug -c worker --previous || \
  echo "The previous container log is no longer available; continue with the current log and describe output."

当前或先前的输出应显示应用自身打印的消息:

configuration missing

当新的运行实例在你来得及检查失败实例之前就已启动时,--previous 尤其有用。不过,它提供的是历史证据,并不保证始终可用:本地运行时可能已经删除了对应的容器记录。如果看到 unable to retrieve container logs,这是预期情况。此时结合当前日志和 describe 输出进行判断;其中的 Last State、Exit Code 和 Restart Count 仍能证明该进程曾反复退出。

-c worker 用于明确指定容器。通过替换失败的命令,让它持续运行,从而修复对应的控制器:

kubectl patch deployment crashy-worker -n practice-debug --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/command","value":["sh","-c","echo worker-ready; while true; do sleep 30; done"]}]'
kubectl rollout status deployment/crashy-worker -n practice-debug --timeout=60s

这条补丁命令可能看起来比较复杂,可以从内向外理解:path 选中第一个容器的 command,op: replace 表示要执行替换操作,value 提供替换后的命令数组。你只修改了一个字段,Deployment 的其他内容保持不变。

这个 JSON 补丁会精确替换 Deployment Pod 模板中的一个字段。在生产环境中,也应在源清单中完成相同的修改。

请等待滚动更新状态,因为命令会立即修改期望状态,但 Pod 的替换和就绪过程是异步发生的。API 成功返回并不等于工作负载已经健康。

在多容器 Pod 中定位目标容器

在本步骤中,你将检查一个由多个容器组成、且每个容器承担不同职责的 Pod。先列出容器名称:

kubectl get pod web-with-sidecar -n practice-debug -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}'

JSONPath 中的 range 会遍历容器列表。分别读取每个容器的日志:

kubectl logs web-with-sidecar -n practice-debug -c reporter --tail=5
kubectl logs web-with-sidecar -n practice-debug -c web --tail=5

--tail=5 会将输出限制为最后 5 行。reporter 通常会反复打印 reporter-alive 消息。NGINX 可能显示启动提示,也可能暂时没有请求日志;这两种情况都正常,因为还没有客户端向它发送 HTTP 请求。

在 web 容器中执行命令:

kubectl exec web-with-sidecar -n practice-debug -c web -- nginx -v

测试所使用的镜像返回:

nginx version: nginx/1.27.5

如果镜像已更新,具体补丁版本可能会变化;重要的是该命令确实在 web 容器中成功执行。

第一个 -- 表示 kubectl 选项到此结束。它后面的所有内容,都是要在所选容器中执行的命令。

理解这一点很重要:-c web 是由 kubectl 解释的选项,而 nginx -v 则是在容器内部运行的命令。如果在多容器 Pod 中省略 -c,kubectl 可能会选择默认容器,导致你看到错误进程的证据。

同一个 Pod 中的容器共享网络命名空间,因此 BusyBox reporter 也可以通过回环地址访问 NGINX。在 wget -qO- 中,-q 用于隐藏进度消息,-O- 用于将响应正文写入标准输出。管道会把正文传给 head,后者只保留开头几行:

kubectl exec web-with-sidecar -n practice-debug -c reporter -- wget -qO- http://127.0.0.1 | head

响应应以 <!DOCTYPE html> 开头。这是一种可复用的诊断模式,适用于后续挑战:在预先准备好的 Pod 内执行一个简单的 HTTP 客户端,无需暴露新的外部端口,就能检查应用行为。

从多个角度确认恢复情况

在本步骤中,你将验证修复控制器后是否生成了健康的新 Pod。结合检查控制器可用性和 Pod 就绪状态:

kubectl get deployments,pods -n practice-debug
kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount,OWNER:.metadata.ownerReferences[0].name'

最后,检查最近的警告事件是否仍在描述正在发生的故障:

--field-selector type=Warning 要求 API 服务器只返回 Warning 事件。随后,--sort-by=.metadata.creationTimestamp 会按照创建时间从旧到新排序,帮助你区分历史故障和当前状态。

kubectl get events -n practice-debug --field-selector type=Warning --sort-by=.metadata.creationTimestamp

恢复后,旧的警告仍可能保留。应以当前 Pod 条件和控制器可用性作为当前健康状况的依据,而将事件用于解释系统是如何进入该状态的。

完成两项修复后,当前状态的输出应大致如下:

NAME                            READY   UP-TO-DATE   AVAILABLE
deployment.apps/broken-image    1/1     1            1
deployment.apps/crashy-worker   1/1     1            1

NAME                                 READY   STATUS    RESTARTS
pod/broken-image-...                 1/1     Running   0
pod/crashy-worker-...                1/1     Running   0
pod/web-with-sidecar                 2/2     Running   0

你仍可能看到早先的 Failed、BackOff,甚至集群启动初期的 FailedCreatePodSandBox 警告。请对照对象名称和时间戳:这些警告指向的是已经被替换的 Pod,而新 Pod 已处于 Running 状态,两个 Deployment 也都可用。事件记录的是历史,并不是实时健康仪表板。

最终结论应综合当前证据:两个 Deployment 均可用,所有预期容器均已就绪,并且没有 Pod 仍处于镜像拉取失败或崩溃循环状态。故障排查要以验证结束,而不是执行完修复命令就算完成。

总结

你使用条件、事件、容器状态、当前日志和先前日志,诊断了镜像与启动故障。你修复的是控制器模板,而不是生成的 Pod,并学会了使用 logs 和 exec 定位单个容器。下一项挑战将要求你在没有操作指引的情况下,应用这套以证据为先的排查流程。