☰
Kubernetes Deployment深度解析:滚动更新、灰度发布与生产实践
2026/10/3 3:28:56 网站建设 项目流程

1. 先搞清楚Deployment到底在解决什么问题

1.1 从Pod到Deployment:为什么中间还隔着ReplicaSet

很多人刚开始学K8s的时候,都会有这个困惑:我直接创建一个Pod不就行了,为什么非要绕一圈搞个Deployment?我当年也踩过这个坑——直接在yaml里写Pod,结果Pod一挂,业务就断了,只能手动去kubectl delete再重新创建,折腾几次就明白了,单打独斗的Pod在生产环境里根本没法用。

Deployment存在的意义,本质上就是**“托管”**。它不是一个真正运行着容器的东西,而是一个控制器,负责声明你想要的最终状态,然后由控制器不断对比“现状”和“期望状态”,缺了就补,多了就回收。Deployment下面管着ReplicaSet,ReplicaSet再管着Pod,这个三层结构很多人觉得多余,但正是这层抽象让滚动更新和版本回滚成为可能。ReplicaSet相当于一个版本的“快照”,每次你修改Deployment的Pod模板,控制器就会创建一个新的ReplicaSet,把副本数从0逐步拉起来,同时把旧的ReplicaSet慢慢缩到0。这个过程就是滚动更新。而回滚就更简单了——直接把Deployment恢复到上一个ReplicaSet对应的版本就行。

1.2 一个最能说明问题的对比场景

我给你画个对比场景,你就明白为什么需要Deployment了。假设你有个订单服务,需要跑3个副本:

用裸Pod的方式,你得维护3份Pod配置,任何一个Pod挂了,你得人工发现、人工重建。流量进来了,少一个副本在服务,用户就能感觉到抖动。而用Deployment,你只需要写一份replicas为3的声明,剩下的交给控制器。Pod被误删了?控制器会立刻重新拉起一个。节点宕机了?Pod会漂移到别的可用节点上。这背后是K8s的自愈能力,而自愈的入口就是Deployment这类工作负载控制器。

Deployment适合跑的,是无状态的12要素应用——API服务、Web前端、任务Worker、消息消费者这一类。有状态的服务比如数据库、Redis、ZooKeeper,建议用StatefulSet,因为它能保证稳定的网络标识和持久化存储绑定。这个选型区分至关重要,我在生产环境里见过有人把MySQL用Deployment跑,结果Pod重建后数据全丢的惨案,就是因为没搞清楚两者的边界。

2. Deployment核心参数逐个拆解

2.1 骨架字段:apiVersion、kind、metadata

一个标准的Deployment yaml长这样:

apiVersion: apps/v1 kind: Deployment metadata: name: order-service namespace: production labels: app: order-service version: v1.2.0 spec: replicas: 3 selector: matchLabels: app: order-service template: metadata: labels: app: order-service version: v1.2.0 spec: containers: - name: order-service image: registry.example.com/order-service:v1.2.0 ports: - containerPort: 8080

这里有几个容易踩坑的点,我逐个说。apiVersion必须是apps/v1,这是Deployment稳定版的API。早期用extensions/v1beta1的写法在K8s 1.16之后已经完全移除了,我在给老项目做升级的时候就碰见过这种陈年yaml导致无法提交的情况。

metadata.name是Deployment的名字,这个名字会作为后续创建的ReplicaSet和Pod名称的前缀。注意metadata.labels和spec.template.metadata.labels不是一回事。前者是Deployment自己的标签,后者是Pod模板的标签,它们可以不一样,但spec.selector.matchLabels必须和Pod模板的标签匹配。为什么要强调这个?因为一旦Deployment创建成功,spec.selector就不可修改了。如果想改selector,只能删掉重建Deployment。我遇到过同事改标签时顺手把selector也改了,结果Deployment直接报错,新ReplicaSet建不出来,业务全挂,教训很深。

2.2 工作负载核心:replicas、selector与template

replicas声明期望的Pod副本数,声明了3,控制器就会保证有3个健康的Pod在运行。但这个字段不是设置完就不动了。做弹性伸缩的时候,HPA(HorizontalPodAutoscaler)会直接修改Deployment的replicas字段,所以我建议线上环境如果有HPA在管理,就不要再手动去改replicas,否则两边会打架。我在实际运维中碰到过这种情况——HPA根据CPU使用率把副本数从3扩到10,结果开发同事手动把replicas改回3,HPA检测到期望值被改,又立刻扩回去,两边来回拉扯,Pod频繁创建销毁,服务端抖动非常明显。

