☰
Kubernetes蓝绿发布实战:从原理到回滚的完整指南
2026/10/21 21:21:26 网站建设 项目流程

凌晨两点,我盯着监控大屏上那个跳动的错误率指标,脑子里只有一个念头:这版本要是能瞬间回退,哪怕让我连加三天班都愿意。那次发布事故之后,我开始认真研究蓝绿发布,并在Kubernetes上完整落地。其实蓝绿发布的原理一点都不复杂:一套在跑的老版本(蓝),一套待命的新版本(绿),流量在两者之间瞬间切换。真正复杂的是怎么在Kubernetes这个生态里把它做得干净、可靠、可回滚。

这篇文章我会把一次完整的蓝绿发布拆开揉碎讲清楚,覆盖核心原理、资源设计、操作步骤、回滚机制,以及我在实践中踩过的坑——会话保持、长连接池、配置漂移这类问题,网上教程基本不会告诉你。适合正在维护Kubernetes集群的运维、SRE,以及需要自己管发布流程的后端开发。哪怕你只是刚接触Kubernetes,跟着操作一遍,也能建立起对发布流程的完整认知。

1. 为什么说蓝绿发布是Kubernetes里的“安全气囊”

1.1 它解决了什么问题

发布永远是线上事故的高发时段。传统的滚动更新(Rolling Update)虽然是Kubernetes默认策略,但它的核心逻辑是“逐步替换旧Pod”:新Pod起来一个,旧Pod销毁一个。这种方式的问题是,新旧Pod会同时接收流量,如果新版本有严重Bug,用户会在整个滚动过程中陆续受害,而且回滚时需要再滚一遍,耗时很长。

蓝绿发布解决的正是这两个痛点。要点是:始终有两套完整的环境,旧版本(蓝)持续承接线上流量,新版本(绿)先在旁边空跑、预热、验证,确认没问题之后,通过一个“开关”把流量整体切过去。切换是瞬时的,回滚也一样——把开关再拨回来。

在Kubernetes里,这个“开关”就是Service的selector。你不需要改动Deployment的任何Pod,只需要修改Service选中哪一组标签,流量就整体换了个方向。这个操作快的话几秒钟就能完成,而且不涉及任何Pod的销毁和重建。

我习惯把这套机制形容成“安全气囊”:平常它低调待命,看起来像是浪费了双倍资源;真出事的时候,它能在你最慌张的时刻帮你把伤害降到最低。

1.2 什么时候该用它,什么时候别硬上

蓝绿发布不是万能的,我在不同团队见到不少滥用的情况,这里先说清楚边界。

适合的场景是:核心业务、对可用性要求高、不能接受长时间发布窗口的服务。比如支付回调接口、用户登录服务、订单核心链路。这类服务一旦发布出错,影响面是几分钟内立刻放大,必须能秒级回滚。蓝绿发布天然适合。另外,如果你的团队发布频率不高、每次都紧张兮兮,蓝绿发布也能显著降低发布心理压力。

不太适合的场景也明显:第一,资源紧张。蓝绿发布需要常驻两套环境,等于Pod数量翻倍。如果你集群本来就捉襟见肘,或者费用敏感,请慎重。第二,对数据库有较强状态依赖且没有做兼容的服务。比如新版本改了表结构、加了字段,切过去之后万一要回滚,老版本连新表结构往往撑不住。这个问题不是蓝绿发布本身能解决的,需要配合数据库的双写或兼容层。第三,无状态、低风险的内部服务,用滚动更新其实更省事,没必要杀鸡用牛刀。

提示:决定要不要上蓝绿发布,先问自己一个问题——万一新版本上线后出问题,你能否接受“回滚后老版本也无法工作”的连锁反应?如果答案是否定的,你得先解决数据库兼容性,再谈发布策略。

2. 动手前的三板斧:Service、Deployment与网络选型

2.1 Service selector是发布切换的“总开关”

Kubernetes里的Service是一层稳定的访问入口,后端Pod通过标签(label)被选中。蓝绿发布的核心操作就是修改这个Service的selector,让流量从选中“蓝”的Pod改为选中“绿”的Pod。

这里有个关键认知:同一个Service下只能有一个selector。你可能见过一些文章说“Service里配置多个selector就能灰度”——那是误解。Service的selector就是一组键值对,最终选中的是一组符合全部标签的Pod。如果你同时选中了蓝绿两套Pod,流量就会在两者之间负载均衡,这不是蓝绿发布,这是混流。

所以蓝绿发布的资源模型非常清晰:

  • 两个Deployment:deployment-blue(标签version: blue),deployment-green(标签version: green)。
  • 一个Service:selector指向当前在线的版本标签,比如version: blue。
  • 切换动作:把Service的selector从version: blue改成version: green。

