不使用 Kubernetes 的蓝绿部署:在 Docker Compose 上实现零宕机
即使没有像 Kubernetes 这样的编排器,也能实现蓝绿部署——只需 Docker Compose、nginx 和精心设计的优雅关闭逻辑即可。这种方法特别适合在专用服务器或虚拟机上工作的团队,在这些环境中使用完整集群在经济或架构上都不切实际。本文涵盖了 Web 服务和后台工作进程的持续部署技术实现,考虑了消息处理特性和状态管理。
Docker Compose 上的蓝绿架构
在基本配置中,每个应用都会复制一份:有两个独立的实例——blue 和 green。两者都在 docker-compose.prod.yml 中作为单独的服务启动,但任何时候只有一个接收流量。路由由反向代理(示例中使用 nginx)处理,在部署期间切换上游地址。关键原则:新实例完全启动并通过健康检查后,才停止旧实例。
配置需要为每个实例显式指定端口:
web-blue:
ports:
- "127.0.0.1:8002:8000"
web-green:
ports:
- "127.0.0.1:8003:8000"
这允许 nginx 通过 localhost 和特定端口访问所需实例。这种设置避免了冲突并简化了生命周期管理。
管理后台工作进程
从队列(例如 RabbitMQ)消费消息的后台进程(worker)需要特殊处理。简单停止容器会导致消息丢失,或在非幂等操作时重复处理。解决方案是在应用层面实现优雅关闭。
这是通过一个带有 is_accepting 标志的单例类来实现的。在处理每个任务前,worker 会检查这个标志:
- 如果
is_accepting == True——执行任务。 - 如果
is_accepting == False——将消息带requeue=True退回队列。
收到 SIGTERM 信号后,将标志设置为 False,然后应用等待所有当前任务完成(预留 5–10 秒缓冲),再关闭。这确保了没有消息丢失,也不会出现数据不一致。
重要细节:
- 对于多进程 worker(例如 ProcessPoolExecutor),
is_accepting状态必须对所有子进程可见。否则,有些消费者会继续处理新任务。 - 非幂等操作需要额外防护:重复处理可能破坏数据完整性。
- 使用分布式标志(例如在 Redis 中)会复杂化架构,但在高负载系统中可能有必要。
部署脚本:逐步逻辑
部署脚本自动化实例切换逻辑。对于 Web 服务和工作进程,逻辑相同,但入口点不同。
主要阶段:
- 确定活跃实例(通过分析 nginx 配置或运行中的容器列表)。
- 启动非活跃实例(
docker compose up -d)。 - 通过健康检查验证新实例就绪(最多 30 次尝试,每次间隔 2 秒)。
- 更新 nginx 配置并运行
nginx -s reload。 - 等待所有活跃 HTTP 请求完成(通常 5 秒)。
- 停止旧实例(
docker compose stop)。
Web 服务的示例:
echo "server 127.0.0.1:${INACTIVE_PORT};" > "$UPSTREAM_CONF"
nginx -t && nginx -s reload
sleep 5
$COMPOSE stop web-${ACTIVE}
这个序列确保流量绝不会指向未准备好的服务,旧实例也仅在完全切换后才关闭。
数据库模式兼容性和迁移
蓝绿部署的主要挑战之一是同时运行两个版本的应用访问同一数据库。为了避免错误,所有迁移必须向后兼容。
推荐方法——两阶段更新:
- 第一阶段部署:在代码中移除对过时字段/表的用法,但保留数据库结构。新旧版本均正常工作。
- 第二阶段部署(完全切换流量后):执行迁移移除未用模式元素。
这会增加操作次数,但消除了旧实例因模式不兼容而崩溃的风险。
关键要点
- Docker Compose 上的蓝绿部署无需 Kubernetes,但需要手动管理实例和代理。
- 工作进程的优雅关闭至关重要——否则消息可能丢失或重复。
- 所有数据库迁移必须向后兼容;字段移除需两阶段处理。
- 切换流量前,必须检查新实例的健康状态。
- Nginx 配置必须原子更新,并用
nginx -t验证。
— Editorial Team
暂无评论。