API Server 内部与准入链:watch 缓存、APF 与聚合层

拆解 kube-apiserver 的内部结构:从认证、鉴权、准入、校验到持久化 etcd 的完整请求链路,Mutating/Validating 准入插件与动态 Webhook 的执行顺序,watch cache 与 resourceVersion 的一致性语义,API 优先级与公平性(APF)的 FlowSchema 调优,以及聚合层与高可用部署要点。

kube-apiserver 是集群里唯一直接读写 etcd 的组件,所有控制器、kubelet、kubectl、Operator 都必须经过它。它既是「声明式 API 的守门人」(认证、鉴权、准入、校验都在这里发生),又是「集群的数据总线」(watch 让成千上万个客户端拿到增量变更)。本文沿着一条请求的生命周期,把 API Server 的内部链路拆开:HTTP 处理 → 认证鉴权 → 准入链 → 校验持久化 → watch 分发,并深入 watch cache、APF 与聚合层这三个最影响稳定性与性能的子系统。

1. API Server 的定位与整体结构

1.1 它到底承担哪些角色

kube-apiserver 是一个无状态的水平扩展 HTTP 服务,同时承担四种角色:

角色说明
API 网关暴露 REST 接口(/api/v1、/apis/<group>/<version>)
认证鉴权中心认证请求身份、授权(RBAC/ABAC/Webhook)
准入与校验Mutating/Validating 准入、schema 校验、默认值填充
数据总线与 etcd 交互,并向所有客户端分发 watch 事件

它不存储集群状态(etcd 才存),也不做业务逻辑(那是控制器的事)。因此 API Server 可以随意扩副本、随意重启——只要 etcd 健康。

1.2 三个监听端口

# 默认端口布局
--secure-port=6443         # 对外 HTTPS API(认证鉴权生效)
--insecure-port=0          # 非安全端口,v1.20 后默认禁用,务必为 0
--bind-address=0.0.0.0

--insecure-port 曾经是 8080 且完全绕过认证,是历史遗留的最大安全隐患,现代集群必须设为 0。

2. 请求链路:从 TCP 到 etcd

2.1 完整处理管线

一条 kubectl apply 请求在 API Server 内部的路径:

TCP/TLS 握手
  ↓
HTTP Handler Chain(DefaultBuildHandlerChain)
  ├─ WithRequestInfo          解析 verb/resource/namespace/name
  ├─ WithMaxInFlightLimit     最大并发限制(已被 APF 取代)
  ├─ WithTimeoutForNonLongRunningRequests
  ├─ WithPanicRecovery
  ├─ WithCORS
  ├─ WithAuthentication       认证 → 写入 user.Info
  ├─ WithAuthorization        鉴权 → SubjectAccessReview
  ├─ WithAudit                审计日志
  ├─ WithImpersonation        模拟身份
  └─ WithRequestInfo
  ↓
REST Storage(etcd3 store)
  ├─ Admission(Mutating → Validating)
  ├─ Validation(schema 校验)
  ├─ Strategy(PrepareForCreate/Update 填默认值)
  └─ etcd 事务写

2.2 认证的几种方式

方式说明典型用途
X.509 客户端证书证书 CN 为用户名,O 为组kubelet、controller-manager
ServiceAccount Token投影卷 + TokenReviewPod 内的控制器
Bearer Token / OIDCJWT 校验人类用户、CI
Webhook Token外部认证服务企业 SSO 集成

认证成功后身份写入 user.Info(username、uid、groups、extra),交给鉴权阶段。认证与鉴权是两件事:前者回答「你是谁」,后者回答「你能不能做这件事」。

2.3 鉴权:RBAC 是主流

RBAC 通过 Role/ClusterRole 与 Binding 授权,本质是把请求的 verb + resource + namespace 与规则做匹配。它由 API Server 内置实现,但规则存在 etcd 里,所以授权本身也依赖 API Server 的读取(有 informer 缓存)。

# 排查某身份能否执行某操作,最快的方式
kubectl auth can-i create pods --namespace default --as system:serviceaccount:default:ci

