
1. 先搞清楚这套系统在解决什么问题1.1 从手动管理容器到声明式编排接触 Kubernetes 之前我用了很长一段时间的 Docker Compose。几十个容器靠脚本和手工在管部署一次要写一堆启动命令节点一挂就得人工介入迁移扩容基本靠复制粘贴再改端口。那会儿最痛苦的不是容器本身而是怎么让一堆容器像一个整体一样运转。Kubernetes 干的事情核心就是这套编排逻辑。它不关心你单个容器内部是什么样的它关心的是整个集群里同时运行着成百上千个容器时怎么让它们各就各位、故障自愈、按需伸缩。这里要特别注意一个概念声明式。大多数刚开始接触的人都会在这里绕弯。Docker 的方式是命令式——你告诉它现在给我跑一个 nginx 容器Kubernetes 是声明式——你告诉它我需要 3 个 nginx 副本至于这 3 个副本怎么创建、分布到哪台机器上、某个副本挂了怎么办Kubernetes 自己会想办法搞定。理解声明式是理解整个 Kubernetes 组件设计的钥匙。整套系统的核心循环就一句话持续比对期望状态和实际状态发现不一致就去纠正直到两者对齐。这个循环由不同组件接力完成也就是标题里说的组件各司其职。另外Kubernetes 里的组件这个词容易让人混淆。一种是指系统级的组件也就是本文介绍的控制平面和工作节点上的这 7 个核心进程另一种是指你业务里的各种应用组件比如微服务拆分出来的各个服务。本文讨论的是前者——那些支撑 Kubernetes 本身运行的基础设施。1.2 控制平面与工作节点的分工逻辑Kubernetes 的架构本质上就是大脑 四肢的模型。控制平面Control Plane负责所有决策比如谁该运行、哪个副本挂掉了、新版 Desired State 是什么。控制平面通常不跑业务容器。工作节点Worker Node真正跑业务容器的地方它做的事情就是把控制平面的指令落地然后把执行结果汇报回去。这种分工不是拍脑袋定的而是有实打实的工程意义。控制平面集中管理方便做统一的鉴权、审计和状态存储工作节点只做执行节点之间没有耦合关系任何一个节点宕机都不会影响控制面的决策。而节点的故障正好由控制面里的 Controller 负责感知和处理。用一句俗话概括控制平面负责“想”工作节点负责“做”。所有想的结果都会落在一个共享的、强一致的状态存储里所有做的过程也都通过这个状态存储来反馈这样整个集群在任何时间点都能对齐到同一个事实。下面这张表先给你一个整体地图后面再逐一展开组件所在位置核心职责kube-apiserver控制平面所有 API 请求的唯一入口负责鉴权、准入控制、读写状态etcd控制平面键值存储保存整个集群的期望状态和实际状态kube-scheduler控制平面为新创建的 Pod 挑选一个合适的节点kube-controller-manager控制平面运行各类控制器把系统状态收敛到期望状态kubelet工作节点节点上的代理负责创建和管理本节点上的 Podkube-proxy工作节点维护 Service 的转发规则实现服务发现与负载均衡容器运行时工作节点真正执行容器进程常见的有 containerd、CRI-O2. 控制面四大件API Server、etcd、Scheduler、Controller Manager2.1 API Server一切请求的唯一入口如果说 Kubernetes 是一个国家API Server 就是它的政务大厅。所有外部请求——不管是 kubectl 命令、CI/CD 系统、还是其他组件的内部通信——都必须先经过 API Server没有任何例外。为什么非要这么绕因为 API Server 干的不只是转发它承担了三层非常关键的关卡认证Authentication先确认你是谁常见方式有客户端证书、Token、ServiceAccount。授权Authorization确认你是否有权限做这个操作默认走 RBAC。准入控制Admission Control在请求真正落库之前做最后的校验和修改比如自动给命名空间打标签、注入 Sidecar、校验资源配额。三层关卡走完API Server 才会把你的请求写入 etcd并返回结果。注意API Server 是唯一直接读写 etcd 的组件其他组件想获取集群状态都是通过 API Server 提供的 Watch 机制来订阅而不是各自直连 etcd。这个设计避免了多组件并发写同一份数据导致的混乱也让 API Server 成为天然的消息总线。我实际排查时的经验当某个组件出现连不上集群或者 leader election lost 这类问题时第一反应不应该是去翻某个业务 Pod 的日志而是先检查 API Server 是否健康、认证凭证是否过期。很多诡异问题最后都出在 API Server 这一层的访问链路上。2.2 etcd集群的真相存储etcd 是一个分布式的、强一致性的键值数据库。集群里所有的资源对象——Deployment、Service、Pod、ConfigMap、Secret——都保存在 etcd 里。它就像一个黑匣子里面装着集群的全部状态。这里头最关键的是强一致三个字。etcd 靠 Raft 共识算法实现集群内部所有数据变更需要大多数节点例如 3 节点集群里至少 2 个节点确认才算真正写入成功。这也是为什么生产环境 etcd 节点的推荐数量是 3、5、7 等奇数——必须保证在少数节点故障时仍然能形成多数派继续工作。在实际操作中etcd 是控制平面里最容易出性能问题的组件没有之一。因为它对磁盘的 fsync 延迟极其敏感。当你发现 API Server 响应偶发超时、或者 kubectl 操作要卡好几秒时很大概率是 etcd 所在的盘太慢或者 etcd 的数据目录快满了。这里有几个实操经验值得记下定期做快照备份etcdctl snapshot save并把这个快照文件异地留存这是集群灾难恢复的唯一救命稻草。别把 etcd 和业务容器混在同一个磁盘上尽量让 etcd 独占 SSD。默认端口 2379 是客户端通信端口2380 是节点间通信端口网络策略里不要乱封。2.3 Scheduler怎么决定 Pod 落在哪个节点Scheduler 的职责只有一个给新创建的 Pod 找到一个最适合运行的节点。听起来简单但最适合三个字背后是一套完整的调度算法。调度过程分为两步业内常叫过滤和打分。过滤阶段把所有不能满足 Pod 需求的节点剔除掉。比如 Pod 要求 4 核 8G那么只剩余 2 核的节点会被过滤掉Pod 要求绑定在某个可用区的节点上那其他可用区的节点也会被过滤掉节点上有污点Taint而 Pod 没有对应的容忍Toleration也过不了这一关。打分阶段对过了过滤的节点按优先级打分。比如资源使用率更均衡的节点会得高分Pod 与已运行 Pod 在同一节点或相邻节点亲和性也会影响分数。最后 Scheduler 选出分数最高的节点把结果写回 API Server——具体来说是给 Pod 的spec.nodeName字段赋值。关键点在于Scheduler 并不直接给节点下指令。它只是建议Pod 应该跑在哪个节点上这个建议写进 API Server 之后目标节点上的 kubelet 才会感知到并真正动手创建容器。这个间接通信模式贯穿整个 Kubernetes 设计所有组件之间都通过 API Server 沟通谁都不直接碰谁的接口。我见过的常见误区是有人以为 Pod 一直 Pending 是 kubelet 没启动。其实 Pending 说明调度器还没选好节点大概率是资源不足、端口冲突或污点不匹配。先去看节点状态和 Pod 事件而不是去重启节点上的 kubelet。2.4 Controller Manager把期望状态变成实际状态Controller Manager 是控制平面里最机械又最核心的一个组件。它内部跑着一堆控制器比如ReplicaSet 控制器保证一个 ReplicaSet 指定数量的 Pod 一定存在少了就补建多了就删掉。Deployment 控制器管理 ReplicaSet 的发布和回滚。Node 控制器节点失联后负责给节点打 NotReady 状态并清理该节点上的 Pod。EndpointSlice 控制器维护 Service 与后端 Pod 的关联关系。每个控制器的干活模式都一样Observe观察当前状态→ Diff和期望状态比一比→ Act执行纠正动作。这就是著名的控制循环Control Loop。你可以把它想象成一个恒温器你设定了 26 度期望状态温度传感器发现现在是 24 度实际状态于是恒温器启动加热Act直到温度回到 26 度。Controller Manager 里的每个控制器都是一个专注于某一类资源的恒温器。为什么不让一个大控制器统一管理所有资源因为关注点分离更好维护、更容易扩展。今天你只需要自定义一种新资源写一个专门的控制器去管它就行没必要重写整个控制面。这也是 Kubernetes 扩展性这么强的根本原因之一。3. 工作节点三件套kubelet、kube-proxy、容器运行时3.1 kubelet节点上的监工如果说控制平面是大脑那 kubelet 就是大脑派到每个节点上的监工。它负责三件大事第一向 API Server 注册节点并且定期汇报心跳和节点状态CPU、内存、磁盘压力、容器运行状态这些数据会更新到 Node 对象上。第二监听 API Server 分配给本节点的 Pod 任务。每当一个 Pod 被调度到本节点kubelet 就会去拉镜像、创建容器、维护容器生命周期。它不关心节点选择只关心分到我头上的活我要干好。第三执行探针Probe检查比如 LivenessProbe判断容器是否活着挂了就重启和 ReadinessProbe判断容器是否就绪就绪了才把流量打过来然后把结果汇报给 API Server。kubelet 本身并不直接创建容器它通过 CRIContainer Runtime Interface调用容器运行时。这里有个概念要分清Pod 只是逻辑上的调度单元实际容器运行起来之前kubelet 会先创建一个叫pause的沙箱容器它负责持有整个 Pod 的网络命名空间和共享存储卷业务容器再逐一加入这个沙箱。这个 pause 容器就是为什么你在节点上用crictl ps能看到一堆pause容器的原因别当成异常进程去处理。实际运维中kubelet 出问题最典型的现象是节点显示 NotReady。这时候先别急着重启节点按顺序排查检查 kubelet 服务状态、看/var/log/messages或 journald 里 kubelet 的日志、确认节点上的容器运行时是否健康、确认证书是否过期。大多数 NotReady 都不是真正的节点坏了而是 kubelet 和运行时之间的断链。3.2 kube-proxy服务的网络转发业务 Pod 的 IP 是随时变化的——节点故障、扩缩容、重新调度都会导致 Pod IP 改变。你不可能让上游服务去记这些飘忽不定的地址所以就有了 Service 这个抽象以及负责让 Service 生效的 kube-proxy。kube-proxy 的工作本质上就是维护一组转发规则当访问 Service 的 ClusterIP 或 NodePort 时把流量转发到某个后端 Pod 上。它有两种主流的实现模式iptables 模式默认且最常见利用 Linux 内核的 NAT 规则做转发随机挑选后端 Pod。IPVS 模式基于内核的 IPVS 模块支持更丰富的负载均衡算法如加权轮询、最小连接数性能更好规则量大时也更有优势。注意kube-proxy 只是个规则维护者它不是代理进程本身真正的数据包转发是在内核态完成的。所以不必担心 kube-proxy 成为性能瓶颈它的瓶颈只在于规则数量过大时的更新延迟。排障时如果遇到Pod 能 ping 通但 Service 访问不通优先怀疑 kube-proxy 的规则没有生效。用iptables -t nat -L查看规则确认是否生成了对应 Service 的 DNAT 条目如果用的是 IPVS 模式就用ipvsadm -ln查看。很多时候不是 kube-proxy 崩溃而是它监听的 EndpointSlice 没有及时更新底层对象变化没被同步过来。3.3 容器运行时真正的执行层容器运行时是整个链路里的最后一公里——真正把镜像拉下来、把进程跑起来的组件。历史上 Kubernetes 长期直接调用 Docker后来因为维护成本和解耦需求演进出 CRI 标准接口并在 v1.24 版本移除了内置的 dockershim。现在主流的运行时是 containerd 和 CRI-ODocker 本身依然能通过外部 shim 使用但已经不是默认路线了。为什么非要搞 CRI 这个标准因为 Kubernetes 不想被某个具体的运行时绑架。只要运行时实现了 CRI 的接口无论底层是 containerd、CRI-O 还是其他兼容实现kubelet 都能用同样的方式驱动它。这套标准接口的思想和 CNI容器网络接口、CSI容器存储接口一脉相承都是 Kubernetes 扩展性的根基。在看节点上的容器时我建议直接用crictl而不是docker命令。比如crictl ps # 查看本节点所有容器 crictl logs container-id # 查看容器日志 crictl inspect container-id # 查看容器详细信息它们输出的信息维度类似但crictl更贴近 Kubernetes 的视角能直接看到 Pod 相关的沙箱和容器。许多新手在节点上用 docker 命令看容器发现和kubectl get pods对不上就是因为没意识到有 pause 沙箱层和业务容器层的区分。4. 一次 Pod 创建请求的完整旅程理解组件之间的协作最好的方式不是背架构图而是跟一次完整的事件流走一遍。下面以kubectl apply -f deployment.yaml为例看看一个 Deployment 从提交到 Pod 运行到底经历了什么。4.1 从 kubectl 到 API Server你敲下命令的那一刻kubectl 会把 YAML 解析成 JSON然后向 API Server 发起一个 POST 请求路径类似/apis/apps/v1/namespaces/default/deployments。这个请求先过认证kubectl 读取你的 kubeconfig 里配置的客户端证书或 Token确认你的身份。再过授权你在 RBAC 规则里有没有权限在这个命名空间创建 Deployment。最后走进准入控制比如系统检查你有没有超出资源配额、命名空间是否存在、有没有需要注入的默认值。三层关卡全部通过API Server 才把这份期望状态写入 etcd然后返回一个 201 Created。这里有个细节这个时刻系统里还没有任何 Pod只是 etcd 里多了一个 Deployment 对象。后续所有动作都是从这次写入之后开始被感知的。4.2 控制器与调度器的接力Deployment 控制器一直在监听 Deployment 对象的变化。它发现 etcd 里多了一个新 Deployment就会创建对应的 ReplicaSet 对象。ReplicaSet 控制器接着登场它计算发现期望副本数是 3当前 Pod 数是 0于是开始创建 3 个 Pod 对象。这 3 个 Pod 对象会被写入 etcd但它们的spec.nodeName还是空的状态是 Pending。Scheduler 此时正在持续监听所有 Pending 的 Pod。它发现了这 3 个没有 nodeName 的 Pod便开始执行上文提到的过滤 打分流程最终选出节点 node-a、node-b、node-c。然后 Scheduler 通过 API Server 把 Pod 的spec.nodeName字段更新为对应节点。这里重申一次Scheduler 没有直接联系 node-a 上的 kubelet它只是把这个决策结果更新到了 API Server 上的 Pod 对象里。kubelet 是自己通过 Watch 机制发现这个变化的。4.3 节点上的执行与反馈node-a 的 kubelet 通过 API Server 的 Watch 接口发现刚才那个 Pod 被绑定到了自己身上。接下来是真正的落地过程kubelet 调用 CRI让容器运行时先创建一个 Pod 沙箱即 pause 容器把网络命名空间、共享卷建好。容器运行时按顺序拉取镜像。如果 Pod 里有 InitContainer会先串行跑完初始化容器。创建业务容器加入沙箱挂载卷配置网络。容器启动后kubelet 开始执行 LivenessProbe 和 ReadinessProbe同时持续采集容器日志和指标。kubelet 把 Pod 的运行状态不断写回 API Server最终kubectl get pods里能看到 Pod 进入 Running 状态并且 READY 列变为1/1。如果这一步里哪一环出问题——镜像拉不下来、探针连续失败、CNI 网络配置报错——Pod 会卡在 ContainerCreating 或 CrashLoopBackOff你需要通过kubectl describe pod看事件去定位具体是运行时、网络还是探针的问题。处理完这条完整链路你对组件的理解才算真正从背清单变成了懂协作。以后再遇到任何集群问题都可以对照这条链路逐层排查。5. 用 kubeadm 搭集群后的排障实战组件协作中的典型坑5.1 读懂 kubeadm init 的 pre-flight 报错很多人的第一个 Kubernetes 集群是用 kubeadm 搭的。执行kubeadm init时首先看到的一段输出就是[init] Using Kubernetes version: v1.26.0 [preflight] Running pre-flight checks这段 pre-flight 检查其实是 kubeadm 在帮你验证这套环境能不能安全地装上控制平面。它包含很多细项但失败高频的大概有这几类检测到 swap 未关闭。kubelet 在大多数部署方式下不支持 swap所以提示先执行swapoff -a并注释掉/etc/fstab里的 swap 行。端口被占用。比如 6443 被别的进程占用、或 10250 端口起不来此时ss -lntp看一下就能定位先停掉占用进程再重试。CRI 版本不匹配。kubeadm 会去连容器运行时的 socket比如/run/containerd/containerd.sock如果 containerd 没启动或 socket 路径配置不对会直接卡在这里。还有一个非常经典的坑kubelet 用的 cgroup 驱动和容器运行时的 cgroup 驱动不一致。比如 kubelet 配置成了systemd但 containerd 还用的cgroupfs节点起来后 kubelet 一直报错Pod 也起不来。解决方法是让两者对齐一般在 containerd 的配置文件里加上SystemdCgroup true5.2 etcd 的备份恢复与性能隐患生产环境里etcd 的坑多数不在坏了怎么修而在没备份坏不起。有一次我把某个测试集群的 etcd 数据目录误删才发现手里的快照还是三天前的——中间的应用变更全部丢失。从那时起我给自己定了个规矩etcd 快照不仅要做做完还要拿到集群之外单独的目录存一份。备份命令很简单ETCDCTL_API3 etcdctl --endpoints127.0.0.1:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db恢复时如果你只有一个节点的单 etcd需要先把集群停掉清空数据目录再用快照文件恢复。多节点集群恢复的流程更复杂建议先在测试环境完整演练一遍别等到线上出事才来翻命令。另一个容易被忽略的是 etcd 的磁盘健康度。etcd 对磁盘 fsync 的延迟非常敏感普通机械盘在高负载下会直接把 API Server 拖到超时。如果你的 etcd 节点和业务节点混部在同一台机器且磁盘 IO 被跑满基本可以预期整个集群开始瘫痪。有条件的话给 etcd 单独挂一块 SSD。5.3 kubelet 与容器运行时的断链排查kubelet 和容器运行时之间是通过 CRI 通信的。它们如果失联表现就是节点 NotReadykubectl describe node 里会看到类似failed to connect to containerd的报错。排查顺序我通常这样走确认 containerd 服务状态systemctl status containerd。用crictl info测试运行时接口是否能正常响应。看 kubelet 日志通常在 journald 里journalctl -u kubelet -f。检查容器运行时 socket 路径和 kubelet 启动参数里的--container-runtime-endpoint是否一致。还有一类隐蔽情况节点上磁盘压满导致容器运行时无法创建临时文件或拉取镜像kubelet 也报 Eviction 相关错误。这时候df -h一看便知清理镜像缓存或日志文件就能恢复不需要重启任何组件。5.4 CNI 网络组件导致的 Pod 网络异常最后聊一个非常高频的问题Pod 卡在 ContainerCreatingdescribe 一看事件里写着类似failed to set up pod network的报错。这通常不是 Kubernetes 组件本身的问题而是 CNI 网络插件比如 Calico、Flannel没配好。排查链路建议按这个顺序先看 CNI Pod 是否 Running。kubectl get pods -n kube-system里如果 calico-node 或 flannel 不是 Running优先解决它。看事件的具体报错kubectl describe pod pod-name最末尾的 Events 区块信息量很大能指明是网卡创建失败还是 IP 分配失败。再看 kubelet 日志里 CNI 相关输出以及 CNI 插件的日志。最后检查节点间的网络互通比如底层防火墙是否放行了 VXLAN 或 BGP 所需端口。另外当整个集群的 Service DNS 解析都异常时链路排查顺序大概是CoreDNS Pod 是否存活 → kube-proxy 的规则是否生成 → 是否被网络策略拦截 → 底层节点是否有多路由冲突。只要按这条链走基本都能在几分钟内把问题定位到具体组件上。我在实际部署集群时最深的体会是K8s 排障别靠猜要按照组件通信链路一层层查。上文那个 Pod 创建旅程其实就是一个天然排查地图——先确认请求到了 API Server再看 Controller 有没有生成对应对象再看 Scheduler 有没有做调度决策再看 kubelet 有没有认领任务最后才看容器运行时的日志。按这个顺序走绝大多数问题十分钟内能定位。这套组件架构设计得这么绕不是没理由的每个角色单一职责、统一通过 API Server 通信虽然多跳了几层网络换来的是整个集群的稳定和解耦。如果你刚开始搭集群建议把这篇文章当成一份对照清单配合 kubeadm init 的输出一步步理解会比死记组件清单有用得多。