selector的匹配规则看起来简单,但有个隐藏细节:它支持matchLabels和matchExpressions两种方式。matchLabels是最常用的,就是精确匹配键值对;matchExpressions可以做In、NotIn、Exists、DoesNotExist这几种操作,适合复杂的选择逻辑。不过我的建议是,能用matchLabels就别用matchExpressions,简洁就是最大的可维护性。

Pod模板template里最核心的是containers列表。这里我多说一句:镜像版本必须写全,不要偷懒写latest标签。生产环境用latest是噩梦——不同的Node上可能缓存了不同时间拉取的镜像,版本漂移会让你排查问题时一头雾水。正确的做法是每次发布都打一个不可变的版本号,比如v1.2.0,并且配合镜像摘要(digest)来锁定内容。imagePullPolicy如果不写,默认规则是:镜像标签为latest时用Always,否则用IfNotPresent。这个默认行为在发布新版本时有一个坑——如果你在原有tag上覆盖推送了新镜像,而节点上已经有旧镜像,K8s可能不会重新拉取。所以要么用新tag,要么显式设置imagePullPolicy: Always。

2.3 更新策略:strategy字段里的rollingUpdate与recreate

Deployment支持两种更新策略,Recreate和RollingUpdate,默认是RollingUpdate。

Recreate的行为很粗暴:先把所有旧Pod全部终止,然后一次性创建新Pod。它的优点是部署过程干净,不会出现新旧版本同时服务的情况;缺点是更新期间服务完全不可用。这个策略适合那种无法平滑切换的场景,比如某些需要对数据库做结构变更的初始化任务。但在生产环境的在线服务上,谁用谁后悔,我见过有人把生产API服务的策略设成Recreate,发布时服务直接中断了两三分钟,监控告警炸了一屏幕。

RollingUpdate才是生产环境的常态,它有两个关键参数:

strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25%

maxSurge表示更新过程中最多可以超出期望副本数的Pod数量,maxUnavailable表示最多可以有多少个Pod处于不可用状态。这两个参数是配合使用的,理解它们需要一点计算能力。

举个例子,期望副本数3,maxSurge: 25%,maxUnavailable: 25%。K8s在计算时会向上取整,所以maxSurge是ceil(3 * 0.25) = 1,意味着最多允许4个Pod同时存在;maxUnavailable向上取整也是1,意味着最少要保持2个Pod可用。更新过程大致是这样:先创建一个新Pod(超出期望,达到4个),等它Ready后,缩一个旧Pod,再创建一个新Pod,周而复始。这样3个副本滚动更新完,始终保持至少2个旧Pod在服务,整体服务不中断,但承载能力是打了一点折扣的。如果业务流量非常大、对容量损耗敏感,可以把maxUnavailable设为0,maxSurge设为1,先起新版Pod再接流量,但这样会多占一份资源,成本会更高。这两个参数的取舍本质上是可用容量vs资源成本的权衡,没有标准答案,完全看业务场景。

还有一个细节点:maxSurge和maxUnavailable不能同时为0,否则滚动更新无法推进,会一直卡住。

2.4 运维保障参数:revisionHistoryLimit、progressDeadlineSeconds、paused

这三个参数平时不起眼,但关键时刻能救命。

revisionHistoryLimit控制保留多少个历史ReplicaSet。默认是10,意味着你可以回滚到最近10个版本。如果把它设置为0,就表示不保留历史记录,也就无法回滚了。有些团队为了省etcd资源把历史版本设成1或2,结果想回滚到更早版本时傻眼了。我建议线上环境保留5到10个,etcd里几个ReplicaSet的元数据开销很小,和出故障时的回滚能力相比,这点成本完全不值一提。

progressDeadlineSeconds是Deployment的“超时判定”参数。它定义了在指定的秒数内,如果Deployment没有完成滚动更新,控制器就会把Deployment的状态标记为Progressing=False,并报一个ProgressDeadlineExceeded的错误。默认值我没记错的话,是600秒。这个参数在排查问题时非常有用——滚动更新卡住不推进,如果总是超时,说明你的新Pod一直没法Ready,要去查镜像是否存在、探针是否通过、资源是否足够。

