Kubernetes on NVIDIA GPUsをEC2上に構築してみる

- [Kubernetes on NVIDIA GPUs](https://developer.nvidia.com/kubernetes-gpu)をEC2上に構築してみます

# Kubernetes on NVIDIA GPUsとは
- NVIDIAが提供しているk8sのパッケージ
- これにより、GPUクラスタを構築することができる

## 使用するメリット
- Flannel network pluginがインストールされている
    - コンテナ間のネットワーク構築ツール
- Helmがインストールされている
    - k8sのパッケージ管理ソフトウェア
- Prometheus、Grafanaが利用できる
    - GPUモニタリングツール
- 各ノードの以下の情報が取得できる
    - GPUメモリ容量
    - ECC(Error Correcting Code)の有無
    - エラーを自動的に訂正する機能
    - GPU compute capabilities
    - ハードウェアの世代
- 上記の情報を基にスケジュールを組める
- CRI-Oがサポートされている
    - Dockerに代わるk8s専用のコンテナ
    - Dockerよりも軽量

# 実行環境
- [公式ドキュメント](https://docs.nvidia.com/datacenter/kubernetes/index.html)にはオンプレ、AWS、Azure、GCP等での手順が記載されている
- 今回はAWSのEC2を使用

# 前提条件

# インスタンス作成
- 基本的に[手順](https://docs.nvidia.com/ngc/ngc-aws-setup-guide/index.html)通りに作成
- P3のインスタンスが必要なので、P3に対応しているリージョンを使用

## 事前準備
- Key Pairの作成
- セキュリティグループの作成
- ノードで共有するストレージが必要な場合はEFSのセットアップが必要ですが、今回は省略します

## ノードのセットアップ
- 基本的なEC2セットアップの手順と同様
- AMIに"NVIDIA Volta Deep Learning AMI"を指定
    - AWS Market Placeにあります
    - AMI自体は無料ですが、Subscriptionが必要になります
    - ディストリビューションはUbuntu
    - 以下が含まれる
        - NVIDIA Driver: 410.48
        - Docker CE: 18.06.1
        - NVIDIA Container Runtime for Docker: (nvidia-docker2) v2.0.3
       
- インスタンスはP3しか選択できませんが、今回はp3.2xlargeを使います
- セキュリティグループには最初に作成したものを指定
- インスタンス作成時に聞かれるKey Pairの選択ですが、こちらも最初に作成したものを指定

## マスターのセットアップ
- こちらは本当に基本的なEC2セットアップの手順と同様
- AMIは何でもできそうな気がしますがマニュアルだとUbuntu16.04が前提になっているのでそれを指定
- とりあえずt2.microを指定
- ノードと同様に最初に作成したセキュリティグループとKey Pairを選択

# k8s環境セットアップ
- ノード、マスター共にsshでログインして作業します

## マスターのセットアップ
- GPGキーを追加

```
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
OK
$ curl -s -L https://nvidia.github.io/kubernetes/gpgkey | sudo apt-key add -
OK
$ curl -s -L https://nvidia.github.io/kubernetes/ubuntu16.04/nvidia-kubernetes.list |\
           sudo tee /etc/apt/sources.list.d/nvidia-kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
deb https://nvidia.github.io/kubernetes/ubuntu16.04/$(ARCH) /
```

- パッケージを更新

```
$ sudo apt update
```


- k8s 関連のパッケージインストール
    - VESIONに1.9.10+nvidiaを指定する
    - Dockerが必要なので、docker.ioを追記しています

```
$ VERSION=1.9.10+nvidia
$ sudo apt install -y kubectl=${VERSION} kubelet=${VERSION} \
                               kubeadm=${VERSION} helm=${VERSION} docker.io
```

- クラスタの初期化
    - これを実行するといろいろと出力されますが、`kubeadm join --token XXXXXXX…`というコマンドをメモしておいてください。後ほどノードからクラスタに参加するためにこのコマンドを実行する必要があります

```
$ sudo kubeadm init --ignore-preflight-errors=all --config /etc/kubeadm/config.yml
```

## ノードのセットアップ
- 大体マスターのセットアップと同じ
- ログインするときにNVIDIA GPU Cloudで取得したAPIキーの入力が必要になります。アカウント作成は無料です。

- GPGキーを追加

```
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
OK
$ curl -s -L https://nvidia.github.io/kubernetes/gpgkey | sudo apt-key add -
OK
$ curl -s -L https://nvidia.github.io/kubernetes/ubuntu16.04/nvidia-kubernetes.list |\
           sudo tee /etc/apt/sources.list.d/nvidia-kubernetes.list
deb http://apt.kubernetes.io/ kubernetes-xenial main
deb https://nvidia.github.io/kubernetes/ubuntu16.04/$(ARCH) /
```

- パッケージを更新

```
$ sudo apt update
```


- k8s 関連のパッケージインストール

```
$ VERSION=1.9.10+nvidia
$ sudo apt install -y kubectl=${VERSION} kubelet=${VERSION} \
                               kubeadm=${VERSION} helm=${VERSION} docker.io
```

- ここでマスターで出力された`kubeadm join --token XXXXXXX…`をroot権限で実行してノードに参加します

```
# sudo kubeadm join --token xxxxxx.xxxxxxxxxxx xxx.xxx.xxx.xxx:xxx --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```
- 以下のように出力されたら成功

```
This node has joined the cluster:
* Certificate signing request was sent to master and a response
  was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.
```

- 試しにマスターに再度ログインしてnodeが認識されているか確認します

```
$ kubectl get nodes
NAME               STATUS    ROLES     AGE       VERSION
<プライベートIP>    Ready     <none>    14s       v1.9.10
```

- 上記のようにノードのプライベートIPが表示され、STATUSがReadyになっていればOK
- GPUの割り当ても下記のように確認できます

```
$ kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu"
NAME               GPU
<プライベートIP>   1
```

# デプロイ
- 試しにデプロイしてみます
- 以下のファイル(Pod.yml)を使用

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  containers:
    - name: cuda-container
      image: nvidia/cuda:9.0-base
      command: ["sleep"]
      args: ["100000"]
      computeResourceRequests: ["nvidia-gpu"]
  computeResources:
    - name: "nvidia-gpu"
      resources:
        limits:
          nvidia.com/gpu: 1
      affinity:
        required:
          - key: "nvidia.com/gpu-memory"
            operator: "Gt"
            values: ["8000"] # change value to appropriate mem for GPU
```

- 以下のコマンドで適用

```
$ kubectl apply -f /etc/kubeadm/examples/pod.yml 
```
- 以下のコマンドでPodが作成されたことを確認

```
$ kubectl get pods
NAME      READY     STATUS    RESTARTS   AGE
gpu-pod   1/1       Running   0          10m
```