Docker Swarmを活用したマルチサーバ、マルチコンテナ環境でのMPIプログラム実行
本稿では複数サーバー複数コンテナ環境でOpenmpi を実行する手順について紹介します。本稿ではコンテナ接続のネットワークとしてとしてDocker Swarmによるオーバーレイネットワークを利用します。
- 構築する環境
- Docker Swarm: コンテナ間の通信確立
- docker network の作成
- コンテナの作成
- ssh鍵の共有
- MPI プログラムの実行
- 単一コンテナでの実行
- 複数コンテナでの実行
- まとめ
参考:
構築する環境
2つの異なるサーバーA、B を使用し、それぞれ2つのコンテナを立ち上げ、合計4つのコンテナでMPI プログラムを実行します。

Docker Swarm: コンテナ間の通信確立
docker network を単純に利用すれば同一サーバー内のコンテナ間で通信が行えるようになりますが、Docker Swarmを利用すればそれを複数サーバー間に拡張できます。
Docker Swarm は Docker のオーケストレーションツールでKubernetes (K8s) のようなもの。SwarmはManager(クラスタ全体の状態の管理)とWorker Node(コンテナを実行)で構成されます。
swarm作成前のdocker network
$ docker network ls NETWORK ID NAME DRIVER SCOPE 8ba95d06e22b bridge bridge local 1a107fbc2ad7 host host local 79a4843011e6 none null local
swarmの初期化
@Server Aでdocker swarm init を実行。server A がマネージャーノードになります。
$ docker swarm init Swarm initialized: current node (favqrqpma9leyba34fgkay0uo) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token <token> <ip::addr> To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
docker network にもoverlayネットワークとしてingress が追加されています。これにより異なるホスト上にあるコンテナ間での通信を可能になり、クラスタ内の任意のノードが、同じサービスの異なるタスク間でトラフィックをルーティングできます。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
8ba95d06e22b bridge bridge local
1a107fbc2ad7 host host local
+ glztbyqxbm5p ingress overlay swarm
79a4843011e6 none null local
@Server B にて、docker swarm init の実行時に表示されたコマンドを貼り付けると、 server B がworker ノードとして追加されます。
$ docker swarm join --token <token> <ip::addr> This node joined a swarm as a worker.
@Server A (swarm manager) でworkerの状態を確認できます。
$ docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION favqrqpma9leyba34fgkay0uo * xxx Ready Active Leader 24.0.1 ama6yi68spcjzo0sx4n8crwu7 yyy Ready Active 24.0.1
docker network の作成
@Server A でオーバーレイネットワーク mpi-network を作成します。Docker networkは、コンテナ間またはコンテナと外部との通信を管理するための仮想ネットワークです。
--driver overlay: ネットワークのドライバタイプを指定。overlayドライバは、異なるDockerホストにまたがるコンテナ間でネットワークを構築するために使われる--attachable:overlayネットワークに任意の単独コンテナのコンテナ(Swarmサービスに属さないコンテナ)も接続することが可能になる。通常、overlayネットワークは Swarm モードで動作するサービスのコンテナに限定される
$ docker network create --driver overlay --attachable mpi-network
コンテナの作成
事前準備
Dockerfileがあるディレクトリで次を実行して鍵を作成しておく。
mkdir ssh ssh-keygen -t rsa -N "" -f ./ssh/id_rsa
Dockerfile
FROM ubuntu:20.04 RUN apt update && apt upgrade RUN DEBIAN_FRONTEND=noninteractive TZ=Asia/Tokyo apt install -y tzdata build-essential libopenmpi-dev RUN apt install -y openssh-server sshpass COPY ./ssh /root/.ssh # 先ほど生成した鍵をコピー RUN mkdir /var/run/sshd RUN echo 'root:tmp' | chpasswd # rootユーザのパスワードを設定 RUN echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config RUN echo 'PasswordAuthentication yes' >> /etc/ssh/sshd_config CMD ["/usr/sbin/sshd", "-D"]
コンテナの起動
@Server A --net で先ほど作成したmpi-network を指定します。
$ docker build -t tmp . $ docker run -itd --net=mpi-network --name tmp_a tmp $ docker run -itd --net=mpi-network --name tmp_b tmp
docker ps でコンテナが立ち上がっているのが見えるはず。例えば次のコマンドでtmp_a コンテナ内に入ることができます。
docker exec -it tmp_a /bin/bash
@Server B でも同様
$ docker build -t tmp . $ docker run -itd --net=mpi-network --name tmp_d tmp $ docker run -itd --net=mpi-network --name tmp_e tmp
ssh鍵の共有
MPIを複数ノードや複数コンテナで実行するには認証なしで相互にログインできる状態にしておく必要があります。
この後一番陥りやすいのが相互にsshする環境の構築である。 複数ノードの場合「ノード間はssh-keyでパスワードなしでssh可」「dockerに入るにはパスワードなしで」を実現しなければならず、通信できない場合にOpenMPIが無反応だったりしてわからんってなるのでちゃんとやる。 (引用: https://vaaaaaanquish.hatenablog.com/entry/2018/09/17/231640)
この際やり方は自由ですが、今回はこのようにしています。まず、上のDockerfileでローカルに生成した鍵をイメージにコピー。
RUN apt install -y openssh-server sshpass COPY ./ssh /root/.ssh # 先ほど生成した鍵をコピー
以下の Python プログラムを実行します。これにより、対象の接続先(他のコンテナ)の ~/.ssh/authorized_keys に自分の公開鍵を登録すること、すべてのコンテナが他のすべてのコンテナに対してパスワードレスで ssh ログインできるように設定されます。
import subprocess containers = [ # host, # name ("ServerA", "tmp_a"), ("ServerA", "tmp_b"), ("ServerB", "tmp_c"), ("ServerB", "tmp_d"), ] for base_host, base_name in containers: for _, other_name in containers: command = f"docker exec -i {base_name} sshpass -p 'tmp' ssh-copy-id -o StrictHostKeyChecking=no {other_name}" print(f"Running {command} on {base_name} in {base_host}") subprocess.run(["ssh", base_host, command])
MPI プログラムの実行
最後に簡単なMPIプログラムを実行してみます。
テストプログラム
tmp.cpp
#include <mpi.h> #include <iostream> #include <cstdlib> #include <unistd.h> // For gethostname function #include <vector> int main(int argc, char *argv[]) { MPI_Init(&argc, &argv); int num_procs; int my_rank; MPI_Comm_size(MPI_COMM_WORLD, &num_procs); MPI_Comm_rank(MPI_COMM_WORLD, &my_rank); // ホスト名を取得するためのバッファを用意 std::vector<char> hostname(1024); // バッファサイズは1024文字 // gethostname関数を呼び出し、結果をhostnameに格納 gethostname(hostname.data(), hostname.size()); // プロセス数、ランク、ホスト名を出力 std::cout << "Size: " << num_procs << ", Rank: " << my_rank << ", Host: " << hostname.data() << std::endl; MPI_Finalize(); return EXIT_SUCCESS; }
コンパイル
mpicxx tmp.cpp -o tmp
単一コンテナでの実行
まずは、そのまま
$ ./tmp Size: 1, Rank: 0, Host: 2728c86d3bbb
ホスト内に2プロセス並列。
$ mpirun -n 2 --allow-run-as-root ./a.out Size: 2, Rank: 0, Host: 2728c86d3bbb Size: 2, Rank: 1, Host: 2728c86d3bbb
問題なく実行できていますね。
複数コンテナでの実行
ホストファイルの作成
ホストファイルの作成のためにコンテナのidアドレスを取得します。docker inspect で調べることができる。
@ Server A
$ for name in tmp_a tmp_b; do echo $name $(docker inspect -f '{{(index .NetworkSettings.Networks "mpi-network").IPAddress}}' $name); done tmp_a 10.0.1.2 tmp_b 10.0.1.4
@ Server B
$ for name in tmp_a tmp_b; do echo $name $(docker inspect -f '{{(index .NetworkSettings.Networks "mpi-network").IPAddress}}' $name); done tmp_d 10.0.1.10 tmp_e 10.0.1.12
@ServerA::tmp_a /app/hostfile
10.0.1.2 slots=2 10.0.1.4 slots=2 10.0.1.10 slots=2 10.0.1.12 slots=2
バイナリの共有
忘れがちなバイナリの共有。同じ箇所に置く必要がある。
for name in tmp_b tmp_d tmp_e; do scp ./tmp $name:/app/; done
実行
root@2728c86d3bbb:/app# mpirun -n 8 --allow-run-as-root --hostfile ./hostfile ./a.out Size: 8, Rank: 0, Host: 2728c86d3bbb Size: 8, Rank: 1, Host: 2728c86d3bbb Size: 8, Rank: 3, Host: f3f5691e9308 Size: 8, Rank: 6, Host: d2dcc0ada683 Size: 8, Rank: 2, Host: f3f5691e9308 Size: 8, Rank: 4, Host: 9307f761eb47 Size: 8, Rank: 7, Host: d2dcc0ada683 Size: 8, Rank: 5, Host: 9307f761eb47
無事に実行できました。
まとめ
Docker Swarm を用いたマルチサーバ、マルチコンテナ間のMPI 実行を紹介しました。
