使用 Docker 部署 Cromwell
说实话,Docker 部署 Cromwell 是我在实际项目中最常用的一种方式。为什么?因为它省心。你想想看,Cromwell 本身依赖 Java,还有各种数据库驱动、Centaur 组件等等。直接用 Docker 镜像,这些东西全部被打包好了,拿到手就能跑。
我曾经在一个客户现场部署 Cromwell,那环境真叫一个干净——连 Java 都没有。用 Docker 部署,五分钟搞定了所有环境问题。下面我把具体的步骤和踩过的坑都写出来。
Docker 部署的三种方式
根据团队习惯和环境条件,我通常推荐三种方式。你可以根据实际情况选一种。
| 部署方式 | 适合场景 | 优点 | 缺点 |
|---|---|---|---|
| docker run | 快速验证、开发测试 | 简单直接,一条命令搞定 | 不方便管理和持久化 |
| docker-compose | 混合云部署、多服务编排 | 支持 MySQL、数据库持久化 | 需要额外编写 YAML 文件 |
| 容器编排(K8s) | 生产环境、大规模集群 | 高可用、自动扩展 | 学习成本高,配置复杂 |
准备工作:拉取镜像
我习惯先确认一下镜像版本。Cromwell 官方在 Docker Hub 上维护了镜像,你也可以自己 build。但说真的,用官方镜像更稳妥。
# 拉取最新稳定版
docker pull broadinstitute/cromwell:latest
# 查看本地已有镜像
docker images | grep cromwell
小经验:我个人习惯把镜像版本号固定下来,比如 broadinstitute/cromwell:85。用 latest 在开发环境没问题,但生产环境一旦镜像更新,行为可能变化,容易出问题。
方法一:直接 docker run
这是最快的验证方式。你不用管数据库、不用管配置,Cromwell 默认用内存数据库。说白了,就是启动一个单机版服务,适合本地调试。
# 启动 Cromwell 服务
docker run --name cromwell-server \
-p 8000:8000 \
broadinstitute/cromwell:85 \
server
跑起来之后,你可以用 curl 测试一下是否正常:
curl http://localhost:8000/swagger
如果能返回 Swagger 接口文档页面,嗯,恭喜你,部署成功了。
注意:这种方法启动的服务不会持久化数据。容器一删,所有 workflow 记录都没了。我只建议在「尝鲜」和「写 Demo」的时候用。
方法二:docker-compose 生产化部署
如果要在多平台迁移的场景下用,我强烈推荐 docker-compose。它可以把 Cromwell、MySQL、数据库初始化脚本全部编排在一起,一步到位。
下面是我项目中常用的 docker-compose.yml 配置文件。我自己做了简化,主要去掉了冗余的卷映射,保留了最关键的部分。
version: "3.8"
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: cromwell_root
MYSQL_DATABASE: cromwell_db
volumes:
- cromwell_mysql_data:/var/lib/mysql
ports:
- "3306:3306"
cromwell:
image: broadinstitute/cromwell:85
ports:
- "8000:8000"
environment:
- CROMWELL_DB_URL=jdbc:mysql://mysql:3306/cromwell_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
- CROMWELL_DB_USER=root
- CROMWELL_DB_PASS=cromwell_root
depends_on:
- mysql
volumes:
- ./cromwell.conf:/cromwell/cromwell.conf
volumes:
cromwell_mysql_data:
这里要注意,Cromwell 的数据库连接信息需要通过环境变量传入。我踩过一个大坑:MySQL 8.0 以上版本默认使用 caching_sha2_password 加密方式,Cromwell 的 JDBC 驱动可能不兼容。解决方法是在连接串上加 allowPublicKeyRetrieval=true。
配置 Cromwell 数据库连接
我们还需要一个 cromwell.conf 配置文件。这个文件告诉 Cromwell 怎么连数据库。它与上面的 docker-compose.yml 在同一个目录下。
database {
profile = "slick.jdbc.MySQLProfile$"
db {
driver = "com.mysql.cj.jdbc.Driver"
url = ${CROMWELL_DB_URL}
user = ${CROMWELL_DB_USER}
password = ${CROMWELL_DB_PASS}
connectionTimeout = 5000
}
}
restart {
strategy = "fixedDelay"
delay = 30
}
你看,这里用了 ${CROMWELL_DB_URL} 这种变量引用方式。Cromwell 会从环境变量中读取这些值。这样做的好处是——配置文件和环境解耦,不同环境只需要改环境变量就行。
启动并验证
一切准备好之后,启动服务:
# 后台启动所有服务
docker-compose up -d
# 查看启动日志,确认没有报错
docker-compose logs -f cromwell
# 检查服务状态
docker-compose ps
看到服务状态都是 Up 之后,再验证 API:
curl -X POST http://localhost:8000/api/workflows/v1 \
-H "Content-Type: application/json" \
-d '{
"workflowSource": "task hello { command { echo hello } }",
"workflowInputs": {}
}'
避坑指南:我曾经遇到一个情况——Cromwell 启动成功了,但提交 workflow 一直报 500 错误。查了半天,发现是 MySQL 的时区问题。Cromwell 要求数据库的 time_zone 设置为 UTC,不然会有一些说不清的时间转换异常。解决方法是在 MySQL 服务配置里加上 default-time-zone='+00:00'。
Docker 环境常见问题排查清单
我整理了一张排查清单,这些都是在实际项目中频繁出现的问题:
- 端口映射冲突:8000 端口被占用。改用
-p 18000:8000映射不同的宿主机端口。 - 容器重启失败:检查 MySQL 服务是否先于 Cromwell 启动。可以通过
docker-compose logs看启动时序。 - 数据丢失:记得把 MySQL 数据卷映射到宿主机路径,否则容器删除后数据全部丢失。
- Java 版本不兼容:官方镜像内部已经带好了 Java,如果自己构建镜像,务必要用 Java 11 或 17。
Docker 日志与容器管理
日常运维中,常用的命令如下。我习惯把常用的几个记成小本本:
# 查看实时日志
docker logs -f cromwell-server
# 进入容器内部
docker exec -it cromwell-server bash
# 拷贝配置文件到容器
docker cp ./cromwell.conf cromwell-server:/cromwell/
# 停止并删除容器
docker-compose down
# 清理所有未使用的数据卷(谨慎使用)
docker volume prune
这里我要提醒一下——docker volume prune 会把没有被使用的数据卷全部删掉。如果你用 docker-compose down -v 也会一起删掉数据卷。数据没了,哭都来不及。所以,生产环境一定要把数据卷映射到宿主机持久目录。
部署流程概览
我把整个部署流程总结成了下面这张图,你可以照着这个顺序一步一步来:
几点额外提醒
嗯,内容基本说完了。但基于我在多个项目中折腾 Docker 的经验,再补充几条:
- 日志清理:Cromwell 日志会随时间膨胀。尤其是 workflow 执行失败时,堆栈信息特别长。建议在 docker-compose.yml 中加入日志轮转配置,或者用 logrotate 定时清理。
- 健康检查:我用
docker-compose的时候,会在 Cromwell 服务中加入healthcheck小节,定期调用/api/swagger端点。这样能第一时间知道服务是否还活着。 - 资源限制:Cromwell 默认会吃不少内存。我建议在
docker-compose.yml里加上mem_limit: 4g,防止它把宿主机撑爆。