一次 kubectl apply 触发的 PUT/PATCH 请求,从
TLS 终止到写入
etcd,中间经过多少个可以拒绝它的组件?这个问题在排障时不能糊弄:若
Webhook 超时拒绝了请求,etcd 完全健康,但 apiserver
日志会显示 admission 错误,而不是 storage 错误。若 APF
排队超时,症状是 504,但 etcd
etcd_request_duration_seconds
不会抬头。把任何一次请求失败归因为「etcd
慢」之前,必须先确认请求是否到达了
storage.Interface。
本文拆解 kube-apiserver 进程模型,钉
generic apiserver(k8s.io/apiserver/pkg/server)的组装路径、HandlerChain
各插槽顺序,以及 GVR(GroupVersionResource)路由机制。不重写
etcd Raft/MVCC 全书(见 etcd
系列),不写 scheduler/controller-manager 内核。
本篇在系列中的位置
篇目 核心内容 第 1 篇 · 控制面全景 缺口、五轴坐标系、16 篇路线 第 2 篇 · 进程与请求路径 generic apiserver、HandlerChain、REST 路由 第 3 篇 · storage.Interface 与 etcd3 codec、prefix、CRUD/Watch 到 etcd 系列目录 全部篇目
版本锚定:Kubernetes v1.30.3(源码 tag
v1.30.3)。本文机制叙述对齐staging/src/k8s.io/apiserver/pkg/server/与cmd/kube-apiserver/。HandlerChain 构造逻辑见DefaultBuildHandlerChain与Config.Complete——顺序由此函数决定,而非固定 HTTP 中间件顺序,正文如实描述文档化层次,不发明行号。
一、generic apiserver 框架
kube-apiserver 不是直接实现了一个 HTTP
服务器。它依赖 k8s.io/apiserver 库的
generic apiserver
框架,把认证、授权、audit、admission 与 REST storage
组合成一个可复用结构——聚合 apiserver(aggregated
apiserver)、CRD apiserver 等扩展也共用这套框架。
组装路径
kube-apiserver 的启动入口在
cmd/kube-apiserver/apiserver.go(tag
v1.30.3)。高层组装步骤是:
- 解析 flags,构造
KubeAPIServerOptions(pkg/kubeapiserver/options/)。 - 调用
options.Complete(),填充ServerRunOptions,其中包含 storage 后端配置(etcd endpoints、cert、prefix)。 - 调用
CreateKubeAPIServer,内部依次构造:GenericAPIServer(k8s.io/apiserver/pkg/server)——负责 HandlerChain、路由注册、运行生命周期。master.Instance——把 core/apps/batch 等 API 组注册到 GenericAPIServer。
GenericAPIServer.PrepareRun().Run()启动 HTTPS 监听。
GenericAPIServer 的核心字段是
Handler(APIServerHandler),它持有两条
http.Handler
链:FullHandlerChain(处理大多数请求)与
Director(路由到正确的 API 组 handler)。
Config 与 Complete 模式
k8s.io/apiserver/pkg/server 使用
Config → CompletedConfig
的两阶段模式:Config
是可变的选项包,Config.Complete() 返回不可变的
CompletedConfig,只有后者才能调用
New 构造
GenericAPIServer。这个模式保证构造时所有依赖字段已填充,避免运行时
nil 指针。
storage 后端通过
Config.RESTOptionsGetter(接口
RESTOptionsGetter)注入——etcd3 的
StorageFactory 实现该接口,返回对应 GVR 的 etcd
配置。storage.Interface
实例在第一次访问时通过工厂创建,而不是在进程启动时全部预建。
二、HandlerChain:请求从入站到 storage 经过哪些插槽
DefaultBuildHandlerChain(k8s.io/apiserver/pkg/server/config.go,tag
v1.30.3)是 generic apiserver 的默认
HandlerChain 构造函数。kube-apiserver
使用该默认实现;扩展 apiserver 可替换。
HandlerChain
是从外到内包裹的中间件栈,请求按以下大致顺序经过各层(顺序由源码中
DefaultBuildHandlerChain
的嵌套调用决定,下述为文档化层次,不等同于固定行号顺序):
[外层]
Panic recovery / CORS / 请求 ID 注入
↓
APF / max-in-flight(流控)
↓
Authentication(authn)
↓
Audit(审计事件注入)
↓
Impersonation
↓
Authorization(authz)
↓
Admission(在 REST handler 内部,不是单独中间件层)
↓
REST handler → storage.Interface → etcd3
[内层]
关键点:
- APF 在 authn
之前:流控层看到的是未认证请求,按 IP/UA
分类,不能依赖用户身份。这与某些文档的「先 authn
再限流」直觉相反。v1.30.3 的 APF 行为见
k8s.io/apiserver/pkg/util/flowcontrol/。 - Admission 在 REST handler 内:Admission
不是一个独立 HTTP 中间件,而是由 REST
handler(
k8s.io/apiserver/pkg/endpoints/handlers/)在调用storage.Interface之前显式调用admission.Interface.Admit/Validate。因此「Admission 拒绝」的错误由 REST handler 返回,不是中间件链直接短路。 - 顺序是可配置的:
BuildHandlerChainFuncOrDefault(Config字段)允许替换DefaultBuildHandlerChain,但 kube-apiserver 使用默认值。描述「固定顺序」时,应对照v1.30.3tag 下该函数的实际实现,不能凭印象写死行号。
失败落点与证据包
| 失败类型 | 落点 | 证据包 |
|---|---|---|
| 429 Too Many Requests | APF / max-in-flight | apiserver_flowcontrol_rejected_requests_total(v1.30.3
metrics) |
| 401 Unauthorized | authn 层 | apiserver log "Authentication failed";无
etcd 写入 |
| 403 Forbidden | authz 层 | apiserver log audit event;无 etcd 写入 |
| 503 / timeout(Webhook) | admission 层(REST handler 内) | Webhook 端点日志;apiserver admission latency metrics |
| 写成功但内容不预期 | admission mutation(MutatingWebhook) | 对比 kubectl apply 与
kubectl get -o yaml 字段 |
| 409 Conflict | storage.Interface GuaranteedUpdate |
rv mismatch;etcd Txn 返回 false |
| 500 / 504(storage) | storage.Interface 或 etcd |
etcd_request_duration_seconds;etcd
侧五轴 |
不要把「apiserver 返回 503」默认等同「etcd 不可用」——Webhook failurePolicy=Fail 也会返回 503。
三、GVR 路由:请求如何到达正确的 REST handler
kube-apiserver 把所有 Kubernetes 资源类型按
GroupVersionResource(GVR)
组织。路由逻辑由
k8s.io/apiserver/pkg/endpoints/(APIGroupVersion、Installer)实现。
路由注册
启动时,每个 API 组(如
apps/v1、core/v1、batch/v1)调用
Install,把各资源类型(deployments、pods、jobs)注册为
HTTP 路径:
/apis/{group}/{version}/{resource}
/apis/{group}/{version}/namespaces/{namespace}/{resource}/{name}
/api/v1/{resource} (core 组)
路径匹配由 go-restful 路由树完成(v1.30.3 中
kube-apiserver 仍使用
emicklei/go-restful/v3)。每条路径映射到一个
rest.Storage(接口),该接口由 API
组初始化时注入,内部持有 storage.Interface
实例。
REST 动词映射
| HTTP 方法 + 路径 | K8s 动词 | 调用 storage 方法 |
|---|---|---|
| GET /{resource} | list | GetList |
| GET /{resource}/{name} | get | Get |
| POST /{resource} | create | Create |
| PUT /{resource}/{name} | update | GuaranteedUpdate |
| PATCH /{resource}/{name} | patch | GuaranteedUpdate(内部) |
| DELETE /{resource}/{name} | delete | Delete |
| GET /{resource}?watch=true | watch | Watch |
GuaranteedUpdate
是写操作的核心——它封装了乐观并发控制,详见 第 3
篇。
subresource 与嵌套路由
/pods/{name}/exec、/pods/{name}/log、/deployments/{name}/scale
等 subresource 注册为独立路径,映射到专属
rest.Storage 实现,不一定经过主资源的 storage
写路径——exec 不写 etcd,scale 写
etcd。排障 subresource 请求时要先确认其 storage
实现类型。
四、请求到达 storage 前的三个拦截点
以 kubectl create deployment
为例,请求可以在以下任意一点被拒绝,都不会产生 etcd
写入:
拦截点 1:APF / max-in-flight
请求入站即分类到 FlowSchema,若对应 PriorityLevel
的队列已满,返回 429。这发生在 TLS 握手完成、HTTP
请求解析后,但在任何 API 处理逻辑之前。调整 APF
配置(FlowSchema/PriorityLevelConfiguration
对象)可以改变不同类型请求的优先级,见 第 12 篇。
拦截点 2:authn + authz
认证失败返回 401,授权失败返回 403。RBAC 决策在此阶段完成,不依赖 storage 状态。若 RBAC 规则本身需要从 etcd 读取(启动时 RBAC informer 未 sync),apiserver 会暂缓服务而不是跳过授权——这是 HA 部署下升级窗口期的已知风险点,见 第 14 篇。
拦截点 3:Admission(Mutating → Validating)
REST handler 在调用
storage.Create/storage.GuaranteedUpdate
之前: 1. 运行所有
MutatingAdmissionWebhook(及内置 mutating
插件),对象可能被修改。 2. 运行所有
ValidatingAdmissionWebhook(及内置
validating
插件),任何一个拒绝即返回错误(failurePolicy=Fail
时,Webhook 不可达也拒绝)。
只有三个拦截点全部通过,storage.Interface.Create
或 GuaranteedUpdate 才会被调用,进而触发 etcd3
的 gRPC 请求。
flowchart TD
req["HTTP request (TLS terminated)"]
apf{"APF / max-in-flight\n队列是否满?"}
authn{"authn 是否通过?"}
authz{"authz 是否允许?"}
adm{"admission 是否通过?"}
storage["storage.Interface\n→ etcd3 gRPC"]
req --> apf
apf -->|"队列满 → 429"| drop1["rejected"]
apf -->|"pass"| authn
authn -->|"失败 → 401"| drop2["rejected"]
authn -->|"pass"| authz
authz -->|"拒绝 → 403"| drop3["rejected"]
authz -->|"pass"| adm
adm -->|"拒绝 → 4xx/5xx"| drop4["rejected"]
adm -->|"pass"| storage
排障口令: - etcd
etcd_request_duration_seconds 无异常 +
apiserver 报 503 → 优先查 Admission(第 9 篇)或 APF(第 12
篇)。 - etcd 指标异常 + apiserver 写超时 → 查 Storage /
etcd 五轴(etcd/15)。 - 401/403 与存储层不相关——查
authn/authz(第 10–11 篇)。
五、设计谱系:generic apiserver 的来历与局限
generic apiserver 框架从 2015 年起从
kubernetes/kubernetes
拆出为独立库(k8s.io/apiserver),目的是让聚合
apiserver(kube-aggregator、各 operator)复用
authn/authz/admission/audit 基础设施,而不是各自重造。这与
Kubernetes 架构中「扩展点在 apiserver 层而非 etcd
层」的设计取向一致。
局限:generic apiserver 框架本身不定义
HandlerChain
的形式语义——DefaultBuildHandlerChain
是一个按惯例演化的实现,没有单独的规范文档。排障时若有疑问,应以
v1.30.3 tag
下的函数实现为准,不以非版本化文档为准。
争论点:HandlerChain 中 APF 在 authn 之前的设计,有观点认为会导致未认证请求消耗 APF 队列资源,影响已认证请求。Kubernetes 社区在 KEP-1040(APF 设计文档)中讨论过这一权衡:把 APF 放在 authn 之后会让 authn 本身成为无保护的瓶颈;放在 authn 之前可以对 authn 请求做速率保护,但需要接受「匿名请求也占队列」的代价。v1.30.3 选择了前者(APF 在 authn 之前)。
参考资料
规范 / 源码(A)
- Kubernetes v1.30.3(源码 tag
v1.30.3) staging/src/k8s.io/apiserver/pkg/server/config.go:DefaultBuildHandlerChain、Config、CompletedConfigstaging/src/k8s.io/apiserver/pkg/endpoints/:APIGroupVersion、Installer、REST 动词映射staging/src/k8s.io/apiserver/pkg/util/flowcontrol/:APF 实现- Kubernetes KEP-1040: API Priority and
Fairness(
kubernetes/enhancements,keps/sig-api-machinery/1040-priority-and-fairness/)
站内对照(A/B)
实验台账
- 本篇无集群实测;HandlerChain 顺序描述基于
v1.30.3tag 源码结构,未伪造请求追踪输出。
上一篇:控制面全景
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【kube-apiserver】控制面全景:缺口、五轴坐标系与 16 篇路线
相对 etcd/13、distributed/50、k8s-network 补齐 kube-apiserver 生产内核缺口;以 Storage/Watch/Admission/Auth/APF 五轴为坐标系定义 16 篇阅读路线;版本锚定 Kubernetes v1.30.3。
【kube-apiserver】storage.Interface 与 etcd3:codec、prefix 与 CRUD 路径
拆解 kube-apiserver 的 storage.Interface 契约与 etcd3 实现:codec 序列化、pathPrefix/resourcePrefix、value.Transformer 加密边界、GuaranteedUpdate 乐观并发,以及 Watch 到 etcd3 的完整路径。版本锚定 Kubernetes v1.30.3 / etcd v3.5.33。
【kube-apiserver】resourceVersion 与 Revision 映射:mod revision、continue 与一致性读期望
钉 Kubernetes resourceVersion 字段与 etcd mod revision 的对应关系;分析 continue token 的分页语义与成本;说明不同 List 路径的一致性期望差异;以及 410 Gone 与 ErrCompacted 的分列。版本锚定 Kubernetes v1.30.3 / etcd v3.5.33。
【kube-apiserver】Watch cache / cacher:dispatch、bookmark 与穿透 etcd
钉 Kubernetes v1.30.3 cacher 对 storage.Interface 的包装:watchCache 滑动窗口与 Ready 门、bookmark 推送路径、cache miss 打穿 etcd 的触发条件,以及 List 风暴成因与 watch cache SLO 与 compaction 间隔之间的开放问题。