news 2026/10/21 5:03:58

Kubernetes Operator原理拆解:从控制循环到CRD与调协机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Kubernetes Operator原理拆解:从控制循环到CRD与调协机制

如果你跟 Kubernetes 打过一段时间交道,大概率会频繁听到 Operator 这个词。我第一次接触它时,正被一个数据库集群的备份恢复搞得焦头烂额,当时就有人跟我说“用 Operator 吧”,我心想这不就是一个跑在集群里的控制器吗,有什么稀奇的。等我真的把 CRD、控制器、调协循环这些东西理清楚,才发现 Operator 背后那套“把运维知识写进代码”的思路,才是它真正值钱的地方。

这篇博客是 kubebuilder 系列的第一篇,我会先把 Kubernetes Operator 的工作原理彻底拆开讲清楚。你不需要先会写代码,我会从“为什么需要 Operator”开始,一路讲到控制循环、CRD、事件触发、调协逻辑,最后再回头看 kubebuilder 到底帮你做了什么。读完你应该能回答一个问题:当你在集群里创建了一个自定义资源对象时,Kubernetes 内部到底发生了什么。

1. Operator 到底解决了什么问题:从“有状态应用运维”说起

1.1 声明式 API 带来的“最后一公里”问题

Kubernetes 的核心设计理念是声明式 API:你告诉集群“我要什么”,集群负责“怎么做到”。比如你创建了一个 Deployment 对象,说“我要 3 个副本”,集群里的控制器就会持续保证实际运行着 3 个副本,多了就杀掉,少了就补上。这套机制对无状态应用非常友好,也是大多数入门教程都会讲的范畴。

但问题在于,真实世界里大量应用是有状态的。数据库要管理数据文件、要处理主从同步、要保证故障时数据不丢;缓存集群要处理分片、要处理节点扩缩容之后的数据迁移;消息队列要管理分区副本的分配。这些应用的运维逻辑极其复杂,远远超出了“保证副本数量”这种基础控制器的能力范围。

你想想,如果你要手动把一个数据库实例从单机扩展成一主两从,需要做什么?先要准备新节点、配置同步关系、等待数据追平、再更新访问入口。这还只是扩容,如果要做故障自动转移,还要考虑主节点宕机后怎么选主、怎么避免脑裂、怎么通知客户端新主节点的地址。这些逻辑如果全交给人工处理,半夜三点被电话叫起来做切换是家常便饭。

Kubernetes 的原生控制器帮不了你。它知道怎么保持副本数,但它不知道数据库主从切换的完整流程。Deployment 控制器只管创建 Pod,至于 Pod 里的数据库进程能否正常对外提供服务,它不关心。这就是声明式 API 的“最后一公里”问题:集群知道你的期望状态,但只有具备领域知识的人才知道怎么把当前状态一步步变成期望状态。

1.2 Operator 与普通控制器的本质区别

Operator 从这个痛点切入。它的定义可以概括为一句话:把某个领域的运维知识编码成自动化逻辑,以控制器的形式运行在 Kubernetes 集群内,把特定应用的当前状态持续调协到期望状态。

注意这里的关键词是“领域知识”。普通控制器处理的是通用的、与应用无关的逻辑,比如副本数管理、滚动更新。Operator 处理的是特定应用的、需要专业知识的逻辑,比如数据库集群的主从切换、缓存节点的分片重排、应用版本升级时的数据迁移。

我用一个生活化的类比来解释:Deployment 控制器像一个物业管家,只管保证每间房里有人住,住的是谁、住得舒不舒服它不管;Operator 则像一位私人医生,不仅知道病人应该在哪个病房,还知道病人该吃什么药、什么时候做检查、指标异常了该怎么处理。

为什么 Operator 能做到这些?因为它背后有一整套 Kubernetes 扩展机制在支撑。你需要让集群认识你的业务对象,需要一个控制器持续观察这些对象,还需要一种方式把业务对象的期望状态和实际状态进行闭环调协。下面几节会逐一拆解这些机制。

