AWS EKSからFargateと別のGPUノードグループを両方使う

こんにちは、(株)日立製作所 研究開発グループ サービスコンピューティング研究部の大崎です。現在北米の拠点で活動しております。

12/3にAWS re:Invent 2019でEKS関連の新機能が発表されました。サーバレスなマネージドコンテナサービスFargateがワーカーとして利用できるという機能です。
https://aws.amazon.com/blogs/aws/amazon-eks-on-aws-fargate-now-generally-available/

Fargateを使えば、ワーカーノード管理の作業の軽減が期待できますね。早速使用したいと思います。

FargateはGPUは以下の通りまだサポートされていないようです。

https://github.com/aws/containers-roadmap/issues/88

その代わりEKSには**複数のノードグループを追加できる**ようです。
今回は、同一EKSクラスタ内に、

- **(1)Fargate**を保持しGPUなしの一般的なワークロードを実行、
- **(2)GPUノードからなるEC2インスタンスをノードグループとして追加**してGPUを使うワークロードも実行可能にする

という**２つの特性を備えたクラスタ**を作ってみます。初回の失敗と対策も記載しました。

# `eksctl`のインストール

12/5現在0.11.0版が公開されましたので、こちらをダウンロードします。Macの場合は以下です。(ただし、これ以降の作業は12/3時点の0.11.0-rc.0を使用しました)

```sh
$ curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
$ sudo mv /tmp/eksctl /usr/local/bin
```

その他インストール方法は公式レポジトリを参照ください。

https://github.com/weaveworks/eksctl

バージョンを確認します。

```sh
$ eksctl version                                                                                                                                                                          
[ℹ]  version.Info{BuiltAt:"", GitCommit:"", GitTag:"0.11.0-rc.0"}
```

# まずはFargateとEKSクラスタを構築

```sh
eksctl create cluster --name test --region us-east-1 --fargate                                                                                                                                    1 ↵
[ℹ]  eksctl version 0.11.0-rc.0
[ℹ]  using region us-east-1
[ℹ]  setting availability zones to [us-east-1a us-east-1f]
[ℹ]  subnets for us-east-1a - public:192.168.0.0/19 private:192.168.64.0/19
[ℹ]  subnets for us-east-1f - public:192.168.32.0/19 private:192.168.96.0/19
[ℹ]  using Kubernetes version 1.14
[ℹ]  creating EKS cluster "test" in "us-east-1" region with Fargate profile
[ℹ]  if you encounter any issues, check CloudFormation console or try 'eksctl utils describe-stacks --region=us-east-1 --cluster=test'
[ℹ]  CloudWatch logging will not be enabled for cluster "test" in "us-east-1"
[ℹ]  you can enable it with 'eksctl utils update-cluster-logging --region=us-east-1 --cluster=test'
[ℹ]  Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster "test" in "us-east-1"
[ℹ]  1 task: { create cluster control plane "test" }
[ℹ]  building cluster stack "eksctl-test-cluster"
[ℹ]  deploying stack "eksctl-test-cluster"
[✔]  all EKS cluster resources for "test" have been created
[✔]  saved kubeconfig as "/Users/xxxxxx/.kube/config"
[ℹ]  creating Fargate profile "fp-default" on EKS cluster "test"
[ℹ]  created Fargate profile "fp-default" on EKS cluster "test"
[ℹ]  "coredns" is now schedulable onto Fargate
[ℹ]  "coredns" is now scheduled onto Fargate
[ℹ]  "coredns" pods are now scheduled onto Fargate
[ℹ]  kubectl command should work with "/Users/xxxxxx/.kube/config", try 'kubectl get nodes'
[✔]  EKS cluster "test" in "us-east-1" region is ready
```

以下のようにEKSクラスタにFargate profileがマッピングされていますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/8d01a317-05cc-d4a0-bb8d-839761ca7130.png)

# GPUノードグループを追加

以下のコマンドで、`p3.2xlarge`インスタンス1台からなるノードグループをEKSクラスタに追加します。

