1. 为什么你的微服务部署还在“停机维护”?聊聊蓝绿部署那点事
嘿,朋友们,我是老王,一个在微服务和容器化领域摸爬滚打了十来年的老码农。不知道你们有没有经历过这种场景:半夜三更,整个团队如临大敌,发布公告写好,服务准备下线,就为了更新一个功能。用户看着“系统维护中”的页面,你在屏幕前祈祷一切顺利,千万别回滚。这种体验,说实话,糟透了。
后来我们引入了滚动更新,情况好了不少,但心里还是不踏实。新版本Pod一个个起来,老版本Pod一个个下线,流量在中间“摇摆”,万一新版本有隐藏的Bug,一部分用户已经“中招”了。直到我们把目光投向了蓝绿部署,才真正找到了那种“优雅”和“从容”的感觉。
简单来说,蓝绿部署就是准备两套完全独立的环境:一套是当前正在服务线上流量的“绿”环境,另一套是部署了新版本的“蓝”环境。当“蓝”环境经过充分验证后,我们通过一个“开关”,瞬间将所有流量从“绿”环境切换到“蓝”环境。如果“蓝”环境有问题,再瞬间切回“绿”环境。整个过程对用户来说是无感的,真正实现了零停机和瞬时回滚。
听起来很美好,对吧?但实操起来,自己搭建两套K8s集群,管理两套负载均衡,维护成本高得吓人。别急,今天我就手把手带你,用阿里云云效和ACK(阿里云容器服务Kubernetes版),在不增加额外硬件和复杂配置的情况下,玩转微服务的蓝绿部署。你会发现,借助成熟的云原生工具链,这件事比你想象的要简单得多。
2. 环境准备:给你的ACK集群和云效流水线“热身”
工欲善其事,必先利其器。在开始编排我们的蓝绿交响曲之前,得先把舞台和乐器准备好。这里不需要你从零开始搭建K8s,阿里云ACK已经为我们提供了托管的、高可用的Kubernetes集群,省去了大量运维的心力。
2.1 搭建你的ACK“双舞台”
蓝绿部署的核心是两套独立的环境。在ACK里,我们不需要真的搞两个物理集群,那样太奢侈了。一个更巧妙的做法是利用Kubernetes的命名空间(Namespace)进行逻辑隔离。
登录阿里云容器服务控制台,创建一个标准的ACK托管版集群。节点配置根据你的业务量来,初期2-4个节点足够。关键步骤来了:为蓝绿环境创建两个独立的命名空间。
# 创建蓝色环境命名空间 kubectl create namespace production-blue # 创建绿色环境命名空间 kubectl create namespace production-green你可以为这两个命名空间打上不同的标签,方便后续管理:
# blue-namespace.yaml apiVersion: v1 kind: Namespace metadata: name: production-blue labels: env: production deployment-color: blue --- # green-namespace.yaml apiVersion: v1 kind: Namespace metadata: name: production-green labels: env: production deployment-color: green接下来,我们需要一个统一的“流量指挥家”——阿里云应用型负载均衡ALB。在ACK集群中创建一个ALB实例,它将成为我们对外暴露服务的唯一入口,也是后续实现流量瞬间切换的关键。在ALB的控制台,创建一个监听器,指向我们后端的ACK集群。先别急着配置具体路由,这个我们后面会通过Ingress来动态管理。
2.2 配置云效流水线:你的自动化“指挥棒”
环境搭好了,我们需要一个自动化的“指挥棒”来协调整个部署流程。这就是阿里云云效的流水线。云效的好处是它与ACK、ACR(容器镜像服务)同属阿里云生态,集成起来异常顺畅。
首先,在云效中创建一个新的“企业级”流水线。在代码源部分,关联你的Git仓库(比如Gitee或GitLab)。然后,我们需要精心设计流水线的阶段。一个典型的蓝绿部署流水线会包含以下阶段:
- 代码质量门禁:自动运行单元测试、代码扫描。
- 构建与打包:编译代码,构建Docker镜像。
- 镜像推送:将镜像推送到ACR,并打上包含Git提交哈希和构建时间的标签。
- 部署到预备环境(蓝):将新版本部署到“蓝色”命名空间。
- 自动化验证:对蓝色环境进行接口测试、集成测试。
- 流量切换:通过更新ALB/Ingress配置,将流量从绿色切换到蓝色。
- 观察与回滚:监控蓝色环境运行状态,如有问题,一键切回绿色。
在流水线配置中,最关键的是要能识别当前“线上”是哪个颜色,以及决定本次部署的目标颜色。我常用的一个策略是:在云效的“环境变量”或“参数”中,用一个变量(如ACTIVE_COLOR)来记录当前生产环境活跃的颜色。每次部署流程都会读取这个变量,然后部署到非活跃的那个颜色环境。
3. 核心实战:一步步实现蓝绿部署流水线
理论说再多不如动手干。下面我们就来拆解流水线中最核心的几个环节,看看具体的配置和命令是怎么写的。
3.1 镜像构建与推送:打好每一次发布的“标签”
构建阶段的核心是生成一个唯一的、可追溯的Docker镜像。我强烈建议不要只用latest标签,这不利于回滚。我的组合拳是:<分支名>-<git提交短哈希>-<时间戳>。
在云效的“构建”阶段,添加一个“执行命令”的步骤:
#!/bin/bash # 获取Git提交短哈希 GIT_SHA=$(git rev-parse --short HEAD) # 获取当前时间戳 TIMESTAMP=$(date +%Y%m%d%H%M%S) # 定义镜像标签 IMAGE_TAG="${CI_COMMIT_REF_NAME}-${GIT_SHA}-${TIMESTAMP}" # 构建镜像 docker build -t $REGISTRY/$IMAGE_REPO:$IMAGE_TAG . # 额外打一个latest标签(指向本次构建) docker tag $REGISTRY/$IMAGE_REPO:$IMAGE_TAG $REGISTRY/$IMAGE_REPO:latest # 推送镜像 docker push $REGISTRY/$IMAGE_REPO:$IMAGE_TAG docker push $REGISTRY/$IMAGE_REPO:latest这里CI_COMMIT_REF_NAME和REGISTRY等都可以作为云效的预定义变量或自定义变量传入。这样,每次构建的镜像都有唯一标识,在ACR中一目了然。
3.2 部署到“蓝色”环境:让新版本先“彩排”
假设当前ACTIVE_COLOR=green,那么本次部署的目标就是blue命名空间。我们需要准备一套Kubernetes的部署清单(YAML文件),但其中关于镜像版本和命名空间的部分需要被流水线动态替换。
我的做法是在代码库中存放一套模板文件,例如deployment-bluegreen-template.yaml。在云效的“部署”阶段,使用sed或envsubst命令进行渲染:
#!/bin/bash # 确定目标颜色 if [ "$ACTIVE_COLOR" == "green" ]; then TARGET_COLOR="blue" TARGET_NAMESPACE="production-blue" else TARGET_COLOR="green" TARGET_NAMESPACE="production-green" fi # 渲染 Kubernetes 配置文件 export DEPLOYMENT_COLOR=$TARGET_COLOR export IMAGE_URL="$REGISTRY/$IMAGE_REPO:$IMAGE_TAG" export NAMESPACE=$TARGET_NAMESPACE # 使用 envsubst 替换模板中的变量 envsubst < k8s/deployment-bluegreen-template.yaml > k8s/deployment-$TARGET_COLOR.yaml envsubst < k8s/service-template.yaml > k8s/service-$TARGET_COLOR.yaml envsubst < k8s/ingress-template.yaml > k8s/ingress-$TARGET_COLOR.yaml # 应用配置到目标命名空间 kubectl apply -f k8s/deployment-$TARGET_COLOR.yaml -n $TARGET_NAMESPACE kubectl apply -f k8s/service-$TARGET_COLOR.yaml -n $TARGET_NAMESPACE # 注意:Ingress暂时不应用,等验证通过后再切换模板文件deployment-bluegreen-template.yaml长这样:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp-${DEPLOYMENT_COLOR} labels: app: myapp version: ${DEPLOYMENT_COLOR} spec: replicas: 2 selector: matchLabels: app: myapp version: ${DEPLOYMENT_COLOR} # 关键!通过version标签区分蓝绿 template: metadata: labels: app: myapp version: ${DEPLOYMENT_COLOR} spec: containers: - name: myapp image: ${IMAGE_URL} # 动态替换为本次构建的镜像 ports: - containerPort: 8080 readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 30 periodSeconds: 5这里有个关键点:Deployment 和 Pod 的标签里,我增加了一个version: blue/green的标签。后续Service和Ingress正是通过选择这个标签来决定将流量路由到哪一组Pod。
3.3 流量切换的魔法:Ingress与Service的联动
这是蓝绿部署最精髓的一步。我们之前创建了ALB作为入口,现在需要在ACK集群内创建Ingress资源来定义路由规则。为了让流量能在蓝绿环境间切换,我们需要两套Service,但只有一个Ingress。
创建蓝绿Service:分别为蓝色和绿色环境创建ClusterIP类型的Service。它们通过
selector中的version标签来分别指向各自环境的Pod。# service-blue.yaml apiVersion: v1 kind: Service metadata: name: myapp-service-blue namespace: production-blue spec: selector: app: myapp version: blue # 选择蓝色环境的Pod ports: - port: 80 targetPort: 8080创建统一的Ingress:这个Ingress定义路由规则,但其后端服务(
serviceName)指向哪个Service,是可以动态修改的。初始状态下,它指向绿色环境的Service。apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: myapp-ingress annotations: # 使用阿里云ALB的注解 alb.ingress.kubernetes.io/backend-protocol: HTTP alb.ingress.kubernetes.io/healthcheck-path: /actuator/health spec: ingressClassName: alb rules: - host: api.yourdomain.com http: paths: - path: / pathType: Prefix backend: service: name: myapp-service-green # 初始指向绿色服务 port: number: 80执行切换:当蓝色环境部署并验证通过后,流量切换就变成了一个极其简单的操作——修改Ingress的一个字段。在云效流水线中添加一个“流量切换”步骤:
# 将Ingress的后端服务从绿色改为蓝色 kubectl patch ingress myapp-ingress -p '{"spec":{"rules":[{"host":"api.yourdomain.com","http":{"paths":[{"path":"/","backend":{"service":{"name":"myapp-service-blue","port":{"number":80}}},"pathType":"Prefix"}]}}]}}'这条
kubectl patch命令会直接更新Ingress的配置。阿里云ALB控制器会实时监听到这个变化,并在几秒内将ALB的后端服务器组从绿色的Pod切换到蓝色的Pod。这个过程是瞬间的,不会丢弃任何在线连接。
3.4 自动化验证与安全回滚
切换流量不是结束。我们必须观察蓝色环境在承接真实流量后的表现。在云效流水线中,可以在切换后加入一个“观察期”(例如5-10分钟),并配置自动化验证。
- 自动化验证:添加一个步骤,使用curl或专业的API测试工具,对新环境的核心接口进行一轮快速冒烟测试,检查响应状态码、关键业务字段是否正确。
- 监控告警对接:确保你的应用监控(如ARMS)和日志服务(SLS)已经就绪。在观察期内,密切关注错误率、延迟等关键指标。可以设置一个云效的“人工卡点”,让负责人查看监控仪表盘确认无误后再继续。
- 一键回滚:如果发现蓝色环境有严重问题,回滚操作和切换操作一样简单——将Ingress的backend改回原来的绿色Service。因为绿色环境一直保持运行且未做任何改动,所以回滚是瞬时且安全的。
# 回滚到绿色环境 kubectl patch ingress myapp-ingress -p '{"spec":{"rules":[{"host":"api.yourdomain.com","http":{"paths":[{"path":"/","backend":{"service":{"name":"myapp-service-green","port":{"number":80}}},"pathType":"Prefix"}]}}]}}' - 环境清理与状态更新:确认蓝色环境稳定运行一段时间(例如30分钟)后,流水线可以执行最后一步:更新环境状态变量(将
ACTIVE_COLOR从green改为blue),并为下一轮部署做好准备。此时,旧的绿色环境可以保留一段时间作为热备,也可以选择将其下线以节省资源。
4. 避坑指南:我在蓝绿部署中踩过的那些“雷”
看起来流程很顺畅?在实际操作中,我踩过不少坑,这里分享给你,希望能帮你省下几个小时甚至几天的排查时间。
第一个大坑:会话(Session)保持问题。如果你的应用是有状态的,用户登录信息存在了绿色环境的Pod内存里,流量突然切到蓝色环境,用户会话就丢了,直接导致“被登出”。解决方案有两个:一是将会话数据外部化,存到Redis等共享存储中,这是云原生应用的最佳实践;二是在ALB层面开启会话保持(基于Cookie或源IP),确保同一个用户的请求在切换窗口期内仍落到同一个颜色环境,但这会延长整体切换时间。
第二个坑:数据库与缓存的数据兼容性。新版本(蓝色)的数据库表结构或缓存数据结构如果与旧版本(绿色)不兼容,切换后立刻就会报错。务必把数据库迁移(Migration)作为部署流程的一部分,并在切换前确保迁移已完成且可回退。对于缓存,可以考虑在部署后让蓝色环境预热缓存,或者使用双写策略来平滑过渡。
第三个坑:配置管理混乱。蓝色和绿色环境除了代码版本,配置(如Nacos中的配置项)也必须严格区分。我的经验是,使用不同的命名空间(Namespace)或分组(Group)来隔离蓝绿环境的配置。在应用启动时,通过环境变量注入当前的颜色标识,从而拉取对应环境的配置。
第四个坑:健康检查不充分。就绪探针(Readiness Probe)没配置好,Pod还没真正启动完成(比如没注册到Nacos、数据库连接池没初始化好)就被标记为Ready,流量切过来直接500错误。一定要确保就绪探针检查的是应用的业务就绪状态,而不仅仅是Web端口监听。Spring Boot Actuator的/actuator/health/readiness端点是个好选择,但要记得自定义健康指示器,把关键外部依赖(数据库、Redis、注册中心)的健康状态都包含进去。
5. 高阶玩法与优化:让部署更丝滑
基础流程跑通后,我们可以玩点更高级的,进一步提升安全性和用户体验。
金丝雀发布与蓝绿部署结合:蓝绿是“全有或全无”的切换,风险依然集中。我们可以先进行金丝雀发布——只将一小部分流量(比如1%)导入蓝色环境,观察监控指标。如果一切正常,再逐步放大蓝色环境的流量比例(5%,10%,50%),最后完成100%切换。阿里云ALB原生支持基于权重的流量转发,你可以通过修改Ingress的注解,轻松实现按权重的流量分发,这比单纯的蓝绿切换更平滑。
自动化测试集成:在部署到蓝色环境后、切换流量前,插入一个自动化集成测试阶段。这个阶段可以自动运行一套针对蓝色环境API的测试用例,只有全部通过才允许进行流量切换。云效流水线支持调用自定义的测试任务,你可以把测试框架(如Postman Collection, Jmeter脚本)集成进来。
基础设施即代码(IaC):我们上面手动创建命名空间、Service、Ingress的过程,完全可以代码化。使用Terraform或阿里云资源编排服务ROS来定义和管理这些K8s资源。这样,你的蓝绿环境本身也是可版本化、可重复创建和销毁的。将环境准备也纳入流水线,实现真正的“一键部署”。
成本优化:维护两套完全一样的环境,资源成本是双倍的。一个优化策略是:在非活跃环境(比如当前的绿色环境)将Pod副本数缩容到1甚至0。当需要部署新版本时,先扩容绿色环境,部署完成并测试后,再进行蓝绿切换,切换完成后再将蓝色环境(现在是旧版本)缩容。这样在大部分时间,你只承担一套完整环境+一个最小化备用环境的成本。
踩过这些坑,优化过这些流程后,你会发现蓝绿部署不再是那个听起来高大上却不敢轻易尝试的技术。它变成了一种可靠的、可预测的发布标准流程。团队对发布的信心增强了,再也不用在深夜提心吊胆。更重要的是,它把发布从一个“运维事件”变成了一个“常规开发活动”,极大地提升了业务的迭代速度。希望这篇实战指南能帮你顺利落地这套流程,如果有任何问题,欢迎随时交流。