Docker Compose 是什么?为什么算法服务需要多个容器?
码海寻道 · AI 算法工程化与系统落地实战第 4 篇

一个算法服务很少只需要一个进程。实际项目通常还需要 PostgreSQL 保存任务和结果,Redis 做缓存和限流,MinIO 保存文件,RabbitMQ 处理异步推理,Nginx 接收外部流量。
如果每个服务都手动安装、手动配置、手动启动,换一台机器就要重复一遍。Docker Compose 用一个 YAML 文件描述多个容器、网络、数据卷、健康检查和环境配置,让开发、测试和单机部署拥有一套可复现的启动方式。
一、为什么算法服务需要多个容器?
算法 API 负责接收请求,但不应该承担所有职责:
text
algorithm-api:HTTP 接口、鉴权、参数校验
algorithm-worker:耗时推理和批量任务
postgres:任务、用户、结果和审计
redis:缓存、限流和短期状态
minio:原始文件、结果文件和模型文件
rabbitmq:异步任务投递
nginx:TLS、路由和外部访问入口
拆分的核心原因不是"服务越多越专业",而是不同组件的生命周期和资源需求不同:API 需要快速响应,Worker 需要消耗 CPU/GPU,数据库需要持久化,缓存可以重建,消息队列需要可靠投递。
二、Compose 文件的基本结构
yaml
services:
api:
build: .
ports:
- "8000:8000"
redis:
image: redis:7-alpine
services 是核心。每个服务可以指定镜像或构建目录、环境变量、端口、网络、Volume、健康检查和依赖关系。服务之间通过 Compose 网络中的服务名访问,而不是使用宿主机 localhost。
例如,API 容器连接 Redis 时应该使用:
text
redis://redis:6379/0
不能写成 redis://localhost:6379/0,因为容器中的 localhost 指向 API 容器自己。
三、一个最小的算法系统
yaml
services:
api:
build: .
environment:
REDIS_URL: redis://redis:6379/0
DATABASE_URL: postgresql://app:app@postgres:5432/app
ports:
- "8000:8000"
depends_on:
redis:
condition: service_healthy
postgres:
condition: service_healthy
postgres:
image: postgres:16
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: app
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
volumes:
postgres_data:
redis_data:
这里有三个容易被忽略的点:
- 数据库和 Redis 使用命名 Volume,避免容器删除后数据直接消失;
depends_on配合service_healthy,避免 API 在数据库尚未可用时立即启动;- 数据库密码只是演示值,生产环境应通过 Secret 或外部密钥管理注入。
Compose 官方文档支持服务级健康检查、命名 Volume 和 Secrets 配置。Docker Compose services 文档
四、服务启动顺序不等于服务可用
错误理解是:
text
postgres 容器先启动
↓
api 容器再启动
↓
数据库一定可以连接
实际情况是,容器进程启动后,数据库可能还在初始化、执行恢复或创建用户。depends_on 只能表达依赖关系,可靠判断应该依赖健康检查;应用自身也要在启动时重试连接,并在数据库不可用时保持未就绪。
五、网络、端口和服务发现
端口有两个概念:
text
容器端口:服务在容器内部监听的端口
宿主机端口:外部访问宿主机时使用的端口
只有需要被宿主机或公网访问的服务才需要 ports:
yaml
api:
ports:
- "8000:8000"
PostgreSQL、Redis 和 RabbitMQ 管理端口通常只供内部服务访问,可以不映射到宿主机,减少暴露面。API 通过服务名访问它们:
text
postgres:5432
redis:6379
rabbitmq:5672
六、环境变量和不同环境
不要把开发、测试和生产配置写成三份完全不同的 Compose 文件。可以把稳定结构放在 compose.yaml,再用环境变量覆盖:
yaml
services:
api:
image: ${ALGO_IMAGE:-algorithm-api:dev}
environment:
APP_ENV: ${APP_ENV:-development}
MODEL_VERSION: ${MODEL_VERSION:-demo}
运行前检查最终配置:
bash
docker compose config
这个命令可以帮助发现变量没有注入、Volume 路径错误和 YAML 合并问题。敏感配置不要提交 .env 到 Git,生产环境应使用 Secret 文件、平台密钥或专用密钥管理系统。
七、健康检查应该检查什么?
yaml
api:
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
算法服务最好区分:
text
/health:进程是否存活
/ready:模型、数据库和必要依赖是否已经准备好
不要在健康检查中执行一次完整推理,也不要让一个短暂的 Redis 故障导致 API 容器不断重启。健康检查应该轻量、有限时,并且能反映服务是否适合接收流量。
八、常用操作命令
bash
# 构建并后台启动
docker compose up -d --build
# 查看服务状态
docker compose ps
# 查看 API 日志
docker compose logs -f api
# 查看某个服务的环境和配置
docker compose config
# 执行一次性命令,例如数据库迁移
docker compose run --rm api python -m app.migrate
# 停止并删除容器,但保留命名 Volume
docker compose down
# 连同 Volume 一起删除,谨慎使用
docker compose down -v
down -v 会删除数据库和缓存数据,开发环境也不要在没有确认目标的情况下执行。
九、Compose 适合什么,不适合什么?
适合:
- 本地开发和联调;
- 单机测试环境;
- 小规模内部服务;
- 复现一套依赖组件;
- 给新成员提供一键启动环境。
不应把 Compose 自动等同于高可用生产平台。生产环境还需要考虑多机调度、滚动发布、备份恢复、监控告警、密钥轮换和故障转移。规模扩大后,可以把同样的服务边界迁移到 Kubernetes 或托管容器平台。
结语
Docker Compose 的真正价值,是把"算法 API 依赖哪些组件、如何连接、数据存在哪里、什么时候算健康"写成可以评审和复现的配置。下一篇将用 Compose 启动 FastAPI、PostgreSQL 和 Redis,做出第一个可以落地的算法服务基础环境。
参考资料
本文为"码海寻道"原创技术文章。Compose 示例用于开发和单机联调,生产部署仍需单独设计高可用、备份和安全策略。