KubernetesでGPUクラスタ構築 〜 初めの一歩

はじめに

約1ヵ月前に、この記事でkubernetesを使ってGPUクラスタを構築する記事を書いた。この時はGPUポッドがPending状態で動作しなかった。その後、とある方の助言もあって何とか動くようになったので、ここにまとめる。

自分の環境では、あるノードを起動しないとGPUポッドが立ち上がらないという問題もあるし、更にノード内のGPU指定、更にノードの指定など、想定していることも出来ていないので、「はじめの一歩」とした。

情報源

  1. [NVIDIA Docker(NVIDIA Container Toolkit)からnvidia-container-runtime + containerdに移行するために知っておくべきこと] このページをとなる方から教えてもらって、Pending状態のGPUポッドが動作するようになった。

  2. NVIDIA/k8s-device-plugin NVIDIAのkubernetesに関するdevice pluginのページ。

  3. NVIDIA Kubernetes Device Plugin このページで、最新版のdevice pluginの情報を得た。

結論

以下のまとめは、自分の環境で色々と試した限りのことなので、各々の環境で、以下が当てはまることは保証の限りではない。

  • Step 1: Install a Container Engineの手順中の/etc/containerd/config.tomlの「CsytemdCgroup = false」はfalseのままでtrueに変更しない。
  • NVIDIA GPU Operatorの手順では、上手くいかなかった。
  • Step 4: Setup NVIDIA Software の手順を変更してインストールした。具体的には、Helmを使わず、「kubectl apply -f」でdevice pluginを組み込んだ。なお、最新版(v0.12.3)で動作した。

インストール操作

Step 1: container Engineをインストール

(1) 必要なパッケージをインストール
sudo apt-get update\
> && sudo apt-get install -y apt-transport-https \
> ca-certificates curl software-properties-common

自分の環境では、全て最新バージョンだった。

(2)overlay, br_netfilter(カーネル)モジュールをロードするように設定
$ cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
> overlay
> br_netfilter
> EOF
$ sudo modprobe overlay \
>    && sudo modprobe br_netfilter
(3)sysctlパラメータをconfファイルに設定
$ cat <<EOF | sudo tee /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
> EOF

$ sudo sysctl --system
(4)Dockerリポジトリを設定
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key --keyring /etc/apt/trusted.gpg.d/docker.gpg add -

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
>    $(lsb_release -cs) \
>    stable"
(5)containerdをインストール
$ sudo apt-get update \
>    && sudo apt-get install -y containerd.io

自分の環境では、containerd.ioは最新バージョン(1.6.7-1)がインストール済だった。

(6)containerdのデフォルトパラメータをconfig.tomlを(作成し)設定
$ sudo mkdir -p /etc/containerd \
>    && sudo containerd config default | sudo tee /etc/containerd/config.toml

既にcontainerd.ioがインストール済だったので、/etc/containerdディレクトリ、config.tomlは既に存在していた。config.tomlはリネームして保存しておいた。

(7) (containerdが)systemd cgroup driverを使うように、config.tomlを変更

結論で述べたように「SystemdCgroup = false」はそのまま。ここでは、/etc/containerd/config.tomlへの変更は加えない。

(8) containerdデーモンを再起動
$ sudo systemctl restart containerd

Step 2: Kubernetesコンポーネントをインストール

(1) 必要なパッケージをインストール
$ sudo apt-get update \
>    && sudo apt-get install -y apt-transport-https curl

自分の環境では最新版がインストールされていた。

(2) リポジトリキーを追加
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
(3) リポジトリを追加
$ cat <<EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list
> deb https://apt.kubernetes.io/ kubernetes-xenial main
> EOF
(4) kubeletをインストール
$ sudo apt-get update \
>    && sudo apt-get install -y -q kubelet kubectl kubeadm
(5) Noteの1:Kuberlet 用にcgroupドライバーを設定

NVIDIAのドキュメントでは、Noteの項番1の箇所。

この時点で既に、/etc/systemd/system/kuberlet.service.d配下に10-kubeadm.confが既に存在。

$ sudo cat << EOF | sudo tee  /etc/systemd/system/kubelet.service.d/0-containerd.conf
> [Service]
> Environment="KUBELET_EXTRA_ARGS=--container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock --cgroup-driver='systemd'"
> EOF
(6) Noteの2:kubeletを再起動
$ sudo systemctl daemon-reload \
>    && sudo systemctl restart kubelet
(7) swapを無効化
$ swapon --show
NAME      TYPE SIZE USED PRIO
/swapfile file   2G   0B   -2
$ sudo swapoff -a
$ swapon --show
$

上記の操作は一時的なもので、サーバを再起動すると再びswapが有効な状態。 永続的に無効化するためには、/etc/fstabの「swap」を含むの行の行頭に#を挿入し、無効化する。 (自分の環境では、/swapfileから始まる行)

