Kubernetes Operator:什么时候该写,写了要注意什么
“我们是不是应该写一个 Operator?”
这个问题在架构评审里出现的频率,远超 Operator 实际应该被使用的频率。Operator 是一个功能强大但成本不低的扩展机制——它值得被认真对待,不值得被滥用。
先想清楚为什么要写
Kubernetes 原生提供的控制器(Deployment、StatefulSet、CronJob)已经能处理大多数无状态服务和简单有状态应用的生命周期管理。
Operator 值得引入的场景只有一种:你有一类应用,它的运维知识足够复杂,无法用现有原生对象表达,并且这个运维逻辑会被重复执行。
典型例子:
- 分布式数据库的滚动升级——需要先升级 follower,再切换 leader,不能直接用
RollingUpdate - 消息队列扩容——新 broker 加入后需要触发分区重均衡,不是简单 scale up
- AI 推理服务的蓝绿发布——需要根据 GPU 型号匹配节点亲和性,挂载特定的模型卷
如果你的场景只是”想定义一些自定义配置”,ConfigMap + Helm 就够了。Operator 是给运维逻辑编码,不是给配置管理增加复杂度。
Reconcile 循环是核心,不是样板代码
所有 Operator 的核心都是一个 Reconcile 函数。它的语义非常简单:
1 | |
触发条件:监听的资源发生变更(Create/Update/Delete),或者你主动返回 RequeueAfter。
关键是理解它不保证幂等性——你的 Reconcile 逻辑必须自己保证幂等。同一个对象可能在很短时间内被 Reconcile 多次(API Server 重启、控制器重启、用户频繁修改),每次执行应该得到相同结果。
实践准则:在每个子操作里,先 GET 当前状态,再判断是否需要修改,而不是直接 CREATE 或 UPDATE。
1 | |
Status 是 Operator 的诚信记录
Operator 和用户之间的沟通渠道是 Status 字段。写得准确的 Status 让用户知道发生了什么;写得马虎的 Status 会让用户陷入”是操作没生效,还是 Operator 在工作”的迷惑中。
几个实践原则:
Phase 要有明确语义。用 Pending / Running / Failed / Terminating 而不是自定义的模糊字符串。
Conditions 比 Phase 携带更多信息。Condition 可以同时存在多个,反映多个维度的状态:
1 | |
不要写可能引起误导的 Status。如果还不确定某个状态,不要写”Running”,写”Initializing”。用户宁愿等待真实信息,也不愿意被误导后花时间排查。
Finalizer:资源删除是最容易出问题的地方
Operator 管理的资源通常有外部依赖(数据卷、云资源、外部服务注册)。直接删除 CR,这些外部依赖不会自动清理。Finalizer 解决的就是这个问题。
1 | |
注意:Finalizer 的清理逻辑必须幂等。如果清理中途失败,Reconcile 会重试,清理逻辑会再次执行,不要假设清理只发生一次。
框架选择:Kubebuilder 还是 Operator SDK
两个框架做的是同一件事,选哪个更多是团队习惯问题,但有几点实际差异值得注意。
Kubebuilder 是社区维护的基础框架,Operator SDK 构建在它之上。如果 Operator 逻辑是纯 Go,直接用 Kubebuilder 依赖更少,升级路径更清晰。
Operator SDK 额外提供 Ansible 和 Helm 模式,适合没有 Go 背景的团队快速启动,但 Ansible Operator 的运维经验相对冷门,遇到问题社区支持有限。
不管选哪个,不要绕过框架自己管 informer 和 cache。这两个框架的核心价值就是帮你处理 List/Watch 的客户端缓存和事件去重,手动管理会引入难以排查的一致性问题。
测试:本地开发是最大的摩擦点
Operator 的测试有几个层次:
单元测试:用 fake client 测试 Reconcile 逻辑,不需要真实集群。
1 | |
集成测试:用 envtest 启动轻量 API Server 和 etcd,在接近真实的环境里测试完整 Reconcile 流程。这个方式测的是控制逻辑,不是网络行为。
端到端测试:在真实集群(或 kind/minikube)里部署 Operator,验证完整行为。对于有 Finalizer、Webhook、外部资源依赖的场景,单元测试覆盖不到真实问题,这一层必须有。
一个容易忽略的点:Operator 的控制循环是异步的。在测试里用 Eventually 而不是同步断言:
1 | |
写 Operator 之前,先问一个问题
如果这个运维知识只需要执行一次,它不应该是 Operator,它应该是 Job 或脚本。
Operator 是为持续运行的控制循环设计的。如果需求是”部署时执行一次数据初始化”,用 init container 或 Helm hook 更直接,不要为了用 Operator 而用 Operator。
这条边界,通常也是运维工具和运维哲学真正分叉的地方。