Skip to content
Deven Home
Go back

Kubernetes Operator 开发:用 CRD 和控制器封装运维知识

Updated:
3 min read
—Edit page
Table of Contents

深入 Operator 模式:自定义 CRD,用 controller-runtime 编写 Reconcile 循环,把有状态应用的运维经验代码化。

Table of contents

Open Table of contents

从 Helm 到 Operator

Helm 解决的是「部署」问题,但有状态应用(数据库、消息队列)的难点在运行时运维:备份、扩容、主从切换、版本升级。这些逻辑没法用模板表达,需要一段持续运行的代码来观察集群状态并做出反应——这就是 Operator。

Operator = CRD(自定义资源) + Controller(调谐循环)。

定义 CRD

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: pgclusters.db.example.com
spec:
  group: db.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                replicas:
                  type: integer
                  minimum: 1
                storageSize:
                  type: string
  scope: Namespaced
  names:
    kind: PGCluster
    plural: pgclusterscrd.yaml

应用 CRD 后,用户就可以像用内置资源一样声明一个 PGCluster,剩下的交给控制器。

Reconcile:核心调谐循环

Operator 的精髓是 level-triggered:不是响应「事件」,而是不断对比「期望状态(spec)」和「实际状态」,把差异收敛为零。

func (r *PGClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	log := log.FromContext(ctx)

	var pg dbv1.PGCluster
	if err := r.Get(ctx, req.NamespacedName, &pg); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
	}

	// 1. 确保 StatefulSet 存在且副本数符合期望
	sts := buildStatefulSet(&pg)
	if err := ctrl.SetControllerReference(&pg, sts, r.Scheme); err != nil {
		return ctrl.Result{}, err
	}
	if err := r.createOrUpdate(ctx, sts); err != nil {
		return ctrl.Result{}, err
	}

	// 2. 更新 status,反馈当前实际状态
	pg.Status.Ready = readyReplicas(ctx, &pg)
	if err := r.Status().Update(ctx, &pg); err != nil {
		return ctrl.Result{}, err
	}

	// 3. 备份还没完成?30 秒后重新调谐
	if !pg.Status.BackupCompleted {
		return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
	}
	return ctrl.Result{}, nil
}

几个关键点:

  • 幂等:Reconcile 可能被触发任意多次,每次都要能安全重入
  • SetControllerReference:建立 owner 关系,删除 CR 时级联回收子资源
  • Status 子资源:spec 归用户写,status 归控制器写,职责分离
  • RequeueAfter:长任务(备份、升级)不要阻塞,定期回来检查

Finalizer:优雅清理

删除 CR 时,如果还有外部资源(云盘、备份)需要清理,用 finalizer 挡住删除,清理完再放行:

if pg.DeletionTimestamp.IsZero() {
	if !controllerutil.ContainsFinalizer(&pg, finalizer) {
		controllerutil.AddFinalizer(&pg, finalizer)
		return ctrl.Result{}, r.Update(ctx, &pg)
	}
} else {
	if controllerutil.ContainsFinalizer(&pg, finalizer) {
		cleanupExternalResources(&pg) // 删除云盘快照等
		controllerutil.RemoveFinalizer(&pg, finalizer)
		return ctrl.Result{}, r.Update(ctx, &pg)
	}
	return ctrl.Result{}, nil
}

工程实践建议

Warning
  • RBAC 最小权限:控制器只需要它管理的那几种资源的权限
  • 速率限制:controller-runtime 默认有指数退避,避免故障资源打爆 API Server
  • 可观测性:暴露 reconcile 次数、失败率、队列深度等指标
  • 测试:用 envtest 起一个本地控制面跑集成测试,别只靠单测 mock

小结

  • Operator 把「运维手册」变成了持续运行的代码
  • 核心是声明式 API + level-triggered 的调谐循环,而非事件驱动
  • Finalizer、owner reference、status 子资源是三个必须掌握的机制
  • 从简单的状态同步做起,再逐步加备份、升级等复杂运维逻辑

Edit page
Share this post:

Previous Post
数据湖与数据仓库的区别
Next Post
数据指标是什么