Kubernetes 基础体系 · 第 82/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes API Aggregation:Extension API Server、发现、认证和可用性
Kubernetes 不只通过内置 kube-apiserver 提供 API。监控指标、服务目录、云资源、策略对象以及 Operator 自定义的复杂资源,都可能需要一个独立进程实现 API。Kubernetes API Aggregation 机制允许这个独立进程以 Extension API Server 的形式接入集群,使客户端可以像访问内置 API 一样访问它:
kubectl
│
│ HTTPS 请求
▼
kube-apiserver
│
│ 根据 API group/version 路由
▼
kube-aggregator
│
│ Service + TLS + 认证信息转发
▼
Extension API Server
│
├── 自己的存储
├── Kubernetes API
└── 外部系统或云厂商 API
这里有三个容易混淆的事实:
- Extension API Server 不是
kube-apiserver的插件线程,而是独立的 HTTP API 服务器。 - 它提供的资源通常不存储在主集群的 etcd 中,存储责任由扩展服务器自己决定。
kube-apiserver负责入口认证和请求转发,但聚合 API 的资源授权通常由 Extension API Server 自己完成。
这套机制适合需要独立 API 语义、独立存储或独立生命周期的扩展。若只是增加一种声明式资源,CRD 通常更简单;若只是修改对象入站请求,则应考虑 Admission Webhook,而不是 API Aggregation。
一、先建立 API 基础:GVK、资源 URL 和 APIService
1. GVK 如何决定请求路径
Kubernetes 对象通常由以下三个字段组合识别:
- Group:API 组,例如
apps、batch、metrics.k8s.io - Version:API 版本,例如
v1、v1beta1 - Kind:对象类型,例如
Deployment、PodMetrics - 三者合称 GVK,即 Group-Version-Kind
例如:
apiVersion: metrics.k8s.io/v1beta1
kind: PodMetrics
metadata:
name: web-7d8f6f7d6b-x7k9p
namespace: default
其中:
Group = metrics.k8s.io
Version = v1beta1
Kind = PodMetrics
客户端访问资源时,Kind 不直接出现在 URL 中,而是使用资源的复数名:
/apis/metrics.k8s.io/v1beta1/namespaces/default/pods
/apis/metrics.k8s.io/v1beta1/namespaces/default/pods/web-7d8f6f7d6b-x7k9p
URL 的基本形式是:
/api/<version>/...
用于核心组,例如:
/api/v1/namespaces/default/pods
非核心 API 组使用:
/apis/<group>/<version>/...
例如:
/apis/apps/v1/namespaces/default/deployments
apiVersion 是对象的 GVK 中 Group 和 Version 的 YAML 表示;它本身不包含资源名。API Server 还必须把 Kind 映射到资源名,例如:
Deployment -> deployments
Pod -> pods
PodMetrics -> pods
因此,一个 Extension API Server 不只是返回任意 JSON。若希望 kubectl、动态客户端和 Kubernetes 控制器正常使用,它需要遵循 Kubernetes API 的资源、元数据、状态码、发现和并发语义。
2. Extension API Server 是按 Group/Version 接入的
API Aggregation 通过 APIService 对象注册一个 API 组版本。一个 APIService 通常对应一个 group/version,而不是一个 Kind:
v1beta1.metrics.k8s.io
这意味着下面两个资源可以由同一个 Extension API Server 处理:
metrics.k8s.io/v1beta1/nodes
metrics.k8s.io/v1beta1/pods
注册对象类似:
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1beta1.metrics.k8s.io
spec:
group: metrics.k8s.io
version: v1beta1
service:
namespace: monitoring
name: metrics-server
port: 443
caBundle: <Extension API Server 服务端证书 CA 的 base64>
groupPriorityMinimum: 100
versionPriority: 100
metadata.name 的约定格式是:
<version>.<group>
例如:
v1.metrics.example.com
v1beta1.metrics.k8s.io
APIService 的字段含义如下:
group和version:声明这个聚合服务器负责哪个 API group/version。service:指定主集群内的 Service,kube-apiserver通过它访问扩展服务器。caBundle:用于验证 Extension API Server 的服务端 TLS 证书。groupPriorityMinimum:在 API 组发现信息中的优先级。versionPriority:同一 API 组中不同版本的优先级。url:也可以把请求转发到集群外部 URL,但生产环境通常需要额外处理网络可达性、证书和安全边界。insecureSkipTLSVerify:跳过后端 TLS 证书验证,仅适合临时实验,不应作为生产方案。
groupPriorityMinimum 和 versionPriority 主要影响发现信息中“首选组和版本”的选择,不是把请求转发到某个资源的路由权重。路由首先由 group/version 决定。
二、API Aggregation 的组件关系和完整请求路径
1. kube-aggregator 在哪里
在标准 Kubernetes 控制面中,API Aggregation 由 kube-apiserver 内部的聚合层提供。历史上也常用名称 kube-aggregator 描述这部分能力,但通常不需要单独部署一个名为 kube-aggregator 的进程。
主 API Server 接收所有客户端请求:
客户端 -> kube-apiserver
对于内置资源,例如:
/api/v1/pods
/apis/apps/v1/deployments
请求由主 API Server 的本地 handler 处理。
对于已注册的聚合 API,例如:
/apis/metrics.k8s.io/v1beta1/pods
主 API Server 根据对应的 APIService 将请求代理到后端 Service。
完整流程可以表示为:
sequenceDiagram
participant C as 客户端
participant K as kube-apiserver<br/>认证与聚合层
participant S as Kubernetes Service
participant E as Extension API Server
participant D as 扩展存储或外部系统
C->>K: GET /apis/example.com/v1/widgets
K->>K: 校验客户端 TLS、Token 或其他认证
K->>K: 查询 APIService v1.example.com
K->>K: 校验后端服务证书
K->>S: 转发 HTTPS 请求
S->>E: 负载均衡到一个扩展服务器 Pod
E->>E: 读取代理认证头并执行授权
E->>D: 查询或修改资源
D-->>E: 返回资源或错误
E-->>S: Kubernetes API 响应
S-->>K: 转发响应
K-->>C: 返回状态码、Headers 和 Body
关键点是:客户端通常不直接连接 Extension API Server,而是连接主 API Server。这样客户端只需要信任集群 API Server 的证书,不需要知道扩展服务的网络地址。
2. 注册之后,谁负责哪些工作
一次请求可以拆成几个责任边界:
| 阶段 | 主要责任方 |
|---|---|
| 客户端到主 API Server 的 TLS | kube-apiserver |
| 客户端身份认证 | 通常是 kube-apiserver |
| group/version 路由 | 主 API Server 的聚合层 |
| 主 API Server 到扩展服务器的 TLS | 聚合层和 Extension API Server |
| 资源路由、参数解析、对象校验 | Extension API Server |
| 对扩展资源的授权 | 通常是 Extension API Server |
| 资源持久化和并发控制 | Extension API Server |
| 资源的业务逻辑 | Extension API Server |
主 API Server 代理请求,并不意味着它理解 Extension API Server 中的每一种 Kind,也不意味着它替扩展资源执行完整的 RBAC 授权。
三、API 发现:客户端如何知道聚合资源存在
1. 发现接口的层级
Kubernetes 客户端通常先访问发现接口,再决定某个资源是否存在、支持哪些动词和版本。
核心组发现接口:
GET /api
GET /api/v1
非核心组发现接口:
GET /apis
GET /apis/<group>
GET /apis/<group>/<version>
注册:
group = example.com
version = v1
之后,主 API Server 会在发现结果中暴露:
GET /apis/example.com
GET /apis/example.com/v1
Extension API Server 则应返回该版本支持的资源列表。一个简化的发现响应类似:
{
"kind": "APIResourceList",
"apiVersion": "v1",
"groupVersion": "example.com/v1",
"resources": [
{
"name": "widgets",
"singularName": "widget",
"namespaced": true,
"kind": "Widget",
"verbs": ["create", "delete", "get", "list", "patch", "update", "watch"],
"shortNames": ["wdg"]
}
]
}
这份信息至少需要与实际路由保持一致:
发现声明支持 widgets
实际却没有 /apis/example.com/v1/namespaces/<ns>/widgets
这种不一致会导致动态客户端、kubectl api-resources 或控制器在运行时失败。
2. APIService 的优先级不是资源版本转换
例如,同一组下有:
example.com/v1
example.com/v1beta1
可以分别注册两个 APIService,并把 v1 的 versionPriority 设置得更高。这会使客户端更倾向于把 v1 视为首选版本。
但 APIService 本身不提供类似 CRD conversion webhook 的对象版本转换协议。若一个客户端请求 v1,另一个客户端请求 v1beta1,扩展服务器必须:
- 分别提供两个 APIService,或
- 只提供一个版本,并明确不支持另一个版本,或
- 自己实现跨版本转换。
不能因为两个版本指向相同的后端进程,就自动得到 Kubernetes 原生 API 的版本转换能力。
3. 聚合发现失败时会发生什么
假设 APIService 已创建,但扩展服务器不可达:
kube-apiserver -> Service -> 无可用 Endpoint
则对该 group/version 的发现或实际请求可能返回失败,常见表现包括:
503 Service Unavailable
kubectl api-resources 可能报告某个 APIService 发现失败;而访问内置资源仍然可以成功。具体显示格式会随 Kubernetes 客户端版本变化,因此诊断时应直接查看 APIService 状态和主 API Server 日志,而不能只根据 kubectl 的一行汇总信息判断。
发现失败的影响可能比一次业务请求失败更大:
- 动态客户端无法建立资源映射。
- 控制器启动时无法确认资源是否存在。
kubectl的资源补全和短名称解析异常。- 某些依赖全量发现的客户端会把整个操作视为失败。
因此,“扩展资源不用时先让它发现失败”也不是无害状态。
四、创建一个 APIService:TLS、Service 和路由配置
下面给出一个最小的注册结构。它不能直接运行,因为 caBundle 必须与实际服务证书匹配;这是有意保留的安全前置条件,而不是填入一个伪造证书。
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1.example.com
spec:
group: example.com
version: v1
service:
namespace: example-system
name: example-apiserver
port: 443
caBundle: LS0tLS1CRUdJTi...
groupPriorityMinimum: 1000
versionPriority: 100
扩展服务器应通过 Service 暴露 HTTPS 端口:
apiVersion: v1
kind: Service
metadata:
name: example-apiserver
namespace: example-system
spec:
selector:
app: example-apiserver
ports:
- name: https
port: 443
targetPort: https
证书的 SAN 至少应覆盖主 API Server 连接 Service 时使用的 DNS 名称。通常需要覆盖:
example-apiserver.example-system.svc
example-apiserver.example-system.svc.cluster.local
实际证书名称取决于集群 DNS、Service 配置以及聚合层的访问方式。不能只为 Pod IP 签发证书,因为 Service 后端 Pod 会变化。
在实验环境中,有人会这样配置:
spec:
insecureSkipTLSVerify: true
这会使主 API Server 不验证扩展服务器的服务端证书。风险在于:如果网络路径或 Service 解析被劫持,主 API Server 可能把请求发送给伪造的 HTTPS 服务。它也不能替代客户端到主 API Server 的 TLS。生产环境应使用受信任 CA 的 caBundle,而不是关闭验证。
应用配置后,可以观察注册状态:
kubectl apply -f apiservice.yaml
kubectl get apiservice v1.example.com
kubectl describe apiservice v1.example.com
正常情况下可以看到:
NAME SERVICE AVAILABLE AGE
v1.example.com example-system/example-apiserver True 10s
也应验证发现:
kubectl get --raw /apis/example.com
kubectl get --raw /apis/example.com/v1
kubectl api-resources --api-group=example.com
如果服务端发现中声明了 widgets,预期可以看到类似:
NAME SHORTNAMES APIVERSION NAMESPACED KIND
widgets wdg example.com/v1 true Widget
这个结果只证明发现链路基本成立,不代表 get、list、watch 或写操作一定可用;这些动词仍必须逐项测试。
五、Extension API Server 的认证:两条 TLS 连接和一组代理头
API Aggregation 中至少存在两条独立的 HTTPS 连接:
客户端 --TLS 连接 1--> kube-apiserver
kube-apiserver --TLS 连接 2--> Extension API Server
连接 1 负责客户端与主 API Server 的安全通信。连接 2 负责主 API Server 与扩展服务器之间的安全通信。不要把这两条连接的证书混为一谈。
1. 主 API Server 如何把用户身份传给扩展服务器
主 API Server 先认证客户端。例如客户端可能使用:
- ServiceAccount Bearer Token
- 客户端证书
- OIDC Token
- Webhook Token Authentication
- 其他由主 API Server 配置的认证方式
认证成功后,聚合层会把身份转发给 Extension API Server,常见请求头包括:
X-Remote-User: alice
X-Remote-Group: developers
X-Remote-Group: system:authenticated
X-Remote-Extra-<key>: <value>
扩展服务器不能仅仅因为请求带有 X-Remote-User 就信任它。它必须确认请求确实来自受信任的聚合层。
常见做法是:
- 主 API Server 使用代理客户端证书连接 Extension API Server。
- Extension API Server 信任签发该代理证书的 CA。
- Extension API Server 仅接受来自该受信任客户端证书的身份头。
- 任何绕过主 API Server 直接访问扩展服务器的请求,都不能伪造有效的代理身份。
主 API Server 侧常见相关配置包括:
--proxy-client-cert-file
--proxy-client-key-file
--requestheader-client-ca-file
--requestheader-allowed-names
--requestheader-username-headers
--requestheader-group-headers
--requestheader-extra-headers-prefix
Extension API Server 侧则需要配置与之匹配的 RequestHeader 认证参数,通常基于 generic API server 框架提供的 delegated authentication 配置。
具体参数名称和默认行为受 Kubernetes 发行版及组件版本影响,部署时必须检查对应版本的 kube-apiserver --help 和 Extension API Server 的配置文档。不能只复制另一套集群的参数,因为控制面证书和信任链通常不同。
2. 为什么需要双向信任
考虑一个错误实现:
Extension API Server:
收到 X-Remote-User 就直接使用
如果这个服务可以被用户直接访问,攻击者可以构造:
X-Remote-User: system:admin
扩展服务器就会错误地把攻击者当成管理员。
正确的信任条件应近似为:
接受代理身份头
当且仅当
请求来自通过 TLS 客户端证书认证的可信聚合层
如果扩展服务器使用 Kubernetes Service 暴露,应通过网络策略、Service 访问边界和监听地址限制绕过路径。TLS 客户端证书验证是身份头防伪的核心,NetworkPolicy 只是额外防护,不能替代证书验证。
3. 认证和授权是两个阶段
认证回答:
“请求者是谁?”
授权回答:
“这个身份是否允许对这个资源执行这个动作?”
主 API Server 认证成功后,扩展服务器仍需决定:
alice 是否允许:
GET /apis/example.com/v1/namespaces/default/widgets
扩展服务器可以:
- 自己维护授权逻辑;
- 调用主 API Server 的
SubjectAccessReview; - 使用 generic API server 的 delegated authorization;
- 结合自己的业务权限系统。
如果采用 SubjectAccessReview,扩展服务器使用的 ServiceAccount 需要有权创建相应的审查对象。例如常见的授权代理配置会授予访问:
authorization.k8s.io/subjectaccessreviews
authentication.k8s.io/tokenreviews
这里的逻辑是:
主 API Server 认证 alice
│
▼
Extension API Server 接收 alice 的身份
│
▼
Extension API Server 调用 SubjectAccessReview
│
▼
主 API Server 根据 RBAC 判断 alice 是否可执行该动作
若只配置了认证而没有授权,结果通常不是“系统自动按照集群 RBAC 工作”,而是扩展服务器可能错误地放行、错误地拒绝,或者对所有请求返回 403 Forbidden。
4. 一个可诊断的认证失败例子
如果主 API Server 与扩展服务器之间的代理证书不被信任,常见结果是:
x509: certificate signed by unknown authority
此时:
- 客户端到主 API Server 的证书可能完全正常;
- 资源发现仍可能失败;
kubectl get --raw /apis/example.com/v1可能返回503;APIService的Available条件可能是False。
如果 TLS 成功但代理身份认证失败,扩展服务器可能返回:
401 Unauthorized
如果认证成功但授权失败,则通常是:
403 Forbidden
通过区分 x509、401、403 和 503,可以快速判断问题处于证书、认证、授权还是可达性阶段。
六、Extension API Server 必须实现什么 API 语义
API Aggregation 只解决“如何接入主 API Server”,不自动为扩展服务器生成资源处理器。扩展服务器仍需要实现 Kubernetes API 的行为。
1. 对象结构:Metadata、Spec 和 Status
一个典型资源可以具有如下结构:
apiVersion: example.com/v1
kind: Widget
metadata:
name: demo
namespace: default
labels:
app: demo
spec:
size: 3
status:
phase: Ready
observedGeneration: 1
这些字段不是装饰:
metadata.name:对象在命名空间中的身份。metadata.namespace:命名空间范围资源的作用域。metadata.uid:对象生命周期中的稳定身份。metadata.resourceVersion:并发控制和 Watch 位置。metadata.generation:Spec 被修改后通常递增。metadata.creationTimestamp:创建时间。metadata.labels和annotations:索引、筛选和附加元数据。spec:用户期望状态。status:服务器或控制器观察到的实际状态。
如果扩展服务器声明支持 Kubernetes 风格对象,却把 metadata.resourceVersion 当作普通字符串随意覆盖,客户端的并发更新和 Watch 续接就会产生错误。
2. CRUD 和 HTTP 状态码
对于一个命名空间资源 widgets,常见路径是:
POST /apis/example.com/v1/namespaces/default/widgets
GET /apis/example.com/v1/namespaces/default/widgets
GET /apis/example.com/v1/namespaces/default/widgets/demo
PUT /apis/example.com/v1/namespaces/default/widgets/demo
PATCH /apis/example.com/v1/namespaces/default/widgets/demo
DELETE /apis/example.com/v1/namespaces/default/widgets/demo
通常应遵守以下语义:
| 操作 | 成功状态 | 常见失败 |
|---|---|---|
| 创建 | 201 Created |
409 Conflict、422 Unprocessable Entity |
| 获取 | 200 OK |
404 Not Found |
| 列表 | 200 OK |
403 Forbidden |
| 更新 | 200 OK |
409 Conflict、404 Not Found |
| 删除 | 200 OK 或符合 API 约定的删除响应 |
404 Not Found |
| Watch | 200 OK,持续返回事件 |
连接关闭或错误事件 |
错误响应也应使用 Kubernetes 风格的 Status 对象,而不是任意字符串:
{
"kind": "Status",
"apiVersion": "v1",
"status": "Failure",
"reason": "NotFound",
"message": "widgets.example.com \"demo\" not found",
"code": 404
}
这样 client-go、动态客户端和控制器才能正确识别错误原因。
3. resourceVersion 与乐观并发
假设客户端读取对象:
resourceVersion = 41
随后两个客户端同时更新:
客户端 A: 使用 resourceVersion=41 更新
客户端 B: 使用 resourceVersion=41 更新
正确的乐观并发过程是:
- A 提交,后端确认当前版本是 41。
- A 更新成功,后端版本变为 42。
- B 提交,仍声明自己基于版本 41。
- 后端发现当前版本已经是 42。
- B 返回
409 Conflict,而不是静默覆盖 A 的修改。
形式化地说,更新条件应满足:
请求中的 rv = 当前存储中的 rv
若条件不成立:
更新失败,返回 409
这正是 resourceVersion 的并发控制直觉。它不是业务对象的版本号,也不能被客户端随意递增。
如果 Extension API Server 后端是外部数据库,必须自己实现等价的 compare-and-swap 语义。例如 SQL 后端可以使用:
UPDATE widgets
SET spec = ?, resource_version = resource_version + 1
WHERE namespace = ?
AND name = ?
AND resource_version = ?;
受影响行数为 0 时,需要区分对象不存在和版本冲突,并返回正确的 Kubernetes 错误。
4. Watch 不是“定时重复 List”
控制器通常使用:
List -> Watch -> 处理事件 -> 从断点继续 Watch
Watch 事件至少包括:
ADDED
MODIFIED
DELETED
BOOKMARK
客户端可能从某个 resourceVersion 开始 Watch:
GET /apis/example.com/v1/widgets?watch=true&resourceVersion=42
扩展服务器必须保证:
- 返回对象的
metadata.resourceVersion可用于后续续接。 - 事件顺序满足后端定义的可观察顺序。
- 已删除对象可以返回
DELETED事件,通常携带最后已知对象。 - 历史版本过期时返回可识别的过期错误,客户端再重新 List。
- 长连接不会因为服务器内部没有立即事件而被错误关闭。
若实现只是每隔几秒重新查询数据库并把全部对象当成 MODIFIED,控制器可能重复处理、产生不必要负载,甚至无法正确判断删除事件。
5. List 分页和一致性
大型资源列表可能使用:
?limit=100
响应中带有继续令牌:
{
"metadata": {
"resourceVersion": "52",
"continue": "..."
}
}
扩展服务器需要保证 Continue Token 的语义:
- 令牌不能被客户端伪造后访问任意数据;
- 令牌失效时返回明确错误;
- 分页期间应具有合理的一致性;
remainingItemCount等字段如果实现,必须与实际结果匹配。
是否完整支持分页、Watch、字段选择器、标签选择器和 Table 转换,都会影响 kubectl 和控制器的兼容性。发现接口中声明支持的能力必须与真实实现一致。
七、扩展 API Server 的 Go 实现边界
生产级 Extension API Server 通常基于 Kubernetes 的 generic API server 体系构建,而不是从 net/http 手写所有协议。原因是 generic API server 可以复用:
- 认证链;
- RequestInfo 解析;
- delegated authentication;
- delegated authorization;
- API 发现;
- REST Storage 接口;
- Watch 编码;
- Kubernetes 风格错误;
- OpenAPI 和审计相关能力。
client-go 主要用于访问 Kubernetes API,不是一个“自动把 Go struct 变成聚合 API”的框架。一个扩展服务器往往同时需要两类客户端:
Extension API Server
├── 接收聚合 API 请求
└── 使用 client-go 访问主 Kubernetes API
例如它可能在处理请求时调用:
typedClient.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
但这只代表扩展服务器作为客户端访问内置资源,并不会自动实现它自己的:
/apis/example.com/v1/widgets
扩展服务器仍需注册自己的 GroupVersion、资源类型和 REST Storage。
1. 处理请求时不要混淆四个版本
一个请求中可能同时出现:
HTTP 路径版本: example.com/v1
对象 apiVersion: example.com/v1
Go 内部类型版本: example.com/v1alpha1
存储格式版本: 数据库自定义格式
它们不一定相同。若扩展服务器提供多个外部版本,应明确:
外部版本 -> 内部版本 -> 存储格式
并在边界处完成转换。不能把 Go 包路径直接当成 Kubernetes API 版本,也不能认为修改 apiVersion 字符串就完成了版本兼容。
2. Admission Webhook 与 Aggregation 的边界
Admission Webhook 和 API Aggregation 都会出现在扩展系统中,但它们处于不同阶段。
例如创建内置 Deployment:
客户端
-> kube-apiserver
-> 认证
-> Admission Webhook
-> 内置资源 REST Storage
-> etcd
而访问聚合资源:
客户端
-> kube-apiserver
-> 认证
-> 聚合路由
-> Extension API Server
-> 扩展存储
如果目标是给内置 Pod 增加校验,应使用 Validating Admission Webhook;如果目标是修改 Pod,应使用 Mutating Admission Webhook。Webhook 不能让一个新的 API group 出现在发现接口中,也不能替代 Extension API Server 的 CRUD 和 Watch。
八、可用性:APIService 的 Available 状态意味着什么
1. APIService 状态不是扩展业务健康的完整证明
查看状态:
kubectl get apiservice v1.example.com -o yaml
常见状态结构:
status:
conditions:
- type: Available
status: "True"
reason: Passed
message: all checks passed
当扩展服务器不可达或发现失败时,可能看到:
status:
conditions:
- type: Available
status: "False"
reason: ServiceAccessError
message: failing or incomplete response from server
Available=True 通常说明聚合层能够访问后端并完成基本检查,不能推出以下结论:
- 所有资源的
GET都正确; - 所有用户都有正确权限;
- 写入一定成功;
- Watch 不会断开;
- 后端数据库没有数据损坏;
- 多副本之间没有状态分歧。
因此,可用性至少应分层观测:
APIService Available
↓
发现接口成功
↓
匿名或测试身份认证行为正确
↓
授权成功/拒绝符合预期
↓
GET、LIST、WATCH、写操作正确
↓
扩展后端存储和外部依赖健康
2. 常见失败路径
Service 没有 Endpoint
检查:
kubectl -n example-system get svc example-apiserver
kubectl -n example-system get endpointslice \
-l kubernetes.io/service-name=example-apiserver
kubectl -n example-system get pods -l app=example-apiserver
如果 EndpointSlice 为空,主 API Server 无法把请求送到 Pod。应检查:
- Service selector 是否匹配 Pod 标签;
- Pod 是否 Ready;
targetPort是否存在;- 容器是否监听预期端口;
- Service 是否位于
APIService.spec.service.namespace指定的命名空间。
TLS SAN 或 CA 不匹配
检查扩展服务器日志和 APIService 状态:
kubectl describe apiservice v1.example.com
kubectl -n example-system logs deploy/example-apiserver
常见错误包括:
x509: certificate is valid for example-apiserver.example.com,
not example-apiserver.example-system.svc
或者:
x509: certificate signed by unknown authority
修复步骤通常是:
- 确认 Service DNS 名称。
- 重新签发包含正确 SAN 的服务端证书。
- 把签发该证书的 CA 编码到
spec.caBundle。 - 重启或热加载扩展服务器证书。
- 重新检查
Available和发现接口。
端口配置不一致
例如:
spec:
service:
port: 443
但 Service 只暴露:
ports:
- port: 8443
聚合层连接的目标端口就不成立。这里的 APIService.spec.service.port 指向 Service port,不是容器端口;容器端口由 Service 的 targetPort 决定。
扩展服务器启动但发现接口错误
Pod 可能处于 Ready,TLS 也正常,但:
GET /apis/example.com/v1
返回非 APIResourceList 的内容,或 groupVersion 与注册版本不一致。这会导致 APIService 仍不可用,或者客户端发现失败。Readiness Probe 只检查进程存活并不能替代 Kubernetes API 发现检查。
3. 依赖故障如何传播
假设扩展服务器的请求都依赖外部云 API:
kubectl
-> kube-apiserver
-> Extension API Server
-> 云 API
云 API 超时时,最外层可能表现为:
kubectl 命令卡住或最终超时
如果扩展服务器没有设置合理的上下文超时,它可能继续占用连接和 goroutine,导致更多请求排队。正确的处理方式是:
- 继承并检查 HTTP 请求的 context;
- 为外部调用设置明确超时;
- 将依赖错误转换为适当的
5xx或 KubernetesStatus; - 对读和写区分重试策略;
- 不要对非幂等写请求盲目重试。
API Aggregation 本身不会替扩展服务器做业务级熔断,也不会把外部系统自动变成高可用系统。
九、高可用设计:多副本、负载均衡和状态一致性
1. Service 多副本解决的是入口可达性
一个常见部署形态是:
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-apiserver
namespace: example-system
spec:
replicas: 3
selector:
matchLabels:
app: example-apiserver
template:
metadata:
labels:
app: example-apiserver
spec:
containers:
- name: apiserver
image: example/apiserver:1.0.0
ports:
- name: https
containerPort: 8443
Service 会把请求分配到多个 Pod,但这只保证入口层面可以有多个后端。若三个 Pod 各自使用本地内存保存资源:
Pod A: widgets = {demo}
Pod B: widgets = {}
Pod C: widgets = {demo, test}
那么 List、Get、Watch 结果会随请求落点变化,资源语义不再一致。
生产可用的状态设计通常是:
多个无状态 Extension API Server Pod
│
▼
共享且具备并发语义的存储
这个存储可以是数据库、云资源系统或专门的后端服务,但必须明确:
- 谁是权威数据源;
- 如何生成和比较
resourceVersion; - 如何产生 Watch 事件;
- 如何处理删除和重连;
- 如何保证多个副本看到一致状态。
2. Watch 连接对扩缩容更敏感
一个 Watch 是长连接:
客户端 -> Pod A
当 Pod A 被终止时,连接会断开。正常的 Kubernetes 客户端会重新 List/Watch,但扩展服务器必须保证:
- 断线后的新 List 能看到最新状态;
- 客户端使用旧
resourceVersion继续 Watch 时,后端能判断该版本是否仍可用; - 不会因为 Pod 间本地缓存不一致而漏掉删除或更新事件。
如果扩展服务器用本地缓存加速读取,需要把缓存失效、事件传播和重启恢复作为正式设计,而不是把缓存当成权威存储。
3. 滚动升级和 API 兼容
更新 Extension API Server 时,旧 Pod 和新 Pod 可能同时处理请求。若两个版本的对象编码、字段校验或存储格式不兼容,就可能出现:
旧 Pod 能读取,新 Pod 不能读取
新 Pod 写入,旧 Pod 返回 500
List 成功,但 Watch 事件无法解码
因此升级顺序通常应满足:
先让新代码兼容旧数据
再逐步替换旧实例
最后再清理旧字段或旧存储格式
如果要移除一个已发布的 API group/version,还必须先处理所有客户端、控制器和已存储对象;删除 APIService 会使对应 URL 立即失去路由,不等于平滑弃用。
十、API Aggregation 与 CRD 的选择
1. CRD 更适合什么
如果资源满足以下特征,CRD 往往更合适:
- 数据可以存储在主集群 etcd;
- 需要标准 CRUD、List、Watch;
- 不需要自定义复杂存储协议;
- 可以使用 CRD schema、默认值和转换机制;
- 资源 API 与 Kubernetes 对象模型相近。
例如:
Database
Backup
Application
这些对象通常是声明式配置,CRD 加 Operator 就能满足需求。
2. Extension API Server 更适合什么
聚合 API 更适合:
- 数据来自外部系统,而不是主集群 etcd;
- 资源数量或查询方式不适合直接存入 etcd;
- 需要特殊的读取、聚合或计算逻辑;
- 需要更强地控制 API 的存储和生命周期;
- 提供类似指标的短生命周期、只读数据。
Metrics Server 就是典型例子:节点和 Pod 指标通常来自实时采集数据,不适合简单地当作普通 Kubernetes 对象持久化。
3. 反例:不要为了“看起来像 Kubernetes”而聚合
假设只需要保存:
kind: Team
spec:
owner: alice
如果没有外部数据源、特殊一致性要求或独立 API 生命周期,使用 Extension API Server 会额外引入:
- TLS 证书管理;
- RequestHeader 认证;
- APIService 可用性;
- Service 网络;
- 发现接口;
- 自己实现 REST 和 Watch;
- 独立存储高可用。
这时 CRD 通常具有更少的故障路径。API Aggregation 的价值不在于“可以自定义 URL”,而在于它允许一个独立 API 后端真正拥有资源 API 的实现责任。
十一、从客户端角度验证一条完整链路
下面是一组有层次的检查命令。假设资源注册为 example.com/v1。
第一步:确认注册对象
kubectl get apiservice v1.example.com
kubectl describe apiservice v1.example.com
检查:
Available=True
如果是 False,先不要测试资源 CRUD,因为路由或发现基础设施还没有成立。
第二步:确认主 API Server 能返回组发现
kubectl get --raw /apis/example.com
预期是包含 APIGroup 的 JSON,而不是 HTML、空响应或 503。
再检查版本发现:
kubectl get --raw /apis/example.com/v1
预期包含:
{
"groupVersion": "example.com/v1",
"resources": [...]
}
第三步:确认资源映射
kubectl api-resources --api-group=example.com
如果没有 widgets,应检查:
- Extension API Server 是否注册了正确的 Kind;
- 发现响应中的
name是否是复数资源名; APIService.spec.group和spec.version是否与发现响应一致;- 主 API Server 是否缓存了旧发现结果,必要时等待缓存刷新或重启相关客户端。
第四步:直接测试资源请求
kubectl get --raw \
/apis/example.com/v1/namespaces/default/widgets
预期是 WidgetList 或符合约定的列表响应。
使用普通客户端命令时:
kubectl get widgets -n default
kubectl get widget demo -n default -o yaml
若发现成功但 kubectl get 返回 404,优先检查资源路由和复数名;若返回 403,优先检查扩展服务器授权;若返回 503,优先检查后端可达性和 APIService 状态。
第五步:验证认证和授权边界
至少测试两种身份:
允许访问的 ServiceAccount
明确不允许访问的普通用户或 ServiceAccount
期望结果不是“所有请求都成功”,而是:
有权限身份 -> 200
无权限身份 -> 403
未认证身份 -> 401 或由主 API Server 拒绝
还要测试绕过主 API Server 直接访问 Pod 或 Service 的情况。如果绕过后可以伪造 X-Remote-User 获得权限,说明扩展服务器错误信任了代理头。
十二、生产故障诊断的顺序
一个可靠的诊断顺序应从外到内,避免直接修改 RBAC 掩盖网络问题。
1. APIService 状态
kubectl get apiservice v1.example.com -o yaml
先看:
status.conditions
关注:
type: Availablestatusreasonmessage
2. Service 和 EndpointSlice
kubectl -n example-system get svc example-apiserver -o yaml
kubectl -n example-system get endpointslice \
-l kubernetes.io/service-name=example-apiserver
确认:
Service 端口存在
EndpointSlice 有地址
Pod Ready
端口与 targetPort 对应
3. Pod 日志和证书
kubectl -n example-system get pods -l app=example-apiserver
kubectl -n example-system logs deploy/example-apiserver
重点查找:
x509
certificate
requestheader
unauthorized
forbidden
timeout
watch
resourceVersion
4. 从主 API Server 的视角检查
kubectl exec 到普通业务 Pod 中测试扩展 Service,并不能完全复现主 API Server 到后端的访问路径。主 API Server 可能运行在控制面节点、静态 Pod 或独立网络中。
应结合:
- 控制面节点网络;
- API Server 日志;
- Service DNS;
- NetworkPolicy;
- 防火墙和云安全组;
- Service 的 ClusterIP 与后端端口。
云厂商托管控制面还可能限制控制面到集群 Service 的网络方式,不能默认认为所有托管集群都拥有相同的聚合网络实现。
5. 最后再检查授权
当 TLS、发现和请求到达都正常后,才检查:
- 主 API Server 是否传递正确身份;
- Extension API Server 是否启用 RequestHeader 认证;
- delegated authorization 的 CA 和地址是否正确;
- 扩展服务器的 ServiceAccount 是否有权执行 TokenReview 或 SubjectAccessReview;
- 业务资源的 RBAC 规则是否符合预期。
十三、规范保证、常见实现和版本边界
需要区分三类信息。
Kubernetes API 机制保证的部分
Kubernetes 为 API Aggregation 定义了:
- 通过
APIService注册 group/version; - 通过主 API Server 代理聚合请求;
- 提供聚合 API 的发现入口;
- 使用 Service 或 URL 定位后端;
- 通过 TLS 和代理身份头连接扩展服务器。
常见实现方式
社区和发行版通常使用:
- generic API server;
- RequestHeader 认证;
- Service 访问扩展服务器;
caBundle验证后端证书;- delegated authentication/authorization;
client-go访问主 API Server;- Deployment 加 Service 提供扩展服务器副本。
这些是常见实现,不代表所有自定义 API Server 都必须使用完全相同的代码结构。
需要特别检查版本的部分
以下内容容易发生版本偏差:
kube-apiserver的认证参数;- generic API server 的配置结构;
- Discovery 的具体响应字段;
- Watch 的 Bookmark、分页和缓存行为;
- APIService 中部分字段的弃用状态;
- 云厂商控制面到扩展 Service 的网络实现;
- Kubernetes 客户端对发现缓存和错误的展示方式。
因此,代码和配置应以目标 Kubernetes 版本对应的 API 文档、组件帮助信息和 k8s.io 模块版本为准。不能因为某个旧版本示例使用了 v1beta1,就假定当前稳定版本仍推荐该版本。
十四、核心因果关系
API Aggregation 的工作条件可以简化为:
请求可用
=
客户端能认证
∧ APIService 正确注册
∧ 主 API Server 能发现 group/version
∧ 主 API Server 能访问 Service
∧ 后端 TLS 证书受信任
∧ Extension API Server 能验证代理身份
∧ Extension API Server 能完成授权
∧ 资源路由和 API 语义实现正确
∧ 后端存储或外部依赖可用
其中任意一项失败,都可能使“同一个 kubectl get”呈现完全不同的错误:
注册错误 -> 404 或没有发现资源
后端不可达 -> 503
证书错误 -> x509
认证失败 -> 401
授权失败 -> 403
资源路由错误 -> 404
并发版本冲突 -> 409
后端异常 -> 5xx
理解这条链路后,Extension API Server 就不再是“向 Kubernetes 添加一个 HTTP 路径”,而是一个需要同时满足发现、认证、授权、资源语义、存储一致性和高可用条件的独立 API 控制面组件。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes Server-Side Apply:Field Manager、冲突、所有权和 Controller
- 下一篇:Kubernetes 调度性能与扩展:Profile、Plugin、Queue、规模和测试
- 延伸:Kubernetes API 对象:GVK、Metadata、Spec、Status、版本和兼容
- 延伸:Kubernetes Admission Webhook 开发:Mutating、Validating、证书和高可用
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论