1. 写在前面为什么我推荐用“破坏式”学习 Kubernetes第五篇了我先把话说在前面这篇文章不是给你讲 kubectl get pods 怎么用也不是抄一遍 Kubernetes 官方文档。我想分享的是一套我自己验证过、也一直在带新人时用的学习方式——把集群“故意搞坏”再一步步修好。在这个破坏、观察、修复、复盘的过程中Kubernetes 的组件边界、调用链、故障表象都会变得异常清晰。我带的运维新人经常问我“Kubernetes 概念太多了Pod、Deployment、Service、Ingress、CRI、OCI、CNI、CSI……学完就忘怎么才能记住”我给的答案很简单去把一个正在运行的 Pod 弄挂把一个节点标记成 NotReady把 CoreDNS 副本数缩到 0然后亲手把它们修好。你踩过一次 ImagePullBackOff 的坑比背十遍 kubelet 工作原理都管用。这一篇是系列第五篇前四篇我们已经把 Kubernetes 的架构组成、集群部署、工作负载、网络与存储都过了一遍。这一篇我打算彻底换一个画风先跟你把 kubelet 到 containerd 的实体调用链捋明白因为这是所有“运行时相关故障”的底层逻辑然后基于这条调用链把从 Pod 到集群的 20 类常见故障全部分类拆开讲清楚再分享一套我自己的排错方法、排查命令和破坏式实验设计最后把这些实战经验对应到 Kubernetes 面试高频题上。内容有点长但都是可以直接抄作业的。2. 先搞懂调用链kubelet 到底是怎么调用 containerd 的做故障排查之前我建议先把“一次 Pod 创建”背后的调用链刻在脑子里。因为集群里 80% 的运行时故障表象千奇百怪本质都是这条链路上某个环节断了。你只有知道了正常的时候数据是怎么流的才能在异常的时候快速定位断点。2.1 从 kubelet 到 CRI一层抽象解决“运行时”之争Kubernetes 早期版本是直接内置支持 Docker 的kubelet 通过 Docker API 操作容器。后来容器运行时越来越多containerd、CRI-O、Kata Containers 纷纷登场Kubernetes 社区做了一个很重要的决定抽象出一层 CRIContainer Runtime Interface用一套统一的 gRPC 接口把 kubelet 和具体的容器运行时解耦。CRI 定义了两类核心服务RuntimeService管理 Pod 沙箱Sandbox和容器生命周期比如 RunPodSandbox、CreateContainer、StartContainer、StopContainer。ImageService管理镜像比如 PullImage、ListImages、RemoveImage。你可以把 CRI 理解成一个“电源插座”kubelet 只需要认准插座规格至于插座后面插的是 containerd 还是 CRI-O它不关心。而 containerd 为了接入 Kubernetes在它内部实现了一个 CRI Plugin在 config.toml 里通常能看到它把 containerd 原生的 API 翻译成了 CRI 语义。这一层翻译是理解整套调用链的钥匙。2.2 一次 Pod 创建到底发生了什么实体调用链我直接用文字把这条链路一步步画出来你在看的时候可以想象自己在追一条请求你执行 kubectl run 或者创建 Deployment请求先到 kube-apiserver经过认证、授权、准入控制后写入 etcd。kube-scheduler 通过 watch 机制发现这个新 Pod经过调度算法选出一个最合适的节点并把调度结果写回 API Server。目标节点上的 kubelet 通过 watch 拿到这个 Pod进入 syncPod 流程。kubelet 调用内部的 Container Runtime Manager由它通过 CRI 客户端向 containerd 的 CRI Plugin 发起 gRPC 请求。首个请求通常是 RunPodSandbox。containerd 的 CRI Plugin 会先去拉取 sandbox_image默认就是 pause 镜像然后通过 containerd 的 Task Service 启动一个 pause 容器作为整个 Pod 的网络、IPC、UTS 等命名空间的“锚点”同时调用 CNI 插件完成 Pod 网络配置。沙箱创建完成后kubelet 继续发起 CreateContainer、StartContainer 请求containerd 开始真正创建业务容器。此时 containerd 会为这个容器拉起一个独立的 containerd-shim 进程我这套环境里是 containerd-shim-runc-v2。shim 进程负责调用 runc create、runc start最终由 runc 通过 Linux 内核的 namespace、cgroup、mount 等机制把容器真正跑起来。这里有两个特别容易忽略的实体pause 容器。它是 Pod 里最先被创建、生命周期贯穿始终的“占位容器”。业务容器无论怎么重启只要 pause 在Pod 的网络标识和沙箱资源就不会变。这也是为什么你在节点上用 crictl ps 会看到每个 Pod 都对应一个 pause 容器。containerd-shim。它的存在是为了不让 containerd 主进程直接当容器的父进程。这样一来containerd 重启、升级都不会杀掉正在运行的容器。每个 shim 对应一个容器它负责接管容器的标准输入输出、退出状态上报并作为 runc 和 containerd 之间的中间人。2.3 顺着 socket 摸下去在节点上“眼见为实”原理讲再多不如自己在节点上敲几条命令。Kubernetes 和 containerd 之间是 gRPC 通信socket 文件一般位于 /run/containerd/containerd.sock。kubelet 的启动参数里一般有--container-runtime-endpointunix:///run/containerd/containerd.sock你可以先在节点上看一下这个 socket 是否真实存在ls -l /run/containerd/containerd.sock然后重点练熟 crictl 这一组命令它是我们排障时最顺手的工具因为 crictl 走的正是 CRI 接口也就是说你手动用 crictl 操作的路径和 kubelet 调用 containerd 的路径是同一个非常有助于把调用链“实体化”crictl pods查看节点上的 Pod 沙箱列表。crictl ps -a查看所有容器包含已退出的注意区分 sandbox 容器和业务容器。crictl inspect 查看单个容器的详细 spec、挂载、PID 等。crictl logs 直接拿容器日志不经过 kubectl。crictl pull手动拉镜像复现 ImagePullBackOff 时好用。另外还有一套工具链是 ctr namespaces list它直接调用 containerd 原生 API和 crictl 的视角不同。两者区别要搞清楚crictl 是 CRI 视角能看懂 Pod 和容器ctr 是 containerd 原生视角看不到 Pod 概念。排障时优先用 crictl涉及 containerd 底层镜像、快照、事件时再用 ctr 辅助。2.4 原理照进排错调用链能帮你做什么为什么要花这么大力气讲调用链因为故障排查的本质就是沿着这条链逐段做排除。举个例子业务容器一直 CreateContainerErrorkubectl describe pod 里只显示一句失败的容器创建很多人就懵了。但如果你知道这条链是 kubelet → CRI → containerd → shim → runc你的排查思路立刻就有了第一段kubelet 是否正常看 journalctl -u kubelet。第二段CRI 接口是否通直接 crictl ps 看能不能连上 containerd socket。第三段containerd 是否正常看 journalctl -u containerd 和 containerd 的日志。第四段runc 启动容器时内核报了什么错看 containerd 日志里带 runc 字样的 Error。顺序排查永远比盯着 kubelet 日志硬猜要快。后面我们讲故障分类时你会发现所有故障最终都能落到这条链路的具体某一段上。3. 从 Pod 到集群20 类常见故障全解析接下来进入正题。我把平时线上和测试环境里遇过的高频故障按“从 Pod 到集群”的维度整理成 20 类。先给一张速查表再挑几类最容易让人卡壳的展开讲排查逻辑和修复手法。3.1 一张速查表先打底故障现象、根因、排查命令层级故障现象常见根因核心排查命令处理方向PodImagePullBackOff镜像名错误、仓库不存在、认证失败kubectl describe pod修镜像名、配 imagePullSecretPodErrImageNeverPullimagePullPolicyNever 但本地无镜像kubectl describe pod换镜像拉取策略或预置镜像PodInvalidImageName镜像名不合法kubectl describe pod修正镜像格式PodCrashLoopBackOff启动命令失败、配置错误、依赖未就绪kubectl logs、kubectl describe pod修应用启动逻辑PodOOMKilled容器内存超 limitkubectl describe pod调 resources.limitsPodPending资源不足、亲和性/污点不满足、PVC 未绑定kubectl describe pod扩容、调整调度约束PodCreateContainerError镜像或运行时层错误crictl ps -a、journalctl -u kubelet看 containerd 日志PodCreateContainerConfigErrorConfigMap/Secret 不存在或字段缺失kubectl describe pod检查引用资源PodRunContainerError运行时启动容器失败journalctl -u containerd看 runc 报错PodDeadlineExceededPod 终止超时kubectl get pod -o yaml调 terminationGracePeriod 或强删PodInit:CrashLoopBackOffinitContainer 反复失败kubectl logs pod -c init容器修初始化逻辑节点NotReadykubelet 心跳中断、运行时异常kubectl describe node、journalctl -u kubelet逐段排查 kubelet节点DiskPressure节点磁盘到达驱逐阈值df -h、crictl rmi 清理镜像清镜像、清日志、加磁盘节点MemoryPressure节点内存不足free -m驱逐 Pod、加节点节点PIDPressurePID 耗尽cat /proc/sys/kernel/pid_max、ps -eLf查进程泄漏网络DNS 解析失败CoreDNS 异常、上游 DNS 失效kubectl exec -it pod -- nslookup查 CoreDNS 状态网络Service 不通Endpoints 为空、kube-proxy 规则异常kubectl get endpoints、iptables-save查 selector网络跨节点 Pod 不通CNI 配置异常、underlay 丢包ping、traceroute、查 CNI 日志查 CNI 插件网络NodePort 访问不通防火墙、安全组、端口占用ss -lntp检查集群外链路存储PV/PVC 挂载失败StorageClass 不存在、权限不足kubectl describe pvc检查存储插件与权限3.2 Pod 生命周期类从 ImagePullBackOff 到 OOMKilled先讲出现频率最高的 ImagePullBackOff。它的表象是 Pod 卡在 ContainerCreatingEvents 里能看到 Failed to pull image。我从排查动作给你拆开第一步kubectl describe pod 看 Events 里的具体报错。如果报 ErrImagePull多半是镜像仓库路径写错、镜像不存在、或者仓库需要认证。注意有时候镜像名写对了但 tag 打错了也会报同样的错误。第二步手动在节点上用 crictl pull 拉一次相同镜像。这一步能排除“kubelet 到镜像仓库的网络问题”和“仓库本身问题”。第三步如果是私有仓库检查 Pod 里是否配置了 imagePullSecrets。我踩过最隐蔽的坑是secret 存在但 service account 没绑定kubelet 压根没把 secret 带给 containerd。再看 CrashLoopBackOff。这个状态说明容器起来了但启动后立刻退出然后又重启反复循环。很多人一看到这个状态就慌了其实排查路径非常固定kubectl logs 拿标准输出和错误输出看应用为什么退出。如果日志为空加 --previous 看上一次容器的日志。如果应用是 init 进程直接退出可能是 entrypoint 脚本问题如果涉及依赖服务数据库、配置中心优先看网络和配置能否连通。我觉得 CrashLoopBackOff 最容易翻车的地方是进程“假启动”。比如一个 Java 应用JVM 起来了但连不上配置中心又在代码里设了启动失败即退出。这时候日志可能会在启动后 30 秒才刷出来需要耐心看完整日志。然后是 OOMKilled。表象是容器状态显示 OOMKilled退出码 137。根因往往是容器内存超过 resources.limits被 cgroup OOM killer 杀掉。排查时kubectl describe pod 能看到最后状态是 OOMKilled以及 reason 为 OOMKilled。用 free -m 看节点内存再用 crictl stats 看各容器真实内存占用。如果应用是 Java注意 JVM 默认堆大小可能和容器 limits 不匹配。我的经验是压测环境下这类问题特别多JVM 还没触发自己的 OOM就先被 cgroup 杀了。3.3 节点与 kubeletNotReady、磁盘压力、运行时失联节点层故障牵扯面大因为一个节点挂掉上面所有 Pod 都要重建对业务的影响往往呈指数级放大。先看 kubectl get node 输出里 STATUS 为 NotReady 的节点再用 kubectl describe node 查看 Conditions里面会写明当前节点处于哪种压力状态。NotReady 最常见的三种原因我按概率排一下kubelet 与 API Server 的通信断了。可能是网络问题、证书过期、或 kubelet 本身崩溃。排查命令是 journalctl -u kubelet -f一定要看实时日志因为很多报错转瞬即逝。节点负载过高导致 kubelet 的心跳上报超时。这时候 ssh 上节点top、free、df 三连看优先确认资源水位。容器运行时挂了。也就是 containerd 进程异常kubelet 调用 CRI 接口超时被迫把节点标记为 NotReady。排查 containerd 状态systemctl status containerd、journalctl -u containerd。DiskPressure、MemoryPressure、PIDPressure 这三类压力本质都是节点资源达到驱逐阈值kubelet 开始按照 QoS 等级驱逐 Pod。排查时DiskPressuredf -h 看根分区和容器数据目录分区。很多时候是容器日志、镜像、已停止容器残留占满磁盘。清理思路是先删无用的镜像crictl rmi再清理日志journalctl --vacuum-size最后看有没有被误写进容器目录的大文件。MemoryPressurefree -m 先看可用内存用 ps 按内存排序找进程。PIDPressure看 /proc/sys/kernel/pid_max 和当前 pid 数量。这种往往是有进程泄漏疯狂创建线程或子进程。我遇到过一次 Java 应用线程池参数写错把机器 PID 直接打满。另外一个很容易被忽略的是 kubelet 和 containerd 之间的状态不一致。比如 containerd 重启过但 kubelet 没有感知crictl 能看到容器kubectl 里 Pod 一直异常。这种时候先重启 kubelet 让状态重新对账往往能自愈。3.4 网络与服务DNS 解析失败、Service 不通、跨节点连不上网络类故障是排障里最烧脑的因为涉及物理网络、CNI、kube-proxy、DNS、Service 多层叠加。我按“先从 Pod 内部往外逐层测”的方法讲。Pod 内 DNS 解析失败先做一件事kubectl exec -it -- nslookup 然后根据报错分两种情况如果 getaddrinfo 直接报错说明 Pod 里的 /etc/resolv.conf 有问题常见原因是 dnsPolicy 被改成 Default导致 Pod 没有用集群的 CoreDNS。如果能解析到 IP 但访问超时说明 CoreDNS 本身异常。查 CoreDNS Pod 状态和日志看看是否有上游 DNS 配置错误。我踩过的一个坑是宿主机 /etc/resolv.conf 里的 nameserver 指向了内网 DNS但 CoreDNS 把它当上游解析外网域名时经常超时。Service 访问不通按这个顺序查kubectl get endpoints 看 Endpoints 是否有 IP。如果没有说明 Service 的 selector 和 Pod 的 label 不匹配这是最最常见的低级错误。如果 Endpoints 有 IP就在集群内随便挑一个 Podcurl 一下 Service 的 ClusterIP看通不通。不通的话检查 kube-proxy 的规则。kube-proxy 默认 iptables 模式下用 iptables-save | grep 能看到规则。如果规则不存在重启 kube-proxy Pod 或者直接看它的日志。跨节点 Pod 网络不通属于 CNI 问题。排查思路先确认 CNI 插件是什么。查看节点上的 /etc/cni/net.d/ 目录。查看 CNI Pod 是否正常比如 Calico 的话就是 calico-node 和 calico-kube-controllers。在源 Pod 里 ping 目标 Pod 的 IP逐跳看丢在哪同时检查节点的路由表比如 route -n 是否包含到 Pod 网段的路由。如果节点上有多个网卡常常是因为 CNI 选错了主网卡导致 VXLAN 或 BGP 隧道建不起来。这种问题用 kubectl logs 看 CNI 组件日志一般都能看到明确的网卡异常提示。3.5 存储与控制面PV/PVC 挂载失败、etcd 抖动存储类故障从使用者视角看就是 Pod 一直 ContainerCreatingEvents 里提示 FailedMount。排查 PV/PVC 有无绑定成功是很关键的一步kubectl get pvc 看 STATUS 是否为 Bound。Pending 状态说明 StorageClass 或存储插件有问题已经 Bound 但挂载失败则要看存储协议本身比如 NFS 挂载超时、CSI 插件未安装。我个人的建议是排存储问题时一定先把 kubelet 日志翻出来看完整报错不要只看 Events因为频繁遇到的是宿主机缺少 nfs-utils 这类基础依赖报错只出现在 kubelet 的日志里。控制面故障里etcd 抖动最有代表性。etcd 是 Kubernetes 所有状态的底座如果它异常你会看到 kube-apiserver 报 etcdserver: request timed out整个集群开始“僵住”。排查时先看 etcd 集群健康状态etcdctl endpoint health --cluster。再关注磁盘 IOetcd 对磁盘延迟极其敏感fsync 太慢会触发 leader 频繁切换。用 iostat 看 etcd 数据盘的 await 值如果常年高于 50ms就得考虑换 SSD 或者走独立盘。还有网络延迟三个 etcd 节点之间延迟高也会导致心跳超时。这类问题我踩过一次调试时发现 etcd 和业务混部在同一批机器流量高峰期直接拖垮了存储链路。4. 破坏式实验怎么设计我的排错方法论与工具链看到这里你可能会说你讲的故障我也都见过但每次都是靠运气或者到处搜怎么能系统地练出排障手感下面这部分就是答案。我把“破坏式学习”落成了一套可执行的方法你不妨照着做在测试环境里把故障一个个制造出来再亲手修掉。4.1 分层排查法把故障“钉”在某一段无论遇到什么问题我的第一个判断永远是这个故障现在发生在调用链的哪一段我把 Kubernetes 排障分成四个层次从小到大容器层Pod镜像、容器创建、应用进程、资源限制。节点层Nodekubelet、containerd、磁盘、内存、网络底层。集群网络层CoreDNS、Service、Ingress、CNI、网络策略。控制面层kube-apiserver、etcd、kube-scheduler、controller-manager。这个分层和调用链是对应的。排查时从上往下走先看最贴近业务、最容易观察的一层不要一上来就查 etcd。我有一次带新人排 Pod 创建失败新人直接去查 kube-apiserver 日志查了半天发现 API Server 一切正常问题其实是镜像仓库地址写错了。这就是没分层导致的“绕远路”。4.2 一组可以直接照做的“破坏实验”清单你如果不知道从哪里开始破坏我推荐从这 10 个动作开始。每一个做完都要记录“现象—根因—修复”三段笔记删掉一个正在运行的 Deployment观察 ReplicaSet 怎么重建。把某 Pod 的镜像名改错观察 ImagePullBackOff。给 Pod 设置一个极小的 memory limit 并压测观察 OOMKilled。把 Deployment 的 replicas 调到调度器无法满足的数量观察 Pending。手动给节点添加一个污点 taint观察已有 Pod 是否被驱逐、新 Pod 是否调度不上。停掉 kubelet 服务 30 秒再启动观察节点 NotReady 到 Ready 的转换。在节点上手动 kill 掉一个业务容器的进程观察容器重启策略。把 CoreDNS 的 Deployment 缩到 0观察集群内域名解析症状。改掉 Service 的 selector观察 Endpoints 为空、Service 不通。删除一个 PVC 对应的底层存储目录观察 FailedMount。做完这些你会对“Kubernetes 是一个自愈系统但自愈的前提是故障能被它识别到”这件事有极其深的理解。比如你手动 kill 进程后kubelet 会按照 restartPolicy 把容器重新拉起来但你如果把节点的 kubelet 停了节点整个进入 NotReady反而不会有人管它。这个边界不亲手做一次破坏是体会不到的。4.3 我在线上验证过的固定排错顺序线上和测试不一样最快止损永远比弄清原理更重要。所以我给自己定了一套固定顺序你自己也可以按这套来先看全局kubectl get nodes、kubectl get pods -A确认故障范围是单 Pod、单节点还是整个集群。再看事件kubectl describe pod/node 里的 Events 往往能直接指向根因。然后看日志按 pod → kubelet → containerd 的顺序逐层追日志。最后动手修复能滚动重启就先滚能删异常 Pod 就先删等业务恢复后再二次复盘根因。这套顺序最大的价值是防止在排查阶段花太久。记住线上场景下恢复业务优先级永远是第一位的。等到故障解除再带着从现场截取的日志去深挖原因。4.4 顺手整理一下排障工具链kubectl一切入口。describe、get、logs、exec 是高频动作。crictl节点上绕开 kubectl 直接看容器运行时状态。ctrcontainerd 原生调试工具。journalctl看 kubelet 和 containerd 系统服务日志。iptables-save / ipvsadm查 kube-proxy 规则。nsenter / netstat / ss进到容器的网络命名空间里做网络排查。etcdctl控制面 etcd 健康检查和数据目录检查。5. 从实战到面试Kubernetes 高频问题延伸思考很多运维去面试前疯狂背八股我的建议恰恰相反把实战里验证过的东西用自己的话讲出来比背概念高级得多。接下来我把这个系列涉及的核心能力对应到面试中最常被问的问题上。5.1 调用链相关kubelet 和 containerd 的关系怎么答面试题直接问“kubelet 是如何调用 containerd 的”其实考察的就是你是否理解 CRI 抽象。你可以这样组织答案kubelet 并不直接调用 containerd而是通过 CRI 接口以 gRPC 方式调用 containerd 内置的 CRI Plugin。调用路径是 kubelet → CRI gRPC 客户端 → /run/containerd/containerd.sock → containerd CRI Plugin → containerd-shim → runc。创建 Pod 时第一个关键请求是 RunPodSandboxcontainerd 会拉取 pause 镜像并创建沙箱随后 kubelet 发起 CreateContainer 和 StartContainercontainerd 启动一个 shim 进程shim 再调用 runc 完成容器创建。如果能再补上 pause 容器和 shim 进程的作用面试官基本就能确定你是真做过底层排查的人。5.2 故障排查类用“分层排错”回答拉开差距比如面试官问“Pod 一直 Pending你怎么排查”。很多人上来就答“资源不足”但更好的回答是先 kubectl describe pod 看 EventsPending 的根因可能有资源不足、节点亲和性不满足、存在污点、PVC 未绑定、或者调度器异常。资源不足要看 allocatable 和 request污点要看 node 的 taints 和 Pod 的 tolerationsPVC 要看 pvc 的 STATUS 是否 Bound。把每个可能都给出对应验证命令再给出解决方案这就是“有实战经验”的回答。另一道高频题“Service 访问不通如何排查”我建议按这个顺序先 get endpoints 确认后端 Pod 是否被正确关联然后进入集群内 Pod 直接访问 ClusterIP 验证网络通路如果还是不通检查 kube-proxy 模式和 iptables/IPVS 规则最后看 CNI 底层是否存在跨节点路由问题。这个回答天然带着分层排错的逻辑面试官会看到你脑子里有一条清晰的链路。5.3 原理型问题从“会用”到“说清楚”还有一类面试题问的是“为什么 Pod 是最小调度单元”“为什么不直接在一个容器里跑多个进程”。这些问题的核心其实是 pause 容器和命名空间共享机制。一个 Pod 里的多个容器共享同一个网络命名空间、IPC 命名空间、UTS 命名空间也能共享 Volume但它们的进程命名空间默认不共享。这种设计让“一个 Pod 里放一个主容器和几个辅助容器比如日志收集 sidecar”成了可能。如果你能顺手解释一下为什么业务容器重启而 Pod IP 不变化——因为 pause 容器决定了网络命名空间的生命周期——那这题基本就满分了。6. 写在最后我的一点个人体会写这个系列之前我以为自己已经对 Kubernetes 的故障有免疫力了结果今年在一次压测环境里还是被一个 kubelet 版本和 containerd 版本不完全兼容的毛病折腾到凌晨三点。版本不匹配这种问题不亲手踩一次光看升级文档你是永远记不住的。这也是我为什么一直坚持“破坏式学习”——教训往往比经验更深刻。如果你现在还在入门阶段我的建议是不要怕弄坏集群。搭一套单节点的 kind 或者 minikube然后照着 4.2 节里的破坏实验清单一天破坏一个坚持两周。等你亲手修好了十来个故障再回头看文档以前看不懂的部分会变得异常清晰。Kubernetes 这个系统天赋不够没关系踩坑来凑踩得多了你就是那个能一眼定位断点的人。