2. Kubernetes 控制循环:期望状态与调协的底层逻辑

2.1 控制循环:一个古老而可靠的自动化思想

要理解 Operator,先要看懂 Kubernetes 控制器的通用模型。这个模型其实一点也不新鲜,控制循环的概念在工业自动化领域已经存在了几十年。

拿空调的恒温器举例。你设定目标温度 25 度,这是期望状态。温度传感器读到的当前室温是 28 度,这是实际状态。控制器比较两者,发现实际温度高于期望温度,就启动制冷;等室温降到 25 度以下,再停止制冷。整个过程不断循环,目标是让实际状态永远逼近期望状态。

Kubernetes 的控制器就是同一个套路。只不过输入从“温度传感器读数”换成了“API Server 里的资源对象”,输出从“开关压缩机”换成了“创建 Pod、更新配置、修改状态”等集群操作。整套循环可以抽象成四个步骤:

  1. 读取期望状态:从集群中获取资源的期望定义,比如 Deployment 的 replicas 字段。
  2. 获取实际状态:通过 API Server 查询当前实际运行的资源,比如正在运行的 Pod 数量。
  3. 比对两者差异:计算期望与实际之间的 gap,决定需要做什么变更。
  4. 执行调协动作:调用 Kubernetes API 完成变更,让实际状态向期望状态靠近。

然后回到第一步,再次读取期望状态,循环往复。这个过程在 Kubernetes 源码里叫reconcile(调协),负责执行这个逻辑的函数叫reconcileFunc。

2.2 调协(Reconcile)到底在调什么

很多初学者会把“调协”理解成“同步”,这个理解方向是对的,但其实不够精确。调协不是把两个状态强行拉齐就完事了,它更强调面向终态的条件满足。

举个例子。你有一个数据库集群的 CR 对象(后面会讲什么是 CR),期望状态是“3 个数据节点,开启同步复制”。调协逻辑要做的不是简单地创建 3 个 Pod,而是要检查很多事情:

  • 当前有没有 3 个 Pod?不够的话需要创建。
  • 这 3 个 Pod 里的数据库进程是否健康?不健康的需要重启或替换。
  • 节点之间的同步复制配置是否正确?配置不对的需要更新。
  • 节点的数据是否处于一致状态?不一致的可能需要触发全量重建。
  • 服务发现入口是否指向了正确的节点列表?指向错了需要更新 Service 的 endpoints。

每一项检查都是一次“期望 vs 实际”的比对,每一项变更动作都可能触发后续的连锁反应。所以调协逻辑本质上是一个决策函数:输入一个资源对象的当前状态,输出一组让状态向期望推进的动作。

注意:调协函数不是一次执行完就结束。它执行完之后,控制器会再次收到事件,再次触发调协。哪怕上一次调协已经完美完成,如果集群什么变化都没有,控制器也会按照一定周期进行重新调协。这个“即使没变化也要定期检查”的机制,是为了防止外部因素(比如网络分区、人为改动)导致实际状态悄悄偏离期望状态。

2.3 为什么说 Kubernetes 里“一切皆控制器”

如果仔细观察 Kubernetes 的架构,会发现它内部几乎所有功能都是围绕控制循环搭建的。Deployment 控制器负责副本管理,EndpointSlice 控制器负责维护服务端点,节点控制器负责节点生命周期管理,调度器本质上也是一个特殊的控制器,它的调协目标是“为待调度的 Pod 找到合适的节点”。

这个设计思路对 Operator 有直接影响:你写的 operator 不需要发明新架构,只需要遵循同一套模式。Kubernetes 提供了一种统一的方式让你描述期望状态、感知变化、执行变更。这也是为什么你会觉得 Operator 的概念和原生控制器如此相似——因为他们的底层逻辑本来就是同一个东西。

