蓝绿部署实战指南:从零到一搭建生产级无缝发布系统
蓝绿部署实战指南:从零到一搭建生产级无缝发布系统
我在公司做运维这几年,最怕的就是发布新版本的时候出问题。记得有一次周五下午发版本,结果服务挂了两个小时,用户投诉电话打爆了,我在机房里忙活到晚上11点才搞定。从那以后,我就开始研究各种发布策略,蓝绿部署就是其中最实用的一种。
今天就把我这几年摸索出来的蓝绿部署经验分享给大家,全是生产环境踩过的坑和总结出的干货。
什么是蓝绿部署
蓝绿部署说白了就是准备两套一模一样的生产环境,一套叫蓝环境,一套叫绿环境。当前用户访问的是蓝环境,我要发新版本的时候,就把新版本部署到绿环境上,测试没问题了再把流量切过去。
这样做的好处是什么?如果新版本有问题,我立马就能切回到旧版本,用户基本感受不到服务中断。
我之前用滚动更新的方式,每次更新都要重启服务,虽然时间很短,但还是会有那么几秒钟的服务不可用。用了蓝绿部署之后,这个问题彻底解决了。
蓝绿部署的核心原理
整个流程其实很简单:
- 当前蓝环境在线提供服务
- 在绿环境部署新版本应用
- 绿环境测试通过后,负载均衡器流量从蓝环境切换到绿环境
- 绿环境成为新的生产环境,蓝环境变成备用环境
关键点就是这个流量切换,必须要做到瞬间完成,不能有中间状态。
实现蓝绿部署的几种方式
方式一:基于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 "已切换到蓝环境"
fiHAProxy的好处是可以实现渐进式切换,比如先把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: 8080Service配置:
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: 8080Argo 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:9090Canary配置:
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: 1m3. 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秒内必须能完成回滚。
成本考虑
蓝绿部署最大的缺点就是资源消耗翻倍,对于小公司来说成本压力不小。
我们的优化策略:
- 非生产环境不用蓝绿部署,用滚动更新就行
- 蓝绿环境可以共享一些无状态的服务,比如Redis、MQ
- 用云厂商的竞价实例降低成本
- 部署完成后可以把备用环境的副本数调小,保留最小可用配置
另外,数据库这种有状态的服务不适合做蓝绿部署,成本太高。我们一般是应用层做蓝绿,数据层用主从切换。
蓝绿部署确实是个好东西,特别是对于那些不能接受服务中断的业务。虽然成本高了点,但是换来的稳定性和用户体验提升是值得的。
我们公司自从用了蓝绿部署后,发布相关的故障基本没有了,周五下午发版本也不再是噩梦。当然,这套方案不是银弹,还是要根据自己的业务场景选择合适的部署策略。
如果你们公司也在考虑蓝绿部署,建议先在测试环境搭一套练练手,把流程跑通了再上生产。毕竟运维这行,稳字当头嘛。
好了,今天的分享就到这里。如果这篇文章对你有帮助,记得点个赞转发一下,让更多的运维兄弟看到。有什么问题也欢迎在评论区讨论,我会尽量回复大家。
关注@运维躬行录,我会持续分享更多生产环境的实战经验,咱们一起在运维这条路上越走越稳!
公众号:运维躬行录
个人博客:躬行笔记