土法炼钢 · 系统与基础设施

【kube-apiserver】进程与请求路径:generic apiserver、HandlerChain 与 REST 路由

文章导航

分类入口
kubernetesdistributed
标签入口
#kubernetes#apiserver#generic-apiserver#handlerchain#gvr#rest#request-path#v1.30.3

目录

一次 kubectl apply 触发的 PUT/PATCH 请求,从 TLS 终止到写入 etcd,中间经过多少个可以拒绝它的组件?这个问题在排障时不能糊弄:若 Webhook 超时拒绝了请求,etcd 完全健康,但 apiserver 日志会显示 admission 错误,而不是 storage 错误。若 APF 排队超时,症状是 504,但 etcd etcd_request_duration_seconds 不会抬头。把任何一次请求失败归因为「etcd 慢」之前,必须先确认请求是否到达了 storage.Interface

本文拆解 kube-apiserver 进程模型,钉 generic apiserverk8s.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 构造逻辑见 DefaultBuildHandlerChainConfig.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)。高层组装步骤是:

  1. 解析 flags,构造 KubeAPIServerOptionspkg/kubeapiserver/options/)。
  2. 调用 options.Complete(),填充 ServerRunOptions,其中包含 storage 后端配置(etcd endpoints、cert、prefix)。
  3. 调用 CreateKubeAPIServer,内部依次构造:
    • GenericAPIServerk8s.io/apiserver/pkg/server)——负责 HandlerChain、路由注册、运行生命周期。
    • master.Instance——把 core/apps/batch 等 API 组注册到 GenericAPIServer。
  4. GenericAPIServer.PrepareRun().Run() 启动 HTTPS 监听。

GenericAPIServer 的核心字段是 HandlerAPIServerHandler),它持有两条 http.Handler 链:FullHandlerChain(处理大多数请求)与 Director(路由到正确的 API 组 handler)。

Config 与 Complete 模式

k8s.io/apiserver/pkg/server 使用 ConfigCompletedConfig 的两阶段模式:Config 是可变的选项包,Config.Complete() 返回不可变的 CompletedConfig,只有后者才能调用 New 构造 GenericAPIServer。这个模式保证构造时所有依赖字段已填充,避免运行时 nil 指针。

storage 后端通过 Config.RESTOptionsGetter(接口 RESTOptionsGetter)注入——etcd3 的 StorageFactory 实现该接口,返回对应 GVR 的 etcd 配置。storage.Interface 实例在第一次访问时通过工厂创建,而不是在进程启动时全部预建。


二、HandlerChain:请求从入站到 storage 经过哪些插槽

DefaultBuildHandlerChaink8s.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
[内层]

关键点

失败落点与证据包

失败类型 落点 证据包
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 applykubectl 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/APIGroupVersionInstaller)实现。

路由注册

启动时,每个 API 组(如 apps/v1core/v1batch/v1)调用 Install,把各资源类型(deploymentspodsjobs)注册为 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.CreateGuaranteedUpdate 才会被调用,进而触发 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)

站内对照(A/B)

实验台账


上一篇控制面全景

下一篇storage.Interface 与 etcd3

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .