4、Docker容器化部署:Dockerfile编写、镜像构建与优化、容器编排基础(Docker Compose)

好,咱们进入第四讲。说实话,Docker 这东西在金融风控里有多重要?我打个比方——你辛辛苦苦训练了一个评分卡模型,在 Jupyter 里跑得飞起,结果运维同学部署到生产环境,发现缺了个 Python 包,版本还对不上。嗯,这种场景我遇到过不下十次。Docker 就是来解决这个问题的。

说白了,容器化就是把你的模型、依赖、配置、运行环境,全部打包成一个「集装箱」。不管推到哪台机器上,跑起来的效果一模一样。这在金融合规审计里尤其重要——监管要查你模型版本,你直接给他一个镜像哈希值,比什么都硬气。

4.1 Dockerfile 编写:从零构建风控模型镜像

先聊聊 Dockerfile。我个人的习惯是,永远从官方镜像开始。金融风控场景下,我推荐用 python:3.9-slim 或者 python:3.9-buster,别用 alpine——虽然它小,但很多科学计算库在 alpine 上编译会出幺蛾子,我踩过这个坑。

来看一个典型的风控模型 Dockerfile:

# 基础镜像选择
FROM python:3.9-slim AS builder

# 设置工作目录
WORKDIR /app

# 先复制依赖文件,利用 Docker 缓存层
COPY requirements.txt .

# 安装依赖,使用国内镜像加速
RUN pip install --no-cache-dir -r requirements.txt \
    -i https://pypi.tuna.tsinghua.edu.cn/simple

# 第二阶段:生产镜像
FROM python:3.9-slim AS runtime

WORKDIR /app

# 从 builder 阶段复制依赖
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

# 复制模型文件和代码
COPY model/ ./model/
COPY src/ ./src/
COPY config/ ./config/

# 创建非 root 用户(安全合规要求)
RUN groupadd -r modeluser && useradd -r -g modeluser modeluser
USER modeluser

# 暴露端口
EXPOSE 8080

# 启动命令
CMD ["python", "src/serve_model.py"]

这里有个细节:多阶段构建。为什么这么做?因为金融风控模型通常依赖很多科学计算库(numpy、pandas、scikit-learn),编译产物很大。我用 builder 阶段装好依赖,runtime 阶段只复制必要的文件,镜像体积能缩小 40% 以上。我在某银行做模型部署时,镜像从 1.2GB 压到了 680MB,运维同事直呼内行。

我的经验:requirements.txt 里不要写死版本号,用 >= 或者 ~= 会更灵活。但生产环境一定要锁定版本,用 pip freeze > requirements_lock.txt 生成锁定文件。

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

镜像构建不只是 docker build -t mymodel . 这么简单。我总结了几条优化原则:

  • 层数越少越好:每个 RUN、COPY 都会产生一层。把多个命令用 && 合并,能减少层数。
  • 利用构建缓存:把不常变动的文件(如 requirements.txt)放在前面 COPY,代码放在后面。这样改代码时不用重装依赖。
  • 清理临时文件:安装完包后,顺手删掉 /var/cache/apt/*/tmp/*

来看一个优化后的构建命令:

# 构建命令
docker build \
  --build-arg MODEL_VERSION=1.2.3 \
  --cache-from mymodel:latest \
  -t mymodel:1.2.3 \
  -t mymodel:latest \
  .

# 扫描镜像漏洞(金融合规必备)
docker scan mymodel:1.2.3

说到漏洞扫描,这是金融场景的硬性要求。我建议在 CI/CD 流程里加上 docker scan 或者 Trivy,一旦发现高危漏洞,直接阻断构建。我曾经因为一个 openssl 的 CVE,被安全团队要求重新构建所有镜像,那叫一个酸爽。

避坑指南:千万不要把模型文件直接 COPY 进镜像!模型文件通常很大(几百 MB),每次构建都会重新传输。我建议用 Docker volume 挂载模型目录,或者用对象存储(如 S3)动态加载。

4.3 容器编排基础:Docker Compose 实战

单个容器好办,但风控系统通常不止一个服务。你想想看:模型推理服务、特征计算服务、Redis 缓存、消息队列……这些容器怎么协同工作?Docker Compose 就是干这个的。

我画了一张图,帮你理解整个编排结构:

金融风控模型容器编排架构 外部请求 Nginx 网关 模型推理服务 端口 8080 特征服务 端口 8081 监控告警服务 端口 9090 Redis 缓存 / 消息队列 MySQL / PostgreSQL

来看一个实际的 docker-compose.yml 配置:

version: '3.8'

services:
  # 模型推理服务
  model-server:
    build:
      context: .
      dockerfile: Dockerfile
    image: credit-model:1.2.3
    ports:
      - "8080:8080"
    volumes:
      - ./models:/app/models:ro
      - ./logs:/app/logs
    environment:
      - MODEL_PATH=/app/models/lr_model.pkl
      - LOG_LEVEL=INFO
      - REDIS_HOST=redis-cache
    depends_on:
      - redis-cache
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 2G

  # 特征计算服务
  feature-service:
    build:
      context: ./feature
      dockerfile: Dockerfile.feature
    ports:
      - "8081:8081"
    environment:
      - REDIS_HOST=redis-cache
    depends_on:
      - redis-cache
    restart: unless-stopped

  # Redis 缓存
  redis-cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

volumes:
  redis-data:

这里有几个关键点我要强调:

  • healthcheck 是必须的:金融系统对可用性要求极高,健康检查能让 Docker 自动重启挂掉的服务。我见过一个案例,模型服务内存泄漏挂了 3 小时没人发现,就是因为没配 healthcheck。
  • 资源限制要明确deploy.resources.limits 能防止某个服务把宿主机内存吃光。我在生产环境遇到过特征服务内存泄漏,把 Redis 都挤挂了,加了限制后问题解决。
  • 数据卷持久化:日志、模型文件、Redis 数据都要挂载到宿主机,否则容器重启数据就丢了。
启动命令: docker-compose -f docker-compose.yml -p credit-risk up -d
查看日志: docker-compose logs -f model-server
滚动更新: docker-compose up -d --no-deps --build model-server

说到滚动更新,金融场景下不能停服。我建议用 --no-deps 只更新指定服务,配合 healthcheck 实现零停机部署。嗯,这里要注意,如果改了 Redis 配置,那所有依赖它的服务都得重启,顺序很重要。

我曾经踩过的坑:在 docker-compose.yml 里直接写死了 image: mymodel:latest。结果某次部署,latest 标签被覆盖了,回滚时发现找不到旧镜像。现在我都是 image: mymodel:1.2.3 加上 latest 标签,回滚时指定具体版本号。

最后聊一句镜像仓库。金融公司一般用私有仓库,比如 Harbor 或者 Nexus。推送命令很简单:

# 打标签
docker tag credit-model:1.2.3 harbor.bank.com/risk/credit-model:1.2.3

# 推送
docker push harbor.bank.com/risk/credit-model:1.2.3

# 拉取
docker pull harbor.bank.com/risk/credit-model:1.2.3

我个人建议在镜像标签里带上 Git commit ID,比如 1.2.3-abc1234,这样能精确追溯到代码版本。审计的时候,监管问「这个模型是哪个代码版本构建的」,你直接查镜像标签就行,省心。


专注资料整理