25 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-07-07 15:48 |
Deployment 与 ReplicaSet
概述
Deployment 是 Kubernetes 中最常用的无状态工作负载控制器,它在 ReplicaSet 之上提供了声明式更新、滚动发布、版本回滚等高级能力。日常开发中,绝大多数无状态服务(Web API、微服务后端、定时任务 Worker)都通过 Deployment 来管理 Pod 的生命周期。理解 Deployment 与 ReplicaSet 的协作关系,是掌握 Kubernetes 应用交付模型的基础。
ReplicaSet 详解
什么是 ReplicaSet
ReplicaSet(简称 RS)的核心职责只有一件事:确保指定数量的 Pod 副本始终处于运行状态。如果某个 Pod 意外退出或被删除,RS 会立即创建新的 Pod 来弥补差额;如果 Pod 数量超过期望值,RS 会删除多余的 Pod。
# 一个独立的 ReplicaSet 示例(通常不直接创建,由 Deployment 自动管理)
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: nginx-rs
namespace: default
spec:
replicas: 3 # 期望副本数
selector:
matchLabels:
app: nginx # Label Selector:通过标签匹配 Pod
template:
metadata:
labels:
app: nginx # Pod 必须携带此标签才能被 RS 管理
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
Label Selector 机制
RS 通过 Label Selector 来发现和管理 Pod,而非维护一个固定的 Pod 列表。这意味着:
- 手动创建的 Pod 只要标签匹配,也会被 RS 纳入管理(数量超出则删除,不足则新建)
- 修改某个 Pod 的标签可以将其从 RS 管理中"摘除",这在调试时非常有用
# 将一个 Pod 从 RS 中移除(不删除 Pod,仅修改标签使其脱离管理)
kubectl label pod nginx-rs-xxxx app=detached --overwrite
# 此时 RS 会发现少了一个副本,自动新建一个 Pod 来补足
[!tip] 调试技巧 需要排查某个 Pod 问题时,先把它从 RS 中"摘出来"(修改标签),这样 RS 不会因为 Pod 异常而自动重建,你可以从容排查。
ReplicaSet vs ReplicationController
ReplicationController(RC)是 RS 的前身,两者核心区别在于 Selector 语义:
| 对比维度 | ReplicationController | ReplicaSet |
|---|---|---|
| Selector 类型 | 仅支持等式(env=prod) |
支持等式 + 集合(env in (prod, staging)) |
| 标签匹配灵活性 | 低 | 高 |
| 推荐程度 | 已废弃,不建议使用 | 推荐使用 |
| 滚动更新 | 需要 kubectl rolling-update(客户端执行) |
由 Deployment 在服务端协调 |
RC 已经是遗留概念,新资源应统一使用 ReplicaSet。不过实践中你几乎不会直接创建 RS,而是通过 Deployment 间接管理。
Deployment 核心机制
声明式管理
Deployment 采用声明式模型:你只需描述"期望状态"(desired state),Kubernetes 控制面会持续将其与"实际状态"(actual state)做对比,自动执行必要的操作来弥合差距。这个循环叫做 Reconcile Loop(调谐循环)。
graph LR
A["用户提交 Deployment<br/>(desired state)"] --> B["Deployment Controller"]
B --> C{"desired == actual ?"}
C -->|No| D["创建/删除/更新<br/>ReplicaSet / Pod"]
D --> B
C -->|Yes| E["状态稳定<br/>不做任何操作"]
工作流程:
- 用户通过
kubectl apply提交 Deployment YAML,声明replicas: 3、image: nginx:1.25等期望状态 - Deployment Controller 观察到当前没有匹配的 RS,创建一个新的 RS
- RS Controller 观察到 Pod 数量为 0,创建 3 个 Pod
- 持续监控:如果某个 Pod 崩溃,RS Controller 检测到 actual < desired,立即补充新 Pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
namespace: default
spec:
replicas: 3 # 声明:我想要 3 个副本
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25 # 声明:我想用这个镜像
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
滚动更新(Rolling Update)
滚动更新是 Deployment 最核心的价值。当你修改了 Pod 模板(比如升级镜像版本),Deployment 不会一次性杀掉所有旧 Pod,而是逐批替换,在更新过程中始终保持服务可用。
关键参数
| 参数 | 默认值 | 含义 |
|---|---|---|
maxSurge |
25% | 更新过程中允许超出 replicas 的最大 Pod 数量 |
maxUnavailable |
25% | 更新过程中允许不可用的最大 Pod 数量 |
[!info] 参数计算 对于
replicas: 4、maxSurge: 25%、maxUnavailable: 25%的配置:
- maxSurge = ceil(4 * 0.25) = 1,最多可以有 5 个 Pod 同时存在
- maxUnavailable = floor(4 * 0.25) = 1,至少保证 3 个 Pod 可用
这意味着每批替换 1 个 Pod,共需 4 批完成更新。
滚动更新过程
sequenceDiagram
participant U as 用户
participant D as Deployment Controller
participant RS1 as ReplicaSet v1
participant RS2 as ReplicaSet v2
U->>D: kubectl apply(image: nginx:1.26)
D->>RS2: 创建新 RS(image: 1.26, replicas: 0)
D->>RS2: scale up +1(新建 1 个新 Pod)
Note over RS1,RS2: maxSurge=1, 新 Pod 启动
D->>RS1: scale down -1(销毁 1 个旧 Pod)
Note over RS1,RS2: maxUnavailable=1, 保证可用
D->>RS2: scale up +1
D->>RS1: scale down -1
Note over RS1,RS2: 重复直到 RS1=0, RS2=4
D->>RS1: 保留 RS1(revision=1, 可回滚)
逐批替换的过程(以 replicas: 4 为例):
| 阶段 | RS v1 (旧) | RS v2 (新) | 总 Pod 数 | 可用 Pod |
|---|---|---|---|---|
| 初始 | 4 | 0 | 4 | 4 |
| 第 1 批 | 4 → 3 | 0 → 1 | 5 | 4 |
| 第 2 批 | 3 → 2 | 1 → 2 | 4 | 4 |
| 第 3 批 | 2 → 1 | 2 → 3 | 4 | 4 |
| 第 4 批 | 1 → 0 | 3 → 4 | 4 | 4 |
滚动更新配置
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 4
strategy:
type: RollingUpdate # 更新策略
rollingUpdate:
maxSurge: 1 # 最多多 1 个 Pod
maxUnavailable: 1 # 最多少 1 个 Pod
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
readinessProbe: # 重要:就绪探针决定新 Pod 何时接收流量
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 5
periodSeconds: 10
[!warning] readinessProbe 的重要性 滚动更新依赖
readinessProbe判断新 Pod 是否就绪。如果没有配置就绪探针,新 Pod 一创建就被视为 Ready,可能导致流量打到尚未初始化完成的 Pod 上,造成请求失败。
回滚策略
Kubernetes 为每次 Deployment 变更自动记录 revision(版本号),支持快速回滚到任意历史版本。
# 查看 rollout 历史
kubectl rollout history deployment nginx-deploy
# 输出示例:
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# 3 <none>
# 查看某个 revision 的详细信息
kubectl rollout history deployment nginx-deploy --revision=2
# 回滚到上一个版本
kubectl rollout undo deployment nginx-deploy
# 回滚到指定版本
kubectl rollout undo deployment nginx-deploy --to-revision=1
# 查看 rollout 状态(是否完成、耗时等)
kubectl rollout status deployment nginx-deploy
revisionHistoryLimit 配置
默认保留 10 个历史 revision。过多的历史 RS 会占用 etcd 资源,过少则可能丢失回滚能力。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
revisionHistoryLimit: 5 # 只保留最近 5 个 revision
# 设为 0 则禁用回滚(不推荐)
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
annotations:
kubernetes.io/change-cause: "升级到 nginx 1.26" # 记录变更原因,显示在 rollout history 中
spec:
containers:
- name: nginx
image: nginx:1.26
[!tip] 记录变更原因 在 Pod template 的
annotations中添加kubernetes.io/change-cause,这样kubectl rollout history会显示每次变更的原因,方便排查。
扩缩容
手动扩缩容
# 命令行直接扩缩
kubectl scale deployment nginx-deploy --replicas=5
# 条件扩缩(只有当前副本数为 3 时才执行)
kubectl scale deployment nginx-deploy --replicas=5 --current-replicas=3
也可以直接修改 YAML 中的 replicas 字段后 kubectl apply。
HPA 自动扩缩
Horizontal Pod Autoscaler(HPA)根据 CPU、内存或自定义指标自动调整 Deployment 的副本数。
# 前置条件:集群中需要安装 Metrics Server
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx-hpa
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deploy # 目标 Deployment
minReplicas: 2 # 最小副本数
maxReplicas: 10 # 最大副本数
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # CPU 使用率超过 60% 时扩容
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 内存使用率超过 80% 时扩容
behavior:
scaleUp:
stabilizationWindowSeconds: 60 # 扩容稳定窗口:60s 内取最大值
policies:
- type: Pods
value: 4 # 每次最多扩 4 个 Pod
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300 # 缩容稳定窗口:300s,防止抖动
policies:
- type: Percent
value: 10 # 每次最多缩 10%
periodSeconds: 60
# 创建 HPA
kubectl apply -f hpa.yaml
# 或用命令行快速创建
kubectl autoscale deployment nginx-deploy --cpu-percent=60 --min=2 --max=10
# 查看 HPA 状态
kubectl get hpa nginx-hpa
[!warning] HPA 与手动 scale 冲突 如果同时使用 HPA 和手动
kubectl scale,HPA 会在下一次指标采集时覆盖手动设置。两者不应混用。
Deployment 更新策略
Deployment 支持两种更新策略,通过 spec.strategy.type 指定:
| 对比维度 | RollingUpdate | Recreate |
|---|---|---|
| 更新方式 | 逐批替换 Pod | 先杀全部旧 Pod,再创建全部新 Pod |
| 更新期间可用性 | 始终可用(前提:正确配置探针) | 有短暂不可用窗口 |
| 资源占用 | 可能临时需要更多资源(maxSurge > 0 时) | 只需要新版本的资源 |
| 适用场景 | 大多数无状态服务 | 不能有两个版本同时运行的场景 |
| 典型用例 | Web 服务、API 后端 | 涉及全局独占资源(如本地文件锁、全局数据库迁移) |
# Recreate 策略示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: legacy-app
spec:
replicas: 3
strategy:
type: Recreate # 全量替换,更新期间服务不可用
selector:
matchLabels:
app: legacy
template:
metadata:
labels:
app: legacy
spec:
containers:
- name: app
image: legacy-app:2.0
[!info] 什么时候用 Recreate? 当新旧版本不能共存时使用 Recreate,比如:应用使用了本地文件锁、需要执行不可逆的数据库 Schema 迁移、或者旧版本会干扰新版本的初始化。大多数情况下 RollingUpdate 是更好的选择。
金丝雀发布与蓝绿部署
Deployment 本身不直接支持金丝雀发布和蓝绿部署,但可以通过组合多个 Deployment + Service 来实现。
金丝雀发布(Canary Release)
核心思路:同时运行两个版本的 Deployment,通过控制副本比例来控制流量分配。
# 主 Deployment:稳定版本,占 90% 流量
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-stable
spec:
replicas: 9 # 9 个副本
selector:
matchLabels:
app: myapp
track: stable
template:
metadata:
labels:
app: myapp
track: stable # 核心标签 app: myapp 相同,Service 可以选中
spec:
containers:
- name: app
image: myapp:v1.0
---
# 金丝雀 Deployment:新版本,占 10% 流量
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-canary
spec:
replicas: 1 # 1 个副本
selector:
matchLabels:
app: myapp
track: canary
template:
metadata:
labels:
app: myapp
track: canary
spec:
containers:
- name: app
image: myapp:v2.0
# Service 通过 app: myapp 同时选中两个 Deployment 的 Pod
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
selector:
app: myapp # 不指定 track,两个版本都会被选中
ports:
- port: 80
targetPort: 8080
流量分配:9 个旧版本 Pod + 1 个新版本 Pod = 约 10% 流量到达新版本。验证无误后,逐步调整副本比例(如 canary: 5, stable: 5),最终全部切换到新版本。
graph LR
Client --> Service
Service -->|~90%| Pod1["Pod v1.0 x9"]
Service -->|~10%| Pod2["Pod v2.0 x1"]
蓝绿部署(Blue-Green Deployment)
核心思路:同时运行蓝(当前版本)和绿(新版本)两套完整的 Deployment,通过切换 Service 的 selector 来瞬间完成流量切换。
# 蓝色 Deployment(当前生产版本)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: myapp:v1.0
---
# 绿色 Deployment(新版本,先部署但不接收流量)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: myapp:v2.0
# Service:切换 selector.version 即可瞬间切换流量
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
selector:
app: myapp
version: blue # 当前指向蓝色,切换为 green 即完成上线
ports:
- port: 80
targetPort: 8080
# 切换流量到绿色版本
kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"green"}}}'
# 回滚:切换回蓝色版本
kubectl patch service myapp-svc -p '{"spec":{"selector":{"version":"blue"}}}'
蓝绿部署的优点是切换瞬间完成(无中间状态),缺点是需要双倍资源。适合对上线/回滚速度要求极高的场景。
Pod 模板字段详解
spec.template.spec 是 Pod 模板的核心,以下是常用字段的逐一说明:
spec:
template:
metadata:
labels: # Pod 标签,用于 Service Selector 和 ReplicaSet 匹配
app: myapp
version: v1
annotations: # 非标识性元数据,供工具或控制器使用
kubernetes.io/change-cause: "初始部署"
spec:
# ---- 容器定义 ----
containers:
- name: app # 容器名称,Pod 内唯一
image: myapp:v1.0 # 镜像地址
imagePullPolicy: IfNotPresent # Always / IfNotPresent / Never
ports:
- containerPort: 8080 # 容器监听端口(声明性质,不强制)
env: # 环境变量
- name: APP_ENV
value: "production"
- name: DB_PASSWORD
valueFrom:
secretKeyRef: # 从 Secret 读取敏感信息
name: db-secret
key: password
resources: # 资源请求与限制
requests: # 调度依据:节点至少需要这么多资源
cpu: 100m
memory: 128Mi
limits: # 上限:超出会被 OOM Kill 或 CPU 限流
cpu: 500m
memory: 256Mi
readinessProbe: # 就绪探针:决定 Pod 是否接收流量
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe: # 存活探针:失败则重启容器
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
startupProbe: # 启动探针:慢启动应用专用,通过前忽略 liveness
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10
# ---- 初始化容器 ----
initContainers: # 在主容器启动前顺序执行
- name: init-db-migration
image: myapp:v1.0
command: ["./migrate.sh"]
# ---- 卷挂载 ----
volumes:
- name: config-vol
configMap:
name: app-config # 挂载 ConfigMap 作为文件
- name: data-vol
emptyDir: {} # 临时卷,Pod 删除后数据丢失
# ---- 调度相关 ----
nodeSelector: # 简单节点选择
disk: ssd
affinity: # 高级亲和性规则
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values: ["linux"]
tolerations: # 容忍污点
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
# ---- 其他 ----
terminationGracePeriodSeconds: 30 # 优雅终止等待时间
serviceAccountName: myapp-sa # 使用的 ServiceAccount
[!tip] imagePullPolicy 最佳实践
- 使用
:latest标签时,必须设置imagePullPolicy: Always,否则可能使用本地缓存的旧镜像- 使用固定版本标签(如
:v1.2.3)时,IfNotPresent更高效,避免重复拉取Never仅用于本地开发环境(如 minikube 中直接使用本地构建的镜像)
常见陷阱与最佳实践
更新卡住排查
滚动更新卡住是常见问题,通常由以下原因导致:
# 1. 查看 Deployment 状态
kubectl rollout status deployment nginx-deploy
# 可能输出:Waiting for rollout to finish: 2 out of 4 new replicas have been updated...
# 2. 查看 Deployment 的 Conditions
kubectl get deployment nginx-deploy -o yaml | grep -A 20 conditions:
# 常见条件:
# - Progressing: False(更新未推进)
# - Available: True/False(是否有足够可用 Pod)
# 3. 查看新 ReplicaSet 的事件
kubectl describe rs nginx-deploy-xxxxxx # 查看新 RS 的 Events
# 常见原因:
# - FailedScheduling:资源不足,没有节点能满足 requests
# - ImagePullBackOff:镜像拉取失败(镜像名错、私有仓库无凭证)
# - CrashLoopBackOff:新容器启动后立即崩溃
# 4. 查看 Pod 事件
kubectl get pods -l app=nginx --sort-by=.metadata.creationTimestamp
kubectl describe pod nginx-deploy-xxxxxx-yyyyy
常见原因和解决方案:
| 症状 | 原因 | 解决方案 |
|---|---|---|
| ImagePullBackOff | 镜像不存在或无权限 | 检查镜像名称、imagePullSecrets |
| CrashLoopBackOff | 应用启动失败 | 查看 Pod 日志 kubectl logs |
| Pending | 资源不足或节点不满足约束 | 检查 kubectl describe pod 中的 Events |
| readinessProbe 一直失败 | 健康检查路径或端口错误 | 确认探针配置与应用实际端口一致 |
[!warning] 超时回滚 Deployment 有一个
progressDeadlineSeconds参数(默认 600 秒),如果更新在该时间内未完成,Deployment 会标记为Progressing=False。注意:这不会自动回滚,只是标记状态。需要手动执行kubectl rollout undo或编写自动化脚本。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
progressDeadlineSeconds: 300 # 5 分钟内未完成更新则标记失败
minReadySeconds: 10 # Pod Ready 后等待 10 秒才视为 Available
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.26
imagePullPolicy 导致的镜像缓存问题
# 场景:修改了 YAML 中的 image 字段但没改 tag,apply 后 Pod 没有更新
# 原因:imagePullPolicy 默认为 IfNotPresent,本地已有旧镜像就不会重新拉取
# 解决方案 1:始终使用不同的 tag(推荐)
image: myapp:v1.0.1 # 而不是 myapp:latest
# 解决方案 2:强制设置 imagePullPolicy
imagePullPolicy: Always
# 解决方案 3:使用 SHA256 digest(最精确)
image: myapp@sha256:abc123...
Deployment vs StatefulSet 选择
| 特性 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机(nginx-5d6b8f7c9-abc12) |
有序(web-0, web-1, web-2) |
| Pod 启动顺序 | 并行 | 有序(0 → 1 → 2) |
| Pod 终止顺序 | 并行(默认) | 逆序(2 → 1 → 0) |
| 网络标识 | 不稳定 | 稳定(web-0.ng-svc) |
| 持久存储 | 共享或无 | 每个 Pod 独立 PVC |
| 适用场景 | 无状态服务 | 有状态服务(数据库、MQ、ZooKeeper) |
| 滚动更新 | 替换 Pod | 逐个更新 |
[!info] 选择原则 如果你的应用满足以下任意条件,应考虑 StatefulSet:
- 需要稳定的网络标识(如 ZooKeeper 集群节点互相发现)
- 需要持久化的、与 Pod 绑定的存储(如 MySQL 数据目录)
- 需要有序的部署/扩缩/更新
其余场景一律使用 Deployment。
其他最佳实践
-
始终配置资源请求和限制:没有 requests 的 Pod 会被调度器视为零消耗,可能导致多个 Pod 被调度到同一节点,引发资源争抢。
-
始终配置 readinessProbe:没有就绪探针,新 Pod 创建后立即被标记为 Ready,流量可能打到未初始化完成的 Pod。
-
不要直接管理 Deployment 创建的 ReplicaSet:RS 的生命周期由 Deployment 控制器管理,手动修改 RS(如 scale)会被 Deployment 覆盖。
-
使用
kubectl apply而非kubectl create:apply 支持声明式管理,可以幂等执行,适合 GitOps 工作流。 -
合理设置 minReadySeconds:该参数让 Pod Ready 后再等待一段时间才被视为 Available,可以用来检测"启动后很快崩溃"的场景。
# 推荐的最小 Deployment 模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 3
revisionHistoryLimit: 5
progressDeadlineSeconds: 300
minReadySeconds: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 保证更新期间始终有足够的可用 Pod
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
terminationGracePeriodSeconds: 30
containers:
- name: myapp
image: myapp:v1.0.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
延伸阅读
- k8s-03-pod — Pod 的生命周期、调度机制、资源管理
- k8s-05-service-and-networking — Service 类型、Ingress、网络策略
- Kubernetes 官方文档 - Deployments
- Kubernetes 官方文档 - ReplicaSet
- Kubernetes 官方文档 - HPA