2. K8s Operator 基础回顾:Operator 模式、CRD 与 Controller、Reconcile 循环、Operator SDK 简介

在深入构建灰度批次发布流水线之前,必须重新夯实 Operator 的核心知识体系。Operator 是 Kubernetes 生态中实现“应用感知”自动化运维的关键模式,它将运维专家的领域知识编码为软件,持续地运行在集群中。本节将系统回顾 Operator 模式的本质、核心组件(CRD 与 Controller)、核心逻辑(Reconcile 循环)以及主流开发框架(Operator SDK)。

2.1 Operator 模式:从“资源”到“应用”的运维抽象

Kubernetes 原生提供了 Deployment、Service、ConfigMap 等基础资源来管理无状态应用。然而,对于有状态应用(如数据库、消息队列)或需要复杂运维流程的应用(如灰度发布、备份恢复),原生资源的能力边界不足。Operator 模式的核心思想是:将整个应用的运维知识(部署、扩缩容、升级、故障恢复)编码为一个 Kubernetes 扩展控制器,使其能够像管理 Pod 一样管理整个应用。

关键洞察: Operator 本质上是“人”的替代者。它将运维工程师的 SOP(标准操作流程)转化为代码,通过 Kubernetes API 持续监听应用状态,并自动执行修正动作,最终达到用户定义的期望状态。

Operator 模式由两个核心组件构成:

  • 自定义资源定义(CRD): 定义一个新的 Kubernetes API 资源类型,用于描述应用的“期望状态”。例如,定义一个 GreyRelease 资源,包含版本号、灰度比例、批次大小等字段。
  • 自定义控制器(Controller): 一个持续运行的循环,负责监听 GreyRelease 资源的变化,并调用 Kubernetes API 创建、更新或删除底层资源(如 Deployment、Service、Ingress),使集群的实际状态向 CR 中定义的期望状态收敛。
对比维度 原生 Kubernetes 控制器(如 Deployment Controller) 自定义 Operator 控制器
管理对象 内置资源(Pod、ReplicaSet) 自定义资源(CR)
领域知识 通用调度、副本管理 特定应用运维逻辑(如灰度策略、备份流程)
复杂度 Kubernetes 核心维护 由应用开发者或 SRE 团队开发维护
典型场景 无状态应用部署 有状态应用、复杂发布策略、自动化运维
课程关联: 在本课程中,我们将开发一个 GreyRelease Operator,它管理的不是单个 Pod,而是一组具有灰度标签的 Pod 版本集合,并动态调整 Service 的流量权重。

2.2 CRD 与 Controller:Operator 的“数据面”与“控制面”

CRD 和 Controller 是 Operator 的一体两面,缺一不可。

2.2.1 自定义资源定义(CRD)

CRD 是 Operator 的“数据面”,它定义了用户如何描述应用的期望状态。一个 CRD 包含:

  • API 组与版本: 如 greyrelease.example.com/v1。
  • 作用域: 集群级别(Cluster)或命名空间级别(Namespaced)。
  • OpenAPI v3 Schema: 定义 CR 的 spec 和 status 字段结构,Kubernetes API Server 会据此进行校验。
# 一个简化的 GreyRelease CRD 示例(YAML 片段)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: greyreleases.greyrelease.example.com
spec:
  group: greyrelease.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                targetVersion:
                  type: string
                batchSize:
                  type: integer
                  minimum: 1
                canaryWeight:
                  type: integer
                  maximum: 100
            status:
              type: object
              properties:
                currentBatch:
                  type: integer
                phase:
                  type: string
  scope: Namespaced
  names:
    plural: greyreleases
    singular: greyrelease
    kind: GreyRelease
    shortNames:
      - gr

2.2.2 自定义控制器(Controller)

Controller 是 Operator 的“控制面”,它通过 Informer 机制监听 CR 及其关联资源的事件,并执行调谐逻辑。Controller 内部通常包含:

  • Informer/ListWatcher: 监听 Kubernetes API 资源变化,减少对 API Server 的频繁请求。
  • Work Queue: 将事件去重并排队,确保同一资源不会同时被多个协程处理。
  • Reconciler: 核心业务逻辑实现,负责比较期望状态与实际状态,并执行操作。
CRD 与 Controller 交互架构 CRD 与 Controller 交互架构 用户 / kubectl API Server CRD 定义 创建 CR 校验 Schema Controller Informer Work Queue Reconciler Watch CR 事件 创建/更新/删除资源 Deployment / Service / ConfigMap
最佳实践: CRD 的 Schema 设计应尽量严谨,利用 required、enum、minimum/maximum 等约束,将错误配置在 API 层拦截,减轻 Controller 的校验负担。

2.3 Reconcile 循环:Operator 的心脏

Reconcile(调谐)循环是 Controller 的核心执行逻辑。它不是一个简单的“if-then”判断,而是一个基于事件驱动、面向期望状态、持续收敛的闭环过程。

