Cromwell架构与组件归纳总结

刚接触Cromwell的时候,你可能觉得它就是个“跑工作流的黑盒子”。把WDL丢进去,出来结果就完事了?其实没那么简单。Cromwell内部拆得很清楚——每个组件各管一摊事,合起来才撑得起混合云这种跨平台的场景。

我个人习惯先从架构图入手,脑子里有了整体轮廓,再去看配置和代码就不会晕。下面这张图是我自己整理的Cromwell核心组件关系,你先扫一眼,后面咱们一个一个拆。

Cromwell核心组件架构图 Cromwell 核心组件架构 API / CLI Cromwell Server Job Manager Metadata Store Call Cache Database Backend A Backend B 可选依赖 箭头表示数据流或调用关系,虚线表示可选配置 混合云部署中通常配置多个Backend(如本地 + 云)

1. Cromwell Server——大脑

Cromwell Server是整个系统的调度核心。你通过HTTP API提交工作流描述(WDL或CWL),Server负责解析、拆解成一个个task,然后分发给后端执行。

我刚开始用的时候老觉得Server就是单点,万一挂了怎么办?其实Cromwell Server本身是无状态的,真正的状态全在Database里。你想想看,只要数据库不丢,换个Server实例一样能恢复所有工作流。

小建议:生产环境一定给Server配健康检查。我常用的是 /engine/v1/status 端点,5秒探活一次。

2. Database——一切状态的归宿

Cromwell支持MySQL、PostgreSQL、H2(仅测试用)。工作流进度、输入输出、元数据、call cache索引……全存里面。

数据库类型生产推荐我踩过的坑
MySQL 8.0+推荐,支持事务连接数设大些,默认150不够用
PostgreSQL 13+同样推荐注意时区配置,否则时间戳对不上
H2仅开发测试重启即丢数据,别问我是怎么知道的

我建议混合云场景下把数据库放在公网可达的云服务上(比如阿里云RDS、AWS RDS),这样不管Server跑在本地还是云上,都能连到同一个库——实现真正的状态共享。

3. Backend——干活的人

Backend是真正运行计算的地方。Cromwell内置了Local、HPC(PBS/LSF/Slurm)、Spark、Google Cloud、AWS Batch等。混合云迁移的核心就是配置多个Backend,让Server根据标签或资源需求动态选择合适的平台。

举个例子:

# application.conf 片段
backend {
  providers {
    local {
      actor-factory = "cromwell.backend.impl.sfs.config.ConfigBackendLifecycleActorFactory"
      config {
        # 本地运行,适合小任务
      }
    }
    gcp {
      actor-factory = "cromwell.backend.impl.sfs.config.ConfigBackendLifecycleActorFactory"
      config {
        # 指向Google Life Sciences API
        project = "my-project"
        location = "us-central1"
      }
    }
  }
}
注意:多个Backend的命名不要重复,否则Cromwell会直接报“Duplicate backend”错误。我曾经手滑把两个都叫“local”,排查了半天。

4. Job Manager与Metadata Store

Job Manager负责与具体后端交互,比如提交任务、轮询状态、拉日志。Metadata Store记录工作流的每一步发生了什么——输入、输出、标准错误、执行时间等等。混合云场景下,Metadata Store特别有用,你可以通过它对比不同后端的执行效率,决定哪些任务丢到云上更划算。

我一般在工作流跑完后,直接查询Metadata API抓取数据:

GET /api/workflows/v1/{id}/metadata?expandSubWorkflows=true

返回的JSON里能看到每个task的start、end、executionStatus、stdout、stderr等字段。

5. Call Cache——别再算第二遍

Call Cache是Cromwell一个特别实用的特性。同输入同命令的任务,在Cache表中查得到结果就直接复用,不重新跑。混合云迁移时这个功能尤其重要:你在本地跑过一遍的任务,切到云上再跑,只要工作流定义不变、输入哈希一致,Cloud端直接拿结果,省时省力。

不过要注意,Cache默认只比较输入文件和命令,如果依赖了外部环境变量或镜像版本,最好显式声明runtime { docker: "…" },否则可能复用错误的结果。我记得有次迁移后一批样本的质控结果和前一天一模一样,后来发现是Cache把旧结果喂给了我——参数没变,但镜像已经更新了,差点造成事故。

避坑指南:如果切换了Backend(比如从Local切到AWS Batch),建议先清空Cache表中相关的条目,或者设置 call-cache-dir 为不同路径,避免跨环境串数据。

6. 组件间如何协作——一个简化的流程

  1. 用户通过REST API提交工作流描述(WDL/CWL)和输入。
  2. Server解析工作流,拆解成任务图,存入Database。
  3. Server把可执行任务交给Job Manager,Job Manager根据任务标签选择对应的Backend。
  4. Backend在目标环境(本地、集群或云)启动计算,实时更新状态回Database。
  5. 每个任务执行完毕,Metadata Store记录完整的执行信息。
  6. 如果开启Call Cache,Server在提交任务前先查Cache表,匹配则跳过执行,直接输出。

说白了,整个架构就是“Server管事,DB记账,Backend干活”。混合云部署要关注的就是让多个Backend能共享同一个Database和Metadata Store。

7. 部署时的配置要点

我通常把配置拆成三个文件:

  • cromwell.conf —— 全局设置(端口、并发、日志级别)
  • database.conf —— 数据库连接
  • backends.conf —— 各后端参数

然后在启动时通过 java -Dconfig.file=/path/to/cromwell.conf … 加载。这样以后改某个后端配置,只改一个文件,不会乱。

个人经验:混合云环境里,建议给每个Backend配独立的 filesystems 和 docker 配置,因为本地和云上的文件路径习惯完全不同。有一次我把云上的共享存储路径写成了本地绝对路径,任务全挂了。