Kubernetes Deployment 的作用是什么?它如何实现滚动更新和回滚?
Kubernetes Deployment 是用来管理无状态应用生命周期的核心控制器。它围绕 ReplicaSet 实现了声明式部署、滚动更新和版本回滚,是日常使用频率最高的 K8s 工作负载类型。
Deployment 到底管什么
Deployment 并不直接管理 Pod。它管理的是 ReplicaSet,再由 ReplicaSet 来确保 Pod 的副本数。这种两层结构是理解 Deployment 更新和回滚机制的关键——每次更新 Pod 模板,Deployment 都会创建一个新的 ReplicaSet,逐步把流量从旧 ReplicaSet 迁移到新 ReplicaSet。
一个最小化的 Deployment 定义:
yamlapiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80
注意 selector.matchLabels 必须和 template.metadata.labels 匹配,否则 Deployment 无法关联到自己的 Pod,这是一个常见的新手错误。
滚动更新是怎么发生的
当你修改了 Deployment 的 Pod 模板(比如换了镜像版本),Kubernetes 不会一次性替换所有 Pod,而是按策略逐步替换。默认使用 RollingUpdate 策略,整个过程依赖两个参数:
- maxUnavailable:更新过程中最多允许多少个 Pod 处于不可用状态。默认值是 25%,即 3 副本的 Deployment 最多允许 1 个 Pod 不可用。
- maxSurge:更新过程中最多允许超出期望副本数多少个 Pod。默认值也是 25%。
以 3 副本为例,默认配置下的滚动更新过程大致如下:Kubernetes 先创建 1 个新 Pod(因为 maxSurge=25%,3 的 25% 向上取整为 1),等新 Pod 就绪后,再终止 1 个旧 Pod,如此循环直到全部替换完成。
这里有一个容易忽略的细节:所谓"就绪",依赖的是 readinessProbe。如果你没有配置 readinessProbe,Kubernetes 只要看到容器启动就认为 Pod ready,这可能导致流量打到还没准备好的新 Pod 上。生产环境中务必配置 readinessProbe。
查看更新状态:
bashkubectl rollout status deployment/nginx-deployment
Recreate 策略什么时候用
除了 RollingUpdate,还有一种 Recreate 策略:先杀掉所有旧 Pod,再创建新 Pod。这会带来停机时间,看起来不如 RollingUpdate,但有些场景必须用它:
- 应用不支持多版本同时运行(比如数据库 schema 变更后旧代码会报错)
- 新旧版本共享的资源无法兼容(比如同一个 ConfigMap 被新旧版本以不同方式解析)
设置方式:
yamlspec: strategy: type: Recreate
选择策略时问自己一个问题:新旧 Pod 能不能同时对外服务?能就用 RollingUpdate,不能就用 Recreate。
回滚机制的底层逻辑
每次更新 Pod 模板,Deployment 都会创建一个新的 ReplicaSet,旧的 ReplicaSet 不会被删除,而是保留作为回滚的锚点。Kubernetes 用 revisionHistoryLimit 控制保留多少个旧 ReplicaSet,默认值是 10。
查看更新历史:
bashkubectl rollout history deployment/nginx-deployment
回滚到上一版本:
bashkubectl rollout undo deployment/nginx-deployment
回滚到指定版本:
bashkubectl rollout undo deployment/nginx-deployment --to-revision=2
回滚的本质是什么?是把当前 Deployment 的 Pod 模板替换成目标 revision 对应的 ReplicaSet 的 Pod 模板,然后走一遍正常的滚动更新流程。所以回滚不是"魔法还原",它和正向更新走的是同一条路径,同样受 maxUnavailable 和 maxSurge 约束。
一个常见问题:如果你发现更新出错了,想暂停更新怎么办?
bashkubectl rollout pause deployment/nginx-deployment
暂停后可以做多次修改,确认没问题后再恢复:
bashkubectl rollout resume deployment/nginx-deployment
扩缩容:手动和自动
手动扩缩容:
bashkubectl scale deployment/nginx-deployment --replicas=5
自动扩缩容需要 HPA(HorizontalPodAutoscaler)。HPA 根据 CPU、内存或自定义指标自动调整 Deployment 的副本数:
yamlapiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nginx-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-deployment minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50
需要注意的是,HPA 扩容是立刻生效的,但缩容有一个默认 5 分钟的稳定窗口(behavior.scaleDown.stabilizationWindowSeconds),防止指标波动导致副本数来回抖动。
Deployment 和其他控制器的选择
面试中常问的一个问题是:什么时候用 Deployment,什么时候用 StatefulSet 或 DaemonSet?
Deployment 适用于无状态应用——Pod 之间没有差异,任何一个 Pod 都能处理任何请求。Web 服务、API 网关、微服务实例都属于这一类。
StatefulSet 适用于有状态应用——每个 Pod 有稳定的网络标识和持久化存储。数据库主从集群、ZooKeeper、Kafka 集群需要用 StatefulSet。
DaemonSet 确保每个节点上运行一个 Pod 副本,常用于日志采集、监控 Agent、网络插件等节点级服务。
选错控制器的后果很直接:用 Deployment 跑数据库,Pod 重建后数据丢失;用 StatefulSet 跑无状态 Web 服务,滚动更新变慢且没有收益。
生产环境中的几个注意点
资源限制必须设置。没有 requests 和 limits 的 Pod 可能抢占节点资源,导致其他 Pod 被驱逐。至少设置 requests,让调度器能正确决策。
健康检查不能省。livenessProbe 检测进程死锁,readinessProbe 控制流量接入。只配 livenessProbe 不配 readinessProbe,是导致滚动更新期间 502 的常见原因。
不要用 latest 标签。image: nginx:latest 意味着每次拉取可能拿到不同版本,这会让 Deployment 的声明式管理失去意义。用明确的版本号,变更时改 YAML 走正常的更新流程。
revisionHistoryLimit 不要设成 0。有些团队为了"清理资源"把它设成 0,结果是无法回滚。旧 ReplicaSet 里的 Pod 都是 0 副本,占用的资源微乎其微,保留回滚能力的收益远大于节省的那点开销。
掌握了 Deployment 的滚动更新机制、回滚原理和与其他控制器的选型逻辑,面试中关于工作负载的大部分问题都能从容应对。