资讯动态

Kubernetes Deployment 实战:从滚动更新到回滚的完整发布指南

发布时间:2026/10/22 11:01:16 来源:尧图企业网站定制
在生产集群里待久了你会发现大家聊 Kubernetes 时提到最多的对象之一就是 Deployment。Deployment 用来承载无状态应用的 Pod开发者提交一份 YAMLPod 副本数量、更新方式、回滚策略全在里面。说白了它就是帮你在 K8s 里安全发布和迭代应用的“发布系统”。今天这篇就围绕 Deployment 部署 Pod以及应用更新、回滚这套完整流程做一次实战向拆解适合刚上手 K8s 的运维同学也适合在开发侧写部署清单的工程师。我会把每个关键字段的逻辑、每条命令的实际输出、踩过的坑一起交代清楚看完你至少能独立完成一次从部署到回滚的完整发布演练。1. 为什么说 Deployment 是 Pod 的“专职管家”1.1 直接创建 Pod 会遇到哪些坑如果刚开始接触 K8s很多人会直接写一个 Pod YAML然后kubectl apply -f pod.yaml。这样当然能把应用跑起来验证阶段完全够用。但只要你把视角切到生产环境问题立刻暴露Pod 本身没有自愈能力。节点重启、Pod 被驱逐、容器 OOM只要这个 Pod 没了它就是没了不会有人帮你再拉起来。你可能会说那我用 ReplicaSet 不就行了ReplicaSet 确实能保证集群里始终有指定数量的 Pod 副本但它只管“数量对不对”不管“新版本怎么发布”。生产环境真正需要的是这样的能力发新版时不能断服务新旧版本要平滑切换发完还能一键回退。ReplicaSet 给不了这些于是 Deployment 出现了。Deployment 可以理解为更上层的控制器它通过管理 ReplicaSet 来间接管理 Pod。你在 Deployment 里描述“我想要 3 个副本镜像版本是 v2.0.0”Deployment 就会确保底层有 3 个 Pod 在跑并且镜像版本始终是你声明的版本。更重要的是它自带滚动更新和回滚能力。每次你改模板镜像Deployment 就会自动创建新的 ReplicaSet把流量从旧版本滚到新版本一旦新版本出问题几十秒内就能退回旧版本。1.2 Deployment、ReplicaSet、Pod 三层关系怎么理解我经常用一个不太严谨但好记的类比Deployment 是物业管委会ReplicaSet 是具体楼栋的管家Pod 是租户。管委会定规则管家执行规则租户实际提供服务。你不需要直接找租户也不需要手动安排管家管委会发一条指令整栋楼都会按流程运转。这种三层结构给运维带来的直接好处是职责分离。Deployment 只关心模板和副本数ReplicaSet 只关心某个版本下的副本数量Pod 只关心容器是否运行。你更新 Deployment 里的镜像时K8s 不会去改旧 ReplicaSet 里的 Pod而是新建一个 ReplicaSet让新 ReplicaSet 的 Pod 慢慢启动等它们 Ready 了再把旧 ReplicaSet 的 Pod 删掉。这就是滚动更新的本质。实际使用中有一个很重要的习惯不要手工去改 Deployment 管理下的 ReplicaSet。很多新手看到kubectl get rs底层有多个 ReplicaSet就想着直接编辑某个 RS 改副本数这个操作会被 Deployment 控制器立刻纠正回来。Deployment 和它下面的 RS 是强绑定关系所有版本变更都应该只发生在 Deployment 这个层面否则整个发布历史就乱了。2. 部署清单的设计思路YAML 里的每个字段都不是摆设2.1 模板与选择器Pod 不认识 Deployment只认标签直接看一份实际能用的 Deployment YAML后面所有操作都基于它apiVersion: apps/v1 kind: Deployment metadata: name: demo-app namespace: demo spec: replicas: 3 selector: matchLabels: app: demo-app revisionHistoryLimit: 10 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 1 template: metadata: labels: app: demo-app spec: containers: - name: app image: registry.example.com/demo-app:v1.0.0 ports: - containerPort: 8080 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 10 periodSeconds: 5 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 20 periodSeconds: 10先看spec.template.metadata.labels。这个标签不是随便写的Pod 创建出来后会带这组标签Deployment 靠spec.selector.matchLabels来找到并管理这些 Pod。两者必须匹配否则创建 Deployment 时会被 API Server 直接拒绝报错内容也很直白selector 跟 template labels 不匹配。还有一点需要注意spec.selector在 Deployment 创建之后是不能改的。因为 selector 天然就是“找 Pod”的过滤条件一旦运行中的 Deployment 改了 selector底层 ReplicaSet 和 Pod 的归属关系就全乱了。所以如果后期发现标签设计有问题通常只能删掉重建 Deployment代价很大。最开始设计标签时就要想清楚最好带上版本无关的稳定维度比如app、tier不要把版本号放进 selector。replicas: 3是期望副本数Deployment 会一直尝试把实际副本数对齐到 3。这里提醒一句如果集群资源紧张replicas 设太大只会让 Pod 长时间 Pending不会自动缩容去适配资源该扩容节点还是得扩容。资源配额问题后面还会细说。2.2 更新策略maxSurge 和 maxUnavailable 怎么搭配YAML 里strategy.type支持两种Recreate和RollingUpdate。Recreate是先删掉所有旧 Pod再创建新 Pod发布期间服务会完全中断只适合那些不能双实例并行且能接受停机的内部任务。绝大多数在线服务都应该用默认的RollingUpdate。滚动更新有两个核心参数很多人背了概念但不会配。先说默认值maxSurge默认 25%maxUnavailable默认 25%。它们的含义要记准maxSurge是滚动更新过程中最多允许超出期望副本数的 Pod 数量maxUnavailable是最多允许不可用的 Pod 数量单位可以是绝对数值也可以是百分比。百分比会基于副本数向上取整比如 3 个副本25% 向上取整就是 1。所以当 replicas3、maxSurge1、maxUnavailable1 时滚动更新期间整个 Deployment 的 Pod 总数最多是 431最少可用的 Pod 数是 23-1。这意味着发布过程中不会出现服务全部不可用的情况同时允许短暂地多占一点资源。我经常把这两个值拆开讲maxSurge1, maxUnavailable0严格保持可用副本数不低于 3每新起一个 Ready Pod才回收一个旧 Pod。发布节奏慢但最稳适合对可用性极其敏感的核心服务。maxSurge1, maxUnavailable1允许短暂有一个 Pod 不可用同时最多多跑一个 Pod发布速度适中绝大多数 Web 服务可以这么配。maxSurge1, maxUnavailable2发布更快但某一瞬间可用 Pod 可能掉到 1服务容量会出现明显抖动测试环境可以这么省时间。实际配置时不要照抄默认值先想清楚如果 3 个副本里瞬间只剩 2 个服务会不会被打爆如果不会maxUnavailable1没毛病如果很敏感就把maxUnavailable设为 0让发布慢一点。很多发布事故不是代码问题而是滚动窗口期容量被击穿导致的连锁故障。2.3 其他影响发布和回滚的字段revisionHistoryLimit是经常被忽略但关键时刻能救命的字段。它控制 Deployment 保留多少个历史 ReplicaSet默认值是 10。这个值决定了你能回滚到多远的历史版本。如果你把它设成 0Deployment 不会保留任何历史版本kubectl rollout undo基本就废了。我见过有人为了省资源把revisionHistoryLimit设成 1结果发坏版本后连前一个版本都回不去只能在代码里重新切 tag。建议保留默认的 10或者根据发布频率适当调大历史 RS 占用的资源非常有限别拿回滚安全性换那一点 etcd 空间。progressDeadlineSeconds控制滚动更新超时时间默认 600 秒。如果新版本在 600 秒内没有完成滚动Deployment 会被标记为ProgressDeadlineExceededkubectl rollout status会返回失败。但注意标记失败不等于自动回滚旧 RS 和新 RS 可能还停在那里需要你手动处理。这个字段更像是一个信号灯提醒你“这次发布卡住了赶紧去看看”。minReadySeconds也建议配置它表示 Pod 进入 Ready 状态后还要稳定运行多少秒才算真正可用。比如设成 15那么 Pod 必须 Ready 后持续 15 秒没有崩溃才会被算作可用副本。没有这个字段时Pod 只要通过 readiness 探针就算可用很多应用刚启动时进程活着但连接池还没建好会被快速拉进流量然后超时。配置一个 10~30 秒的minReadySeconds可以显著过滤启动抖动。3. 从零部署到发布一次完整的实操记录3.1 环境准备与基础 YAML实操之前先确认两件事集群能正常访问kubectl能用。我用的是虚拟的示例镜像registry.example.com/demo-app版本号从 v1.0.0 开始。实际生产环境建议用私有镜像仓库并且不要用latest标签原因后面专门说。把上面那段 YAML 保存成deployment.yaml然后执行kubectl create namespace demo kubectl apply -f deployment.yaml如果 YAML 有问题API Server 会直接报错。最常见的两个错误selector 和 labels 不匹配或者镜像名写错。都没问题的情况下输出会显示deployment.apps/demo-app created。此时 Deployment 已经创建但 Pod 可能还在拉镜像需要继续观察。3.2 首次部署和验证创建一个 Deployment 不等于发布完成。后面这些命令是我每次发布都要敲的kubectl get deployment demo-app -n demo -o wide kubectl get rs -n demo -l appdemo-app kubectl get pods -n demo -l appdemo-app -o wide kubectl rollout status deployment/demo-app -n demo第一条看 Deployment 的期望副本数、当前副本数、可用副本数第二条看底下的 ReplicaSet首次部署会出现一个 RS名字类似demo-app-7f6c9d8d4f后面的随机串是 Pod 模板哈希第三条看实际 Pod 状态重点观察READY和STATUS正常情况下会从ContainerCreating变成RunningREADY变成3/3第四条会直接告诉你滚动更新是否完成如果输出deployment demo-app successfully rolled out说明这次发布成功。kubectl rollout status是个很有用的命令它像阻塞等待会持续观察 Deployment 的滚动过程。如果滚动卡住这个命令会一直等直到超时或你自己 Ctrl-C 中断。注意 Ctrl-C 只是停止观察滚动更新在集群里仍在继续别以为取消了。首次部署生成的 RS 对应 Revision 1。你可以通过kubectl rollout history deployment/demo-app -n demo看到这个历史记录。后面每次更新模板镜像都会新增一个新的 Revision。3.3 应用更新set image 与 apply 两种方式现在模拟一次常规版本迭代把镜像从 v1.0.0 升到 v2.0.0。第一种常见方式是直接用命令kubectl set image deployment/demo-app appregistry.example.com/demo-app:v2.0.0 -n demo命令里的app是容器名对应 YAML 里spec.template.spec.containers[0].name。如果容器名写错命令会提示找不到容器。执行后Deployment 的 template 发生了变化控制器会立刻创建新的 ReplicaSet并开始滚动更新。第二种方式是改 YAML 再 apply。一般配合 Git 代码仓库做 GitOps 发布在仓库里把image改成 v2.0.0提交然后在集群上执行kubectl apply -f deployment.yaml这种方式的好处是清单文件始终和线上状态保持一致回滚时也能用同一个文件历史来追溯。我个人推荐团队采用这种方式而不是有人用set image、有人直接kubectl edit时间一长集群里的 Deployment 和仓库里的 YAML 就产生漂移后面排障会非常痛苦。无论哪种方式背后逻辑都一样只有spec.template发生变化才会触发新版本发布。如果你只是改了replicasDeployment 不会新增历史 Revision只会在当前 RS 上调副本数。所以不要以为改了 Deployment 就一定有 rollout得看改的是什么字段。更新过程中可以随时执行kubectl rollout status deployment/demo-app -n demo看进度同时开另一个终端看kubectl get pods -n demo -l appdemo-app -w你会看到新 RS 的 Pod 一个接一个变成 Running 和 Ready旧 RS 的 Pod 同步被终止Pod 名后的哈希也逐渐替换成新版本。3.4 暂停与继续控制发布节奏的隐藏技能滚动更新默认是自动执行完的但有些场景需要你中途控制节奏。比如新版本已经发到一半你突然在监控图上看到异常想先别让新版本继续扩散这时候可以用kubectl rollout pause deployment/demo-app -n demo暂停之后Deployment 不会再创建新的 Pod已经创建的新 Pod 继续运行旧 Pod 也不会继续缩容整个发布过程停在当前状态。这个状态非常适合“灰度一半先观察”确认没问题后再执行kubectl rollout resume deployment/demo-app -n demo恢复之后Deployment 会继续完成未完成的滚动更新。还有一类用法是发布前先 pause然后连续修改多次 Pod 模板此时不会每次都触发滚动更新直到 resume 才统一按最终模板发布一次。这样可以避免中间态频繁触发新 RS减少无意义的滚动。这里必须提醒一个坑在 pause 状态下直接执行kubectl rollout undo行为可能不符合直觉。Cluster 上 rollout 历史是有的但暂停状态下回滚动作不会立刻反映到 Pod 上。我踩过之后学到的经验是先rollout resume确认发布流程恢复再做回滚不要在一个 paused 的 Deployment 上盲目 undo。4. 回滚操作别让故障版本在线上过夜4.1 查看发布历史与 Revision接着说回滚。假设 v2.0.0 发布后发现接口报错率直线上升你要尽快恢复。第一步永远不是直接执行 undo而是先看清楚自己站在哪个位置。kubectl rollout history deployment/demo-app -n demo输出大致是这样的REVISION CHANGE-CAUSE 1 none 2 noneCHANGE-CAUSE 在新版 K8s 里默认是none旧版本里的--record参数已经被废弃所以不用指望它自动记录变更原因。如果你想让 history 可读比较好的做法是在 CI/CD 平台里记录每次发布的版本号和 commit集群里的 Revision 跟发布平台上的版本 ID 做一个映射。真要临时在 K8s 里记录原因可以手动给 Deployment 加注解但这不是主力方案。想查看某个 Revision 的完整模板信息可以执行kubectl rollout history deployment/demo-app -n demo --revision2这里能看到这个 Revision 对应的 Pod Template 里的镜像版本、标签、探针配置。回滚前确认一下 Revision 2 是不是真的就是你想要的那个旧版本比直接猜要稳妥得多。4.2 回滚到上一个版本和指定版本快速回到上一个版本执行kubectl rollout undo deployment/demo-app -n demo默认情况下它会回滚到当前 Deployment 历史里的上一个 Revision。对于刚才的场景就是 v1.0.0。如果想明确回滚到某个指定版本后面加参数kubectl rollout undo deployment/demo-app -n demo --to-revision1这里要理解一个容易混淆的细节回滚不是把集群“时光倒流”到 Revision 1而是基于 Revision 1 的模板再触发一次新发布。回滚完成后kubectl rollout history里会多一个 Revision 3它和 Revision 1 的镜像内容相同但 Revision 编号是新的。所以不要惊讶为什么回滚后历史里少了 Revision 2其实它还在只是被新 Revision 3 替代了当前状态。回滚到指定版本的前提是目标 Revision 对应的 ReplicaSet 还保留在集群里也就是revisionHistoryLimit没有把它清掉。如果你发现--to-revision1后 Deployment 没有反应先确认历史列表里是否还有 1 这个编号。kubectl rollout undo同样可以配合kubectl rollout status观察结果。如果回滚过程中发现选错了版本你可以立马再执行一次 undo 回滚到另一个方向反正它本质就是一次普通发布。4.3 回滚时的观察点回滚发布看起来很简单但线上操作时不能只盯一条命令。我回滚时的习惯是同时观察四个维度kubectl get deployment demo-app -n demo -w kubectl get rs -n demo -l appdemo-app kubectl get pods -n demo -l appdemo-app kubectl get events -n demo --sort-by.lastTimestampDeployment 的AVAILABLE列如果一直低于期望副本数说明回滚也没顺利RS 列表里新 RS 和旧 RS 的副本数变化能告诉你滚动到哪一步了Pod 的状态和 Events 会直接暴露具体失败原因。我从不在回滚时只看rollout status到成功就关掉终端因为“成功”只代表控制器按预期完成了流程不代表业务已经恢复。最好在回滚后继续观察几分钟监控指标确认错误率降下来再宣布“回滚完成”。还有一个容易被忽略的点如果新版本发布时镜像 tag 是不可变的但旧版本镜像已经被覆盖或者从仓库删了回滚也会失败。生产环境尽量保证历史版本镜像长期可用至少保留最近几个版本别因为清理镜像省成本把回滚后路堵死。5. 线上排查实录与高频问题速查5.1 滚动更新卡住的典型现场有次我在测试环境模拟发布一个故意把健康检查写错的应用现场是这样的kubectl get deployment里DESIRED是 3CURRENT可能是 4AVAILABLE只有 2Pod 列表里新版本 Pod 一直不是Running就是CrashLoopBackOff旧版本 Pod 也不减少。这正是滚动更新卡住的典型状态。原因通常有三种资源不足导致 Pod 一直 Pending映像拉取失败导致 Pod 处于ImagePullBackOffreadiness 探针一直失败导致新 Pod 永远不被认为 Ready控制器不敢继续缩容旧副本。排查顺序我建议是先kubectl get pods -l appdemo-app看整体状态再kubectl describe pod pod-name看具体事件最后查 Deployment 和 RS 的 condition。不要一上来就重启 Pod先看原因。如果发现是 Pending用kubectl describe pod看节点有没有资源压力如果是 ImagePullBackOff看 Events 里的拉取错误如果是探针失败看 Pod 日志和/healthz接口返回。如果卡住的服务正在线上应急处置不是直接删 Deployment而是用kubectl rollout undo先把流量拉回旧版本。等新版本的问题排查清楚再重新发布。滚动卡住时 undo 通常能顺利执行因为它是基于历史版本模板不需要依赖当前新 RS 里面的问题代码。5.2 镜像拉取失败的原因链镜像拉取问题是新手上路最高频的故障之一现象是 Pod 状态显示ImagePullBackOff或ErrImagePull。原因链条其实不长镜像名称拼写错误、镜像 tag 不存在、私有仓库需要认证、节点访问不了仓库。排查时先执行kubectl describe pod pod-name -n demo在 Events 里能看到类似Failed to pull image的提示。如果指向认证失败需要在 Deployment 的 template 中加imagePullSecretsspec: template: spec: imagePullSecrets: - name: registry-keyregistry-key需要提前在命名空间创建好。如果节点本身访问不了镜像仓库就得检查节点上的 DNS、路由和仓库白名单。另一个隐蔽坑是镜像 tag 用latest节点上如果已经有本地缓存即使远端latest更新了Pod 也不一定会重新拉取要看imagePullPolicy的取值。对latest默认是Always但如果你指定了具体版本号默认策略是IfNotPresent本地有缓存就会用缓存。生产发布尽量用语义化 tag别依赖latest否则更新和回滚的语义都会变得很模糊。5.3 Readiness 探针失效与发布“假成功”readiness 探针是滚动更新能否正确判定“新 Pod 可用”的关键。如果没配 readiness 探针Pod 只要进程启动成功就会被标记为 Ready。结果是滚动更新可以在几秒内完成坏版本瞬间全量上线。很多严重的发布事故就是这种“发布太快”导致的。配置探针时要注意几个细节。initialDelaySeconds要给够应用启动慢但探针检查太早会导致 Pod 反复被杀死。periodSeconds建议不低于 5太频繁会给应用造成不必要的压力。timeoutSeconds和failureThreshold也要按应用特性设不能只看默认值。readiness 和 liveness 分开配置不要共用同一个接口readiness 失败只是不把 Pod 加入流量liveness 失败会直接重启容器。如果把一个重负载接口同时用作两种探针接口一慢Pod 可能还没 ready 就被 kill形成恶性循环。我习惯 readiness 指向一个业务就绪检查接口liveness 指向一个不需要依赖外部组件就能响应的轻量接口。5.4 避坑清单与个人经验整理一个实际问题速查表都是我踩过或者帮别人排查过的现象常见原因处理思路更新后 Pod 一直 Pending节点资源不足、亲和性规则太严格describe pod 看 Node 事件评估调度条件ImagePullBackOff镜像名或 tag 错、私有仓库未认证describe pod 看拉取错误补齐 imagePullSecrets滚动更新卡住AVAILABLE 不足readiness 探针失败看新 Pod 日志和健康检查接口必要时 undoundo 后没有效果目标 Revision 已被清理或 Deployment 处于 pause检查 history 列表先 resume 再回滚发布成功但业务异常探针配置太宽松坏版本已全量回滚后收紧探针延长灰度观察时间历史版本消失revisionHistoryLimit 太小或为 0调整 revisionHistoryLimit重新规划版本保留策略最后分享一个我在实际运维中养成的小习惯任何一次发布或回滚我都会先记录当时的基线状态包括kubectl rollout history里的 Revision、当前镜像版本、重点监控指标。别依赖自己的记忆。很多回滚事故不是因为命令不对而是操作太快根本没确认自己回滚到了哪里。每次执行kubectl rollout undo之前先敲一遍kubectl rollout history deployment/demo-app -n demo看清楚--to-revision的编号再动手。这个习惯救过我太多次也希望你早点养成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