mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2026 字
6 分钟
深度剖析:创建一个 Deployment 时,Kubernetes 底层发生了什么
2023-10-08

当你执行 kubectl apply -f deployment.yaml 时,Kubernetes 并不是”一次性”创建出所有 Pod。它启动了一个多阶段的控制循环:kubectl 将 YAML 提交给 API Server,API Server 经过认证、鉴权、准入控制后写入 etcd;Deployment Controller 监听到新对象,创建 ReplicaSet;ReplicaSet Controller 再创建 Pod;Scheduler 为 Pod 选择节点;kubelet 在节点上启动容器。

理解这个流程,就理解了 Kubernetes 声明式 API 与控制器模式的核心机制。

一、全局视角:创建 Deployment 的完整流程#

sequenceDiagram participant K as kubectl participant API as API Server participant AUTH as 认证/鉴权 participant ADM as 准入控制 participant ETCD as etcd participant DC as Deployment Controller participant RC as ReplicaSet Controller participant SCHED as Scheduler participant KL as kubelet participant CRI as 容器运行时 participant CNI as CNI 插件 K->>API: POST /apis/apps/v1/deployments API->>AUTH: 认证(ServiceAccount/token) AUTH->>ADM: 鉴权(RBAC) ADM->>ADM: 准入控制链(Mutating→Validating) ADM->>ETCD: 持久化 Deployment 对象 ETCD-->>DC: Watch 事件通知 DC->>API: 创建 ReplicaSet API->>ETCD: 持久化 ReplicaSet ETCD-->>RC: Watch 事件通知 RC->>API: 创建 Pod API->>ETCD: 持久化 Pod ETCD-->>SCHED: Watch 未调度 Pod SCHED->>API: 更新 Pod.nodeName API->>ETCD: 更新 Pod ETCD-->>KL: Watch 分配到本节点的 Pod KL->>CRI: 创建容器(CRI API) KL->>CNI: 配置网络(CNI ADD) KL->>API: 更新 Pod 状态为 Running

二、第一阶段:kubectl → API Server#

2.1 kubectl 的处理#

kubectl apply 并不是简单地发送 YAML 文本。它做了以下工作:

  1. 读取 YAML 文件:解析为 Kubernetes API 对象
  2. 计算变更:对比当前集群中的对象和 YAML 中的对象,生成增量补丁(如果对象已存在)
  3. 发送请求:通过 HTTP POST(新对象)或 PATCH(更新对象)发送到 API Server
# 查看 kubectl 实际发送的请求
kubectl apply -f deployment.yaml --v=8
# 输出(简化):
# I0513 POST https://api-server:6443/apis/apps/v1/namespaces/default/deployments
# Request Body: {"kind":"Deployment","metadata":{"name":"nginx"},...}

2.2 API Server 认证#

API Server 首先验证请求者的身份:

认证方式说明适用场景
X509 证书kubectl 使用 kubeconfig 中的客户端证书管理员操作
ServiceAccount TokenPod 内自动挂载的 JWT TokenPod 内访问 API
OIDC Token外部身份提供商(如 LDAP、GitHub)企业统一认证

2.3 API Server 鉴权(RBAC)#

认证通过后,API Server 检查请求者是否有权限执行该操作:

用户: admin
操作: POST /apis/apps/v1/namespaces/default/deployments
RBAC 检查: ClusterRoleBinding admin → ClusterRole cluster-admin → 允许

RBAC 的检查逻辑:遍历所有与用户关联的 RoleBinding/ClusterRoleBinding,检查对应的 Role/ClusterRole 是否包含匹配的规则。

2.4 准入控制链#

鉴权通过后,请求进入准入控制链(Admission Chain)。准入控制器可以修改(Mutating)或验证(Validating)请求对象:

flowchart LR A["请求对象"] --> M["Mutating 阶段<br/>修改对象"] M --> V["Validating 阶段<br/>验证对象"] V --> E["写入 etcd"] V -->|"验证失败"| R["拒绝请求<br/>返回错误"] style M fill:#fff3e0,stroke:#e65100 style V fill:#e8f5e9,stroke:#2e7d32 style R fill:#ffcdd2,stroke:#c62828

常见的准入控制器:

