生产环境发版 Django 后端,往往要求 HTTP 服务全程可用。本文介绍一种基于 Docker Compose 的滚动更新方案:应用代码打入镜像,部署两个 Gunicorn 节点,通过分步 up -d 配合 --wait,在更新过程中始终保留至少一个可访问的后端实例。
方案概览
┌─────────────┐
│ Nginx │
│ (upstream) │
└──────┬──────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ backend │ │ backend2 │
│ :8001 │ │ :8002 │
└────────┬────────┘ └────────┬────────┘
│ │
└────────────┬────────────┘
▼
┌─────────────────┐
│ celery_worker │
└─────────────────┘
三个服务共用同一应用镜像,职责如下:
| 服务 | 端口 | 职责 |
|---|---|---|
backend | 8001 | HTTP 节点 1,镜像构建入口 |
backend2 | 8002 | HTTP 节点 2,滚动更新期间承接流量 |
celery_worker | — | 异步任务 Worker + Beat |
Nginx 将两个 backend 配置为 upstream。更新节点 1 时,节点 2 继续对外服务;更新节点 2 时,节点 1 已就绪,同样可承接流量。
一、应用镜像 Dockerfile
应用代码与 Python 依赖均在镜像构建阶段写入,运行时直接启动服务。
FROM registry.example.com/myorg/app-base:1.0
WORKDIR /app
COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir -r /tmp/requirements.txt
COPY . /app
requirements.txt 先于源码 COPY,Docker 可缓存依赖安装层——仅代码变更时,无需重复执行 pip install。
二、docker-compose.yaml
backend 定义 build 并产出镜像;backend2 与 celery_worker 引用同一镜像 tag。日志等需持久化的目录通过 volume 挂载,其余内容均来自镜像。
version: "3.8"
services:
backend:
build:
context: .
dockerfile: Dockerfile
image: registry.example.com/myorg/app:latest
volumes:
- /var/log/app/backend/:/var/log/backend/
command: sh /app/deploy/runtime/run_backend.sh
restart: unless-stopped
ports:
- "8001:8000"
healthcheck:
test: ["CMD-SHELL", "python -c \"import requests; requests.get('http://127.0.0.1:8000/health/', timeout=5)\""]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
networks:
- app_net
backend2:
image: registry.example.com/myorg/app:latest
volumes:
- /var/log/app/backend2/:/var/log/backend/
command: sh /app/deploy/runtime/run_backend.sh
restart: unless-stopped
depends_on:
backend:
condition: service_healthy
ports:
- "8002:8000"
healthcheck:
test: ["CMD-SHELL", "python -c \"import requests; requests.get('http://127.0.0.1:8000/health/', timeout=5)\""]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
networks:
- app_net
celery_worker:
image: registry.example.com/myorg/app:latest
volumes:
- /var/log/app/backend/:/var/log/backend/
command: sh deploy/runtime/run_celery.sh
restart: unless-stopped
networks:
- app_net
networks:
app_net:
driver: bridge
三、滚动更新
发版命令
docker compose build
docker compose up -d celery_worker
docker compose up -d backend --wait
docker compose up -d backend2
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1 | docker compose build | 构建新镜像,不影响正在运行的容器 |
| 2 | up -d celery_worker | 先更新异步任务,HTTP 链路不受影响 |
| 3 | up -d backend --wait | 重建节点 1 并等待就绪;此期间节点 2 正常服务 |
| 4 | up -d backend2 | 重建节点 2;此期间节点 1 已就绪,正常服务 |
核心原则:任意时刻,Nginx upstream 中至少有一个 backend 处于运行状态。
时序
关于 --wait
第三步中的 --wait 会让 Compose 阻塞到 backend 就绪后再返回,再执行第四步更新 backend2。整条发版链路可直接写入脚本或 CI,无需额外轮询。
四、FAQ
为什么发版前要先 build?
应用代码在 build 阶段写入镜像。只有执行 up -d 才会用新镜像重建容器;restart 仅重启现有容器,不会加载新镜像。
为什么不一次性 docker compose up -d?
一次性更新会同时重建两个 backend,造成短暂的全员下线。分步更新确保始终有一个 HTTP 节点在线。
五、其他实现方式
本文方案基于 Docker Compose 手动编排,适合单机或少量节点。若部署在多节点集群上,同样的滚动发布目标也可交由 Docker Swarm 或 Kubernetes 等平台自动完成副本逐批替换与流量切换。
869

被折叠的 条评论
为什么被折叠?



