Galaxyでジョブを分散実行するには（実践編）

[昨日の記事](https://qiita.com/nasunom/items/1a12f941ae93c14f994b)の続きになりますが、実践編ということでSLURMクラスタを使ったジョブの分散実行環境を構築してみたいと思います。

[SLURM](https://slurm.schedmd.com/)はLinux環境で動作するオープンソースのジョブスケジューラで、スパコンなど大規模な計算ノードクラスタで広く利用されています。Galaxyはジョブ実行プラグイン(Job Runner) として SLURM をサポートしており、ツールの実行先としてSLURMクラスタの計算ノードを使うことができます。

## Galaxy + SLURM のサーバ構成

Galaxy と SLURM によるクラスタを構築するためには、以下の各機能を単一または複数のサーバに割り当てる必要があります。

1. Galaxy (Webサーバ, データベース)
2. SLURMクラスタ・コントローラ
3. SLURMクラスタ・計算ノード
4. 共有ファイルシステム

今回の構築例では、SLURM計算ノード用に2台のサーバを用意し、他の1台にGalaxyとSLURMコントローラをインストールします。また、共有ファイルシステムにはNFSを採用し、Galaxy+SLURMコントローラ側のディスク領域を各計算ノードからNFSマウントして利用することにします。

## 共有ファイルシステムの準備

まず、Galaxy+SLURMコントローラをインストールするサーバ上で NFS サーバを起動しておきます。
NFSのインストールおよび設定の詳細はここでは省略しますが、NFSサーバ側の `/export` ディレクトリをエクスポートして共有するには `/etc/exports` ファイルに以下のように記述します。

```file:/etc/exports
/export a.b.c.d/z(rw,sync,no_subtree_check,fsid=0,no_root_squash)
```
**注） `a.b.c.d/z` にはエクスポートされるファイルシステムへのアクセスを許可するネットワークを適切に設定する必要があります。(e.g. `192.168.1.0/24`)**

各SLURM計算ノードにおいて、共有ファイルシステムをローカルの `/export` ディレクトリにマウントします。
以下のコマンドは、GalaxyサーバのIPアドレスが `192.168.1.100` の場合の例です。

```
mount -t nfs 192.168.1.100:/export /export
```

## DockerによるGalaxyサーバ起動

Galaxy Projectの開発メンバーの一人である [Björn Grüning](https://github.com/bgruening) 氏によりGalaxyのDockerコンテナイメージが公開されています。このイメージには SLURM コントローラ機能が含まれているため、今回はこれを利用します。

https://hub.docker.com/r/bgruening/galaxy-stable/

Galaxy + SLURMコントローラを稼働させるサーバ上で、以下のようにDockerコンテナを起動します。

```
docker run -d --net host --hostname galaxy --name galaxy \
  -v /export:/export \
  -e "NONUSE=nodejs,proftp,reports,slurmctld,slurmd,condor" \
  bgruening/galaxy-stable:17.05
```

SLURMコントローラは、あとで（Galaxy起動後に）手動で設定を行うため、ここでは `NONUSE` オプションで `slurmctld, slurmd` を指定します。
その他の起動オプションについては、GitHub の [bgruening/docker-galaxy-stable](https://github.com/bgruening/docker-galaxy-stable) リポジトリに非常に丁寧なドキュメントがあります。

## DockerによるSLURMノード起動

上記の Galaxy サーバ用 Docker コンテナとともに利用可能な SLURM 計算ノード用コンテナとして、[bgruening/docker-galaxy-stable](https://github.com/bgruening/docker-galaxy-stable) で提供されているDockerfile（コンテナビルド用スクリプト）があります。

https://github.com/bgruening/docker-galaxy-stable/tree/master/test/slurm

これを `docker build` したコンテナイメージ(`galaxy-slurm`) を用意します。
SLURM計算ノードを稼働させる各サーバ上で、以下のようにDockerコンテナを起動します。

```
# SLURMノード #1
docker run -d --net host --hostname c1 --name slurm1 -v /export:/export galaxy-slurm

# SLURMノード #2
docker run -d --net host --hostname c2 --name slurm2 -v /export:/export galaxy-slurm
```

## SLURMクラスタの設定

### ホスト名解決

SLURMクラスタを運用する場合、各サーバはIPアドレスではなくホスト名でアクセスできなければなりません。内部向けDNS Serviceを起動する方法もありますが、クラスタを構成するノード数が少なければ `/etc/hosts` に記述するやり方でも構いません。
上記で起動した各コンテナ内の `/etc/hosts` に、`galaxy`, `c1`, `c2` のホスト名に対応するIPアドレスを記述します。（実際のIPアドレスは構築環境により異なります。）

```file:/etc/hosts
172.17.0.2 galaxy
172.17.0.3 c1
172.17.0.4 c2
```

### SLURM設定ファイルの共有ファイルシステムへの配置

SLURMでは以下の2つの設定ファイルが必要です。

1. slurm.conf
2. munge.key

クラスタを構成する全サーバでこれらのファイルを共有するために、NFSでエクスポートされるパス `/export` の直下に配置します。

Galaxyサーバには起動時に生成された `/etc/munge/munge.key` と `/etc/slurm-llnl/slurm.conf` があるはずですので、これらを共有ファイルシステムにコピーします。

```
cp /etc/munge/munge.key /etc/slurm-llnl/slurm.conf /export
```

次に、 `slurm.conf` の内容を編集します。重要なのは、末尾の2行です。

- NodeName, Nodes : 計算ノードのホスト名のリスト
- CPU, RealMemory(単位:MB) : 計算ノードのマシンスペックに合わせた値

```file:/export/slurm.conf
  :
  :
NodeName=c[1-2] CPUs=4 RealMemory=8192 State=UNKNOWN
PartitionName=debug Nodes=c[1-2] Default=YES MaxTime=INFINITE State=UP Shared=YES
```

### SLURMコントローラ・デーモンの再起動

Galaxyサーバ上にあるSLURMコントローラ( `slurmctld` )を再起動します。
このとき、共有ファイルシステムの設定ファイル `/export/slurm.conf` 読み込むように指示します。

```
# GalaxyサーバのDockerコンテナに接続
docker run -ti galaxy bash

# galaxy コンテナ内で実行
pkill slurmctld && pkill munged
munged --key-file=/export/munge.key -f
slurmctld -L /home/galaxy/logs/slurmctld.log -f /export/slurm.conf
```

### SLURMノード・デーモンの再起動

SLURMノード側はデフォルトで `/export/slurm.conf` を読み込むように設定されているため、関係するデーモンを再起動します。各SLURMノード上で以下のコマンドを実行します。

```
# SLURMノードのDockerコンテナに接続
docker run -ti slurm1 bash

# slurm1,2 コンテナ内で実行
supervisorctl restart munge
supervisorctl restart slurmd
```

SLURMクラスタを構成するサーバの Docker コンテナ内で `sinfo` コマンドを実行した結果、`STATE`=`idle` のノードが2つ見えればOKです。

```file:sinfoの実行結果
PARTITION AVAIL  TIMELIMIT  NODES  STATE   NODELIST
debug*        up   infinite      2   idle  c[1-2]
```

## Galaxyジョブ実行プラグイン設定

最後に、Galaxyがジョブ実行先としてSLURMクラスタを使うように設定します。
具体的には Galaxyがジョブ実行プラグイン(Job Runner)として `SlurmJobRunner` を使うための設定です。
`bgruening/galaxy-stable` のDockerコンテナを利用している場合、 `/etc/galaxy/job_conf.xml` を以下のように書きます。（共有ファイルシステムのPATHではないので注意）

```xml:/etc/galaxy/job_conf.xml
<?xml version="1.0"?>
<job_conf>
    <plugins workers="2">
        <plugin id="slurm" type="runner" load="galaxy.jobs.runners.slurm:SlurmJobRunner">
            <param id="drmaa_library_path">/usr/lib/slurm-drmaa/lib/libdrmaa.so</param>
        </plugin>
        <plugin id="local" type="runner" load="galaxy.jobs.runners.local:LocalJobRunner" workers="2"/>
    </plugins>
    <handlers default="handlers">
        <handler id="handler0" tags="handlers"/>
        <handler id="handler1" tags="handlers"/>
    </handlers>
    <destinations default="slurm_cluster">
        <destination id="slurm_cluster" runner="slurm">
            <param id="nativeSpecification">-p work -n 4</param>
            <param id="embed_metadata_in_job">False</param>
            <env file="/export/galaxy-central/.venv/bin/activate" />
        </destination>
        <destination id="local" runner="local"/>
    </destinations>
    <tools>
        <tool id="upload1" destination="local"/>
    </tools>
    <limits>
    </limits>
</job_conf>
```

ファイルアップロードツール (upload1) に関しては、SLURMを使わずにローカルサーバ上で実行する（LocalJobRunnerを使う）ように記述している点にご注意ください。

以上でGalaxyジョブを分散実行するための環境が出来上がりました！

実際に解析等に活用するためのツールのインストールを行う際は、GalaxyサーバだけでなくSLURM計算ノードのほうにも同様にインストールすることをお忘れなく。 :smile: