7. 複数仮想マシンによるクラスタの作成例

mdx上にデプロイした複数の仮想マシンを使って簡単なクラスタを構築する例について説明します。

7.1. Ansibleとその概要

複数のVMをデプロイしたときに、VMを一台一台手動で設定していくのは現実的ではありません。
そこで複数のマシンを自動で一気に設定してくれるプロビジョニングツールを利用することになります。

ここでは、そうしたプロビジョニングツールのひとつである Ansible を使ってmdx上に複数のVMを展開して設定する例を紹介します。

Ansibleは、OSの中身の設定、たとえばパッケージのインストールや設定ファイルの変更、デーモンの起動など、OSのインストール後に行う作業を自動化するためのツールです。
Ansibleは大量のVMを一気に設定するようなユースケースをはじめさまざまな分野で広く利用されており、Linuxの主要なディストリビューションやmacOSでも実行可能です。

Ansibleを実行するために最低限必要なファイルは、

  • playbook

    設定するマシンの中で実行する処理を記述したYAML形式のファイル

  • inventory

    設定を行う対象のマシンのIPアドレスや付加する情報などを記述したファイル

の2つです。

例えばplaybookとして deploy-jupyter.yaml を用意し、その中にはJupyterlabをデプロイするために必要な処理を記述します。
次にその処理を実行したいVMのIPアドレスを記載したinventoryとして hosts というファイルを用意し、 ansible-playbook -i hosts deploy-jupyter.yaml と打てば、複数のVMにJupyterlabを立ち上げることができます。
Ansibleの特徴のひとつはAgent-lessであることです。
Ansibleでは、 ansible-playbook コマンド (ないし ansible コマンド)を実行して他のホストを設定/制御するホストをControl node、逆にControl nodeから設定/制御されるホスト(この場合はVM)をManaged nodeと呼びます。
Ansibleを実行するためにはControl nodeがManaged nodeへssh(と大抵の場合はsudo)できればよく、事前にManaged nodeに何らかのエージェントソフトウェアを入れる必要はありません。
もちろん、Control nodeにはAnsibleをインストールする必要があります。
                       +---------+
playbook.yaml          |         |
hosts                  | Managed |
+---------+     +----->|  node1  |
|         |     |      |         |
| Control | ssh |      +---------+
|  node   +-----+
|         |     |      +---------+
+---------+     |      |         |
                |      | Managed |
                +----->|  node2  |
                       |         |
                       +---------+
上記の図は、とても簡略化したAnsibleの実行イメージを示しています。
Control nodeに、Managed node1と2のIPアドレスを記載したinventoryファイルを用意し、どのように設定するかを記載したplaybookを用意して ansible-playbook コマンドを実行すると、ssh越しに2台のManaged Nodeが設定されます。

7.2. https://github.com/mdx-jp/machine-configs

machine-configsレポジトリ に、 mdx上で複数のVMによる簡単なクラスタを構築するためのplaybookを用意しています。
ここでは、machine-configsの使い方を説明します。

注釈

現在全てのplaybookは ubuntu server 20.04 テンプレートから作成したVMに対して実行することを想定しています。

クラスタの構築を始めるにあたって、まずはmdx上に複数のVMを作成してください。
仮想マシン利用の流れ を参考に ansible-playbook を実行するVM (ControlNode)を1台と、クラスタになるVM (Managed Node)を必要な台数作成します。
なおControl nodeは、Ansibleがインストールされ、Managed nodeにsshで接続できるホストであれば、mdxのVMである必要はありません。
mdxのVMに割り当てられるIPアドレスは、IPv4はプライベートアドレスですが、IPv6はグローバルアドレスです。
例えば、適切なACLの設定を行えばIPv6疎通性のある手元のホストから直接AnsibleでVMを設定することも可能です。

下の図では、 ansible-playbook を実行する test というノードをubuntu-2004-serverテンプレートからデプロイし、続いてクラスタになる vm1 から vm8 までの8台のVMを、同様にubuntu-2004-serverテンプレートから、VMデプロイ時の仮想マシン名に vm[1-8] と入力することで一度にデプロイしました。

クラスタを構成するためのVMデプロイ例

ACLの設定やssh公開鍵の投入などは、 ネットワーク設定仮想マシン利用の流れ を参照し、利用者自身の環境に合わせて実施してください。

OpenMPIやLustreストレージにRDMAで接続する場合は、ストレージネットワークを SR-IOV で作成してください。

7.3. クラスタの構築: 準備編

7.3.1. Ansibleのインストール

まずは ansible-playbook を実行するVM(上記例では test という名前のVM)にログインし、Ansibleをインストールします (最初にわかりやすさのためにホスト名を変更しています)。
Ansible実行時にはこのホストから各VMにsshすることになります。
そのため、このホストにsshする際はssh-agent (ssh -A)などを用いて、このホストから各VMにmdxuserでsshできるようにしてください。
mdxuser@ubuntu-2004:~$ sudo hostnamectl set-hostname ansible
mdxuser@ubuntu-2004:~$ bash

