蓝绿发布
蓝绿发布
蓝绿发布是一种零停机部署策略,通过维护两套完全相同的生产环境(蓝环境和绿环境),实现快速切换和回滚。
什么是蓝绿发布
核心概念
┌─────────────────────────────────────┐
│ 负载均衡器 │
│ (Service/Ingress) │
└──────────────┬──────────────────────┘
│
│ 流量切换
│
┌──────┴──────┐
│ │
蓝环境 v1 绿环境 v2
(运行中) (待上线)
│ │
┌────▼────┐ ┌───▼─────┐
│ 3 Pods │ │ 3 Pods │
│ v1.0 │ │ v2.0 │
└─────────┘ └─────────┘
蓝环境(Blue):当前正在运行的生产环境
绿环境(Green):新版本部署的环境
蓝绿发布流程
- 部署绿环境:在生产集群部署新版本(v2.0)
- 测试验证:对绿环境进行完整测试
- 流量切换:将负载均衡器指向绿环境
- 监控观察:观察新版本运行状态
- 保留蓝环境:保留旧版本一段时间用于快速回滚
- 清理资源:确认无问题后删除蓝环境
优势与劣势
✅ 优势:
- 零停机部署:切换瞬间完成
- 快速回滚:秒级回滚到旧版本
- 完整测试:可在生产环境测试新版本
- 风险可控:出问题立即切回
- 用户无感知:平滑过渡
❌ 劣势:
- 资源消耗:需要双倍资源
- 数据库迁移:需要兼容两个版本
- 成本较高:临时需要 2 倍实例
- 状态管理:有状态应用需特殊处理
实现方式
方式 1:基于 Service 标签选择器
这是最简单直接的实现方式。
1. 部署蓝环境(当前版本)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-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: myapp
image: myapp:v1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
2. 创建 Service(指向蓝环境)
apiVersion: v1
kind: Service
metadata:
name: myapp-service
labels:
app: myapp
spec:
selector:
app: myapp
version: blue # 当前指向蓝色环境
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
3. 部署绿环境(新版本)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-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: myapp
image: myapp:v2.0 # 新版本
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
4. 测试绿环境
# 创建临时测试 Service
kubectl expose deployment myapp-green \
--name=myapp-green-test \
--port=80 \
--target-port=8080 \
--type=ClusterIP
# 端口转发测试
kubectl port-forward svc/myapp-green-test 8080:80
# 在另一个终端测试
curl http://localhost:8080/health
curl http://localhost:8080/api/test
# 运行完整测试套件
./scripts/integration-test.sh http://localhost:8080
5. 切换流量到绿环境
# 方法 1:使用 kubectl patch
kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'
# 方法 2:使用 kubectl edit
kubectl edit service myapp-service
# 修改 selector.version: green
# 方法 3:使用 kubectl apply
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
version: green
ports:
- protocol: TCP
port: 80
targetPort: 8080
EOF
6. 验证切换
# 查看 Service 选择器
kubectl get service myapp-service -o yaml | grep -A2 selector
# 查看 Endpoints
kubectl get endpoints myapp-service
# 测试访问
for i in {1..10}; do
curl http://myapp-service/version
sleep 1
done
7. 保留蓝环境观察期
# 监控新版本指标
kubectl top pods -l version=green
# 查看日志
kubectl logs -f -l version=green --tail=100
# 检查错误率
kubectl logs -l version=green | grep ERROR | wc -l
8. 确认无误后清理蓝环境
# 删除蓝环境
kubectl delete deployment myapp-blue
# 清理测试 Service
kubectl delete service myapp-green-test
# 重命名绿环境为蓝环境(为下次发布做准备)
kubectl patch deployment myapp-green -p '{"metadata":{"name":"myapp-blue"}}'
kubectl patch deployment myapp-green -p '{"spec":{"selector":{"matchLabels":{"version":"blue"}},"template":{"metadata":{"labels":{"version":"blue"}}}}}'
方式 2:使用 Ingress 流量切换
更适合对外暴露的服务。
1. 创建两个 Service
# 蓝环境 Service
apiVersion: v1
kind: Service
metadata:
name: myapp-blue-svc
spec:
selector:
app: myapp
version: blue
ports:
- port: 80
targetPort: 8080
---
# 绿环境 Service
apiVersion: v1
kind: Service
metadata:
name: myapp-green-svc
spec:
selector:
app: myapp
version: green
ports:
- port: 80
targetPort: 8080
2. 配置 Ingress(指向蓝环境)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-blue-svc # 指向蓝环境
port:
number: 80
3. 切换到绿环境
# 更新 Ingress
kubectl patch ingress myapp-ingress -p '
{
"spec": {
"rules": [{
"host": "myapp.example.com",
"http": {
"paths": [{
"path": "/",
"pathType": "Prefix",
"backend": {
"service": {
"name": "myapp-green-svc",
"port": {
"number": 80
}
}
}
}]
}
}]
}
}'
# 验证
curl http://myapp.example.com/version
方式 3:使用 Argo Rollouts 蓝绿发布
最推荐的自动化方式。
1. 安装 Argo Rollouts
# 安装 Argo Rollouts
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
# 安装 kubectl 插件
curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64
sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
2. 定义 Rollout 资源
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp-rollout
spec:
replicas: 3
revisionHistoryLimit: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v1.0
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
# 蓝绿发布策略
strategy:
blueGreen:
# 激活的 Service(面向用户)
activeService: myapp-active
# 预览 Service(用于测试)
previewService: myapp-preview
# 自动升级
autoPromotionEnabled: false
# 自动升级延迟
autoPromotionSeconds: 30
# 保留旧版本的时间
scaleDownDelaySeconds: 30
# 保留旧版本的副本数
scaleDownDelayRevisionLimit: 1
3. 创建 Services
# Active Service(生产流量)
apiVersion: v1
kind: Service
metadata:
name: myapp-active
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
---
# Preview Service(测试流量)
apiVersion: v1
kind: Service
metadata:
name: myapp-preview
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
4. 触发蓝绿发布
# 更新镜像触发发布
kubectl argo rollouts set image myapp-rollout \
myapp=myapp:v2.0
# 查看发布状态
kubectl argo rollouts get rollout myapp-rollout --watch
# 输出示例:
# Name: myapp-rollout
# Namespace: default
# Status: ॥ Paused
# Strategy: BlueGreen
# Images: myapp:v1.0 (stable, active)
# myapp:v2.0 (preview)
# Replicas:
# Desired: 3
# Current: 6
# Updated: 3
# Ready: 6
# Available: 6
5. 测试预览环境
# 通过 preview service 测试
kubectl port-forward svc/myapp-preview 8080:80
# 在另一个终端
curl http://localhost:8080/health
curl http://localhost:8080/api/test
# 运行测试套件
./scripts/smoke-test.sh http://localhost:8080
6. 升级(切换流量)
# 手动升级
kubectl argo rollouts promote myapp-rollout
# 查看升级进度
kubectl argo rollouts get rollout myapp-rollout --watch
# 输出示例:
# Name: myapp-rollout
# Namespace: default
# Status: ✔ Healthy
# Strategy: BlueGreen
# Images: myapp:v2.0 (stable, active)
# Replicas:
# Desired: 3
# Current: 3
# Updated: 3
# Ready: 3
# Available: 3
7. 回滚
# 中止发布(回滚到旧版本)
kubectl argo rollouts abort myapp-rollout
# 撤销到上一个版本
kubectl argo rollouts undo myapp-rollout
# 撤销到指定版本
kubectl argo rollouts undo myapp-rollout --to-revision=2
数据库兼容性处理
蓝绿发布的关键挑战:数据库必须兼容新旧两个版本。
数据库迁移策略
方案 1:向后兼容的迁移
阶段 1:添加新列(旧版本忽略)
v1.0 代码 + 数据库(有新列)
↓
阶段 2:部署新版本
v2.0 代码开始使用新列
↓
阶段 3:删除旧列(下次发布)
确认旧版本已完全下线后删除
示例:添加新字段
-- ✅ 阶段 1:添加新列(可为空)
ALTER TABLE users
ADD COLUMN email VARCHAR(255) NULL;
-- ✅ 阶段 2:新版本开始填充数据
-- v2.0 应用代码写入 email 字段
-- ✅ 阶段 3:设置为非空(下次发布)
ALTER TABLE users
MODIFY COLUMN email VARCHAR(255) NOT NULL;
方案 2:Feature Flag 控制
// 应用代码中使用 Feature Flag
func (s *UserService) GetUser(id int) (*User, error) {
user, err := s.db.GetUserByID(id)
if err != nil {
return nil, err
}
// Feature Flag 控制新功能
if s.featureFlags.IsEnabled("use_new_email_field") {
user.Email = s.db.GetUserEmail(id)
}
return user, nil
}
方案 3:双写策略
// 同时写入新旧字段
func (s *UserService) UpdateUser(user *User) error {
return s.db.Transaction(func(tx *sql.Tx) error {
// 写入旧字段(兼容 v1.0)
if err := tx.UpdateUserLegacy(user); err != nil {
return err
}
// 写入新字段(v2.0 使用)
if err := tx.UpdateUserNew(user); err != nil {
return err
}
return nil
})
}
最佳实践
1. 健康检查
确保应用有完善的健康检查端点。
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
健康检查端点实现:
// /health/live - 存活检查
func LivenessHandler(w http.ResponseWriter, r *http.Request) {
// 检查应用是否还活着
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
// /health/ready - 就绪检查
func ReadinessHandler(w http.ResponseWriter, r *http.Request) {
// 检查依赖服务
if err := checkDatabase(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
if err := checkRedis(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("Ready"))
}
2. 资源预留
确保有足够资源运行双环境。
# 设置资源限制
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
# 使用 PodDisruptionBudget 保证可用性
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myapp-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: myapp
3. 监控指标
切换前后密切监控关键指标。
# Prometheus 查询示例
# 错误率
rate(http_requests_total{status=~"5.."}[5m])
# 响应时间
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
# QPS
rate(http_requests_total[1m])
4. 自动化脚本
完整的蓝绿部署脚本:
#!/bin/bash
set -e
APP_NAME="myapp"
NEW_VERSION=$1
NAMESPACE="production"
if [ -z "$NEW_VERSION" ]; then
echo "Usage: $0 <new-version>"
exit 1
fi
echo "🚀 开始蓝绿发布: $APP_NAME -> $NEW_VERSION"
# 1. 确定当前颜色
CURRENT_COLOR=$(kubectl get service $APP_NAME-service -n $NAMESPACE -o jsonpath='{.spec.selector.version}')
echo "📍 当前颜色: $CURRENT_COLOR"
# 2. 确定新颜色
if [ "$CURRENT_COLOR" == "blue" ]; then
NEW_COLOR="green"
else
NEW_COLOR="blue"
fi
echo "🎨 新颜色: $NEW_COLOR"
# 3. 部署新版本
echo "📦 部署 $NEW_COLOR 环境..."
kubectl set image deployment/$APP_NAME-$NEW_COLOR \
$APP_NAME=myregistry/$APP_NAME:$NEW_VERSION \
-n $NAMESPACE
# 4. 等待就绪
echo "⏳ 等待 $NEW_COLOR 环境就绪..."
kubectl rollout status deployment/$APP_NAME-$NEW_COLOR -n $NAMESPACE --timeout=5m
# 5. 创建测试 Service
echo "🧪 创建测试 Service..."
kubectl expose deployment $APP_NAME-$NEW_COLOR \
--name=$APP_NAME-$NEW_COLOR-test \
--port=80 \
--target-port=8080 \
--type=ClusterIP \
-n $NAMESPACE \
--dry-run=client -o yaml | kubectl apply -f -
# 6. 运行冒烟测试
echo "🔍 运行冒烟测试..."
if ! ./scripts/smoke-test.sh $APP_NAME-$NEW_COLOR-test.$NAMESPACE.svc.cluster.local; then
echo "❌ 冒烟测试失败,回滚部署"
kubectl delete service $APP_NAME-$NEW_COLOR-test -n $NAMESPACE
exit 1
fi
# 7. 切换流量
echo "🔄 切换流量到 $NEW_COLOR 环境..."
kubectl patch service $APP_NAME-service \
-n $NAMESPACE \
-p "{\"spec\":{\"selector\":{\"version\":\"$NEW_COLOR\"}}}"
echo "✅ 流量已切换到 $NEW_COLOR 环境"
# 8. 监控
echo "📊 监控新版本 30 秒..."
sleep 30
# 9. 检查健康状态
echo "🏥 检查健康状态..."
READY_PODS=$(kubectl get pods -l app=$APP_NAME,version=$NEW_COLOR -n $NAMESPACE -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}' | wc -w)
TOTAL_PODS=$(kubectl get deployment $APP_NAME-$NEW_COLOR -n $NAMESPACE -o jsonpath='{.spec.replicas}')
if [ "$READY_PODS" -ne "$TOTAL_PODS" ]; then
echo "⚠️ 警告: 只有 $READY_PODS/$TOTAL_PODS 个 Pod 就绪"
read -p "是否继续?(y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "🔙 回滚到 $CURRENT_COLOR"
kubectl patch service $APP_NAME-service \
-n $NAMESPACE \
-p "{\"spec\":{\"selector\":{\"version\":\"$CURRENT_COLOR\"}}}"
exit 1
fi
fi
# 10. 清理
echo "🧹 清理测试 Service..."
kubectl delete service $APP_NAME-$NEW_COLOR-test -n $NAMESPACE
echo "✅ 蓝绿发布完成!"
echo "📝 旧版本 ($CURRENT_COLOR) 仍在运行,建议保留 1-7 天"
echo " 手动删除命令: kubectl delete deployment $APP_NAME-$CURRENT_COLOR -n $NAMESPACE"
5. 回滚检查清单
□ 监控错误率是否上升
□ 检查响应时间是否正常
□ 查看应用日志是否有异常
□ 验证数据库连接是否正常
□ 测试关键业务功能
□ 检查依赖服务状态
□ 确认用户反馈
故障处理
问题 1:切换后服务不可用
排查步骤:
# 1. 检查 Service Endpoints
kubectl get endpoints myapp-service
# 2. 检查 Pod 状态
kubectl get pods -l version=green
# 3. 检查 Pod 日志
kubectl logs -l version=green --tail=100
# 4. 检查健康检查
kubectl describe pod <pod-name> | grep -A10 Readiness
快速回滚:
kubectl patch service myapp-service \
-p '{"spec":{"selector":{"version":"blue"}}}'
问题 2:数据库不兼容
解决方案:
# 1. 立即回滚应用
kubectl patch service myapp-service \
-p '{"spec":{"selector":{"version":"blue"}}}'
# 2. 回滚数据库迁移
mysql -u root -p < rollback.sql
# 3. 检查数据一致性
./scripts/verify-data-consistency.sh
问题 3:资源不足
临时扩容:
# 手动扩容节点
kubectl scale deployment myapp-green --replicas=2
# 或使用 Cluster Autoscaler 自动扩容
kubectl annotate deployment myapp-green \
cluster-autoscaler.kubernetes.io/safe-to-evict="false"
对比:蓝绿 vs 金丝雀
| 特性 | 蓝绿发布 | 金丝雀发布 |
|---|---|---|
| 切换方式 | 一次性切换所有流量 | 渐进式增加流量 |
| 资源消耗 | 需要双倍资源 | 资源消耗较小 |
| 回滚速度 | 秒级回滚 | 需要逐步回滚 |
| 风险控制 | 风险集中在切换时刻 | 风险分散,影响范围小 |
| 测试验证 | 可充分测试完整环境 | 生产环境逐步验证 |
| 适用场景 | 重要系统、需要快速回滚 | 大规模应用、风险高的更新 |
| 复杂度 | 相对简单 | 需要流量控制机制 |
选择建议:
- 小型应用、资源有限:金丝雀发布
- 关键业务、要求零停机:蓝绿发布
- 数据库变更较大:金丝雀发布(逐步验证)
- 有充足资源、需要快速回滚:蓝绿发布
小结
蓝绿发布是一种简单高效的零停机部署策略:
核心要点:
- 维护蓝绿两套环境
- 流量一次性切换
- 快速回滚能力
- 需要双倍资源
实现方式:
- Service 标签选择器:最简单
- Ingress 流量切换:适合对外服务
- Argo Rollouts:自动化管理
关键挑战:
- 数据库兼容性
- 资源成本
- 状态管理
下一章我们将学习更多云原生发布策略和工具。