典型的 Reconcile 循环包含以下步骤:

  1. 接收事件: 从 Work Queue 中取出一个键(Namespace/Name),代表一个发生了变化的 CR 或关联资源。
  2. 获取期望状态: 从 API Server 读取最新的 CR 实例,解析 spec 字段,得到用户期望的最终状态。
  3. 获取实际状态: 查询集群中由该 CR 管理的所有子资源(如 Deployment、Pod),获取当前的实际运行状态。
  4. 比较与计算差异: 对比期望状态与实际状态,计算出需要执行的操作列表(如:创建缺失的 Deployment、更新 Service 的 Selector、删除多余的 Pod)。
  5. 执行操作: 通过 Kubernetes Client 调用 API Server,执行创建、更新或删除操作。
  6. 更新状态: 更新 CR 的 status 字段,将当前进度、版本、错误信息等反馈给用户。
  7. 重新入队或完成: 如果操作未完成(如等待 Pod 就绪),则重新将 CR 键放入 Work Queue,等待下一次调谐;如果已完成,则不再入队。
常见误区: Reconcile 循环必须是“幂等”的。即使同一个事件被多次触发,或者 Reconcile 过程中发生故障重启,最终结果也必须一致。不要在 Reconcile 中依赖外部状态或执行非幂等操作(如“追加”而不是“覆盖”)。
Reconcile 循环流程图 Reconcile 循环流程图 1. 接收事件 (Key) 2. 获取期望状态 (Spec) 3. 获取实际状态 4. 比较期望 vs 实际 有差异? 是 5. 执行操作 (Create/Update/Delete) 6. 更新 Status 否 完成 (Requeue)

2.4 Operator SDK 简介:加速 Operator 开发

手动编写 Informer、Work Queue 和 Reconcile 循环的样板代码非常繁琐且容易出错。Operator SDK 是 Red Hat 主导的开源框架,它封装了底层复杂性,提供了脚手架工具和标准化的开发模式,让开发者可以专注于业务逻辑。

Operator SDK 支持三种主流开发语言:

语言 底层框架 适用场景 学习曲线
Go controller-runtime 生产级 Operator,性能要求高,社区生态最丰富 中等
Ansible operator-sdk-ansible 运维团队,已有大量 Ansible Playbook,快速原型 低
Helm operator-sdk-helm 将现有 Helm Chart 包装为 Operator,管理复杂应用 低

使用 Operator SDK(Go 版本)创建 Operator 的标准流程:

  1. 初始化项目: operator-sdk init --domain=example.com --repo=github.com/example/greyrelease-operator
  2. 创建 API 与 CRD: operator-sdk create api --group=greyrelease --version=v1 --kind=GreyRelease --resource=true --controller=true
  3. 实现 Reconcile 逻辑: 编辑 controllers/greyrelease_controller.go 文件中的 Reconcile 方法。
  4. 生成与部署: make docker-build docker-push IMG=your-registry/greyrelease-operator:latest 和 make deploy IMG=your-registry/greyrelease-operator:latest
课程实践: 本课程后续章节将使用 Operator SDK (Go) 构建 GreyRelease Operator。我们将重点实现 Reconcile 循环中的灰度批次管理逻辑,包括:根据 batchSize 创建新版本 Pod、动态调整 Service 的 selector 权重、以及监控 Pod 就绪状态以推进下一批次。

Operator SDK 自动生成的 Reconcile 方法骨架如下:

// controllers/greyrelease_controller.go
func (r *GreyReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)

    // 1. 获取 GreyRelease CR 实例
    var greyRelease greyreleasev1.GreyRelease
    if err := r.Get(ctx, req.NamespacedName, &greyRelease); err != nil {
        // 如果 CR 被删除,则忽略
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 2. 获取当前管理的 Deployment 列表
    var deployments appsv1.DeploymentList
    if err := r.List(ctx, &deployments, client.MatchingLabels{
        "app": greyRelease.Spec.AppName,
    }); err != nil {
        return ctrl.Result{}, err
    }

    // 3. 核心调谐逻辑:比较期望版本与当前版本,执行灰度批次操作
    // ... (将在后续章节详细实现)

    // 4. 更新 CR 状态
    greyRelease.Status.Phase = "Progressing"
    if err := r.Status().Update(ctx, &greyRelease); err != nil {
        return ctrl.Result{}, err
    }

    return ctrl.Result{RequeueAfter: 10 * time.Second}, nil // 10秒后重新调谐
}
关键参数: ctrl.Result{RequeueAfter: 10 * time.Second} 是 Reconcile 循环中常用的策略,用于实现“轮询”效果。在灰度发布场景中,我们通常需要等待 Pod 就绪或批次间隔,使用 RequeueAfter 比直接返回 Requeue: true 更优雅,可以避免在短时间内频繁调谐。