Slurm HPCクラスタとKubernetesを同居させてみた（後編）

## はじめに
こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の小林です。

本日のアドベントカレンダーは、HPCジョブスケジューラ`Slurm`とコンテナオーケストレータ`Kubernetes`の[同居クラスタを構築する投稿](https://qiita.com/mk32/items/6b4f9a7e5ae82eb640d9)の後編です。`Singularity`ベースのワークロードを双方から実行可能な環境を構築しきり、サンプルプログラムを実行してみます。

アドベントカレンダーではHPCジョブスケジューラの一つである`PBS`に[関する記事](https://qiita.com/MasafumiTsuyuki/items/acf4b19b54937496e32b)もあるので興味がある方は要チェックです。

## 手順

### 概要
構築手順の概要を示します。本日は`手順2`以降を紹介します。前回の投稿は[こちら](https://qiita.com/mk32/items/6b4f9a7e5ae82eb640d9)です。

1. Slurmクラスタを構築する
    1. マスタ/ワーカ共通準備
    2. Slurmマスタの構築
    3. Slurmワーカの構築
2. <a href='#-2-singularityを導入するマスタワーカ共通'>Singularityを導入する (本日はここから)</a>
    1. <a href='#-21-singularityの導入'>Singularityの導入</a>
    2. <a href='#-22-singularity-crisycriの導入'>Singularity-CRI(Sycri)の導入</a>
3. <a href='#-3-k8sクラスタを構築する'>K8sクラスタを構築する</a>
    1. <a href='#-31-マスタワーカ共通準備'>マスタ/ワーカ共通準備</a>
    2. <a href='#-32-k8sマスタの構築'>K8sマスタの構築</a>
    3. <a href='#-33-k8sワーカの構築'>K8sワーカの構築</a>
    4. <a href='#-34-cniの設定'>CNIの設定</a>


### <a id='2' > 2. Singularityを導入する(マスタ/ワーカ共通)</a>

ここではコンテナランタイムの`Singularity`を導入します。コンテナランタイムと一口に言っても、[高レイヤと低レイヤの２つ](https://medium.com/nttlabs/container-runtime-d3e25189f67a)があります。`Singularity`はコンテナ自体の操作を提供する低レイヤのランタイムを指しますが、ここでは`Kubernetes`などとのコンテナオーケストレータ向けにAPIを提供する高レイヤのランタイムとして`Singularity-CRI`も含めます。

#### <a id="2-1"> 2.1. Singularityの導入</a>
基本的には[公式のインストールページ](https://sylabs.io/guides/3.4/user-guide/installation.html#install-on-linux)を参考にします。執筆当時の最新バージョンは`v3.4.0`です。

執筆当時のGO最新版はv1.14ですが、さしあたり筆者が使い慣れたv1.13を[GOの公式](https://github.com/golang/go/wiki/Ubuntu)を参考にインストールしています。

```
# 依存するツール・ライブラリのインストール
sudo apt-get install -y \
    build-essential \
    libssl-dev \
    uuid-dev \
    libgpgme11-dev \
    squashfs-tools \
    libseccomp-dev \
    wget \
    pkg-config \
    git \
    cryptsetup-bin
# GO1.13のインストール
sudo add-apt-repository ppa:longsleep/golang-backports && \
sudo apt-get update && \
sudo apt-get install -y golang-go
```

次に`Singularity`をインストールします。

```
git clone https://github.com/sylabs/singularity.git && \
     cd singularity && \
     git checkout v3.4.0
./mconfig && \
    make -C ./builddir && \
    sudo make -C ./builddir install
sudo apt-get install -y socat
```

#### <a id="2-2"> 2.2. Singularity-CRI(Sycri)の導入</a>

続いて`Sycri`を[公式のGitHub](https://github.com/sylabs/singularity-cri#quick-start)を参考にインストールします。

```
git clone https://github.com/sylabs/singularity-cri.git && \
   cd singularity-cri && \
   git checkout tags/v1.0.0-beta.5 -b v1.0.0-beta.5 && \
   make && \
   sudo make install
```

`Sycri`サービスはデーモンとして起動させるので[公式ページ](https://sylabs.io/guides/cri/1.0/user-guide/k8s.html#create-singularity-cri-service)を参考にサービスファイルを`/etc/systemd/system/sycri.service`に追加します。

```
[Unit]
Description=Singularity-CRI
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=1
ExecStart=/usr/local/bin/sycri
Environment="PATH=/usr/local/libexec/singularity/bin:/bin:/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"

[Install]
WantedBy=multi-user.target
```

サービスを起動します。

```
sudo systemctl start sycri && sudo systemctl enable sycri
```

実行後、`sycri`サービスが正常起動しているか確認します。

```
sudo systemctl status sycri
```

### <a id="#3"> 3. K8sクラスタを構築する</a>
最後にKubernetesクラスタを構築します。手順は[UbuntuへのK8sインストール記事](https://phoenixnap.com/kb/install-kubernetes-on-ubuntu)の`Step. 3-6`を参考にしています。
ただしこの記事ではコンテナランタイムが`Docker`になっているので[Singularityの公式ページ](https://sylabs.io/guides/cri/1.0/user-guide/k8s.html#modify-kubelet-config)を参考に修正します。


#### <a id="#3-1"> 3.1. マスタ/ワーカ共通準備</a>

`Kubernetes`レポジトリを`apt`に追加します。

```
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add
sudo apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
sudo apt-get install -y kubeadm kubelet kubectl
```

`Kubernetes`の実行に必要なサーバ設定を施します。
下記の`Swapの停止`に関してはサーバ再起動後も設定が維持するために[/etc/fstabを修正する方法](https://www.mirantis.co.jp/blog/how-install-kubernetes-kubeadm/)もあります。

```
# Swapの停止
sudo swapoff -a
# NW周りの設定. lsmodの結果が何も表示されなければmodprobe以下を実行
lsmod | grep br_netfilter
sudo modprobe br_netfilter 
sudo cat /proc/sys/net/bridge/bridge-nf-call-iptables # 1が出力されることを確認
sudo cat /proc/sys/net/bridge/bridge-nf-call-ip6tables # 1が出力されることを確認
sudo sysctl net.ipv4.ip_forward=1
```

コンテナランタイム変更設定を`/var/default/kubelet`に追加します。

```
KUBELET_EXTRA_ARGS=--container-runtime=remote \
 --container-runtime-endpoint=unix:///var/run/singularity.sock \
 --image-service-endpoint=unix:///var/run/singularity.sock
```

#### <a id="#3-2"> 3.2. K8sマスタの構築</a>
Kubernetesマスタサーバ関連のサービスを起動します。執筆者環境では`CRI`とPodネットワークの`Cidr`を指定をする必要がありました。また、`Sycri`サービスが正常に起動していることを確認してください。

```
sudo kubeadm init --cri-socket unix:///var/run/singularity.sock --pod-network-cidr=10.92.0.0/16
```

コマンドが正常に終了すればワーカで実行すべきコマンド例が出るのでメモします。

```
> kubeadm join 192.168.X.Y:6443 --token 111aaa.9999xxxxxxx \
    --discovery-token-ca-cert-hash sha256:XXXXXXXXXXXXXXX
```

Kubectlを利用するためのコンフィグを整備します。これも`kubeadm`が正常終了すると出力されます。

```
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```

試しに`kubectl get svc`などを叩いてエラー以外が帰って来ることを確認します。

#### <a id="#3-3"> 3.3. K8sワーカの構築</a>
マスタで得られた`kubeadm join hogehoge`コマンドに`--cri-socket unix:///var/run/singularity.sock`を加えて実行します。

```
kubeadm join 192.168.X.Y:6443 --token 111aaa.9999xxxxxxx \
    --discovery-token-ca-cert-hash sha256:XXXXXXXXXXXXXXX \
    --cri-socket unix:///var/run/singularity.sock
```

#### <a id="#3-4"> 3.4. CNIの設定</a>
`K8s`では`L2 over L3`ネットワーク構築のためにCNI(今回は[Calico](https://www.projectcalico.org/))を導入する必要があります。CNIを導入することで、現状は`kubectl get pod --all-namespaces`を実行すると`pending`状態の`coredns`も有効になります。

通常はCNI導入は`kubectl`コマンド一発で入る場合が多いのですが、今回の環境ではCNIが利用するいくつかのパスがインストールに伴い作成されずCNIが立ち上がりませんでした。そこで以下のコマンドを先に`マスタ`と`ワーカ`の両方で実行しておきます。

```
sudo mkdir -p /var/lib/cni/networks && \
sudo mkdir -p /ect/cni/net.d && \
sudo mkdir -p /var/lib/calico && \
sudo mkdir -p /run/calico && \
sudo touch /var/lib/calico/nodename
```

また、マスタとワーカの両方で`Hostname`の設定も`/etc/hostname`と`/etc/hosts`でなされているか確認します。
特に`/etc/hosts`では以下のようにします。

```
127.0.0.1    localhost.localdomain localhost
127.0.1.1    YOUR_HOSTNAME # 適宜ホスト名に変更
```

最後に`Calico`をインストールします。

```
kubectl apply -f https://docs.projectcalico.org/v3.8/manifests/calico.yaml
```

`Calico`や`CoreDNS`コンテナが起動しない場合は、`kubectl logs SOME_POD_NAME`や`kubectl describe pod SOME_POD_NAME`で原因を追求して対応します。コンテナイメージが`Pull`できない場合には`Calico`コンテナや`CoreDNS`のlogや各ノードの`Journal`を参照してコンテナ利用するパスの有無や権限を適宜変更します。

以上の手順を実行すると、`Slurm`コマンドである`sinfo`と、`kubernetes`コマンドである`kubectl`が同居した環境を構築できます。

## 使ってみる
### Slurm with Singularity
[このサイト](https://www.rc.fas.harvard.edu/resources/documentation/software/singularity-on-the-cluster/)を参考にSingularityコンテナをSlurmから実行してみましょう。コマンドはマスタサーバから実行しています。

まずは利用可能なSlurmのパーティションを確認します。

```
sinfo
> PARTITION AVAIL  TIMELIMIT  NODES  STATE NODELIST
debug*       up   infinite      1   idle koba-slurm2
```

`debug`という名前の`idle`状態のパーティションがワーカノードに割り当てられていることがわかります。次にSlurmのジョブスクリプト`singularity.sbatch`を定義します。上の参考サイトのスクリプトについて、パーティション`#SBATCH -p`と実行する`singularityコマンド`を改変しています。

```
#!/bin/bash
#SBATCH -J singularity_test
#SBATCH -o singularity_test.out
#SBATCH -e singularity_test.err
#SBATCH -p shared
#SBATCH -t 0-00:30
#SBATCH -N 1
#SBATCH -c 1
#SBATCH --mem=4000

# Singularity command line options
singularity pull --name hello-world.sif shub://vsoch/hello-world
singularity exec hello-world.sif cat /etc/os-release
```

実行してみます。

```
sbatch singularity.sbatch
```

結果をNFSなどに出していない場合、ジョブが実行されたノード上に出るのでワーカノードにログインしてホームディレクトリを見ると`singularity_test.out`と`singularity_test.err`があります。

`Slurm`によるジョブ制御と`Singularity`のコンテナ実行が連携できていることがわかります。

### Kubernetes with Singularity
K8sについては`Sycri`の[ユーザドキュメント 7.1.](https://sylabs.io/guides/cri/1.0/user-guide.pdf)を参考にしてみましょう。
こちらもマスタノードで実行します。

これまでの手順を行うとサーバ上に`Sycri`のGitレポジトリクローンがあると思います。リポジトリの`example/k8s/hello-kubernetes.yaml`を利用します。

```
kubectl apply -f examples/k8s/hello-kuberentes.yaml
kubectl get pod
# > hello-kubernetes-8764bc78f-86t2x   1/1     Running             0          2m10s
# > hello-kubernetes-8764bc78f-nq2pb   1/1     Running             0          2m10s
# > hello-kubernetes-8764bc78f-xjrxx   1/1     Running             0          2m9s
```

`kubectl get svc`でサービス公開されたポートを確認し叩いてみるとサービスが提供できていることがわかります。

```
curl koba-slurm2:SOME_PORT
# > Hello, World!
```

## まとめ

以上の手順で`Slurm`と`Kubernetes`が`Singularity`のワークロード実行可能な環境を構築できました。


現状はこのような実行環境のユースケースは少ないと思いますが、将来的に[HPCWireの記事](https://www.hpcwire.com/2019/09/19/kubernetes-containers-and-hpc/)で期待されているような`新しいAIワークロード`のニーズが生まれるかもしれませんし、
その際には単なる同居ではなくHPCジョブスケジューラとKubernetes双方の`ジョブスケジューリングを統合する機能`が必要になる可能性があります。
`Sylabs`の[GitHub/WLM Operator](https://github.com/sylabs/wlm-operator)とみなすこともできるかもしれませんね。

今後の関連動向についても引き続き情報発信をしていきたいと思います。