文章

K8s Controller Operator 开发

K8s Controller/Operator 开发

一句话:K8s 的灵魂是 声明式 + 调谐(Reconcile)——你描述”期望状态”,Controller 不断对比”实际状态”并修正。Go 是写 Controller 的母语(client-go / controller-runtime)。本文是你 K8s 特性详解 系列在”怎么造 K8s 扩展”层面的补全。

前提:Go 基础速查 已讲模块与基础。本文讲控制循环的内部机制。

client-go 架构(底层机制)

graph LR
    API[K8s API Server] --> REF[Reflector]
    REF --> DELTA[DeltaFIFO 队列]
    DELTA --> INFORMER[Informer]
    INFORMER --> INDEX[(本地 Indexer 缓存)]
    INFORMER --> HANDLER[Event Handler]
    HANDLER --> WORKQ[Workqueue]
    WORKQ --> RECON[Reconcile 逻辑]
    RECON --> API2[调 API 修正状态]
  • Reflector:watch API Server,把事件写进 DeltaFIFO
  • Informer:消费队列、更新本地缓存(Indexer)、触发回调
  • Indexer:本地缓存,避免每次读 API(降负载)
  • Workqueue:去重 + 重试,保证事件最终被处理
  • Reconcile:你写的业务逻辑——对比期望 vs 实际,下发修正

关键认知:Informer 的本地缓存让 Controller 几乎不直连 API Server 读状态,只有”修正动作”才写 API。这是 K8s 能 scale 到上千节点的原因。

controller-runtime:别再手写 Informer

直接 client-go 写很啰嗦,生产用 controller-runtime(kubebuilder 底层):

func (r *MyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var obj MyCRD
    if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }
    // 1. 对比期望 vs 实际
    if !obj.Spec.Ready {
        // 2. 修正:创建依赖资源 / 调外部系统
        if err := r.createDependency(ctx, &obj); err != nil {
            return ctrl.Result{RequeueAfter: 5 * time.Second}, err  // 退避重试
        }
    }
    // 3. 更新状态
    return ctrl.Result{}, r.Status().Update(ctx, &obj)
}

Reconcile 的设计哲学:

  • 幂等:同一输入多次调用结果一致(因为会反复触发)
  • 不保存状态:状态在 K8s 对象里,不在内存(重启可恢复)
  • 出错就 Requeue:返回 error 或 RequeueAfter 自动重试

一个最小 Operator 骨架

mgr, _ := ctrl.NewManager(cfg, ctrl.Options{Scheme: scheme})
_ = ctrl.NewControllerManagedBy(mgr).
    For(&myv1.MyCRD{}).            // watch 哪种资源
    Owns(&appsv1.Deployment{}).    // 它创建的子资源
    Complete(&MyReconciler{Client: mgr.GetClient(), Scheme: mgr.GetScheme()})
_ = mgr.Start(ctrl.SetupSignalHandler())

CRD 设计原则

原则说明
Spec/Status 分离Spec = 期望(用户写),Status = 实际(Controller 写)
Status 要有条件conditions[] 表达进度(Progressing/Ready/Failed)
用 Finalizer删除前做清理(如释放外部资源),避免孤儿资源
版本化v1alpha1 → v1beta1 → v1,用 conversion webhook
不要塞太多一个 CRD 管一类事,避免”上帝对象”

与你的 vault 强相关

  • K8s 特性详解(25 篇):你已精通 K8s 对象模型、准入控制、调度——本文是”怎么扩展它”
  • Cilium 架构与数据面组件:Cilium 本身就是一个超复杂 Operator(cilium-operator 调谐 CiliumNode/CiliumEndpoint)
  • CI-CD:Operator 用 kubebuilder 生成,CI 里跑 make test + 镜像构建
  • GPU 集群运维:自定义 GPU 调度 Operator 的思路同源

常见坑

现象解法
Reconcile 不幂等反复创建重复资源先 Get 判断存在再创建
缓存与直连不一致读到的状态过期r.Get(走缓存)还是 APIReader(直连)要分清
事件风暴一个变更触发无限 Reconcile比较 Spec 变化才下发;用 Owns 正确设 owner reference
Finalizer 泄漏资源删不掉(Terminating)Finalizer 清理逻辑必须成功才移除

关联知识

参考资源

  • client-go 示例:kubernetes/client-go/blob/master/examples
  • controller-runtime 文档
  • Kubebuilder 官方 Book

学习时间

内容日期状态
K8s Controller/Operator2026-07-24完成:client-go 架构、controller-runtime、骨架、CRD 设计、坑

状态

  • client-go 架构
  • controller-runtime Reconcile
  • Operator 骨架
  • CRD 设计原则
  • 常见坑