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。

理解这些组件前,还需要掌握三个前置概念:

  1. Kubernetes 中的大多数资源都是 API 对象;
  2. 组件通常通过 watch 观察对象变化,而不是互相直接调用内部函数;
  3. 控制器通过调谐循环实现最终一致,而不是一次操作就保证全局状态立即完成。

一、先建立整体模型:声明式对象和调谐循环

一个 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 最终在节点上启动容器。

可以把一个控制器的目标写成:

Reconcile(Scurrent,Sdesired)A\text{Reconcile}(S_{\text{current}}, S_{\text{desired}}) \rightarrow A

其中:

  • SdesiredS_{\text{desired}} 是 API 对象中声明的期望状态;
  • ScurrentS_{\text{current}} 是控制器观察到的当前状态;
  • AA 是需要执行的动作,例如创建 Pod、更新副本数或写入 Status。

理想情况下,控制器重复执行调谐后满足:

ScurrentSdesiredS_{\text{current}} \rightarrow S_{\text{desired}}

这里的箭头不是“一次调用立即完成”,而是表示经过多个异步步骤最终收敛。网络延迟、镜像拉取、节点故障、权限错误和资源不足都会让收敛变慢或无法完成。

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[容器进程]

关键路径是:

  1. 客户端向 API Server 提交对象;
  2. API Server 完成认证、鉴权、准入和校验;
  3. API Server 将持久化对象写入 etcd;
  4. 控制器和 Scheduler 通过 API Server 读取对象并建立 Watch;
  5. 控制器更新 API 对象或创建新对象;
  6. kubelet 从 API Server 得到 Pod 配置,在本地运行容器;
  7. kubelet 将节点和 Pod 状态回报给 API Server;
  8. 控制器根据新的 Status 再次调谐。

组件通常不应该绕过 API Server 直接访问 etcd。etcd 是 Kubernetes 的内部存储实现,而 Kubernetes API 的认证、鉴权、准入、版本转换和资源语义都由 API Server 提供。


二、API Server:集群状态的统一入口

2.1 API Server 做什么

kube-apiserver 是 Kubernetes API 的 HTTP(S) 服务端。它承担几类职责:

  1. 提供 API 端点
    例如:

    /api/v1/namespaces/default/pods
    /apis/apps/v1/namespaces/default/deployments
    /api/v1/nodes
    
  2. 解析和转换 API 对象
    客户端发送的版本可能不是存储版本。API Server 可以在外部版本和内部表示之间转换。

  3. 认证请求者身份
    常见身份来源包括客户端证书、Bearer Token、OIDC、Webhook 等。具体可用方式取决于集群配置。

  4. 鉴权请求者是否有权限
    Kubernetes 常用 RBAC 判断主体是否可以对某个资源执行某种动作,例如 get podscreate deployments

  5. 执行准入控制
    准入阶段可以拒绝请求,也可以修改请求对象。内置准入插件和 Admission Webhook 都可能参与这一过程。

  6. 校验资源并持久化
    API Server 验证字段、版本和业务规则,然后将持久化结果写入 etcd。

  7. 提供 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 为什么必须配合

控制器通常采用如下模式:

  1. List 某类资源,获得当前快照;
  2. 保存列表中对象的最新 resourceVersion
  3. 从这个版本开始 Watch 后续事件;
  4. 收到事件后把相关对象放入工作队列;
  5. 调谐时再次读取对象,并根据最新状态采取动作。

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 集群节点数为 NN,形成多数派所需的节点数为:

Q=N2+1Q = \left\lfloor \frac{N}{2} \right\rfloor + 1

例如:

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 为例,控制器可以按以下步骤工作:

  1. 读取 ReplicaSet 的 spec.replicas

  2. 根据 selector 查找匹配的 Pod;

  3. 过滤不属于当前 ReplicaSet 的对象;

  4. 计算差值:

    Δ=desiredReplicascurrentReplicas\Delta = \text{desiredReplicas} - \text{currentReplicas}

  5. 如果 Δ>0\Delta > 0,创建若干 Pod;

  6. 如果 Δ<0\Delta < 0,删除多余 Pod;

  7. 更新 ReplicaSet 的 status.replicasreadyReplicas 等观察字段;

  8. 等待新的 Pod 事件或定时重新调谐。