鉴权规则与最小权限设计详见 Kubernetes 安全与 RBAC 。

3. 准入链:Mutating 与 Validating

3.1 两个阶段、两种顺序

准入控制(Admission Control)分为变更(Mutating)与校验(Validating)两类,执行顺序是先全部 Mutating,再全部 Validating:

请求体(用户提交的原始对象)
  ↓
Mutating Admission(可修改对象)
  ├─ MutatingAdmissionWebhook(外部 webhook,按名字排序)
  ├─ LimitRanger(填默认资源)
  ├─ DefaultStorageClass
  ├─ PodNodeSelector、DefaultTolerationSeconds ...
  ↓
Object Schema Validation(OpenAPI schema)
  ↓
Validating Admission(只读,不可改)
  ├─ ValidatingAdmissionWebhook
  ├─ ResourceQuota(校验配额)
  ├─ NamespaceLifecycle、PodSecurity ...
  ↓
持久化到 etcd

关键约束:Mutating webhook 改完对象后,所有 Validating 阶段看到的都是改后的版本。这解释了一个经典困惑——「我提交的对象里没写 resources,为什么 Validating webhook 看到了默认值」:因为 LimitRanger 在更早的 Mutating 阶段已经填好了。

3.2 内置准入插件的典型作用

插件阶段作用
LimitRangerMutating填默认 requests/limits
ResourceQuotaValidating校验命名空间配额
NamespaceLifecycleValidating拒绝向 Terminating 命名空间创建对象
DefaultStorageClassMutating给未指定 StorageClass 的 PVC 填默认值
PodSecurityValidating执行 Pod 安全标准(baseline/restricted)
NodeRestrictionValidating限制 kubelet 只能改自己的 Node 对象

启用列表由 --enable-admission-plugins 控制:

--enable-admission-plugins=NodeRestriction,PodSecurity,ResourceQuota
--disable-admission-plugins=...

3.3 动态准入 Webhook

外部准入通过 MutatingWebhookConfiguration 与 ValidatingWebhookConfiguration 注册:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: policy.example.com
webhooks:
  - name: check.example.com
    clientConfig:
      service:
        name: policy-webhook
        namespace: policy-system
        path: /validate
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
    failurePolicy: Fail          # Fail 会阻断;Ignore 会放行(危险)
    sideEffects: None
    admissionReviewVersions: ["v1"]
    timeoutSeconds: 10

Webhook 是 API Server 同步链路的一部分,因此它一旦变慢或不可用,会直接拖垮整个 API(failurePolicy: Fail + 超时 = 全集群无法创建 Pod)。生产要求:

  • 至少 2 副本 + PodDisruptionBudget;
  • 用 namespaceSelector / objectSelector 收窄拦截范围;
  • 优先 failurePolicy: Ignore 或加熔断,避免 webhook 成为单点;
  • timeoutSeconds 不超过 10s,且 webhook 内部要短超时。

更完整的 Webhook 编写与调试见 Kubernetes 准入 Webhook 。

一句话:准入链是「先改后验、Webhook 同步阻塞」——Mutating 补默认值,Validating 守规则,而外部 Webhook 直接挂在请求路径上,它的可用性就是 API Server 的可用性。

4. watch cache 与一致性读

4.1 为什么要 watch cache

如果每个客户端的每次 LIST 都直查 etcd,etcd 早就被打爆。API Server 内置一层 watch cache(watch 缓存):

etcd  →  watch(API Server 与 etcd 的长连接)
         ↓
      watch cache(每个资源一份,含滑动窗口事件缓冲)
         ↓
   客户端 LIST  → 从缓存读
   客户端 WATCH → 从缓存的事件缓冲回放 + 订阅增量

默认缓存大小由 --watch-cache-sizes 或按资源类型配置,--watch-cache=true(默认开启)。缓存让 1 万个 kubelet 的 watch 只对应 1 条到 etcd 的 watch。

4.2 resourceVersion 与一致性语义

每个对象都带 metadata.resourceVersion(etcd 的全局单调递增版本号),它决定读的一致性:

请求方式语义是否走缓存
不传 resourceVersion最新数据(可能略旧)是(默认 Most Recent)
resourceVersion=0任意最新,允许过期是(强制走缓存)
resourceVersion="<具体值>"至少该版本否(直查 etcd,可能 410 Gone)
resourceVersion 与 resourceVersionMatch精确/不早于语义视情况
# 强制读 etcd(一致性读),用于控制器做乐观并发
kubectl get pod my-pod -o json --resource-version=0

当客户端请求的 resourceVersion 已从 etcd 压实(compaction)掉,会返回 410 Gone,客户端必须重新 LIST——这是「informer 重新 list 导致 API 压力尖峰」的根因。

4.3 三种 LIST 模式

模式说明用途
Most Recent默认,尽量新一般查询
Not Older Than至少不早于某版本分页一致性
Exact精确版本控制器做 snapshot 语义

大 LIST 一定要分页(--chunk-size,默认 kubectl 用 500):

kubectl get pods -A --chunk-size=500

5. APF:API 优先级与公平性

5.1 为什么需要 APF

APF(API Priority and Fairness) 取代了旧的 --max-requests-inflight 全局并发限制。旧机制的问题:所有请求共享一个池,某个控制器疯狂 LIST 就能把整个 API Server 拖慢,且无法区分「关键系统请求」与「低优先级批处理」。

APF 的核心思路:按请求特征分队列 + 加权公平排队(WFQ),保证关键流量不被饿死。

5.2 两个核心对象

对象作用
FlowSchema把请求匹配到某个优先级等级(按 user、namespace、resource 等)
PriorityLevelConfiguration定义该等级的并发份额(nominalConcurrencyShares)与队列行为
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: PriorityLevelConfiguration
metadata:
  name: workload-low
spec:
  type: Limited
  limited:
    nominalConcurrencyShares: 10     # 相对份额(不是绝对值)
    limitResponse:
      type: Queue                    # 超出并发时排队
      queuing:
        queues: 128
        queueLengthLimit: 50
        handSize: 6
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
  name: service-accounts
spec:
  matchingPrecedence: 9000
  priorityLevelConfiguration:
    name: workload-low
  rules:
    - subjects:
        - kind: Group
          group:
            name: system:serviceaccounts
      resourceRules:
        - verbs: ["list", "watch"]
          apiGroups: ["*"]
          resources: ["*"]

5.3 APF 的可观测与调优

# 观察每个优先级等级的排队与拒绝
kubectl get --raw /metrics | grep apiserver_flowcontrol
# 关键指标:
#   apiserver_flowcontrol_current_inqueue_requests
#   apiserver_flowcontrol_rejected_requests_total
#   apiserver_flowcontrol_request_wait_duration_seconds

出现 429 Too Many Requests 且 rejected_requests_total 上涨,说明某等级的队列被打满。调优顺序:先定位是哪个 FlowSchema 贡献了拒绝(按 flow_schema label 聚合),再判断是该提升份额还是该治理客户端(例如把某个狂 LIST 的控制器改成 informer + watch)。

一句话:APF 把「一个大队列」变成「多个带权重的队列」——它保证 kubelet、控制器这些关键流量不被批处理任务淹没,代价是需要理解 FlowSchema 的匹配优先级。

6. 聚合层:扩展 API 的两种路径

6.1 CRD 与聚合 API 的区别

扩展 Kubernetes API 有两条路:

方式存储适用
CRD(CustomResourceDefinition)etcd(复用 API Server)声明式资源,主流选择
聚合 API(APIService)自定义后端需要自定义存储、代理到外部服务(如 metrics-server、KEDA)

CRD 由 API Server 直接服务,无需额外组件;聚合 API 则通过 APIService 把 /apis/<group>/<version> 的请求反向代理给一个实现了 Kubernetes API 契约的 Service。

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
  name: v1beta1.metrics.k8s.io
