运维知识
悠悠
2026年4月16日

蓝绿部署实战指南:从零到一搭建生产级无缝发布系统

蓝绿部署实战指南:从零到一搭建生产级无缝发布系统

我在公司做运维这几年,最怕的就是发布新版本的时候出问题。记得有一次周五下午发版本,结果服务挂了两个小时,用户投诉电话打爆了,我在机房里忙活到晚上11点才搞定。从那以后,我就开始研究各种发布策略,蓝绿部署就是其中最实用的一种。

今天就把我这几年摸索出来的蓝绿部署经验分享给大家,全是生产环境踩过的坑和总结出的干货。

什么是蓝绿部署

蓝绿部署说白了就是准备两套一模一样的生产环境,一套叫蓝环境,一套叫绿环境。当前用户访问的是蓝环境,我要发新版本的时候,就把新版本部署到绿环境上,测试没问题了再把流量切过去。

这样做的好处是什么?如果新版本有问题,我立马就能切回到旧版本,用户基本感受不到服务中断。

我之前用滚动更新的方式,每次更新都要重启服务,虽然时间很短,但还是会有那么几秒钟的服务不可用。用了蓝绿部署之后,这个问题彻底解决了。

蓝绿部署的核心原理

整个流程其实很简单:

  1. 当前蓝环境在线提供服务
  2. 在绿环境部署新版本应用
  3. 绿环境测试通过后,负载均衡器流量从蓝环境切换到绿环境
  4. 绿环境成为新的生产环境,蓝环境变成备用环境

关键点就是这个流量切换,必须要做到瞬间完成,不能有中间状态。

实现蓝绿部署的几种方式

方式一:基于Nginx的蓝绿部署

这是我最常用的方式,成本低,配置简单。

先准备两台服务器,192.168.1.10(蓝)和192.168.1.11(绿),前面放一台Nginx做负载均衡。

Nginx配置文件关键部分:

upstream blue_pool {
    server 192.168.1.10:8080 weight=1 max_fails=3 fail_timeout=30s;
}

upstream green_pool {
    server 192.168.1.11:8080 weight=1 max_fails=3 fail_timeout=30s;
}

# 当前生产环境指向蓝环境
upstream production {
    server 192.168.1.10:8080;
}