mdxuser@ansible:~$ sudo apt install ansible
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
cowsay sshpass
The following NEW packages will be installed:
  ansible
  0 upgraded, 1 newly installed, 0 to remove and 17 not upgraded.
Need to get 5794 kB of archives.
After this operation, 58.0 MB of additional disk space will be used.
Get:1 http://jp.archive.ubuntu.com/ubuntu focal/universe amd64 ansible all 2.9.6+dfsg-1 [5794 kB]
Fetched 5794 kB in 1s (4666 kB/s)
Selecting previously unselected package ansible.
(Reading database ... 125879 files and directories currently installed.)
Preparing to unpack .../ansible_2.9.6+dfsg-1_all.deb ...
Unpacking ansible (2.9.6+dfsg-1) ...
Setting up ansible (2.9.6+dfsg-1) ...
Processing triggers for man-db (2.9.1-1) ...

7.3.2. machine-configsレポジトリの取得

次にplaybookが用意されている machine-configs のGitレポジトリをクローンしてそこに移動します。

mdxuser@ansible:~$ git clone https://github.com/mdx-jp/machine-configs
Cloning into 'machine-configs'...
remote: Enumerating objects: 785, done.
remote: Counting objects: 100% (785/785), done.
remote: Compressing objects: 100% (510/510), done.
remote: Total 785 (delta 376), reused 622 (delta 214), pack-reused 0
Receiving objects: 100% (785/785), 119.50 KiB | 9.96 MiB/s, done.
Resolving deltas: 100% (376/376), done.
mdxuser@ansible:~$ cd machine-configs/
mdxuser@ansible:~/machine-configs$ ls
ansible.cfg  mdxcsv2inventory.py  playbook.yml  roles
files        mdxpasswdinit.py     README.md     vars

7.3.3. inventoryファイルの作成

playbookを実行するためには、設定したいVMのアドレスを記載したinventoryファイルが必要です。
machine-configsのレポジトリには、このinventoryファイルを簡単に作成するためのスクリプト mdxcsv2inventory.py が用意されています。
ユーザポータルの [仮想マシン]タブから[コントロール]で、[SELECT MULTIPLE VMS]を選択し、[ACTION]の[CSVダウンロード]をクリックすると、VM一覧で選択したVMに関するIPアドレスなどが記載されたCSVファイルをダウンロードすることができます。
ここからダウンロードしたCSVファイルをAnsibleを実行するVMへ(scpやsftpなどで)持っていきます。

mdxcsv2inventory.py にダウンロードしてきたCSVファイルを与えると、CSVファイルに記載されているVMをManaged Nodeとするinventoryファイルを生成します。

mdxuser@ansible:~/machine-configs$ ./mdxcsv2inventory.py user-portal-vm-info.csv
[all:vars]
ansible_user=mdxuser
ansible_remote_tmp=/tmp/.ansible
ethipv4prefix=10.13.200.0/21
rdmaipv4prefix=10.141.200.0/21
ethipv6prefix=2001:2f8:1041:21e::/64

[default]
10.13.204.85    hostname=vm1 ethipv4=10.13.204.85    rdmaipv4=10.141.200.147
10.13.204.83    hostname=vm2 ethipv4=10.13.204.83    rdmaipv4=10.141.200.146
10.13.204.89    hostname=vm3 ethipv4=10.13.204.89    rdmaipv4=10.141.204.70
10.13.200.158   hostname=vm4 ethipv4=10.13.200.158   rdmaipv4=10.141.204.63
10.13.204.90    hostname=vm5 ethipv4=10.13.204.90    rdmaipv4=10.141.200.149
10.13.204.87    hostname=vm6 ethipv4=10.13.204.87    rdmaipv4=10.141.200.150
10.13.204.84    hostname=vm7 ethipv4=10.13.204.84    rdmaipv4=10.141.204.64
10.13.204.86    hostname=vm8 ethipv4=10.13.204.86    rdmaipv4=10.141.204.67
[default] という表記はグループを示しています。Ansibleでは、inventoryでホストをグループにまとめ、playbookの中ではグループに対してどのような処理を行うかを記述します。
mdxcsv2inventory.py は全てのVMのアドレスを記載したグループとしてこの [default] を作成しています。
後で利用するために、この出力結果を hosts.ini というファイルに保存しておきます。
mdxuser@ansible:~/machine-configs$ ./mdxcsv2inventory.py user-portal-vm-info.csv > hosts.ini

7.3.4. Ansibleを実行する前の事前準備