当你理解了控制循环,再看 kubebuilder 生成的代码结构,就会明白它为什么要你去实现一个 Reconcile 方法。这个方法就是你的调协函数,是 operator 的核心中枢。

3. CRD 与自定义资源:让 API Server 认识你的业务对象

3.1 自定义资源(CR):把业务对象变成集群“一等公民”

控制循环需要有输入对象。对于 Pod、Deployment 这些内置资源,Kubernetes 已经帮你定义好了。但你要管理的是数据库集群、缓存集群这类业务对象,Kubernetes 并不认识它们,所以你需要通过CustomResourceDefinition(CRD)把这些业务对象注册进集群。

注册了 CRD 之后,你就可以像使用原生资源一样使用自定义资源。你可以 kubectl create 一个数据库集群对象,可以 kubectl get 查看它们,可以 kubectl patch 修改它们。这个自定义资源对象从一个普通的 YAML 文件,变成了一个被 API Server 管理、具备 schema 校验、支持访问控制的对象,这就是“一等公民”的含义。

日常开发中,我们一般把“CRD”理解成一组类型定义,把“CR”理解成按照这个类型定义创建的实例对象。CRD 定义了一张数据库表结构,CR 就是按表结构插入的一行数据。

3.2 Schema 定义:告诉 API Server 你的对象长什么样

CRD 的核心内容是 OpenAPI Schema。它声明了业务对象包含哪些字段、字段类型是什么、哪些字段必填、哪些字段允许为空、哪些字段之间有依赖关系。API Server 会拿着这份 schema 对所有请求做校验,格式不对根本写不进 etcd。

我拿一个简化版的数据库集群 CRD 举例,它的核心字段大概会长这样:

apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: databaseclusters.example.io spec: group: example.io names: kind: DatabaseCluster plural: databaseclusters singular: databasecluster shortNames: - dbc scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: replicas: type: integer minimum: 1 version: type: string storageSize: type: string status: type: object properties: readyNodes: type: integer phase: type: string

这里有几个细节值得展开。group和version共同决定了这个资源的 API 路径,比如上面这个 CRD 的 API 路径就是example.io/v1。kind是你在 YAML 里写的类型名,plural是 kubectl 命令行里用的资源名,shortNames是给 kubectl 用的简短别名。

scope字段也很重要,它决定资源是集群级别还是命名空间级别。数据库集群这类资源通常定义成 Namespaced,因为不同团队不同项目可能有各自的数据库集群,用命名空间隔离更自然。

Status 子资源需要单独说明。Kubernetes 的设计规范里,spec 是用户声明的期望状态,status 是控制器回写的实际状态。通常控制器没有权限修改 spec(除非用户主动变更),但可以自由更新 status。这个隔离保证了“用户声明”和“系统反馈”两个通道互不干扰。在 kubebuilder 的脚手架代码中,你可以看到它用+kubebuilder:subresource:status标记来启用 Status 子资源。

3.3 从 CRD 到客户端:kubebuilder 的脚手架价值

手写一个完整的 CRD 文件不是不行,但等你需要维护多个版本、处理字段默认值、加校验规则、写转换逻辑的时候,纯手工管理很快就失控了。而且 CRD 只是第一步,你还需要一组强类型的客户端代码去读写这些自定义资源。

kubebuilder 在这一步提供了巨大的价值。它基于 Go 的反射机制和类型定义,自动生成 CRD 的 YAML 清单和对应的客户端代码。开发者只需要定义一个 Go struct,标上 kubebuilder 的 marker 注释,比如+kubebuilder:validation:Minimum=1,kubebuilder 就能生成带校验规则的 CRD 定义。