paused就是暂停字段。设置为true时,Deployment暂停滚动更新,你可以在保持当前Pod不变的条件下,修改Pod模板或扩容,但不会触发更新。这个字段在灰度场景下非常实用。比如我想先更新成v1.1版本,但又不想立刻全量发布,可以先把paused设为true,提交新模板,观察新ReplicaSet是否正常创建,确认没问题后再unpause继续推进。这算是K8s原生自带的一个“半手动灰度”能力。

3. 灰度发布:从原理到落地

3.1 灰度发布的基本思路与K8s原生能力边界

灰度发布,也叫金丝雀发布(Canary Release),核心思路是让新版本先承接一小部分流量,验证没问题后再逐步放大,最终全量替换。它的价值在于把爆雷的范围限制住——即使新版本有Bug,受影响的也只是那1%的用户,而不是全体用户。

经常有人把灰度发布和蓝绿发布混为一谈,我简单区分一下:蓝绿发布是准备两套完全独立的环境,流量从蓝切到绿,切换是瞬时的;灰度发布是同一套系统内新旧版本并存,流量按比例逐步迁移。蓝绿发布切换干净、回滚快,但成本翻倍;灰度发布成本低、细分度好,但对系统的兼容性要求高——新旧版本要能同时服务同一类请求,数据库结构要兼容。

K8s原生对于灰度发布的能力,说句实话,是有限的。Deployment本身只能做“滚动更新”,也就是渐次替换,但它无法精确控制流量比例。比如你想让新版本只接收10%的流量,Deployment做不到,因为Service的负载均衡是随机分发的,权重无法按Pod级别精细控制。所以,要做真正的灰度,必须引入额外的流量控制机制。下面我给出几种从简到繁的方案,你可以根据自己的环境选。

3.2 基于多Deployment的手动灰度方案

这是最基础、也是我建议初学者先掌握的方式。思路是创建两个Deployment,一个跑旧版本,一个跑新版本,通过调整副本数比例来近似控制流量比例。

# 旧版本 v1.0,副本数9 apiVersion: apps/v1 kind: Deployment metadata: name: order-service-v1 namespace: production spec: replicas: 9 selector: matchLabels: app: order-service version: v1.0 template: metadata: labels: app: order-service version: v1.0 spec: containers: - name: order-service image: registry.example.com/order-service:v1.0 --- # 新版本 v1.1,副本数1 apiVersion: apps/v1 kind: Deployment metadata: name: order-service-v1-1 namespace: production spec: replicas: 1 selector: matchLabels: app: order-service version: v1.1 template: metadata: labels: app: order-service version: v1.1 spec: containers: - name: order-service image: registry.example.com/order-service:v1.1

然后让Service的selector只匹配app: order-service,不区分version,这样流量会在新旧Pod之间按副本数比例随机分配。9比1就是大约10%的流量进新版,1比9就是10%进旧版。通过调整两个Deployment的replicas,就能实现10%、50%、90%、100%的逐步放量。

这个方案的优点显而易见:纯K8s原生,不需要额外组件,逻辑好理解。缺点也很明显:控制粒度粗糙,Pod级比例不等于流量比例,因为每个Pod的QPS和资源利用率不一样。另外,扩容缩容过程中Pod数量变化可能导致流量比例抖动。所以这个方案适合灰度要求不高的场景,或者作为其他方案的过渡。

3.3 基于单Deployment配合Ingress的流量灰度

如果想把流量控制做到更精细,可以在Ingress/网关层做文章。比如你用的是Nginx Ingress Controller,它支持nginx.ingress.kubernetes.io/canary系列注解,可以按权重或按Header/Cookie来切分流量。

思路是:主Ingress指向旧Deployment的Service,另外创建一个Canary Ingress,指向新Deployment的Service,并设置灰度权重。

# 主入口,流量全量指向旧版本 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: order-service-ingress namespace: production spec: rules: - host: order.example.com http: paths: - path: / pathType: Prefix backend: service: name: order-service-v1 port: number: 8080 --- # Canary入口,10%的流量导向新版本 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: order-service-canary namespace: production annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "10" spec: rules: - host: order.example.com http: paths: - path: / pathType: Prefix backend: service: name: order-service-v1-1 port: number: 8080

除了按权重,Nginx Ingress还支持按请求头分流:

nginx.ingress.kubernetes.io/canary-by-header: "canary" nginx.ingress.kubernetes.io/canary-by-header-value: "true"

这个用法非常适合内部测试——测试人员发请求时带上canary: true的Header,就能稳定命中新版本,而普通用户完全不受影响。这也是我最推荐的灰度方式之一,因为它逻辑简单、可控性强、不侵入业务代码。需要注意的坑是:Canary Ingress和主Ingress的host必须一致,并且该host下只能有一个Canary规则,配置多了会报错。不同Ingress Controller的注解写法不一样,如果你是自建Istio或者Traefik,注解名和字段规则都要重新查。

3.4 进阶:Argo Rollouts的金丝雀发布

如果你的团队灰度发布是日常高频操作,我建议直接上Argo Rollouts。它不是替换Deployment,而是引入一个RolloutCRD资源,在原生Deployment的基础上扩展了精细化灰度能力。它的核心设计是把“发布策略”和“流量切换”拆分管理——你可以定义一个步骤列表,例如“先切5%流量,等2分钟,再切50%,加一个手动审批,最终100%”。

Argo Rollouts支持多种流量管理后端,包括Nginx Ingress、Istio、ALB等,并且带一个Dashboard,可以直接看到当前发布进度、每个ReplicaSet的副本数和流量占比。用起来之后,你会发现灰度发布从“手工活”变成了“流程化操作”,尤其是带审批环节的步骤卡点——新版本发布到一半,需要负责人点确认才能继续,这对生产环境的安全保障价值极高。不过天下没有免费的午餐,引入Argo Rollouts意味着你的集群里多了一组Controller和CRD,变更和升级都要考虑兼容性。我的建议是:如果集群规模不大、发布频率低,用前面两种方案就够;如果是发布频繁、业务要求严格的生产环境,Argo Rollouts值得投入。

4. 生产环境参数调优的实操心得

4.1 资源请求与限制:requests和limits怎么定

Deployment的Pod模板里,resources字段是我在排障时最常见的问题来源。很多人不写资源限制,容器可以无限使用节点内存,结果出现OOM时不是容器被杀,而是整个节点内存耗尽,触发系统OOM Killer,把无辜Pod也一起干掉,这是生产环境的大忌。

resources: requests: cpu: 500m memory: 512Mi limits: cpu: 1 memory: 1Gi

requests是调度依据,K8s scheduler在把Pod分配到节点时,会检查节点的可分配资源是否满足Pod的requests总和。limits是运行时上限,CPU的limits有权重压制效果——当节点CPU争抢时,超过limits的Pod会被限流,内存的limits则是严格限制,超过会触发OOM Kill。

那参数值怎么定?我一般的思路是这样:先不加limits,只加requests,跑几天看监控里的实际用量峰值,然后根据峰值乘以1.5到2的余量来设置limits。CPU的requests用500m可能偏保守,如果业务是计算密集型的,要多留余量。有一个很容易忽视的点:limits和requests的比值越大,Pod在CPU争抢时被压制得越狠。如果requests设得很小、limits设得很大,节点忙起来时这个Pod的性能会非常不稳定。建议两者差距控制在2到4倍以内。

4.2 探针参数:livenessProbe和readinessProbe的关键字段

探针是Deployment能稳定运行的关键防线。readinessProbe决定Pod是否被纳入Service的Endpoints——探针失败,流量就不会打到这个Pod上;livenessProbe决定Pod是否需要重启——探针失败,Kubelet会杀掉容器并重建。

readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 2 successThreshold: 1 failureThreshold: 3 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 20 timeoutSeconds: 2 failureThreshold: 3

这几个参数里,最容易出问题的就是initialDelaySeconds和failureThreshold。initialDelaySeconds太短,应用还在启动阶段、健康检查接口还没就绪,就会误杀容器;我见过Java应用启动需要40秒,探针initialDelaySeconds只写了3秒,结果Pod一直处于CrashLoopBackOff。反过来,failureThreshold设得太大,容器真正出问题时响应太慢,故障恢复时间长。我的习惯是:initialDelaySeconds设为应用平均启动时间的1.2倍,failureThreshold保持2到3,timeoutSeconds不要超过periodSeconds。

还有一个细节:readinessProbe和livenessProbe的路径应该区分开。readiness可以检查依赖的下游服务是否就绪,比如数据库连接池是否初始化完成;liveness只检查进程本身是否活着,不要让它依赖外部服务,否则数据库抖动时Kubelet会不断重启你的容器,造成更大的故障。

