EKS東京リージョンでGPUコンテナクラスタを立ててみる

# EKSクラスタの構築
AWSの公式ドキュメントではCloudFormationなどを駆使して、EKSクラスタを構築する方法が記載されていますが、ここではそれらを隠蔽してくれるeksctlを使います。
## eksctlのインストール
```bash
$ brew tap weaveworks/tap
$ brew install weaveworks/tap/eksctl
```
## クラスタの作成
ここではp2.xlarge * 1のクラスタを構築します。

```bash

$ eksctl create cluster --name gpu-cluster --region ap-northeast-1 --nodes 1 --nodes-min 1 --nodes-max 1 --node-type p2.xlarge --version=1.11
```

# NVIDIA device pluginのインストール
GPUのスケジューリングを有効にするためにNVIDIA device pluginをインストールします。

```bash

$ kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v1.11/nvidia-device-plugin.yml
```

# (Optional) GPU Mutating Webhookの追加
[nvidia.com/gpuリソースを指定しないコンテナからGPUが見えてしまう問題](https://github.com/NVIDIA/k8s-device-plugin#running-gpu-jobs)のために、Mutating Admission Webhookを追加します。
## クライアントCAの取得・アップロード
EKSではGKEなどと違い、kube-systemのConfigMap:extension-apiserver-authenticationにclient-ca-fileが入っていないので入れておきます。

```bash

$ aws eks describe-cluster \
        --region=ap-northeast-1 \
        --name=gpu-cluster \
        --output=text \
        --query 'cluster.{certificateAuthorityData: certificateAuthority.data, endpoint: endpoint}' | \
        awk '{print $1}' | \
        base64 -D
```
で出力されたCAをConfigMapに入れます。

```diff
$ kubectl edit configmap extension-apiserver-authentication -n kube-system
apiVersion: v1
data:
+ client-ca-file: |
+   -----BEGIN CERTIFICATE-----
+   xxxx
+   -----END CERTIFICATE-----
  requestheader-allowed-names: '["front-proxy-client"]'
  requestheader-client-ca-file: |
    -----BEGIN CERTIFICATE-----
    xxxx
    -----END CERTIFICATE-----
  requestheader-extra-headers-prefix: '["X-Remote-Extra-"]'
  requestheader-group-headers: '["X-Remote-Group"]'
  requestheader-username-headers: '["X-Remote-User"]'
kind: ConfigMap
```
## gpu-mutating-webhookのデプロイ
```bash

$ git clone https://github.com/takmatsu/gpu-mutating-webhook.git
$ cd gpu-mutating-webhook
```
```bash

$ kubectl apply -f deployment/namespace.yaml
$ ./deployment/webhook-create-signed-cert.sh
$ cat deployment/mutatingwebhook.yaml | \
    deployment/webhook-patch-ca-bundle.sh > \
    deployment/mutatingwebhook-ca-bundle.yaml
$ kubectl apply -f deployment/deployment.yaml
$ kubectl apply -f deployment/service.yaml
$ kubectl apply -f deployment/mutatingwebhook-ca-bundle.yaml
```

# (Optional) EFS Provisonerの追加
EKSではデフォルトでPersistent VolumeとしてAWSElasticBlockStoreが使えますが、ReadWriteOnceのみしか使えないため、ReadWriteManyなどが使いたい場合はEFS Provisonerを使用するとRWXなPVがDynamic Provisioningできます。
## EFSの作成
```
aws efs create-file-system --creation-token gpu-nfs --region ap-northeast-1
```
## EFSにSecurityGroupの設定
* EFSのIDの確認

```bash

$ aws efs describe-file-systems
```
* サブネットの確認

```bash

$ aws ec2 describe-subnets --filters Name=tag:Name,Values=\*gpu-cluster\* Name=tag:aws\:cloudformation\:logical-id,Values=\*Public\* --query 'Subnets[].{SubnetId: SubnetId}' --output text
```
* セキュリティグループの確認

```bash

$ aws ec2 describe-security-groups --filters Name=group-name,Values=\*gpu-cluster\* --query 'SecurityGroups[].{GroupName: GroupName, GroupId: GroupId}' --output text | grep ClusterSharedNodeSecurityGroup | awk {'print $1'}
```

* 設定

```bash

$ aws efs create-mount-target --file-system-id fs-****** --subnet-id subnet-****** --security-groups sg-******
```
×サブネット分

## efs-provisonerのデプロイ
```
$ git clone https://github.com/kubernetes-incubator/external-storage.git
$ cd external-storage/aws/efs
```

* namespaceの作成

```
$ NAMESPACE=efs-provisioner
$ kubectl create namespace $NAMESPACE
```
* RBACの設定

```
$ sed -i '' "s/namespace:.*/namespace: $NAMESPACE/g" ./deploy/rbac.yaml
$ kubectl apply -f deploy/rbac.yaml -n $NAMESPACE
```

* manifestの編集

```diff

$ vi deploy/manifest.yaml
@@ -4,8 +4,8 @@ kind: ConfigMap
 metadata:
   name: efs-provisioner
 data:
-  file.system.id: yourEFSsystemid
-  aws.region: regionyourEFSisin
+  file.system.id: fs-*****
+  aws.region: ap-northeast-1
   provisioner.name: example.com/aws-efs
   dns.name: ""
 ---
@@ -53,7 +53,7 @@ spec:
       volumes:
         - name: pv-volume
           nfs:
-            server: yourEFSsystemID.efs.yourEFSregion.amazonaws.com
+            server: fs-*****.efs.ap-northeast-1.amazonaws.com
             path: /
```

* デプロイ

```
kubectl apply -f deploy/manifest.yaml -n $NAMESPACE
```
# その他
* GPUインスタンスは高いので、使わないときにワーカーノードに使っているAuto Scaling Groupを0にするLambdaなどを書くといいかもしれない、K8sを使っているとステートを持つ情報はコントロールプレーンか外部ストレージに入っているので戻すと自動復旧する（はず）