GPU搭載Azure Arc enabled KubernetesをAzure Machine Learningで利用する(1) Kubernetes設定編

# はじめに
Azure Advent Calender 2022の10日目の枠が空いていたので埋めさせてもらいました。

https://qiita.com/advent-calendar/2022/azure

今年の秋ごろNVIDIAのGPUを搭載の自作PCを作りました。

https://twitter.com/ussvgr/status/1568572518386237442

作ったはいいものの特に用途もなく持て余してる状態でした。  
このマシンを有効活用すべく Azure Machine Learningのコンピューティングリソースとして利用できるようにしたいと思いつきました。  
本記事はそのセットアップの履歴をメモしたものです。

# 環境
ハードウェアのスペックは以下のとおりです。

- CPU: Core(TM) i3-12100F
- MEM: 32GB
- GPU: NVIDIA GeForce RTX3050 8GB

ソフトウェアとしては以下のバージョンで構成しました。
Ubuntuはサーバー版ではなくデスクトップ版をMinimal installしたものを利用しています。  

- Ubuntu: 22.04.1
- NVIDIA Driver: 525.60.11
- Containerd: 1.6.12
- Kubernetes: 1.25.5

# 設定
ここから環境の設定手順を記していきます。

## 1. NVIDIAドライバーのインストール
まずはGPUのドライバーをインストールします。  
以下のコマンドを実行すると、

```sh
ubuntu-drivers devices
``` 

このようにインストール可能なドライバー一覧が表示されます。

```sh
== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias : pci:v000010DEd00002507sv00001043sd00008876bc03sc00i00
vendor   : NVIDIA Corporation
driver   : nvidia-driver-515-open - distro non-free
driver   : nvidia-driver-510 - distro non-free
driver   : nvidia-driver-525-open - distro non-free recommended
driver   : nvidia-driver-515 - distro non-free
driver   : nvidia-driver-470 - distro non-free
driver   : nvidia-driver-515-server - distro non-free
driver   : nvidia-driver-525 - distro non-free
driver   : nvidia-driver-470-server - distro non-free
driver   : xserver-xorg-video-nouveau - distro free builtin
```

今回は `nvidia-driver-525` をインストールしました。

```sh
sudo apt install nvidia-driver-525
```

ドライバーの読み込み直しのためにOSごと再起動します。

```sh
sudo reboot
```

`nvidia` のモジュールを認識していることを確認します。

```sh
lsmod | grep nvidia
```

```sh
nvidia_uvm           1363968  0
nvidia_drm             69632  3
nvidia_modeset       1208320  5 nvidia_drm
nvidia              56127488  190 nvidia_uvm,nvidia_modeset
drm_kms_helper        311296  1 nvidia_drm
drm                   622592  7 drm_kms_helper,nvidia,nvidia_drm
```

## 2. Containerdのインストール
続いてContainerdをインストールします。    
このあたりのドキュメントを参考にしつつ進めます。

https://kubernetes.io/ja/docs/setup/production-environment/container-runtimes/#containerd

https://docs.docker.com/engine/install/ubuntu/

Swapを無効化します。

```sh
sudo swapoff -a

# /etc/fstabからswapfileのマウント行を削除
sudo vi /etc/fstab
```

必要なモジュールを読み込みます。  

```sh
sudo modprobe overlay
sudo modprobe br_netfilter
sudo modprobe vxlan
```

再起動後に自動でモジュールを読み込みするために設定ファイルも書いておきましょう。

```/etc/modules-load.d/containerd.conf
overlay
br_netfilter
vxlan
```

カーネルパラメータの設定を行います。  
下記の設定ファイルを作って、

```/etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables  = 1
net.ipv4.ip_forward                 = 1
net.bridge.bridge-nf-call-ip6tables = 1
```

コマンドで反映させます。

```sh
sudo sysctl --system
```

Containerdをインストールします。

```sh
sudo apt install ca-certificates curl gnupg lsb-release

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update && sudo apt install -y containerd.io
```

## 3. NVIDIA Container Toolkitのインストール
次にNVIDIA Container Toolkitのインストールを行います。  
以下のドキュメントを参考にしつつ、一部変更しながら進めました。

https://docs.nvidia.com/datacenter/cloud-native/kubernetes/install-k8s.html

NVIDIAのリポジトリを追加し、`nvidia-container-toolkit`パッケージをインストールします。  

```sh
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg && \
  curl -s -L https://nvidia.github.io/libnvidia-container/ubuntu22.04/libnvidia-container.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
      sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list

sudo apt update && sudo apt install -y nvidia-container-toolkit
```

ContainerdがNVIDIA Container Toolkitを利用するように設定します。  
まずは前段でインストールしたContainerdのコマンドで、デフォルトの設定ファイルを生成します。

```sh
containerd config default | sudo tee /etc/containerd/config.toml
```

Containerdのランタイムとして `nvidia` を使うように編集していきます。
編集後ファイルとの差分をパッチ形式で用意しました。

