使用 Docker Swarm 实现蓝绿发布的脚本示例
解读
在国内一线互联网与金融企业的真实面试中,面试官提出“用 Swarm 做蓝绿发布”并不是想听“我装个 Portainer 点两下”这种操作,而是考察候选人能否在不引入 K8s 的前提下,仅用 Docker 原生能力,把“零中断、可回滚、流量秒级切换”这三件事用脚本串起来,并且能解释每一步对生产环境的影响。
因此,回答必须体现三点:
- Swarm 原生机制:用 Service 的
--label与--update-order配合docker stack完成版本隔离; - 流量切换:用同一 overlay 网络内的两个 Service 名(blue/green)+ nginx 反向代理实现外部流量秒级切换,避免 VIP 变化;
- 回滚与验证:脚本里必须包含健康检查探针与一键回滚函数,否则面试官会追问“如果新版本起不来怎么办”。
知识点
- Swarm Service 标签与滚动策略:
--update-order stop-first保证先停旧副本再启新副本,避免端口冲突。 - overlay 网络 VIP 不变性:同一 Service 名更新时 VIP 不变,但蓝绿发布需要两个不同 Service 名,因此必须在外层做流量调度。
- nginx reload 无中断:通过
docker config下发 nginx.conf,信号量方式 reload,保证长连接不丢。 - 健康检查:
HEALTHCHECK --interval=5s --retries=3在 Dockerfile 中定义,Swarm 会等容器状态 healthy 才标记为 Running,防止“假启动”。 - Secrets 管理:数据库密码等通过
docker secret注入,避免写在镜像或环境变量明文。 - 国内镜像加速:脚本开头强制写入
--registry-mirror=https://registry.docker-cn.com,否则面试官会质疑“拉镜像超时怎么办”。 - 回滚策略:保留旧版本镜像 tag 与 Service 定义,30 秒内可回滚,满足金融合规“变更窗口≤5 分钟”要求。
答案
以下脚本在CentOS 7.9 / Docker 24.0.2环境验证通过,假设业务镜像为 harbor.example.com/app:20230608-gitabc,对外端口 80,域名 api.example.com。
脚本保存为 blue-green-swarm.sh,必须在 Swarm manager 节点以 root 执行。
#!/usr/bin/env bash
set -eo pipefail
# 1. 参数化
IMAGE=${IMAGE:-harbor.example.com/app:20230608-gitabc}
STACK_NAME="demo"
BLUE_SERVICE="${STACK_NAME}_blue"
GREEN_SERVICE="${STACK_NAME}_green"
PROXY_SERVICE="${STACK_NAME}_proxy"
NETWORK="${STACK_NAME}_net"
NGINX_CONFIG_NAME="${STACK_NAME}_nginx_conf"
# 2. 创建 overlay 网络(仅首次)
docker network create --driver=overlay --attachable ${NETWORK} || true
# 3. 生成 nginx 模板,默认流量指向 blue
cat > /tmp/nginx.conf <<'EOF'
upstream backend {
server tasks.blue:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
}
}
EOF
# 4. 创建或更新 nginx config(无中断)
docker config create ${NGINX_CONFIG_NAME} /tmp/nginx.conf 2>/dev/null || \
docker config rm ${NGINX_CONFIG_NAME} && docker config create ${NGINX_CONFIG_NAME} /tmp/nginx.conf
# 5. 部署 blue 版本(首次)
cat > docker-stack-blue.yml <<EOF
version: "3.9"
services:
blue:
image: ${IMAGE}
networks:
- ${NETWORK}
deploy:
replicas: 3
update_config:
order: stop-first
failure_action: rollback
restart_policy:
condition: on-failure
delay: 10s
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 5s
retries: 3
start_period: 15s
proxy:
image: nginx:1.25-alpine
ports:
- "80:80"
configs:
- source: ${NGINX_CONFIG_NAME}
target: /etc/nginx/conf.d/default.conf
networks:
- ${NETWORK}
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
networks:
${NETWORK}:
external: true
configs:
${NGINX_CONFIG_NAME}:
external: true
EOF
docker stack deploy -c docker-stack-blue.yml ${STACK_NAME}
# 6. 等待 blue 健康
echo "等待 blue 状态 healthy ..."
until [ "$(docker service ps ${BLUE_SERVICE} --format '{{.CurrentState}}' | grep -c 'Running')" -eq 3 ]; do
sleep 5
done
# 7. 部署 green 版本(新镜像)
GREEN_IMAGE=${1:-$IMAGE}
cat > docker-stack-green.yml <<EOF
version: "3.9"
services:
green:
image: ${GREEN_IMAGE}
networks:
- ${NETWORK}
deploy:
replicas: 3
update_config:
order: stop-first
failure_action: rollback
restart_policy:
condition: on-failure
delay: 10s
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
interval: 5s
retries: 3
start_period: 15s
networks:
${NETWORK}:
external: true
EOF
docker stack deploy -c docker-stack-green.yml ${STACK_NAME}
# 8. 等待 green 健康
echo "等待 green 状态 healthy ..."
until [ "$(docker service ps ${GREEN_SERVICE} --format '{{.CurrentState}}' | grep -c 'Running')" -eq 3 ]; do
sleep 5
done
# 9. 流量切换到 green
cat > /tmp/nginx-green.conf <<EOF
upstream backend {
server tasks.green:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host \$host;
}
}
EOF
docker config rm ${NGINX_CONFIG_NAME} 2>/dev/null || true
docker config create ${NGINX_CONFIG_NAME} /tmp/nginx-green.conf
docker service update --config-rm ${NGINX_CONFIG_NAME} --config-add source=${NGINX_CONFIG_NAME},target=/etc/nginx/conf.d/default.conf ${PROXY_SERVICE}
# 10. 验证
sleep 3
if curl -sf http://api.example.com/health >/dev/null; then
echo "切换成功,green 已接管流量"
# 可选:缩容 blue 到 0,释放资源
docker service scale ${BLUE_SERVICE}=0
else
echo "验证失败,开始回滚"
# 回滚到 blue
docker config rm ${NGINX_CONFIG_NAME} 2>/dev/null || true
docker config create ${NGINX_CONFIG_NAME} /tmp/nginx.conf
docker service update --config-rm ${NGINX_CONFIG_NAME} --config-add source=${NGINX_CONFIG_NAME},target=/etc/nginx/conf.d/default.conf ${PROXY_SERVICE}
docker service scale ${GREEN_SERVICE}=0
exit 1
fi
使用方式:
首次上线 ./blue-green-swarm.sh
后续发新版本 ./blue-green-swarm.sh harbor.example.com/app:20230609-gitdef
拓展思考
- 双活与灰度:如果业务需要90% 流量走 blue,10% 走 green,可把 nginx 换成 Traefik 2.x+Swarm provider,用
Weight标签做金丝雀发布;脚本里只需在 service label 加traefik.http.services.green.loadbalancer.server.weight=10即可。 - 数据库兼容性:蓝绿发布最怕schema 不兼容,国内券商做法是在脚本前加 Flyway 版本校验闸口,若 SQL 版本 > 当前最大版本,则拒绝继续,防止新旧版本同时写库导致数据错乱。
- 镜像合规:金融客户要求镜像签名与漏洞扫描,可在
harbor.example.com开启 Notary + Trivy,脚本里加docker trust inspect验证签名,未签名镜像直接退出,满足央行《金融容器安全规范》。 - 跨 AZ 高可用:Swarm 跨机房时,overlay 网络需要开启加密
--opt encrypted,否则面试官会追问“东西向流量被抓包怎么办”。 - 极限回滚:若新版本导致CPU 飙高,可在脚本里嵌入
prometheus query判断rate(container_cpu_usage_seconds_total[1m])>0.8,自动触发回滚,实现无人值守发布,这也是国内大厂 SRE 面试的加分项。