08 | 声明式 API 与自定义控制器
2026/7/15 6:01:35 网站建设 项目流程

系列专栏《深入剖析 Kubernetes》· 基于张磊极客时间专栏思想整理 · 适合 CSDN 发布

08 | 声明式 API 与自定义控制器

前两篇我们用了Deployment,体验了"声明期望状态,系统自动纠偏"。但很少有人追问:K8s 是怎么把这件事做成通用能力的?当你需要管理 K8s 原生没有的对象(比如一个 etcd 集群、一个数据库实例)时,又该怎么办?

答案就是本篇的两块基石:声明式 API自定义控制器(Custom Controller / Operator)


一、声明式 vs 命令式

  • 命令式(Imperative):你下指令序列——“先建 A,再建 B,删掉 C”。Ansible playbook、传统 shell 脚本都属此类。缺点是状态难追踪,失败了难以续跑。
  • 声明式(Declarative):你只给终态——“我要 3 个 nginx Pod 在跑”。系统自己算出现在离终态还差什么,并执行弥补动作。

K8s 的kubectl apply -f xxx.yaml就是声明式:同一份 YAML 反复 apply 是幂等的,系统会比较并只做必要的变更。

命令式: 你指挥每一步 你 -> 建Pod -> 建Service -> 改副本 (过程驱动) 声明式: 你描述目标,系统调谐 你 -> "期望: 3副本" -> [controller 不断 reconcile] -> 实际=期望

二、Etcd + 声明式 API = "期望状态"的真相

K8s 所有资源对象(Pod、Deployment……)都是保存在 Etcd 里的"记录"。当你apply一个 Deployment,API Server 把它写进 Etcd,这就是"期望状态"。

真正干活的控制器并不在 API Server 里,而是独立的循环进程。它们通过一套叫Informer / List-Watch的机制监听 Etcd 变化:

用户 kubectl apply | v +-----------+ 写 +------+ | API Server| -----> | Etcd | (期望状态 & 实际状态都在这) +-----------+ +------+ ^ List/Watch(增量事件) | | | +----- Informer 缓存 -------+ | v +-------------+ | Controller | 调谐: 读期望 -> 比对实际 -> 改实际 | (reconcile) | +-------------+ | v 调用 API Server 创建/删除 Pod
  • List-Watch:先全量 List 一次,之后通过 Watch 长连接接收增量事件,几乎零延迟感知变化。
  • Informer 本地缓存(Store):避免每次 reconcile 都打 API Server,提升性能。
  • Workqueue:把变更事件排队,保证同一对象串行处理,避免竞态。

三、CRD:定义你自己的资源

想让 K8s “认识"一个新对象(比如EtcdCluster),先登记它的"表结构”——这就是CRD(Custom Resource Definition)

apiVersion:apiextensions.k8s.io/v1kind:CustomResourceDefinitionmetadata:name:etcdclusters.etcd.database.example.comspec:group:etcd.database.example.com# API 组names:kind:EtcdCluster# 资源类型名plural:etcdclusters# REST 路径用的复数singular:etcdclusterscope:Namespaced# 还是 Cluster 级versions:-name:v1alpha1served:truestorage:trueschema:openAPIV3Schema:# 字段定义(期望状态长啥样)type:objectproperties:spec:type:objectproperties:size:# 期望的 etcd 节点数type:integerdefault:3

apply 之后,kubectl get etcdclusters就能看到你的自定义资源了——但此时它只是 Etcd 里一条记录,啥也不会做,因为还没有控制器去"调谐"它。


四、自定义控制器:把运维知识写进代码

控制器的伪代码(基于 client-go / controller-runtime 思路):

// 伪代码:一个最简 reconcile 循环funcReconcile(ctx,req){// 1. 取出用户的期望状态cluster:=client.Get("EtcdCluster",req.Name)// 2. 观察实际状态(现在有几个 etcd Pod 在跑)actualPods:=client.ListPods(label=cluster.Name)// 3. 对比期望与实际iflen(actualPods)<cluster.Spec.Size{// 4. 弥合差距:创建缺失的 Pod / 成员fori:=len(actualPods);i<cluster.Spec.Size;i++{client.Create(makeEtcdPod(cluster,i))}}elseiflen(actualPods)>cluster.Spec.Size{client.Delete(actualPods[len(actualPods)-1])// 缩容}// 5. 更新状态字段(status),返回(稍后会被再次触发)cluster.Status.Ready=len(actualPods)client.Status().Update(cluster)return// 控制器会基于事件/定时再次 reconcile}

controller-runtime(Kubebuilder 框架)时,你只需实现Reconcile()方法,框架已帮你封装好 Informer、Workqueue、客户端。这正是 Operator 开发的标准姿势。


五、Operator 模式:把"运维经验"编码

Operator = CRD + 自定义控制器,核心理念是:把资深 SRE 的运维知识写成代码

etcd-operator为例:部署一个 etcd 集群,人工要做备份、故障转移、版本升级、成员扩缩容。etcd-operator 把这些知识固化进控制器——你只写:

apiVersion:etcd.database.example.com/v1alpha1kind:EtcdClustermetadata:name:demo-etcdspec:size:3# 我要 3 节点 etcd,剩下交给 operator

operator 自动创建 3 个 Pod、组集群、做健康检查、节点挂了自动补、升级时按序滚动。运维从"人肉操作"变成了"声明 + 自动执行"。

用户声明: EtcdCluster{size:3} | v +-------------------+ | etcd-operator | 内置: 建集群/备份/故障转移/升级 等知识 | (controller) | reconcile 循环反复纠偏 +-------------------+ | 调用 K8s API v 3 个 etcd Pod 稳定运行,挂一个自动补一个

小结 / 核心要点

  • 声明式 = 给终态,系统调谐;命令式 = 给步骤序列。K8s 全程声明式,幂等且自愈。
  • Etcd 存"期望 + 实际",API Server 是入口,控制器是执行者,三者通过 List-Watch 解耦。
  • Informer 缓存 + Workqueue让控制器高效、串行、可靠地感知变化。
  • CRD 定义"新资源长什么样",但光有 CRD 不会动,必须配控制器。
  • Operator = CRD + 控制器,把 SRE 运维经验代码化,是 K8s 扩展能力的终极形态(如 etcd-operator 自动管集群生命周期)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询