ワークフロー言語であるWDLをCromwellを使ってGridEngineと連携させて動かす方法を紹介します。一応GridEngineを例に説明しますが、TorqueやSlurmのようにディスクを計算ノード間で共有してジョブスケジューリングしてくれるものであれば多少の改変で対応可能なはずです。日本のアカデミックの共有計算機でよくある構成で利用可能なはずです。
WDLとは
WDLとはBroad Instituteが中心になって開発するワークフロー言語です。CWLやSnakemakeなどワークフロー言語は星の数ほど存在しますが、WDLはゲノム解析の定番ソフトウェアであるGATKのお膝元で作られていることもあり、GATKでどのように解析すればよいかはWDLとしてワークフローが提供されています。また、gnomADのような大規模解析でも実績があるため、万が一数千、数万のゲノム解析をやらざるをえなくてなっても対応可能です。
Cromwellとは
CromwellはWDLの実行エンジンです。Cromwell自身はWDLとCWLに対応しており、各種クラウドプラットフォームやジョブスケジューラに対応しています。Scalaで書かれており、jarファイルひとつで完結しているため、Javaのランタイムさえあれば依存関係に悩むことなくセットアップすることができます。
準備するもの
- GridEngine (UGE, SGEなど)
- Cent OS 7でSon of GridEngineをビルドする方法はgistにあるDockerfileをご覧ください
- Son of GridEngineのセットアップ方法は私の昔のQiitaの記事をご覧ください
- Singularity
-
カスタム版Cromwell
- 若干使いやすくするためにカスタムをしています
- 詳細は設定ファイルの中で説明します
設定ファイル
Cromwellの設定は設定例がCromwellのGitHubに載っています。ただ、ここに記載の例では不足する項目も多いので以下に私が使っているものを紹介します。
include required(classpath("application"))
# サーバーの待ち受けポートとIPアドレスの設定
webservice {
port = 8080
interface = 0.0.0.0
}
# Call Cacheを有効化し、同じデータに対して同じ処理をするときには以前使ったデータを再利用する
call-caching {
enabled = true
}
# ファイルのハッシュをキャッシュしておく
system {
file-hash-cache = true
}
# Docker Hubへ接続しない
# インターネットから切断した環境では必要
services {
HealthMonitor {
config {
check-dockerhub = false
}
}
}
# データベースに接続する
# しなくても使えないことはないがcall cacheを使うためには必須
# H2の組み込みデータベースも使えるが、安全のためにも外部のDBを使う方がよい
database {
profile = "slick.jdbc.MySQLProfile$"
db {
driver = "com.mysql.cj.jdbc.Driver"
url = "jdbc:mysql://mysqlserver/cromwell?rewriteBatchedStatements=true&useSSL=false"
user = "cromwell"
password = "password"
connectionTimeout = 5000
}
}
# DockerのイメージのハッシュをDocker Hubと直接通信するのではなくdockerコマンドの呼び出しで行う
# インターネットから切断されている環境では必須
# 実際にはSingularityを使うためにdockerコマンドを模したシェルスクリプトでごまかす
docker {
hash-lookup {
method = "local"
}
}
# 実行エンジンの設定
backend {
# SGEと名前がついているが、下の providers の中に書く名前と一致させればよい
default = "SGE"
providers {
SGE {
# Shared file systemのバックエンドを利用する
actor-factory = "cromwell.backend.impl.sfs.config.ConfigBackendLifecycleActorFactory"
config {
filesystems {
local {
localization: [
"soft-link"
]
caching {
# あるジョブの計算結果を別のジョブの入力とするときにどのようにするかを指定
# hard-link, soft-link, copy, cached-copy が利用可能
# オフィシャルのcromwellはdockerを使うジョブでsoft-linkが使えないが、カスタム版ではその制限を外してある
duplication-strategy: [
"soft-link"
]
# ファイルの同一性判定をどのようにやるかを指定
# デフォルトはfileだが、入力ファイルが多いとハッシュでCPUを食いつぶすのでパスと変更日時から作ったハッシュで代用する
# この場合はsoft-linkのみしか使えない
hashing-strategy = "path+modtime"
}
}
}
# 実行時パラメータとして受け入れるものを指定
# CPUコア数と利用メモリ、docker imageが指定でき、docker imageはオプション扱い
runtime-attributes = """
Int cpu = 1
Float memory_gb = 4
String? docker
"""
# Dockerを使わない場合のジョブの投入
# 最初にスロットあたりのメモリ使用量を計算し、それを元にmem_reqとs_vmemの2つのパラメータを設定している
submit = """
MEM_G_PER_CORE=`echo 'scale=3; ${memory_gb}/${cpu}'|bc`G
qsub \
-terse \
-V \
-b y \
-N ${job_name} \
-wd ${cwd} \
-o ${out} \
-e ${err} \
-pe def_slot ${cpu} \
-v JAVA_TOOL_OPTIONS="-XX:+UseSerialGC -XX:CICompilerCount=2" \
-l mem_req=$MEM_G_PER_CORE,s_vmem=$MEM_G_PER_CORE \
/usr/bin/env bash ${script}
"""
# Dockerを使う場合のジョブの投入
# 最初にスロットあたりのメモリ使用量を計算し、それを元にmem_reqとs_vmemの2つのパラメータを設定している
# Dockerを使う設定だとsoft-linkの指定先が読めない問題が発生するため、すべてのsoft-linkとその指定先を探し出してbindする
# 実際にはSingluraityを呼び出すが、singularityはデフォルトでホームディレクトリをホストのものを使ってしまうので、使わないように設定しておく
submit-docker = """
set -xe
IMAGE=$HOME/singularity-images/sha256/${docker}.sif
MEM_G_PER_CORE=`echo 'scale=3; ${memory_gb}/${cpu}'|bc`G
mkdir -p ${cwd}/home
binds=`find ${cwd} -type l|while read one; do
real_path=$(realpath $one)
link_path=$(readlink $one)
echo -n '--bind' $real_path:$link_path:ro ' '
done`
qsub \
-terse \
-V \
-b y \
-N ${job_name} \
-wd ${cwd} \
-o ${out} \
-e ${err} \
-pe def_slot ${cpu} \
-v JAVA_TOOL_OPTIONS="-XX:+UseSerialGC -XX:CICompilerCount=2" \
-l mem_req=$MEM_G_PER_CORE,s_vmem=$MEM_G_PER_CORE \
singularity exec --no-home -H ${cwd}/home --bind ${cwd}:${docker_cwd} --bind ${script}:${script}:ro $binds $IMAGE ${job_shell} ${script}
"""
# 120秒おきにqstatを実行しジョブの実行状況を確認する
exit-code-timeout-seconds = 120
# Job IDのフォーマット
# qsubの結果の標準出力がどのようになっているか指定する
job-id-regex = "(\\d+)"
# Jobをキャンセルしたいときに実行するコマンド
kill = "qdel ${job_id}"
kill-docker = "qdel ${job_id}"
# Jobの実行状況を確認するためのコマンド
check-alive = "qstat -j ${job_id}"
# 同時に投入するジョブの数
# ここを超えるジョブ数はcromwellのキューでまち状態になる
concurrent-job-limit = 1000
}
}
}
}
パスに通すシェルスクリプト
SingularityにDockerのふりをさせるために以下のシェルスクリプトをdocker
という名前で保存し、パスを通します。また、必要なイメージはdocker pull
で取得すると適切な名前で$HOME/singularity-images
以下に保存してくれます。インターネットに繋がっていない環境の場合、インターネットに繋がっている環境でこのスクリプトを利用してdocker pull
を実行し、$HOME/singularity-images
を持ち込むとうまく動きます。
#!/bin/bash
set -e
IMAGE_DIR=$HOME/singularity-images/tag
IMAGE_DIR2=$HOME/singularity-images/sha256
export PATH=/usr/local/pkg/singularity/current/bin:${PATH}
#mkdir -p "${IMAGE_DIR}"
case $1 in
"pull" )
DOCKER_IMAGE="$2"
IMAGE_PATH=${IMAGE_DIR}/${DOCKER_IMAGE}.sif
if [ -e ${IMAGE_PATH} ];then
echo "This image is exists. Please delete ${IMAGE_PATH} to recreate image"
else
mkdir -p ${IMAGE_PATH%/*}
singularity build "${IMAGE_PATH}" "docker://${DOCKER_IMAGE}"
sha256sum "${IMAGE_PATH}" > "${IMAGE_PATH}.sha256sum"
IMAGE_WO_TAG=${DOCKER_IMAGE%:*}
SHA256_CONTENT=$(cat "${IMAGE_PATH}.sha256sum")
SHA256=${SHA256_CONTENT%% *}
IMAGE_PATH2=${IMAGE_DIR2}/${IMAGE_WO_TAG}@sha256:${SHA256}.sif
mkdir -p ${IMAGE_PATH2%/*}
ln ${IMAGE_PATH} ${IMAGE_PATH2}
fi
;;
"images" )
find "${IMAGE_DIR}" -name '*.sif'|while read line; do
IMAGE_PATH=$(echo $line|sed -e "s|^${IMAGE_DIR}/||g")
IMAGE_NAME=${IMAGE_PATH%.sif}
BASE_NAME=${IMAGE_NAME%:*}
TAG_NAME=${IMAGE_NAME#*:}
SHA256_CONTENT=$(cat ${line}.sha256sum)
SHA256=${SHA256_CONTENT%% *}
printf "${BASE_NAME}\t${TAG_NAME}\tsha256:${SHA256}\n"
done
;;
"find" )
DOCKER_IMAGE="$2"
IMAGE_PATH=${IMAGE_DIR}/${DOCKER_IMAGE}.sif
if [ -e ${IMAGE_PATH} ];then
echo ${IMAGE_PATH}
else
echo "Image not found"
exit 1
fi
;;
* )
echo invalid command
exit 1
;;
esac
ここまで設定すればWDLがGridEngineでDockerも含めて動くはずです。(あとでもうちょっと追記します)