4. Docker容器化部署:Dockerfile编写、镜像构建与优化、docker-compose编排多服务

说实话,量化策略的部署,最头疼的就是环境问题。

你想想看,策略在本地跑得好好的,一上服务器就各种报错。Python版本不对,依赖包冲突,系统库缺失……我早期做实盘的时候,光折腾环境就花了大半天。后来用了Docker,这些问题基本都解决了。

这一章,我就把Docker容器化部署的核心经验分享给你。包括怎么写Dockerfile、怎么构建和优化镜像、怎么用docker-compose编排多服务。

核心思路:把策略和它的运行环境打包成一个“集装箱”。不管搬到哪台机器上,开箱即用。

Docker容器化部署知识体系 Dockerfile编写 基础镜像 / 指令 / 分层 镜像构建与优化 多阶段构建 / 缓存 / 瘦身 docker-compose编排 多服务 / 网络 / 数据卷 FROM RUN / COPY 多阶段构建 层缓存 services volumes 目标:一次构建,到处运行,稳定可靠

4.1 Dockerfile编写:从零开始打包你的策略

Dockerfile说白了就是一个“菜谱”。告诉Docker怎么一步步做出你的策略镜像。

我个人习惯,写Dockerfile之前先想清楚三件事:

  • 基础镜像选什么?——Python官方镜像还是精简版?
  • 依赖怎么装?——系统库、Python包、C扩展
  • 策略代码放哪?——目录结构、配置文件

来看一个典型的量化策略Dockerfile:

# 1. 选择基础镜像
FROM python:3.9-slim-buster

# 2. 设置工作目录
WORKDIR /app

