1. Cromwell 简介与背景

做生信流程开发的人应该都听过 Cromwell。它是什么?说白了就是一个工作流管理系统,专门用来跑 WDL(Workflow Description Language)写的流程。我最早接触这东西是在 2018 年,当时团队还在用 Shell 脚本串工具,每次换环境都要改一堆路径,非常痛苦。后来切换到 Cromwell,才体会到什么叫“一次定义,到处运行”。

Cromwell 的核心价值在于它把流程定义和执行环境彻底解耦了。你写好的 WDL 工作流,可以在本地笔记本跑,也可以直接扔到 AWS Batch、Google Cloud Life Sciences 或者 HPC 集群上,底层的计算资源完全不用改流程代码。嗯,这就是为什么这门课要专门讲本地与云端切换——你不能只会在本地调通,线上崩了就不会排查。

1.1 Cromwell 解决了什么问题?

在没有工作流引擎之前,生信流程通常是这样的:

  • 每个步骤写成独立的 Shell 脚本
  • 用 Makefile 或 Snakemake 串联依赖
  • 日志散落在不同目录,失败时手动重跑
  • 切换云平台得重写整个 IO 逻辑

Cromwell 帮你封装了这些东西:

  • 声明式依赖 – 你在 WDL 里写 call stepB { input: file_from_stepA },引擎自动判断顺序
  • 可插拔后端 – 后端只负责执行,流程本身不关心
  • 持久化状态 – 就算机器重启,Cromwell 也能从断点继续跑
  • 标准输出 – 每次运行的 metadata 都是 JSON 格式,便于追踪
我的习惯:新项目我会先用 java -jar cromwell.jar run 本地调通,再切到云端。这样能在开发阶段快速迭代,避免浪费云资源。

1.2 WDL 与 Cromwell 的关系

WDL 是语言,Cromwell 是引擎。你写出 WDL 文件,Cromwell 负责解析、调度、执行。它不是唯一能跑 WDL 的引擎(还有 miniwdl),但 Cromwell 是社区最成熟、对云支持最好的一个。

看一个最简单的 WDL 文件长什么样:

workflow hello {
  call sayHello
}

task sayHello {
  command {
    echo "Hello, Cromwell!"
  }
  output {
    String greeting = read_string(stdout())
  }
}

用 Cromwell 跑它:

java -jar cromwell-85.jar run hello.wdl

输出里你会看到每个任务的 stdout、stderr 和返回值。我在生产环境里经常把 output 字段写成 File 类型,这样 Cromwell 会自动把结果上传到云存储,不需要我手动 copy。

1.3 多种后端支持

Cromwell 官方支持的后端,我用表格列一下常用的:

后端类型 典型配置 适用场景
Local 单机 JVM 线程池 开发、调试、小规模测试
HTCondor 集群调度器 高校/HPC 现有集群
AWS Batch Docker + S3 弹性伸缩,按需付费
Google Cloud Life Sciences GCS + 虚拟机组 Google 生态下的生信
SLURM HPC 作业管理器 超算中心

注意一个关键点:本地后端和云后端对输入的路径处理方式完全不同。本地用绝对路径或相对路径,云端则要用 URI(如 gs://bucket/path/to/file)。这门课后面会专门讲路径映射的配置,这是个容易翻车的地方。

我曾经遇到过:直接把生产环境的 WDL 文件拉到本地跑,结果任务失败了。查了半天发现是 File 输入指定了 gs:// 路径,本地后端根本读不到。从那以后我都在 WDL 里用 String filename 存储原始路径,由配置文件决定怎么解析。

1.4 Cromwell 工作流执行架构

下面这张图展示了 Cromwell 的核心工作流程:

Cromwell 工作流执行架构 WDL 工作流 定义步骤 + 依赖 Cromwell 引擎 解析 / 调度 / 执行 本地后端 Local / SLURM 云后端 AWS Batch / GCP 配置解耦:cromwell.conf 指定后端类型,WDL 文件无需修改 切换后端只需改一行 backend.providers.default.config

流程很清晰:你写好 WDL 丢给 Cromwell 引擎,引擎根据配置文件决定把任务发给本地还是云端。每个后端的行为差异,比如文件系统挂载、Docker 支持、资源请求上限,都由 Cromwell 的配置层吸收掉了。

1.5 为什么要学“本地与云端切换”

很多人学 Cromwell 只停留在本地跑 demo,觉得配置好复杂。等真要上生产,发现云端的路径映射搞不定,Docker 镜像拉不下来,任务日志找不着,这才回头翻文档。我的观点是:最好从一开始就按“可切换”的方式来组织你的 WDL 和配置文件。

举一个最简单的对比:

  • 本地路径写法:/home/user/data/sample.fastq
  • 云端路径写法:s3://bucket/project/sample.fastq

看起来只是前缀不同,但 Cromwell 的 call_cache 和 workflow_failure_mode 在云上会有更复杂的交互。后面我会用专门的一章讲 Input 路径的抽象化原则。

核心认知:Cromwell 的伟大之处不在于它能跑什么工作流,而在于它让你在切换执行环境时,工作流不需要重写。这是所有生信平台工程师都应该掌握的能力。

1.6 检查你的 Cromwell 环境

在开始课程后续章节之前,我建议你先确认自己的 Cromwell 版本。执行以下命令:

java -jar cromwell.jar --version

如果输出类似 Cromwell 85 或更高版本,就说明环境正常。如果是 83 以前的版本,有些新特性(如 file_input 模式)可能不支持,建议升级。我干活时用的都是 85 以上,遇到 bug 也比较少。

另外,检查你的 cromwell.conf 是否引用了正确的后端。一个最简的本地配置是这样的:

backend {
  default = "Local"
  providers {
    Local {
      actor-factory = "cromwell.backend.impl.sfs.config.ConfigBackendLifecycleActorFactory"
      config {
        root = "/tmp/cromwell-executions"
        filesystems {
          local {
            localization = [
              "soft-link",
              "hard-link",
              "copy"
            ]
          }
        }
      }
    }
  }
}

这个配置告诉 Cromwell:默认用 Local 后端,文件通过软链接本地化。等后面切换到云后端时,你只需要在这个文件里新增一个 AWS 或 GCP 的 provider 块,然后把 default 改过去就行。

好了,背景铺完了。第二章我们会动手搭建第一个 Cromwell 本地环境,然后跑一个真实(但不复杂)的 WDL 流程。到时候你会看到“解析-调度-执行”的完整链路,顺便踩几个我当年踩过的坑。