mdxで提供されるubuntuの仮想マシンテンプレートは、mdxuserの最初のログイン時にmdxuserのパスワードを設定する必要があります。
Ansibleはsshした先で設定変更などを行うため、このパスワード設定が完了していないと、Ansibleの実行が失敗します。
そこでmachine-configsに含まれる mdxpasswordinit.py を使って、inventoryファイルの [default] グループのホストに対して一気に初期パスワードを設定します。
mdxuser@ansible:~/machine-configs$ ./mdxpasswdinit.py ./hosts.ini
Target hosts: 10.13.204.85, 10.13.204.83, 10.13.204.89, 10.13.200.158, 10.13.204.90, 10.13.204.87, 10.13.204.84, 10.13.204.86
New Password:
Retype New Password:
initializing the first password...
10.13.204.85: Success
10.13.204.83: Success
10.13.204.89: Success
10.13.200.158: Success
10.13.204.90: Success
10.13.204.87: Success
10.13.204.84: Success
10.13.204.86: Success

この操作はVMに対して一回だけ実行すれば大丈夫です。

7.4. Playbookの準備と実行

現在machine-configsが提供するVMへの操作は、下記の通りです。

Role

Desciprition

common

ホスト名や/etc/hostsを設定し、指定したパッケージをインストールする

desktop_common

xrdpをインストールする

nfs_server

VMをNFSサーバにし、/homeをexportする

nfs_client

NFS越しに/homeをマウントする

ldap_server

VMをLDAPサーバにし、LDAPアカウントを作成する

ldap_client

VMをLDAPクライアントにし、LDAPサーバを参照するように設定する

jupyter

jupyter labをインストールし、デーモンとして起動する

reverse_proxy

VMをリバースプロキシにし、特定ポートへのアクセスを他のVMの特定ポートに転送する

mpi

OpenMPIを使えるように設定する

Ansibleでは、Managed nodeに対して実行する一連の処理をtaskと呼び、そのtaskをひとまとめにしたものをRoleと呼びます。
machine-configsには上記のRoleが含まれています。
machine-configsの playbook.yml が、上記の全てを適用するPlaybookです。
この playbook.yml でホストに対してRoleを適用するブロックは下記のようになっています。
- name: setup NFS server
  hosts: nfsserver
  roles:
  - nfs_server
これは、 nfsserver というホストのグループに対して、 nfs_server のRoleを適用する、という記述です。
mdxcsv2inventory.py はデフォルトでは [default] というグループしか作成しません。
上記を実行するためには、VMが1台所属する nfsserver というグループを作成しなければなりません。
これには、直接inventoryファイルを編集して [nfsserver] というセクションを追加しても大丈夫ですが、下記のように mdxcsv2inventory.py を使用してグループを作成することもできます。
mdxuser@ansible:~/machine-configs$ ./mdxcsv2inventory.py user-portal-vm-info.csv -g nfsserver vm1
[all:vars]
ansible_user=mdxuser
ansible_remote_tmp=/tmp/.ansible
ethipv4prefix=10.13.200.0/21
rdmaipv4prefix=10.141.200.0/21
ethipv6prefix=2001:2f8:1041:21e::/64

[default]
10.13.204.85    hostname=vm1 ethipv4=10.13.204.85    rdmaipv4=10.141.200.147
10.13.204.83    hostname=vm2 ethipv4=10.13.204.83    rdmaipv4=10.141.200.146
10.13.204.89    hostname=vm3 ethipv4=10.13.204.89    rdmaipv4=10.141.204.70
10.13.200.158   hostname=vm4 ethipv4=10.13.200.158   rdmaipv4=10.141.204.63
10.13.204.90    hostname=vm5 ethipv4=10.13.204.90    rdmaipv4=10.141.200.149
10.13.204.87    hostname=vm6 ethipv4=10.13.204.87    rdmaipv4=10.141.200.150
10.13.204.84    hostname=vm7 ethipv4=10.13.204.84    rdmaipv4=10.141.204.64
10.13.204.86    hostname=vm8 ethipv4=10.13.204.86    rdmaipv4=10.141.204.67

[nfsserver]
# group with regexp 'vm1'
10.13.204.85    hostname=vm1 ethipv4=10.13.204.85    rdmaipv4=10.141.200.147

mdxuser@ansible:~/machine-configs$ ./mdxcsv2inventory.py user-portal-vm-info.csv -g nfsserver vm1 > hosts.ini
mdxcsv2inventory.py-g [GROUPNAME] [VMNAME] オプションを使うことで、指定したVMの所属する任意の名前のホストグループを作成することができます。
なお [VMNAME] の部分は正規表現になっているので、複数のVMが所属するグループを作成することもできます。

[nfsserver] の他にも playbook.yml にあるように、LDAPサーバにするには [ldapserver] グループを、リバースプロキシにするには [reverproxy] グループを上記の手順で作成してください。