```containerd-config.patch
--- config.toml 2022-12-15 01:52:36.050473142 +0900
+++ /etc/containerd/config.toml 2022-12-15 01:56:06.024187694 +0900
@@ -76,7 +76,7 @@
       max_conf_num = 1

     [plugins."io.containerd.grpc.v1.cri".containerd]
-      default_runtime_name = "runc"
+      default_runtime_name = "nvidia"
       disable_snapshot_annotations = true
       discard_unpacked_layers = false
       ignore_rdt_not_enabled_errors = false
@@ -122,7 +122,17 @@
             NoPivotRoot = false
             Root = ""
             ShimCgroup = ""
-            SystemdCgroup = false
+            SystemdCgroup = true
+
+        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia]
+          privileged_without_host_devices = false
+          runtime_engine = ""
+          runtime_root = ""
+          runtime_type = "io.containerd.runc.v2"
+
+          [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
+            BinaryName = "/usr/bin/nvidia-container-runtime"
+            SystemdCgroup = true

       [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
         base_runtime_spec = ""
```

以下のコマンドで適用します。

```sh
sudo patch /etc/containerd/config.toml containerd-config.patch
```

Containerdを再起動しましょう。

```sh
sudo systemctl restart containerd
```

再起動後、テスト用のイメージを起動して動作確認します。  

```sh
sudo ctr image pull docker.io/nvidia/cuda:11.6.2-base-ubuntu20.04
sudo ctr run --rm -t \
    --runc-binary=/usr/bin/nvidia-container-runtime \
    --env NVIDIA_VISIBLE_DEVICES=all \
    docker.io/nvidia/cuda:11.6.2-base-ubuntu20.04 \
    cuda-11.6.2-base-ubuntu20.04 nvidia-smi
```

以下のように `Driver Version` と `CUDA Version` が表示されていればOKです。  

```sh
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 525.60.11    Driver Version: 525.60.11    CUDA Version: 12.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ...  Off  | 00000000:01:00.0 Off |                  N/A |
| 42%   25C    P8     6W / 130W |     33MiB /  8192MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
```

## 4. Kubernetesクラスターのセットアップ

Kubernetesクラスターのセットアップを行います。  
まずはパッケージのリポジトリを追加します。GPGファイルを取得して配置し、  

```sh
curl -o kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg
sudo mv kubernetes-archive-keyring.gpg /usr/share/keyrings/kubernetes-archive-keyring.gpg
```

リポジトリのlistファイルを作成します。

```/etc/apt/sources.list.d/kubernetes.list
deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main
```

Kubernetes関連パッケージをインストールします。

```sh
sudo apt update
sudo apt install kubelet=1.25.5-00 kubeadm=1.25.5-00 kubectl=1.25.5-00
sudo apt-mark hold kubelet kubeadm kubectl
```

`kubeadm` でクラスターのセットアップを行います。

```sh
sudo kubeadm init
```

一般ユーザーで`kubectl`コマンドを使えるように設定ファイルをコピーします。  

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

`Helm` をインストールします。

```sh
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt update
sudo apt install helm
```

今回はCNIとしてCiliumを利用しました。Helmを使ってインストールします。  

```sh
helm repo add cilium https://helm.cilium.io/

helm install cilium cilium/cilium --version 1.12.4 \
  --namespace kube-system \
  --set operator.replicas=1
```

今回のクラスターは1台のみの構成のため、コントロールプレーンとワーカーは兼用です。  
コントロールプレーンロールのノードにもPodをスケジュールできるようにuntaintします。  

```sh
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
```

## 5. Azure Arcへの接続
KubernetesクラスターをAzure Arcに接続していきます。

https://learn.microsoft.com/ja-jp/azure/azure-arc/kubernetes/quickstart-connect-cluster?tabs=azure-cli

Azure CLIを使ってセットアップします。

```sh
# CLIに拡張機能をインストール
az extension add --name connectedk8s

# サブスクリプションにプロバイダーを登録
az provider register --namespace Microsoft.Kubernetes
az provider register --namespace Microsoft.KubernetesConfiguration
az provider register --namespace Microsoft.ExtendedLocation

# 登録完了を確認
az provider show -n Microsoft.Kubernetes -o table
az provider show -n Microsoft.KubernetesConfiguration -o table
az provider show -n Microsoft.ExtendedLocation -o table

# リソースグループを作成
az group create -n AzureML -l japaneast

# KubernetesクラスターをAzure Arcに接続
az connectedk8s connect -n GPUCluster -g AzureML
```

コマンドが正常に完了し、ポータル上での状態表示が以下のように `Connected` になっていればOKです。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274613/087fb49e-88c7-4e83-1ffa-dfa8e6beacf4.png)

# おわりに
分量が多くなってしまったので、GPU搭載KubernetesクラスターのAzure Arc接続までで一区切りとします。  
別記事にてAzure Machine LearningでのKubernetesクラスター利用について紹介します。後半に続く（予定）