控制平面为什么被这样拆开
一句话总结
Kubernetes 不是“执行命令的系统”,而是**“写下期望状态后,由控制器不断让现实趋近该状态的系统”**。 控制平面之所以由多个部分组成,是因为执行这种循环的主体按职责进行了拆分。
为什么需要它
命令式系统执行“启动 3 个 Pod”后就结束了。如果一个节点随后死亡呢?没人会在意, 因为命令早已执行完毕。
声明式系统会保存“希望保持 3 个 Pod 的状态”。随后会有主体不断读取当前状态, 与期望状态比较,并在不同的时候缩小差距。这个循环称为 reconciliation loop, 它就是 Kubernetes 的全部。其余机制都是为了让这个循环安全且可扩展。
从这个角度看,组件拆分便很自然:状态写在哪里(etcd),由谁允许读写该状态(kube-apiserver), 由谁运行循环(kube-controller-manager),由谁运行决定把 Pod 放到哪个节点的特殊循环(kube-scheduler), 以及由谁在节点上真正启动容器(kubelet)。
它如何运作
控制平面
| 组件 | 职责 | 消失后会怎样 |
|---|---|---|
| etcd | 保存集群全部状态的唯一存储 | 集群失去记忆 |
| kube-apiserver | 通往 etcd 的唯一入口。认证、授权、准入和验证 | 任何人都无法读写任何内容 |
| kube-controller-manager | Deployment/ReplicaSet/Node/Endpoint 等数十种控制器循环 | 声明会被保存,但什么也不会发生 |
| kube-scheduler | 为 Pending Pod 分配节点 | Pod 会被创建,却永远保持 Pending |
**任何组件都不会直接操作 etcd。**scheduler、controller manager 和 kubelet 全部通过 apiserver。 这样认证、授权、准入和审计日志才能集中在同一处生效。这是通向 KCSA 的安全设计起点。
了解 etcd 中的键结构也很有帮助。所有资源都位于 /registry/ 下,
命名空间资源采用 /registry/<종류>/<네임스페이스>/<이름>,集群资源采用
/registry/<종류>/<이름>。值默认使用 protobuf 序列化。
节点
- kubelet——节点代理。它 watch apiserver,发现分配给本节点的 Pod 后,通过 CRI 命令运行时执行。
- kube-proxy——在节点上写入规则,把 Service 的虚拟 IP 转发到真实 Pod IP。 (如果由 Cilium 等技术通过 eBPF 接替此职责,也可以完全不安装 kube-proxy。)
- 容器运行时——例如 containerd。
API 由资源组成
与 Kubernetes 对话只有一种方式——对 REST 资源执行 CRUD。创建 Deployment、删除 Pod,
采用的都是同一种语法。用 kubectl api-resources 可以查看本集群已知的全部资源,
用 kubectl explain 可以查看每个字段的含义。养成不打开文档网站,直接询问集群的习惯,
无论考试还是现场工作都会最快。
标签选择器——松耦合的黏合剂
Kubernetes 中几乎没有“此 Service 直接指向那 3 个 Pod”这样的直接引用。 它会添加标签,再通过 selector 选择。Service、ReplicaSet、NetworkPolicy 全都通过 selector 寻找目标。 所以一个 selector 拼写错误就会造成无声故障——对象能正常创建,只是什么也匹配不到。
在实际工作中会遇到的情况
这是作者的家庭实验室真实发生过的事情。某天集群停止响应,并出现
dial tcp 10.0.0.111:6443: connect: no route to host。原因是 DHCP——
控制平面节点的 IP 从 .111 更新成了 .120。
这次事件让组件拆分以可见的方式显现出来:**etcd 与 kube-apiserver 因尝试绑定已经不存在的地址
.111 而失败,陷入 CrashLoopBackOff;kube-scheduler 和 controller-manager 因绑定 127.0.0.1,
进程仍然正常存活。**它们虽然活着,却什么也做不了,因为没有 apiserver,控制器就无从读取或写入。
决定性证据在证书中。apiserver.crt 的 Subject Alternative Name 里没有 .120。
所以只修改 IP 无法恢复——使用 SAN 中不存在的地址连接时,TLS 验证会失败。
后来把同一集群扩展到 7 个节点(3 个控制平面 + 4 个 GPU worker)时,又得到了更惨痛的教训。
即使建立 3 个 etcd 成员并形成 quorum,controlPlaneEndpoint 填写的却不是 VIP 或 DNS,
而是 cp-1 的物理 IP(10.0.0.120:6443)。cp-1 一旦死亡,etcd quorum 仍以 2/3 正常工作,
另外两个节点上的 apiserver 进程也都正常,但 kubectl 与 7 个 kubelet 会全部无法连接。
这就是“控制平面仍活着,却没人能找到入口”的状态。
这次事故可以归纳为一句话:数据可用性与访问可用性是完全不同的问题。
后续实验要做什么
紧接着的实验会创建 kcna-arch 命名空间、列出节点,并通过 kubectl api-resources 和
kubectl explain 直接向集群询问 API。然后用标签选择器筛选 Pod,
最后分别以命令式与声明式两种方式创建同一个 Deployment,确认两者的区别。