ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

【Kubernetes从入门到精通】第13篇:Deployment——无状态应用的“自动档“

2026/8/5 16:52:25 拓冰建站 浏览量
【Kubernetes从入门到精通】第13篇:Deployment——无状态应用的“自动档“

上一篇【第12篇】Annotation——K8s的“便利贴“文化
下一篇【第14篇】ReplicaSet——Deployment背后的"影子武士"


摘要

如果你只会K8s里一个控制器,那必须是Deployment。为什么?因为你90%的应用都是无状态的——Web服务、API网关、前端页面、消息消费者……这些东西不存本地状态,挂了就重新拉一个起来,完全不带心疼的。Deployment就是为这类应用量身定做的"自动档":你只需要告诉它"我要3个副本,用这个镜像",剩下的升级、回滚、扩缩容、自愈——它全包了。

今天这篇文章,咱们从Deployment/ReplicaSet/Pod的三层关系讲起,把YAML里的每个关键字段扒一遍,然后重点演示滚动更新时新旧ReplicaSet的"交接过程"——这是很多人面试被问倒的地方。最后再聊聊回滚操作和版本管理,让你拥有完整的Deployment驾驭能力。


一、Deployment / ReplicaSet / Pod——三层"管理层级"

很多人刚开始学Deployment的时候会困惑:Deployment下面有个ReplicaSet,ReplicaSet下面才是Pod——为啥要搞三层?两层不行吗?

【Deployment → ReplicaSet → Pod 三层关系图】 你(用户) │ │ kubectl create deployment myapp --image=nginx --replicas=3 ▼ ┌──────────────────────────────────────────────────────────────┐ │ Deployment (版本管理器) │ │ • 管理"版本":v1 → v2 → v3 ... │ │ • 控制升级策略(滚动/重建) │ │ • 提供回滚能力 │ │ • 记录变更历史 │ └────────────────┬─────────────────────────────────────────────┘ │ 创建并管理 ▼ ┌──────────────────────────────────────────────────────────────┐ │ ReplicaSet v1 (副本数量保证) │ │ • 保证 "3个Pod" 始终在运行 │ │ • Pod挂了?立刻补一个新的 │ │ • 你说扩容到5个?行,再起俩 │ │ • 它不关心镜像版本——那是Deployment的事 │ └────────────────┬─────────────────────────────────────────────┘ │ 创建并监控 ▼ ┌──────────────────────────┐ │ Pod │ Pod │ Pod │ ← 3个一模一样的Pod副本 │ nginx│ nginx│ nginx │ └──────────────────────────┘ 升级到 v2 时: Deployment 创建 ReplicaSet v2——► 新RS逐步创建新Pod Deployment 缩容 ReplicaSet v1——► 旧RS逐步删旧Pod 完成替换后:RS v1 保留(0个Pod,用于回滚)

要点:Deployment不是直接管Pod的——中间夹了一个ReplicaSet。这个设计精妙之处在于:每次更新镜像,Deployment就创建一个新的ReplicaSet,旧的ReplicaSet保留着(但不跑Pod了),这样你想回滚的时候,直接用旧RS的Pod模板重新创建Pod就行了。ReplicaSet就是Deployment的"版本快照"

【三层分工——各司其职】 层级 │ 职责 │ 类比 ───────────────┼──────────────────────────────┼──────────── Deployment │ 版本管理、升级策略、回滚 │ 项目经理 ReplicaSet │ 保证Pod数量、故障替换、扩缩容 │ 班组长 Pod │ 真正干活的容器 │ 工人

下面这个对比表格帮你理解为什么不能少掉中间层:

功能如果只有Deployment+Pod有ReplicaSet中间层的好处
滚动更新Deployment要跟踪每个Pod的旧/新状态,逻辑爆炸Deployment只管"命令"新旧RS分别扩缩容
回滚回滚时要记住旧Pod模板的参数,容易丢旧RS本身就是完整的"Pod模板快照",用就完了
故障恢复挂了多少Pod、在哪起的,全得自己记录RS专管这事,Pod挂了立刻补,不需要Deployment操心
扩缩容又管版本又管数量,职责不清RS负责数量,Deployment负责版本,边界清晰

