第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 的认知地图:

Cromwell 核心知识体系(第1章) 用户 / 提交者 WDL 工作流定义 Cromwell 引擎 状态机(异步、可恢复) 后端工厂(Backend) Local HPC Cloud (AWS/GCP/Azure) Kubernetes 元数据库(MySQL/PG) 核心优势 一次定义,随处执行 容器化隔离 断点续跑,Call Caching

从图里你能看到:用户提交 WDL 给 Cromwell引擎,引擎把任务丢给后端工厂,具体的执行环境可以是本地、HPC、云原生、K8s。所有任务状态写入元数据库,确保宕机不丢数据。这就是 Cromwell「一次定义,随处执行」的底气。

避坑指南:初学者最常犯的错

  1. 忘了装 Docker:Cromwell 默认用 docker 执行 WDL 里的 runtime 镜像。我曾经让实习生配环境,他死活说跑不动,结果就是没装 Docker。
  2. 路径写死:~/data/input.txt 这种路径在本地没问题,但切到云端后权限和路径全不一样。所有输入输出请用 Cromwell 的 File 类型,通过 inputs.json 传入。
  3. 内存配太小:WDL 里写的 memory: "2 GB" 可不是建议,是硬要求。低于门槛的任务会被 Cromwell 直接排队等资源或者报错。

我的调试建议:第一次跑新流程,务必在 Local 后端用 java -jar cromwell.jar run workflow.wdl --inputs inputs.json 跑一遍。本地通了再推给集群或云上。相信我,这一步能帮你省下80%的排查时间。