这套流程我实测下来非常顺手。定义好类型之后执行make manifests,CRD 文件自动生成;执行make generate,DeepCopy 方法自动补齐。你几乎不需要手写样板代码,可以把精力全部放在调协逻辑上。但反过来说,如果不懂底层的 CRD 机制,遇到生成结果不符合预期的情况,你都不知道从何排查。所以这一节的内容虽然偏理论,但它在实操中真的能救命。

4. 事件驱动:控制器是如何感知资源变化的

4.1 Informer 与 Watch:不用轮询就能感知变化

控制器要持续调协,就必须知道业务对象什么时候发生了变化。最简单的方案是每隔几秒轮询一次 API Server,拿到全量数据再做比对。但在一个几百个节点的集群里,这种方式对 API Server 的压力非常大,而且大部分查询结果根本没变化,纯属浪费。

Kubernetes 采用的方案是Watch + Informer机制。控制器通过 API Server 建立一个长连接(Watch),当有资源被创建、更新、删除时,API Server 会主动推送事件给控制器。控制器这边用 Informer 接收这些事件,在本地维护一份内存缓存,从而既拿到了最新数据,又避免了对 API Server 的频繁查询。

这就像你关注了一个公众号,有新文章时会收到推送通知,而不是每隔一分钟就去刷新一次公众号首页。对于控制器这种需要实时响应资源变化的组件,这种推送模式大大降低了系统的整体负载。

4.2 事件如何进入工作队列

Watch 事件到达控制器后,需要经过一层处理才能触发调协逻辑。这个处理过程涉及三个核心组件:Informer、EventHandler、Workqueue。

Informer 收到事件后,会先将资源对象更新到本地缓存(Indexer),然后调用你注册的 EventHandler。EventHandler 的工作是决定“这个事件应该让哪个对象的调协逻辑被触发”。通常映射关系很简单:一个 Kind 的资源对象发生变更,就触发它对应的 Reconcile。

事件到达之后不会立即执行调协,而是先进入一个工作队列(Workqueue)。这个队列是控制器的交通调度中心。它有几个关键特性:

  • 去重:队列中同一个对象的多个未处理事件会被合并。比如一个 Deployment 在 1 秒内连续发生 3 次更新,队列里只保留一条记录,调协函数只执行一次。
  • 延迟:可以配置事件入队后的等待时间,避免频繁变化导致调协风暴。
  • 失败重试:调协失败后,事件不会丢弃,而是按退避策略重新入队。

这里有一个很多初学者踩过的坑:以为 Watch 事件一到就会立刻调用 Reconcile,但事实上中间隔着工作队列。理解这个模型对调试非常关键。有一次我在本地测试时发现资源更新后调协函数迟迟不执行,折腾了半天才发现是队列的退避参数被我调得太长,事件被卡在队列里等待。

4.3 为什么 Predicate(过滤事件)这么重要

默认情况下,任何变更都会触发调协,但并非所有变更都值得调协。如果你的调协逻辑只关心 spec 字段的变化,那么 status 字段每次被控制器自己更新都会再次触发调协,这会造成循环触发。

Kubebuilder 里用Predicate(事件过滤器)来解决这个问题。最常用的两个内置 Predicate 是:

  • GenerationChangedPredicate:只有对象的 Generation 变化时才触发调协。
  • ResourceVersionChangedPredicate:只要资源版本变化就触发。

Generation 是什么呢?它表示用户对该对象的 spec 进行修改的次数。spec 每变更一次,Generation 就递增一次。Status 的更新不会影响 Generation。所以使用GenerationChangedPredicate可以实现一个非常理想的效果:只有用户改了期望状态才触发调协,控制器自己回写 status 不会引发调协风暴。

这个细节单独看不算复杂,但在生产环境中,如果一个 operator 没有做好事件过滤,很可能出现“调协逻辑更新了 status,status 变化触发调协,调协又更新 status”的死循环。我见过有团队在集群负载异常时排查了半天,最后发现罪魁祸首就是循环触发。

4.4 调协失败的重试机制与退避策略