spec:
  service:
    name: metrics-server
    namespace: kube-system
    port: 443
  group: metrics.k8s.io
  version: v1beta1
  groupPriorityMinimum: 100
  versionPriority: 100
  insecureSkipTLSVerify: false
  caBundle: <base64-ca>

6.2 聚合层的可用性陷阱

聚合 API 有个致命特性:API Server 启动时会等待所有 APIService 就绪,否则 /apis 的发现接口会失败或变慢。因此:

  • metrics-server 挂了可能拖慢 kubectl get(发现阶段);
  • caBundle 过期会导致聚合 API 全部 503;
  • APIService 的 Service 必须真实可路由,否则 API Server 会周期性重试。
# 检查聚合 API 健康
kubectl get apiservices
kubectl get --raw /apis/metrics.k8s.io/v1beta1

6.3 聚合层的请求语义

注意聚合层不经过准入链(准入只作用于内置与 CRD 资源),也不参与 watch cache。请求被原样转发,返回的数据由后端服务负责。这意味着鉴权仍然生效(API Server 先鉴权再代理),但对象校验、默认值填充等策略需要后端自己实现。

7. 高可用与性能调优

7.1 高可用部署

3 个 apiserver 副本(或 5 个)
  ├─ 前置负载均衡(VIP / L4 LB),健康检查 /healthz
  ├─ 每个副本连自己的 etcd 端点列表(etcd 是 3/5 节点集群)
  └─ 无状态,可任意重启;证书 SAN 必须包含 LB 地址
# 关键健康端点
curl -k https://127.0.0.1:6443/livez    # 进程存活
curl -k https://127.0.0.1:6443/readyz   # 可服务(含 etcd 连通性检查)
curl -k https://127.0.0.1:6443/readyz?verbose   # 逐项检查

7.2 常见性能参数

参数作用建议
--max-requests-inflight非长请求并发上限APF 启用后次要,通常 400
--max-mutating-requests-inflight写请求并发上限200
--watch-cache-sizes各资源缓存事件数大集群调高 pods/nodes
--etcd-serversetcd 端点列全 3/5 个,逗号分隔
--requestheader-*聚合层身份透传与 front-proxy CA 配套
--audit-policy-file审计策略生产必开,注意日志量
--profiling=false关闭 pprof生产建议关闭

7.3 常见故障定位

症状可能原因排查
429 Too Many RequestsAPF 队列满apiserver_flowcontrol_rejected_requests_total
410 GoneresourceVersion 被压实客户端需重新 LIST
etcdserver: request timed outetcd 慢etcd 磁盘 fsync 延迟
webhook call failed准入 webhook 不可用查 webhook 服务与 timeoutSeconds
Unable to connect to the serverapiserver 全挂readyz、证书、etcd
LIST 变慢watch cache 冷 / 大对象--watch-cache-sizes、分页

8. 小结

子系统核心机制关键点
请求管线handler chain 顺序执行认证 → 鉴权 → 审计 → 准入
准入链Mutating 全跑完再 ValidatingWebhook 同步阻塞,可用性=API 可用性
watch cacheAPI Server 侧缓存 + 事件回放把 N 个 watch 收敛成 1 条到 etcd
一致性resourceVersion 决定读语义410 Gone 需重新 LIST
APFFlowSchema + 加权公平队列429 时先定位是哪个 FlowSchema
聚合层APIService 反向代理不经过准入,caBundle 过期即全挂

一句话记住:API Server 是集群的「唯一写入口 + 数据总线」——认证鉴权把住身份,准入链把住规则,watch cache 撑住扇出,APF 保证公平,聚合层提供扩展。它的可用性直接等于集群的可用性,所以任何挂在它同步链路上的组件(Webhook、聚合后端、etcd)都必须按「会拖垮全集群」的标准来设计。集群级的排障路径可参考 Kubernetes 集群排障与诊断 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「云原生」更多文章

  1. 证书管理与自动轮换:cert-manager 与 kubelet 证书轮换
  2. Sidecar 模式与原生边车容器:init 容器、生命周期与重启策略
  3. CoreDNS 与服务发现:插件链、NDOTS 与 Headless Service