API Server 与声明式 API:一切皆资源
上一篇(核心概念导读)描述了 Kubernetes 的对象体系和控制器循环。这一篇进入 API Server 内部。API Server 不是普通的 HTTP 代理;它是整个集群的唯一写入路径,也是所有控制器、kubelet、用户工具共享的信息总线。
声明式 API 经常被描述为"说你想要什么,而不是怎么做"。这个描述准确,但不完整。Kubernetes 的声明式 API 背后有一套精确的语义:冲突检测、字段所有权、幂等写入。理解这些语义,才能解释为什么 kubectl apply 不是简单的 HTTP PUT。
本文只问一个问题:一个 apply 请求经过了哪些关卡?
请求管道全貌
一个 kubectl apply 请求从客户端到 etcd 的完整路径:
1 | |
每个关卡都是独立插件。任何一个关卡拒绝请求,API Server 立即返回 4xx,后续关卡不再执行。
GVK 三元组与资源发现
Kubernetes API 用 Group/Version/Kind(GVK)三元组唯一标识一种资源类型。例如:
apps/v1/Deployment:Group=apps,Version=v1,Kind=Deploymentcore/v1/Pod:Group 为空(core group),Version=v1,Kind=Podnetworking.k8s.io/v1/Ingress:扩展 Group
GVK 到 REST 路径的映射规则:
1 | |
core group(空 Group)用 /api 而非 /apis,这是历史遗留设计。
API 发现端点 /apis 和 /api 返回所有已注册资源的元数据。kubectl api-resources 就是解析这两个端点。每个条目包含:资源名(deployments)、Kind(Deployment)、是否命名空间级、支持的动词(get、list、watch、create、update、patch、delete)。
多版本共存是 API Server 的核心设计。同一资源可以同时有 v1beta1 和 v1 两个版本。API Server 内部维护一个 hub version(通常是最新稳定版),所有版本在写入前转换成 hub version 存储,读取时按请求版本转换输出。这个 conversion 可以由 API Server 内置代码完成,也可以委托给 conversion webhook(CRD 场景常用)。
Authentication:确认身份
所有请求首先要证明"你是谁"。API Server 支持多种认证机制,按配置顺序依次尝试,任一成功即通过:
- X.509 客户端证书:
kubectl默认使用~/.kube/config中的客户端证书,Subject 的 CN 字段作为用户名,O 字段作为 Group - Bearer token:静态 token 文件(不推荐生产使用)
- ServiceAccount token(JWT):Pod 内部自动挂载,由 API Server 或外部 OIDC 签发,支持
--bound-service-account-tokens绑定特定 Pod 生命周期 - OIDC:对接外部身份提供商(Dex、Keycloak、Azure AD),token 携带标准 OIDC claims
认证成功后,请求携带一个 UserInfo:用户名、UID、所属 Groups。这个身份信息向后传递给 Authorization 阶段。认证失败返回 401 Unauthorized。
匿名请求(未携带凭据)在 API Server 开启 --anonymous-auth 时以 system:anonymous 身份处理,通常只允许访问健康检查端点。
Authorization:RBAC 决策
Authorization 阶段决策"你能做什么"。Kubernetes 默认使用 RBAC(Role-Based Access Control)。
RBAC 四个核心对象:
Role:定义单个命名空间内的权限规则ClusterRole:定义集群级别的权限规则(跨命名空间,或访问集群级资源如 Node)RoleBinding:把 Role 或 ClusterRole 绑定到 Subject(User/Group/ServiceAccount),作用域是单个命名空间ClusterRoleBinding:把 ClusterRole 绑定到 Subject,作用域是整个集群
每个请求生成一个 SubjectAccessReview,Authorizer 遍历该用户相关的所有 Binding,检查是否有规则匹配当前请求的 Group+Resource+Verb+Namespace 组合。任一规则匹配即允许(allow-wins),没有匹配则拒绝(implicit deny)。
1 | |
除 RBAC 外,API Server 还支持 Node Authorizer(专门处理 kubelet 的权限,只允许 kubelet 访问其所在节点上的对象)和 Webhook Authorizer(把决策委托给外部服务,OPA 等策略引擎可以通过这个接口接入)。
Admission:修改与校验的双层闸门
Admission 分为两个阶段,顺序固定,不可互换。
Mutating Admission Webhooks
Mutating webhook 收到当前对象,可以返回一个 JSON Patch(RFC 6902),API Server 将 patch 应用到对象上,然后将修改后的对象传给下一个 webhook。常见用途:
- Sidecar 注入:Istio 的
istio-sidecar-injector在每个符合条件的 Pod 创建时注入istio-proxy容器和 init container - 默认值补全:为缺少
imagePullPolicy的容器设置默认值 - 标签/注解标准化:自动添加环境标识、团队归属等标准 label
多个 Mutating webhook 串行执行,每个 webhook 收到的是前一个修改后的对象。reinvocationPolicy: IfNeeded 控制是否在其他 webhook 修改对象后重新调用本 webhook(解决相互依赖的注入场景)。
Object Schema Validation
Mutating 阶段结束后,API Server 对最终对象进行 OpenAPI Schema 校验。内置资源使用硬编码的 Go struct 校验;CRD 使用 spec.validation.openAPIV3Schema,支持 x-kubernetes-validations(CEL 表达式)做跨字段约束:
1 | |
Validating Admission Webhooks
Validating webhook 是只读的:收到最终对象,只能返回 allow 或 deny(附带拒绝原因字符串)。多个 Validating webhook 并行执行,任一返回 deny 整个请求失败。常见用途:
- 强制执行团队策略:所有 Deployment 必须有 resource limits、镜像必须来自私有仓库
- OPA/Gatekeeper 策略引擎:Constraint 对象定义 Rego 策略,GatekeeperWebhook 注册为 Validating webhook 执行检查
- Kyverno:类似 Gatekeeper,但用 YAML 语法写策略
写入 etcd:乐观锁保证一致性
通过所有 Admission 关卡后,API Server 将对象序列化为 protobuf(内部存储格式,比 JSON 更紧凑),写入 etcd。写入时携带 resourceVersion——这是 etcd 的 revision 值——用于乐观锁。
乐观锁工作方式:
1 | |
resourceVersion 是不透明字符串,客户端不应解析其数值,只应原样回传。不同资源的 resourceVersion 不可相互比较——它们对应的是 etcd 全局 revision,但含义是"该对象最后一次写入时的 etcd revision"。
Server-Side Apply:字段所有权
经典的 kubectl apply(Client-Side Apply)在客户端计算 diff,用 kubectl.kubernetes.io/last-applied-configuration annotation 记录上次状态,然后发送 strategic merge patch。这种方式的局限:多个工具同时管理同一对象时,last-applied 相互覆盖,造成字段丢失。
Server-Side Apply(SSA,kubectl apply --server-side)把 diff 计算移到服务端,引入字段所有权:
- 每个字段由一个
fieldManager(字符串标识符)拥有 - 对象的
managedFields记录所有字段归属 - 若 Manager A 尝试修改 Manager B 拥有的字段,API Server 返回 409 Conflict(
--force-conflicts可强制接管)
1 | |
SSA 适合 GitOps 场景:CI/CD 工具作为一个 fieldManager 只管理声明的字段,Operator 管理另一组字段,互不干扰。
Watch 机制:变更通知总线
Watch 是 API Server 向客户端推送资源变更事件的机制。客户端发起长连接:
1 | |
API Server 保持此连接,etcd 有新事件时以 chunked HTTP 或 HTTP/2 stream 推送,每个事件是 JSON 对象:
1 | |
事件类型:ADDED、MODIFIED、DELETED、BOOKMARK(仅携带当前 resourceVersion,用于定期刷新客户端已知版本)、ERROR。
API Server 内部有一个 watch cache(内存环形缓冲),etcd watch 事件先进入 watch cache,再广播给所有客户端连接,避免大量客户端直接 watch etcd 造成压力。
断线重连:客户端在重连时携带上次收到的 resourceVersion,从该版本续流。若该 revision 已被 etcd compaction 清除,API Server 返回 410 Gone,客户端需要重新 List 打快照再 Watch——这就是 Informer 的 List-Watch 循环。
可观察实验
1 | |
实验映射到内部机制
kubectl apply --server-side -f nginx-deploy.yaml 的内部路径:
- kubectl 读取 YAML,只保留用户声明的字段(去掉 status、managedFields 等服务端字段)
- 发送
PATCH /apis/apps/v1/namespaces/default/deployments/nginx,Content-Type:application/apply-patch+yaml - API Server SSA handler 从 etcd 读取当前对象(若不存在则创建)
- 计算 merge:新声明字段归属
my-toolmanager;未声明字段保持原有归属 - 检测 ownership conflict(其他 manager 拥有同一字段时返回 409,除非
--force-conflicts) - 经过 Mutating Webhook → Validation → Validating Webhook 管道
- 写入 etcd,返回含
managedFields的完整对象
Watch 的内部路径:
- 客户端发起 Watch 请求,携带
resourceVersion - API Server 的 cacher 检查 watch cache:若 cache 中有该 revision 后的事件,直接从 cache 返回;否则从 etcd 续流
- etcd 收到写入后通知 API Server cacher,cacher 更新内存 cache 并广播给所有 watcher
- 客户端 Informer 收到事件,写入 DeltaFIFO 队列(见第 03 篇)
练习
-
用
kubectl apply --server-side --field-manager=manager-a和--field-manager=manager-b分别管理同一个 Deployment 的不同字段(replicas和image),然后观察managedFields。再尝试用 manager-a 修改 manager-b 拥有的字段,观察 409 冲突错误。最后用--force-conflicts强制接管,观察managedFields的变化。 -
编写一个最简 Mutating Admission Webhook(Go 或 Python):对所有 Pod 创建请求,自动添加 annotation
injected-by: my-webhook。部署到 kind 集群(需要 TLS 证书,可用cert-manager或自签发),验证注入效果。观察 webhook 处理延迟对 Pod 创建速度的影响。 -
用
-v=9运行kubectl get pods,在输出中找到完整的 HTTP 请求/响应,记录resourceVersion、Accept头(kubectl 优先请求 protobuf 格式:application/vnd.kubernetes.protobuf)、Authorization头中的 Bearer token。用jwt.io或base64 -d解码 ServiceAccount JWT,查看iss、sub、aud、exp字段,理解 token 绑定机制。
模式提炼
API Server 的设计体现了两个经典的软件工程模式,组合使用:
声明式入口。调用者提交期望终态,不指定操作步骤,系统保证最终收敛。这个模式把"状态描述"和"状态执行"分离到不同的进程(API Server 负责接受,控制器负责执行),使系统极具弹性:控制器可以重启、升级、崩溃恢复,而不丢失任何待处理的意图,因为意图持久化在 etcd 里。
管道过滤器。请求按固定顺序经过多个处理阶段(认证 → 授权 → Mutating → 校验 → Validating → 持久化),每阶段职责单一,失败即短路。各阶段之间通过标准接口(AdmissionReview webhook)解耦,允许外部逻辑以插件形式接入,不需要修改 API Server 核心代码。这与企业应用中 Servlet Filter / gRPC Interceptor 的设计原理相同,区别在于 Kubernetes 的 Admission 阶段还允许修改被处理的对象(Mutating 阶段),而不只是放行或拒绝。
Server-Side Apply 在此之上引入了字段所有权语义。这解决了"谁有权修改哪个字段"的协调问题——不靠团队约定,而靠系统记录和强制。多个 controller 和人工操作可以同时管理同一对象的不同字段,所有权冲突由 API Server 检测并暴露,而非静默覆盖。
工程迁移表
| K8s 机制 | 工程类比 | 关键相似点 |
|---|---|---|
| Authentication | Spring Security AuthenticationManager |
请求在进入业务逻辑前验证身份,支持多种认证方式串联 |
| Authorization (RBAC) | Spring Security AccessDecisionManager |
基于角色的访问控制,allow-wins,无显式 deny |
| Mutating Webhook | Servlet Filter / AOP @Around Advice |
在请求处理前修改输入,结果对调用方透明 |
| Validating Webhook | Bean Validation @Valid / JSR-380 |
只读校验,失败抛异常,多个 validator 可并行 |
| resourceVersion 乐观锁 | JPA @Version / 数据库 UPDATE WHERE version=? |
写入时携带版本号,版本不匹配拒绝,调用方自行重试 |
| Watch long-poll | SSE / WebSocket 服务端推送 | 服务端保持连接推送增量事件,无需客户端轮询 |
| Server-Side Apply 字段所有权 | Git merge conflict markers | 谁拥有哪些行有记录,冲突时需要显式解决而非静默覆盖 |
| List + Watch 分离 | MySQL binlog position + snapshot | 先获取基线快照,再从固定位置追增量,不遗漏 |
常见误解
误解一:kubectl 直接读写 etcd。
事实是所有 kubectl 操作都经过 API Server 的完整 6 阶段管道,etcd 端口在生产集群中仅对 API Server 开放。这个隔离是 Kubernetes 安全模型的基础:etcd 只信任 API Server,API Server 负责所有的认证、授权和准入控制。从 etcd 直接读写会绕过整个安全层,在任何正式环境都是危险操作。
误解二:Admission Webhook 只是验证,不能修改对象。
Admission 分两轮:Mutating 阶段的 webhook 可以通过返回 JSON Patch 修改对象,这是 sidecar 注入、默认值填充的实现基础。Validating 阶段的 webhook 才是只读的,只能放行或拒绝。两者注册在不同的资源类型上(MutatingWebhookConfiguration vs ValidatingWebhookConfiguration),不会混用。将两者混淆会导致配错配置类型,注入逻辑失效且没有错误提示。
误解三:resourceVersion 是时间戳,可以用来推算写入时间。
resourceVersion 是 etcd 全局写入 revision 的字符串化,语义是单调递增的版本序号,不是时间。同一个对象的两个 resourceVersion 之间的差值代表全集群在这段时间内的总写入次数,与时间无关。不同对象的 resourceVersion 不可比较大小——一个 Pod 的 rv=200 和一个 ConfigMap 的 rv=150 不意味着 Pod 比 ConfigMap 更新。真实修改时间信息在 metadata.managedFields[].time 和 conditions 的 lastTransitionTime 字段里。
误解四:kubectl apply 和 kubectl replace 等价。
kubectl replace 是完整替换(HTTP PUT),覆盖当前对象的全部字段,包括其他 Operator 写入的 annotation 和 controller 维护的 status。kubectl apply 只修改声明的字段,未声明的字段保留。在多 manager 场景(CI/CD + Operator 协同管理同一对象)下,replace 会覆盖 Operator 管理的字段,触发控制器持续修复,造成状态震荡。
系列导航
- 00 核心概念导读
- 01 API Server 与声明式 API
- 02 etcd 与持久化
- 03 控制器模式与 Informer 机制
- 04 Pod 生命周期
- 05 调度器深入
- 06 kubelet 与容器运行时
- 07 网络模型与 CNI
- 08 Service 与 kube-proxy
- 09 Ingress 与 Gateway API
- 10 存储体系
- 11 配置与密钥管理
- 12 RBAC 与安全模型
- 13 资源管理与自动伸缩
- 14 CRD 与 Operator 模式
- 15 Helm 与应用打包
- 16 可观测性
- 17 生产集群运维
参考资料
- Kubernetes API Concepts: https://kubernetes.io/docs/reference/using-api/api-concepts/
- Server-Side Apply: https://kubernetes.io/docs/reference/using-api/server-side-apply/
- Admission Controllers Reference: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
- API Conventions (community): https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
- Dynamic Admission Control: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