あとは playbook.yml 自体を編集して、デプロイしたい環境に合わせて、不必要なRoleの適用箇所をコメントアウトしてください。
例えばubuntu serverを使うのであれば、 desktop_common は必要無いかもしれません。

inventoryの作成と playbook.yml の編集が終わったら、下記のコマンドをすると、Ansibleが全VMに設定を実施します。

mdxuser@ansible:~/machine-configs$ ansible-playbook -i hosts.ini playbook.yml

7.5. machine-configsが提供するRole

ここでは、machine-configsに用意されているRoleについて説明します。

7.5.1. common

commonは、VMに対してホスト名を設定し、/etc/hostsを設定し、指定したパッケージをインストールします。
ホスト名や/etc/hostsに記載される名前は、inventoryの hostname などの変数のものです。
また vars/common.yml を編集することで、実行時にインストールするパッケージを追加することが出来ます。

7.5.2. desktop_common

desktop_commonはxrdpをインストールします。

7.5.3. nfs_server

nfs_serverは、VMにNFSサーバをインストールし、/homeをexportします。
このとき、mdxuserのホームディレクトリは/home.local/mdxuserに移動されます。

7.5.4. nfs_client

nfs_clientは、VMにNFSをインストールし、NFSサーバから/homeをマウントします。
このとき、mdxuserのホームディレクトリは/home.local/mdxuserに移動されます。

マウントするNFSサーバは、 [nfsserver] グループの先頭にあるVMになります。

7.5.5. ldap_server

ldap_serverは、VMをLDAPサーバにし、指定されたグループやユーザを作成します。
LDAPドメインやパスワードなどは、 vars/ldap.yml を編集することで変更できます。
LDAPグループやLDAPユーザを作成するには、 machine-configs/files ディレクトリ配下に ldap_groups.csvldap_users.csv というファイルを作成してください。
このCSVファイルのサンプルとして machine-configs/fils ディレクトリに ldap_groups.csv.inldap_users.csv.in が用意してあります。
files/README.md を見つつ、作成したいLDAPグループやLDAPユーザを追加してください。

7.5.6. ldap_client

ldap_clientは、VMにLDAPをインストールし、LDAPクライアントとしてLDAPサーバを参照するようにします。

参照するLDAPサーバは、 [ldapserver] グループの先頭にあるVMになります。

7.5.7. jupyter

jupyterは、jupyter labをインストールし、デーモンプロセスとして実行します。
デーモンプロセスはmdxuserのホームディレクトリにあるvirtualenv環境で起動し、8888番ポートをListenします。

7.5.8. reverse_proxy

reverse_proxyは、Nginxをインストールし、リバースプロキシとして設定します。
reverse_proxyの動作は、 [default] グループのVMについて、自身の 8000 + n ポートへのアクセスを各VMの8888番ポートに転送します。
jupyter Roleと組み合わせることで、下記のようなクラスタを構築することができます。
                                   User
                                     |
                                     v
                               mdx Global IPv4
                                  Address
                                     |
                                     |
                       +---------+   |
                       |  Nginx  |   |
                       |   (VM)  |   |
                       +----+----+   |
                            |  ^     |
                            |  +-----+
                            |              Ethernet Network (Private Address)
       +--------------------+------------------+------------------+
       |                    |                  |                  |
       v                    v                  v                  v
+--------------+   +--------------+   +--------------+   +--------------+
|  Jupyterlab  |   |  Jupyterlab  |   |  Jupyterlab  |   |  Jupyterlab  |  ...
|     (VM1)    |   |     (VM2)    |   |     (VM3)    |   |     (VM4)    |
+--------------+   +--------------+   +--------------+   +--------------+
mdxでVMに付与されるIPv4アドレスはプライベートアドレスであり、インターネット越しに直接アクセスすることはできません。
そこで reverse_proxy Roleを適用したVMに DNAT を使ってグローバルIPv4アドレスをマッピングすることで、外部から各VMのjupyter labにアクセスすることができるようになります。

DNATをマッピングしたら、ブラウザで http://[DNATのアドレス]:8001 にアクセスすると、上の図のVM1のJupyterlabに、 http://[DNATのアドレス]:8002 にアクセスすれば、VM2のJupyterlabにアクセスすることができます。

なお、各Jupyterlabは認証無しで起動するので、リバースプロキシになるNginxのVMには適切な ACL を設定してください。

vars/reverse_proxy.yml を編集することで、バックエンドになるVMのグループ(デフォルトは [default])やプロキシする先のポート番号(デフォルトは 8888)を変更することができます。

7.5.9. mpi

mpiは、/etc/bash.bashrc にOpenMPIへのパスを設定します。
VMにインストールされているOpenMPIは、OFEDと一緒にインストールされたものです。