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

发布时间:2026/8/5 16:30:55
【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下面有个ReplicaSetReplicaSet下面才是Pod——为啥要搞三层两层不行吗【Deployment → ReplicaSet → Pod 三层关系图】 你用户 │ │ kubectl create deployment myapp --imagenginx --replicas3 ▼ ┌──────────────────────────────────────────────────────────────┐ │ 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 │ 真正干活的容器 │ 工人下面这个对比表格帮你理解为什么不能少掉中间层功能如果只有DeploymentPod有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用这个找到自己的PodmatchLabels:app:nginxtemplate:# ★ Pod模板——最重要的部分metadata:labels:app:nginx# 必须匹配上面的selectorspec:containers:-name:nginximage:nginx:1.25.3# ★ 镜像——升级就是改这个ports:-containerPort:80resources:# 资源限制requests:memory:128Micpu:250mlimits:memory:256Micpu:500m2.1 replicas——“我要几个”replicas就是你想要的Pod副本数。K8s会尽全力保证这么多Pod始终在跑。# 创建3副本的Deploymentkubectl create deployment myapp--imagenginx--replicas3--dry-runclient-oyaml# 运行时调整副本数kubectl scale deployment nginx-deployment--replicas5# 查看当前副本状态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# 我要找 appnginx 的Podtemplate:metadata:labels:app:nginx-backup# ← 写了 nginx-backup对不上# API Server: 报错Pod label不匹配selector创建失败2.3 template——“Pod长什么样”template就是一个完整的Pod定义去掉apiVersion和kind。Deployment把这份模板交给ReplicaSetRS照这个模板生产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个PodRS-v2 还不存在 RS-v1 (旧) ████████████████████████████████ 3个Pod RS-v2 (新) ................................ 0个Pod Step 1Deployment 创建 RS-v2RS-v2 创建1个新Pod RS-v1 (旧) ████████████████████████████████ 3个Pod RS-v2 (新) ████............................ 1个Pod (maxSurge1) Step 2新Pod就绪后RS-v1 删掉1个旧Pod RS-v1 (旧) ████████████████████............ 2个Pod RS-v2 (新) ████............................ 1个Pod Step 3RS-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 5RS-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在服务maxSurge1 maxUnavailable1 最终状态 RS-v1 → 0个Pod保留不回滚过段时间自动清理 RS-v2 → 3个Pod主力干活要点整个过程对用户来说几乎无感知——K8s确保在任何时刻正在对外提供服务的Pod数量始终维持在replicas - maxUnavailable到replicas maxSurge之间。这就是滚动的精髓像坦克履带一样旧的慢慢退、新的慢慢上服务不停机。3.2 maxUnavailable 和 maxSurge——两个气门芯这两个参数控制滚动更新的速度和风险spec:strategy:type:RollingUpdaterollingUpdate:maxUnavailable:1# 升级时最多可以少几个PodmaxSurge:1# 升级时最多可以多几个Pod参数含义默认值设小了设大了maxUnavailable升级过程中最大不可用Pod数25%向下取整升级慢但稳升级快但同时挂的Pod多maxSurge升级过程中最多额外创建的Pod数25%向下取整省资源但慢快但临时占用更多资源【maxSurge1, maxUnavailable1 的滚动过程replicas3】 时间线 ──────────────────────────────────────────────► Pod数 4│ ●──────● ← 临时多了1个PodmaxSurge1 3│●────●────●──────────────────●────●────● 2│ │ │ │ 1│ │ │ │ 0│ │ │ │ │ │ 全部更新完成 旧Pod逐步被杀掉 新Pod逐步创建并接管流量 解释 • 最多4个Pod同时存在31 surge • 最少2个Pod同时在跑3-1 unavailable • 理想配置replicas3时 maxSurge1 maxUnavailable1 • 保守配置maxSurge0 maxUnavailable1先杀旧的再建新的资源紧张时用# 保守升级——先删再建不额外占资源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-deploymentnginxnginx: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 1kubectl rollout undo deployment/nginx-deployment# 或者回滚到指定版本kubectl rollout undo deployment/nginx-deployment --to-revision1【回滚原理——用旧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--revision1# 展示该revision对应的Pod模板镜像、配置等# 暂停滚动更新发现问题时立刻停防止全量影响kubectl rollout pause deployment/nginx-deployment# 恢复滚动更新kubectl rollout resume deployment/nginx-deployment4.2 revisionHistoryLimit——“我最多存几个历史版本”spec:revisionHistoryLimit:10# 保留最近10个版本的ReplicaSet这个字段控制Deployment保留多少个旧ReplicaSet。每个旧RS本身不占Pod资源Pod数量为0但会占用K8s API Server的存储空间。revisionHistoryLimit效果10默认保留10个历史版本的RS旧的自动清理0不保留任何历史版本无法回滚不要设0100空间充足的集群可以多保留一些# 清理旧RS手动触发kubectl delete rs-lappnginx--field-selectorspec.replicas0五、K8s的滚动更新触发方式——“动了这里就升级”5.1 改了镜像——触发滚动更新这是最常见的触发方式# 方式1直接改镜像kubectlsetimage deployment/nginx-deploymentnginxnginx:1.27# 方式2edit YAMLkubectl edit deployment/nginx-deployment# 改 spec.template.spec.containers[0].image# 方式3apply新的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发生变化哪怕只加了个annotationK8s就会创建新的ReplicaSet并开始滚动更新。所以修改Pod模板要慎重——有时候你不经意改了个label就触发了一次全量滚动。六、常用的Deployment操作命令——速查# 创建 kubectl create deployment myapp--imagenginx:1.25--replicas3kubectl create deployment myapp--imagenginx:1.25--replicas3--dry-runclient-oyamldeploy.yaml# 查看 kubectl get deployments# 列出所有Deploymentkubectl get deployment nginx-oyaml# 查看完整YAMLkubectl describe deployment nginx-deployment# 查看详细状态和事件# 升级 kubectlsetimage deployment/nginx-deploymentnginxnginx:1.27 kubectl rollout status deployment/nginx-deployment# 实时看升级进度# 扩缩容 kubectl scale deployment nginx-deployment--replicas5# 回滚 kubectl rollouthistorydeployment/nginx-deployment# 看版本历史kubectl rollout undo deployment/nginx-deployment# 回滚到上一个版本kubectl rollout undo deployment/nginx-deployment --to-revision2# 回滚到指定版本# 暂停/恢复 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--imagenginx:1.25--replicas3# Step 2看看创建了什么kubectl get deploy,rs,pod-lappmy-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--replicas5kubectl get pods-lappmy-web --no-headers|wc-l# 输出5# Step 4升级镜像滚动更新开始kubectlsetimage deployment/my-webnginxnginx: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 nginxnginx:1.27 --recordtrue# Step 6发现新版本有问题回滚kubectl rollout undo deployment/my-web kubectl rollout status deployment/my-web# Step 7验证回滚成功kubectl get rs-lappmy-web# my-web-7d8f9c6a5b 5 5 5 ← RS v1 重新上岗# my-web-9a1b2c3d4e 0 0 0 ← RS v2 退场本篇小结Deployment是K8s里看似简单实则精巧的控制器设计典范三层架构Deployment→RS→PodDeployment管版本和策略RS管Pod数量和故障替换Pod负责干活。这层分层不是过度工程是解决了版本管理和数量保障的耦合问题滚动更新RollingUpdate逐步替换Pod服务不中断——maxSurge控制多几个maxUnavailable控制少几个回滚rollout undo旧RS保留了完整的Pod模板回滚就是让旧RS重新上岗零成本后悔版本历史revisionHistoryLimit控制保留多少个旧RS既能回滚又不浪费空间下一篇咱们专门讲Deployment背后的那个影子武士——ReplicaSet。它会告诉你为什么RS明明默默无闻却是整个副本管理机制的核心。上一篇【第12篇】Annotation——K8s的“便利贴“文化下一篇【第14篇】ReplicaSet——Deployment背后的影子武士