调协不是每次都能成功。比如创建数据库 Pod 时镜像拉取失败、更新 Service 时端口冲突、调外部系统接口超时,这些都是真实场景里常见的问题。Kubernetes 的 workqueue 提供了内置的重试机制,你完全不需要自己实现恢复逻辑。

默认的退避策略大致是这样的:第一次失败后等待一段时间,第二次失败后等待更长时间,上限可以配置。例如第一次失败 5 秒后重试,第二次 10 秒,第三次 20 秒,呈指数增长,上限比如 1000 秒。同时还要设置一个失败次数的最大值,超过之后事件会被丢弃,避免一个永远无法成功的调协不停消耗资源。

本质上这套机制遵循的是“有界指数退避”策略。相比每次失败都立刻重试,它给了系统更多自愈时间。比如数据库节点在做主从切换时,新节点可能需要几十秒才能完成数据追平,立即重试只会反复失败,延迟重试反而能让它等到合适时机。

5. 调协逻辑背后:幂等、状态更新与 Finalizer

5.1 从“现状发现”到“期望对齐”的代码路径

当 Reconcile 函数被触发,它会收到一个请求对象,里面包含你要调协的资源名称和命名空间。接下来调协逻辑一般会走这样的路径:

  1. 通过 client 获取目标 CR 对象。
  2. 如果对象不存在,说明被删除了,执行清理逻辑后直接返回。
  3. 读取 CR 的 spec,结合集群当前实际状态,计算出需要执行的操作列表。
  4. 依次执行操作,比如创建/更新子资源、修改配置、调用外部 API。
  5. 将操作结果更新到 CR 的 status 字段。
  6. 返回结果,告诉 workqueue 这次调协成功还是失败。

这个过程看起来简单,但有两条铁律必须遵守。第一,调协函数必须是幂等的;第二,调协函数绝对不能修改自己的输入对象。

5.2 幂等性:为什么调协函数必须可以反复执行

调协函数天然会被反复执行。资源变更会触发调协、重试会触发调协、周期性的重新调协也会触发它。如果一个调协函数执行一次会创建 3 个 Pod,执行两次变成 6 个,执行三次变成 9 个,那这个 operator 就是灾难。

正确的做法是,调协函数只负责“对齐差异”,而不是无脑创建。每次执行时先查当前已有的 Pod 数量,如果已经有 3 个就什么都不做;只有发现数量不足时才补上缺失的。这个模式在编程里有个熟悉的名字,叫“幂等”,同一个操作执行一次和执行一百次,产生的结果一致。

我在实际开发中养成了一个习惯:调协函数的每个分支都以“当前状态是否需要变更”作为判断前提,而不是以“是否存在某个对象”作为前提。比如创建 Service 的逻辑,不是“创建一个 Service”,而是“如果不存在 Service 就创建,如果存在就对比字段是否需要更新”。这样即使调协被意外触发一百次,也不会产生任何破坏性副作用。

5.3 Status 子资源与 ObservedGeneration

调协完成后,你通常需要把执行结果写入 status。这里有一个很常见的设计问题:控制器怎么知道自己的调协结果是否过期了?

举个例子。用户在 t1 时刻创建一个数据库集群对象,期望 3 个节点。控制器开始调协,花了 30 秒创建 Pod。在 t2 时刻用户把期望改成 5 个节点。此时如果控制器某个子流程还拿着 t1 时刻缓存的数据在处理,并且把 status 更新成了“readyNodes: 3”,那么这个状态其实是过期状态,并不反映用户最新期望。

Kubernetes 的解决方式是ObservedGeneration字段。规范要求控制器在写完 status 后,把当前 CR 的generation值拷贝到status.observedGeneration。下游用户或上层系统读取这个字段时,就可以判断:如果 observedGeneration 小于 metadata.generation,说明这个 status 是基于旧 spec 计算出来的,不能信任。

