Kubernetes Kubernetes

调试失败的 Pod:describe、logs、exec 与 port-forward

一份可重复的清单,用来找出 Pod 为什么是 Pending、崩溃或没有响应。

12 min 初级

迟早会有一个 Pod 无法启动、不断重启,或者在运行却没有响应。修复几乎总是通过少数几个 kubectl 命令、按正确的顺序使用就能看出来。本指南给你这个顺序:读取状态,询问事件,读取日志,进入容器,然后追踪网络路径。

先读取状态

从状态列开始:

kubectl get pods -o wide

最常见的状态,以及首先该看哪里:

  • Pending。 Pod 没有被分配到节点。通常是没有足够的 CPU 或内存、配额已用尽,或者卷无法绑定。describe 会告诉你是哪个。
  • ContainerCreating。 节点正在准备容器。如果一直停在这个状态,请检查是否缺少卷,或镜像拉取缓慢。
  • ErrImagePull / ImagePullBackOff。 无法拉取镜像,最常见的原因是名称或标签拼写错误,或缺少私有镜像仓库的凭据。
  • CrashLoopBackOff。 容器启动、退出,并以逐渐增长的延迟被反复重启。应用本身在失败,所以请读取它的日志。
  • CreateContainerConfigError。 Pod 引用了不存在的 ConfigMap、Secret 或键。
  • Running 但 READY 0/1。 容器已启动,但就绪探针失败。
  • OOMKilled(在 describe 中显示)。容器超过了它的内存 limit。

问 Kubernetes 为什么:describe 与事件

kubectl describe pod POD_NAME

滚动到底部的 Events 部分。它读起来像一条时间线:Scheduled、Pulling、Failed、BackOff。诸如 0/3 nodes are available: insufficient cpu 或 Failed to pull image "ngnix:1.25" 之类的消息,通常直接指出了问题。再往上,查看每个容器的 State 和 Last State,了解退出码和原因。要获得整个命名空间发生了什么的视图:

kubectl get events --sort-by=.metadata.creationTimestamp

读取容器的输出

kubectl logs POD_NAME
kubectl logs POD_NAME -c CONTAINER_NAME
kubectl logs POD_NAME --all-containers
kubectl logs POD_NAME --previous
kubectl logs POD_NAME -f --since=10m --tail=100

当 Pod 有多个容器时使用 -c,流式输出使用 -f,用 --since 和 --tail 限制输出,并用 --previous 读取上一个崩溃实例的日志,这正是崩溃循环所需要的。要试一试,创建一个让容器有意失败的 Deployment:

kubectl create deployment crashy --image=busybox:1.36 -- \
  sh -c 'echo starting; echo "fatal: DB_URL is not set" >&2; exit 1'
kubectl get pods -l app=crashy
kubectl logs deployment/crashy --previous

状态会变成 CrashLoopBackOff,而日志显示了原因。用 kubectl delete deployment crashy 清理。

请记住,日志属于 Pod。当它被删除或替换时,它的日志会随之被清理,所以要保留历史,你需要像 Fluentd 或 Fluent Bit 这样的日志收集器,把日志发送到某个持久的地方。

进入内部:exec、port-forward 与 cp

kubectl exec -it POD_NAME -- sh
kubectl exec POD_NAME -- env
kubectl port-forward pod/POD_NAME 8080:80
curl http://localhost:8080
kubectl cp POD_NAME:/path/to/file ./file

exec 在容器中运行命令,port-forward 把本地端口连接到 Pod 中的端口(对 svc/NAME 同样有效),cp 把文件复制进来或出去。许多生产镜像都是最小化的,没有 shell 或 curl。在这种情况下,kubectl debug -it POD_NAME --image=busybox:1.36 --target=CONTAINER_NAME 会附加一个共享该 Pod 命名空间的临时调试容器。

你在运行中的容器内所做的任何更改,比如编辑配置文件,在 Pod 被替换的那一刻就会丢失。使用 exec 来调查,但要在镜像、清单或 ConfigMap 中做出修复。

沿请求路径向回推

如果 Pod 看起来健康,但应用无法访问,请从内向外追踪路径:

  1. Pod 在运行并且 READY 吗?先修复 Pod。
  2. Service 的 selector 与 Pod 的标签匹配吗?kubectl get pods --show-labels 和 kubectl describe service NAME 应当一致。
  3. Service 有端点吗?kubectl get endpointslices -l kubernetes.io/service-name=NAME 应当列出 Pod IP。列表为空意味着 selector 不匹配,或没有就绪的 Pod。
  4. 端口匹配吗?Service 的 targetPort 必须是容器实际监听的端口。
  5. 从另一个 Pod 访问能行吗?kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://NAME。这会测试 DNS 和 Service,排除集群之外的任何东西。
  6. 会不会是 NetworkPolicy 在阻挡?用 kubectl get networkpolicy 列出它们。
  7. 最后,检查它前面的 Ingress 或外部负载均衡器。

每一步都把问题缩小到一层,这比猜测要快。

热门指南