我见过一个团队自己写了个"山寨Deployment",没搞RS这一层,结果上线回滚的时候发现旧Pod模板丢了——因为那个模板只存在某个开发者的Shell历史里。这事儿教育我们:三层设计不是K8s过度工程,是真金白银买来的教训


二、Deployment YAML——从零写一个部署

先来看一个最完整的Deployment配置,然后咱们拆开讲每个字段:

apiVersion:apps/v1kind:Deploymentmetadata:name:nginx-deployment# Deployment的名字namespace:default# 部署在哪个命名空间labels:app:nginx# 给自己贴个标签(方便查找)annotations:kubernetes.io/change-cause:"初始化部署 v1.25.3"# 变更说明spec:replicas:3# ★ 期望副本数——核心字段strategy:# ★ 升级策略type:RollingUpdate# 滚动更新(默认)rollingUpdate:maxUnavailable:1# 升级时最多有多少Pod暂时不可用maxSurge:1# 升级时最多额外创建多少个PodrevisionHistoryLimit:10# ★ 保留几个历史版本(用于回滚)selector:# ★ 选择器——Deployment用这个找到"自己的Pod"matchLabels:app:nginxtemplate:# ★ Pod模板——最重要的部分!metadata:labels:app:nginx# 必须匹配上面的selector!spec:containers:-name:nginximage:nginx:1.25.3# ★ 镜像——升级就是改这个ports:-containerPort:80resources:# 资源限制requests:memory:"128Mi"cpu:"250m"limits:memory:"256Mi"cpu:"500m"

2.1 replicas——“我要几个?”

replicas就是你想要的Pod副本数。K8s会尽全力保证这么多Pod始终在跑。

# 创建3副本的Deploymentkubectl create deployment myapp--image=nginx--replicas=3--dry-run=client-oyaml# 运行时调整副本数kubectl scale deployment nginx-deployment--replicas=5# 查看当前副本状态kubectl get deployment nginx-deployment# NAME READY UP-TO-DATE AVAILABLE AGE# nginx-deployment 3/3 3 3 5m

要点kubectl scale改的是Deployment的replicas字段,不是直接改Pod数量。改变会先更新Deployment对象,Deployment再通知RS扩缩容——走的还是声明式API那套流程。

2.2 selector——“谁是我的Pod?”

selector是Deployment用来找到"属于自己的Pod"的筛选器。Pod模板的labels必须匹配selector,否则API Server直接拒绝创建。

# ❌ 错误示例:Pod的label没有匹配selectorspec:selector:matchLabels:app:nginx# 我要找 app=nginx 的Podtemplate:metadata:labels:app:nginx-backup# ← 写了 nginx-backup,对不上!# API Server: "报错!Pod label不匹配selector,创建失败"

2.3 template——“Pod长什么样?”

template就是一个完整的Pod定义(去掉apiVersion和kind)。Deployment把这份模板交给ReplicaSet,RS照这个模板生产Pod。

【template 就是 Pod 的"模具"】 ┌───────────────────┐ │ template │ ← Deployment 里定义的这个模板 │ ┌─────────────┐ │ │ │ 容器定义 │ │ │ │ 镜像、端口等 │ │ │ │ 资源限制 │ │ │ │ 环境变量 │ │ │ │ 挂载卷 │ │ │ └─────────────┘ │ └────────┬──────────┘ │ ReplicaSet 拿着这个模具 │ 需要几个 Pod 就 "咔嚓" 几个 ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Pod │ │ Pod │ │ Pod │ │ nginx:1.25││ nginx:1.25││ nginx:1.25│ └──────────┘ └──────────┘ └──────────┘

三、滚动更新——Deployment的"看家本事"

滚动更新(RollingUpdate)是Deployment的默认升级策略,也是它最值钱的功能。它的核心思想就八个字:逐步替换,服务不中断

3.1 滚动更新的完整过程

假设你有一个3副本的Deployment,镜像从nginx:1.25升级到nginx:1.26