server {
    listen 80;
    server_name api.example.com;
  
    location / {
        proxy_pass http://production;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

切换的时候,写个脚本:

#!/bin/bash
# switch.sh

NGINX_CONF="/etc/nginx/conf.d/app.conf"
CURRENT_ENV=$(grep -A1 "upstream production" $NGINX_CONF | grep server | awk '{print $2}' | cut -d: -f1)

if [ "$CURRENT_ENV" = "192.168.1.10" ]; then
    # 当前是蓝环境,切换到绿环境
    sed -i 's/server 192.168.1.10:8080;/server 192.168.1.11:8080;/' $NGINX_CONF
    echo "已切换到绿环境"
else
    # 当前是绿环境,切换到蓝环境
    sed -i 's/server 192.168.1.11:8080;/server 192.168.1.10:8080;/' $NGINX_CONF
    echo "已切换到蓝环境"
fi

# 重新加载Nginx配置
nginx -s reload

这个方案的优点是简单直接,缺点是需要手动执行切换脚本。不过对于中小型项目来说已经够用了。

方式二:基于HAProxy的蓝绿部署

HAProxy在流量切换方面比Nginx更灵活,支持动态权重调整。

HAProxy配置:

global
    daemon
    maxconn 4096

defaults
    mode http
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms

frontend web_frontend
    bind *:80
    default_backend web_servers

backend web_servers
    balance roundrobin
    # 初始状态:蓝环境权重100,绿环境权重0
    server blue 192.168.1.10:8080 weight 100 check
    server green 192.168.1.11:8080 weight 0 check

# 管理接口
stats socket /var/run/haproxy.sock mode 666 level admin
stats timeout 2m

切换脚本:

#!/bin/bash
# haproxy_switch.sh

SOCKET="/var/run/haproxy.sock"

# 获取当前蓝环境权重
BLUE_WEIGHT=$(echo "show stat" | socat stdio $SOCKET | grep "web_servers,blue" | cut -d, -f19)

if [ "$BLUE_WEIGHT" = "100" ]; then
    # 切换到绿环境
    echo "set weight web_servers/blue 0" | socat stdio $SOCKET
    echo "set weight web_servers/green 100" | socat stdio $SOCKET
    echo "已切换到绿环境"
else
    # 切换到蓝环境
    echo "set weight web_servers/blue 100" | socat stdio $SOCKET
    echo "set weight web_servers/green 0" | socat stdio $SOCKET
    echo "已切换到蓝环境"
fi

HAProxy的好处是可以实现渐进式切换,比如先把10%的流量切到新环境,观察一段时间没问题再全量切换。

方式三:基于Kubernetes的蓝绿部署

如果你们公司已经用了K8s,那蓝绿部署就更简单了。K8s的Service可以通过selector来控制流量指向。

先创建两个Deployment:

# blue-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-blue
  labels:
    app: myapp
    version: blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
      - name: app
        image: myapp:v1.0
        ports:
        - containerPort: 8080
---
# green-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-green
  labels:
    app: myapp
    version: green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
      - name: app
        image: myapp:v2.0
        ports:
        - containerPort: 8080

Service配置:

apiVersion: v1
kind: Service
metadata:
  name: app-service
spec:
  selector:
    app: myapp
    version: blue  # 当前指向蓝环境
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer

切换的时候只需要修改Service的selector:

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

这种方式的优势是与K8s生态结合紧密,可以利用K8s的健康检查、自动扩缩容等功能。

开源工具推荐

1. Argo Rollouts

这是专门为K8s设计的渐进式交付工具,支持蓝绿部署、金丝雀发布等多种策略。

安装很简单:

kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

配置文件示例:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollout-bluegreen
spec:
  replicas: 2
  strategy:
    blueGreen:
      activeService: rollout-bluegreen-active
      previewService: rollout-bluegreen-preview
      autoPromotionEnabled: false
      scaleDownDelaySeconds: 30
  selector:
    matchLabels:
      app: rollout-bluegreen
  template:
    metadata:
      labels:
        app: rollout-bluegreen
    spec:
      containers:
      - name: rollouts-demo
        image: argoproj/rollouts-demo:blue
        ports:
        - containerPort: 8080

Argo Rollouts的界面很友好,可以直观地看到部署状态,手动控制切换时机。

2. Flagger

Flagger是另一个优秀的渐进式交付工具,支持多种Service Mesh和Ingress控制器。

helm repo add flagger https://flagger.app
helm upgrade -i flagger flagger/flagger \
--namespace=istio-system \
--set crd.create=false \
--set meshProvider=istio \
--set metricsServer=http://prometheus:9090

Canary配置:

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: podinfo
  namespace: test
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: podinfo
  progressDeadlineSeconds: 60
  service:
    port: 9898
    targetPort: 9898
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      threshold: 99
      interval: 1m

3. Jenkins Blue Ocean

如果你用Jenkins做CI/CD,Blue Ocean插件提供了很好的流水线可视化界面,配合Pipeline脚本可以实现自动化蓝绿部署。

Pipeline脚本示例:

pipeline {
    agent any
  
    environment {
        BLUE_PORT = "8080"
        GREEN_PORT = "8081"
        CURRENT_ENV = ""
    }
  
    stages {
        stage('检测当前环境') {
            steps {
                script {
                    def response = sh(
                        script: "curl -s http://localhost/health | jq -r '.env'",
                        returnStdout: true
                    ).trim()
                  
                    if (response == "blue") {
                        env.CURRENT_ENV = "blue"
                        env.TARGET_ENV = "green"
                        env.TARGET_PORT = GREEN_PORT
                    } else {
                        env.CURRENT_ENV = "green"
                        env.TARGET_ENV = "blue"
                        env.TARGET_PORT = BLUE_PORT
                    }
                }
            }
        }
      
        stage('部署到目标环境') {
            steps {
                sh """
                    docker stop app-${TARGET_ENV} || true
                    docker rm app-${TARGET_ENV} || true
                    docker run -d --name app-${TARGET_ENV} -p ${TARGET_PORT}:8080 myapp:${BUILD_NUMBER}
                """
            }
        }
      
        stage('健康检查') {
            steps {
                script {
                    def maxRetries = 30
                    def retries = 0
                  
                    while (retries < maxRetries) {
                        def response = sh(
                            script: "curl -s -o /dev/null -w '%{http_code}' http://localhost:${TARGET_PORT}/health",
                            returnStdout: true
                        ).trim()
                      
                        if (response == "200") {
                            echo "健康检查通过"
                            break
                        }
                      
                        retries++
                        sleep(10)
                    }
                  
                    if (retries >= maxRetries) {
                        error("健康检查失败")
                    }
                }
            }
        }
      
        stage('切换流量') {
            steps {
                sh """
                    sed -i 's/server 192.168.1.10:${CURRENT_ENV == 'blue' ? BLUE_PORT : GREEN_PORT};/server 192.168.1.10:${TARGET_PORT};/' /etc/nginx/conf.d/app.conf
                    nginx -s reload
                """
            }
        }
    }
  
    post {
        failure {
            // 回滚逻辑
            sh """
                sed -i 's/server 192.168.1.10:${TARGET_PORT};/server 192.168.1.10:${CURRENT_ENV == 'blue' ? BLUE_PORT : GREEN_PORT};/' /etc/nginx/conf.d/app.conf
                nginx -s reload
            """
        }
    }
}

4. GitLab CI/CD

GitLab自带的CI/CD功能也很强大,可以通过.gitlab-ci.yml配置蓝绿部署流水线。

stages:
  - build
  - deploy-staging
  - switch-traffic
  - cleanup

variables:
  BLUE_HOST: "blue.example.com"
  GREEN_HOST: "green.example.com"

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

deploy-to-staging:
  stage: deploy-staging
  script:
    - |
      if [ "$(kubectl get service production -o jsonpath='{.spec.selector.version}')" = "blue" ]; then
        export TARGET_ENV="green"
        export TARGET_HOST=$GREEN_HOST
      else
        export TARGET_ENV="blue"
        export TARGET_HOST=$BLUE_HOST
      fi
    - kubectl set image deployment/app-$TARGET_ENV app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - kubectl rollout status deployment/app-$TARGET_ENV
  environment:
    name: staging
    url: http://$TARGET_HOST

switch-production:
  stage: switch-traffic
  script:
    - |
      if [ "$(kubectl get service production -o jsonpath='{.spec.selector.version}')" = "blue" ]; then
        kubectl patch service production -p '{"spec":{"selector":{"version":"green"}}}'
      else
        kubectl patch service production -p '{"spec":{"selector":{"version":"blue"}}}'
      fi
  environment:
    name: production
    url: http://api.example.com
  when: manual

cleanup-old-version:
  stage: cleanup
  script:
    - |
      if [ "$(kubectl get service production -o jsonpath='{.spec.selector.version}')" = "blue" ]; then
        kubectl scale deployment/app-green --replicas=0
      else
        kubectl scale deployment/app-blue --replicas=0
      fi
  when: manual

生产环境踩坑记录

我在实施蓝绿部署的过程中踩了不少坑,这里分享几个典型的:

坑1:数据库迁移问题

有一次我们发新版本,新版本的数据库schema有变化。结果切换到新环境后发现数据不一致,只能紧急回滚。后来我们的解决方案是:

  • 数据库迁移必须向前兼容
  • 先发布兼容新旧schema的代码版本
  • 再执行数据库迁移
  • 最后发布纯新版本代码

坑2:静态资源缓存问题

前端的js、css文件有缓存,切换环境后用户可能还在使用旧的静态资源,导致接口调用失败。我们的做法是给静态资源文件名加上版本号或者hash值。

坑3:健康检查不充分

之前只检查了HTTP状态码是200就认为服务正常,结果有一次数据库连接池耗尽,HTTP接口能访问但是业务逻辑有问题。现在我们的健康检查会验证关键业务接口。

坑4:流量切换不够平滑

最开始用的是简单的全量切换,有时候会有短暂的502错误。后来改成先切换一小部分流量观察,没问题再全量切换。

监控和回滚策略

蓝绿部署最重要的是要有完善的监控和快速回滚机制。

我们用Prometheus + Grafana做监控,关键指标包括:

  • 响应时间
  • 错误率
  • 吞吐量
  • 资源使用率

告警规则:

groups:
- name: bluegreen-deployment
  rules:
  - alert: HighErrorRate
    expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.01
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "错误率过高"
    
  - alert: HighResponseTime
    expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "响应时间过长"

回滚脚本我写得特别简单粗暴:

#!/bin/bash
# rollback.sh

echo "开始回滚..."

# 获取当前环境
CURRENT=$(kubectl get service production -o jsonpath='{.spec.selector.version}')

if [ "$CURRENT" = "blue" ]; then
    kubectl patch service production -p '{"spec":{"selector":{"version":"green"}}}'
    echo "已回滚到绿环境"
else
    kubectl patch service production -p '{"spec":{"selector":{"version":"blue"}}}'
    echo "已回滚到蓝环境"
fi

echo "回滚完成,请检查服务状态"

关键是要快,发现问题后30秒内必须能完成回滚。

成本考虑

蓝绿部署最大的缺点就是资源消耗翻倍,对于小公司来说成本压力不小。

我们的优化策略:

  1. 非生产环境不用蓝绿部署,用滚动更新就行
  2. 蓝绿环境可以共享一些无状态的服务,比如Redis、MQ
  3. 用云厂商的竞价实例降低成本
  4. 部署完成后可以把备用环境的副本数调小,保留最小可用配置

另外,数据库这种有状态的服务不适合做蓝绿部署,成本太高。我们一般是应用层做蓝绿,数据层用主从切换。

蓝绿部署确实是个好东西,特别是对于那些不能接受服务中断的业务。虽然成本高了点,但是换来的稳定性和用户体验提升是值得的。

我们公司自从用了蓝绿部署后,发布相关的故障基本没有了,周五下午发版本也不再是噩梦。当然,这套方案不是银弹,还是要根据自己的业务场景选择合适的部署策略。

如果你们公司也在考虑蓝绿部署,建议先在测试环境搭一套练练手,把流程跑通了再上生产。毕竟运维这行,稳字当头嘛。


好了,今天的分享就到这里。如果这篇文章对你有帮助,记得点个赞转发一下,让更多的运维兄弟看到。有什么问题也欢迎在评论区讨论,我会尽量回复大家。

关注@运维躬行录,我会持续分享更多生产环境的实战经验,咱们一起在运维这条路上越走越稳!

公众号:运维躬行录
个人博客:躬行笔记

文章目录

博主介绍

热爱技术的云计算运维工程师,Python全栈工程师,分享开发经验与生活感悟。
欢迎关注我的微信公众号@运维躬行录,领取海量学习资料

微信二维码