```sh
$ eksctl create nodegroup --cluster test gpu-node-groups --nodes 1 --node-type p3.2xlarge
[ℹ]  eksctl version 0.11.0-rc.0
[ℹ]  using region us-east-1
[ℹ]  will use version 1.14 for new nodegroup(s) based on control plane version
[ℹ]  nodegroup "gpu-node-groups" will use "ami-xxxxxxxxxxxxxxx" [AmazonLinux2/1.14]
[ℹ]  1 nodegroup (gpu-node-groups) was included (based on the include/exclude rules)
[ℹ]  will create a CloudFormation stack for each of 1 nodegroups in cluster "test"
[ℹ]  2 parallel tasks: { create nodegroup "gpu-node-groups", no tasks }
[ℹ]  building nodegroup stack "eksctl-test-nodegroup-gpu-node-groups"
[ℹ]  --nodes-min=1 was set automatically for nodegroup gpu-node-groups
[ℹ]  --nodes-max=1 was set automatically for nodegroup gpu-node-groups
[ℹ]  deploying stack "eksctl-test-nodegroup-gpu-node-groups"
[ℹ]  adding identity "arn:aws:iam::xxxxxxxxxx:role/eksctl-test-nodegroup-gpu-node-gr-NodeInstanceRole-xxxxxxxx" to auth ConfigMap
[ℹ]  nodegroup "gpu-node-groups" has 0 node(s)
[ℹ]  waiting for at least 1 node(s) to become ready in "gpu-node-groups"
[ℹ]  nodegroup "gpu-node-groups" has 1 node(s)
[ℹ]  node "ip-192-168-6-6.ec2.internal" is ready
[ℹ]  as you are using a GPU optimized instance type you will need to install NVIDIA Kubernetes device plugin.
[ℹ]  	 see the following page for instructions: https://github.com/NVIDIA/k8s-device-plugin
[✔]  created 1 nodegroup(s) in cluster "test"
[✔]  created 0 managed nodegroup(s) in cluster "test"
[ℹ]  checking security group configuration for all nodegroups
[ℹ]  all nodegroups have up-to-date configuration

```


configファイルを作成してからコマンドを実行する方法もあります。以下を参照ください。

https://eksctl.io/usage/managing-nodegroups/

次に、出力に書いてあるように、`k8s-device-plugin`が必要なので、以下レポジトリの"Enabling GPU Support in Kubernetes"を参照してコマンドを実行します。

https://github.com/NVIDIA/k8s-device-plugin

```
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/1.0.0-beta4/nvidia-device-plugin.yml
```

クラスタの状態を確認します。

```sh
$ kubectl get nodes \
"-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu"
NAME                                     GPU
fargate-ip-192-168-108-92.ec2.internal   <none>
fargate-ip-192-168-98-49.ec2.internal    <none>
ip-192-168-35-0.ec2.internal             1
```

最初の2つのnodeは、株式会社サイバーエージェント青山さんが以下の記事に記載の通りcoreDNS用のPodのためのnodeと思われます。

https://amsy810.hateblo.jp/entry/2019/12/04/151642

最後の1つはGPUノードですね。GPUが一つ見つかりました。

確認の方法は以下を参照しました。

https://aws.amazon.com/blogs/compute/running-gpu-accelerated-kubernetes-workloads-on-p3-and-p2-ec2-instances-with-amazon-eks/

# Podをデプロイしてみる

せっかくできたクラスタに、(1)GPU不要な一般的なPod、(2)GPUを必要とするPod、をそれぞれ実行してみます。

## (1) 一般的なPodをデプロイしてみる

```
$ kubectl create deploy nginx-test --image=nginx                                                                                                                            deployment.apps/nginx-test created
```

Fargateの特性みたいですが、Pod追加と同時にノードが増えるのですね、面白いです。

```sh
$ kubectl get nodes
NAME                                     STATUS   ROLES    AGE    VERSION
fargate-ip-192-168-108-92.ec2.internal   Ready    <none>   106m   v1.14.8-eks
fargate-ip-192-168-96-116.ec2.internal   Ready    <none>   36s    v1.14.8-eks
fargate-ip-192-168-98-49.ec2.internal    Ready    <none>   106m   v1.14.8-eks
ip-192-168-35-0.ec2.internal             Ready    <none>   98m    v1.14.7-eks-1861c5
```

