APIServer 全貌、关键对象与生产经验复盘

📅 2026/6/22 19:34:44
APIServer 全貌、关键对象与生产经验复盘
前言为什么要做章节总结啃完第五章的 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 不直接对外——所有客户端只能走 APIServerAPIServer 在前面做认证/鉴权/限流统一数据视图——所有组件看到的是同一份经过 strategy 处理后的对象status 字段、QoS class 这些都是 APIServer 加的可观测的总入口——所有变更都经过 APIServeraudit 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转发到外部 APIServicemetrics.k8s.io、custom.metrics.k8s.iokubectl top不可用KubeAPIServerK8s 原生资源Pod、Service、Deployment、ConfigMap整个集群挂APIExtensionsServerCRD 资源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) │ │ │ └─────────────────────────────────────────────────────────────┘顺序非常关键限流在认证之前→ 即使匿名请求也能被限流防 DDoSMutating 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.gotypeSchemestruct{gvkToTypemap[schema.GroupVersionKind]reflect.Type typeToGVKmap[reflect.Type][]schema.GroupVersionKind converter*conversion.Converter versionPrioritymap[string][]string...}Scheme 是 K8s 类型系统的通讯录干三件事类型注册每个资源类型Pod、Deployment都要在 Scheme 里注册其 GVKGroupVersionKind版本转换v1.Pod ↔ internal.Pod、v1beta1.Deployment ↔ v1.Deployment序列化/反序列化JSON、YAML、protobuf 互转K8s 多版本机制的核心客户端用 v1etcd 存内部版本internal转换全靠 Scheme。这就是为什么 K8s 能兼容老 API 又能演进——内部存 internal对外提供多版本视图。4.3 RESTStorageREST 与存储的桥梁每种资源都有一个RESTStorage长这样typeRESTstruct{*genericregistry.Store proxyTransport http.RoundTripper}genericregistry.Store是个通用 CRUD 引擎资源专属逻辑通过Strategy注入字段作用NewFunc怎么创建空对象func() runtime.Object { return Pod{} }CreateStrategyCreate 时的预处理 校验UpdateStrategyUpdate 时的预处理 校验DeleteStrategyDelete 时的预处理Storage底层存储后端DryRunnableStorage → etcd3Strategy 模式的精髓通用 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-runserver——上面跑完所有逻辑到这一层直接返回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 Certkubelet、kubectl admin、kube-controller-manager默认 必备ServiceAccount TokenPod 内访问 APIServer必备Bootstrap Token新 Node 注册kubeadm 默认OIDC公司 SSO 接入强烈推荐Webhook Token自定义认证高度定制需求静态 Token File内网测试⚠️ 生产禁用生产坑用了静态 token file 然后忘记轮换——token 一旦泄露就是永久后门。OIDC 短期 token RBAC才是 SOTA。5.2 Authorization你能干吗K8s 支持多种 authz 模式按顺序执行——任意一个允许就放行--authorization-modeNode,RBAC,Webhook │ │ │ │ │ └─ 自定义 webhook │ └─ 角色绑定最常用 └─ kubelet 专用限制 kubelet 只能读自己节点的资源Node 模式很重要它限制 kubelet只能 get/list 自己节点上的 Pod、Secret 等——防止一个被攻陷的 Node 横向窃取整个集群的 Secret。1.13 默认开启。5.3 Admission合规检查 改写Admission 分两类MutatingAdmission可以改 object注入 sidecar、补默认值ValidatingAdmission只能 yes/noPodSecurity、ResourceQuota执行顺序Mutating → Schema 校验 → ValidatingK8s 内置的关键 admission pluginPlugin类型作用NamespaceLifecycleV不允许操作正在删除的 namespaceServiceAccountM自动绑定默认 SA、挂载 tokenLimitRangerM根据 LimitRange 补默认 requests/limitsResourceQuotaV检查是否超过 namespace 配额PodSecurityV替代旧的 PodSecurityPolicyDefaultStorageClassM给 PVC 自动选默认 StorageClassMutatingAdmissionWebhookM用户自定义如 Istio sidecar 注入ValidatingAdmissionWebhookV用户自定义如 OPA Gatekeeper第 4 章学过的 sidecar 注入就是 MutatingAdmissionWebhook——所有 service meshIstio、Linkerd都靠这个机制实现自动注入。六、限流策略快速回顾详见 5.4。这里只放精华表方案场景实现建议client-go RateLimiter客户端自限速令牌桶默认 QPS5 太低必调MaxInFlightLimitserver 整体粗限流chan 信号量APF 未启用时的兜底EventRateLimitevent 资源专用多桶令牌桶event 多的集群必开APF优先级 公平队列Shuffle Sharding 并发预算生产首选记住三个关键源码模式chan select default 非阻塞信号量MaxInFlightShuffle Sharding 隔离吵闹邻居APFwatermark 滑动指标平滑throttle metrics七、上手 APIServer 源码的实战路线图阶段一跑通最小请求半天clonekubernetes/kubernetes切到 stable tag如 v1.30在 IDE 里打开cmd/kube-apiserver/apiserver.go找到 main 函数一路跟到app.Run→CreateServerChain5.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.goPrepareForCreateconversion多版本转换protobuf 生成make update走完这套你就是半个 K8s 贡献者了。阶段四写一个 Admission Webhook一周参考 4.3、4.4 的 sidecar 注入实战。把 webhook 部署到集群、生效、捕获请求、改写、放行——这是 APIServer 扩展能力的实战。阶段五调 APF一周模拟流量用kube-burner或k6观察 APF 各 priority level 的指标调整nominalConcurrencyShares——这是真正的 SRE 实战。关键工具dlvGo 调试器 vscode-go源码断点kube-burnerAPIServer 压测利器Prometheus 官方 dashboardskubernetes/kubernetes仓库带的 mixin八、生产经验复盘第五章学到的核心教训#教训来自1永远不要直连 etcd——绕过所有保护全章2mutating webhook 不要改 status——会被信任并保留5.33client-go 默认 QPS5生产必须调高5.44--max-requests-inflight0是关闭限流不是无限5.45system:masters 永远豁免限流——业务别用 cluster-admin5.46feature gate 关了字段会被静默丢弃——排查必查5.37加密 key 一定要备份——丢了等于数据丢5.38watch 不受 MaxInFlight 控制切到 APF5.49三个 Server 委托链顺序Aggregator → Kube → Extension5.110Strategy 模式是 K8s 处理资源多样性的核心抽象5.2九、章节思考题回答这些问题验证你是否真懂了同一个 Pod 对象在内存、etcd、kubectl 三处的形态分别是什么经过哪些转换为什么 K8s 选 Strategy 模式而不是给每个资源写独立 handlerAPF 完全替代得了 MaxInFlight 吗什么场景下还会用 MaxInFlight如果你要为某种新资源比如自定义 GPU 资源加 APIServer 支持最少要改哪些文件一个 Pod 创建后scheduler 是怎么知道它的这背后的机制是什么十、本节小结APIServer 是 K8s 控制面的核心枢纽理解它的关键是抓住几条主线架构主线三个 ServerAggregator/Kube/Extension 一个通用基座GenericAPIServer请求主线限流 → Authn → Authz → Admission → Storage → Audit数据主线Scheme 注册 → RESTStorage 路由 → Strategy 处理 → Storage 写 etcd保护主线四层限流client/MaxInFlight/EventRateLimit/APF 三道关Authn/Authz/Admission