WDL 初识:从“手动挡”到“自动挡”的工作流革命

说实话,我第一次见到 WDL 的时候,心里是抵触的。

那时候还在跑传统的生物信息流程。拿一组 RNA-seq 数据,先手动 SSH 登录服务器,建目录,传脚本,设环境变量,提交 SGE 任务,盯着日志,失败了手动调参重跑……一个 50 样本的项目,光跟流程错误斗智斗勇就能耗掉小半个月。更别提换到云上——AWS Batch 的参数怎么写?Google Cloud 的存储挂载怎么搞?每个平台都得重新学一套运维手艺。

直到我偶然翻到 Broad Institute 的 GATK 最佳实践文档,里面跑 Germline 管线时,用的就是一堆后缀是 .wdl 的文件。我当时想:“这什么东西?怎么比 Makefile 还啰嗦?”但硬着头皮读完第一个 WDL 脚本后,我突然意识到:这就是我一直在找的“工作流通用语言”。

一句话总结:WDL 就是让你用一份脚本,在本机、本地集群、AWS、GCP、Azure 上跑同一个流程,不用改代码,不用记平台差异。

WDL 到底是什么?

WDL,全称 Workflow Description Language。说人话:它是一种专门写“多步骤任务编排”的声明式语言。你只要说“我要跑这个软件”、“输入文件是这些”、“输出放在那里”,剩下的依赖管理、任务调度、数据传递——WDL 帮你搞定。

跟 Shell 脚本比,WDL 有几个硬核区别:

维度 Shell 脚本 WDL
可移植性 依赖具体平台命令 平台无关,靠 Cromwell 翻译
依赖管理 手动 if/exit 判断 语言内建的 call 和声明式依赖
数据传递 全局变量 + 文件路径 类型安全的变量绑定
资源声明 无标准方式 runtime 块显式声明 CPU/内存/磁盘
复现性 难以保证环境一致 Docker 是头等公民

我个人习惯,把 WDL 理解成一个“工作流编译器”的输入文件。它不是直接运行命令的,你得有一个“引擎”去解释它、调度它、执行它。

这个引擎,就是 Cromwell。

Cromwell 工作流引擎:背后的“总指挥”

Cromwell 是 Broad Institute 开源的一个工作流管理系统,专门用来跑 WDL(也支持 CWL)。你写好 .wdl 文件,加上输入 JSON,把它丢给 Cromwell,剩下就不用管了。

Cromwell 会帮你做这几件事:

  • 解析 DAG:分析 WDL 里 call 之间的依赖,构建一个有向无环图(DAG)。
  • 分发任务:根据每个 task 的 runtime,决定把任务发到本地、集群还是云端。
  • 管理状态:跟踪每个 task 的成功、失败、重试、跳过。
  • 处理输入输出:自动在本地文件系统和云存储之间搬运数据。

我曾在一个项目里,用 Cromwell 跑一个 1800 样本的 WGS 分析。流程里包含 7 个工具,2 个超长步骤(跑一次 8 小时),中间还会因为磁盘写满掉任务。Cromwell 的自动重试和 call cache 功能,帮我把最终失败率从 12% 降到了 0.3%。说实话,当时如果没有 Cromwell,那个项目根本不可能按期交付。

WDL 在生物信息中的角色:统一沟通语言

生物信息行业,有一个绕不开的问题:“你们的流程能在我们集群上跑吗?”

以前,每个实验室都有自己的流程。A 组写的 Snakemake 脚本,B 组换到 SGE 就得全改;C 组用 Nextflow 写的管线,D 组的 AWS Batch 根本不认——大家重复造轮子,而且造出来的轮子还不通用。

WDL 的出现,本质上是在生物信息领域建立了一种“流程即代码”的规范。就像你用 Python 写数据分析,换台机器装个 pip 就能跑一样。WDL 让你写一次工作流,在不同计算后端——SLURM、AWS Batch、Google Cloud Life Sciences、Azure Batch——都能直接运行。不需要改一行流程逻辑。

