3.15. Kubernetes 系统扩展:CRD、聚合 API 与 Operator
Kubernetes 的内置资源覆盖不了所有领域。CRD 让你声明新的资源类型,聚合 API 让独立的 API Server 接入同一套发现与鉴权,Operator 则把运维知识写成控制器。本章沿着这三条路径扩展控制平面,并补上多 control-plane 的高可用基础。
- 层级:
L3 · 平台
- 难度:
进阶
- 预计:
90 分钟
- 前置:
核心对象、RBAC、集群部署
3.15.1. 你将学到
CRD 的 v1 结构、版本字段、structural schema、printer columns 与 status 子资源。
控制器/Operator 的 reconcile 循环,finalizer、ownerReference 与 leader election 的作用。
用 APIService 把外部 API Server 接入 kube-apiserver,以及它需要的 TLS 与 RBAC 委托。
RBAC 聚合规则,以及多 control-plane + etcd 的高可用要点。
3.15.2. 扩展控制平面的三条路径
CRD 是最轻的扩展:向 kube-apiserver 注册新的 group/version/kind,对象存进 etcd,读写、watch、RBAC、发现都由 kube-apiserver 直接处理,不需要额外进程。代价是只能使用通用 API 语义,无法自定义存储或特殊子资源行为。
聚合 API 把请求代理给一个独立运行的 API Server。这个 server 自己实现 REST 语义、存储和业务逻辑,但通过 APIService 注册到主 apiserver,客户端仍然只面对一个 API 端点。它适合需要 protobuf、特殊存储或复杂生命周期的场景,代价是要自己处理 TLS、鉴权委托和高可用。
方式 |
新增资源类型 |
数据存储 |
适用场景 |
|---|---|---|---|
CRD |
是,声明式 |
etcd |
绝大多数领域资源 |
聚合 API |
是,独立 server |
自定义 |
特殊语义、性能或协议需求 |
Operator |
否,组合 CRD 与控制器 |
通过 CRD 存 etcd |
有状态应用的运维自动化 |
3.15.3. CRD:把自定义资源变成一等公民
apiextensions.k8s.io/v1 要求每个版本提供 structural schema:必须有 type: object,未知字段默认被裁剪,x-kubernetes-preserve-unknown-fields 只应在必要时使用。v1beta1 在 v1.22 已移除,旧清单必须迁移;versions 列表中只能有一个 storage: true,其余版本靠 conversion webhook 转换。
additionalPrinterColumns 决定 kubectl get 的列,subresources.status 把 spec 与 status 分开:普通更新写 spec,控制器通过 /status 子资源写状态,两者不会互相覆盖。subresources.scale 还能让 HPA 和 kubectl scale 识别自定义资源;v1.31 支持用 CEL 的 x-kubernetes-validations 写跨字段校验。
3.15.4. 控制器与 Operator
控制器从 informer 缓存里读对象,把 key 放进 workqueue,然后执行 reconcile:读取实际状态,计算与期望状态的差异,发起变更,再返回。这个过程是 level-triggered 而不是 edge-triggered,所以重复执行必须幂等;失败就重新入队,直到成功或达到重试上限。
Operator 是“CRD + 控制器 + RBAC + 部署清单”的打包,核心是把领域知识编码进 reconcile。删除外部资源前要先清理,靠的是 finalizer:控制器给对象加上 finalizer,删除时先做清理,完成后移除 finalizer,对象才真正消失。ownerReferences 让垃圾回收器在父对象删除时级联清理子对象;多个副本运行时必须开启 leader election,否则会重复执行同一动作。
3.15.5. 聚合 API Server
聚合 API 的关键是 APIService:它声明 group、version 和一个 Service,kube-apiserver 收到该组请求后先完成认证,再把请求代理给这个 Service。扩展 apiserver 必须能验证用户身份,因此需要 system:auth-delegator 权限去调用 TokenReview 和 SubjectAccessReview;还需要读取 kube-system/extension-apiserver-authentication ConfigMap,拿到客户端 CA 配置。
TLS 是第二个硬要求。生产环境在 APIService 里写 caBundle,让 kube-apiserver 校验扩展 server 的证书;insecureSkipTLSVerify: true 只适合实验。Available condition 为 False 时,对应组的资源会从 kubectl api-resources 消失,请求返回 503。
3.15.6. RBAC 聚合
聚合 ClusterRole 不直接写 rules,而是用 aggregationRule 选择一组带标签的 ClusterRole,控制器把它们的 rules 合并进来。内置的 admin、edit、view 就是这样实现的:给自定义角色打上 rbac.authorization.k8s.io/aggregate-to-edit: "true",它的权限就会自动进入 edit。聚合角色不能手工改 rules,下一次同步会覆盖;要调整权限,只能改被选中的角色或标签。
3.15.7. 控制平面高可用
多 control-plane 的前提是一个稳定接入地址:kubeadm init --control-plane-endpoint 指向 VIP 或负载均衡器,后续 kubeadm join --control-plane 都连这个地址。apiserver 无状态,controller-manager 与 scheduler 通过 leader election 保证同一时刻只有一个实例工作。
stacked etcd 与 control-plane 同机部署,3 个节点容忍 1 个故障,5 个容忍 2 个;2 个节点没有意义,因为 quorum 需要多数派。规模较大时改用 external etcd,把 etcd 独立到低延迟磁盘上。etcd 要定期做 snapshot 并实际演练恢复;只有备份文件、没有恢复流程,等于没有备份。
3.15.8. 动手实验
实验一:定义 User CRD、状态子资源与打印列
目标:注册 users.auth.ilinux.io,创建自定义资源,用 schema 拒绝非法输入,并通过 status 子资源更新状态。
步骤 1 · 创建 CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: users.auth.ilinux.io
spec:
group: auth.ilinux.io
scope: Namespaced
names:
kind: User
plural: users
singular: user
shortNames: ["u"]
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: ["userID", "groups"]
properties:
userID:
type: integer
minimum: 1
maximum: 65535
groups:
type: array
items: {type: string}
status:
type: object
properties:
phase: {type: string}
additionalPrinterColumns:
- {name: UserID, type: integer, jsonPath: .spec.userID}
- {name: Groups, type: string, jsonPath: .spec.groups}
- {name: Phase, type: string, jsonPath: .status.phase}
subresources:
status: {}
kubectl apply -f crd-user.yaml
kubectl wait --for=condition=Established crd/users.auth.ilinux.io --timeout=60s
步骤 2 · 创建自定义资源
apiVersion: auth.ilinux.io/v1alpha1
kind: User
metadata:
name: admin
namespace: default
spec:
userID: 1
email: [email protected]
groups: ["superusers", "administrators"]
kubectl apply -f user-admin.yaml
kubectl get users
kubectl get u admin -o yaml
kubectl get users 的表头就是 printer columns。创建 userID: 0 的对象会被 schema 拒绝,错误里会指出 minimum 约束。
步骤 3 · 写 status 子资源
kubectl patch user admin --subresource=status --type=merge \
-p '{"status":{"phase":"Active"}}'
kubectl get u admin
直接改 spec 不会影响 status,反之亦然;控制器应使用 /status 端点,否则并发更新会互相覆盖。
实验二:用 APIService 接入示例 apiserver
目标:部署原书的 sample-apiserver,把 wardle.example.com/v1alpha1 注册进集群,并通过聚合层访问 Flunder 资源。
步骤 1 · 应用全部清单
kubectl apply -f book-repo/chapter12/sample-apiserver/
kubectl -n wardle rollout status deploy/wardle-server
kubectl get apiservice v1alpha1.wardle.example.com
kubectl api-resources | grep wardle
kubectl get flunders
目录里的 01–08 依次创建命名空间、ServiceAccount、RBAC、Service、Deployment 与 APIService。05 把 SA 绑定到 system:auth-delegator,06 让它读取 kube-system 的认证配置;少了任何一个,扩展 apiserver 都无法完成认证委托,聚合层会返回 401/403。
步骤 2 · 检查 APIService
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1alpha1.wardle.example.com
spec:
group: wardle.example.com
version: v1alpha1
groupPriorityMinimum: 1000
versionPriority: 15
insecureSkipTLSVerify: true
service:
name: api
namespace: wardle
port: 443
Available 应为 True,kubectl get flunders 应返回示例 server 里的数据。若显示 ServiceUnavailable,先看 kubectl -n wardle logs deploy/wardle-server 和 Endpoints;若返回 401/403,回到步骤 1 检查两个委托绑定。insecureSkipTLSVerify 只用于实验,生产应准备 CA 并写 caBundle。
3.15.8.1. 验证清单
CRD 的
Established为 True,kubectl get users显示 UserID、Groups、Phase 三列。userID: 0或缺少groups的对象被拒绝,错误信息包含 schema 约束。kubectl patch --subresource=status能更新 Phase,且不影响 spec。kubectl get apiservice v1alpha1.wardle.example.com的 Available 为 True。
3.15.9. 常见问题与排错
现象 |
可能原因 |
处理方式 |
|---|---|---|
|
CRD 未 Established,或请求的 version 未 |
看 CRD conditions 与 versions;确认 group/version 拼写 |
CRD 创建报 |
多个版本都写了 |
只保留一个 storage 版本,其余 served 版本配 conversion |
|
printer columns 写错层级,或 CRD 没重新应用 |
检查 |
status 更新被 spec 覆盖 |
没走 |
用 |
APIService Available=False |
Service 无 Endpoints、TLS 校验失败、端口不对 |
看 conditions、Endpoints 与扩展 server 日志 |
3.15.10. 进阶挑战
给 User CRD 增加
v1版本,配置 conversion webhook,把v1alpha1的对象无损转换到v1。用 kubebuilder 写一个最小 User 控制器:创建时生成随机密码写入 Secret,删除时用 finalizer 清理 Secret。
用
aggregationRule把自定义权限聚合进edit,再用kubectl auth can-i验证普通编辑者获得了新权限。
3.15.11. 小结
CRD 用声明式 schema 扩展 API,资源存 etcd;v1 要求 structural schema,status 子资源隔离 spec 与状态。
Operator 用 reconcile 循环持续逼近期望状态,finalizer 负责删除清理,leader election 防止重复执行。
聚合 API 把独立 server 挂到 kube-apiserver 后面,必须解决 TLS、auth-delegator 与 auth-reader 三件事。
多 control-plane 依赖统一接入地址、奇数 etcd 与 leader election;stacked etcd 简单,external etcd 更适合大规模集群。