(8) kubeadm initを実行
$ sudo kubeadm init --pod-network-cidr=192.168.0.0/16
[init] Using Kubernetes version: v1.24.3
・・・省略・・・
Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.11.3:6443 --token 7ffwr1.xm119vzqvmhqgevl \
	--discovery-token-ca-cert-hash sha256:5d2f3065e38020b668ba1b766d95aea197182e35143511db7062f247f12c81d3 
$

最後の「kubeadm join … sha256…」の部分はメモっておくこと。worker nodeとするワークステーションで、次のように実行することで、クラスタを作成できる。

$ sudo kubeadm join 192.168.11.3:6443 --token 7ffwr1.xm119vzqvmhqgevl \
> --discovery-token-ca-cert-hash sha256:5d2f3065e38020b668ba1b766d95aea197182e35143511db7062f247f12c81d3 
[preflight] Running pre-flight checks
・・・省略・・・
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
(9) 認証用ファイルを$HOME配下にコピー
$ mkdir -p $HOME/.kube \
>    && sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config \
>    && sudo chown $(id -u):$(id -g) $HOME/.kube/config

Step 3: ネットワークを設定

(1) Calicoでネットワークを設定
$ kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
(2) masterにもworker役を割当

NVIDIAドキュメントに「最も単純化したsingle-nodeクラスターに、GPU Podをスケジューリングできる」とある通り、master(control plane) nodeにもPodをスケジューリングできるようになる。

$ kubectl taint nodes --all node-role.kubernetes.io/master-

自分の環境では、master nodeにはPodをスケジューリングさせたくなかったので、これは実行していない。

ここまでで、master(control plane) node(kubeadm init操作)とworker node(kubeadm join操作)を各々の1つ構築した状態となる。自分の環境でのnodeの状態は次のとおり。

$ kubectl get nodes
NAME     STATUS   ROLES           AGE     VERSION
europe   Ready    control-plane   2m57s   v1.25.0
saisei   Ready    <none>          21s     v1.25.0

前の記事では、更にもう1台をクラスタ構成に追加したが、追加しなかった。

Step 4: NVDIAソフトウェアを設定

自分の環境では、control planeにもGPUが挿さっているので、既にNVIDIAドライバーはインストール済み。結論で述べたように、helmを使ってのインストールはしなかった。

具体的な手順は、以下の通り。

(1) (nvidia-container-runtimeパッケージをインストールのため)nvidia-dockerのリポジトリを設定
$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
   && curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
   && curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
(2) nvidia-container-runtimeパッケージをインストール
$ sudo apt-get update \
   && sudo apt-get install -y nvidia-container-runtime
(3) config.tomlを編集

/etc/containerd/config.tomlを次のように編集。

79c79
<       default_runtime_name = "nvida"
---
>       default_runtime_name = "runc"
125,132d124
<             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.v1"
<           [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options]
<             BinaryName = "/usr/bin/nvidia-container-runtime"

その後、containerdデーモンを再起動。

$ sudo systemctl restart containerd
(4) NVIDIA Device Pluginをインストール
$ kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.12.3/nvidia-device-plugin.yml

Step 5: 確認

以下のように、GPUポッドを起動して、状態を確認し、ログを確認し、動作していることが確認できた。

$ cat gpu-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: gpu-operator-test
spec:
  restartPolicy: OnFailure
  containers:
  - name: cuda-vector-add
    image: "nvidia/samples:vectoradd-cuda10.2"
    resources:
      limits:
         nvidia.com/gpu: 1
         
$ kubectl apply -f gpu-pod.yaml
pod/gpu-operator-test created
$ kubectl get pods
NAME                READY   STATUS      RESTARTS   AGE
gpu-operator-test   0/1     Completed   0          8s
$ kubectl logs gpu-operator-test
[Vector addition of 50000 elements]
Copy input data from the host memory to the CUDA device
CUDA kernel launch with 196 blocks of 256 threads
Copy output data from the CUDA device to the host memory
Test PASSED
Done

まとめ

以上の手順で、めでたしめでたし、と言いたいところだが、GPU搭載されている別のノード(mokusei)をクラスタに追加しても、そのノードでは動作しない。Pending状態のままとなる。

saiseiが起動中は動くのだが、次のとおりsaiseiをシャットダウンして、mokuseiのみのクラスタでは、GPUポッドを起動しても、実行されない。

$ kubectl get nodes
NAME      STATUS     ROLES           AGE     VERSION
europe    Ready      control-plane   68m     v1.25.0
mokusei   Ready      <none>          66m     v1.25.0
saisei    NotReady   <none>          9m51s   v1.25.0

$ kubectl apply -f gpu-pod.yaml
pod/gpu-operator-test created

$ kubectl get pods gpu-operator-test
NAME                READY   STATUS    RESTARTS   AGE
gpu-operator-test   0/1     Pending   0          2m50s

次は、上記の問題をおいおい解決していく予定。

また、上記問題解決のために、ノードに搭載されているGPUの指定、ノードの指定などを一緒に調べていく必要がありそうだ。