想象你住在一个云端智慧小区,物业中心备有一整套标准登记表:住户登记表、公寓入住表、访客通行证。这些表对应 Kubernetes 里内建的资源类型,比如 Pod、Deployment。某天,小区计划新增一个「AI 模型游乐园」,标准登记表里根本没有这一项,于是物业中心设计了一张全新的「宠物乐园登记表」——这就是 CRD(自定义资源定义)。但光有登记表还不够,游乐园建好后还需要一位不知疲倦的「专属管家」来检查设施、清理场地、调整设备,让现实状态始终贴合表格上写的「理想状态」。这位管家就是 Operator。本文会用这个「小区物业」的比喻,带你从零看懂 CRD 与 Operator 是什么、如何工作,以及它们为什么能让 AI 模型服务真正实现自动化运维。
对 AI 从业者来说,掌握 CRD 与 Operator,意味着你能把模型训练、推理服务、自动伸缩这些领域知识写成 Kubernetes 原生自动化能力,成为云原生 AI 基础设施的「设计师」而非单纯的使用者。
目录
可视化演示
展示Kubernetes自定义资源定义CRD与Operator模式:API扩展机制、控制循环Reconcile、Leader选举高可用及AI模型服务自动化实践。切换模式查看不同环节。
本节配有交互动效(进入视口后自动加载,约 3KB)。
CRD定义与API扩展 CRD声明自定义资源类型,扩展Kubernetes API Server Custom Resource通过kubectl管理,与原生资源体验一致 CRD → API → CR (instance) CRD将领域模型注入K8s生态
CRD 概念入门
物业中心的文件柜里,已经有「住户登记表」「公寓入住表」这些标准表。忽然有一天,小区要开设「AI 模型游乐园」,但现有表格里没有任何一类能描述「游乐园想养几只模型、需要多少显卡、对外服务端口是多少」。于是物业中心提交了一份崭新的「宠物乐园登记表」给总管理处(Kubernetes API Server),申请把这个新表纳入官方表格体系。这张新表,就是你向 Kubernetes 申请的自定义资源定义 CRD。
在 Kubernetes 集群中,Pod、Deployment、Service 是内建资源;CRD 是让你自己发明新资源类型的方法。开发者提交一份 YAML 配置,声明「我想要一个模型服务,两个副本,模型名 bert」,这份配置被称为自定义资源对象。Kubernetes 并不会真的帮你拉起模型,它只是把你的「心愿单」存下来——真正的执行者,是后续会出现的 Operator。
自定义资源本质上就是「类型 + 声明」的组合:类型告诉系统这是什么,声明告诉系统你希望它最终变成什么样。
回到小区比喻:你递给物业的「宠物乐园登记表」,只是让系统知道世上存在这种表、也知道你想要的设施清单。可如果没有管家来按表施工和照看,这张表就永远只是一纸空文。
- Kind(种类)定义这份登记表的名字,例如
ModelService。 - Group(组)给这一类资源分组,避免不同领域的资源重名,例如
ai.example.com。 - Version(版本)表示登记表的版本号,例如
v1,方便未来演进字段。 - Spec(期望规格)用户填写的心愿清单,描述模型的副本数、镜像、端口等。
API 定义规范
拿到新表之前,物业中心需要先规定表格的「版式」:哪些格子必须填、填数字还是填文本、可不可以留空。在 Kubernetes 中,这份「版式」就是 CRD 的 Schema,它用 OpenAPI 描述自定义资源的字段结构。
定义 Schema 时,最核心的是 spec 字段的结构。比如 replicas 的类型是整数,范围是 1 到 100;model 是字符串且不能为空;port 是 1 到 65535 的整数。这样当用户提交 YAML 时,API Server 可以直接校验格式,不合法就拒绝存储。就像物业管理员一眼看出「登记表里年龄填了 -5」,马上打回。
除了校验,Schema 还能设置默认值。如果用户没写 replicas,系统自动填入 1;如果没写 strategy,默认使用 RollingUpdate。这些规则让用户写配置时更轻松,也让资源行为更可控。
表格版式定好了,剩下的问题就是:谁来照着这份版式,把小区的宠物乐园真正建起来并维护好?这个答案,正是后面的「协调循环」。
- properties(字段集合)列出哪些格子可以填以及各自类型。
- required(必填项)规定哪些字段不能省略,例如
model。 - default(默认值)用户没填时系统自动补上的值。
协调循环原理
管家怎么知道该干活呢?他不会等业主来吆喝,而是每隔一会儿就在小区里转一圈,对照「登记表上的理想样子」和「游乐园的当前样子」:要是垃圾桶满了就清空,要是设施坏了就报修,要是宠物少了就补领。这种「看两眼、改一改、再看两眼」的循环,在 Kubernetes 里叫协调循环。
在 Operator 中,核心逻辑被封装进一个函数:Reconcile。当自定义资源被创建、更新、删除,或底层资源状态发生变化时,事件会触发这个函数。它读取资源当前的 Spec 和 Status,算出期望与现实的差距,然后执行创建 Pod、更新 Deployment、滚动升级等操作,操作完成后再次观察。
如果把协调循环写成直觉公式,就是:期望状态减去实际状态等于误差,当误差不为零时,控制器就该行动了。
管家不会因为打扫过一次就永远收工,只要小区状态发生变化,他都会再次出现在宠物乐园里检查。这种永不满足的「纠偏」机制,正是 Operator 能持续保证系统处于期望状态的原因。
- Desired(期望状态)来自 CRD 对象的
spec,即用户声明的心愿。 - Actual(实际状态)从集群中观察到的事实,比如当前 Pod 数量、镜像版本。
- Δ(差距)期望与实际的差,决定是否需要动作。
- 动作策略Δ 不为 0 时,调整资源让 Δ 趋近于 0。
Operator 设计模式
如果说 CRD 是登记表,那么 Operator 就是负责照单维护的专属管家。它的设计模式很简单:用 CRD 定义资源的「期望结构」,再用一个控制器监听这类资源的生命周期,按你写好的运维逻辑自动执行。传统运维靠人盯着监控、手动扩容、手动回滚;Operator 把人的经验固化成代码,让机器自己盯着。
比如一个 AI 模型的线上服务,过去需要运维同学半夜盯告警、手动拉起新 Pod。有了 Operator,Controller 会定期检查模型服务副本数、GPU 利用率、模型版本是否与 Spec 一致。不一致就自动调整,完全不需要人肉介入。
所以,Operator 不是一个神秘的黑盒,它是「控制器 + 自定义资源 + 领域知识」的组合,把一个垂直领域的运维自动化变成现实。
有了这位专属管家,小区管理者不再需要为每一件小事操心。但要让管家在真实小区里可靠上岗,我们还需要一套标准工具来「雇佣」他——这就是 controller-runtime 框架。
- Controller(控制器)持续 watch 自定义资源并触发 Reconcile 的执行框架。
- CRD(自定义资源)为管家定义工作的「对象清单」。
- Domain Knowledge(领域知识)把「如何扩容」「如何回滚」等判断规则写入代码。
controller-runtime 实践
自己从零搭建一个 Kubernetes 控制器,首先要写 API 客户端、事件监听、缓存、重试队列,工作量巨大。于是社区提供了 controller-runtime 库,相当于给所有 Operator 管家提供了一套标准培训体系和工具箱。你只需要关心 Reconcile 函数里写什么业务逻辑,其他与 K8s 交互的复杂度由框架接管。
在这个框架里,Manager 是总调度中心:它负责启动所有控制器、维护缓存、提供客户端。Builder 则像招聘流程:指定 watch 哪种资源,再把事件交给哪个 Reconcile 方法。开发者通常用 ctrl.NewControllerManagedBy(mgr) 在几行代码内完成「监听 ModelService 资源,并调用 Reconcile 处理」的装配。
Reconcile 函数本身是一个纯逻辑函数,它接收一个资源名字(NamespacedName),返回处理结果。框架负责把事件变成调用,把重试和排队从业务逻辑中剥离出去。
有了统一工具箱,Operator 的开发效率大幅提升。不过,当很多小区业主同时依赖这位管家时,还要解决「多个管家不能重复打扫」的问题。
- NamespacedName(资源定位)标识一个资源所在的命名空间和名字。
- Reconcile 方法根据名字读取资源并执行协调逻辑。
- Result(返回结果)告诉框架本次是否成功、何时重试。
高可用与状态管理
如果把 Operator 部署多份,就能避免单点故障。但如果两个管家同时发现垃圾桶满了,双双伸手去清,反而可能互相干扰。Kubernetes 给出的方案叫 Leader Election(领导者选举):所有副本中只选出一个「值班管家」,其余作为候补,只有在 Leader 挂掉时才顶上。
controller-runtime 内置了 Leader Election 机制。每个 Operator 副本启动时会争夺一个 lease 锁,获得锁的副本成为 Leader,真正执行 Reconcile;其他副本保持待命。这就像小区里虽然雇了三个管家,但只有戴着值班牌的那位可以动工具,其他人只观察、不干预。
另一个关键机制是 Status 子资源。用户定义的 spec 是「心愿」,控制器把实际运行情况写进 status,比如当前副本数、最近一次成功更新时间、可用的模型版本。Kubernetes 会保护 status 字段,普通用户不能随意篡改,只有控制器有权限写入,确保状态信息来源可信。Leader 数量 N 与配额 Q 的关系:
多个副本安全值班,状态又能被可信记录后,Operator 终于可以放心上岗。最后,我们来看看它是如何被用在 AI 模型服务这种真实的高价值场景中的。
- N(副本总数)参与选举的 Operator 实例数量。
- Q(法定票数)成为 Leader 所需的最小票数。
- 选举条件只有得票数超过半数,才进入值班状态。
AI 模型服务实战
在 AI 平台中,模型从训练到上线、再到版本更新和回滚,链路非常长。传统方式是平台工程师写一堆运维脚本,再配一个值班机器人;但这种方式难以应对复杂状态变化。用 CRD 和 Operator,我们可以为「模型服务」量身定制一套自动化的生命周期管理系统。
比如定义一个 ModelService 自定义资源:用户提交 YAML,声明模型名、需要的 GPU 数量、期望副本数和版本策略。ModelService Operator 收到后,会自动创建底层 Deployment、Service、HorizontalPodAutoscaler,并把推理服务的实时状态写回 status。当模型流量突增时,Operator 还会根据等待队列长度自动扩缩容。
扩缩容可以抽象为一道简单计算题:假设当前请求负载为 L,单副本能承受的阈值为 T,那么需要的副本数 R 至少是 L/T 向上取整,并且不能小于 1。
回到「AI 模型游乐园」的比喻:ModelService CRD 是那张「游乐园经营申请书」,而 ModelService Operator 就是那个懂模型、懂 GPU、懂扩容缩容的超级管家。从今往后,你只需要在申请书上写清楚「我要多大的游乐园」,剩下的脏活累活,管家全部搞定。
- L(当前负载)观测到的每秒请求数或排队任务数。
- T(单副本阈值)每个副本能承载的最大负载上限。
- R(目标副本数)需要的副本数量。
AI 应用场景
AI 模型服务的全自动发布与扩容
阶段:模型部署与运维
解决的问题:传统方式需要运维人员盯着监控,手动创建 Deployment、Service,流量突增时手动扩容,非常容易出错。通过 ModelService 自定义资源和 Operator,平台可以把「部署、扩容、回滚」固化成代码,自动完成。
具体案例:某算法团队上线一个 BERT 问答服务。他们在 YAML 里声明 replicas: 2、model: bert-base,提交后 Operator 自动创建底层资源;当压测流量达到单副本阈值的 80% 时,Operator 自动扩容到 5 个副本,并在流量回落后缩容。
模型版本灰度发布与自动回滚
阶段:模型版本管理
解决的问题:新版本模型可能效果变差或推理延迟飙升,人工回滚耗时且容易遗漏。Operator 可以监控新版 Pod 的启动成功率与延迟指标,一旦低于阈值,自动回滚到上一个稳定版本。
具体案例:某推荐系统发布 v2 模型,Operator 按 10% 流量灰度验证,发现错误率超过 5% 后自动把流量切回 v1,同时将 ModelService 的 status 标记为 RolledBack,方便研发排查。
多集群统一模型调度
阶段:多云与多集群模型分发
解决的问题:大公司跨地域部署多个 K8s 集群,每个集群的模型版本和资源状况难以统一。通过 CRD 定义跨集群的 ModelService,Operator 可以按集群负载自动分发模型。
具体案例:某语音识别团队使用 CustomResource 描述「模型需要华东区 4 张 A100、华南区 2 张 A100」,Operator 自动在多个集群间协调资源,保证服务就近接入。
Python 演示
class ModelServiceOperator: def __init__(self, model="bert", replicas=1): self.model = model self.replica_count = replicas self.generation_count = 0 def fetch_actual(self): return { "replicas": self.replica_count, "generation": self.generation_count } def apply_change(self, target_replicas): self.replica_count = target_replicas self.generation_count += 1 def reconcile(self, spec): desired = spec.get("replicas", 1) actual = self.fetch_actual() if actual["replicas"] == desired: return ("no-op", actual) old = actual["replicas"] self.apply_change(desired) new_state = self.fetch_actual() return ("scaled", { "from": old, "to": new_state["replicas"], "generation": new_state["generation"] }) def main(): operator = ModelServiceOperator(model="bert-base", replicas=2) specs = [ {"name": "bert-qa", "replicas": 3}, {"name": "gpt-lite", "replicas": 1}, {"name": "bert-qa", "replicas": 3}, ] for spec in specs: action, detail = operator.reconcile(spec) print("%s %s -> %s" % (spec["name"], action, detail)) if __name__ == "__main__": main()
class ModelServiceOperator:对应一个自定义资源的专属控制器,就像宠物乐园的专属管家。def fetch_actual(self)读取当前实际状态,对应控制器从 K8s 集群查询真实资源。def reconcile(self, spec)核心协调函数,比较 spec 中的期望值与 observed 实际值。if actual["replicas"] == desired:差距为零时无需动作,直接返回 no-op。self.apply_change(desired)差距非零时执行扩缩容,并更新 generation 记录变更代数。print("%s %s -> %s" % ...)模拟观察者看到的一次次协调结果。
常见误区
误区很多初学者认为只要 apply 一个 CRD 文件,Kubernetes 就会自动为这个新资源创建 Pod、Service 并维护运行状态。
正解CRD 只是「新登记表」的注册,它让 APIServer 认识这种资源类型、能存储和校验 schema,但不会执行任何业务逻辑。真正对资源进行创建、删除、扩容、回滚的,是监听这个 CRD 的 Operator/Controller。没有控制器,CRD 只是一张空表格。
为何常见:因为 K8s 内置资源都有对应的 controller-manager 组件自动管理,初学者容易误以为任何 CRD 也有系统内置的控制器。
误区不少人把「定义 CRD」和「开发 Operator」混为一谈,认为写完 CRD YAML 就算完成了一个 Operator。
正解CRD 定义数据模型,Operator 定义行为逻辑。一个完整的 Operator 至少包含两大部分:CRD(声明资源的 API 结构)和 Controller(对资源变化做出反应的协调逻辑)。只有 CRD 没有 Controller,相当于只有岗位说明书却没有员工来执行。
为何常见:因为很多 CRD 示例 YAML 里同时包含 Deployment 部署文件,让人以为 CRD 这个文件本身很完整,实际上还需要额外部署 Controller 镜像。
误区有人以为 Reconcile 是「一次性的回调函数」,资源状态匹配后就再也不执行了。
正解Reconcile 是持续性的协调循环,除了资源创建、更新、删除事件外,控制器还会周期性 re-list 资源,即使没有任何变更也会触发一次协调,以确保集群现状没有被外力漂移。
为何常见:因为很多教程只展示「新建资源后触发一次 reconcile」的场景,容易让人忽略 K8s 控制器的最终一致性设计本质。
误区为了高可用,有人把所有 Operator 副本都配置为活跃状态,认为这样可以提升处理速度。
正解普通无状态服务可以多副本同时服务,但 Operator 控制器在同一时刻只能有一个 Leader 执行 Reconcile,否则多个副本同时修改同一资源会导致资源冲突和重复操作。其他副本必须通过 Leader Election 进入待命状态。
为何常见:因为从小型 Website 服务的「多副本分流」直觉出发,会自然认为控制器副本也是多多益善,忽略了并发写冲突问题。
误区初学者以为 spec 和 status 都是资源对象的一部分,用户可以随时通过 kubectl edit 修改 status。
正解Kubernetes 将 CRD 的 spec 和 status 做了区分,并可以通过 subresources.status 开启 status 子资源保护。开启后,普通客户端保存资源时不会修改 status,只有控制器才有权更新 status,从而保证状态信息的可信度。
为何常见:因为一个 YAML 文件里确实可以同时看到 spec 和 status 字段,名称相似,容易让人忽略它们在 API 权限上的隔离。
面试题
Q1. CRD 在 Kubernetes 中的全称是什么?
- A.Custom Resource Definition← 正确答案
- B.Custom Runtime Design
- C.Container Resource Driver
- D.Configurable Role Definition
答案:A。CRD 全称是 Custom Resource Definition,用于定义用户自定义资源类型。其他选项都是对缩写字母的合理猜测,但并非 Kubernetes 官方术语。记住它是「资源定义」而非「运行时设计」即可。
Q2. Operator 模式的核心思想是什么?
- A.把运维经验固化成控制器代码← 正确答案
- B.用定时脚本任务代替人工巡检
- C.让运维人员手动编辑 YAML 配置
- D.通过 Webhook 修改 Kubernetes 源码
答案:A。Operator 的核心是把某个垂直领域的运维经验写成代码,由控制器自动执行。定时脚本通常只做一次性任务,而 Operator 基于事件驱动和协调循环,能持续保证期望状态,所以 A 更准确。
Q3. CRD 定义中通常包含哪些部分?
- A.API 组与版本定义← 正确答案
- B.资源字段结构定义← 正确答案
- C.Pod 调度策略配置
- D.网络负载均衡配置
答案:AB。CRD 定义的是资源的 API 元信息和 schema,包括 group、version、kind、properties 等。Pod 调度策略与网络负载均衡是部署细节,不属于 CRD 本身的定义范围,而是由 Operator 根据这些定义去创建底层资源。
Q4. 当 Reconcile 函数返回 error 时,controller-runtime 会发生什么?
- A.将请求重新入队并再次协调← 正确答案
- B.立即删除当前自定义资源
- C.停止整个控制器进程
- D.把资源状态强制改为失败
答案:A。返回 error 表示本次协调没有成功,框架会把该资源重新加入队列,并按退避策略重试。删除资源、退出进程、强制修改 status 都不是默认行为。错误重试机制保证了系统在异常情况下依然具备自愈能力。
Q5. Leader Election 在 Operator 中的主要作用是什么?
- A.确保同时只有一个活跃控制器工作← 正确答案
- B.随机删除异常 Pod 以恢复集群
- C.自动扩展 Operator 的副本数量
- D.更新 CRD 的 schema 版本
答案:A。Leader Election 用于多副本控制器选主,只有 Leader 执行 Reconcile,其余副本待命。它不是删除 Pod 或扩缩容的机制,也不负责 CRD 版本升级。多副本下没有 Leader Election 会导致多个控制器写同一资源。
Q6. 下面哪些属于 ModelService Operator 的典型职责?
- A.监听 ModelService 资源变化← 正确答案
- B.创建底层 Deployment 与 Service← 正确答案
- C.把运行状态写回 status 字段← 正确答案
- D.直接修改用户提交的 YAML 文件
答案:ABC。Operator 会 watch 自定义资源、创建管理底层资源、并更新 status 反映实际状态。它不会修改用户来源的 CR 对象定义,spec 由用户声明、status 由控制器维护,两者职责分离。
Q7. controller-runtime 中负责启动并管理多个控制器的组件是?
- A.Manager← 正确答案
- B.Webhook
- C.Informer
- D.Scheduler
答案:A。Manager 是 controller-runtime 的顶层组件,负责初始化客户端、缓存、Leader Election 以及注册和启动控制器。Webhook 用于准入校验,Informer 负责事件监听,Scheduler 负责 Pod 调度,二者都不是管理控制器的组件。
Q8. Status 子资源的用途是什么?
- A.保存控制器观测到的运行状态← 正确答案
- B.存储用户输入的模型参数
- C.定义资源的字段校验规则
- D.配置容器镜像地址
答案:A。status 保存的是实际运行状态,比如副本数、版本、条件信息,由控制器写入。用户输入参数属于 spec,校验规则属于 schema,镜像地址也是 spec 的一部分。Status 与 Spec 分离是 Operator 设计的重要原则。
Q9. 创建 CRD 后,要让自定义资源真正生效还需要做什么?
- A.部署对应的 Operator 控制器← 正确答案
- B.重启 Kubernetes 所有节点
- C.修改 kube-apiserver 源码
- D.为每个 Node 手动执行 script
答案:A。CRD 只是注册资源类型,真正为资源提供业务能力的是 Operator。部署控制器后,它才会监听资源并执行创建、更新、删除等逻辑。重启节点和修改源码显然不是正确做法。
Q10. 关于事件与 Reconcile 的关系,哪些说法是正确的?
- A.watch 到资源事件后会把请求入队← 正确答案
- B.Reconcile 函数不需要读取实际状态
- C.所有待处理事件共享一个工作队列← 正确答案
- D.状态变化后不需要重新触发协调
答案:AC。controller-runtime 通过 watch 监听资源事件,事件被放入共享工作队列,然后由 Reconcile 消费处理。Reconcile 必须读取实际状态才能计算差距。状态变化后也应该产生新事件并入队,从而继续协调,因此 D 错误。