物理GPUノードの計算能力を、メンバと共用で利用したい!
そんな思いに、KubernetesのGPUノードはぴったりです。
KubernetesのGPUノードで何ができるの?
物理サーバと違うのは、GPUを利用した計算のスケジューリングを、人間同士の調整不要で行える点です。
例えば、実行中のGPUコンテナが終了するまで、次のGPUコンテナが起動しません。
これは「ずっと見張っていて、GPUが空いたら実行!」とする必要は無く、何も意識せずにGPUコンテナを実行することで、計算リソースが空くのを勝手に待ってくれます。
やること
今回は構築支援ツールkubeadmを利用して作成したクラスタに、物理のGPUノードを追加しました。
その後Tensorflow GPUコンテナでGPU利用した計算を試してみます!
試した環境
Kubernetesクラスタ環境マシン構成は以下です。
ホスト名 | cpu | メモリ | GPU | OS | 仮想/物理 |
---|---|---|---|---|---|
controller-00 | 2コア | 2GB | なし | Ubuntu18.04 | 仮想 |
controller-01 | 2コア | 2GB | なし | Ubuntu18.04 | 仮想 |
controller-02 | 2コア | 2GB | なし | Ubuntu18.04 | 仮想 |
worker-00 | 1コア | 2GB | なし | Ubuntu18.04 | 仮想 |
worker-01 | 1コア | 2GB | なし | Ubuntu18.04 | 仮想 |
Kubernetesコンポーネントのバージョンは以下です。 | |||||
ネットワークはflannelで構成しています。 |
コンポーネント | バージョン |
---|---|
Kubernetes | v1.18.2 |
Docker | 19.03.8 |
flannel | v0.12.0-amd64 |
nvidia-docker2 | 1.0.0-rc10 |
NVIDIA driver | 440.82 |
NVIDIA device-plugin for Kubernets | 1.0.0-beta6 |
追加するノード
追加するノードは以下です。
ホスト名 | cpu | メモリ | GPU | OS | 仮想/物理 |
---|---|---|---|---|---|
worker-gpu | 8コア x2 | 128GB | NVIDIA Geforce GTX1060 | Ubuntu18.04 | 物理 |
GPUノードを準備する
基本的にはkubeadm joinで参加させる時と同様に、ノードの準備を行いますが、以下が違います。
- GPUのドライバをインストールする
- ランタイムをnvidia-docker2に変更する
そして参加した後
- NVIDIA device-plugin for Kubernetesを導入する
1. GPUノードの前提作業
Ubuntu 18.04をインストールして、既存のクラスタと疎通をとれるようにしておきます。
flannelをデフォルトで利用している場合は、1枚目のNICでクラスタと通信できるようにしてください。
flannelはデフォルトだとOSの1枚目のNICでポッドネットワークを作成するため、2枚目以降のNICで構築したい場合は、flannelのマニフェストを書き換える必要があります。
2. GPUノードの準備
この項はkubeadmのノード追加では恒例の作業なのでご存知の方は読み飛ばしてください。
まずカーネルパラメータを設定します。
# cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
sudo sysctl --system
kubeletを起動するためにswapを無効化しておきます。ノードの再起動が発生する場合は/etc/fstabのswap行をコメントアウトします。
# swapoff -a
# swapon --show
#
swapon --showコマンドを実行して何も表示されなければ、swapは無効になっています。
Dockerをインストールします。
まず前提パッケージのインストールします。
# apt-get update && apt-get install -y \
> apt-transport-https ca-certificates curl software-properties-common gnupg2
Dockerリポジトリを追加します。
# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
Dockerをインストールします。
# apt-get update && apt-get install -y \
> containerd.io=1.2.13-1 \
> docker-ce=5:19.03.8~3-0~ubuntu-$(lsb_release -cs) \
> docker-ce-cli=5:19.03.8~3-0~ubuntu-$(lsb_release -cs)
Docker設定ファイルを作成します。
# cat > /etc/docker/daemon.json <<EOF
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
EOF
Dockerサービス用のディレクトリを作成します。
# mkdir -p /etc/systemd/system/docker.service.d
3.NVIDIAドライバのインストール
GPUノードに適切なGPUドライバを確認します。
# apt install ubuntu-drivers-common
~~~~~~
# ubuntu-drivers devices
== /sys/devices/pci0000:40/0000:40:02.0/0000:41:00.0 ==
modalias : pci:v000010DEd00001C03sv000010DEsd00001C03bc03sc00i00
vendor : NVIDIA Corporation
model : GP106 [GeForce GTX 1060 6GB]
manual_install: True
driver : nvidia-driver-435 - distro non-free
driver : nvidia-driver-390 - distro non-free
driver : nvidia-driver-440 - distro non-free recommended ←
driver : xserver-xorg-video-nouveau - distro free builtin
#
nvidia-driver-440が最適であることが分かりました。
NVIDIA公式からダウンロードしてインストールする方法もありますが、パッケージ管理ツール以外でインストールしてしまうと、カーネルモジュールの更新によって動作しなくなることがあるので、アップデート時も依存関係を考慮してくれるaptでインストールします。
# apt install nvidia-driver-440
GPUが認識されているか確認してみます。
# nvidia-smi
Fri May 15 14:40:29 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82 Driver Version: 440.82 CUDA Version: 10.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GeForce GTX 106... Off | 00000000:41:00.0 Off | N/A |
| 25% 33C P8 9W / 120W | 0MiB / 6070MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
#
ちゃんと認識されています。
これでドライバをインストールすることが出来ました。
4. nvidia-docker2のインストール
GPUを利用するためのランタイムをインスト―ルします。
# distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
# curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
OK
# curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
deb https://nvidia.github.io/libnvidia-container/ubuntu18.04/$(ARCH) /
deb https://nvidia.github.io/nvidia-container-runtime/ubuntu18.04/$(ARCH) /
deb https://nvidia.github.io/nvidia-docker/ubuntu18.04/$(ARCH) /
# apt-get update && sudo apt-get install -y nvidia-docker2
~~~~~~~~
#
Dockerが利用するランタイムを変更します。
# vi /etc/docker/daemon.json
## 内容
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2",
"default-runtime": "nvidia",
"runtimes": {
"nvidia": {
"path": "/usr/bin/nvidia-container-runtime",
"runtimeArgs": []
}
}
}
Dockerを再起動& enableにしておきます。
# systemctl restart docker && systemctl enable docker
Synchronizing state of docker.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable docker
Dockerのランタイムがnvidiaに変更されていることを確認します。
# docker info | grep Runtime
Runtimes: nvidia runc
Default Runtime: nvidia
WARNING: No swap limit support
#
これでGPUノードをクラスタに追加する準備が出来ました!
GPUノードをクラスタに追加する
おなじみのあのコマンドを実行するだけです!
と、そのまえにトークンの有効期限は24時間という事なので、期限が切れている場合はマスターノードでトークンを発行しましょう!
# kubeadm token create
p4mn00.l3zru21qawhp4m60
初回join時のハッシュも紛失してしまった場合はハッシュも再取得します。
# openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt \
> | openssl rsa -pubin -outform der 2>/dev/null \
> | openssl dgst -sha256 -hex | sed 's/^.* //'
6170c56742029031fb2845088b8dec4f73d7d9e90459dd9e07a1ffc8eb619f9e
さあお待ちかねのあのコマンドを実行しましょう!
# kubeadm join 192.168.184.213 --discovery-token p4mn00.l3zru21qawhp4m60 --discovery-token-ca-cert-hash sha256:6170c56742029031fb2845088b8dec4f73d7d9e90459dd9e07a1ffc8eb619f9e
~~~~~~~~
This node has joined the cluster:
~~~~~~~~
参加できているか確認してみましょう。
# kubectl get nodes
NAME STATUS ROLES AGE VERSION
controller-00 Ready master 26h v1.18.2
controller-01 Ready master 25h v1.18.2
controller-02 Ready master 25h v1.18.2
worker-00 Ready <none> 25h v1.18.2
worker-01 Ready <none> 25h v1.18.2
worker-gpu Ready <none> 44s v1.18.2
#
うまく参加できたようです!
NVIDIA device pluginを実行する
参加したGPUノードのリソースを見てみましょう。
# kubectl describe node worker-gpu
Name: worker-gpu
Roles: <none>
~~~~~
Addresses:
InternalIP: 192.168.207.201
Hostname: worker-gpu
Capacity:
cpu: 32
ephemeral-storage: 1951507452Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 131948100Ki
pods: 110
Allocatable:
cpu: 32
ephemeral-storage: 1798509264786
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 131845700Ki
pods: 110
~~~~~
PodCIDR: 10.200.5.0/24
PodCIDRs: 10.200.5.0/24
Non-terminated Pods: (2 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
kube-system kube-flannel-ds-amd64-v4zsx 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) 45s
kube-system kube-proxy-vv9kh 0 (0%) 0 (0%) 0 (0%) 0 (0%) 45s
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 100m (0%) 100m (0%)
memory 50Mi (0%) 50Mi (0%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
~~~~~
#
まだGPUに関わる情報が見えません。
NVIDIA device-pluginを適用してみます。
# wget https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta6/nvidia-device-plugin.yml
# kubectl -f apply nvidia-device-plugin.yml
daemonset.apps/nvidia-device-plugin-daemonset created
#
デーモンセットで各ノードにGPUを参照するポッドを配置しているようです。
もう一度GPUノードを見てみます。
# kubectl describe node worker-gpu
Name: worker-gpu
Roles: <none>
~~~~~~
Addresses:
InternalIP: 192.168.207.201
Hostname: worker-gpu
Capacity:
cpu: 32
ephemeral-storage: 1951507452Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 131948100Ki
nvidia.com/gpu: 1
pods: 110
Allocatable:
cpu: 32
ephemeral-storage: 1798509264786
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 131845700Ki
nvidia.com/gpu: 1
pods: 110
~~~~~~~~
PodCIDR: 10.200.5.0/24
PodCIDRs: 10.200.5.0/24
Non-terminated Pods: (3 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
kube-system kube-flannel-ds-amd64-v4zsx 100m (0%) 100m (0%) 50Mi (0%) 50Mi (0%) 64m
kube-system kube-proxy-vv9kh 0 (0%) 0 (0%) 0 (0%) 0 (0%) 64m
kube-system nvidia-device-plugin-daemonset-6ztxk 0 (0%) 0 (0%) 0 (0%) 0 (0%) 63m
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 100m (0%) 100m (0%)
memory 50Mi (0%) 50Mi (0%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
nvidia.com/gpu 0 0
~~~~~~
#
項目 nvidia.com/gpu: が追加されました。
これでなんかリソース指定でGPUを使えそうですね!
GPUポッドを起動してみる
ポッド上からGPUが利用出来るか確認します。
以下のマニフェストを作成して
# tens-pod.yml
apiVersion: v1
kind: Pod
metadata:
name: tens-pod
spec:
containers:
- name: tens-container
image: tensorflow/tensorflow:latest-gpu-py3
command: ["/bin/sleep"]
args: ["3600"]
resources:
limits:
nvidia.com/gpu: 1 ←これでGPUを要求しています。
適用します。
# kubectl apply -f tens-pod.yml
pod/tens-pod created
# k describe po | grep -e Name -e Node
Name: tens-pod
Namespace: default
Node: worker-gpu/192.168.207.201
SecretName: default-token-8c9dw
Node-Selectors: <none>
#
ちゃんとGPUノードで起動していますね!
エライ!!
ポッドに接続してGPUが見えているか確認してみましょう!
# kubectl exec -it tens-pod -- /bin/bash
________ _______________
___ __/__________________________________ ____/__ /________ __
__ / _ _ \_ __ \_ ___/ __ \_ ___/_ /_ __ /_ __ \_ | /| / /
_ / / __/ / / /(__ )/ /_/ / / _ __/ _ / / /_/ /_ |/ |/ /
/_/ \___//_/ /_//____/ \____//_/ /_/ /_/ \____/____/|__/
WARNING: You are running this container as root, which can cause new files in
mounted volumes to be created as the root user on your host machine.
To avoid this, run the container by specifying your user's userid:
$ docker run -u $(id -u):$(id -g) args...
# nvidia-smi
Thu May 14 16:51:10 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82 Driver Version: 440.82 CUDA Version: 10.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GeForce GTX 106... Off | 00000000:41:00.0 Off | N/A |
| 25% 34C P8 9W / 120W | 0MiB / 6070MiB | 0% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
#
ちゃんとGPUが認識されていますね!
Tensorflowの簡単なものを実行して、GPUが動作しているか確認してみましょう。
# python
## 以下をコピペして実行
from tensorflow.python.client import device_lib
device_lib.list_local_devices()
import tensorflow as tf
mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(512, activation=tf.nn.relu),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)
実行中にGPUの負荷を見てみます。
# nvidia-smi
Thu May 14 17:16:14 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82 Driver Version: 440.82 CUDA Version: 10.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 GeForce GTX 106... Off | 00000000:41:00.0 Off | N/A |
| 25% 44C P2 25W / 120W | 5905MiB / 6070MiB | 9% Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
| 0 13584 C python 5895MiB |
+-----------------------------------------------------------------------------+
#
負荷と温度がほんのり上がっているのが分かります。
また、ProssesにPythonが表示されています。
これでGPUポッドが実行できるようになりました!!
おわりに
それぞれの用途に合わせて、マニフェストやコンテナイメージにPythonなどのコードを仕込んで、
ジョブコントローラで実行してもいいですね!
コンテナのジョブとしての実行も、Kubernetesの魅力のひとつですよね!
くれぐれも、GPUを占有しないように、計算が完了したらポッドが終了するような設定を行ってください!
弊社ブログではKubernetes,AWS, Ansibleをはじめとしたさまざまな技術記事を紹介しています。
是非ご覧ください!