4.3 优雅终止:terminationGracePeriodSeconds与preStop

Pod被终止时,Kubelet会先发SIGTERM给主进程,然后等待一段时间,超时后发SIGKILL强杀。这个等待时间就是terminationGracePeriodSeconds,默认30秒。很多应用收到SIGTERM后会开始清理连接、把未完成的事务提交完再退出,这个时间如果不够,进程会被强杀,造成请求中断。

对于请求量大的在线服务,我建议在容器里加一个preStop钩子,做缓冲等待:

lifecycle: preStop: exec: command: ["sh", "-c", "sleep 5"]

为什么要sleep?因为Pod从“被删除”到“从Service的Endpoints里摘除”是有延迟的。Kubelet删除Pod时,Pod的status会变为Terminating,但Endpoints的更新是异步的,可能还有几秒的窗口期,流量还会继续向这个Pod转发。preStop里的sleep能让新请求在Pod真正停掉之前,先等Endpoints更新完成,避免连接被直接掐断。这个5秒的值不是拍脑袋定的,它应该大于你集群中Endpoints传播的典型延迟,我一般根据网络环境设5到10秒。另外注意,preStop的sleep时间加上应用自己的优雅退出时间,必须小于terminationGracePeriodSeconds,否则进程会被强杀,preStop白做了。

4.4 调度与容灾:nodeSelector、affinity、topologySpreadConstraints

Deployment的Pod要落到哪些节点上,也是参数调优的重要一环。最简单的nodeSelector是把Pod固定调度到带特定标签的节点上,比如GPU节点就通过nodeSelector: gpu: "true"来指定。但nodeSelector太死板,更灵活的是nodeAffinity,支持硬性要求(requiredDuringSchedulingIgnoredDuringExecution)和软性偏好(preferredDuringSchedulingIgnoredDuringExecution)。

我更想强调的是topologySpreadConstraints,这个参数在K8s 1.19之后稳定可用,它能把Pod均匀打散到不同的拓扑域里——比如不同的可用区或不同的节点。对于多可用区的生产集群,这是保证容灾的基础。

topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: order-service

maxSkew: 1表示各个可用区上的Pod数量差异最多为1,whenUnsatisfiable: DoNotSchedule表示如果不满足条件就不调度(硬性)。这样配置后,3个副本会尽量分散在3个可用区,任何一个可用区挂掉,只剩2个副本但服务不会全挂。代价是调度约束变强,集群碎片化更严重,节点资源利用率会下降。所以DoNotSchedule要和maxSkew配合好,否则在节点资源紧张时,Pod可能长时间Pending。我见过生产环境因为加了硬性拓扑约束,扩容时Pod一直Pending,排查半天才发现是某个可用区资源不够。

5. 常见故障与排查实录

5.1 滚动更新卡住:新ReplicaSet不推进

Deployment滚动更新时最常见的故障就是卡住不动。现象是新的ReplicaSet已经创建,但Pod一直不是Ready状态,旧的Pod也不缩容。排查第一步永远是用kubectl get pods看新Pod的状态,然后再用kubectl describe pod <pod-name>看事件的详细内容。

我遇到过的典型原因有这么几类:

  • 镜像地址错误或镜像不存在:事件里会出现Failed to pull image或者ImagePullBackOff。如果错误是ErrImagePull,先检查镜像仓库地址是否写对、镜像tag是否存在。
  • 探针失败:Pod虽然在Running,但readinessProbe一直失败,事件里会看到Readiness probe failed。这种情况要进去看应用的日志,多半是数据库连不上、缓存连接失败、或者配置文件不对。
  • 资源不足:事件里出现FailedScheduling,提示Insufficient cpu或Insufficient memory,说明节点资源不够跑新Pod。这时要么扩容节点,要么临时把maxSurge调小。

排查的时候我有个习惯:先看kubectl rollout status deployment/<name>,它会直接告诉你卡在哪一步;再看kubectl describe deployment/<name>底部Conditions,如果出现ProgressDeadlineExceeded,说明超时了,ReplicaSet不会继续推进。这时候先去解决Pod不Ready的根因,等Pod变健康后,滚动更新会自动恢复。

5.2 新版本一上就CrashLoopBackOff

