サブロウ丸

Sabrou-mal サブロウ丸

主にプログラミングと数学

Docker Swarmを活用したマルチサーバ、マルチコンテナ環境でのMPIプログラム実行

本稿では複数サーバー複数コンテナ環境でOpenmpi を実行する手順について紹介します。

参考:

環境

2つのサーバーA, B を使用し、その中でそれぞれ2つのコンテナを立ち上げます。 合計4つのコンテナを用いてMPI プログラムを実行します。

Docker Swarm

コンテナ間の通信の確立のために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

@Server B でも同様に mpi-network ネットワークを作成。

$ docker network create --driver overlay --attachable mpi-network

コンテナの作成

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 openssh-server vim

RUN mkdir /var/run/sshd
RUN echo 'root:tmp' | chpasswd  # rootユーザのpasswordを設定
RUN echo 'PermitRootLogin yes\nPasswordAuthentication yes' >> /etc/ssh/sshd_config

RUN mkdir /app
WORKDIR app

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)

この際やり方は自由ですが、Server Aでrsa鍵を生成しそれを全てのコンテナで共有して持つように作業しました。

@Server A

1 rsa鍵をコンテナ外で生成し、tmp_a コンテナにコピー。

$ ssh-keygen
...

$ docker cp ~/.ssh/id_rsa tmp_a:/root/.ssh/  # id_rsa は作成した鍵の名前に置き換え
$ docker cp ~/.ssh/id_rsa.pub tmp_a:/root/.ssh/
$ docker exec tmp_a bash -c 'cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys'

2 Server A :: tmp_a → 他のコンテナに同じ鍵を配布。

for name in tmp_b tmp_d tmp_e; do scp ~/.ssh/* $name:/root/.ssh/; done

あとは全てのコンテナペアで認証なしで、コンテナ間でログインできる必要があります。 最初のsshログイン時にyes を打ち込むことを要求されるで、次のように作業。もっと手軽な方法があるかも。。

3 @ServerA::tmp_a コンテナで 自身を含む全てのコンテナに一度ログイン。
4 /root/.ssh/known_hosts を tmp_aから他のコンテナに配布します

for name in tmp_a tmp_b tmp_d tmp_e; do ssh $name; done  # 3
for name in tmp_b tmp_d tmp_e; do scp ~/.ssh/known_hosts $name:/root/.ssh/; done  # 4

これでok。

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 $(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $name); done
10.0.1.2
10.0.1.4

@ Server B

$ for name in tmp_d tmp_e; do echo $(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $name); done
10.0.1.10
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 実行を紹介しました。