冷蟊初退 孤灯野澜 志起鸡鸣 墓不悲秋 技术交流 软件开发 商业合作 加Q:411239339

Day 27 生产部署策略

浏览:19次阅读
没有评论

共计 3210 个字符,预计需要花费 9 分钟才能阅读完成。

每次上线都像拆炸弹?改完代码心里没底,点下部署按钮手都在抖?别慌,今天我们就来聊聊怎么让生产部署变得像换灯泡一样简单——关掉旧的,拧上新的,灯还不能灭。

本文你将学到

  • 蓝绿部署的核心原理和实操方法
  • 金丝雀发布如何让风险降到最低
  • Docker 环境下实现零停机更新的完整方案
  • 出问题时秒级回滚的保命技巧

阅读时间 : 约 12 分钟
实操时间 : 约 40 分钟
难度等级 : 中高级(需要 Day 1-26 的基础)


为什么要聊部署策略

先说个真实场景:你辛辛苦苦写了一周的新功能,测试环境跑得好好的,信心满满地部署到生产环境。结果——白屏了。用户炸锅了,老板电话打过来了,你手忙脚乱地回滚,发现回滚脚本上次改完忘记测了。

这种场景,但凡做过几年开发的人都经历过。问题的根源不是代码写得差,而是部署策略太粗暴。就像搬家,你不能把所有东西一股脑扔到新房子里,得有计划地搬,搬错了还能搬回来。

蓝绿部署:两套房子轮着住

原理

蓝绿部署的思路特别简单:准备两套完全一样的环境,一套跑老版本(蓝),一套部署新版本(绿)。新版本验证没问题后,把流量从蓝切到绿。出问题?秒切回蓝。

                    用户请求
                       |
                       v
                 +----------+
                 |  Nginx   |
                 | 负载均衡  |
                 +----------+
                  /        \
            当前流量      待切换
                /            \
        +---------+    +---------+
        | 蓝环境   |    | 绿环境   |
        | (v1.0)  |    | (v1.1)  |
        | 运行中   |    | 已就绪   |
        +---------+    +---------+
              |              |
        +---------+    +---------+
        | 数据库   |    | 数据库   |
        | (共享)  |    | (共享)  |
        +---------+    +---------+

这就像你有两套房子,一套在住,另一套装修好了。搬过去住两天,发现马桶漏水?直接搬回老房子,一点不耽误。

实操:Docker Compose 实现蓝绿部署

先准备目录结构:

mkdir -p ~/docker-blue-green && cd ~/docker-blue-green

创建一个简单的应用来演示。先写 app/server.js

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

Dockerfile

cat > app/Dockerfile << 'EOF'
FROM node:20-alpine
WORKDIR /app
COPY server.js .
EXPOSE 3000
CMD ["node", "server.js"]
EOF

创建蓝环境的 compose 文件 docker-compose.blue.yml

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 文件 docker-compose.green.yml

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 配置 nginx.conf

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 文件 docker-compose.nginx.yml

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

切换流量到绿环境——修改 nginx.conf 中的 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 权重实现金丝雀

修改 nginx.conf 支持权重分配:

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

关键配置解读:

  • parallelism: 1:每次只更新 1 个容器
  • delay: 10s:两次更新之间等待 10 秒
  • order: start-first:先启动新容器,再停掉旧容器(保证零停机)
 滚动更新过程:

时间轴 →
        ┌─────────┐
实例 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

自动化回滚脚本

创建 rollback.sh

#!/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"
chmod +x rollback.sh

健康检查驱动的自动回滚

更高级的玩法是让系统自己判断要不要回滚:

#!/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% 的场景。等团队规模大了、服务多了,再逐步引入蓝绿或金丝雀。千万别一上来就搞全套,过度工程比没有工程更可怕。

小结

今天我们学了三种核心部署策略:

  • 蓝绿部署 :两套环境轮着来,切换快、回滚也快,适合大版本上线
  • 金丝雀发布 :先放一小波流量试水,没问题再全量,适合灰度测试
  • 滚动更新 :逐个替换容器,简单实用,适合日常迭代

核心思想就一句话: 永远给自己留退路 。部署不是赌博,是工程。有了这些策略,你再也不用上线时拆炸弹了。

记住几个关键点:

  1. 一定要有健康检查,让系统自己告诉你它是不是好的
  2. 一定要有回滚方案,而且回滚方案要提前测过
  3. 数据库变更要向前兼容,这是所有部署策略的基础

明天是 Day 28 容器故障排查 ,我们来聊聊容器出问题时怎么快速定位和修复。毕竟部署只是开始,运维才是持久战。

正文完
创作不易,扫码加点动力
post-qrcode
 0
果较瘦
版权声明:本站原创文章,由 果较瘦 于2026-03-29发表,共计3210字。
转载说明:除特殊说明外本站文章皆由果较瘦原创发布,转载请注明出处。