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 控制器的工作流程
- 监听(Watch):通过 List-Watch 接口订阅 CRD 和关联资源(如 Pod、Service)的事件。
- 入队(Enqueue):将事件封装为
ReconcileRequest(包含命名空间和名称)放入工作队列。 - 调谐(Reconcile):从队列中取出请求,执行核心业务逻辑。
- 更新状态(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 运行时架构。
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。