Kubernetes 运算符
Kubernetes Operator
构建能够正确协调(reconcile)的 Operator。大多数 Operator 的 Bug 并非 Kubernetes 本身的 Bug,而是协调循环(reconcile-loop)的 Bug:缺失 finalizers、阻塞调用、瞬时错误未重新入队(requeue)、状态漂移、RBAC 权限过大。本技能旨在让这些问题在部署到集群前被确定性地发现。
使用场景
- 构建新的 Kubernetes Operator(CRD 的控制器)
- 审查现有 Operator 的能力等级(capability-level)差距
- 审计 CRD 规范中 status/conditions/finalizer 的正确性
- 选择框架(controller-runtime / kubebuilder / operator-sdk / metacontroller / KOPF)
- 设计自定义资源(Custom Resource)的 API 表面
- 强化 RBAC、领导者选举(leader election)或 Webhook 验证
不适用场景
- 纯 Helm Chart 打包 $\rightarrow$ 使用
helm-chart-builder
- 标准 kubectl 操作 / 蓝绿部署 $\rightarrow$ 使用
senior-devops
- 通用的 k8s 安全态势 $\rightarrow$ 使用
cloud-security
- “我想运行一个工作负载” $\rightarrow$ 那是 Deployment / Job,而不是 Operator
核心原则:Operator 是协调循环,而非脚本
观察(实际状态) → 期望 = 读取(spec) → 对比(实际, 期望) → 执行动作 → 更新(status)
↓
重新入队 / 完成失败的 Operator 通常具有以下特征:
1. 将协调视为命令式(先做 A,再做 B,最后做 C),而非声明式(幂等地使 实际状态=期望状态)
2. 瞬时失败时未重新入队
3. 未使用 finalizers,导致产生孤儿资源
4. 修改 spec 而非 status
5. 未使用 status 子资源(更新 status 会触发 spec 协调 $\rightarrow$ 导致死循环)
6. 在协调过程中产生阻塞(长 HTTP 调用、锁)
7. 忘记领导者选举 $\rightarrow$ 在多副本部署时出现脑裂
以下 3 个工具可捕捉上述每类问题。
快速上手
SKILL=engineering/kubernetes-operator/skills/kubernetes-operator
验证 CRD 设计
python "$SKILL/scripts/crd_validator.py" --crd config/crd/myapp.yaml
对 Go 语言编写的 reconcile 函数进行静态分析
python "$SKILL/scripts/reconcile_lint.py" --controller controllers/myapp_controller.go
根据 OperatorHub 能力等级 (1-5) 进行评分
python "$SKILL/scripts/operator_capability_audit.py" --operator-dir .3 个 Python 工具
全部仅依赖标准库。运行 --help 查看详情。
crd_validator.py
根据 Operator 模式最佳实践验证 CRD YAML。
python scripts/crd_validator.py --crd config/crd/myapp.yaml
python scripts/crd_validator.py --crd config/crd/ --format json检查项:
spec.versions[*].subresources.status是否已定义
设置(status 子资源)
- 除非有明确理由,否则
spec.scope应为Namespaced(而非Cluster)
- 定义了 Singular 和 listKind
spec.versions[*].schema.openAPIV3Schema包含类型定义(顶层不得使用x-kubernetes-preserve-unknown-fields: true)
- 必须有一个版本被标记为
served: true且storage: true
- Schema 中包含 Conditions 数组(允许使用
metav1.Conditions)
- Printer columns 包含
Age和Status/Phase
reconcile_lint.py
用于检查 Go 控制器 reconcile 函数是否存在反模式。
python scripts/reconcile_lint.py --controller controllers/myapp_controller.go检查项(基于正则的启发式分析):
- 返回值格式为
(ctrl.Result, error)
- 错误会触发非零次重新入队(
return ctrl.Result{Requeue: true}, err)
- 对 spec 对象调用
client.Update()会被标记(控制器应仅更新 status)
- reconcile 内部使用
time.Sleep会被标记(应使用RequeueAfter)
- 缺乏上下文取消机制的 HTTP 调用会被标记
- 添加 finalizer 后缺失
defer
- CRD 中存在 conditions 但未调用
IsConditionTrue/SetCondition
- Reconcile 函数超过 80 行(建议提取子函数)
operator_capability_audit.py
根据 OperatorHub 的 5 个能力等级对 Operator 进行评分。
python scripts/operator_capability_audit.py --operator-dir .等级:
- L1 — 基础安装: 定义了 CRD,控制器可将其部署
- L2 — 无缝升级: 包含 PDB、转换 Webhook、版本偏差策略
- L3 — 全生命周期: 支持备份、恢复、故障恢复
- L4 — 深度洞察: 提供指标端点、Prometheus 规则、告警
- L5 — 自动驾驶: 支持自动扩缩容、自动调优、异常检测
报告当前等级以及提升至下一等级的具体步骤。
工具链概览
根据语言和复杂度选择框架。详见 references/tooling_landscape.md。
| 框架 | 语言 | 适用场景 | 维护状态 |
|---|---|---|---|
| controller-runtime | Go | 生产级,低层级控制 | 活跃 (sig-api-machinery) |
| kubebuilder | Go | 标准脚手架,约定优先 | 活跃 (Kubernetes SIGs) |
| operator-sdk | Go / Helm / Ansible | OpenShift / 混合范式团队 | 活跃 (Red Hat) |
| metacontroller | 任意 (基于 webhook) | 多语言团队,避免使用 Go | 活跃度较低 |
| KOPF | Python | Python 技术栈,异步优先 | 活跃 (社区) |
| java-operator-sdk | Java | JVM 技术栈 | 活跃 (Red Hat / Java SIG) |
决策规则:
- 新 Operator + Go 技术栈 $\rightarrow$ kubebuilder
- 新 Operator + Python 技术栈 $\rightarrow$ KOPF
- 新 Operator + 无法确定语言 $\rightarrow$ metacontroller
- 目标平台为 OpenShift $\rightarrow$ operator-sdk
CRD 设计原则
详见 references/crd_design.md。快速指南:
1. status 是控制器观察世界的唯一事实来源。 Spec 是用户期望的状态;status 是控制器观察到的状态。
2. 使用 status 子资源。 否则,更新 status 会重新触发 reconcile(导致死循环)。
3. 使用 Conditions。 如 Ready、Reconciling、Degraded。每项应包含 reason 和 message。
4. 添加 finalizers。 否则,删除操作会与控制器产生竞态,导致外部资源成为孤儿。
5. 从第一天起就对 CRD 进行版本管理。 v1alpha1 $\rightarrow$ v1beta1 $\rightarrow$ v1。规划转换 Webhook。
6. 通过 OpenAPI v3 schema 进行校验。 不要依赖控制器去执行本应在准入阶段(admission)就失败的校验。
7. 为 kubectl get 配置 additionalPrinterColumns。 至少显示 Age、Phase 和 Ready。
8. 除非...否则请为 CRD 设置命名空间。
管理集群范围的资源。
对账循环 (Reconcile loop) 原则
详见 references/reconcile_loop.md。快速指南:
1. 幂等性。 对同一状态执行两次对账 $\rightarrow$ 结果相同,且无副作用。
2. 一次读取,决定,执行。 在对账过程中不要重复观察外部世界。
3. 更新 status 而非 spec。 Spec 属于用户。
4. 返回需要重新入队的错误。 对于已知的瞬时故障,使用 ctrl.Result{RequeueAfter: ...}。
5. 绝不阻塞。 禁止使用 time.Sleep。禁止在没有 context 的情况下进行长时间 HTTP 调用。
6. 使用缓存。 通过控制器的缓存客户端读取;除非有特殊理由,否则不要跳过缓存。
7. 运行副本数 >1 时启用 Leader 选举。 否则请启用单副本模式。
8. 设置 OwnerReferences。 级联删除是 Operator 模式自带的便利功能。
工作流
工作流 1:引导一个新的 Operator (Go + kubebuilder)
1. 选择 Group/Version/Kind:例如 apps.example.com/v1alpha1, kind=MyApp
2. kubebuilder init --domain example.com --repo github.com/org/myapp-operator
3. kubebuilder create api --group apps --version v1alpha1 --kind MyApp
4. 对 config/crd/bases/apps.example.com_myapps.yaml 运行 crd_validator.py
→ 在编写控制器代码前修复所有 WARN
5. 实现 reconcile 函数(Karpathy 原则 2:先实现最简单的正确版本)
6. 对 controllers/myapp_controller.go 运行 reconcile_lint.py
7. 运行 operator_capability_audit.py --operator-dir . — 确认达到 L1 级别
8. 在 kind 集群中测试:kubectl apply -f config/samples/
9. 添加 status conditions;目标是在同一个 PR 中达到 L2 级别工作流 2:审计现有的 Operator
1. 运行 operator_capability_audit.py --operator-dir <path>
2. 运行 crd_validator.py --crd config/crd/
3. 运行 reconcile_lint.py --controller controllers/
4. 对结果进行分级处理:
- FAIL → 阻断发布;在下次部署前修复
- WARN → 提交 issue;在 30 天内修复
5. 在 README 中记录当前的能力级别并提交
6. 计划每季度提升一个能力级别工作流 3:选择框架
1. 确定主要语言限制(团队技能)
2. 确定部署目标(原生 k8s vs OpenShift)
3. 确定 Operator 复杂度(单 CRD vs 多 CRD vs 集群范围)
4. 参考 references/tooling_landscape.md 进行比对
5. 在最终决定前构建一个为期一周的 POC (概念验证)参考资料
references/operator_pattern.md— Operator 的定义,以及何时使用它而非替代方案
references/crd_design.md— CRD 设计原则、版本管理、转换 Webhook
references/reconcile_loop.md— 对账模式、错误处理、幂等性
references/tooling_landscape.md— 框架对比 + 决策树
斜杠命令
/operator-audit — 对 Operator 仓库运行全部 3 个工具并生成 Markdown 报告。
资源模板
assets/crd_template.yaml— 包含 status 子资源、conditions、finalizer 提示和 printer columns 的 CRD
assets/reconcile_skeleton.go— 包含幂等性、conditions、finalizers 和重新入队模式的 Go 控制器对账函数
反模式
- 在 reconcile 内部使用
time.Sleep(30 * time.Second)— 会阻塞其他对账。请使用RequeueAfter。
- 使用
r.Client.Update(ctx, obj)设置状态 — 应使用r.Status().Update(ctx, obj)。
- 未启用 Leader 选举且副本数 $\ge 2$ — 导致脑裂。
- 缺少 finalizer — 删除时会导致外部资源变成孤儿。
- CRD 没有 status 子资源 — 状态更新会触发 spec 对账(导致死循环)。
- Reconcile 函数超过 200 行 —
reconcileXxx 子程序。
- spec 根节点设置
x-kubernetes-preserve-unknown-fields: true—— 会导致验证失效。
- 命令式调谐 (Imperative reconcile)** —— “如果是在创建,执行 A;如果是在更新,执行 B;如果是在删除,执行 C”。这种模式是错误的。调谐的本质应该是:无论处于何种状态,均使实际状态 = 期望状态。
可验证的成功标准
掌握此技能的团队应达到:
- 100% 的新 CRD 在合并前通过
crd_validator.py验证
- 所有调谐函数通过
reconcile_lint.py的严格模式检查
- Operator 在公开发布前达到 OperatorHub Capability Level 3(全生命周期管理)
- 调谐 Bug 的平均修复时间:< 1 天(生产环境无死循环)