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,运维同事直呼内行。
>= 或者 ~= 会更灵活。但生产环境一定要锁定版本,用 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,被安全团队要求重新构建所有镜像,那叫一个酸爽。
4.3 容器编排基础:Docker Compose 实战
单个容器好办,但风控系统通常不止一个服务。你想想看:模型推理服务、特征计算服务、Redis 缓存、消息队列……这些容器怎么协同工作?Docker Compose 就是干这个的。
我画了一张图,帮你理解整个编排结构:
来看一个实际的 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 配置,那所有依赖它的服务都得重启,顺序很重要。
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,这样能精确追溯到代码版本。审计的时候,监管问「这个模型是哪个代码版本构建的」,你直接查镜像标签就行,省心。