Kubernetes 控制平面深度剖析:从 kubectl apply 到 etcd 写入的全链路

一、引言:你以为的「提交」,其实是一场精密接力

几乎每个 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 机制收到这次变更通知

两个最重要的认知:

  1. RBAC 只管「能不能调接口」,准入控制管「对象长什么样」。这就是为什么你即使有 create pod 权限,提交一个特权容器仍可能被 PSA/Kyverno 拒绝------授权通过了,但准入没通过(呼应《RBAC》《Kyverno》两篇)。
  2. 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)
    三个核心认知:
  1. 控制器不会直接读 etcd,也不会每次都问 apiserver。它先 List 一次拿全量建本地缓存(Indexer),之后只处理 watch 推送的增量事件。这就是为什么一个集群里有几百个控制器也不会把 apiserver 打爆------读几乎全在本地缓存命中。
  2. reconcile 是「基于事件的幂等函数」。它不关心「谁、为什么」触发,只关心「当前实际状态和声明是否一致」。同一个事件可能被处理多次(网络重试),所以 reconcile 必须幂等。
  3. 最终一致性 = 不断 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 协作的组件:

  1. API Server 是唯一入口:它做认证、授权、准入、结构校验,然后把对象写入 etcd。它不做业务逻辑------它只是 etcd 的守门人与校验器。
  2. etcd 是唯一事实来源:所有对象最终都是 registry/// 下的一个键值;它的 Raft 共识、WAL fsync 延迟、MVCC revision 决定了整个集群的可用性与一致性。
  3. List-Watch + Informer 是控制平面的血液循环:控制器不轮询、不直读 etcd,而是先 List 建缓存、再 Watch 增量、经 Workqueue 驱动 reconcile------这才是「声明式 + 最终一致」能成立的底层引擎。
  4. 排障的钥匙在分层定位:从「能不能连」→「认证/授权」→「etcd 写入」→「控制器 reconcile」逐层下钻,绝大部分控制平面故障都能在 4.1~4.4 的表里找到对应根因。
相关推荐
openFuyao2 小时前
openFuyao x 复旦大学软件工程实验室 Panel 回顾:Agentic时代,集群调度技术体系将如何演进?
kubernetes·openfuyao
螺蛳粉 螺蛳粉3 小时前
第 05 篇:etcd 深入:Raft 共识、watch 机制与快照备份恢复实战(底座认知 05)
docker·云原生·容器·kubernetes·etcd·分布式存储·kube-apiserver
相思难忘成疾6 小时前
从零搭建 Kubernetes 1.30 集群:kubeadm 部署一主多从实战(RHEL 9.3 + Docker + flannel)
linux·运维·kubernetes
weirong7 小时前
给 K8s 加一个自动值班员:分级故障自愈引擎
kubernetes
云和数据.ChenGuang7 小时前
Python 闭包详解:从原理到实战
linux·开发语言·人工智能·python·kubernetes
weirong12 小时前
为什么 HPA 会震荡?手写一个带冷却窗口的伸缩决策引擎
kubernetes
-星空下无敌13 小时前
Windows Docker K8s 安装部署Naocs ,SpringBoot 3.x 4.x 整合Naocs 版本兼容 配置中心、服务注册与发现
windows·docker·nacos·kubernetes·springboot·alibaba
吴声子夜歌13 小时前
Kubernetes权威指南——深入掌握Pod(使用StatefulSet搭建MongoDB集群)
容器·kubernetes
fb_1234513 小时前
K8s动态卷供应实战_StorageClass与LocalPath_NFS双Provisioner
云原生·容器·kubernetes