|
|
每次上线都像拆炸弹?改完代码心里没底,点下部署按钮手都在抖?别慌,今天我们就来聊聊怎么让生产部署变得像换灯泡一样简单——关掉旧的,拧上新的,灯还不能灭。
本文你将学到
蓝绿部署的核心原理和实操方法
金丝雀发布如何让风险降到最低
Docker 环境下实现零停机更新的完整方案
出问题时秒级回滚的保命技巧
━━━━━━━━━━━━━━━━━━━━
阅读时间: 约 12 分钟
实操时间: 约 40 分钟
难度等级: 中高级(需要 Day 1-26 的基础)
━━━━━━━━━━━━━━━━━━━━
为什么要聊部署策略
先说个真实场景:你辛辛苦苦写了一周的新功能,测试环境跑得好好的,信心满满地部署到生产环境。结果——白屏了。用户炸锅了,老板电话打过来了,你手忙脚乱地回滚,发现回滚脚本上次改完忘记测了。
这种场景,但凡做过几年开发的人都经历过。问题的根源不是代码写得差,而是部署策略太粗暴。就像搬家,你不能把所有东西一股脑扔到新房子里,得有计划地搬,搬错了还能搬回来。
蓝绿部署:两套房子轮着住
原理
蓝绿部署的思路特别简单:准备两套完全一样的环境,一套跑老版本(蓝),一套部署新版本(绿)。新版本验证没问题后,把流量从蓝切到绿。出问题?秒切回蓝。
- 用户请求
- |
- v
- +----------+
- | Nginx |
- | 负载均衡 |
- +----------+
- / \
- 当前流量 待切换
- / \
- +---------+ +---------+
- | 蓝环境 | | 绿环境 |
- | (v1.0) | | (v1.1) |
- | 运行中 | | 已就绪 |
- +---------+ +---------+
- | |
- +---------+ +---------+
- | 数据库 | | 数据库 |
- | (共享) | | (共享) |
- +---------+ +---------+
复制代码
这就像你有两套房子,一套在住,另一套装修好了。搬过去住两天,发现马桶漏水?直接搬回老房子,一点不耽误。
实操:Docker Compose 实现蓝绿部署
先准备目录结构:
- mkdir -p ~/docker-blue-green && cd ~/docker-blue-green
复制代码
创建一个简单的应用来演示。先写:
- mkdir -p app
- cat > app/server.js << 'EOF'
- const http = require('http');
- const version = process.env.APP_VERSION || 'unknown';
- const color = process.env.APP_COLOR || 'unknown';
- const server = http.createServer((req, res) => {
- if (req.url === '/health') {
- res.writeHead(200, { 'Content-Type': 'application/json' });
- res.end(JSON.stringify({ status: 'healthy', version, color }));
- return;
- }
- res.writeHead(200, { 'Content-Type': 'text/plain' });
- res.end(`Hello from ${color} environment, version ${version}\n`);
- });
- server.listen(3000, () => {
- console.log(`${color} server v${version} running on port 3000`);
- });
- EOF
复制代码
写:
- cat > app/Dockerfile << 'EOF'
- FROM node:20-alpine
- WORKDIR /app
- COPY server.js .
- EXPOSE 3000
- CMD ["node", "server.js"]
- EOF
复制代码
创建蓝环境的 compose 文件:
- services:
- app-blue:
- build: ./app
- environment:
- - APP_VERSION=1.0.0
- - APP_COLOR=blue
- networks:
- - web
- healthcheck:
- test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
- interval: 5s
- timeout: 3s
- retries: 3
- networks:
- web:
- external: true
复制代码
创建绿环境的 compose 文件:
- services:
- app-green:
- build: ./app
- environment:
- - APP_VERSION=1.1.0
- - APP_COLOR=green
- networks:
- - web
- healthcheck:
- test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
- interval: 5s
- timeout: 3s
- retries: 3
- networks:
- web:
- external: true
复制代码
Nginx 配置:
- upstream backend {
- server app-blue:3000;
- }
- server {
- listen 80;
- location / {
- proxy_pass http://backend;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- }
- location /health {
- proxy_pass http://backend;
- }
- }
复制代码
创建 Nginx 的 compose 文件:
- services:
- nginx:
- image: nginx:alpine
- ports:
- - "8080:80"
- volumes:
- - ./nginx.conf:/etc/nginx/conf.d/default.conf
- networks:
- - web
- networks:
- web:
- external: true
复制代码
启动蓝环境:
- # 创建共享网络
- docker network create web
- # 启动蓝环境
- docker compose -f docker-compose.blue.yml up -d --build
- # 启动 Nginx
- docker compose -f docker-compose.nginx.yml up -d
复制代码
验证蓝环境正常工作:
- curl http://localhost:8080
- # 预期输出: Hello from blue environment, version 1.0.0
复制代码
现在部署绿环境:
- # 启动绿环境(此时蓝环境仍在服务)
- docker compose -f docker-compose.green.yml up -d --build
- # 验证绿环境健康
- docker compose -f docker-compose.green.yml ps
复制代码
切换流量到绿环境——修改中的 upstream:
- sed -i 's/app-blue:3000/app-green:3000/' nginx.conf
- # 重新加载 Nginx 配置(零停机)
- docker compose -f docker-compose.nginx.yml exec nginx nginx -s reload
复制代码
验证切换成功:
- curl http://localhost:8080
- # 预期输出: Hello from green environment, version 1.1.0
复制代码
回滚
发现问题?一条命令切回去:
- sed -i 's/app-green:3000/app-blue:3000/' nginx.conf
- docker compose -f docker-compose.nginx.yml exec nginx nginx -s reload
复制代码
就这么简单。蓝环境一直在那儿等着你,随时可以切回来。
金丝雀发布:先派侦察兵
原理
蓝绿部署虽然好,但有个问题——切换是全量的,要么全走新版本,要么全走老版本。金丝雀发布更精细,先让一小部分流量(比如 5%)走新版本,观察一段时间没问题,再逐步放大比例。
- 用户请求 (100%)
- |
- v
- +-----------+
- | Nginx |
- | 权重分配 |
- +-----------+
- / \
- 95%流量 5%流量
- / \
- +-----------+ +-----------+
- | 稳定版本 | | 金丝雀版本 |
- | (v1.0) | | (v1.1) |
- | 3个实例 | | 1个实例 |
- +-----------+ +-----------+
- 第一阶段: 5% → 观察 10 分钟
- 第二阶段: 25% → 观察 10 分钟
- 第三阶段: 50% → 观察 10 分钟
- 第四阶段: 100% → 全量发布完成
复制代码
名字的来历很有意思:早年矿工下矿井前,会先放一只金丝雀进去。金丝雀对有毒气体特别敏感,如果金丝雀没事,矿工再下去。我们的做法也一样——先让少量用户当"金丝雀",没问题再全量放开。
实操:Nginx 权重实现金丝雀
修改支持权重分配:
- upstream backend {
- # 稳定版本 - 权重 95
- server app-blue:3000 weight=95;
- # 金丝雀版本 - 权重 5
- server app-green:3000 weight=5;
- }
- server {
- listen 80;
- location / {
- proxy_pass http://backend;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- }
- }
复制代码
确保两个环境都在运行,然后重载 Nginx:
- docker compose -f docker-compose.nginx.yml exec nginx nginx -s reload
复制代码
验证流量分配:
- # 发送 20 个请求,观察分布
- for i in $(seq 1 20); do
- curl -s http://localhost:8080
- done
复制代码
预期输出大致是 19 个 blue、1 个 green(5% 的比例不是精确的,但趋势是对的)。
逐步调大金丝雀比例:
- # 第二阶段: 25%
- sed -i 's/weight=95/weight=75/' nginx.conf
- sed -i 's/weight=5/weight=25/' nginx.conf
- docker compose -f docker-compose.nginx.yml exec nginx nginx -s reload
- # 第三阶段: 50%
- sed -i 's/weight=75/weight=50/' nginx.conf
- sed -i 's/weight=25/weight=50/' nginx.conf
- docker compose -f docker-compose.nginx.yml exec nginx nginx -s reload
- # 第四阶段: 全量切换
- sed -i 's/weight=50/weight=0/' nginx.conf # 蓝环境权重设为 0
- sed -i 's/weight=50/weight=100/' nginx.conf # 绿环境全量
- docker compose -f docker-compose.nginx.yml exec nginx nginx -s reload
复制代码
Docker 零停机滚动更新
如果你用的是 Docker Swarm 或者单纯的 Docker Compose,还有一种更简单的方式——滚动更新。它的原理是逐个替换容器,始终保证有容器在服务。
Docker Compose 滚动更新
创建- docker-compose.rolling.yml
复制代码 :
- services:
- app:
- build: ./app
- environment:
- - APP_VERSION=1.1.0
- - APP_COLOR=rolling
- deploy:
- replicas: 3
- update_config:
- parallelism: 1
- delay: 10s
- order: start-first
- restart_policy:
- condition: on-failure
- healthcheck:
- test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
- interval: 5s
- timeout: 3s
- retries: 3
- networks:
- - web
- networks:
- web:
- external: true
复制代码
关键配置解读:
:每次只更新 1 个容器
:两次更新之间等待 10 秒
:先启动新容器,再停掉旧容器(保证零停机)
- 滚动更新过程:
- 时间轴 →
- ┌─────────┐
- 实例1 │ v1.0 │ ──停止──→ ┌─────────┐
- └─────────┘ │ v1.1 │
- └─────────┘
- ┌─────────┐ ┌─────────┐
- 实例2 │ v1.0 │ ─────── 停止 ───────→│ v1.1 │
- └─────────┘ └─────────┘
- ┌─────────┐ ┌─────────┐
- 实例3 │ v1.0 │ ──────────── 停止 ───────────────→│ v1.1 │
- └─────────┘ └─────────┘
- 始终有至少 2 个实例在服务,用户无感知
复制代码
完整的回滚方案
部署策略再好,也得有后手。回滚方案就是你的保险丝。
镜像版本化管理
- # 构建时打上版本标签
- docker build -t myapp:1.0.0 -t myapp:latest ./app
- # 同时保留多个版本
- docker tag myapp:1.0.0 myapp:rollback
- docker tag myapp:1.1.0 myapp:latest
复制代码
自动化回滚脚本
创建:
- #!/bin/bash
- set -e
- PREVIOUS_VERSION=${1:?"用法: ./rollback.sh <版本号>"}
- CURRENT_ENV=$(cat .current-env 2>/dev/null || echo "blue")
- echo "当前环境: $CURRENT_ENV"
- echo "回滚到版本: $PREVIOUS_VERSION"
- # 确定回滚目标环境
- if [ "$CURRENT_ENV" = "blue" ]; then
- ROLLBACK_ENV="green"
- ROLLBACK_COMPOSE="docker-compose.green.yml"
- else
- ROLLBACK_ENV="blue"
- ROLLBACK_COMPOSE="docker-compose.blue.yml"
- fi
- # 检查回滚版本的镜像是否存在
- if ! docker image inspect "myapp:$PREVIOUS_VERSION" > /dev/null 2>&1; then
- echo "错误: 镜像 myapp:$PREVIOUS_VERSION 不存在"
- exit 1
- fi
- # 切换 Nginx 上游
- sed -i "s/app-$CURRENT_ENV:3000/app-$ROLLBACK_ENV:3000/" nginx.conf
- docker compose -f docker-compose.nginx.yml exec nginx nginx -s reload
- # 更新当前环境记录
- echo "$ROLLBACK_ENV" > .current-env
- echo "已回滚到 $ROLLBACK_ENV 环境 (版本 $PREVIOUS_VERSION)"
- echo "验证: curl http://localhost:8080/health"
复制代码
健康检查驱动的自动回滚
更高级的玩法是让系统自己判断要不要回滚:
- #!/bin/bash
- # auto-rollback-check.sh
- HEALTH_URL="http://localhost:8080/health"
- MAX_FAILURES=3
- CHECK_INTERVAL=10
- failure_count=0
- echo "开始健康检查监控..."
- while true; do
- response=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" 2>/dev/null)
- if [ "$response" != "200" ]; then
- failure_count=$((failure_count + 1))
- echo "健康检查失败 ($failure_count/$MAX_FAILURES), HTTP状态: $response"
- if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
- echo "连续失败 $MAX_FAILURES 次,触发自动回滚!"
- ./rollback.sh "$(cat .previous-version)"
- exit 1
- fi
- else
- failure_count=0
- fi
- sleep "$CHECK_INTERVAL"
- done
复制代码
部署策略对比:选哪个
| 特性 | 蓝绿部署 | 金丝雀发布 | 滚动更新 |
|------|---------|-----------|---------|
| 资源占用 | 双倍 | 少量额外 | 几乎不变 |
| 回滚速度 | 秒级 | 秒级 | 分钟级 |
| 风险控制 | 全量切换 | 渐进式 | 渐进式 |
| 复杂度 | 中等 | 较高 | 较低 |
| 适用场景 | 重大版本更新 | 功能灰度测试 | 常规迭代 |
选择建议:
日常小版本迭代 → 滚动更新,简单够用
大版本上线、数据库迁移 → 蓝绿部署,回滚无忧
新功能灰度、A/B 测试 → 金丝雀发布,精细控制
常见问题 Q&A
Q1: 蓝绿部署需要双倍的服务器资源,成本太高怎么办?
A: 不一定需要常驻双倍资源。平时只跑一套环境,部署时临时拉起另一套,切换完成后可以把旧环境缩容。用云服务器的弹性伸缩功能,按需付费,成本可以控制在增加 10%-20% 左右。
Q2: 金丝雀发布时,如果新版本往数据库写了脏数据怎么办?
A: 这是个好问题。核心原则是数据库变更要向前兼容。新版本的数据库迁移脚本必须保证:老版本读得了新数据,新版本也读得了老数据。具体做法是"先加后删"——先加新字段,双版本都能跑了,再在下个版本删旧字段。
Q3: 小团队没精力搞这么复杂的部署流程,有简化方案吗?
A: 从滚动更新开始就够了。Docker Compose 配合健康检查,加一个简单的回滚脚本,覆盖 80% 的场景。等团队规模大了、服务多了,再逐步引入蓝绿或金丝雀。千万别一上来就搞全套,过度工程比没有工程更可怕。
小结
今天我们学了三种核心部署策略:
蓝绿部署:两套环境轮着来,切换快、回滚也快,适合大版本上线
金丝雀发布:先放一小波流量试水,没问题再全量,适合灰度测试
滚动更新:逐个替换容器,简单实用,适合日常迭代
核心思想就一句话:永远给自己留退路。部署不是赌博,是工程。有了这些策略,你再也不用上线时拆炸弹了。
记住几个关键点:
一定要有健康检查,让系统自己告诉你它是不是好的
一定要有回滚方案,而且回滚方案要提前测过
数据库变更要向前兼容,这是所有部署策略的基础
明天是 Day 28 容器故障排查,我们来聊聊容器出问题时怎么快速定位和修复。毕竟部署只是开始,运维才是持久战。 |
|