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 | 投影卷 + TokenReview | Pod 内的控制器 |
| Bearer Token / OIDC | JWT 校验 | 人类用户、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 内置准入插件的典型作用
| 插件 | 阶段 | 作用 |
|---|---|---|
| LimitRanger | Mutating | 填默认 requests/limits |
| ResourceQuota | Validating | 校验命名空间配额 |
| NamespaceLifecycle | Validating | 拒绝向 Terminating 命名空间创建对象 |
| DefaultStorageClass | Mutating | 给未指定 StorageClass 的 PVC 填默认值 |
| PodSecurity | Validating | 执行 Pod 安全标准(baseline/restricted) |
| NodeRestriction | Validating | 限制 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-servers | etcd 端点 | 列全 3/5 个,逗号分隔 |
--requestheader-* | 聚合层身份透传 | 与 front-proxy CA 配套 |
--audit-policy-file | 审计策略 | 生产必开,注意日志量 |
--profiling=false | 关闭 pprof | 生产建议关闭 |
7.3 常见故障定位
| 症状 | 可能原因 | 排查 |
|---|---|---|
429 Too Many Requests | APF 队列满 | apiserver_flowcontrol_rejected_requests_total |
410 Gone | resourceVersion 被压实 | 客户端需重新 LIST |
etcdserver: request timed out | etcd 慢 | etcd 磁盘 fsync 延迟 |
webhook call failed | 准入 webhook 不可用 | 查 webhook 服务与 timeoutSeconds |
Unable to connect to the server | apiserver 全挂 | readyz、证书、etcd |
| LIST 变慢 | watch cache 冷 / 大对象 | --watch-cache-sizes、分页 |
8. 小结
| 子系统 | 核心机制 | 关键点 |
|---|---|---|
| 请求管线 | handler chain 顺序执行 | 认证 → 鉴权 → 审计 → 准入 |
| 准入链 | Mutating 全跑完再 Validating | Webhook 同步阻塞,可用性=API 可用性 |
| watch cache | API Server 侧缓存 + 事件回放 | 把 N 个 watch 收敛成 1 条到 etcd |
| 一致性 | resourceVersion 决定读语义 | 410 Gone 需重新 LIST |
| APF | FlowSchema + 加权公平队列 | 429 时先定位是哪个 FlowSchema |
| 聚合层 | APIService 反向代理 | 不经过准入,caBundle 过期即全挂 |
一句话记住:API Server 是集群的「唯一写入口 + 数据总线」——认证鉴权把住身份,准入链把住规则,watch cache 撑住扇出,APF 保证公平,聚合层提供扩展。它的可用性直接等于集群的可用性,所以任何挂在它同步链路上的组件(Webhook、聚合后端、etcd)都必须按「会拖垮全集群」的标准来设计。集群级的排障路径可参考 Kubernetes 集群排障与诊断 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。