【滚动更新——新旧RS的Pod数量变化全过程】 初始状态:RS-v1 管着3个Pod,RS-v2 还不存在 RS-v1 (旧) ████████████████████████████████ 3个Pod RS-v2 (新) ................................ 0个Pod Step 1:Deployment 创建 RS-v2,RS-v2 创建1个新Pod RS-v1 (旧) ████████████████████████████████ 3个Pod RS-v2 (新) ████............................ 1个Pod (maxSurge=1) Step 2:新Pod就绪后,RS-v1 删掉1个旧Pod RS-v1 (旧) ████████████████████............ 2个Pod RS-v2 (新) ████............................ 1个Pod Step 3:RS-v2 再创建1个新Pod RS-v1 (旧) ████████████████████............ 2个Pod RS-v2 (新) ████████........................ 2个Pod Step 4:新Pod就绪,RS-v1 再删1个旧Pod RS-v1 (旧) ████████........................ 1个Pod RS-v2 (新) ████████........................ 2个Pod Step 5:RS-v2 创建最后一个新Pod RS-v1 (旧) ████████........................ 1个Pod RS-v2 (新) ████████████.................... 3个Pod Step 6:新Pod就绪,RS-v1 删掉最后一个旧Pod RS-v1 (旧) ................................ 0个Pod (保留用于回滚) RS-v2 (新) ████████████████████████████████ 3个Pod ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 整个过程中,始终保持 ≥3 个Pod在服务!(maxSurge=1 + maxUnavailable=1) 最终状态: RS-v1 → 0个Pod(保留,不回滚过段时间自动清理) RS-v2 → 3个Pod(主力干活)

要点:整个过程对用户来说几乎无感知——K8s确保在任何时刻,正在对外提供服务的Pod数量始终维持在replicas - maxUnavailablereplicas + maxSurge之间。这就是"滚动"的精髓:像坦克履带一样,旧的慢慢退、新的慢慢上,服务不停机。

3.2 maxUnavailable 和 maxSurge——两个"气门芯"

这两个参数控制滚动更新的速度和风险:

spec:strategy:type:RollingUpdaterollingUpdate:maxUnavailable:1# 升级时最多可以"少"几个PodmaxSurge:1# 升级时最多可以"多"几个Pod
参数含义默认值设小了设大了
maxUnavailable升级过程中最大不可用Pod数25%(向下取整)升级慢,但稳升级快,但同时挂的Pod多
maxSurge升级过程中最多额外创建的Pod数25%(向下取整)省资源,但慢快,但临时占用更多资源
【maxSurge=1, maxUnavailable=1 的滚动过程(replicas=3)】 时间线 ──────────────────────────────────────────────► Pod数 4│ ●──────● ← 临时多了1个Pod(maxSurge=1) 3│●────●────●──────────────────●────●────● 2│ │ │ │ 1│ │ │ │ 0│ │ │ │ │ │ 全部更新完成 旧Pod逐步被杀掉 新Pod逐步创建并接管流量 解释: • 最多4个Pod同时存在(3+1 surge) • 最少2个Pod同时在跑(3-1 unavailable) • 理想配置:replicas=3时 maxSurge=1 maxUnavailable=1 • 保守配置:maxSurge=0 maxUnavailable=1(先杀旧的再建新的,资源紧张时用)
# 保守升级——先删再建(不额外占资源)spec:strategy:rollingUpdate:maxSurge:0# 绝不多建一个PodmaxUnavailable:1# 允许暂时少1个Pod# 激进升级——快速轮换spec:strategy:rollingUpdate:maxSurge:2# 可以多建2个,总共5个Pod峰值maxUnavailable:0# 一个都不能少!始终保证3个Pod在服务

要点:我见过有团队为了"快",把maxSurge设得特别大、maxUnavailable也设得特别大,结果升级时Node资源不够,新Pod创建失败,旧Pod又被杀了——服务瘫痪。这不是参数的问题,是你资源规划的问题。先确认Node上有余量再调这些参数。

3.3 Recreate——"全杀全建"策略

如果不设strategy.type,默认就是RollingUpdate。另一种策略是Recreate——先把所有旧Pod全杀掉,再批量创建新Pod。中间会有一段完全不可用的真空期。