我自己的经验是,在资源的 status 里一定要加observedGeneration,并且每次调协结束后同步更新。这个字段本身不复杂,但很多自研 operator 会忽略它,导致用户在排查问题时资源状态看起来正常,实际逻辑早已不匹配。

5.4 Finalizer:删除前的“善后”逻辑

Operator 管理的资源通常关联着真实业务系统。删除一个数据库集群对象时,你不能只是把 API Server 里的对象删掉就完事。底层数据卷可能还需要清理,外部 DNS 记录可能需要删除,监控告警可能需要注销。如果这些善后工作没做完,直接删除对象,很多资源就会变成无人管理的孤儿。

Finalizer(终结器)就是为这个场景设计的机制。你在 CRD 里给资源注册一个 Finalizer 字符串,比如databasecluster.example.io/finalizer。当用户发起删除请求时,如果资源上存在 Finalizer,API Server 不会立即删除对象,而是把对象的deletionTimestamp字段打上时间戳,让它进入“待删除”状态。

控制器的 Reconcile 在每次执行时会检查:如果发现deletionTimestamp存在,说明资源正在被删除。此时它执行善后逻辑,比如清理底层数据卷,然后从对象上移除 Finalizer。Finalizer 被移除后,API Server 才会真正把对象从 etcd 里删掉。

这套机制保证了删除操作也是幂等的:即使控制器在处理删除时崩溃重启,重启后再次调用协,检查到 deletionTimestamp 依然存在,重新执行善后逻辑即可。直到善后逻辑真正完成,Finalizer 被移除,资源才会消失。

这里的常见坑有两个:一是 Finalizer 忘记移除,导致资源一直卡在 Terminating 状态无法删除;二是在没有做完善后逻辑时就执行了移除。第一条会阻塞用户的删除操作,第二条会造成数据残留。两种问题我都见过,调试时可以先kubectl get 资源 -o yaml观察 deletionTimestamp 和 finalizers 字段。

6. kubebuilder 在整套机制中的位置:从原理到脚手架

6.1 手写控制器与使用 kubebuilder 的差异

很多人以为 kubebuilder 是 Operator 的运行时框架,其实不太准确。准确地说,kubebuilder 是一个围绕controller-runtime库构建的脚手架工具,它负责生成项目骨架、CRD 定义、Controller 代码模板、Webhook 模板、RBAC 清单,而真正支撑控制器运行的是 controller-runtime 这个底层库。

如果你选择纯手写控制器,你需要自己处理很多基础设施逻辑:写 Informer、写 Workqueue、管理事件处理循环、处理 Leader 选举、处理指标暴露。这些逻辑每个 operator 都大同小异,但手写一遍非常耗时,而且容易出现隐蔽的 bug。

kubebuilder 的价值在于把这些标准化逻辑全部封装好,你只需要关注自定义的部分:定义你的 CRD 类型,实现你的 Reconcile 方法,配置需要监听哪些资源。生成的项目已经内置了健康检查、指标端点、Leader 选举这些生产环境必备的能力,编译之后可以直接部署到集群里。

我用过的真实感受是:kubebuilder 项目生成的那一瞬间,你会觉得“这就是个空壳子”,但等你要加各种生产能力时,会发现脚手架已经帮你铺好了一切。从“空壳子”到“能跑的 operator”,你需要填的就是核心调协逻辑这一块。

6.2 controller-runtime 的核心抽象

理解 controller-runtime 的几个核心接口,对读懂 kubebuilder 生成的代码至关重要。

Manager是顶层概念,它管理着整个控制器的生命周期:启动时创建 client 和 cache,配置 scheme 和权限,管理多个 Controller 的并发运行。你在 kubebuilder 生成的main.go里会看到ctrl.NewManager的调用。

Cache负责内置的 Informer 缓存。Manager 启动时会为所有注册的 Kind 建立 Informer,并且提供从缓存读取对象的接口。需要注意,缓存数据有一定延迟,如果调协逻辑强依赖最新数据,可能需要由 API Server 实时读取。

