docker compose滚动部署实践

生产环境发版 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 依赖均在镜像构建阶段写入,运行时直接启动服务。

dockerfile 复制代码
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 并产出镜像;backend2celery_worker 引用同一镜像 tag。日志等需持久化的目录通过 volume 挂载,其余内容均来自镜像。

yaml 复制代码
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

三、滚动更新

发版命令

bash 复制代码
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 处于运行状态。

时序

Nginx backend2 :8002 backend :8001 Celery 运维 Nginx backend2 :8002 backend :8001 Celery 运维 #mermaid-svg-tEjAI0gibk2NQ5Xe{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tEjAI0gibk2NQ5Xe .error-icon{fill:#552222;}#mermaid-svg-tEjAI0gibk2NQ5Xe .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tEjAI0gibk2NQ5Xe .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tEjAI0gibk2NQ5Xe .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tEjAI0gibk2NQ5Xe .marker.cross{stroke:#333333;}#mermaid-svg-tEjAI0gibk2NQ5Xe svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tEjAI0gibk2NQ5Xe p{margin:0;}#mermaid-svg-tEjAI0gibk2NQ5Xe .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEjAI0gibk2NQ5Xe text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tEjAI0gibk2NQ5Xe .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tEjAI0gibk2NQ5Xe .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tEjAI0gibk2NQ5Xe .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tEjAI0gibk2NQ5Xe .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tEjAI0gibk2NQ5Xe #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tEjAI0gibk2NQ5Xe .sequenceNumber{fill:white;}#mermaid-svg-tEjAI0gibk2NQ5Xe #sequencenumber{fill:#333;}#mermaid-svg-tEjAI0gibk2NQ5Xe #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tEjAI0gibk2NQ5Xe .messageText{fill:#333;stroke:none;}#mermaid-svg-tEjAI0gibk2NQ5Xe .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEjAI0gibk2NQ5Xe .labelText,#mermaid-svg-tEjAI0gibk2NQ5Xe .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tEjAI0gibk2NQ5Xe .loopText,#mermaid-svg-tEjAI0gibk2NQ5Xe .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tEjAI0gibk2NQ5Xe .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tEjAI0gibk2NQ5Xe .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tEjAI0gibk2NQ5Xe .noteText,#mermaid-svg-tEjAI0gibk2NQ5Xe .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tEjAI0gibk2NQ5Xe .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEjAI0gibk2NQ5Xe .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEjAI0gibk2NQ5Xe .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEjAI0gibk2NQ5Xe .actorPopupMenu{position:absolute;}#mermaid-svg-tEjAI0gibk2NQ5Xe .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tEjAI0gibk2NQ5Xe .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEjAI0gibk2NQ5Xe .actor-man circle,#mermaid-svg-tEjAI0gibk2NQ5Xe line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tEjAI0gibk2NQ5Xe :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 期间 B2 仍服务流量 期间 B1 已就绪,仍服务流量 docker compose buildup -d celery_workerup -d backend --wait节点 1 就绪up -d backend2

关于 --wait

第三步中的 --wait 会让 Compose 阻塞到 backend 就绪后再返回,再执行第四步更新 backend2。整条发版链路可直接写入脚本或 CI,无需额外轮询。


四、FAQ

为什么发版前要先 build

应用代码在 build 阶段写入镜像。只有执行 up -d 才会用新镜像重建容器;restart 仅重启现有容器,不会加载新镜像。

为什么不一次性 docker compose up -d

一次性更新会同时重建两个 backend,造成短暂的全员下线。分步更新确保始终有一个 HTTP 节点在线。


五、其他实现方式

本文方案基于 Docker Compose 手动编排,适合单机或少量节点。若部署在多节点集群上,同样的滚动发布目标也可交由 Docker SwarmKubernetes 等平台自动完成副本逐批替换与流量切换。


相关推荐
難釋懷几秒前
Nginx浏览器强制缓存
运维·nginx·缓存
花和满楼7 分钟前
K8s 1.36 ImageVolume GA:OCI 镜像不再只能跑容器
java·容器·kubernetes
大博士.J10 分钟前
视频号主体违规与认证上限?正确的解决方式
运维·python·自动化
hey you~1 小时前
400电话选型技术测评:一个开发视角的线路质量评估方案
运维·网络·数据库·400电话·企业通信
筱羽_筱羽1 小时前
跟着“Ubuntu18.04/20.04编译Linux5.10.76+Xenomai-3.2.1+IgH”教程遇到的问题
linux·运维·ubuntu
DolphinScheduler社区1 小时前
Apache DolphinScheduler 6 月治理优化,补齐调度运维全链路细节
大数据·运维·云原生·apache·海豚调度
hj2862512 小时前
Docker 核心知识点整理(网络 + Dockerfile + 原理 + 命令)
网络·docker·容器
无忧.芙桃3 小时前
线程同步与线程互斥(上)
linux·运维·操作系统·运维开发
xywww1687 小时前
大模型 API 选型实战:GPT、Gemini、Claude 接入时该看哪些指标?
运维·服务器·人工智能·python·gpt·langchain
梦想的颜色10 小时前
【Docker原理】Docker 容器一文打尽:生命周期、环境管控、迁移备份、日志进程排查、垃圾清理
运维·docker·容器·容器生命周期·容器日志排查·容器迁移备份·容器进程管理