# 3. 安装系统依赖(ta-lib等C库需要)
RUN apt-get update && apt-get install -y \
    build-essential \
    libta-lib0 \
    libta-lib-dev \
    && rm -rf /var/lib/apt/lists/*

# 4. 复制依赖文件并安装Python包
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 5. 复制策略代码
COPY . .

# 6. 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 7. 启动命令
CMD ["python", "main.py"]

小技巧:requirements.txt单独复制,是为了利用Docker的层缓存。代码改了,依赖不用重装。我见过很多新手把COPY . .写在前面,每次改代码都要重新装一遍依赖,太慢了。

4.2 镜像构建与优化:让镜像更小、更快

镜像太大,传输慢、启动慢。我早期构建的镜像有1.2GB,部署到服务器要等好几分钟。后来优化到200MB左右,体验完全不一样。

4.2.1 多阶段构建

这是我最常用的优化手段。说白了就是:编译阶段用一个“大”镜像,运行阶段用另一个“小”镜像

# 第一阶段:编译
FROM python:3.9-slim-buster AS builder

WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 第二阶段:运行
FROM python:3.9-slim-buster

WORKDIR /app
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY . .

CMD ["python", "main.py"]

你看,编译阶段的中间产物(比如C扩展的编译文件)不会带到最终镜像里。镜像体积能减少40%以上。

4.2.2 层数优化

Docker镜像是由一层层文件系统叠加的。层数越多,构建和拉取越慢。

我建议:

  • 把多个RUN命令合并成一个,用&&连接
  • 清理不必要的缓存文件(比如apt-get cleanpip --no-cache-dir
  • 不经常变的内容放前面,经常变的内容放后面

注意:别为了合并而合并。如果某个RUN步骤经常变,单独写反而更好。因为Docker会缓存每一层,单独写可以复用前面的缓存。

4.2.3 使用.dockerignore

这个文件很多人忽略。它告诉Docker:哪些文件不要打包进镜像。

# .dockerignore
.git
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
*.log
data/
config_local.yaml

我曾经见过一个同事,把整个.git目录(几百MB)打包进了镜像。嗯,那镜像能不胖吗?

4.3 docker-compose编排多服务:让策略、数据库、监控协同工作

量化实盘很少只有一个服务。通常有:

  • 策略服务:跑核心策略逻辑
  • 数据服务:行情数据、历史数据
  • 数据库:存储交易记录、配置
  • 监控服务:日志收集、告警

docker-compose就是用来编排这些服务的。一个docker-compose.yml文件,搞定所有。

version: '3.8'

services:
  # 策略服务
  strategy:
    build: ./strategy
    container_name: quant_strategy
    restart: always
    depends_on:
      - redis
      - postgres
    environment:
      - TZ=Asia/Shanghai
      - DB_HOST=postgres
      - REDIS_HOST=redis
    volumes:
      - ./logs:/app/logs
      - ./config:/app/config
    networks:
      - quant_net

  # 行情数据服务
  datafeed:
    image: python:3.9-slim-buster
    container_name: quant_datafeed
    restart: always
    command: python data_feed.py
    volumes:
      - ./datafeed:/app
      - ./data:/data
    networks:
      - quant_net

  # Redis(缓存、消息队列)
  redis:
    image: redis:6-alpine
    container_name: quant_redis
    restart: always
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - quant_net

  # PostgreSQL(持久化存储)
  postgres:
    image: postgres:13-alpine
    container_name: quant_postgres
    restart: always
    environment:
      POSTGRES_DB: quantdb
      POSTGRES_USER: quantuser
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
    networks:
      - quant_net

  # 监控服务(Prometheus + Grafana)
  prometheus:
    image: prom/prometheus
    container_name: quant_prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - quant_net

  grafana:
    image: grafana/grafana
    container_name: quant_grafana
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    networks:
      - quant_net

volumes:
  redis_data:
  pg_data:

networks:
  quant_net:
    driver: bridge

关键点:

  • depends_on控制启动顺序,但不等服务就绪。我建议在策略代码里加重试逻辑
  • volumes挂载日志和配置,方便调试和持久化
  • networks让服务之间通过服务名通信,不用记IP
  • 敏感信息(如数据库密码)用环境变量或.env文件,别写死在yml里

4.4 避坑指南:我踩过的那些坑

做量化容器化部署这几年,我踩过不少坑。分享几个典型的:

我曾经……把数据库密码直接写在docker-compose.yml里,然后不小心提交到了GitHub。第二天发现有人连上了我的数据库。从那以后,我所有敏感信息都用.env文件,并且加到.gitignore里。

坑一:时区问题

Docker容器默认是UTC时间。量化策略如果按本地时间交易,订单时间会错乱。记得在Dockerfile里设置TZ=Asia/Shanghai

坑二:日志不持久化

容器重启后,日志就没了。排查问题的时候特别痛苦。一定要用volumes把日志目录挂载出来。

坑三:资源限制

多个容器跑在同一台机器上,可能会互相抢资源。我建议在docker-compose里加上资源限制:

services:
  strategy:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 2G

坑四:镜像版本管理

别用latest标签。我建议用v1.0.0这样的语义化版本,或者加上Git commit hash。这样回滚的时候才知道哪个版本是稳定的。

4.5 部署与运维小贴士

最后,分享几个我日常用的命令和习惯:

场景 命令 说明
构建镜像 docker build -t quant-strategy:v1.0 . 打上版本标签
启动所有服务 docker-compose up -d 后台运行
查看日志 docker-compose logs -f strategy 实时跟踪策略日志
重启单个服务 docker-compose restart strategy 不用重启全部
进入容器调试 docker exec -it quant_strategy bash 排查问题用
清理无用镜像 docker image prune -a 释放磁盘空间

我的习惯:每次部署前,先在本地用docker-compose up跑一遍,确认所有服务都能正常启动。别直接推到生产环境,那太冒险了。

好了,Docker容器化部署的核心内容就这些。从Dockerfile编写,到镜像优化,再到多服务编排,每一步都有讲究。你按这个思路去实践,量化策略的部署会变得非常顺畅。


公众号:蓝海资料掘金营,微信deep3321