)
我们每天都在敲kubectl apply -f xxx.yaml然后看到一个 Pod 从Pending变成Running。中间那几秒到底发生了什么搞清楚这条链路你排障的思路会从猜变成顺着链条定位。这篇文章按时间顺序把它拆成六段。目录一、先看全景六阶段链路二、阶段①kubectl 不是下发命令而是提交期望三、阶段②apiserver 的三道关卡四、阶段③控制器接力把 Deployment 变成 Pod五、阶段④调度器挑一台机器六、阶段⑤kubelet 让容器真正跑起来七、阶段⑥kube-proxy 让服务可访问八、Pod 卡住了按阶段查九、为什么说这是收敛不是一次性命令十、小结一、先看全景六阶段链路阶段主角干了什么①kubectl读 YAML发 HTTP 请求②kube-apiserver认证、授权、准入写入 etcd③controller-managerDeployment 创建 ReplicaSetReplicaSet 创建 Pod④kube-scheduler选节点写绑定关系⑤kubelet containerd拉镜像、建容器、探针检查⑥kube-proxy更新 iptables/IPVS 规则Service 可访问下面逐段拆。二、阶段①kubectl 不是下发命令而是提交期望kubectl做的事比想象中简单把 YAML 转成 JSON向 apiserver 发一个 HTTP 请求。但这里有一个常被忽略的细节——apply 之前kubectl 会先 GET 一次现有对象。如果对象不存在就发POST创建如果存在就计算差异发PATCH更新。apply → GET存在吗 → 不存在POST → 存在 PATCH带 last-applied-configuration 做三方合并这就解释了两个常见现象create重复执行会报AlreadyExistsapply不会手动kubectl edit改过的字段可能在下一次apply时被改回去或者产生冲突——因为 kubectl 按我上次提交的和集群现在的做三方合并另外别忘了kubectl 需要知道自己连哪个集群、用什么身份kubectl config view --minify # 看当前 context、server、证书 kubectl auth whoami # 看当前身份1.27第一步错误通常出在这里连错集群、证书过期、context 不对。kubectl 连不上和集群坏了是两件完全不同的事先分清。三、阶段②apiserver 的三道关卡apiserver 是唯一能读写 etcd 的组件所有请求都要过它。它像一道安检门设了三道关卡关卡解决什么问题失败表现认证 Authentication你是谁Unauthorized (401)授权 Authorization你能不能做这件事Forbidden (403)提示 RBAC 拒绝准入 Admission这个对象合不合规明确报错如超出配额、镜像不在白名单认证常见方式两种X.509 客户端证书kubeconfig 里常见的client-certificate-data证书里的 CN 是用户名O 是用户组Bearer TokenServiceAccount 的 token或云厂商的临时凭证授权默认是RBAC用户/ServiceAccount → RoleBinding → Role/ClusterRole → 允许的动词和资源。排查 403 的标准动作kubectl auth can-i create pods --namespace default kubectl auth can-i --list --namespace default准入准入控制器在对象真正落库前做校验或改写常见的有ResourceQuota命名空间的总资源配额LimitRange没写 resources 时给默认值或强制要求写Pod Security Admission限制 privileged、hostPath 等危险配置Mutating/Validating Webhook各种自定义校验到这一步对象终于被写入etcd——集群唯一的事实来源。写入成功kubectl就返回了deployment.apps/xxx created。注意此时容器一个都还没启动。kubectl说的成功只是期望状态已记录。四、阶段③控制器接力把 Deployment 变成 Podetcd 里多了一个 Deployment 对象但 Deployment 本身不产生 Pod。kube-controller-manager里的 Deployment 控制器一直在watch这类对象。它发现期望 3 个副本实际 0 个于是创建或更新一个ReplicaSet把 Pod 模板交给它ReplicaSet 控制器接着 watch发现期望 3 个实际 0 个创建 3 个Pod对象为什么中间要夹一层 ReplicaSet因为版本管理每次改镜像Deployment 会新建一个 ReplicaSet旧的保留revisionHistoryLimit控制保留几个滚动更新就是两个 RS 此消彼长的过程回滚就是把旧 RS 扩回去。kubectl get rs -o wide # 看每个 RS 的 DESIRED / CURRENT kubectl rollout history deploy/xxx kubectl rollout undo deploy/xxx --to-revision1到这里3 个 Pod 对象存在于 etcd 里但nodeName是空的——它们还没有归属任何节点状态是Pending。五、阶段④调度器挑一台机器kube-schedulerwatch 到有 Pod 没有 nodeName开始为它选节点分三步步骤目的典型约束Filter预选筛掉不可能运行的节点资源不足、taint 不容忍、nodeSelector/affinity 不匹配、端口冲突Score优选在可行节点里打分排序资源均衡、亲和性权重、镜像本地已存在Bind绑定把结果写回 Pod更新spec.nodeNameFilter 阶段全军覆没Pod 就一直是Pending事件里会写原因例如0/3 nodes are available: 3 Insufficient memory. 0/3 nodes are available: 3 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }.看到 Pending 的第一反应应该是kubectl describe pod看 Events 最后一行而不是重启集群。还要注意调度只是分配不等于启动。绑定完成后 Pod 状态可能还是 Pending因为节点上还没开始拉镜像。六、阶段⑤kubelet 让容器真正跑起来节点上的 kubelet watch 到分给我的 Pod开始干活通过 CRI 调用 containerdRunPodSandbox→ 先建 pause 容器和 Pod 级 Namespace拉镜像没有就拉拉不到就ImagePullBackOff建业务容器加入 pause 的命名空间挂 Volume按 cgroup 设资源限制执行探针startup → readiness → liveness上报状态定期向 apiserver 汇报节点心跳断了节点会变NotReady上面的 Pod 会被驱逐这条链路上每一环失败症状都不一样症状卡在哪ContainerCreating长时间不动拉镜像慢、挂载 Volume 失败CSI 问题ImagePullBackOff镜像地址错、私有仓库凭据缺失、网络不通CrashLoopBackOff容器启动了但反复退出看logs --previousRunning但READY 0/1readiness 探针没通过Service 不会把流量给它kubelet 与 apiserver 之间是双向的kubelet 主动 watch 分配给自己节点的 Pod也主动上报状态。apiserver 不会主动连 kubelet——这一点决定了排查方向节点上的 kubelet 日志往往比 apiserver 日志更有用。七、阶段⑥kube-proxy 让服务可访问Pod 起来了但 Pod IP 会变没人能直接依赖它。于是有了 Service。kube-proxy在每个节点上 watch Service 和 EndpointSlice把访问 Service IP:Port → 转发到某个 Pod IP:Port的规则写进内核模式实现适用iptables默认规则链 DNAT通用Pod 数量中等IPVS内核四层负载均衡哈希表大规模、高并发规则多时性能更好Service 四种类型类型作用ClusterIP集群内虚拟 IP默认类型NodePort每个节点开一个端口外部可通过节点IP:端口访问LoadBalancer云厂商提供外部负载均衡器ExternalName不做转发只做 DNS CNAME排查服务访问不通顺序是kubectl get endpointslices -l kubernetes.io/service-namexxx # 有没有后端 kubectl get pods -o wide # Pod 是否 Ready kubectl exec -it pod -- curl http://service-ip:port # 集群内通不通最常见的坑Pod 没 ReadyEndpoints 是空的Service 自然转发不到任何地方。这时候别去翻 iptables先看探针。八、Pod 卡住了按阶段查把上面六个阶段变成一张排障表现象大概率阶段第一条命令kubectl超时、连不上①kubectl cluster-info、检查 kubeconfig401 Unauthorized② 认证检查证书是否过期403 Forbidden② 授权kubectl auth can-i ...创建报配额/校验错误② 准入看错误详情里的 quota/webhook 名Pod 一直Pending无节点④ 调度kubectl describe pod看 EventsPending但有 nodeName⑤ kubeletjournalctl -u kubelet -fImagePullBackOff⑤kubectl describe pod看拉取错误CrashLoopBackOff⑤kubectl logs --previousRunning但访问不通⑥kubectl get endpointslice一句话总结排障心法先确定 Pod 卡在哪个状态再对应到哪个阶段最后只看那个阶段的日志。不要一上来就kubectl logs那时候容器可能根本还没建。九、为什么说这是收敛不是一次性命令上面整条链路的驱动力是controller-manager 的 Reconcile 循环for { 期望状态 从 etcd 读到的 spec 实际状态 从集群观察到的 status if 期望 ! 实际 { 执行动作让实际逼近期望 } }这个循环永不停歇。所以你删掉一个 PodReplicaSet 发现副本数不足立刻补一个你把节点关机控制器发现 Pod 不在重新创建并调度到别的节点你改了镜像版本Deployment 创建新 RS滚动替换这就是声明式的本质你只描述想要什么系统自己想办法达到并持续保持。也正因为如此K8s 里没有重启服务这个说法——你把 Pod 删了它自己就回来了你改配置它自己就滚动了。命令式思维在这里会处处别扭。十、小结kubectl apply提交的是期望状态不是启动命令apiserver 三道关卡认证你是谁→ 授权能不能做→ 准入合不合规通过后写 etcd控制器负责从 Deployment 到 Pod的展开中间夹一层 ReplicaSet 是为了版本与回滚调度器 Filter → Score → BindPending先看 Eventskubelet 通过 CRI 建 pause 沙箱、拉镜像、起容器、跑探针、上报状态kube-proxy 把 Service 规则写进内核Pod 不 Ready 就没有 Endpoints整条链路是持续收敛的闭环不是一个一次性命令把这六个阶段记在脑子里下次 Pod 起不来你会知道该看哪一层而不是盲目重启。