CrashLoopBackOff表示容器启动后又崩溃,反复循环。这个错误的原因五花八门,但最常见的是这几个:

  • 应用启动时依赖的资源不存在:比如配置中心连不上、数据库schema没有迁移、依赖的其他服务还没就绪。
  • 配置文件不匹配:新版本的环境变量、ConfigMap里的配置项缺失,代码里读取不到直接panic。
  • 资源限制过低:启动时内存就超了limits,直接被OOM Kill。这时用kubectl describe pod看Last State的OOMKilled状态就能发现。

排查CrashLoopBackOff,第一步是看日志:kubectl logs <pod-name> --previous,注意--previous参数很重要,因为容器已经重启了,当前日志可能已经是第二次启动的,上一次崩溃的信息要看--previous拿。如果日志没有输出,就检查容器启动命令和entrypoint是不是有问题。还有一种情况是探针导致的误杀——容器其实正常,但livenessProbe由于initialDelaySeconds太短,在应用还没就绪时连续失败3次,Kubelet就把容器杀了。这种问题看日志会发现应用其实在正常打印启动信息。

5.3 灰度流量放大了,服务却超时

灰度发布时经常遇到的情况:新版本承接了10%的流量没问题,放大到50%后开始大量超时和报错。这个问题往往是新版本存在性能瓶颈或资源不足,而不是功能逻辑问题。

我的排查套路是这样:先看新版本Pod的CPU和内存监控曲线,如果CPU已经打满或内存持续高位,说明是资源问题,需要调大requests和limits或者增加副本数。如果资源没问题,看应用的错误日志和链路追踪,判断是否是数据库慢查询、下游服务限流、或者新版本代码里有锁竞争之类的性能隐患。

还有一个容易被忽略的坑:新旧版本共存时,如果两个版本写的是同一张数据库表且schema不兼容,新版本写入的数据结构变了,旧版本读取时可能会报错。做灰度之前,要确保新旧版本的数据兼容性——数据库的变更要向后兼容(加字段而不是删字段、加索引而不是改索引),这是灰度发布能否成功的前提。

5.4 版本回滚的正确姿势

灰度发布发现新版本有问题,最快的止损方式就是回滚。Deployment回滚有几个层次的命令,区别要搞清楚:

# 回滚到上一个版本 kubectl rollout undo deployment/order-service # 回滚到指定版本 kubectl rollout undo deployment/order-service --to-revision=3 # 查看历史版本记录 kubectl rollout history deployment/order-service

rollout undo默认回退到上一个版本,本质上是把Pod模板恢复成旧ReplicaSet的模板,然后触发一次新的滚动更新。注意:回滚也是一次发布,它同样受maxSurge和maxUnavailable约束,同样可能因为资源不足或镜像问题卡住。

关于--to-revision的编号,可以通过rollout history查看,每个REVISION对应一个ReplicaSet。这里我提醒一句:如果revisionHistoryLimit设置过小,比如只有2,那只能回滚到上一版,更早的版本由于历史ReplicaSet被清理,就回不去了。所以生产环境即使回滚场景少,也建议保留5个以上的历史版本。

如果在灰度过程中,新版本还没全量、旧版本还有副本在运行,那么回滚就更简单——直接把新版本的Deployment副本数缩到0,或者直接删掉新版本的Deployment,流量自然全部回到旧版本。这也是多Deployment灰度方案的一个优势:回滚不触发滚动更新,瞬时完成。


最后聊点实操上的体感。Deployment的很多参数,读文档的时候觉得都懂,真正上线跑一轮流量、踩几个故障之后,才知道哪些是关键。我在实际维护中发现,出问题最多的往往不是Deployment本身的配置,而是Pod模板里那些“小字段”——探针的延时设置、资源上下限、优雅退出时长、镜像拉取策略。这些字段单个看都不起眼,但组合起来就是服务稳定性的底层保障。

灰度发布这个事,我也想说一句:工具和方法都是次要的,关键是流程和纪律。灰度前要确认数据库兼容性,灰度中要有监控告警盯流量和错误率,灰度后要有明确的全量与回滚标准。把这些实践沉淀成团队的发布规范,比纠结用哪个灰度工具重要得多。如果你想把灰度做得更默认更自动化,可以试试Argo Rollouts,但在那之前,先把Deployment这批基础参数吃透,它们才是你每天和K8s打交道最常碰到的“肌肉记忆”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询