Kubernetes 运算符

kubernetes-operator
分类编程
作者Alireza Rezvani
许可MIT
评分4.50/5
使用12.0K

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 是协调循环,而非脚本

code
观察(实际状态) → 期望 = 读取(spec) → 对比(实际, 期望) → 执行动作 → 更新(status)
                                                                          ↓
                                                                   重新入队 / 完成

失败的 Operator 通常具有以下特征:
1. 将协调视为命令式(先做 A,再做 B,最后做 C),而非声明式(幂等地使 实际状态=期望状态)
2. 瞬时失败时未重新入队
3. 未使用 finalizers,导致产生孤儿资源
4. 修改 spec 而非 status
5. 未使用 status 子资源(更新 status 会触发 spec 协调 $\rightarrow$ 导致死循环)
6. 在协调过程中产生阻塞(长 HTTP 调用、锁)
7. 忘记领导者选举 $\rightarrow$ 在多副本部署时出现脑裂

以下 3 个工具可捕捉上述每类问题。

快速上手

bash
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。

bash
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: truestorage: true

  • Schema 中包含 Conditions 数组(允许使用 metav1.Conditions

  • Printer columns 包含 AgeStatus/Phase

reconcile_lint.py

用于检查 Go 控制器 reconcile 函数是否存在反模式。

bash
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 进行评分。

bash
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。ReadyReconcilingDegraded。每项应包含 reason 和 message。
4. 添加 finalizers。 否则,删除操作会与控制器产生竞态,导致外部资源成为孤儿。
5. 从第一天起就对 CRD 进行版本管理。 v1alpha1 $\rightarrow$ v1beta1 $\rightarrow$ v1。规划转换 Webhook。
6. 通过 OpenAPI v3 schema 进行校验。 不要依赖控制器去执行本应在准入阶段(admission)就失败的校验。
7. kubectl get 配置 additionalPrinterColumns 至少显示 AgePhaseReady
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)

code
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

code
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:选择框架

code
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 天(生产环境无死循环)