控制器类型作用
NamespaceLifecycleValidating防止在终止中的 Namespace 创建对象
LimitRangerMutating为未设置资源限制的 Pod 注入默认值
ServiceAccountMutating为 Pod 注入 ServiceAccount 和 Token Volume
PodSecurityValidating检查 Pod 是否符合安全标准
ResourceQuotaValidating检查是否超出 Namespace 配额
DefaultTolerationSecondsMutating为 Pod 注入默认容忍时间
MutatingAdmissionWebhookMutating调用自定义 Webhook 修改对象
ValidatingAdmissionWebhookValidating调用自定义 Webhook 验证对象
Note

Mutating 阶段先执行,Validating 阶段后执行。Mutating 阶段可以修改对象(如注入 sidecar、设置默认值),Validating 阶段只能接受或拒绝。Webhook 准入控制器是扩展 Kubernetes 的核心机制——详见 Kubernetes 扩展机制

三、第二阶段:etcd 持久化#

准入控制通过后,API Server 将对象序列化为 JSON 并写入 etcd:

{
"kind": "Deployment",
"apiVersion": "apps/v1",
"metadata": {
"name": "nginx",
"namespace": "default",
"uid": "a1b2c3d4-...",
"creationTimestamp": "2026-05-13T10:00:00Z",
"resourceVersion": "12345"
},
"spec": {
"replicas": 3,
"selector": {...},
"template": {...}
},
"status": {
"replicas": 0,
"updatedReplicas": 0,
"conditions": [...]
}
}

etcd 的关键特性:

  • 强一致性:使用 Raft 协议,确保所有节点数据一致
  • Watch 支持:客户端可以监听键的变化,实时获取通知
  • 版本管理:每个对象有 resourceVersion,用于乐观并发控制
Warning

etcd 是集群的核心——如果 etcd 不可用,整个集群无法创建/更新/删除任何对象。生产环境必须部署 3-5 个 etcd 节点,确保高可用。

四、第三阶段:Deployment Controller#

4.1 Informer 机制#

Controller 不直接轮询 API Server,而是使用 Informer 机制:

flowchart LR A["API Server"] -->|"List + Watch"| B["Informer<br/>本地缓存"] B --> C["OnAdd 回调<br/>新 Deployment"] B --> D["OnUpdate 回调<br/>Deployment 变更"] B --> E["OnDelete 回调<br/>Deployment 删除"] C --> F["Controller<br/>处理逻辑"] style B fill:#e3f2fd,stroke:#1565c0 style F fill:#fff3e0,stroke:#e65100

Informer 的工作流程:

  1. List:首次启动时,从 API Server 获取所有 Deployment 对象,建立本地缓存
  2. Watch:之后监听 API Server 的变更事件,实时更新本地缓存
  3. 回调:缓存更新后触发 OnAdd/OnUpdate/OnDelete 回调,Controller 在回调中执行业务逻辑

Informer 的优势:减少 API Server 压力(不需要反复 List),响应速度快(Watch 事件实时推送)。

4.2 Deployment Controller 的处理#