Service本身不会重启,Pod也不会重建,只改了转发目标。整个过程对调用方透明,这是这个设计最妙的地方。

2.2 版本标签设计:别把宝全押在app标签上

标签设计直接影响切换的安全性和便捷性。我见过有些团队偷懒,只给Pod打一个标签app: myapp,然后两个Deployment都打这个标签。等切换的时候发现Service把两边的Pod都选中了,流量直接混流,发布当场乱套。

正确的做法是至少两个标签维度:

  • app: myapp:标识应用名,用来做全局筛选和排查。
  • version: blue或version: green:标识版本身份,Service selector用它做精确定位。

如果你有金丝雀发布的需求,还可以再加一个track: stable或release: canary之类的标签,不过那是后话。蓝绿发布开始阶段,严格按照app + version两个标签就够了。

还有一个细节容易被忽略:Deployment的名字建议带上版本标识,比如myapp-blue和myapp-green。这样在kubectl get pods看到的列表清晰明了,不用每次费力猜测哪个Pod属于哪个版本。别小看这个习惯,发布出问题的时候,每一秒都在抢时间,列表越清爽,定位越快。

2.3 对外流量入口的三种选择

Service的selector决定了流量切给谁,但外部流量怎么进集群,是另一个需要提前想清楚的问题。根据集群的接入方式,蓝绿切换的操作位置可能会略有不同。

ClusterIP + Ingress:这是最常见的生产方案。Ingress把域名流量转到Service,Service再转到Pod。蓝绿切换只需要修改Service的selector,Ingress完全不用动。这也是我认为最干净的方式——发布动作被收敛在“Service”这一个点上,不管前面挂的是Ingress还是API网关,都不需要感知。

NodePort:适合测试环境。改Service的selector依然生效,但如果你直接通过节点IP:NodePort访问,注意NodePort本身在切换过程中不会变化,流量是被平滑转发的。

LoadBalancer(云负载均衡):云平台提供的LB类型。这里有个重要细节:如果业务已经通过LB的会话保持把请求绑定到了某个Pod IP,那光改Service selector不一定能让存量会话感知到变化,这个问题后面第4节会专门讲。

提示:生产环境我强烈建议走ClusterIP + Ingress的方式,把“对外暴露”和“发布切换”彻底解耦。这样你甚至可以在不改Ingress、不碰LB配置的前提下,用一行命令完成蓝绿切换。

3. 核心实战:一次完整的蓝绿发布从切流量到回滚

3.1 环境准备与初始状态

我用一个模拟的Web API服务来演示,镜像用Nginx替代,业务上可以理解为返回版本号的服务。整个演示只需要一个Kubernetes集群,Minikube或Kind都可以跑通。

先看初始状态。当前线上运行的是“蓝”版本,负责处理所有真实流量。

apiVersion: apps/v1 kind: Deployment metadata: name: demo-app-blue labels: app: demo-app version: blue spec: replicas: 3 selector: matchLabels: app: demo-app version: blue template: metadata: labels: app: demo-app version: blue spec: containers: - name: app image: nginx:1.25 ports: - containerPort: 80

对应Service的初始状态,selector指向version: blue。

apiVersion: v1 kind: Service metadata: name: demo-app-svc spec: selector: app: demo-app version: blue ports: - port: 80 targetPort: 80

此时检查一下Pod和Endpoints,确认流量确实都打在蓝色Pod上:

kubectl get pods -l version=blue kubectl get endpoints demo-app-svc

Endpoints列表里应该能看到三个蓝色Pod的内网IP,这是后面判断是否成功切换的直接依据。

3.2 新版本以“绿”身份低调上线

新版本代码准备完毕后,创建 “绿” Deploymente,镜像换用新版本,标签同样遵循app: demo-app+version: green。

apiVersion: apps/v1 kind: Deployment metadata: name: demo-app-green labels: app: demo-app version: green spec: replicas: 3 selector: matchLabels: app: demo-app version: green template: metadata: labels: app: demo-app version: green spec: containers: - name: app image: nginx:1.27 readinessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 5 periodSeconds: 5 ports: - containerPort: 80

这一步很关键:绿版本上线后,它不会接收任何线上流量,因为Service的selector还只认version: blue。但绿版本Pod的健康状态、启动速度、日志输出都应该被仔细监控。

给绿版本配置readinessProbe是一个我在实战中特别强调的点。readinessProbe决定了一个Pod是不是“就绪”,也就是是否被Service纳入Endpoints。如果绿版本没有这个探针,它可能一启动就被当成可用,但实际上业务线程池还没初始化完成。到时候切换过去,流量直接打到一个外强中干的服务上,体验会非常糟糕。