## (2) GPUを使うPodをデプロイしてみる

以下のようなPodを起動します。本当であればGPUノードにデプロイされて`nvidia-smi`の実行結果がログに出力されてほしいところです。

```gpupod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nvidia-smi
spec:
  restartPolicy: OnFailure
  containers:
  - name: nvidia-smi
    image: nvidia/cuda:latest
    args:
    - "nvidia-smi"
    resources:
      limits:
        nvidia.com/gpu: 1
```

```sh
$ kubectl apply -f gpupod.yaml
```

本処理は6分ほど待ちます。

### 初回の結果：デプロイ失敗... 原因は?

```sh
kubectl get po -w
NAME                         READY   STATUS    RESTARTS   AGE
nginx-test-9876c7b6c-fcpwj   1/1     Running   0          4m19s
nvidia-smi                   0/1     Pending   0          5s
NAME                         READY   STATUS    RESTARTS   AGE
nvidia-smi                   0/1     Pending   0          68s
nvidia-smi                   0/1     ContainerCreating   0          68s
nvidia-smi                   0/1     RunContainerError   0          6m1s
nvidia-smi                   0/1     RunContainerError   1          6m2s
nvidia-smi                   0/1     CrashLoopBackOff    1          6m3s
```

なぜ`nvidia-smi`は起動失敗するかというと、せっかく用意したGPUノードグループではなく**GPUがないFargateにデプロイされてしまう**ことが原因でした。GPUがない環境で動かすとデバイスが見えず不正終了してしまうのです。

原因は私が指定したnamespaceでした。以下を見てください。`eksctl create cluster --fargate`で作成したFargate Profileには以下のように`default, kube-system`と書かれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/8d01a317-05cc-d4a0-bb8d-839761ca7130.png)

EKSからFargateを使うときには、**Fargate Profileに「どういうPodをFargateに割り付けるか」という条件を書く必要**があり、デフォルトでは`default, kube-system`になっています。re:Invent 2019の"Deep dive: Fargate under the hood"セッションでは、EKSにwebhookを独自に追加し、その内部で予めどちらに振り分けるかを決定している、と説明がありました。

私の場合は、GPUを使うPodも何も考えずに`default`にデプロイしたために、Fargateにデプロイされていたわけです。

### 解決策

namespaceを条件としてFargateに割り付けているので、その条件を外すようにnamespaceを新規作成して、デプロイ先に指定します。

```sh
$ kubectl create namespace gpu-jobs
$ kubectl apply -f gpupod.yaml --namespace gpu-jobs
```

```sh
$ kubectl logs nvidia-smi -n gpu-jobs
Thu Dec  5 01:25:18 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.87.00    Driver Version: 418.87.00    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  Tesla V100-SXM2...  On   | 00000000:00:1E.0 Off |                    0 |
| N/A   30C    P0    24W / 300W |      0MiB / 16130MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
```

無事起動し、コンテナ内でちゃんとGPUが見えますね。

### 削除

以下コマンドで、Fargateやノードグループを一括で削除可能です。

```sh
eksctl delete cluster --name test
```

# まとめ

Amazon EKSから、コンテナサービスFargateを利用しスケーラビリティを確保しつつ、GPUも使えるようにしました。具体的には、Fargateと`p3.2xlarge`インスタンスのEC2のノードグループをEKSに接続し、それぞれにPodをデプロイすることを確認しました。namespace,ラベルでのデプロイ先割付がなされました。単純なリソース要求量(Podの`requirements`)でのスケジューリング以外の要素として考慮する必要があることが分かりました。helmチャートなどをデプロイする人はどちらにデプロイされるかを意識することが必要かもしれません。

> AWS, EC2は、Amazon Web Services, Inc. の米国または他の国における商標登録または商標です。
> Kubernetesは、The Linux Foundationの米国または他の国における商標登録または商標です。