蓝绿部署维护两套完全相同的生产环境——蓝环境(当前线上)和绿环境(新版本)。切换只需修改路由指向,实现瞬间切换。
┌──────────────┐
│ 负载均衡器 │
└──────┬───────┘
│
┌────────────┼────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 🔵 蓝环境 │ │ 🟢 绿环境 │
│ v1.0 (线上) │ │ v2.0 (待切) │
│ 3 replicas │ │ 3 replicas │
└──────────────┘ └──────────────┘
│ │
└─────────┬───────────────┘
▼
┌──────────────┐
│ 共享数据库 │
│ (需迁移兼容) │
└──────────────┘
切换:100%→蓝 ══切换══▶ 100%→绿
回滚:100%→绿 ══回滚══▶ 100%→蓝| 策略 | 停机时间 | 回滚速度 | 资源成本 | 风险暴露 |
|---|---|---|---|---|
| 蓝绿部署 | 零 | 秒级 | 2x | 全量切换瞬间 |
| 金丝雀发布 | 零 | 分钟级 | 1.1x | 渐进式暴露 |
| 滚动更新 | 近零 | 分钟级 | 1x | 新老共存期 |
| Recreate | 有停机 | 分钟级 | 1x | 全量暴露 |
# 创建蓝绿环境目录
mkdir -p /opt/bluegreen/{blue,green,conf}
# 蓝环境(当前线上v1)
cat > /opt/bluegreen/blue/index.html << 'EOF'
🔵 蓝环境 v1.0 - 当前线上
EOF
# 绿环境(新版本v2)
cat > /opt/bluegreen/green/index.html << 'EOF'
🟢 绿环境 v2.0 - 新版本
EOF
# Nginx配置——指向蓝环境
cat > /opt/bluegreen/conf/nginx.conf << 'EOF'
upstream active_env {
server 127.0.0.1:8081; # blue
}
server {
listen 80;
location / {
proxy_pass http://active_env;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
EOF
# 切换到绿环境脚本
cat > /opt/bluegreen/switch-to-green.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail
echo "🟢 切换到绿环境..."
if ! curl -sf http://127.0.0.1:8082/health > /dev/null; then
echo "❌ 绿环境健康检查失败!"; exit 1
fi
sed -i 's/server 127.0.0.1:8081.*# blue/server 127.0.0.1:8082; # green/' /opt/bluegreen/conf/nginx.conf
nginx -t && nginx -s reload
echo "✅ 已切换到绿环境"
SCRIPT
chmod +x /opt/bluegreen/switch-to-green.sh
# 回滚到蓝环境
cat > /opt/bluegreen/switch-to-blue.sh << 'SCRIPT'
#!/bin/bash
sed -i 's/server 127.0.0.1:8082.*# green/server 127.0.0.1:8081; # blue/' /opt/bluegreen/conf/nginx.conf
nginx -t && nginx -s reload
echo "✅ 已回滚到蓝环境"
SCRIPT
chmod +x /opt/bluegreen/switch-to-blue.sh# 蓝环境Deployment
cat > blue-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-blue
labels:
app: myapp
slot: blue
spec:
replicas: 3
selector:
matchLabels:
app: myapp
slot: blue
template:
metadata:
labels:
app: myapp
slot: blue
spec:
containers:
- name: myapp
image: myregistry/myapp:v1.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
EOF
# 绿环境Deployment(仅改slot和image)
cat > green-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
labels:
app: myapp
slot: green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
slot: green
template:
metadata:
labels:
app: myapp
slot: green
spec:
containers:
- name: myapp
image: myregistry/myapp:v2.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
EOF
# Service——通过selector切换
cat > service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
spec:
selector:
app: myapp
slot: blue # ← 切换时改为green
ports:
- port: 80
targetPort: 8080
type: ClusterIP
EOF
kubectl apply -f blue-deployment.yaml
kubectl apply -f green-deployment.yaml
kubectl apply -f service.yaml
# 切换到绿环境
kubectl patch svc myapp-svc -p '{"spec":{"selector":{"slot":"green"}}}'
# 回滚到蓝环境
kubectl patch svc myapp-svc -p '{"spec":{"selector":{"slot":"blue"}}}'#!/bin/bash
# bluegreen-deploy.sh — 完整蓝绿部署自动化
set -euo pipefail
NAMESPACE="${1:-default}"
APP_NAME="myapp"
IMAGE_TAG="${2:?Usage: $0 }"
log() { echo "[$(date '+%H:%M:%S')] $*"; }
get_active_slot() {
kubectl get svc myapp-svc -n "$NAMESPACE" -o jsonpath='{.spec.selector.slot}'
}
deploy_inactive() {
local inactive="$1"
log "部署 ${APP_NAME}-${inactive} (${IMAGE_TAG})..."
kubectl set image deployment/"${APP_NAME}-${inactive}" myapp="myregistry/${APP_NAME}:${IMAGE_TAG}" -n "$NAMESPACE"
kubectl rollout status deployment/"${APP_NAME}-${inactive}" -n "$NAMESPACE" --timeout=300s
}
health_check() {
log "健康检查..."
for i in $(seq 1 30); do
if curl -sf "http://myapp-svc.$NAMESPACE.svc.cluster.local/health" > /dev/null 2>&1; then
log "健康检查通过 ✅"; return 0
fi
log "等待就绪... ($i/30)"; sleep 5
done
log "❌ 健康检查超时"; return 1
}
switch_traffic() {
local target="$1"
log "切换流量到 $target..."
kubectl patch svc myapp-svc -n "$NAMESPACE" -p "{"spec":{"selector":{"slot":"${target}"}}}"
log "流量切换完成 ✅"
}
main() {
local active=$(get_active_slot)
local inactive=$([[ "$active" == "blue" ]] && echo "green" || echo "blue")
log "当前活跃: $active, 部署到: $inactive"
deploy_inactive "$inactive"
if ! health_check; then
log "❌ 失败,保持 $active"; exit 1
fi
switch_traffic "$inactive"
log "✅ 完成!回滚命令:kubectl patch svc myapp-svc -n $NAMESPACE -p '{"spec":{"selector":{"slot":"$active"}}}'"
}
main "$@"
┌─────────────────────┐
│ Ingress │
└──────────┬───────────┘
┌──────────▼───────────┐
│ Service │
│ selector: slot=blue │◄── 切换点
└──────┬───────────────┘
┌────────────┴────────────┐
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ 🔵 myapp-blue │ │ 🟢 myapp-green │
│ 3 pods (v1.0) │ │ 3 pods (v2.0) │
└───────────────────┘ └───────────────────┘
│ │
└────────────┬────────────┘
┌────────▼─────────┐
│ 共享PVC/DB │
└──────────────────┘# 检查Service Endpoints
kubectl get endpoints myapp-svc
# 检查Pod readiness
kubectl get pods -l app=myapp -o wide
# 查看Pod日志
kubectl logs -l slot=green --tail=50
# 检查readinessProbe
kubectl describe deploy myapp-green | grep -A5 Readiness
# 常见:readinessProbe未通过/Pod label不匹配/port不匹配# 查看应用错误日志
kubectl logs -l slot=green | grep -i "error"
# Expand-Contract模式:
# Phase 1: 加新列(兼容旧版)ALTER TABLE users ADD COLUMN phone VARCHAR(20);
# Phase 2: 切换流量到绿环境
# Phase 3: 确认后删旧列 ALTER TABLE users DROP COLUMN old_phone;
# 回滚:只需切回蓝环境,旧代码兼容当前schema掌握Nginx/K8s蓝绿切换、自动化脚本、数据库兼容策略——零停机不是梦想,是工程实践