创建绿版本后验证:

kubectl get pods -l version=green kubectl get endpoints demo-app-svc

此时Endpoints里应该仍然只有蓝色Pod的IP,绿色Pod虽然处于Running状态,但没有被Service选中。这一步验证通过,说明蓝绿两套环境已经就绪,且互不干扰。

3.3 流量切换的三种姿势与操作细节

等绿版本Pod全部进入Ready状态,就可以执行切换了。有三种姿势,我逐个说,你根据团队习惯选一种。

方式一:kubectl patch 直接改selector

最直接的方式,一行命令完成切换:

kubectl patch service demo-app-svc -p '{"spec":{"selector":{"app":"demo-app","version":"green"}}}'

这条命令把Service的selector从version: blue改成version: green。执行之后,Service的Endpoints会逐渐从三个蓝色IP变成三个绿色IP。整个过程通常只需要几秒,但极端情况下可能到几十秒,取决于集群规模和控制面组件刷新速度。

方式二:编辑Service YAML并apply

如果你倾向于把Service配置纳入GitOps管理,可以修改Service的manifest文件,然后apply:

kubectl apply -f service.yaml

前提是service.yaml里的selector已经改成了version: green。这种方式的好处是配置变更可追溯,配合git提交信息,你知道每一次切换是谁、在什么时候改了什么。相比patch,它更符合生产环境的审计要求,但也更慢一点,适合发布窗口不那么紧的团队。

方式三:脚本封装,一键切换

把切换动作封装成Shell脚本,适合需要频繁发布、或者值班同学不想记住长命令的团队。我做的一个简化版切换脚本逻辑:

switch(){ if [ "$1" = "green" ]; then kubectl patch service demo-app-svc -p \ '{"spec":{"selector":{"app":"demo-app","version":"green"}}}' elif [ "$1" = "blue" ]; then kubectl patch service demo-app-svc -p \ '{"spec":{"selector":{"app":"demo-app","version":"blue"}}}' else echo "usage: switch [blue|green]" fi }

不管用哪种方式,切换后第一件事就是检查Endpoints:

kubectl get endpoints demo-app-svc

如果看到Endpoint地址都换成了绿色Pod的IP,说明流量已经切到位。注意先不要急着销毁蓝版本,后面还要依赖它做回滚。

3.4 验证、巡检与一键回滚

流量切到绿版本后,不要立刻宣布发布完成。正确的是先观察一段时间。我习惯在切换后的5分钟、15分钟、30分钟三个时间点做巡检:

  • 错误率是否维持在切换前的基线水平?
  • 接口P99延迟有没有明显劣化?
  • 业务日志中是否有异常堆栈?
  • Pod的CPU、内存曲线是否在合理范围?

如果绿版本表现正常,可以进入“稳定观察期”,此时蓝版本先保留。观察期可以持续15分钟,也可以持续几小时,取决于业务形态。确认没问题后,再清理蓝版本,把资源释放出来。

如果绿版本出了问题,回滚就是再把开关拨回去:

kubectl patch service demo-app-svc -p '{"spec":{"selector":{"app":"demo-app","version":"blue"}}}'

整个过程用户几乎无感,因为Service的入口IP没有变,只是后端Pod换了。这也是蓝绿发布最吸引人的地方:回滚不是“修复”,而是“切回另一个已经被验证过的环境”。

等确认蓝版本已经稳定兜底后,再下线绿版本:

kubectl delete deployment demo-app-green

注意:除非紧急到没时间思考,否则不要在切换后立刻删除旧版本环境。蓝绿发布之所以安全,是因为你手里永远握着“当前正在跑且已验证的版本”。一旦删掉蓝版本,你就把保险绳解掉了。

4. 我踩过的坑:会话保持、连接池、配置漂移与排查速查表

4.1 会话保持:切流量之后用户掉登录

第一次在带有用户态的Web应用上做蓝绿发布,我遇到一个很尴尬的问题:流量切换过去了,但大量用户发现自己“被下线”,需要重新登录。

原因不复杂。蓝绿切换过程中,Service后端的Pod整批换了,而如果业务在Pod本地保存了Session,用户在旧Pod上的登录态自然带不到新Pod。尤其是那些没有做Session共享、依赖Pod内存保存状态的单体应用,切换到绿版本后,所有Session都丢了。

这个问题的本质不是蓝绿发布的问题,而是应用状态分布的问题。解决方案有几个方向:

  • 把Session存在外部存储,比如Redis,这样Pod无论怎么换,Session还在。
  • 如果无法快速改造,就在Service开启会话保持,让同一用户的请求尽量打到同一个Pod。云LB也有对应的会话保持策略,可以在LB层配置。
  • 发布前提前通知,选择流量低峰期切换,把掉线的影响降到最低。