我的经验是:如果你准备写一个新流程(比如基于 GATK4 或 something 类似的长管线),优先考虑 WDL,而不是从 Shell 脚本或 Makefile 开始。后期运维带来的时间节省,远超初期学习 WDL 语法的成本。

WDL 在云原生中的角色:工作流“翻译官”

云原生这两年很火。容器化、Kubernetes、Serverless……但这些技术跟生信分析之间存在一个“语义鸿沟”。生信分析需要写复杂的多步骤管线,而云原生平台提供的只是原始的计算资源接口——你告诉它“我要一个 4 核的容器”,它只给你一个容器。怎么编排 200 个容器的依赖和时序?那是生信分析自己的事。

WDL + Cromwell 组合,正好填上了这个沟。Cromwell 充当了“编排层”的角色:它把 WDL 的抽象逻辑,翻译成对应云后端的 API 调用。

  • 在 AWS 上,它调用 Batch 提交任务,管理 S3 输入输出。
  • 在 GCP 上,它调用 Life Sciences API,管理网络、磁盘、区域。
  • 在本地,它直接 fork 子进程或提交给 SLURM。

你写的 WDL 脚本,完全不知道后端是哪一种。你只需要告诉 Cromwell:“我用哪个配置文件启动。”这就是工作流的“云原生”真相:不是流程本身跑了 Docker 就叫云原生,而是流程编排跟底层基础设施完全解耦,可以即插即用。

踩坑提示:千万不要在 WDL 里硬编码路径或平台相关命令!比如在 task 里写 ls /mnt/data/input.bam,换到云上这个路径不存在,任务直接挂。所有输入必须通过 workflow 级别的 input 参数传递,让 Cromwell 在运行时注入正确路径。

三个概念的关系图

下面这张图,画的是 WDL、Cromwell、执行后端三者之间的协作关系。也是我每次给团队新人讲架构时一定会展示的一张图。看一眼就明白:

WDL 工作流核心概念与执行流程 用户编写 WDL 输入 JSON 可选 Docker 镜像 Cromwell 工作流引擎 解析 WDL · 构建 DAG · 分发任务 本地 / SLURM AWS Batch GCP / Azure Batch 结果文件 · 日志 · 元数据 核心优势 • 一次性编写 • 多后端运行 • 自动重试 • 公平调度 • 缓存复用 • 可复现性

你看,用户只需要关心最上层的三个东西:WDL 脚本、输入参数、容器镜像。中间的所有调度、状态管理、后端适配,全交给 Cromwell。Cromwell 把你写的 WDL 翻译成具体平台的任务,跑完后统一收集结果。你不需要登录任何一台机器查看任务状态——Cromwell 的 REST API 和监控界面足以搞定。

小试牛刀:第一个 WDL 片段

光说不练假把式。我们先看一个最简单的 WDL 轮廓,感受一下它的骨架长什么样:

workflow my_first_workflow {
  call say_hello
}

task say_hello {
  String name

  command {
    echo "Hello, ${name}!"
  }

  output {
    String greeting = read_string(stdout())
  }

  runtime {
    docker: "ubuntu:20.04"
    cpu: 1
    memory: "1GB"
  }
}

这个脚本做了三件事:

  1. 定义了一个叫 my_first_workflow 的工作流。
  2. 在里面调用了一个叫 say_hello 的任务。
  3. 这个任务,用 ubuntu 容器,打印一句问候语,并把 stdout 作为输出捕获。

你给它一个输入参数 {"my_first_workflow.say_hello.name": "WDL"},Cromwell 就会启动一个容器,跑 echo,返回结果。就这么直白。

个人建议:刚上手时,先不要一次性写大流程。把每个任务拆成一个独立 .wdl 文件,然后用 workflow 里的 import 机制组装。我吃过亏——一开始就把 15 个任务塞进一个文件里,调试困难,后来花了一个周末重构才解脱。模块化写 WDL 是正确姿势。