假设期望副本为 3,当前匹配 Pod 数为 1:

desired = 3
current = 1
delta   = 2

控制器会尝试创建 2 个 Pod,但创建成功不等于 Pod 已经 Ready。之后 kubelet 更新 Pod 状态,控制器再次调谐,可能得到:

desired = 3
current = 3
ready   = 1

这时副本数量已经满足,但可用副本仍未满足。specstatus 分别表达“想要什么”和“现在怎么样”,不能混为一谈。

4.3 为什么控制器必须幂等

幂等意味着同一个调谐动作执行一次或多次,最终结果等价。控制器需要幂等,是因为:

  • Watch 事件可能重复;
  • 控制器可能重启;
  • API 请求可能超时,但服务端实际上已经成功;
  • 进程可能在动作完成后、记录结果前崩溃;
  • 多个事件可能合并为一次调谐;
  • 队列中的对象可能被多次入队。

错误的控制器逻辑可能是:

每收到一次 ADD 事件,就无条件创建一个 Pod

如果事件重复,就会创建多余 Pod。正确逻辑应是:

读取当前实际对象数量
计算与期望数量的差值
只创建或删除使状态接近目标所需的对象

这也是“事件驱动”与“状态驱动”的区别:事件触发调谐,但状态决定动作。

4.4 工作队列与失败重试

常见控制器实现会将资源键,例如:

namespace/name

放入工作队列。处理流程通常是:

  1. 从队列取出键;
  2. 从 Informer 缓存读取对象;
  3. 执行调谐;
  4. 成功则处理完成;
  5. 暂时性错误则重新入队并退避;
  6. 永久性错误则记录事件或更新 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 使用可扩展的调度框架。不同版本的插件和扩展点可能变化,但基本思想可以概括为:

  1. 从待调度 Pod 队列取出一个 Pod;
  2. 根据资源、约束和策略筛选可行节点;
  3. 对可行节点打分;
  4. 选择得分最高或经过决策的节点;
  5. 通过 API Server 完成绑定;
  6. 若绑定失败或没有可行节点,记录失败并根据策略重试。

筛选条件可能包括:

  • Pod 请求的 CPU、内存等资源;
  • Node 的 Ready 状态;
  • NodeSelector;
  • Node Affinity;
  • Pod Affinity/Anti-Affinity;
  • Taints 和 Tolerations;
  • 拓扑分布约束;
  • Volume 的可用性和拓扑限制;
  • 调度器配置的其他插件。

这里的资源计算通常基于 Pod 的资源请求,而不是当前实际 CPU 使用率:

node allocatablepod requestsnew pod request\text{node allocatable} - \sum \text{pod requests} \geq \text{new pod request}

例如,节点可分配内存为 8 GiB,已有 Pod 的内存请求总和为 6 GiB,新 Pod 请求 3 GiB:

86=2<38 - 6 = 2 < 3

即使节点当前监控显示只使用了 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 资源发送轻量心跳。控制面根据心跳和状态判断节点是否健康。

节点失联后的处理不是瞬间完成的:

  1. kubelet 停止更新心跳;
  2. 控制器观察到节点长期没有更新;
  3. Node Condition 可能变为 Unknown
  4. 节点可能被标记为不可调度或触发 Pod 驱逐相关流程;
  5. 上层控制器根据 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 版本和兼容

一个对象的 apiVersionkind 共同决定其 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

需要依次判断:

  1. Deployment 的 spec 是否正确;
  2. ReplicaSet 是否被创建;
  3. ReplicaSet 的 selector 是否能匹配 Pod;
  4. Pod 是否创建但未调度;
  5. Pod 是否已调度但节点无法启动;
  6. 控制器是否因权限、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 PodScheduledFailedScheduling
节点执行 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、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。