第1章:Cromwell简介与核心概念
说起工作流引擎这事儿,我最早接触的是Cron和Makefile。那时候配个生物信息分析的流程,得手动写一堆Shell脚本拼起来,一跑就是三四天。中间哪个步骤崩了,得从断点重来?基本靠祈祷。
后来我遇到了Cromwell。说实话,第一眼看到它时,我心想:这不就是个任务调度器吗?用了一阵才发现,它解决的核心问题远不止“调度”这么简单。
Cromwell 是什么
Cromwell 是 Broad Institute 开源的工作流执行引擎。用我的话讲——它是一个只管执行、不管环境的容器化流程管家。
你只需要定义好工作流图(先做什么、后做什么、每个步骤用什么 Docker 镜像、输入输出是什么),剩下的任务排队、资源申请、错误重试、结果收集,Cromwell 全帮你干了。
我个人的习惯是:不管项目多小,只要超过3个脚本步骤,我第一反应就是写个WDL扔给Cromwell跑。为什么?因为它让你不需要记住中间文件在哪,重跑就是改个参数的事情。
核心创新引擎:WDL
Cromwell 原生使用 WDL(Workflow Description Language)来描述工作流。WDL 的设计理念其实很朴素——把流程拆成工作流(workflow)和任务(task)两层。
- Task:原子操作单元。定义输入、命令、运行时要求(CPU/内存/Docker)、输出。
- Workflow:任务之间的调用关系和数据传递。
我们来看一个最简单的例子。假设你要做一个「先切分文件、再排序」的小流程:
task split_file {
input {
File inputFile
Int chunkSize
}
command {
split -l ~{chunkSize} ~{inputFile} chunk_
}
output {
Array[File] chunks = glob("chunk_*")
}
runtime {
docker: "ubuntu:20.04"
memory: "4 GB"
}
}
task sort_chunks {
input {
File chunk
}
command {
sort ~{chunk} > sorted_~{basename(chunk)}
}
output {
File sorted = "sorted_~{basename(chunk)}"
}
runtime {
docker: "ubuntu:20.04"
memory: "2 GB"
}
}
workflow my_pipeline {
input {
File bigFile
Int size = 10000
}
call split_file { input: inputFile = bigFile, chunkSize = size }
call sort_chunks as sort_all {
input: chunk = split_file.chunks
}
output {
Array[File] sortedResults = sort_all.sorted
}
}
注意看:~{} 这种写法是 WDL 的变量插值。我个人觉得 WDL 把表达力和简洁性平衡得不错,你写出来的东西基本就是思考过程的镜像。
执行模型:异步、状态驱动、可恢复
Cromwell 的执行模型和传统脚本有本质不同。它不做“顺序直跑”,而是维护一个内部状态机。
- 每个任务提交后,Cromwell 会持续轮询任务状态。
- 任务挂了?记录失败原因,根据配置决定是否重试。
- 任务完成后,把输出注册到数据库,再触发下游任务。
为什么要这么做?因为工作流一旦跨机器、跨集群、跨云,任何一个环节的失效概率都会上升。你想想看,如果中间某个节点被 Preempt(抢占式实例被回收),整个流程要完全重跑?我见过有人用一个巨长的 Python 脚本硬撑,结果每周都要手动救火。
Cromwell 的可恢复性:默认每完成一个任务就把元数据写入 MySQL/PostgreSQL。哪怕 Cromwell 服务端重启,也能从断点恢复运行中的工作流。这功能在我处理3000+任务的大规模多组学流程时,救过我不止一次。
可扩展后端架构
Cromwell 不绑死在某个计算平台。它通过Backend(后端插件)机制,让你自由选择执行环境。
| 后端类型 | 典型场景 | 我常用的场景 |
|---|---|---|
| Local后端 | 单机测试、小样本调试 | 写WDL后先拿1个样本local跑通 |
| HPC后端(LSF/SGE/Slurm) | 企业内部集群 | 银行、药企的合规环境,不能出内网 |
| AWS Batch / Google Life Sciences / Azure Batch | 云上弹性扩展 | 我负责的全球协作项目,靠这个关掉自建机房 |
| Kubernetes | 云原生、混合云 | 当前最推荐的方式,混合部署成本最低 |
讲到这里你可能会问:多个后端怎么共存? Cromwell 支持在一个实例里配置多后端,工作流提交时指定 backend 标签即可。例如让高内存任务跑云上,常规任务丢本地集群。
我曾经踩过的坑:Cromwell 的默认配置对某些后端的超时检测不够主动。如果你用 Local 后端测试没问题,但切换到 HPC 后端时发现任务一直挂在"Running"状态——记得检查后端的 job-shell 配置和 Cromwell 的 call-caching 策略。
知识体系结构总览
下面这张图展示了本章覆盖的核心概念之间的关系。你可以把它当作理解 Cromwell 的认知地图:
从图里你能看到:用户提交 WDL 给 Cromwell引擎,引擎把任务丢给后端工厂,具体的执行环境可以是本地、HPC、云原生、K8s。所有任务状态写入元数据库,确保宕机不丢数据。这就是 Cromwell「一次定义,随处执行」的底气。
避坑指南:初学者最常犯的错
- 忘了装 Docker:Cromwell 默认用
docker执行 WDL 里的 runtime 镜像。我曾经让实习生配环境,他死活说跑不动,结果就是没装 Docker。 - 路径写死:
~/data/input.txt这种路径在本地没问题,但切到云端后权限和路径全不一样。所有输入输出请用 Cromwell 的File类型,通过inputs.json传入。 - 内存配太小:WDL 里写的
memory: "2 GB"可不是建议,是硬要求。低于门槛的任务会被 Cromwell 直接排队等资源或者报错。
我的调试建议:第一次跑新流程,务必在 Local 后端用 java -jar cromwell.jar run workflow.wdl --inputs inputs.json 跑一遍。本地通了再推给集群或云上。相信我,这一步能帮你省下80%的排查时间。