【Recreate 策略——简单粗暴】 RS-v1 (旧) ████████████████████████████████ 3个Pod RS-v2 (新) ................................ 0个Pod Step 1: 杀掉全部旧Pod RS-v1 (旧) ................................ 0个Pod RS-v2 (新) ................................ 0个Pod ⚠️ 服务不可用!所有请求全部失败! Step 2: 创建新Pod RS-v2 (新) ████████████████████████████████ 3个Pod ✅ 服务恢复 适用场景:应用不支持新旧版本同时运行(如数据库迁移)
# Recreate 配置spec:strategy:type:Recreate# 先杀光旧的,再起新的
策略优点缺点适用场景
RollingUpdate零停机、平滑过渡要求应用兼容新旧版本并存无状态Web服务(90%情况)
Recreate简单、不占额外资源有停机窗口单实例应用、一次只能跑一个实例的数据库

四、回滚操作——“搞砸了?一键倒带”

升级完发现CPU飙了、报错了、用户投诉了——别慌,Deployment给你准备了完整的"后悔药"。

# 升级到有问题的新版本kubectlsetimage deployment/nginx-deploymentnginx=nginx:1.26# 看升级状态kubectl rollout status deployment/nginx-deployment# Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas...# "完了,新版本有问题!"# 查看变更历史kubectl rollouthistorydeployment/nginx-deployment# REVISION CHANGE-CAUSE# 1 初始化部署 v1.25.3# 2 升级到 v1.26(有问题!)# 回滚到上一个版本(回到 REVISION 1)kubectl rollout undo deployment/nginx-deployment# 或者回滚到指定版本kubectl rollout undo deployment/nginx-deployment --to-revision=1
【回滚原理——用旧RS的Pod模板重新创建Pod】 升级前: RS-v1: 3个Pod (nginx:1.25) ← revision 1 升级后出问题: RS-v1: 0个Pod(保留了模板) ← revision 1 RS-v2: 3个Pod (nginx:1.26) ← revision 2(有问题!) 执行 kubectl rollout undo: RS-v1: 逐步起3个Pod (nginx:1.25) ← 恢复! RS-v2: 逐步缩减到0个Pod ← 退场

要点:回滚的本质是让旧ReplicaSet重新"上岗"。K8s不需要记住旧版本的镜像名、配置、环境变量——因为旧RS的Pod模板里全存着呢。这就是"版本快照"思想的精妙之处。

4.1 查看版本细节

# 查看某个revision的详细信息kubectl rollouthistorydeployment/nginx-deployment--revision=1# 展示该revision对应的Pod模板(镜像、配置等)# 暂停滚动更新(发现问题时立刻停,防止全量影响)kubectl rollout pause deployment/nginx-deployment# 恢复滚动更新kubectl rollout resume deployment/nginx-deployment

4.2 revisionHistoryLimit——“我最多存几个历史版本?”

spec:revisionHistoryLimit:10# 保留最近10个版本的ReplicaSet

这个字段控制Deployment保留多少个旧ReplicaSet。每个旧RS本身不占Pod资源(Pod数量为0),但会占用K8s API Server的存储空间。

revisionHistoryLimit效果
10(默认)保留10个历史版本的RS,旧的自动清理
0不保留任何历史版本(无法回滚!不要设0)
100空间充足的集群可以多保留一些
# 清理旧RS(手动触发)kubectl delete rs-l'app=nginx'--field-selector='spec.replicas=0'

五、K8s的滚动更新触发方式——“动了这里就升级”

5.1 改了镜像——触发滚动更新

这是最常见的触发方式:

# 方式1:直接改镜像kubectlsetimage deployment/nginx-deploymentnginx=nginx:1.27# 方式2:edit YAMLkubectl edit deployment/nginx-deployment# 改 spec.template.spec.containers[0].image# 方式3:apply新的YAMLkubectl apply-fnginx-deployment.yaml# 方式4:用patchkubectl patch deployment nginx-deployment-p\'{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.27"}]}}}}'

5.2 改了template——也触发滚动更新

不仅改镜像,改Pod模板里的任何东西(环境变量、资源限制、探针配置等)都会触发滚动更新:

# 这些改动都会触发滚动更新:spec:template:spec:containers:-name:nginximage:nginx:1.27# ← 改镜像:触发!env:-name:LOG_LEVELvalue:"debug"# ← 加环境变量:触发!resources:limits:memory:"512Mi"# ← 改资源限制:触发!

要点:只要Deployment的spec.template发生变化(哪怕只加了个annotation),K8s就会创建新的ReplicaSet并开始滚动更新。所以修改Pod模板要慎重——有时候你不经意改了个label就触发了一次全量滚动。