Client是对 API Server 操作的封装,支持创建、更新、删除、查询资源。kubebuilder 默认提供的是将缓存读和直接写结合的模式:读操作从缓存走,写操作直连 API Server。

Controller负责编排整个控制循环:注册 EventHandler、管理 Workqueue、调用你的 Reconcile 方法。你在生成代码里看到的For、Owns、Watches这三个方法,就是告诉 Controller 要监听哪些资源的哪些事件。

这三个方法的区别值得多说一句。For(&MyKind{})表示监听主资源本身的变化;Owns(&corev1.Pod{})表示监听主资源创建的子资源(Pod 等),并且事件会关联回主资源;Watches()表示监听你指定的任意资源,并且通过自定义 handler 决定触发哪个主资源的调协。用Owns是最科学的:当子资源状态变化时,调协逻辑被触发,从而检查是否需要修补子资源。

6.3 一个完整的调协流程串讲

现在把所有原理串起来,完整走一遍从“用户提交一段 YAML”到“业务集群就绪”的流程。

假设你已经在集群里部署了一个数据库 operator。

用户在终端执行:

kubectl apply -f databasecluster.yaml

这个 YAML 的内容是创建DatabaseCluster资源,期望 3 个节点。

第一步,请求先到 API Server。API Server 校验 YAML 是否符合 CRD 定义的 OpenAPI Schema。字段不对就拒绝,校验通过则把对象写进 etcd。此时集群里多了这个 CR 对象,但还没有任何实际业务进程在跑。

第二步,CRD 的 Watch 事件被 Informer 捕获。这个 Informer 是 operator 在 Manager 启动时注册的,它一直监听databaseclusters.example.io类型的事件。事件被送入 EventHandler,映射到对应的 Reconcile 请求,然后进入 Workqueue。

第三步,operator 的 Reconcile 函数被触发。它先从 cache 里读取DatabaseCluster对象,拿到用户声明的期望状态:3 个节点、某个数据库版本、某个数据卷大小。

第四步,Reconcile 查询当前集群中是否存在这 3 个节点对应的 Pod。假设一个都没有,它开始创建第一个节点:创建 ConfigMap 作为配置文件、创建 PVC 作为数据卷、创建 StatefulSet 来管理 Pod。StatefulSet 创建后由 Kubernetes 内置的 StatefulSet 控制器实际创建 Pod。

第五步,第一个节点创建完成,数据库进程启动。Reconcile 检查连接状态,确认节点可访问,然后创建第二个节点,并配置它与第一个节点建立同步关系。这个过程可能需要多次调协才能完成,因为每个节点从 Pod 启动到数据库进程就绪,都需要时间。

第六步,所有节点就绪后,Reconcile 创建 Service 作为访问入口。如果涉及客户端感知主从结构,可能还要更新自定义 Endpoint 或 DNS 记录。

第七步,Reconcile 把结果写回 status:节点数量、就绪节点数、当前阶段(比如 Running)。同时更新observedGeneration为当前 generation。用户执行kubectl get databasecluster时,就能看到状态信息。

第八步,如果此后用户修改 YAML 把节点数从 3 改成 5,spec 变化导致 generation 增加,Watch 事件再次触发调协。Reconcile 对比发现实际节点数不足,开始补齐第 4、5 个节点。

第九步,如果用户删除 YAML,对象进入 Terminating 状态。Reconcile 检测到 deletionTimestamp,执行善后逻辑:逐节点下线、移除同步关系、清理数据卷,最后移除 Finalizer。对象从 etcd 中消失。

这个流程里,Kubernetes 的控制循环、CRD 机制、Watch 事件、调协逻辑、Finalizer 机制全部串联起来了,而且每一步的触发都是事件驱动、幂等安全的。

6.4 学习路径建议:搞清楚原理再动手

