引言
很多人第一次接触 Kubernetes,记住的是一串组件名称:API Server、etcd、Scheduler、Controller Manager、kubelet。再往后一些,会知道Deployment管副本,Scheduler管调度,Operator能做自动化运维。但如果继续追问一步:一个Pod为什么会出现在某台节点上,一个CRD为什么能像原生资源一样被kubectl get到,一个数据库Operator为什么能够自动扩容、备份与故障恢复,就需要我们对于K8S的组件工作原理和它们之间的编排协同(orchestration)有深刻的理解。
为了便于阅读,可以先记住本文试图回答的三个问题:
- 一个 Pod 如何被调度
- 一个 CRD 如何被注册
- 一个 Operator 如何运行
一、控制平面总览:Kubernetes 在管理什么
1.1 控制平面与数据平面
从整体视角看,Kubernetes 可以分为两部分。
第一部分是控制平面。它负责接收用户意图、保存系统对象、分发变化事件、做出调度与编排决策,并持续推动整个集群向目标状态收敛。第二部分是数据平面,也就是各个工作节点以及其上的运行时环境。它们真正执行容器、挂载卷、配置网络、回报状态。
这里需要区分一个常见误解:控制平面并不直接“运行业务”。业务容器运行在节点上,控制平面的职责是定义、记录、判断、驱动。它更像一个持续运转的控制系统,而不是一个直接搬运工作负载的执行系统。
1.2 声明式模型中的“期望状态”与“实际状态”
Kubernetes 常被称为声明式系统,意思不是“可以写 YAML”,而是用户提交给系统的,不是一步一步的操作命令,而是目标状态的描述。
例如,用户提交一个Deployment,说“我希望有 3 个副本,镜像版本是某个 tag,暴露若干端口”。这不是在告诉系统“先创建 Pod,再调度,再拉起容器”,而是在声明一个结果。至于为了达到这个结果,系统要创建哪些中间对象、何时重试、如何补偿失败,则由控制平面负责。
因此,控制平面长期处理的是两类状态之间的关系:
- 期望状态:对象中由用户或上层控制器声明出来的目标。
- 实际状态:节点、容器、网络、存储等运行后真实呈现出来的状态。
etcd中保存的是集群对象的持久化表示,它是控制平面判断“系统现在应当怎样”的唯一权威来源,但它并不等于外部世界本身。真实容器是否已经启动,卷是否真的完成挂载,探针是否已通过,这些仍然发生在节点侧,然后再通过状态上报反映回 API 对象中。控制平面的工作,就是不断比较这两部分并推动二者靠近。
1.3 以 API Server 为中心的星形结构
Kubernetes 控制平面的核心结构,不是各组件彼此直连,而是围绕API Server形成的星形协作模型。
- 用户通过
kubectl、控制台、SDK 等入口访问API Server。 - 调度器(Scheduler)通过
API Server读取未调度Pod,再把绑定结果写回去。 - 各种控制器(Controller)通过
list-watch观察对象变化,再把修正结果写回去。 kubelet通过API Server获取分配给本节点的对象,并上报运行状态。Operator也不是直接操作底层数据库或容器,而是围绕 API 对象驱动自动化行为。
这意味着 Kubernetes 的协作基础不是组件之间的私有协议,而是统一的对象模型与统一的 API 入口。系统中的大多数行为,最终都可以归结为四件事:存状态、看变化、做决策、推动收敛。
K8S组件一览
如果只记住一句话,那么可以这样概括:Kubernetes 控制平面是一个以API Server为中枢、以etcd为持久化状态源、以控制器模型推动系统收敛的分布式控制系统。
二、API Server:控制平面的统一入口
2.1 它不只是一个 REST 服务
从表面上看,API Server提供了一套 HTTP API,资源通过GET、POST、PUT、PATCH、DELETE进行读写,似乎只是一个普通的 REST 服务。但在架构上,它承担的角色远比“暴露接口”重要得多。
API Server是控制平面的前门,也是控制平面的总线。所谓前门,是指所有合法的状态变更几乎都必须先进入它;所谓总线,是指不同组件之间并不直接共享内存或直连 RPC,而是围绕它提供的对象语义协同。
因此,API Server至少同时承担四类职责:
- 统一入口:所有写操作都从这里进入系统。
- 统一语义:对象应该长什么样、有哪些字段、哪些字段只读、哪些字段可更新,都由它定义和执行。
- 统一安全边界:认证、鉴权、准入控制都在这一层完成。
- 统一事件出口:对象变化会在这里被观察、缓存和分发。
这也是为什么 Kubernetes 的扩展能力如此强。无论是内建资源、CRD、Admission Webhook,还是各种控制器,本质上都接在这条主线上。
2.2 一个请求进入 API Server 后会经历什么
以一次创建对象的请求为例,API Server的典型处理过程可以概括为以下顺序:
API server 的请求处理流程
- 认证(authentication):确认请求是谁发来的,例如证书、Token、
ServiceAccount等。 - 鉴权(authorization):确认这个身份是否有权对该资源执行该动作。
- 准入控制(admission):在对象真正持久化之前执行策略检查与修改,例如默认值填充、镜像策略检查、配额校验、Webhook 注入等。
- 模式校验(validation):确认对象结构、字段与版本是否合法。
- 持久化(persistence):将对象写入后端存储,也就是
etcd。 - 事件传播(distribution):对象变更进入缓存与
watch链路,供控制器、调度器、节点侧组件继续处理。
这条链路说明一件重要的事:API Server不是简单地把一份 JSON 存起来就结束了。它是 Kubernetes 对“资源对象”这一概念进行制度化管理的地方。
2.3list-watch:控制器模型的基础设施
如果说写路径定义了对象如何进入系统,那么list-watch则决定了对象如何在系统内部流动。
控制器、调度器、kubelet之所以能够及时响应对象变化,并不是因为API Server主动逐一通知每个组件,而是因为这些组件会先list当前对象集合,再基于某个版本点持续watch后续变化。这样一来,它们既能得到一份起始快照,又能不断接收增量事件。
整个 Kubernetes 控制器模型都建立在这个机制之上。没有watch,控制器只能靠高频轮询,就既低效又不稳定;有了watch,系统就能围绕对象变化形成相对统一的事件驱动模式。
因此可以说,API Server的关键不只在于“接受请求”,更在于“向整个控制平面提供统一的事件观察窗口”。
三、etcd:控制平面的状态存储层
3.1etcd存的不是业务数据,而是集群控制状态
Kubernetes 中几乎所有能被kubectl get到的对象,最终都会以某种形式存入etcd。Pod、Deployment、ConfigMap、Secret、Lease、CRD,乃至各类状态字段,都会成为其中的键值记录。
这时很容易产生一个误解:既然它存了这么多东西,那它是不是就相当于集群的“业务数据库”。严格说并不是。etcd保存的是集群控制状态,不是业务系统自己的事务数据。可以把它理解为集群控制状态的核心键值存储。
etcd被 Kubernetes 视为核心,是因为它保存的是整个控制平面判断与协同所依赖的权威状态。
3.2 为什么控制平面需要强一致存储
从分布式系统的角度看,控制平面最怕的情况不是慢,而是状态混乱、对象认知不一致、多个组件对同一事实做出冲突判断。
如果一个Pod是否已绑定到某节点,不同组件看到的答案不一致;如果多个调度器实例对同一个对象做出相互冲突的判断;如果控制器以为某个副本不存在而重复创建,而另一个组件又以为它已经存在,那么系统就会迅速进入不可预测状态。
因此,Kubernetes 需要一个强一致的状态存储层。对控制平面来说,写入成功必须意味着“这个状态已经成为全局上可以被信赖的事实”。这也是etcd被选中的原因之一。它不是为了追求极致吞吐,而是为了保证元数据层面的正确性。
在这方面,控制平面的取舍相当明确:对于集群对象这类元状态,宁可牺牲部分写入吞吐,也要保证一致性。因为一旦控制面失去一致性,后面的调度、编排、恢复与扩展都将失去依据。
极简版的Raft 协议实现
3.3revision、历史版本与对象演化
etcd不只是简单地覆盖旧值。它维护的是带版本演化的键值状态,每次变更都会产生新的修订版本。Kubernetes 把这一能力映射到对象层,形成了对象的resourceVersion、历史变更、冲突检测等一系列上层语义。如果对数据库原理比较熟悉,这里可以联想到MVCC。二者并不完全等同,但在“为并发读写保留版本演化信息”这一点上,确实有相通之处。
于是,一个对象不再只是“现在长什么样”,还隐含着“它是在什么版本基础上长成现在这个样子”。这为乐观并发控制、缓存同步和事件追踪提供了基础。
在工程实践中,很多控制面问题都可以借助这个视角理解:为什么控制器要基于最新对象做reconcile,为什么旧缓存有可能导致冲突,为什么状态更新最好保持幂等。背后都与对象版本的演化有关。
3.4watch的底层支点
前面提到控制器依赖list-watch模式,而这一机制能够稳定成立,底层也离不开etcd的版本化与事件能力。对象变化可以围绕版本点持续输出,从而让上层组件获得一个相对连续的变化流。
对 Kubernetes 来说,这一点意义非常大。因为它使控制平面不必围绕“谁调用了谁”来组织,而可以围绕“对象发生了什么变化”来组织。控制器无需关心是谁修改了对象,只需要看到对象变化并决定是否采取行动即可。
关于etcd背后的分布式一致性、Raft选主与日志复制机制以及分布式算法如何在云原生领域被实现,可以参见我之前的科普文章:
编辑【万字长文】从 Lamport 到 Kubernetes:分布式共识算法是怎么主导云原生与AI时代的基础设施31 赞同 · 5 评论 文章
四、Scheduler:把未落位的 Pod 绑定到合适节点
4.1 调度器处理的对象是什么
调度器最重要的工作对象,是尚未绑定节点的Pod。这类Pod已经是合法对象,也已经进入控制平面的状态体系,但它们还没有确定应该运行在哪台机器上。
这里需要区分“创建 Pod”和“调度 Pod”这两个动作。Pod作为对象,往往由更上层的控制器创建,例如ReplicaSet控制器。调度器并不负责生成这个对象,它负责的是在已有对象基础上做出放置决策。
4.2 调度流程:过滤、打分、绑定
从概念上看,调度过程可以分成三个阶段。
Scheduler 的调度流程
第一阶段是过滤。系统先排除那些明显不合适的节点。例如资源不够、污点不匹配、亲和性不满足、卷约束无法满足、节点不在指定拓扑域中。
第二阶段是打分。在剩余候选节点中,调度器会根据一系列规则计算优先级,例如资源均衡、镜像本地性、拓扑分散、亲和性偏好等。
第三阶段是绑定。调度器选择得分最高或最合适的节点,将这个结果写回API Server。从语义上说,这意味着该Pod的“落位决策”已经形成,接下来的执行责任就从调度层转向节点侧。
这套流程说明,调度器的本质是“选择”,而不是“启动”。它通过对象绑定来表达自己的决策,而不直接调用容器运行时去创建进程。
4.3 调度依据为什么如此复杂
很多初学者会把调度理解为“找一台有空闲 CPU 和内存的机器”。这当然是最基础的条件,但并不是全部。
在真实集群里,调度决策往往还要同时考虑:
- 资源请求与可分配余量。
- 节点标签与选择器。
- 亲和性与反亲和性。
- 污点与容忍。
- 拓扑分布约束。
- 本地盘、可用区、专用硬件等环境特征。
因此,调度器并不是一个简单的负载均衡器,而是一个把多种约束收束成单次放置决策的组件。它要解决的问题,是“什么节点最适合这个Pod”,而不是“现在把请求随机发到哪台机器”。
4.4 默认调度与可扩展框架
Kubernetes 默认提供一套调度框架与插件机制,使调度逻辑既有统一骨架,又可以按需扩展。
这也是为什么 Kubernetes 没有把调度拆成完全去中心化的共识过程。调度决策需要依赖相对全局的资源视图,而单次决策通常又足够短,因此由一个统一的调度框架完成选择,是一种复杂度与收益之间较为平衡的方案。
五、Controller Manager:让系统持续朝期望状态收敛
5.1 什么是控制器
如果说API Server提供了统一的状态入口,etcd提供了可信的状态存储,Scheduler负责解决对象落位问题,那么控制器就是推动整个系统持续收敛的执行逻辑。
控制器可以理解为一类长期运行的自动化程序。它不断观察某类对象,比较“现在是什么样”和“目标应该是什么样”,若发现差异,就发起修正动作。修正动作本身仍然通过API Server进入系统。
这里还需要区分两个层次。平时所说的Controller Manager,通常指的是kube-controller-manager这一管理进程;而其中真正执行具体自动化逻辑的,是一个个面向不同资源的控制器。也就是说,Controller Manager更接近控制器的宿主与组织者,“控制器”才是实际承担reconcile逻辑的基本单元。
5.2 为什么 Kubernetes 里会有很多控制器
Kubernetes 没有把所有自动化逻辑塞进一个巨大的“总控制器”里,而是把不同职责拆成多个相对独立的控制器。例如:
Deployment控制器负责把发布层面的目标转成ReplicaSet。ReplicaSet控制器负责维持指定数量的Pod副本。Node控制器负责观察节点状态与心跳。Job控制器负责判断批处理任务是否完成。EndpointSlice控制器负责根据服务后端维护流量目标集合。
这种拆分方式的好处在于,每个控制器只关注自己那一段因果关系。这样既降低了单个组件的复杂度,也让系统更容易扩展。未来新增一种资源或能力时,往往只需要新增相应控制器,而不必重写整个控制平面。
5.3reconcile:控制器的通用工作模式
尽管控制器种类很多,但它们背后有一个高度统一的模式,这个模式通常被概括为reconcile。
其一般过程可以写成下面这个公式:
读取对象 -> 判断差异 -> 更新对象或创建下游对象 -> 等待下一次事件
Operator的reconcile流程可视化
之所以说这是 Kubernetes 最核心的思想之一,是因为大量内建功能和生态扩展都遵循这一模型。控制器不假设系统一次动作就能到位,也不假设世界在自己操作之后保持静止。它会在对象每次变化后再次判断,再次修正,直到差异消失或者进入某种稳定状态。
5.4 幂等性与持续控制
控制器通常不应当写成“一次性脚本”。在分布式环境里,请求可能失败,缓存可能滞后,外部系统可能短暂不可用,之前提交的动作也可能部分成功。如果控制器逻辑不是幂等的,就很容易在重试过程中制造新的错误。
因此,成熟的控制器通常具备两个特征:
- 持续运行:它不是执行一次就结束,而是长期存在,不断响应对象变化。
- 幂等修正:它面对同一个目标状态重复执行时,应当尽量得到同样的结果,而不是层层叠加副作用。
理解这一点,对理解Operator尤其重要。因为Operator并不是某种新的魔法,而是控制器模式在特定领域中的延伸。
六、控制平面的执行终点
虽然本文重点是控制平面,但节点侧不能完全省略。原因很简单:如果没有节点执行层,控制平面的所有对象、判断与收敛都无从落地。
从职责上看,节点侧至少包含三类关键角色:
kubelet:观察分配给本节点的对象,把PodSpec转化为具体执行动作,并持续汇报状态。- 容器运行时:如
containerd、docker,负责拉取镜像、创建/删除容器,kubelet 通过 CRI 调用它们实现具体运行,底层一般由runc或kata执行。 DaemonSet:确保每个节点都运行某些系统级 Pod,例如日志收集(如 fluentd)、监控(如 node-exporter)、网络插件(如 CNI 各实现)或 DNS(如 CoreDNS)等。
以容器启动为例,控制平面只会把“这个Pod应当在某节点上运行”的状态表达出来。真正到节点上,通常是kubelet依据该对象调用CRI接口,请运行时准备镜像、创建容器,再由底层机制建立进程隔离与资源限制。这里不再展开到CNI、CSI或内核实现细节,因为它们已经超出本文的重点。
需要记住的只是:控制平面负责定义、分发和纠偏,节点侧负责执行和回报。
七、三个根本问题之一:一个 Pod 是如何被创建并调度的
为了把前面的静态组件真正串起来,最好的办法是沿着一条完整链路走一遍。这里以更常见的Deployment场景为例,因为在真实环境中,用户很少直接手写单个Pod作为最终工作负载。
7.1 从提交对象开始
用户执行kubectl apply -f deployment.yaml时,首先进入系统的是一个Deployment对象。这个对象经过认证、鉴权、准入和校验后,由API Server写入etcd。
到这一步为止,系统里还没有真正运行的业务容器。它只是多了一份被持久化的目标描述:期望存在一个满足某些规格的发布对象。
7.2 上层控制器补齐中间对象
Deployment控制器通过watch看到新对象后,会判断当前实际状态与期望状态之间的差异。发现还没有对应副本集时,它会创建一个ReplicaSet。
随后,ReplicaSet控制器看到自己的目标副本数尚未满足,又会继续创建若干Pod对象。到这时,系统中才出现真正代表运行实例的Pod,但这些Pod仍然只是 API 对象,其中大多还没有绑定到节点。
这一阶段非常能体现 Kubernetes 的控制器分层思路。用户并没有直接命令系统“去创建三个 Pod”。用户只声明了发布目标,而系统通过多个控制器逐层把目标展开成更具体的对象。
7.3 调度器为 Pod 选择节点
调度器持续监听未绑定节点的Pod。当新的Pod进入队列后,调度器会读取当前节点状态、资源约束与调度规则,经过过滤、打分与选择过程,最终为该Pod选出一个最合适的节点。
接着,调度器把绑定结果写回API Server。从对象视角看,就是Pod的放置信息发生了变化;从系统视角看,这代表控制平面已经形成了“这个实例应当在哪台机器上运行”的决策。
7.4 节点侧接管执行
目标节点上的kubelet同样通过API Server观察分配给自己的对象。一旦发现这个新Pod,它就会把对象规格转化为具体执行动作,例如准备卷、拉取镜像、通过CRI请求运行时创建容器。
如果运行时是containerd,那么通常由kubelet调用CRI接口与之交互,再由containerd协调底层组件完成容器启动与隔离环境建立。我们可以看到一道清晰职责边界:调度器决定放在哪里,kubelet负责把这个决定落成真实运行状态。
7.5 状态回流与持续收敛
容器启动后,节点侧会不断把状态回报给控制平面,例如Pod的阶段、容器是否就绪、探针是否通过、节点是否健康等。这些状态变化重新进入API Server与存储层,进而被各类控制器继续观察。
于是,控制平面的工作并未在“调度完成”这一刻结束。若节点故障、容器退出、探针失败或副本数不足,控制器又会基于新的状态再次采取动作。Kubernetes 的运行逻辑,并不是一条单向流程,而是一个持续闭环。
pod 建立流程的可视化
把这条链路概括成一句话,就是:对象先进入状态系统,再由控制器逐层展开,由调度器完成落位,由节点侧负责执行,最后由状态回流推动下一轮收敛。
八、三个根本问题之二:CRD 是如何在 API Server 中注册的
8.1CRD解决的是什么问题
Kubernetes 的强大之处,不仅在于它内建了一套对象,如Pod、Deployment、Service,还在于它允许用户把自己的领域概念也纳入同一套 API 体系。
CRD,即CustomResourceDefinition,本质上就是对“新增一种资源类型”这件事的声明。它告诉系统:现在除了内建对象之外,还应当存在一种新的对象,它的名字是什么,属于哪个 API 组,支持哪些版本,字段结构如何,哪些字段应该校验。
这意味着 Kubernetes 不是一个封闭系统。它的对象模型是可以扩展的,而且这种扩展不是绕开原有体系另起炉灶,而是接入原有 API 机制。
8.2 一个新资源类型如何进入 API 体系
从流程上看,CRD的注册并不复杂。用户首先提交一个CustomResourceDefinition对象,这个对象本身也是通过API Server写入系统的。随后,扩展 API 相关的机制会根据这个定义,把新的资源类型纳入可发现、可校验、可存储、可访问的 API 范畴。
从外部使用体验上看,结果就是:
kubectl api-resources能看到这个新资源。- 客户端可以通过标准 REST 路径访问它。
kubectl get、kubectl describe、watch、RBAC、Admission 等通用机制都可以继续作用于它。
CRD的注册流程
8.3 Schema、版本与子资源
一个成熟的CRD通常不只定义名字,还要定义结构与演化方式。
- Schema 校验:确保用户提交的自定义资源至少满足基本结构要求,而不是任意字段都能写。
- 版本管理:允许资源在
v1alpha1、v1beta1、v1等多个阶段演进。 - 子资源划分:常见的是
spec与status分离,前者表达期望,后者表达观察结果。
这种设计延续了 Kubernetes 内建对象的核心思想。spec与status的区分尤其重要,因为它把“用户想要什么”和“系统观察到了什么”分开表达,为控制器模式提供了清晰的边界。
8.4CRD与 Aggregated API 的边界
需要顺手区分的另一点是,Kubernetes 的扩展并不只有CRD一种方式。CRD更适合声明式资源扩展,也就是“我要新增一种对象类型,并让它进入统一 API 体系”。如果需求更接近于新增一套定制 API 服务,则还可以走 Aggregated API 等其他路径。
但在大多数云原生运维与平台工程实践中,CRD已经足以承载大量扩展需求。因为真正关键的是把领域对象纳入 Kubernetes 的状态与控制循环中。
九、三个根本问题之三:Operator 的运行逻辑是什么
9.1 只有CRD还不够
有了CRD,系统只是学会了“认识一种新对象”;但认识对象,不等于知道该如何对它采取行动。
例如,你定义了一个MyDatabase资源,Kubernetes 现在可以接受、存储、查询这个对象,也可以校验它的字段是否合规。但如果没有后续逻辑,系统并不会自动帮你创建 StatefulSet、初始化存储、执行备份、处理主从切换。换言之,CRD只提供了资源模型,尚未提供控制行为。
这时就需要Operator。
9.2Operator本质上是一类面向领域的控制器
Operator并不是脱离 Kubernetes 的新机制,本质上仍然是控制器模型的一种具体化。它通常围绕某个自定义资源长期运行,持续观察对象变化,并把领域知识编码成自动化动作。
它和普通控制器的差别,更多体现在处理对象的领域含义上。例如,ReplicaSet控制器处理的是“副本数是否满足”,而数据库Operator处理的可能是“实例是否已经初始化”“是否需要扩容”“是否应当触发备份”“是否需要执行故障切换”。
因此,可以把Operator理解为:把原本依赖人工判断与人工操作的运维流程,改写成一个围绕 API 对象持续运行的控制器。
9.3Operator的典型工作循环
一个典型Operator的工作过程,大致如下:
- 通过
list-watch观察某类自定义资源对象。 - 读取该对象的
spec,理解期望配置。 - 检查下游资源或外部系统的实际状态。
- 若发现差异,则创建、修改或删除相关对象,例如
StatefulSet、Service、Secret、备份任务等。 - 在需要清理、善后或级联管理时,结合
finalizer、所有权关联等机制维护对象生命周期。 - 将观察结果写回对象的
status,必要时写入条件、阶段、错误信息等。 - 等待下一次事件,再重复这一过程。
这个过程与 Kubernetes 内建控制器几乎同构。不同之处在于,Operator往往携带更强的业务或中间件领域知识。例如,数据库主从拓扑如何初始化,升级时必须遵循什么顺序,哪些状态可以自动恢复,哪些状态必须暂停等待人工介入。
十、把几条链路收束成一个统一模型
写到这里,再回头看前面的三条链路:
Pod的创建与调度,展示的是对象如何从高层目标逐步展开成可执行实例。CRD的注册,展示的是新的对象类型如何进入 Kubernetes 的统一 API 体系。Operator的运行,展示的是新的控制逻辑如何围绕对象持续工作。
表面上看,这三件事分别属于工作负载编排、API 扩展、自动化运维,彼此差异很大;但从控制平面角度看,它们背后遵循的是同一套结构。
第一,系统中的核心单位始终是对象。不论是原生对象还是自定义对象,最终都要以 API 资源的形式存在。
第二,系统围绕对象维护状态。用户提交的是期望状态,组件观察的是实际状态,存储层保存的是可被信赖的持久化状态表示。
第三,系统通过list-watch获得事件。组件不需要预先知道是谁会修改对象,只需要对变化做出响应。
第四,系统通过控制器或调度器执行控制循环。它们读取状态、判断差异、提交修正,再等待新的变化到来。
由此可以得到一个相对统一的理解框架:Kubernetes 的核心并不只是容器编排,而是以API Server为中枢、以对象模型为界面、以状态变化为信号、以控制循环为动力的自动化控制系统。
也正因为如此,Kubernetes 才能既管理内建工作负载,也承载不断扩展的生态。它的关键不在于控制平面里究竟有多少个组件,而在于这些组件通过一致的对象模型与事件机制,被组织进同一种协作方式中。