六、常用的Deployment操作命令——速查

# ====== 创建 ======kubectl create deployment myapp--image=nginx:1.25--replicas=3kubectl create deployment myapp--image=nginx:1.25--replicas=3--dry-run=client-oyaml>deploy.yaml# ====== 查看 ======kubectl get deployments# 列出所有Deploymentkubectl get deployment nginx-oyaml# 查看完整YAMLkubectl describe deployment nginx-deployment# 查看详细状态和事件# ====== 升级 ======kubectlsetimage deployment/nginx-deploymentnginx=nginx:1.27 kubectl rollout status deployment/nginx-deployment# 实时看升级进度# ====== 扩缩容 ======kubectl scale deployment nginx-deployment--replicas=5# ====== 回滚 ======kubectl rollouthistorydeployment/nginx-deployment# 看版本历史kubectl rollout undo deployment/nginx-deployment# 回滚到上一个版本kubectl rollout undo deployment/nginx-deployment --to-revision=2# 回滚到指定版本# ====== 暂停/恢复 ======kubectl rollout pause deployment/nginx-deployment# 暂停滚动更新kubectl rollout resume deployment/nginx-deployment# 恢复滚动更新# ====== 重启(原地重启所有Pod) ======kubectl rollout restart deployment/nginx-deployment# ====== 删除 ======kubectl delete deployment nginx-deployment# Deployment没了,RS和Pod也会被级联删除

七、一次完整的Deployment实战——从部署到回滚

咱们实操一把,把上面讲的全串起来:

# Step 1:创建Deploymentkubectl create deployment my-web--image=nginx:1.25--replicas=3# Step 2:看看创建了什么kubectl get deploy,rs,pod-lapp=my-web# NAME READY UP-TO-DATE AVAILABLE AGE# deployment.apps/my-web 3/3 3 3 10s## NAME DESIRED CURRENT READY AGE# replicaset.apps/my-web-7d8f9c6a5b 3 3 3 10s## NAME READY STATUS RESTARTS AGE# pod/my-web-7d8f9c6a5b-abc12 1/1 Running 0 10s# pod/my-web-7d8f9c6a5b-def34 1/1 Running 0 10s# pod/my-web-7d8f9c6a5b-ghi56 1/1 Running 0 10s# Step 3:扩容到5个kubectl scale deployment my-web--replicas=5kubectl get pods-lapp=my-web --no-headers|wc-l# 输出:5# Step 4:升级镜像(滚动更新开始)kubectlsetimage deployment/my-webnginx=nginx:1.27--recordkubectl rollout status deployment/my-web# Waiting for deployment "my-web" rollout to finish...# deployment "my-web" successfully rolled out# Step 5:看版本历史kubectl rollouthistorydeployment/my-web# REVISION CHANGE-CAUSE# 1 <none># 2 kubectl set image deployment/my-web nginx=nginx:1.27 --record=true# Step 6:发现新版本有问题,回滚!kubectl rollout undo deployment/my-web kubectl rollout status deployment/my-web# Step 7:验证回滚成功kubectl get rs-lapp=my-web# my-web-7d8f9c6a5b 5 5 5 ← RS v1 重新上岗# my-web-9a1b2c3d4e 0 0 0 ← RS v2 退场

本篇小结

Deployment是K8s里"看似简单,实则精巧"的控制器设计典范:

  1. 三层架构(Deployment→RS→Pod):Deployment管版本和策略,RS管Pod数量和故障替换,Pod负责干活。这层分层不是过度工程,是解决了版本管理和数量保障的耦合问题
  2. 滚动更新(RollingUpdate):逐步替换Pod,服务不中断——maxSurge控制"多几个",maxUnavailable控制"少几个"
  3. 回滚(rollout undo):旧RS保留了完整的Pod模板,回滚就是让旧RS重新上岗,零成本"后悔"
  4. 版本历史(revisionHistoryLimit):控制保留多少个旧RS,既能回滚又不浪费空间

下一篇咱们专门讲Deployment背后的那个"影子武士"——ReplicaSet。它会告诉你,为什么RS明明"默默无闻",却是整个副本管理机制的核心。


上一篇【第12篇】Annotation——K8s的“便利贴“文化
下一篇【第14篇】ReplicaSet——Deployment背后的"影子武士"