Kubernetes 基础体系 · 第 2/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 架构:API Server、Scheduler、Controller Manager、etcd 和 Node
Kubernetes 的核心不是“启动容器的一组命令”,而是一个围绕 API 对象运行的控制系统。用户提交的是期望状态(Desired State),控制面组件观察集群当前状态并不断采取动作,使实际状态逐步接近期望状态。
本文讨论 Kubernetes 控制平面的主要组件:
- API Server:所有 Kubernetes API 请求的入口,以及控制面组件之间的协调接口。
- etcd:保存 Kubernetes API 对象持久状态的一致性键值存储。
- Scheduler:为尚未绑定节点的 Pod 选择合适的 Node。
- Controller Manager:运行多个控制器,把当前状态调谐到期望状态。
- Node:实际运行 Pod 的工作节点,包括 kubelet、容器运行时和通常存在的 kube-proxy。
理解这些组件前,还需要掌握三个前置概念:
- Kubernetes 中的大多数资源都是 API 对象;
- 组件通常通过
watch观察对象变化,而不是互相直接调用内部函数; - 控制器通过调谐循环实现最终一致,而不是一次操作就保证全局状态立即完成。
一、先建立整体模型:声明式对象和调谐循环
一个 Kubernetes API 对象通常可以抽象为:
对象 = GVK + metadata + spec + status
其中:
- GVK(Group、Version、Kind)描述对象的 API 类型;
- metadata 包含名称、命名空间、标签、注解、UID、resourceVersion、ownerReferences 等;
- spec 表示用户或上层控制器声明的期望状态;
- status 表示系统观察到的当前状态。
例如,一个 Deployment 的 spec.replicas: 3 表示用户希望存在 3 个匹配的 Pod,但不表示提交请求返回时 3 个 Pod 已经创建完成。Deployment 控制器会根据这个期望状态创建或更新 ReplicaSet,ReplicaSet 控制器再创建 Pod,Scheduler 为 Pod 选择节点,kubelet 最终在节点上启动容器。
可以把一个控制器的目标写成:
其中:
- 是 API 对象中声明的期望状态;
- 是控制器观察到的当前状态;
- 是需要执行的动作,例如创建 Pod、更新副本数或写入 Status。
理想情况下,控制器重复执行调谐后满足:
这里的箭头不是“一次调用立即完成”,而是表示经过多个异步步骤最终收敛。网络延迟、镜像拉取、节点故障、权限错误和资源不足都会让收敛变慢或无法完成。
API Server、etcd 和其他组件的关系
flowchart LR
User[kubectl 或客户端] --> API[API Server]
Controller[Controller Manager 中的控制器] --> API
Scheduler[Scheduler] --> API
Kubelet[kubelet] --> API
API <--> Etcd[(etcd)]
API --> Obj[API 对象与 Watch 事件]
Obj --> Controller
Obj --> Scheduler
Obj --> Kubelet
Kubelet --> Runtime[容器运行时]
Runtime --> Container[容器进程]
关键路径是:
- 客户端向 API Server 提交对象;
- API Server 完成认证、鉴权、准入和校验;
- API Server 将持久化对象写入 etcd;
- 控制器和 Scheduler 通过 API Server 读取对象并建立 Watch;
- 控制器更新 API 对象或创建新对象;
- kubelet 从 API Server 得到 Pod 配置,在本地运行容器;
- kubelet 将节点和 Pod 状态回报给 API Server;
- 控制器根据新的 Status 再次调谐。
组件通常不应该绕过 API Server 直接访问 etcd。etcd 是 Kubernetes 的内部存储实现,而 Kubernetes API 的认证、鉴权、准入、版本转换和资源语义都由 API Server 提供。
二、API Server:集群状态的统一入口
2.1 API Server 做什么
kube-apiserver 是 Kubernetes API 的 HTTP(S) 服务端。它承担几类职责:
-
提供 API 端点
例如:/api/v1/namespaces/default/pods /apis/apps/v1/namespaces/default/deployments /api/v1/nodes -
解析和转换 API 对象
客户端发送的版本可能不是存储版本。API Server 可以在外部版本和内部表示之间转换。 -
认证请求者身份
常见身份来源包括客户端证书、Bearer Token、OIDC、Webhook 等。具体可用方式取决于集群配置。 -
鉴权请求者是否有权限
Kubernetes 常用 RBAC 判断主体是否可以对某个资源执行某种动作,例如get pods、create deployments。 -
执行准入控制
准入阶段可以拒绝请求,也可以修改请求对象。内置准入插件和 Admission Webhook 都可能参与这一过程。 -
校验资源并持久化
API Server 验证字段、版本和业务规则,然后将持久化结果写入 etcd。 -
提供 List/Watch
控制器和其他客户端通常先 List 当前对象,再 Watch 后续变化。
API Server 本身不是“容器管理器”。它通常不直接调用容器运行时启动容器,也不负责决定每个 Pod 应该放在哪台节点上。
2.2 一次写请求的完整路径
以创建 Deployment 为例,请求大致经过以下步骤:
客户端
↓
TLS 与 HTTP 解析
↓
认证 Authentication
↓
鉴权 Authorization
↓
准入 Admission
↓
API Schema 与业务校验
↓
写入 etcd
↓
返回 API 对象
每一步的失败含义不同:
- 认证失败通常是
401 Unauthorized; - 鉴权失败通常是
403 Forbidden; - 对象格式或字段不合法通常是
400 Bad Request; - 资源版本冲突通常是
409 Conflict; - Webhook 超时或拒绝可能导致请求失败;
- etcd 或 API Server 不可用时,可能返回
5xx或超时。
一次成功的创建请求只说明 API 对象已经被接受并持久化,不能说明 Deployment 已经完成滚动更新,也不能说明 Pod 已经在节点上运行。
2.3 List 和 Watch 为什么必须配合
控制器通常采用如下模式:
- 先
List某类资源,获得当前快照; - 保存列表中对象的最新
resourceVersion; - 从这个版本开始
Watch后续事件; - 收到事件后把相关对象放入工作队列;
- 调谐时再次读取对象,并根据最新状态采取动作。
Watch 事件的类型通常包括:
ADDED:对象出现;MODIFIED:对象变化;DELETED:对象删除;BOOKMARK:用于推进资源版本的书签事件,具体行为由客户端和服务端能力决定。
Watch 不是可靠消息队列。连接可能断开,历史事件可能因 compaction 等原因无法继续提供。客户端必须能够重新 List,再从新的 resourceVersion 建立 Watch。
因此,控制器不能假设:
每个事件只到达一次
每个事件一定按业务顺序到达
收到事件时对象仍然保持事件中的内容
更准确的设计是:事件只负责提示“某个对象可能需要重新调谐”,真正的状态以重新读取 API 对象为准。
2.4 resourceVersion 不是业务版本号
metadata.resourceVersion 用于表示 API 存储系统中的资源版本位置,常被用于并发控制和 Watch 起点。它不是:
- 用户自定义的版本号;
- Deployment 的发布版本;
- 所有对象共享的可比较业务时间戳;
- 可以由客户端自行递增的字段。
例如,两个客户端同时更新同一个对象时,客户端可以基于旧的 resourceVersion 发起更新。如果对象已经被别人修改,API Server 可能返回 409 Conflict,客户端应重新读取并决定如何合并,而不是盲目覆盖。
三、etcd:Kubernetes 的一致性持久状态
3.1 etcd 保存什么
etcd 是分布式一致性键值存储。Kubernetes 使用它保存 API 对象及相关元数据,例如:
- Pods;
- Deployments;
- Services;
- ConfigMaps;
- Secrets;
- Nodes;
- Leases;
- Events;
- CRD 的实例对象。
从概念上可以把 Kubernetes 对象映射为键:
/registry/pods/default/web-abc
/registry/deployments/default/web
/registry/nodes/node-1
具体内部键路径属于实现细节,不应让业务程序直接依赖。正确的访问方式是 Kubernetes API,而不是直接读写 etcd。
etcd 保存的是控制面所需的声明状态和观察状态,但容器的可写文件系统、镜像层、进程内存和节点本地日志并不存储在 etcd 中。
3.2 一致性和多数派
etcd 集群通常使用基于 Raft 的一致性协议。设 etcd 集群节点数为 ,形成多数派所需的节点数为:
例如:
| etcd 节点数 | 多数派 |
|---|---|
| 1 | 1 |
| 3 | 2 |
| 5 | 3 |
对于 3 节点集群,通常可以容忍 1 个节点故障;如果只剩 1 个节点,无法形成多数派,写入通常无法继续提交。增加节点并不会线性增加可用性,奇数规模通常更适合多数派容错。
这里要区分两种情况:
- etcd 进程还活着,但节点之间无法形成多数派;
- etcd 形成多数派,但 API Server 到 etcd 的网络路径故障。
两者都可能表现为 API 请求超时或失败,但诊断路径不同。
3.3 etcd 故障对集群的影响
当 API Server 无法向 etcd 完成写入时:
- 新建、更新、删除对象可能失败;
- 控制器无法可靠记录新的期望状态;
- Scheduler 无法完成 Pod 绑定;
- kubelet 可能继续运行已经获得的 Pod,但无法持续获取新配置或上报状态;
- 依赖 API Server 的扩缩容、滚动发布和故障恢复会受到影响。
因此,“现有容器还在运行”不等于“控制面正常”。数据平面可能短时间继续工作,但集群已经失去持续调谐能力。
etcd 的生产风险主要包括:
- 备份不完整或无法恢复;
- 磁盘延迟过高;
- 数据目录误删;
- 证书过期;
- 网络分区;
- 版本升级和恢复流程没有演练;
- 将 etcd 暴露在不受信任的网络中。
etcd 备份必须考虑一致性快照、加密材料、恢复后的集群标识和灾难恢复流程。不能只复制正在写入的数据库目录就宣称完成可靠备份。
四、Controller Manager:把对象状态调谐到期望状态
4.1 Controller Manager 不是一个单一控制器
kube-controller-manager 是多个控制器的集合。不同控制器负责不同资源关系,例如:
- Node Controller:处理节点可达性和节点生命周期相关状态;
- Deployment Controller:根据 Deployment 管理 ReplicaSet;
- ReplicaSet Controller:确保匹配的 Pod 副本数符合要求;
- Job Controller:管理 Job 对应的 Pod 和完成状态;
- EndpointSlice Controller:根据 Service 和 Pod 更新 EndpointSlice;
- Namespace Controller:处理命名空间终止及相关清理;
- ServiceAccount Controller:根据集群版本和配置处理相关 ServiceAccount 逻辑。
控制器通过 API Server 观察资源,再通过 API Server 创建、更新或删除对象。它们通常不直接调用其他控制器的内部方法。
控制器之间的协作依赖 API 对象关系。例如:
Deployment
└── ownerReferences → ReplicaSet
└── ownerReferences → Pod
ownerReferences 让控制器能够识别从属对象,也为垃圾回收器提供关系信息。实际控制器还会使用标签选择器、UID、Finalizer 和状态字段确认对象归属。
4.2 调谐循环的具体过程
以 ReplicaSet 为例,控制器可以按以下步骤工作:
-
读取 ReplicaSet 的
spec.replicas; -
根据 selector 查找匹配的 Pod;
-
过滤不属于当前 ReplicaSet 的对象;
-
计算差值:
-
如果 ,创建若干 Pod;
-
如果 ,删除多余 Pod;
-
更新 ReplicaSet 的
status.replicas、readyReplicas等观察字段; -
等待新的 Pod 事件或定时重新调谐。
假设期望副本为 3,当前匹配 Pod 数为 1:
desired = 3
current = 1
delta = 2
控制器会尝试创建 2 个 Pod,但创建成功不等于 Pod 已经 Ready。之后 kubelet 更新 Pod 状态,控制器再次调谐,可能得到:
desired = 3
current = 3
ready = 1
这时副本数量已经满足,但可用副本仍未满足。spec 和 status 分别表达“想要什么”和“现在怎么样”,不能混为一谈。
4.3 为什么控制器必须幂等
幂等意味着同一个调谐动作执行一次或多次,最终结果等价。控制器需要幂等,是因为:
- Watch 事件可能重复;
- 控制器可能重启;
- API 请求可能超时,但服务端实际上已经成功;
- 进程可能在动作完成后、记录结果前崩溃;
- 多个事件可能合并为一次调谐;
- 队列中的对象可能被多次入队。
错误的控制器逻辑可能是:
每收到一次 ADD 事件,就无条件创建一个 Pod
如果事件重复,就会创建多余 Pod。正确逻辑应是:
读取当前实际对象数量
计算与期望数量的差值
只创建或删除使状态接近目标所需的对象
这也是“事件驱动”与“状态驱动”的区别:事件触发调谐,但状态决定动作。
4.4 工作队列与失败重试
常见控制器实现会将资源键,例如:
namespace/name
放入工作队列。处理流程通常是:
- 从队列取出键;
- 从 Informer 缓存读取对象;
- 执行调谐;
- 成功则处理完成;
- 暂时性错误则重新入队并退避;
- 永久性错误则记录事件或更新 Status,等待对象变化或人工修复。
工作队列通常不会把完整对象作为长期事实来源,因为队列中的对象可能已经过期。缓存和 API Server 中的最新对象才是调谐依据。
4.5 Controller Manager 的边界
Controller Manager 不负责所有控制逻辑:
- Scheduler 负责节点选择,不是普通控制器的一个隐式步骤;
- kubelet 负责节点本地 Pod 生命周期,不由 Controller Manager 直接启动容器;
- kube-proxy 负责节点上的 Service 网络规则,通常独立运行;
- Cloud Controller Manager 负责与云厂商 API 相关的节点、路由、负载均衡等逻辑,是否部署以及支持范围取决于环境。
在自建集群和云托管集群中,控制器的拆分、参数和可见性可能不同。不能仅凭某个托管平台的控制台组件列表推断 Kubernetes 规范要求。
五、Scheduler:为 Pod 选择 Node
5.1 Scheduler 处理什么对象
Scheduler 关注的是尚未绑定节点的 Pod。一个典型的待调度 Pod 具有:
spec:
nodeName: ""
Scheduler 为 Pod 选择节点后,通常通过 API Server 写入绑定结果,使 Pod 的 spec.nodeName 指向某个 Node。
Scheduler 不做以下事情:
- 不在节点上启动容器;
- 不负责拉取镜像;
- 不保证应用已经 Ready;
- 不负责修复节点上的容器进程;
- 不把任意一个 Pod 直接“复制”到每个节点。
这些工作由 kubelet、容器运行时和其他控制器完成。
5.2 调度的基本阶段
现代 Kubernetes Scheduler 使用可扩展的调度框架。不同版本的插件和扩展点可能变化,但基本思想可以概括为:
- 从待调度 Pod 队列取出一个 Pod;
- 根据资源、约束和策略筛选可行节点;
- 对可行节点打分;
- 选择得分最高或经过决策的节点;
- 通过 API Server 完成绑定;
- 若绑定失败或没有可行节点,记录失败并根据策略重试。
筛选条件可能包括:
- Pod 请求的 CPU、内存等资源;
- Node 的
Ready状态; - NodeSelector;
- Node Affinity;
- Pod Affinity/Anti-Affinity;
- Taints 和 Tolerations;
- 拓扑分布约束;
- Volume 的可用性和拓扑限制;
- 调度器配置的其他插件。
这里的资源计算通常基于 Pod 的资源请求,而不是当前实际 CPU 使用率:
例如,节点可分配内存为 8 GiB,已有 Pod 的内存请求总和为 6 GiB,新 Pod 请求 3 GiB:
即使节点当前监控显示只使用了 1 GiB 内存,基于请求的调度仍可能拒绝该节点。这是“资源请求”与“实时使用量”的重要区别。
5.3 调度算例
假设有两个节点:
node-a:
allocatable CPU = 4
已分配请求 = 2.5
node-b:
allocatable CPU = 4
已分配请求 = 1
新 Pod 请求 1 CPU,且没有其他约束:
node-a 剩余请求容量 = 4 - 2.5 = 1.5,合格
node-b 剩余请求容量 = 4 - 1 = 3,合格
两个节点都通过过滤。打分阶段可能偏向资源更充足的 node-b,但具体得分由启用的插件和配置决定,不能假设所有版本都采用同一种固定公式。
如果新 Pod 添加:
spec:
nodeSelector:
disk: nvme
而只有 node-a 具有 disk=nvme,则 node-b 会在过滤阶段被排除,打分不再改变这个事实。
5.4 “Pending”不总是 Scheduler 的问题
Pod 处于 Pending 可能表示:
- Scheduler 尚未完成调度;
- 没有满足条件的节点;
- PVC 尚未绑定;
- 镜像尚未拉取;
- Pod 已经绑定节点但容器尚未启动;
- 准入或其他控制器仍在处理对象。
诊断时应先看:
kubectl get pod <pod-name> -o wide
kubectl describe pod <pod-name>
kubectl get events --sort-by=.lastTimestamp
如果 PodScheduled 条件为 False,并且 Events 中有 FailedScheduling,才更直接地指向 Scheduler 的过滤结果。若 Pod 已经有 NODE,问题通常已经越过“选择节点”阶段,应继续检查 kubelet、容器运行时、镜像、卷和网络。
六、Node:真正运行工作负载的地方
6.1 Node 是 API 对象,也是物理或虚拟资源载体
Node 在 Kubernetes 中既是一个 API 对象,也代表一个可被调度的工作节点。其常见信息包括:
- 节点名称和标签;
spec.taints;status.capacity;status.allocatable;status.conditions;- kubelet 心跳相关的 Lease。
需要区分:
capacity = 节点理论或发现的总容量
allocatable = Kubernetes 允许 Pod 使用的容量
allocatable 通常小于 capacity,因为系统进程、kubelet、容器运行时和节点保留资源需要消耗一部分资源。Scheduler 主要依据 allocatable 和 Pod requests 做资源可行性判断。
6.2 kubelet、容器运行时和 kube-proxy
一个典型 Node 上至少涉及:
kubelet
kubelet 是节点代理,主要负责:
- 向 API Server 注册或维护 Node 信息;
- 观察分配给本节点的 Pod;
- 通过 CRI 调用容器运行时;
- 创建 Pod Sandbox、容器和卷挂载;
- 执行探针和生命周期管理;
- 上报 Pod Status;
- 发送 Node 心跳和 Lease 更新;
- 执行节点级驱逐等逻辑。
kubelet 不是 Controller Manager 的子进程。它在每个 Node 上独立运行,通常直接与 API Server 和容器运行时交互。
容器运行时
容器运行时通过 CRI 为 kubelet 提供创建和管理容器的能力。常见实现包括 containerd 和 CRI-O。Docker Engine 不再是 Kubernetes 默认的直接运行时接口;旧版本曾经存在 dockershim,但该组件已经移除。具体运行时由集群发行版和节点配置决定。
kube-proxy
kube-proxy 通常负责根据 Service 和 EndpointSlice 等对象,在节点上配置服务转发规则。实现可能使用 iptables、IPVS 或其他机制,具体取决于版本和配置。
kube-proxy 并不是 Service 本身,也不负责选择 Pod 的调度节点。某些网络插件或 eBPF 数据平面可能替代或部分替代 kube-proxy,因此云厂商和发行版之间存在实现差异。
6.3 Node 心跳和失联判断
kubelet 会更新 Node Status,并通常使用 Lease 资源发送轻量心跳。控制面根据心跳和状态判断节点是否健康。
节点失联后的处理不是瞬间完成的:
- kubelet 停止更新心跳;
- 控制器观察到节点长期没有更新;
- Node Condition 可能变为
Unknown; - 节点可能被标记为不可调度或触发 Pod 驱逐相关流程;
- 上层控制器根据 Pod 状态和业务策略创建替代副本。
这里有一个容易误解的边界:控制面不能可靠地“远程杀死”一个已经与控制面断联的节点上的进程。它可以在 API 层面标记状态、删除对象或在其他节点创建替代 Pod,但旧节点若仍然运行,可能出现重复实例。分布式系统需要借助租约、 fencing、存储和应用自身的选主机制降低这种风险。
七、完整数据流:从 Deployment 到运行中的 Pod
下面使用一个最小 Deployment 展示各组件如何协作。
7.1 创建对象
准备文件 web.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
执行:
kubectl apply -f web.yaml
预期可能看到:
deployment.apps/web created
这个输出只表示 Deployment 对象被 API Server 接受。它不代表两个 Pod 已经启动。
7.2 Deployment 控制器创建 ReplicaSet
查询对象:
kubectl get deployment web
kubectl get rs -l app=web
可能看到:
NAME READY UP-TO-DATE AVAILABLE AGE
web 0/2 0 0 2s
几秒后可能变为:
NAME READY UP-TO-DATE AVAILABLE AGE
web 2/2 2 2 20s
中间状态取决于 API Server、控制器、Scheduler、镜像仓库和节点速度,不能把时间当作 Kubernetes API 的规范保证。
过程是:
Deployment.spec.replicas = 2
↓
Deployment Controller 创建或更新 ReplicaSet
↓
ReplicaSet.spec.replicas = 2
↓
ReplicaSet Controller 创建 2 个 Pod
7.3 Scheduler 绑定 Pod 到 Node
查看 Pod:
kubectl get pod -l app=web -o wide
示例输出:
NAME READY STATUS RESTARTS AGE IP NODE
web-7d8f5c6b7d-abcde 1/1 Running 0 15s 10.244.1.10 node-a
web-7d8f5c6b7d-fghij 1/1 Running 0 15s 10.244.2.11 node-b
Pod 进入 Running 前通常经历:
API 对象创建
↓
未绑定 Node,进入 Scheduler 队列
↓
Scheduler 过滤和打分
↓
写入 Pod.spec.nodeName
↓
目标节点 kubelet 观察到 Pod
↓
创建 Pod Sandbox 和容器
↓
镜像拉取
↓
容器启动
↓
kubelet 更新 Pod.status
调度完成后,spec.nodeName 已经存在,但容器可能仍处于 ContainerCreating。因此:
Scheduled ≠ Running
Running ≠ Ready
Ready ≠ 业务一定正确
7.4 修改期望状态
将副本数改为 3:
kubectl scale deployment web --replicas=3
检查:
kubectl get deployment web
kubectl get pods -l app=web -o wide
控制器不会修改某个 Pod 的“副本数”,而是根据新的 Deployment 期望状态更新中间对象,最终创建第三个 Pod。已有两个 Pod 是否被替换,取决于修改的字段和 Deployment 的更新策略。
清理资源:
kubectl delete deployment web
删除 Deployment 后,相关 ReplicaSet 和 Pod 通常会根据 ownerReferences 和控制器行为被清理。删除过程是异步的,命令返回不等于所有从属对象已经从节点消失。
八、API 对象中的 Spec、Status 和版本关系
8.1 Spec 与 Status 的因果方向
通常:
用户或上层控制器 → spec
系统控制器和 kubelet → status
例如:
spec:
replicas: 2
status:
replicas: 2
readyReplicas: 1
availableReplicas: 1
这表示期望副本数为 2,但当前只有 1 个可用副本。控制器根据这种差异继续工作。
Status 不是绝对真相,它是某个组件在某个时刻观察并写入 API 的结果。API Server 只负责保存这个字段,不会自动判断 Pod 是否真的能提供业务服务。
8.2 API 版本和兼容
一个对象的 apiVersion 与 kind 共同决定其 API 类型。例如:
apiVersion: apps/v1
kind: Deployment
当前稳定 API 应优先使用稳定版本,例如 Deployment 使用 apps/v1。但“稳定”主要表示 API 契约和生命周期承诺更强,不表示所有字段在所有 Kubernetes 版本中都完全相同。
需要注意:
- Kubernetes 会逐步弃用旧 API;
- API 版本弃用与字段弃用是两个问题;
- 集群支持的资源版本可以通过发现 API 查询;
- CRD、Admission Webhook 和云厂商扩展会增加版本差异;
- 托管 Kubernetes 可能隐藏部分控制面参数和组件。
查询集群支持的资源:
kubectl api-resources
kubectl api-versions
查询对象实际 API 信息:
kubectl explain deployment.spec
kubectl explain pod.spec.containers.resources
kubectl explain 使用服务器提供的 OpenAPI Schema,因而比只查看本地文档更适合确认当前集群支持的字段。
九、高可用架构和并发行为
9.1 API Server 通常可以水平扩展
多个 API Server 可以对外提供服务,但它们通常共享同一个 etcd 集群。客户端请求可以被负载均衡到不同 API Server,状态一致性由共同的持久化层和 API 语义保证。
API Server 多副本并不等于整个控制平面高可用:
- etcd 仍需要多数派;
- Scheduler 和 Controller Manager 需要处理领导者选举或避免重复主动执行;
- 云控制器可能还有独立的可用性要求;
- 负载均衡器、证书和网络也可能成为单点。
9.2 多个控制器为什么不会无限重复创建
控制器通常使用以下机制减少并发冲突:
- 根据对象 UID、标签和 ownerReferences 判断归属;
- 对 API 更新使用 resourceVersion;
- 遇到
409 Conflict时重新读取对象; - 通过队列串行处理同一资源键;
- 让动作幂等;
- 使用 Server-Side Apply 或明确的字段管理策略处理字段所有权。
但这不意味着所有更新都会自动合并。两个控制器如果竞争写入同一字段,可能互相覆盖或持续冲突。生产系统中应明确哪个控制器拥有哪个字段,避免多个组件同时管理同一份配置。
9.3 Watch 断开时发生什么
一个典型恢复流程如下:
已有 List(resourceVersion=100)
↓
Watch 从 100 开始
↓
收到若干事件到 120
↓
连接断开
↓
重新建立 Watch
↓
若服务端仍保留历史,从可接受版本继续
↓
若返回资源版本过旧,则重新 List
↓
以新快照建立新的 Watch
如果客户端只依赖“断线前最后一个事件”,而没有重新同步当前对象,就可能漏掉状态变化。可靠控制器必须把 Watch 当作增量提示,并具备全量重新同步能力。
十、故障路径与诊断方法
10.1 API Server 不可用
表现可能包括:
kubectl get pods
返回超时、连接拒绝或 Unable to connect to the server。
诊断方向:
kubectl cluster-info
kubectl get --raw='/readyz?verbose'
在有权限访问控制面节点的自建集群中,还应检查 API Server 进程或静态 Pod、证书、监听端口和到 etcd 的网络。托管集群通常无法直接查看这些组件,只能依赖云厂商控制面状态和审计、事件等接口。
10.2 etcd 不可用
API Server 可能仍能建立 HTTP 连接,但读写请求异常、延迟显著升高或返回内部错误。此时要区分:
- API Server 自身健康;
- API Server 是否能连接 etcd;
- etcd 是否具备多数派;
- etcd 磁盘是否能及时提交事务。
不能只检查 kube-apiserver 进程存在,就判定控制面正常。
10.3 Scheduler 不调度
检查:
kubectl get pod <pod-name> -o yaml
kubectl describe pod <pod-name>
重点看:
spec.nodeName是否为空;PodScheduled条件;- Events 中的
FailedScheduling; - 节点是否有足够的 allocatable 资源;
- taint 是否有对应 toleration;
- affinity、拓扑约束和 PVC 是否满足。
常见误判是只看节点当前使用率,而忽略 Pod requests、污点、亲和性和卷拓扑。
10.4 控制器不调谐
例如 Deployment 长期不是期望副本数:
kubectl describe deployment web
kubectl get rs
kubectl get pods
kubectl get events --sort-by=.lastTimestamp
需要依次判断:
- Deployment 的
spec是否正确; - ReplicaSet 是否被创建;
- ReplicaSet 的 selector 是否能匹配 Pod;
- Pod 是否创建但未调度;
- Pod 是否已调度但节点无法启动;
- 控制器是否因权限、Webhook 或 API Server 错误无法写入。
只查看 Deployment 的 status 不能定位所有问题,必须沿着对象关系向下检查。
10.5 Node 不健康
检查:
kubectl get nodes
kubectl describe node <node-name>
kubectl get lease -n kube-node-lease
如果 Node 是 NotReady,继续查看:
- kubelet 日志;
- 容器运行时状态;
- 磁盘和 inode;
- 内存压力;
- CNI 网络;
- 节点到 API Server 的网络和证书;
- 时间同步。
节点状态是控制面观察到的结果,Ready 也不能证明每个业务端口都可访问;业务健康仍应由 Readiness Probe、Service 流量和应用监控验证。
十一、常见误解和真实边界
误解一:API Server 会启动 Pod
API Server 只接受、校验、存储和分发 API 对象。真正启动容器的路径是:
API Server → kubelet → CRI → 容器运行时
Scheduler 只负责把 Pod 与 Node 绑定,Controller Manager 负责维持对象关系和副本状态。
误解二:Scheduler 负责把 Pod 放到“最空闲”的节点
Scheduler 主要依据声明的资源请求和调度约束工作,不是简单读取实时 CPU 利用率。一个节点当前很空闲,但如果 allocatable 中可供请求使用的容量不足,仍可能无法调度。
误解三:删除 API 对象会立即杀死所有进程
删除 Pod 通常会触发终止流程,kubelet 根据对象变化停止容器;但网络断开、节点失联、Finalizer、强制删除和运行时异常都会改变过程。删除 API 对象首先改变的是控制面状态,不是对远端进程提供绝对即时的强制终止保证。
误解四:etcd 是 Kubernetes 的普通缓存
etcd 是控制面关键持久状态存储。缓存通常可以丢失并重建,而 etcd 中的对象丢失可能意味着期望状态、Secret、证书、Service 配置和控制器关系一起丢失。生产环境必须把 etcd 备份和恢复当作灾难恢复能力的一部分。
误解五:Pod 运行了就代表服务可用
Pod 的生命周期状态、容器状态和业务可用性不同:
PodScheduled:已经选择节点
Running:容器处于运行阶段
Ready:通过就绪条件,可接收流量
业务可用:应用真正满足业务协议和依赖
Service 通常依据就绪状态选择端点,但应用自身的错误、依赖故障和网络策略仍可能导致请求失败。
十二、如何用一条故障链理解所有组件
面对一个“Deployment 已提交但应用不可用”的问题,可以沿着状态链逐段验证:
客户端请求
↓
API Server 是否接受?
↓
Deployment 是否持久化?
↓
ReplicaSet 是否出现?
↓
Pod 是否创建?
↓
Pod 是否被 Scheduler 绑定?
↓
目标 Node 是否 Ready?
↓
kubelet 是否创建容器?
↓
容器是否启动?
↓
Readiness 是否通过?
↓
Service 是否拥有正确 EndpointSlice?
↓
网络和应用请求是否成功?
每一段对应不同组件:
| 阶段 | 主要组件 | 典型验证 |
|---|---|---|
| 请求接受和对象持久化 | API Server、etcd | kubectl apply、API 返回码 |
| 副本对象生成 | Controller Manager | Deployment、ReplicaSet |
| 节点选择 | Scheduler | PodScheduled、FailedScheduling |
| 节点执行 | kubelet、容器运行时 | Pod Events、节点日志 |
| 节点和 Pod 状态 | kubelet、Controller Manager | Node Conditions、Pod Status |
| Service 端点 | EndpointSlice Controller、网络组件 | EndpointSlice、Service |
| 业务可用性 | 应用和探针 | Readiness、实际请求 |
这条链体现了 Kubernetes 的核心设计:每个组件只负责一部分状态转换,组件之间通过 API 对象和事件协作;状态变化是异步的,错误必须在对应的层次上诊断。
十三、生产环境中的版本和实现差异
Kubernetes 的 API、控制器行为和节点组件在版本演进中会发生变化。使用当前稳定 API 时,仍应注意以下边界:
- 不要依赖已弃用或已移除的 API 版本;
- 升级前检查资源 API 版本、Webhook、CRD 和客户端兼容性;
- Kubernetes 遵循控制面与 kubelet 的版本偏差规则,但具体允许范围应以目标版本官方文档为准;
- Scheduler 插件、默认配置、特性门控和控制器参数可能随版本变化;
- etcd 的部署方式、备份工具和证书路径可能由发行版决定;
- 云厂商可能托管 API Server、Scheduler、Controller Manager 和 etcd,使用户无法直接修改其参数或查看日志;
- 网络插件、Ingress、负载均衡器、CSI 和云控制器会引入超出核心 Kubernetes 的行为。
规范保证的是 API 语义和资源生命周期;具体调度打分、控制器重试间隔、云负载均衡收敛时间、网络规则实现和日志位置,往往属于实现或平台差异,不能写成跨集群都成立的固定事实。
Kubernetes 架构可以归纳为一条闭环:
用户声明 spec
→ API Server 校验并写入 etcd
→ Controller Manager 和 Scheduler 观察对象
→ 控制器创建对象,Scheduler 绑定 Node
→ kubelet 在 Node 上执行 Pod
→ Node 和 Pod 回报 status
→ 控制器根据新状态继续调谐
API Server 提供统一入口,etcd 保存持久状态,Controller Manager 维持资源关系,Scheduler 负责节点选择,Node 执行实际工作负载。它们通过 API、Watch、队列、版本控制和幂等调谐共同构成一个最终一致的控制系统。理解这条状态链,才能正确区分“请求已接受”“对象已创建”“Pod 已调度”“容器已运行”和“服务真正可用”这几个不同阶段。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 下一篇:Kubernetes API 对象:GVK、Metadata、Spec、Status、版本和兼容
- 延伸:Kubernetes 调谐循环:Desired State、Watch、Queue、幂等和最终一致
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论