当 Deployment Controller 收到新 Deployment 的 OnAdd 回调时:

  1. 检查是否已有匹配的 ReplicaSet
  2. 如果没有,创建新的 ReplicaSet(初始版本 revision=1
  3. 设置 ReplicaSet 的 replicas 为 Deployment 的 replicas
  4. 更新 Deployment 的 status 字段
# Deployment Controller 创建的 ReplicaSet
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-7d6f5c4b2d # 自动生成的名称(包含 Pod 模板哈希)
labels:
app: nginx
pod-template-hash: 7d6f5c4b2d # Deployment 自动添加的标签
ownerReferences: # 声明 ReplicaSet 属于 Deployment
- apiVersion: apps/v1
kind: Deployment
name: nginx
uid: a1b2c3d4-...
controller: true
spec:
replicas: 3
selector:
matchLabels:
app: nginx
pod-template-hash: 7d6f5c4b2d
template: ... # 与 Deployment 的 Pod 模板一致

ownerReferences 是 Kubernetes 的垃圾回收机制——当 Deployment 被删除时,所有引用它的 ReplicaSet 和 Pod 都会被自动回收。

五、第四阶段:ReplicaSet Controller#

ReplicaSet Controller 监听新 ReplicaSet 的创建:

  1. 计算当前匹配 selector 的 Pod 数量
  2. 当前数量 < replicas(0 < 3):需要创建 3 个 Pod
  3. 按 ReplicaSet 的 template 创建 Pod,每个 Pod 的 ownerReferences 指向 ReplicaSet
# ReplicaSet Controller 创建的 Pod
apiVersion: v1
kind: Pod
metadata:
name: nginx-7d6f5c4b2d-abc12 # 自动生成
labels:
app: nginx
pod-template-hash: 7d6f5c4b2d
ownerReferences:
- apiVersion: apps/v1
kind: ReplicaSet
name: nginx-7d6f5c4b2d
controller: true
spec:
nodeName: "" # 未指定节点,等待调度
containers:
- name: nginx
image: nginx:1.25

注意 Pod 的 nodeName 为空——这意味着 Pod 还没有被调度,Scheduler 将负责选择目标节点。

六、第五阶段:Scheduler 调度#

Scheduler 监听 nodeName="" 的 Pod,为它们选择最合适的 Node。

6.1 调度算法#

flowchart TB A["未调度 Pod"] --> B["预选 Filter<br/>排除不满足条件的 Node"] B --> C["优选 Score<br/>为满足条件的 Node 打分"] C --> D["选择最高分 Node"] D --> E["绑定 Bind<br/>更新 Pod.nodeName"] style A fill:#e3f2fd,stroke:#1565c0 style E fill:#c8e6c9,stroke:#2e7d32

**预选(Filter)**阶段排除不满足硬性条件的 Node:

预选条件说明
PodFitsResourcesNode 的可分配资源是否满足 Pod 的 requests
PodFitsHostPortsNode 的 HostPort 是否已被占用
PodMatchNodeSelectorPod 的 nodeSelector/nodeAffinity 是否匹配
NoDiskConflictPod 的 Volume 是否与 Node 上已有 Volume 冲突
PodToleratesNodeTaintsPod 的 tolerations 是否能容忍 Node 的 taints

**优选(Score)**阶段为满足条件的 Node 打分:

优选策略说明
LeastRequestedPriority优先选择资源使用率最低的 Node(Spread 策略)
MostRequestedPriority优先选择资源使用率最高的 Node(Bin-Packing 策略)
BalancedResourceAllocationCPU 和内存使用率尽量均衡
NodeAffinityPriority优先满足 Node 亲和性
ImageLocalityPriority优先选择已拉取所需镜像的 Node

6.2 绑定#

Scheduler 选择 Node 后,向 API Server 发送 Bind 请求:

apiVersion: v1
kind: Binding
metadata:
name: nginx-7d6f5c4b2d-abc12
target:
apiVersion: v1
kind: Node
name: node-2

API Server 更新 Pod 的 nodeName 字段为 node-2

七、第六阶段:kubelet 启动容器#

kubelet 在每个 Node 上运行,通过 Watch 监听分配到本节点的 Pod。

7.1 kubelet 的 Pod 处理流程#

flowchart TB A["Watch 到新 Pod<br/>nodeName=node-2"] --> B["生成 CRI 配置"] B --> C["调用 CRI 创建容器"] C --> D["调用 CNI 配置网络"] D --> E["调用 CSI 挂载存储"] E --> F["启动容器"] F --> G["执行 PostStart Hook"] G --> H["等待 Startup Probe"] H -->|"通过"| I["启动 Liveness/Readiness Probe"] I -->|"Readiness 通过"| J["更新 Pod 状态为 Ready"] style A fill:#e3f2fd,stroke:#1565c0 style J fill:#c8e6c9,stroke:#2e7d32

7.2 CRI:容器创建#

kubelet 通过 CRI(Container Runtime Interface)与容器运行时通信:

kubelet → containerd(通过 gRPC)
CreateContainer(podConfig) → 返回 containerID
StartContainer(containerID) → 容器进程启动

CRI 的关键步骤:

  1. 拉取镜像(image: nginx:1.25
  2. 创建 Pause 容器(持有 Pod Network Namespace)
  3. 创建应用容器(加入 Pause 的 Network Namespace)
  4. 启动容器进程

7.3 CNI:网络配置#

kubelet 调用 CNI 插件为 Pod 配置网络:

kubelet → CNI 插件(如 Calico/Cilium)
ADD: 创建 veth pair、配置 IP、设置路由

CNI 插件的工作:

  1. 创建 veth pair(一端在 Pod Network Namespace,一端在宿主机)
  2. 为 Pod 分配 IP(从 Pod CIDR 中分配)
  3. 配置 Pod 内的路由和 DNS
  4. 配置宿主机的路由(将 Pod IP 路由到 veth 对)
  5. 如果是跨节点通信,配置节点间的路由或封装规则

7.4 CSI:存储挂载#

如果 Pod 引用了 PVC,kubelet 调用 CSI 插件挂载存储:

kubelet → CSI 插件
NodeStageVolume → 将存储挂载到临时目录
NodePublishVolume → 将临时目录 bind-mount 到 Pod 目录

7.5 状态上报#

容器启动后,kubelet 定期向 API Server 上报 Pod 状态:

status:
phase: Running
conditions:
- type: Ready
status: "True"
containerStatuses:
- name: nginx
ready: true
restartCount: 0
state:
running:
startedAt: "2026-05-13T10:01:30Z"

八、滚动更新的完整流程#

当更新 Deployment 的镜像版本时(nginx:1.25nginx:1.26),触发滚动更新:

sequenceDiagram participant K as kubectl participant API as API Server participant ETCD as etcd participant DC as Deployment Controller participant RC_OLD as ReplicaSet v1 participant RC_NEW as ReplicaSet v2 participant SCHED as Scheduler participant KL as kubelet K->>API: PATCH Deployment image=nginx:1.26 API->>ETCD: 更新 Deployment ETCD-->>DC: Watch 事件 DC->>API: 创建 ReplicaSet v2 (replicas=0) DC->>API: 更新 ReplicaSet v2 replicas=1 DC->>API: 更新 ReplicaSet v1 replicas=2 API->>ETCD: 持久化变更 ETCD-->>RC_NEW: 创建 1 个 Pod ETCD-->>RC_OLD: 缩容到 2 个 Pod RC_NEW->>API: 创建 Pod (nginx:1.26) SCHED->>API: 调度 Pod KL->>KL: 启动新 Pod Note over KL: Readiness Probe 通过后 DC->>API: ReplicaSet v2 replicas=2 DC->>API: ReplicaSet v1 replicas=1 Note over DC: 继续迭代... DC->>API: ReplicaSet v2 replicas=3 DC->>API: ReplicaSet v1 replicas=0

滚动更新的关键控制参数:

spec:
strategy:
rollingUpdate:
maxSurge: 1 # 最多多创建 1 个 Pod
maxUnavailable: 0 # 最多允许 0 个 Pod 不可用
minReadySeconds: 30 # Pod Ready 后等待 30 秒才继续
progressDeadlineSeconds: 600 # 10 分钟内未完成则标记为 Progressing=False

Deployment Controller 的更新逻辑:

  1. 创建新 ReplicaSet(revision=2),初始 replicas=0
  2. 按策略逐步扩容新 ReplicaSet、缩容旧 ReplicaSet
  3. 每次扩容前等待新 Pod 的 Readiness Probe 通过
  4. 如果 progressDeadlineSeconds 内未完成,标记 Deployment 为失败
  5. 旧 ReplicaSet 保留(replicas=0),用于回滚

九、回滚机制#

kubectl rollout undo deployment/nginx

回滚时,Deployment Controller:

  1. 找到上一个 ReplicaSet(revision=1
  2. 将旧 ReplicaSet 的 replicas 扩容到 3
  3. 将当前 ReplicaSet(revision=2)的 replicas 缩容到 0
  4. 更新 Deployment 的 image 回退到 nginx:1.25

整个过程与滚动更新的逻辑相同——只是方向相反。

总结#

阶段组件关键机制
kubectl → API Serverkubectl, API Server认证/鉴权/准入控制链
etcd 持久化API Server, etcdRaft 强一致性、Watch 机制
Deployment → ReplicaSetDeployment ControllerInformer + 控制循环
ReplicaSet → PodReplicaSet ControllerPod 模板 + ownerReferences
Pod 调度Scheduler预选 Filter + 优选 Score
容器启动kubeletCRI 创建容器 + CNI 配置网络 + CSI 挂载存储
滚动更新Deployment Controller新 RS 扩容 + 旧 RS 缩容
回滚Deployment Controller旧 RS 扩容 + 新 RS 缩容

整个流程的核心是声明式 API + 控制器模式——kubectl 只描述期望状态,多个控制器各自负责一部分收敛工作,通过 Watch 机制实时响应变更,通过 ownerReferences 管理对象间的依赖关系。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

深度剖析:创建一个 Deployment 时,Kubernetes 底层发生了什么
https://blog.souloss.com/posts/kubernetes/k8s-what-happens-deployment/
作者
Souloss
发布于
2023-10-08
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时