08 | 声明式 API 与自定义控制器
系列专栏《深入剖析 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一个 DeploymentAPI Server 把它写进 Etcd这就是期望状态。真正干活的控制器并不在 API Server 里而是独立的循环进程。它们通过一套叫Informer / List-Watch的机制监听 Etcd 变化用户 kubectl apply | v ----------- 写 ------ | API Server| ----- | Etcd | (期望状态 实际状态都在这) ----------- ------ ^ List/Watch(增量事件) | | | ----- Informer 缓存 ------- | v ------------- | Controller | 调谐: 读期望 - 比对实际 - 改实际 | (reconcile) | ------------- | v 调用 API Server 创建/删除 PodList-Watch先全量 List 一次之后通过 Watch 长连接接收增量事件几乎零延迟感知变化。Informer 本地缓存Store避免每次 reconcile 都打 API Server提升性能。Workqueue把变更事件排队保证同一对象串行处理避免竞态。三、CRD定义你自己的资源想让 K8s “认识一个新对象比如EtcdCluster先登记它的表结构”——这就是CRDCustom 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:3apply 之后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(labelcluster.Name)// 3. 对比期望与实际iflen(actualPods)cluster.Spec.Size{// 4. 弥合差距创建缺失的 Pod / 成员fori:len(actualPods);icluster.Spec.Size;i{client.Create(makeEtcdPod(cluster,i))}}elseiflen(actualPods)cluster.Spec.Size{client.Delete(actualPods[len(actualPods)-1])// 缩容}// 5. 更新状态字段status返回稍后会被再次触发cluster.Status.Readylen(actualPods)client.Status().Update(cluster)return// 控制器会基于事件/定时再次 reconcile}用controller-runtimeKubebuilder 框架时你只需实现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剩下交给 operatoroperator 自动创建 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 自动管集群生命周期。