一、引言:你以为的「提交」,其实是一场精密接力
几乎每个 K8s 使用者都写过 kubectl apply -f deploy.yaml,但很少有人能完整说出:这一个命令,从敲下回车到对象真正落库,中间到底经过了几道关卡?
多数人的心智模型是一个「黑盒」:
kubectl apply ──▶ ??? ──▶ 对象出现在集群里
而真实的链路是:
kubectl
│ (1) 读取 kubeconfig,构造 HTTPS 请求,携带身份凭据
▼
kube-apiserver
│ (2) Authentication ── 你是谁?
│ (3) Authorization ── 你有权限吗?(RBAC,呼应《RBAC 最小权限》)
│ (4) Mutating Admission ── 先改对象(Kyverno mutate / PSA,呼应《Kyverno》《Pod 安全》)
│ (5) Validating Admission ── 再校验对象(Kyverno validate / ResourceQuota,呼应《准入控制》《多租户治理》)
│ (6) Schema Validation ── 字段合法吗?
│ (7) 写入 etcd ── 唯一事实来源(呼应《ETCD 精讲》的存储角色)
▼
etcd(持久化落库,返回 revision)
│ (8) 事件通知(watch)
▼
Controller Manager / Scheduler / kubelet(感知变化,进入 reconcile)
这条链路就是控制平面(Control Plane)的全部核心职责。本文要做的,就是把上面 8 步拆开,讲清每个组件的角色、它们之间如何协作,以及如何在实际集群里观察与排错。
一个关键认知纠偏:很多人把 kubelet、CNI 网络插件也算进「控制平面」。严格来说,控制平面只包括 kube-apiserver / etcd / kube-controller-manager / kube-scheduler------它们是「决策与元数据存储」层;kubelet 是「执行层」,CNI 是「网络数据面」,都不在控制平面定义内。
二、原理解析
2.1 控制平面组件全景与职责边界
组件
角色
是否唯一入口
说明
kube-apiserver
集群网关 + 校验器 + etcd 守门人
✅ 是
所有读写都要经过它,唯一能直接读写 etcd 的组件
etcd
强一致键值存储
集群唯一事实来源,仅 apiserver 直连
kube-scheduler
决策 Pod 落在哪台节点
高可用多副本
只「决定」不「执行」(呼应《调度器全链路》)
kube-controller-manager
一组控制循环的集合
高可用多副本
包括 Deployment/Node/Namespace 等数十个控制器
cloud-controller-manager
云厂商适配(可选)
取决于部署
节点/路由/负载均衡等云资源对接
设计哲学:状态变化 只通过 API Server 发生;其他组件都是「监听器 + 响应器」。这正是声明式架构的精髓------没有一个中央协调者去「命令」谁做什么,而是每个组件 watch 自己关心的对象,然后让实际状态向期望状态收敛。
2.2 API Server 请求生命周期(七道关卡)
kube-apiserver 收到一个写请求后,按固定顺序依次穿过以下关卡,任何一关拒绝,整个请求直接 4xx/5xx 返回,不会落到 etcd:
请求
│
├─ ① TLS 终止 + Authentication(认证)
│ 身份来源:客户端证书 / ServiceAccount Token / OIDC / Webhook / Bootstrap Token
│ 产出:user.Info(用户名、组、UID)
│
├─ ② Authorization(授权)
│ RBAC(最常用) / ABAC / Node / Webhook
│ 判定:该 user 对该 resource 的 verb 是否允许
│
├─ ③ Mutating Admission(变更准入,可改对象)
│ 顺序执行:按顺序调用各 mutating webhook + 内置 mutating 插件
│ 典型:Kyverno mutate、Pod Security Admission 注入默认值、ServiceAccount token 挂载
│
├─ ④ Validating Admission(验证准入,只读不改)
│ 典型:Kyverno validate、ResourceQuota 校验、LimitRange 校验、PodSecurity 拒绝
│
├─ ⑤ Schema / 内置 Validation(结构校验)
│ 字段类型、枚举值、必填项、openAPIV3Schema 强校验(呼应《CRD/Operator》)
│
├─ ⑥ 持久化写入 etcd
│ apiserver 把对象序列化为 protobuf/json 写入 etcd 的对应 key
│ etcd 返回全局递增的 modRevision
│
└─ ⑦ 返回结果 + 广播事件(watch)
其他组件通过 watch 机制收到这次变更通知
两个最重要的认知:
- RBAC 只管「能不能调接口」,准入控制管「对象长什么样」。这就是为什么你即使有 create pod 权限,提交一个特权容器仍可能被 PSA/Kyverno 拒绝------授权通过了,但准入没通过(呼应《RBAC》《Kyverno》两篇)。
- apiserver 自身不做业务逻辑。它不决定 Pod 调度、不创建容器、不拉镜像------它只负责「接收、校验、落库、广播」。所有「让事情发生」的逻辑都在下游控制器和 kubelet 里。
2.3 etcd:强一致的单一事实来源
ETCD 篇(Helm 部署)讲了怎么把它跑起来,这里讲它在控制平面内部扮演的角色。
(1) Raft 共识与 Quorum
etcd 是一个 Raft 复制的状态机。关键事实:
- 集群必须奇数节点(3/5/7),容忍 ⌊N/2⌋ 个故障
- 所有写请求先发到 Leader,Leader 通过 Raft 日志复制到多数派(quorum)后才提交
- 一次写成功 = Leader 本地持久化 + N/2+1 个节点确认
3 节点:容忍 1 个故障
5 节点:容忍 2 个故障
偶数节点(如 4):容错仍是 1(quorum=3),且脑裂时更易陷入无多数派 → 反模式
(2) WAL + fsync:延迟命门
每个写先追加到 WAL(Write-Ahead Log) 并 fsync 到磁盘,再更新内存 B+ 树(boltdb)。WAL 的 fsync 延迟直接决定 etcd 的写吞吐和稳定性------磁盘慢一秒,leader 就可能因为心跳超时被踢下台(呼应《ETCD 精讲》篇的 SSD 要求)。
(3) MVCC 与 resourceVersion
etcd 用 MVCC(多版本并发控制):每次写入都产生一个全局递增的 revision。K8s 把它暴露为对象的 .metadata.resourceVersion。
这一点至关重要------List-Watch 机制正是基于 revision 实现的增量同步(见 2.4 节)。
(4) watch 不是轮询
客户端向 apiserver 发一个带 resourceVersion 的 watch 请求,apiserver 在 etcd 上注册一个 watch,etcd 有变更时通过长连接推送给客户端。不是客户端定时轮询,而是事件驱动的流式推送。这就是为什么集群里有成千上万个 Pod,控制器仍能近实时感知变化。
2.4 List-Watch 与 Informer:控制器的「感知引擎」
这是整篇最该理解的一环,也是前面《CRD/Operator》《调度器》反复提到「reconcile」「最终一致」的底层引擎。
每个内置控制器(以及你写的 Operator)都遵循同一套机制:
┌─────────────────────────────────────────┐
│ kube-apiserver │
│ (唯一数据源,etcd 的代理与校验层) │
└───────────────┬─────────────────────────┘
│ List(首次全量,带 resourceVersion)
│ + Watch(之后增量事件流)
▼
┌──────────────────────────────────────────┐
│ Reflector │ ← 监听者
│ 把事件(ADDED/MODIFIED/DELETED)加入队列 │
└──────────────────┬───────────────────────┘
▼
┌──────────────────────────────────────────┐
│ DeltaFIFO │ ← 去重事件队列
└──────────────────┬───────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Indexer(本地缓存 LocalStore) │ ← 内存镜像
│ 控制器绝大多数读都命中这里,不回源 apiserver │
└──────────────────┬───────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Workqueue(限速队列) │ │
│ 把「需要处理的 key」交给 reconcile 逻辑 │
└──────────────────┬───────────────────────┘
▼
Reconcile(协调函数)
比较期望状态 vs 实际状态,发出修正动作
(如 Deployment 控制器创建 ReplicaSet、Pod)
三个核心认知:
- 控制器不会直接读 etcd,也不会每次都问 apiserver。它先 List 一次拿全量建本地缓存(Indexer),之后只处理 watch 推送的增量事件。这就是为什么一个集群里有几百个控制器也不会把 apiserver 打爆------读几乎全在本地缓存命中。
- reconcile 是「基于事件的幂等函数」。它不关心「谁、为什么」触发,只关心「当前实际状态和声明是否一致」。同一个事件可能被处理多次(网络重试),所以 reconcile 必须幂等。
- 最终一致性 = 不断 reconcile。apiserver 落库后,对象可能暂时「声明已更新、实际未更新」;控制器会在秒级内通过 watch 感知并补齐。排障里看到的「刚改完还没生效」往往是这个时间差。
2.5 Leader Election:高可用组件的互斥
kube-scheduler、kube-controller-manager 都是多副本部署,但同一时刻只能有一个实例真正「干活」,否则会重复调度/重复创建。它们通过 Lease 资源(endpoint Lease) 做 leader election:抢到租约的成为 active,其他 standby。apiserver 自身也是多副本但通过负载均衡 + etcd 单一事实来源天然无冲突。
三、实战演示
以下命令假定你有一个可访问控制平面节点的环境(如 kubeadm 集群的 master 节点,或对托管集群有 equivalent 观察手段)。托管集群(如 EKS/GKE)看不到静态 Pod 和 etcd 证书,相关步骤会注明替代观察方式。
场景 1:追踪一次 kubectl apply 的认证与授权过程
用 -v=8 打开 kubectl 的详细日志,直接看到它发往 apiserver 的请求与响应:
提交一个对象,并打开最高级别调试日志
kubectl apply -f deploy.yaml -v=8
你会看到类似输出:
I1029 10:00:00.000000 12345 round_trippers.go:553] GET https://10.96.0.1:443/openapi/v2 200 OK
I1029 10:00:00.001000 12345 round_trippers.go:553] POST https://10.96.0.1:443/apis/apps/v1/namespaces/default/deployments 201 Created
-v=8 会显示:
- kubectl 如何读取 ~/.kube/config 选择凭据
- 实际访问的 apiserver 地址与路径
- 401/403 时能看到具体的授权拒绝原因(呼应《RBAC》篇的排查手法)
实用技巧:当你不确定「是认证挂了还是授权挂了」,先 -v=8,再看是 401 Unauthorized(身份问题)还是 403 Forbidden(RBAC 策略问题)。
场景 2:查看 API Server 审计日志,还原「谁动了什么」
apiserver 的审计(Audit)能记录每个请求的 user、verb、resource、响应码。先看集群是否开启:
找到 apiserver 静态 Pod 的审计配置(kubeadm 默认在 /etc/kubernetes/manifests)
ps aux | grep kube-apiserver | grep -o '--audit-policy-file=\^ *'
若配置了,审计日志路径由 --audit-log-path 指定,例如:
sudo tail -n 20 /var/log/kubernetes/audit/audit.log | jq '.'
一条审计记录长这样(节选):
{
"verb": "create",
"user": { "username": "system:serviceaccount:prod:deployer" },
"objectRef": { "resource": "deployments", "namespace": "prod", "name": "shop-web" },
"responseStatus": { "code": 403 }
}
这正是安全审计与《RBAC》篇「最小权限」落地的可观测基础:谁、对哪个对象、做了什么、成功与否,一目了然。
场景 3:直接连 etcd,看 K8s 对象到底存成什么样
⚠️ 此操作需要 etcd 客户端证书,且直接读写 etcd 极其危险,仅用于学习和排障,绝不可在生产随意写。
从 kubeadm 静态 Pod 清单里取得 etcd 证书路径
ETCD_POD=$(sudo crictl ps --name etcd -q)
sudo crictl inspect $ETCD_POD | jq -r '.info.config.volumes\[\].source' | grep pki
用 etcdctl 读一个 Pod 的原始存储(注意前缀是 /registry/)
ETCDCTL_API=3 etcdctl
--cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/etcd/server.crt
--key=/etc/kubernetes/pki/etcd/server.key
--endpoints=https://127.0.0.1:2379
get /registry/pods/default/shop-web-7d9c-abcde | head -c 500
你会看到对象被序列化成 protobuf/json 存在 registry///<name 的路径下。K8s 里一切对象,最终都是 etcd 里的一个 key-value------这就是「etcd 是唯一事实来源」的直观证据。
场景 4:观察 List-Watch 事件流
用 kubectl get --watch 变相观察 watch 机制(它底层就是 watch 请求 + 增量事件):
终端 1:开启 watch
kubectl get pods -n default --watch
终端 2:制造一个变更
kubectl scale deployment shop-web --replicas=3
终端 1 会实时打印:
shop-web-7d9c-xyz 0/1 Pending 0 0s
shop-web-7d9c-xyz 0/1 ContainerCreating 0 1s
shop-web-7d9c-xyz 1/1 Running 0 3s
这条实时流就是 watch 推送的增量事件------控制器用的也是同一套机制,只是它把事件喂给了 reconcile 逻辑而非你的终端。
场景 5:确认控制平面组件健康与 leader
查看静态 Pod 形式的控制平面组件是否 Running
kubectl get pods -n kube-system | grep -E 'kube-apiserver|kube-controller|kube-scheduler|etcd'
查看 scheduler / controller-manager 的 leader(Lease)
kubectl get lease -n kube-system
NAME HOLDER
kube-scheduler kube-scheduler-master-1
kube-controller-manager kube-controller-manager-master-1
如果某个组件有多个副本但 HOLDER 只有一个,说明 leader election 正常工作;若 HOLDER 频繁变化,往往是组件与 apiserver 心跳超时(见排错 3.2)。
四、排错实战
控制平面出问题的典型症状是:kubectl 卡住、所有写操作超时、节点 NotReady 但机器还活着、部分功能间歇性失效。下面按「组件 → 症状 → 根因 → 动作」组织。
4.1 API Server 无响应 / 请求超时
现象
常见根因
排查动作
kubectl 所有命令 timeout 或 Unable to connect
apiserver Pod 没起来 / 证书过期 / 资源耗尽
crictl ps | grep kube-apiserver;看 apiserver 日志 journalctl -u kubelet;查证书有效期
x509: certificate has expired
证书过期(kubeadm 默认 1 年)
kubeadm certs check-expiration;过期则 kubeadm certs renew all
写操作慢但读正常
etcd 写入延迟高(磁盘慢 / WAL fsync 慢)
见 4.2,查 etcd 指标 etcd_disk_wal_fsync_duration_seconds
关键动作------查证书有效期(很多「莫名其妙集群挂了」其实是证书到期):
kubeadm 集群一键检查所有证书有效期
sudo kubeadm certs check-expiration
RESIDUAL TIME 列显示剩余时间,CA 默认 10 年,其余 1 年
4.2 etcd 延迟高 / leader 频繁切换
现象
常见根因
排查动作
apiserver 日志大量 etcdserver: request timed out
etcd 磁盘 WAL fsync 慢
确认 etcd 用 SSD;iostat -x 1 看 %util 与 await
etcd leader 频繁切换
节点间网络抖动 / 心跳超时 / 磁盘慢致 leader 超时
etcdctl endpoint status --write-out=table 看 leader 与 raftTerm;看 etcd_server_leader_changes_seen_total
etcdserver: mvcc: database space exceeded
etcd 历史版本未 compact,db 撑满
etcdctl compact + etcdctl defrag(呼应《ETCD 精讲》的定期 compact+defrag)
这条与《ETCD 精讲》篇呼应:生产必须定期 compact + defrag,并给 etcd 配独立 SSD,否则控制平面会周期性雪崩。
4.3 控制器「不干活」
现象
常见根因
排查动作
Deployment 一直不创建 Pod
controller-manager 没在 leader / 没 Running
kubectl get lease -n kube-system 看 holder;kubectl logs -n kube-system kube-controller-manager-xxx
自定义 Operator 无反应
RBAC 缺 watch/list 权限 / Pod 没 Running(呼应《CRD/Operator》)
看 Operator 日志 permission denied;查其 ServiceAccount 的 ClusterRole
多个副本争抢
leader election 异常
看 Lease HOLDER 是否稳定
4.4 watch 风暴:Too many requests / Watch channel full
现象
常见根因
排查动作
apiserver 日志 Too many requests from
某客户端 watch 太频繁或断开重连风暴
找源头 Pod:谁在疯狂 list/watch(常见是配置错误的控制器或监控 agent)
client-go: watch of ... ended with unexpected ...
etcd 历史被 compact,watch 断点已失效,被迫重新 List
这是正常的 resilience 机制,但若频繁发生说明 watch 周期过短
4.5 排错决策树
控制平面异常
├─ kubectl 完全连不上?
│ ├─ 证书过期? → kubeadm certs check-expiration / renew
│ └─ apiserver Pod 没起? → crictl 查状态 + 看 kubelet 日志
├─ 能连但写超时/报错?
│ ├─ 401 → 身份认证问题(kubeconfig / token)
│ ├─ 403 → RBAC 授权问题(呼应《RBAC》用 auth can-i 验证)
│ └─ 5xx/timeout → etcd 慢 → 查 etcd fsync / leader(4.2)
├─ 能写但「改了不生效」?
│ └─ 控制器未 reconcile → 查 controller-manager / Operator 日志与 RBAC(4.3)
└─ 间歇性失效?
└─ watch 风暴 / leader 切换 → 查客户端与网络(4.4 / 4.2)
五、总结
控制平面是 K8s 的「大脑」,但它不是一台中央计算机,而是一组各司其职、通过 watch 协作的组件:
- API Server 是唯一入口:它做认证、授权、准入、结构校验,然后把对象写入 etcd。它不做业务逻辑------它只是 etcd 的守门人与校验器。
- etcd 是唯一事实来源:所有对象最终都是 registry/// 下的一个键值;它的 Raft 共识、WAL fsync 延迟、MVCC revision 决定了整个集群的可用性与一致性。
- List-Watch + Informer 是控制平面的血液循环:控制器不轮询、不直读 etcd,而是先 List 建缓存、再 Watch 增量、经 Workqueue 驱动 reconcile------这才是「声明式 + 最终一致」能成立的底层引擎。
- 排障的钥匙在分层定位:从「能不能连」→「认证/授权」→「etcd 写入」→「控制器 reconcile」逐层下钻,绝大部分控制平面故障都能在 4.1~4.4 的表里找到对应根因。