前言:为什么要做章节总结?
啃完第五章的 4 篇,你应该能感受到——APIServer 是个怪兽。
它既是 REST API 网关、又是认证授权中心、还是准入控制网关、还是存储抽象层、最后还要做限流——一个组件包了五个角色,源码十几万行。
我自己第一次啃完时也是懵的,零散的细节记一堆,全局图却画不出来——直到一次面试问"画出 kubectl create pod 的完整链路",我才发现自己学过等于没学。
这篇就是把整章的细节串成一张图,让你能在脑子里跑通一次完整请求——这才算真正掌握了 APIServer。
本节重点
- APIServer 内部三个 Server 的职责(核心/扩展/聚合)
- 一次请求的完整生命周期(Authn → Authz → Admission → Storage)
- 关键对象总览:GenericAPIServer / Scheme / RESTStorage / Storage Backend
- 上手 APIServer 源码的实战路线图
一、APIServer 的核心定位
一句话定义:APIServer 是 K8s 控制面唯一直接读写 etcd 的组件,所有其他组件(scheduler、controller-manager、kubelet)全部通过 APIServer 间接访问数据。
这个"唯一中介"设计带来三个关键效果:
- etcd 不直接对外——所有客户端只能走 APIServer,APIServer 在前面做认证/鉴权/限流
- 统一数据视图——所有组件看到的是同一份经过 strategy 处理后的对象(status 字段、QoS class 这些都是 APIServer 加的)
- 可观测的总入口——所有变更都经过 APIServer,audit log 能看到一切
💡生产建议:千万别让运维工具直接连 etcd(哪怕"只是读")——绕过 APIServer 就绕过了 RBAC、绕过了 audit、绕过了 strategy 处理。我们公司内部有条死规矩:直连 etcd 的 PR 一律拒绝。
二、整体架构:三个 Server + 一个通用基座
┌──────────────────────────────────┐ │ 用户 / 客户端 │ │ kubectl / controller / kubelet │ └──────────────┬───────────────────┘ │ HTTPS ▼ ┌────────────────────────────────────────────────┐ │ kube-apiserver 进程 │ │ ┌──────────────────────────────────────────┐ │ │ │ AggregatorServer (聚合层 / 最外层) │ │ │ │ └─ 转发 metrics-server、custom apiservice│ │ │ │ ├─ 委托链 ──┐ │ │ │ └─────────────────┼─────────────────────────┘ │ │ ┌─────────────────▼─────────────────────────┐ │ │ │ KubeAPIServer (核心 API) │ │ │ │ └─ Pod / Service / Deployment / ... │ │ │ │ ├─ 委托链 ──┐ │ │ │ └─────────────────┼─────────────────────────┘ │ │ ┌─────────────────▼─────────────────────────┐ │ │ │ APIExtensionsServer (CRD) │ │ │ │ └─ CustomResourceDefinitions │ │ │ └───────────────────────────────────────────┘ │ │ 全部依赖 ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ GenericAPIServer (通用基座) │ │ │ │ go-restful + 通用路由 + handler chain │ │ │ └──────────────────┬───────────────────────┘ │ └─────────────────────┼──────────────────────────┘ ▼ ┌──────────────────────┐ │ etcd v3 (分布式存储)│ └──────────────────────┘三个 Server 的分工
| Server | 负责什么 | 典型资源 | 失败影响 |
|---|---|---|---|
| AggregatorServer | 转发到外部 APIService | metrics.k8s.io、custom.metrics.k8s.io | kubectl top不可用 |
| KubeAPIServer | K8s 原生资源 | Pod、Service、Deployment、ConfigMap | 整个集群挂 |
| APIExtensionsServer | CRD 资源 | CRD 定义 + 所有 CR 实例 | 所有 CRD 不可用 |
💡委托链 (Delegation Chain) 的执行顺序:请求先到Aggregator→ 它处理不了就委托给 KubeAPIServer→ 还处理不了就委托给 APIExtensions→ 都没匹配返回 404。详见 5.1 启动流程。
三、一次完整请求的生命周期
以kubectl create -f pod.yaml为例:
┌─────────────────────────────────────────────────────────────┐ │ 请求生命周期 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 1️⃣ HTTP 入口 │ │ POST /api/v1/namespaces/default/pods │ │ │ │ │ ▼ │ │ 2️⃣ 限流 (Throttle) │ │ ├─ MaxInFlight (chan 信号量) 或 │ │ └─ APF (优先级 + 公平队列) │ │ │ │ │ ▼ │ │ 3️⃣ Authentication (认证: 你是谁?) │ │ ├─ X.509 客户端证书 │ │ ├─ ServiceAccount Token │ │ ├─ Bootstrap Token │ │ └─ OIDC / Webhook Token │ │ │ │ │ ▼ │ │ 4️⃣ Authorization (鉴权: 你能干吗?) │ │ ├─ RBAC (最常见) │ │ ├─ ABAC │ │ ├─ Node (kubelet 专用) │ │ └─ Webhook │ │ │ │ │ ▼ │ │ 5️⃣ Mutating Admission (变更准入) │ │ ├─ DefaultIngressClass │ │ ├─ DefaultStorageClass │ │ ├─ ServiceAccount (注入默认 SA) │ │ └─ MutatingAdmissionWebhook (自定义,比如 sidecar 注入) │ │ │ │ │ ▼ │ │ 6️⃣ Schema Validation (OpenAPI 校验) │ │ │ │ │ ▼ │ │ 7️⃣ Validating Admission (校验准入) │ │ ├─ PodSecurity │ │ ├─ ResourceQuota │ │ ├─ LimitRanger │ │ └─ ValidatingAdmissionWebhook │ │ │ │ │ ▼ │ │ 8️⃣ RESTStorage.Create() │ │ └─ genericregistry.Store.Create │ │ ├─ BeforeCreate │ │ │ ├─ Strategy.PrepareForCreate │ │ │ │ ├─ Status = Pending │ │ │ │ ├─ QOSClass = GetPodQOS │ │ │ │ └─ DropDisabledPodFields │ │ │ ├─ EnsureObjectMeta (UID) │ │ │ ├─ Validate │ │ │ └─ Canonicalize │ │ ├─ Storage.Create (写 etcd) │ │ │ ├─ Encode (protobuf) │ │ │ ├─ Transformer (加密) │ │ │ └─ Txn().If(notFound).Put() │ │ └─ Decorator / AfterCreate │ │ │ │ │ ▼ │ │ 9️⃣ Audit (审计) │ │ └─ 写 audit log │ │ │ │ │ ▼ │ │ 🔟 Response │ │ └─ 返回 201 Created + 完整 Pod 对象 (含 RV) │ │ │ └─────────────────────────────────────────────────────────────┘🚨顺序非常关键:
- 限流在认证之前→ 即使匿名请求也能被限流(防 DDoS)
- Mutating Admission 在 Schema Validation 之前→ webhook 可以加字段
- Mutating Admission 在 Strategy.PrepareForCreate 之后→ 这就是 5.3 里那个 status 被改空的坑的根源
四、关键对象总览
4.1 GenericAPIServer(通用基座)
位置:staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go
职责:
- 用
go-restful注册路由 - 装配 handler chain(认证 / 鉴权 / 准入 / 限流 / 存储)
- 管理 PostStartHook 生命周期
为什么有它?因为三个 Server 大部分逻辑(HTTP、限流、认证、handler chain)都是一样的——把通用部分抽出来,三个 Server 各自只关心自己的资源注册。这是教科书级别的模板方法模式。
4.2 Scheme(类型注册中心)
位置:staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
typeSchemestruct{gvkToTypemap[schema.GroupVersionKind]reflect.Type typeToGVKmap[reflect.Type][]schema.GroupVersionKind converter*conversion.Converter versionPrioritymap[string][]string...}Scheme 是 K8s 类型系统的"通讯录",干三件事:
- 类型注册:每个资源类型(Pod、Deployment)都要在 Scheme 里注册其 GVK(GroupVersionKind)
- 版本转换:
v1.Pod ↔ internal.Pod、v1beta1.Deployment ↔ v1.Deployment - 序列化/反序列化:JSON、YAML、protobuf 互转
💡K8s 多版本机制的核心:客户端用 v1,etcd 存内部版本(internal),转换全靠 Scheme。这就是为什么 K8s 能"兼容老 API 又能演进"——内部存 internal,对外提供多版本视图。
4.3 RESTStorage(REST 与存储的桥梁)
每种资源都有一个RESTStorage,长这样:
typeRESTstruct{*genericregistry.Store proxyTransport http.RoundTripper}genericregistry.Store是个通用 CRUD 引擎,资源专属逻辑通过Strategy注入:
| 字段 | 作用 |
|---|---|
NewFunc | 怎么创建空对象(func() runtime.Object { return &Pod{} }) |
CreateStrategy | Create 时的预处理 + 校验 |
UpdateStrategy | Update 时的预处理 + 校验 |
DeleteStrategy | Delete 时的预处理 |
Storage | 底层存储后端(DryRunnableStorage → etcd3) |
Strategy 模式的精髓:通用 Store 处理 90% 的 CRUD,每个资源只写自己那 10% 的特殊逻辑。详见 5.2。
4.4 Storage Backend(存储后端)
podStorage.Storage │ ▼ DryRunnableStorage (拦截 dryRun) │ ▼ CacherStorage (内存 watch cache,加速 list) │ ▼ etcd3.store (真正的 etcd 客户端) │ ▼ etcd v3 集群几个关键层:
- CacherStorage:内存 watch cache——所有 watch 请求不直接打 etcd,而是命中这个 cache。这就是为什么大量 watch 不会把 etcd 拖死
- DryRunnableStorage:拦截
--dry-run=server——上面跑完所有逻辑,到这一层直接返回 - etcd3.store:序列化 + 加密 + Txn 写入
🚨CacherStorage 是性能关键:如果它和 etcd 失联(resourceVersion 严重落后),就会触发relist——一次 list 整个资源的全部对象。生产中如果看到
apiserver_storage_consistency_checks_total异常,赶紧排查,否则可能引发 list 风暴。
4.5 关键对象关系图
┌──────────────────┐ │ GenericAPIServer│ 通用基座 └────────┬─────────┘ │ 持有 ▼ ┌──────────────────┐ │ go-restful路由 │ └────────┬─────────┘ │ 注册资源 ▼ ┌──────────────────┐ │ RESTStorage │ 每个资源一个 │ (Pod/SVC/...) │ └────────┬─────────┘ ┌────────┴────────┐ ▼ ▼ ┌──────────┐ ┌──────────┐ │ Strategy │ │ Storage │ │ (业务逻辑)│ │ (存储链) │ └────┬─────┘ └────┬─────┘ │ │ ▼ ▼ ┌──────────┐ ┌──────────┐ │ Scheme │ │ Cacher → │ │ (类型注册)│ │ etcd3 │ └──────────┘ └──────────┘五、Authentication / Authorization / Admission 三道关
5.1 Authentication(你是谁?)
目的:识别请求来源,确定user.Info(含 username、groups、UID)。
常见 authn 方式(按生产使用频率):
| 方式 | 用途 | 生产建议 |
|---|---|---|
| X.509 Client Cert | kubelet、kubectl admin、kube-controller-manager | 默认 + 必备 |
| ServiceAccount Token | Pod 内访问 APIServer | 必备 |
| Bootstrap Token | 新 Node 注册 | kubeadm 默认 |
| OIDC | 公司 SSO 接入 | 强烈推荐 |
| Webhook Token | 自定义认证 | 高度定制需求 |
| 静态 Token File | 内网测试 | ⚠️ 生产禁用 |
🚨生产坑:用了静态 token file 然后忘记轮换——token 一旦泄露就是永久后门。OIDC + 短期 token + RBAC才是 SOTA。
5.2 Authorization(你能干吗?)
K8s 支持多种 authz 模式,按顺序执行——任意一个允许就放行:
--authorization-mode=Node,RBAC,Webhook │ │ │ │ │ └─ 自定义 webhook │ └─ 角色绑定(最常用) └─ kubelet 专用(限制 kubelet 只能读自己节点的资源)💡Node 模式很重要:它限制 kubelet只能 get/list 自己节点上的 Pod、Secret 等——防止一个被攻陷的 Node 横向窃取整个集群的 Secret。1.13+ 默认开启。
5.3 Admission(合规检查 + 改写)
Admission 分两类:
- MutatingAdmission:可以改 object(注入 sidecar、补默认值)
- ValidatingAdmission:只能 yes/no(PodSecurity、ResourceQuota)
执行顺序:Mutating → Schema 校验 → Validating
K8s 内置的关键 admission plugin:
| Plugin | 类型 | 作用 |
|---|---|---|
NamespaceLifecycle | V | 不允许操作正在删除的 namespace |
ServiceAccount | M | 自动绑定默认 SA、挂载 token |
LimitRanger | M | 根据 LimitRange 补默认 requests/limits |
ResourceQuota | V | 检查是否超过 namespace 配额 |
PodSecurity | V | 替代旧的 PodSecurityPolicy |
DefaultStorageClass | M | 给 PVC 自动选默认 StorageClass |
MutatingAdmissionWebhook | M | 用户自定义(如 Istio sidecar 注入) |
ValidatingAdmissionWebhook | V | 用户自定义(如 OPA Gatekeeper) |
🚨第 4 章学过的 sidecar 注入就是 MutatingAdmissionWebhook——所有 service mesh(Istio、Linkerd)都靠这个机制实现自动注入。
六、限流策略快速回顾
详见 5.4。这里只放精华表:
| 方案 | 场景 | 实现 | 建议 |
|---|---|---|---|
| client-go RateLimiter | 客户端自限速 | 令牌桶 | 默认 QPS=5 太低,必调 |
| MaxInFlightLimit | server 整体粗限流 | chan 信号量 | APF 未启用时的兜底 |
| EventRateLimit | event 资源专用 | 多桶令牌桶 | event 多的集群必开 |
| APF | 优先级 + 公平队列 | Shuffle Sharding + 并发预算 | 生产首选 |
记住三个关键源码模式:
- chan + select default= 非阻塞信号量(MaxInFlight)
- Shuffle Sharding= 隔离吵闹邻居(APF)
- watermark= 滑动指标平滑(throttle metrics)
七、上手 APIServer 源码的实战路线图
阶段一:跑通最小请求(半天)
- clone
kubernetes/kubernetes,切到 stable tag(如 v1.30) - 在 IDE 里打开
cmd/kube-apiserver/apiserver.go,找到 main 函数 - 一路跟到
app.Run→CreateServerChain(5.1 讲的入口) - 用 IDE 的 “Call Hierarchy” 工具向下钻,画一张调用图
阶段二:找一个资源跟到底(一天)
挑一个简单资源(比如 ConfigMap),通过断点 + 日志跟踪:
HTTP handler → REST.Create → genericregistry.Store.Create → BeforeCreate → Strategy.PrepareForCreate → Storage.Create → etcd3.store.Create跟完一次你就永远忘不了这条链路。
阶段三:尝试加一个字段(两天)
给某个 K8s 资源(用自定义 fork)加个字段。你会被迫接触:
- types.go(定义结构)
- defaults.go(默认值)
- validation.go(校验)
- strategy.go(PrepareForCreate)
- conversion(多版本转换)
- protobuf 生成(make update)
走完这套你就是半个 K8s 贡献者了。
阶段四:写一个 Admission Webhook(一周)
参考 4.3、4.4 的 sidecar 注入实战。把 webhook 部署到集群、生效、捕获请求、改写、放行——这是 APIServer 扩展能力的实战。
阶段五:调 APF(一周)
模拟流量(用kube-burner或k6),观察 APF 各 priority level 的指标,调整nominalConcurrencyShares——这是真正的 SRE 实战。
💡关键工具:
- dlv(Go 调试器)+ vscode-go:源码断点
- kube-burner:APIServer 压测利器
- Prometheus + 官方 dashboards:
kubernetes/kubernetes仓库带的 mixin
八、生产经验复盘:第五章学到的核心教训
| # | 教训 | 来自 |
|---|---|---|
| 1 | 永远不要直连 etcd——绕过所有保护 | 全章 |
| 2 | mutating webhook 不要改 status——会被信任并保留 | 5.3 |
| 3 | client-go 默认 QPS=5,生产必须调高 | 5.4 |
| 4 | --max-requests-inflight=0是关闭限流不是无限 | 5.4 |
| 5 | system:masters 永远豁免限流——业务别用 cluster-admin | 5.4 |
| 6 | feature gate 关了字段会被静默丢弃——排查必查 | 5.3 |
| 7 | 加密 key 一定要备份——丢了等于数据丢 | 5.3 |
| 8 | watch 不受 MaxInFlight 控制,切到 APF | 5.4 |
| 9 | 三个 Server 委托链顺序:Aggregator → Kube → Extension | 5.1 |
| 10 | Strategy 模式是 K8s 处理资源多样性的核心抽象 | 5.2 |
九、章节思考题
回答这些问题,验证你是否真懂了:
- 同一个 Pod 对象在内存、etcd、kubectl 三处的形态分别是什么?经过哪些转换?
- 为什么 K8s 选 Strategy 模式而不是给每个资源写独立 handler?
- APF 完全替代得了 MaxInFlight 吗?什么场景下还会用 MaxInFlight?
- 如果你要为某种新资源(比如自定义 GPU 资源)加 APIServer 支持,最少要改哪些文件?
- 一个 Pod 创建后,scheduler 是怎么"知道"它的?这背后的机制是什么?
十、本节小结
APIServer 是 K8s 控制面的核心枢纽,理解它的关键是抓住几条主线:
- 架构主线:三个 Server(Aggregator/Kube/Extension)+ 一个通用基座(GenericAPIServer)
- 请求主线:限流 → Authn → Authz → Admission → Storage → Audit
- 数据主线:Scheme 注册 → RESTStorage 路由 → Strategy 处理 → Storage 写 etcd
- 保护主线:四层限流(client/MaxInFlight/EventRateLimit/APF)+ 三道关(Authn/Authz/Admission)