4. Docker Compose 入门:docker-compose.yml 编写与多容器编排
单个容器能跑一个服务,但质谱数据处理流程没那么简单。你可能需要同时运行数据预处理、峰检测、数据库写入三个任务。一个一个敲 docker run?太累了。这时候 Docker Compose 就派上用场了。
我个人习惯把 Compose 看作是一个项目的“说明书”。你写一个 YAML 文件,把服务、网络、存储卷都定义清楚。然后一条命令,全部启动。今天我们就来吃透这个 docker-compose.yml 该怎么写。
4.1 为什么需要多容器编排?
在真实项目中,我常常需要把流程拆成多个独立的模块。比如质谱数据经过预处理后,要送到另一个容器做谱库检索,最后结果存到数据库。每个模块都是独立的容器,但它们之间需要通信。
你想想看,如果全靠命令行启动容器,要记 IP 地址,要手动建网络,还要控制启动顺序。稍微改个配置就得重来。这不是在写代码,这是在给自己找麻烦。
Docker Compose 解决了这些痛点:
- 服务定义在一个文件里,版本可控
- 自动创建网络,服务间用服务名通信
- 可以指定依赖关系,控制启动顺序
- 卷和端口映射统一管理
4.2 docker-compose.yml 核心结构
一个典型的 Compose 文件长这样。我挑几个重点部分来剖析:
version: '3'
services:
preprocess:
image: ms-tools/preprocess:1.0
volumes:
- ./data:/data
networks:
- ms-net
database:
image: postgres:13
environment:
POSTGRES_DB: mslib
POSTGRES_USER: msuser
POSTGRES_PASSWORD: mspass
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- ms-net
web:
image: ms-tools/webui:1.0
ports:
- "8080:80"
depends_on:
- database
networks:
- ms-net
volumes:
pgdata:
networks:
ms-net:
4.3 版本声明
文件第一行就是version: '3'。很多人以为这是 Compose 工具的版本,其实不是。它指定的是 Compose 文件格式的版本。我建议直接用 '3',原因后面说。
不同版本支持的语法略有差异。比如版本 2 不支持 depends_on 的条件等待,版本 3 引入了 deploy 块用于 Swarm 模式。日常开发用版本 3 最稳。
4.4 服务定义:把流程拆成块
services 下面每个键就是一个服务。服务名最好起得一看就懂——preprocess、database、web。名字会作为主机名在容器间通信使用。
每个服务都要告诉 Compose 用哪个镜像。常用的方式有两种:
| 方式 | 适用场景 | 示例 |
|---|---|---|
| 直接指定 image | 使用现成的镜像,比如 PostgreSQL | image: postgres:13 |
| 指定 build 上下文 | 需要现场构建镜像 | build: ./app |
我这里举的例子都用的 image。如果你有一个自定义的质谱处理程序,可以写成这样:
services:
peak-detector:
build: ./peak-detector
volumes:
- ./raw_data:/input
- ./results:/output
这时候 Compose 会先执行 docker build,再启动容器。嗯,这里要注意:如果 build 和 image 同时存在,Compose 会用 build 构建的镜像,但标签打的是 image 指定的名字。
4.5 卷挂载:数据怎么传递?
质谱数据可能几百兆甚至几个 G,不能放在镜像里。卷挂载是必学的技能。
挂载有两种形式:
- 绑定挂载:直接映射宿主机路径,比如
./data:/data - 命名卷:用卷名管理,比如
pgdata:/var/lib/postgresql/data
我个人更推荐命名卷来存放持久化数据。为什么呢?因为绑定挂载如果路径写错,数据可能写到意料之外的地方。命名卷由 Docker 管理,安全多了。
如果你要在多个容器间共享数据,可以用同一个命名卷:
volumes:
shared-data:
services:
converter:
image: ms-tools/converter
volumes:
- shared-data:/work
analyzer:
image: ms-tools/analyzer
volumes:
- shared-data:/work
这样 converter 写完文件,analyzer 就能读到。顺序呢?用 depends_on 控制。
4.6 网络配置:服务间怎么通信?
Compose 会自动创建一个默认网络,所有服务都在里面。所以默认情况下,服务间用服务名就能 Ping 通。但有些场景下你需要自定义网络。
核心:在同一网络下,服务名就是主机名。比如 database 服务,其他服务访问它时,连接字符串写host=database就行了。不用查 IP,不用记端口。
看一个实际的质谱流程例子:
services:
preprocess:
image: ms-tools/preprocess
networks:
- frontend
- backend
database:
image: postgres:13
networks:
- backend
networks:
frontend:
backend:
preprocess 服务同时接入两个网络,可以接收来自前端的请求,也能访问后端的数据库。而数据库只在 backend 网络里,前台模块无法直接接触数据库。我曾经在项目中就用这个思路做安全隔离,效果很好。
4.7 依赖与启动顺序
depends_on 是编排的关键。它告诉 Compose:先启动哪个服务,再启动哪个。比如:
services:
database:
image: postgres:13
webui:
image: ms-tools/webui
depends_on:
- database
这样 webui 会在 database 之后启动。但注意,depends_on 只保证容器启动了,不保证数据库准备好接受连接。如果 webui 启动太快,数据库还没就绪怎么办?
我曾经踩过这个坑。后来我在应用代码里加了个重试机制,效果不错。也可以写个等待脚本:
services:
webui:
image: ms-tools/webui
depends_on:
- database
command: >
sh -c "while ! nc -z database 5432; do sleep 1; done; exec myapp"
这相当于在容器启动后先等数据库端口可用,再启动主程序。稳妥。
4.8 环境变量与配置
环境变量可以用 environment 块写死,也可以通过 env_file 引入文件。我推荐把敏感信息放文件里,不要明文写在 Compose 文件中。
services:
database:
image: postgres:13
env_file:
- ./db.env
db.env 文件的内容:
POSTGRES_DB=mslib
POSTGRES_USER=msuser
POSTGRES_PASSWORD=mysecretpassword
这样 Compose 文件可以提交到版本控制,但 .env 文件要加入 .gitignore。
4.9 常用 Compose 命令一览
| 命令 | 作用 | 常用场景 |
|---|---|---|
docker compose up -d |
后台启动所有服务 | 日常启动流程 |
docker compose down |
停止并删除容器和网络 | 清理环境 |
docker compose logs -f |
实时查看所有服务的日志 | 调试和排错 |
docker compose ps |
查看服务运行状态 | 快速检查 |
docker compose exec |
在运行中的容器执行命令 | 进入容器排查问题 |
4.10 一套完整的质谱流程 Compose 配置
最后,我展示一个接近实际项目的配置。包含预处理、峰检测、谱库检索、数据库四个服务,还加了 healthcheck 确保数据库就绪:
version: '3'
services:
preprocessing:
image: ms-tools/preprocess:2.1
volumes:
- ./raw_mzml:/data/raw
- ./preprocessed:/data/out
networks:
- ms-pipeline
peak-picking:
image: ms-tools/peak:1.4
depends_on:
preprocessing:
condition: service_started
volumes:
- ./preprocessed:/data/in
- ./peaks:/data/out
networks:
- ms-pipeline
library-search:
image: ms-tools/library:0.9
depends_on:
- peak-picking
volumes:
- ./peaks:/data/in
- ./results:/data/out
networks:
- ms-pipeline
db-writer:
image: ms-tools/dbwriter:1.0
depends_on:
database:
condition: service_healthy
volumes:
- ./results:/data/in
networks:
- ms-pipeline
database:
image: postgres:13
environment:
POSTGRES_DB: massspec
POSTGRES_USER: msadmin
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- msdata:/var/lib/postgresql/data
networks:
- ms-pipeline
healthcheck:
test: ["CMD-SHELL", "pg_isready -U msadmin -d massspec"]
interval: 10s
timeout: 5s
retries: 5
volumes:
msdata:
networks:
ms-pipeline:
注意 database 服务加了 healthcheck,db-writer 的 depends_on 等待条件设置为 service_healthy。这样能确保写入数据时数据库已经可用,不会报连接失败。
4.11 本章知识结构一览
下面这张图总结了 docker-compose.yml 的核心要素和它们之间的关系。你看一眼就能抓住主线:
从中心节点向外,三根线分别指向 Services、Networks、Volumes。这三个部分就是 Compose 文件的骨架。每个服务定义镜像来源、依赖关系,再挂载卷、接入网络,整个流水线就串起来了。