我后来在团队的发布检查清单里加了一条:发布前必须确认Session是否有外部存储依赖。这个检查项看起来不起眼,但它救过我们不止一次。

4.2 长连接连接池不释放

再说一个隐蔽的坑:长连接。蓝绿切换后,Service的Endpoints变了,但已经建立的TCP长连接不会立刻断开。比如有些客户端用连接池保持了一堆到旧Pod的连接,Service后端的Pod换了,但客户端还是把请求打到旧Pod的IP上。

这个问题的表现是:明明Service已经指向绿版本了,但旧Pod的QPS还在持续下降、迟迟归不了零,连接一直悬挂着。

排查思路是先看连接状态:

kubectl exec -it [旧Pod名称] -- netstat -an | grep ESTABLISHED | wc -l

如果这个数字不降,说明连接池没有主动断开。解决方向有两个。一是规范应用实现优雅停机:在Pod被删除前,通过preStop钩子调用应用的管理接口,让进程主动关闭监听、等待存量请求处理完再退出。二是在Service层不依赖单个Pod IP做长连接,把连接收敛到网关或LB层,由它们统一管理后端连接。

我在实际项目里还遇到过更极端的:客户端是某个老旧的Java服务,连接池的校验周期很长,旧连接能存在半小时。后来我们的办法是在蓝绿切换前先让旧Pod的readinessProbe失效,把旧Pod从Service的Endpoints里摘掉,然后再慢慢排空连接。这相当于“先摘流量,再切开关”,比粗暴切换要平滑得多。

4.3 配置版本漂移,回滚回不去

蓝绿发布有个隐形杀手叫“配置漂移”。新版本上线时,团队往往同时更新了ConfigMap或环境变量,比如调整了日志级别、改了某个开关。因为Service只负责切换Pod的标签选择,它不关心Pod用了什么配置,所以当蓝版本和新配置混在一起时,问题就开始出现了。

最典型的翻车现场是这样:绿版本带上了新的配置和新的镜像,跑得很好。这时你决定回滚到蓝版本。然后你发现蓝版本虽然镜像没变,但Deployment的Pod模板已经因为某个发布流程被更新过,挂载了新的ConfigMap,启动后行为完全不是我想要的老样子。你以为你回滚了,其实你滚回了一个“用新配置跑老代码”的混合体。

应对策略是给ConfigMap和Secret做版本化命名。比如demo-app-config-v1、demo-app-config-v2,Deployment通过明确的版本引用锁定配置。蓝绿两个Deployment各自绑定自己的配置版本,不要共用同一个动态更新的ConfigMap对象。这样,切换Pod就等于把代码和对应的配置一起切换,回滚也一并回滚,不会出现“代码回去了、配置没回去”的尴尬。

4.4 常见问题排查速查表

最后整理一个我在实战中沉淀的排查速查表。发布出问题时,按表逐项排查能省下不少时间:

现象可能原因排查命令解决方案
切换后Service没有更新Endpointsselector写错或Pod未Readykubectl get endpoints/kubectl describe svc检查标签键值是否完全匹配,治理readinessProbe
旧Pod连接迟迟不断长连接连接池未释放kubectl exec -it [旧Pod] -- netstat -an设置preStop优雅停机,先摘流量再切lib
用户Session丢失Session存于Pod本地检查应用Session存储位置改用Redis外部存储,或低峰期发布
回滚后服务错误率不降配置版本没跟代码一起回滚对比新旧Pod的环境变量和ConfigMapConfigMap版本化,每个版本绑定独立配置
绿版本启动就Ready但请求404readinessProbe未配置真实健康接口kubectl describe pod查看探针状态配置基于业务接口的readinessProbe
Service下出现蓝绿两个版本Podselector标签写得太宽kubectl get pods -l app=demo-app严格使用app+version双标签,不要只按应用名选择

还有一个很多人忽略的细节:排查问题时,多使用kubectl describe而不是只get。describe会显示事件的完整历史,比如调度失败、探针失败、镜像拉取失败,信息量比只看当前状态大得多。尤其在紧急排障时,一句事件日志往往就指明了方向。

我个人在实际操作中的体会是,蓝绿发布不是一套单纯“照着配”的模板,它更像是一种思维:怎么让“上线”和“回滚”都变成一个低风险的切换动作,而不是一次提心吊胆的重建。Kubernetes只是把它实现得比传统裸机时代简单太多。最后再分享一个落地的小建议——先拿一个低风险服务试跑几轮蓝绿发布,等团队把整个流程跑顺了,再逐步推广到核心链路。别第一次上蓝绿就用在最要命的系统上,就像你不会第一次开车就上高速。

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

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

立即咨询