最后说说学习建议。我见过不少开发者直接打开 kubebuilder 文档,跟着教程生成一个项目,然后把示例代码跑通就以为学会了。这种做法能获得一种“我学会了”的错觉,但遇到一个超出示例范围的需求就会卡住。

真正值得花时间的路径是这样的。先深入理解控制循环和声明式 API,这是 Operator 的地基。再搞清楚 CRD 的 schema 设计、版本管理、字段校验规则,这是你业务对象的核心。然后专门研究 controller-runtime 的事件模型和队列机制,这是控制器的临界区。最后才是打开 kubebuilder 写代码,边写边对照前面理解的概念。

我个人还有一个技巧:在阅读 kubebuilder 生成的代码时,每看到一个接口就追一层源码,比如ctrl.NewManager里发生了什么、For方法的实现是如何绑定 EventHandler 的。第一次追源码会很吃力,但追完两三次,整个 operator 的工作机制就会像一张地图一样在脑海里铺开。

下一篇我会继续沿着这个系列往下走,聚焦在 CRD 设计的具体细节上,包括多版本管理、字段校验、默认值注入、CEL 校验规则这些内容。这些是写 operator 时最容易踩坑的部分,也是生产环境里最能体现设计功力的地方。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 5:03:25

同城搬家货运系统实战:Spring Boot订单与支付核心源码解析

做Java后端这几年,我陆续带过不少新人,也看过很多开源项目,但真正让我觉得“拿来就能跑、跑起来能讲、讲完能自己改”的实战项目其实不多。同城搬家货运系统算是其中一个典型——业务链路长、涉及角色多、并发场景真实,既有地图定…

作者头像 李华
网站建设 2026/10/21 5:02:19

光伏功率短期预测实战:LSTM、TCN、CNN-LSTM与TCN-LSTM模型对比

做光伏发电短期功率预测这个项目时,我最深的感受是:模型从来不是天花板,数据才是。这个项目最终对比了LSTM、TCN、CNN-LSTM、TCN-LSTM四种模型,从数据清洗、特征工程、模型搭建到部署推理完整跑了一遍。如果你正准备做时间序列预测…

作者头像 李华
网站建设 2026/10/21 5:01:50

AI进度管控实战:用数据感知消灭项目延期

项目延期这件事,几乎每个做过项目的人都被折磨过。明明排期的时候大家都拍着胸脯说没问题,可一到关键节点,不是“遇到一个技术难点”,就是“需求又变了一点”,再问下去就是含糊其辞的“快了快了”。我过去带项目也是这…

作者头像 李华
网站建设 2026/10/21 5:01:25

知识工作插件实战:从剪藏到复利的工作流与选型指南

1. 知识工作插件到底在解决什么问题做知识管理这事儿,绕不开一个尴尬的真相:工具折腾得越多,真正留下的知识越少。我见过不少朋友,电脑里装了各种笔记软件,书签收藏夹塞了几千条链接,本地硬盘散落着几十个版…

作者头像 李华
网站建设 2026/10/21 4:59:34

hashcat断点续跑与自动备份:restore/potfile机制实战指南

1. 把默认机制吃透:restore 文件与 potfile 到底在干什么先说一个很多人会忽略的事实:hashcat 不是没有自动保存,而是它默认已经有一套“进度保存”机制,只是大多数人从来没去了解过这套机制的工作原理,等到进度丢了才…

作者头像 李华
网站建设 2026/10/21 4:59:19

智能体Skills技能化:从概念到落地全解析

你们有没有发现,现在聊AI应用,几乎绕不开一个词:skills。很多人在群里问,到底什么是skills、怎么让自己的智能体能会干活、能干对活。这个问题的答案,往往就藏在技能设计上。所谓skills,不是简历上写的技能,而是给模型准备的一套可复用的能力模块:查数据、发通知、处理文件、调…

作者头像 李华