3、Operator 模式深入:自定义资源(CRD)、自定义控制器、调谐循环(Reconcile)原理

Operator 模式是 Kubernetes 生态中实现“应用级自动化运维”的核心设计范式。它通过扩展 Kubernetes API,将领域专家的运维知识编码为软件,自动执行部署、扩缩容、备份、恢复等复杂操作。本章深入剖析 Operator 模式的三大支柱:自定义资源定义(CRD)、自定义控制器以及调谐循环(Reconcile)。

3.1 自定义资源定义(CRD)—— 扩展 Kubernetes API

CRD 是 Operator 的数据模型层。它允许你在不修改 Kubernetes 核心代码的情况下,定义新的资源类型(如 RedisCluster、EtcdBackup)。Kubernetes 会像处理原生资源(Pod、Service)一样,为 CRD 提供 RESTful API、存储(etcd)和校验。

3.1.1 CRD 的核心结构

一个 CRD 清单通常包含以下关键字段:

字段 说明 示例值
group API 组,用于逻辑分组 cache.example.com
names.kind 资源类型名称(首字母大写) RedisCluster
names.plural 资源复数名称,用于 URL 路径 redisclusters
scope 作用域:Namespaced 或 Cluster Namespaced
versions[].schema OpenAPI v3 结构定义,描述 spec 和 status openAPIV3Schema
关键设计原则: CRD 的 spec 是用户期望的“目标状态”,status 是控制器观测到的“当前状态”。Operator 的职责就是不断缩小两者之间的差距。

3.1.2 CRD 示例(RedisCluster)

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: redisclusters.cache.example.com
spec:
  group: cache.example.com
  names:
    kind: RedisCluster
    listKind: RedisClusterList
    plural: redisclusters
    singular: rediscluster
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            properties:
              replicas:
                type: integer
                minimum: 1
                default: 3
              version:
                type: string
                default: "7.0"
          status:
            type: object
            properties:
              readyReplicas:
                type: integer
              phase:
                type: string

3.2 自定义控制器 —— 控制循环的大脑

控制器是一个持续运行的循环,它通过 Kubernetes 的 Informer 机制监听资源的变化(创建、更新、删除)。当 CRD 实例发生变化时,控制器会将事件入队,并触发调谐逻辑。

3.2.1 控制器的工作流程

  1. 监听(Watch):通过 List-Watch 接口订阅 CRD 和关联资源(如 Pod、Service)的事件。
  2. 入队(Enqueue):将事件封装为 ReconcileRequest(包含命名空间和名称)放入工作队列。
  3. 调谐(Reconcile):从队列中取出请求,执行核心业务逻辑。
  4. 更新状态(Update Status):将调谐结果写回 CRD 的 status 字段。
Operator SDK 实践: 使用 controller-runtime 库时,你只需实现 Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) 方法。框架自动处理事件监听、队列管理和并发控制。

3.3 调谐循环(Reconcile)—— 核心业务逻辑

调谐循环是 Operator 的心脏。它接收一个请求,读取当前状态,对比期望状态,然后执行一系列操作使当前状态趋近于期望状态。这个过程是幂等的、声明式的。

3.3.1 调谐循环的典型步骤

步骤 操作 说明
1 获取 CR 实例 从集群中读取 RedisCluster 对象
2 校验并设置默认值 确保 spec 字段合法,填充默认值
3 获取关联资源 列出当前命名空间下属于该 CR 的 Pod、Service
4 对比期望 vs 实际 检查副本数、镜像版本、端口等是否匹配
5 执行调和操作 创建/更新/删除 Pod、Service、ConfigMap 等
6 更新状态 写入 status.readyReplicas、status.phase
7 返回结果 返回 ctrl.Result{RequeueAfter: 30 * time.Second} 定期重试

3.3.2 调谐循环的 Go 代码骨架

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

    // 1. 获取 CR 实例
    var redisCluster cachev1.RedisCluster
    if err := r.Get(ctx, req.NamespacedName, &redisCluster); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 2. 设置默认值(如果未设置)
    if redisCluster.Spec.Replicas == 0 {
        redisCluster.Spec.Replicas = 3
    }

    // 3. 获取当前关联的 Pod 列表
    var podList corev1.PodList
    if err := r.List(ctx, &podList, client.MatchingLabels{
        "app":     "redis",
        "cluster": redisCluster.Name,
    }); err != nil {
        return ctrl.Result{}, err
    }

    // 4. 对比期望副本数与实际 Pod 数
    currentReplicas := len(podList.Items)
    desiredReplicas := int(redisCluster.Spec.Replicas)

    if currentReplicas < desiredReplicas {
        // 5. 创建缺失的 Pod
        for i := currentReplicas; i < desiredReplicas; i++ {
            pod := buildRedisPod(&redisCluster)
            if err := r.Create(ctx, pod); err != nil {
                return ctrl.Result{}, err
            }
        }
    } else if currentReplicas > desiredReplicas {
        // 删除多余的 Pod
        for i := desiredReplicas; i < currentReplicas; i++ {
            if err := r.Delete(ctx, &podList.Items[i]); err != nil {
                return ctrl.Result{}, err
            }
        }
    }

    // 6. 更新状态
    redisCluster.Status.ReadyReplicas = desiredReplicas
    redisCluster.Status.Phase = "Running"
    if err := r.Status().Update(ctx, &redisCluster); err != nil {
        return ctrl.Result{}, err
    }

    // 7. 30 秒后重新调谐,确保持续一致
    return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

3.4 Operator 模式整体架构

下图展示了 CRD、控制器、调谐循环三者如何协同工作,形成一个完整的 Operator 运行时架构。

Operator 模式运行时架构 用户 / 管理员 kubectl apply CRD 实例 (RedisCluster) 存储到 etcd Kubernetes API Server List-Watch 监听 自定义控制器 (Informer + 工作队列) 入队 ReconcileRequest 调谐循环 (Reconcile 方法) 调谐循环内部步骤 1. 获取 CR 实例 (Get) 2. 获取关联资源 (List Pods/Services) 3. 对比期望状态 vs 当前状态 4. 执行调和操作 (Create/Update/Delete) 创建/更新 Pod/Service 执行操作 更新 CR Status 写入状态

3.5 关键概念对比

概念 职责 类比(运维工程师)
CRD 定义新的资源类型和数据结构 运维工单模板(定义字段和格式)
CR 实例 用户提交的具体配置(如“3 副本 Redis 7.0”) 一张填写好的工单
控制器 监听事件、管理队列、触发调谐 工单分发系统(接收工单并分配给工程师)
调谐循环 执行具体操作,使实际状态匹配期望状态 工程师执行工单步骤(创建 Pod、配置网络)
常见陷阱: 调谐循环必须设计为幂等的。多次执行相同请求不应产生副作用(如重复创建 Pod)。Operator SDK 的 RequeueAfter 和 RequeueOnError 机制可帮助处理临时故障,但业务逻辑本身必须健壮。

3.6 调谐循环的触发时机

调谐循环并非只在 CR 变更时触发。以下情况都会导致 Reconcile 被调用:

  • 主资源变更:CRD 实例被创建、更新、删除。
  • 关联资源变更:控制器 Watch 的 Pod、Service 等发生变化(例如 Pod 被意外删除)。
  • 定时重调谐:通过 RequeueAfter 设置周期性检查,确保状态持续一致。
  • 错误重试:调谐返回 error 时,框架会自动重新入队(指数退避)。

理解这些触发机制,有助于你设计出反应迅速且资源消耗合理的 Operator。