AWS
GPU
kubernetes
eks
cluster-autoscaler

AWS EKSでGPUノードグループをデプロイしcluster autoscalerでオートスケールする

はじめに

deep learningのインフラとして最近kubernetesが注目されています。6/6にAWS EKSがGAされましたのでdeep learningのインフラの選択肢として調査しました。

調査の結果のポイントは次のような感じかと思います。
・AWS EKSには専用のAMIがあり、それにcuda等各種ソフトウェアをインストールしてGPUノード用のAMIを作成する
・AWS AutoscalingはCPU使用率を閾値としているが、今回は利用できるGPUの有無でAutoscaleさせたいため、kubernetesのcluster-autoscalerを使用する
・kubernetesのcluster-autoscalerを動作させる為にGPUノードを起動しっぱなしにしておくのはもったいないのでcluster-autoscalerを動作させるノードグループとGPUインスタンスを動作させるノードグループの2つに分けて構築する

クライアント環境

今回構築検証した際のクライアント環境は以下です。
Windows 10 Home
curl
python 3.6.1
pip 10.0.1 (python 3.6)

構築作業

Getting Started with Amazon EKS を元に作業を進めます。またこの記事を書いている時点でEKSはUS East (N. Virginia)とUS West (Oregon)のみで利用できるため今回はUS East (N. Virginia)で構築しています。

ロールの作成

key value
Role name eksServiceRole

クラスタの作成

key value
Cluster name lminfratest
Kubernetes version Kubernetes 1.10 (default)
Role ARN eksServiceRole
VPC (default)
Subnets subnet-798e2925 (172.31.32.0/20) us-east-1a
subnet-527adb7c (172.31.80.0/20) us-east-1c
Security groups (default)

クライアント環境

'To install kubectl for Amazon EKS'と'To install heptio-authenticator-aws for Amazon EKS'の項を参照しkubectlとheptio-authenticator-awsをインストールします。
.kube/configについては、EKS > Clusters > lminfratestと辿り'API server endpoint'の項を<endpoint-url>に、'Certificate authority'の項を<base64-encoded-ca-cert>に書き、<cluster-name>にはクラスタ作成時に入力したCluster nameを入れます。
18.png

kubectl get svc を実行し以下のように表示されれば成功です。

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.100.0.1   <none>        443/TCP   1h

後で使うawscliもインストールしておきます。

pip install awscli
aws configure
AWS Access Key ID [********************]: xxx
AWS Secret Access Key [********************]: xxx
Default region name [None]: us-east-1
Default output format [None]: json

AMI作成

Getting Started with Amazon EKS に記述されていますが、EKS Worker Nodeには専用のAMI(Amazon Linux 2ベース)が用意されています。今回GPUノードグループを構築したい為このAMIをベースにcuda等各種ソフトウェアをインストールしたものをAMIとして登録しそれを使用します。事前にキーペアは作成済とします。OSはUbuntuですがAWS上のkubernetesでGPUを使うの記事を参考にさせて頂きました。

sudo su - 
yum groupinstall "Development Tools"
yum install kernel-devel kernel-headers
reboot

wget http://jp.download.nvidia.com/XFree86/Linux-x86_64/390.67/NVIDIA-Linux-x86_64-390.67.run
sh NVIDIA-Linux-x86_64-390.67.run --silent

wget https://developer.nvidia.com/compute/cuda/9.0/Prod/local_installers/cuda_9.0.176_384.81_linux-run
sh cuda_9.0.176_384.81_linux-run --silent --toolkit --override

distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | \
  sudo tee /etc/yum.repos.d/nvidia-docker.repo
yum list
#Importing GPG key
#...
#Is this ok [y/N]:
#のようなメッセージが出る為 y とします。

yum install docker nvidia-docker2

docker --version
Docker version 17.06.2-ce, build 3dfb8343b139d6342acfd9975d7f1068b5b1c3d3

sed -i -e 's/fd:\/\//fd:\/\/ -s=overlay2/g' /lib/systemd/system/docker.service

tee /etc/docker/daemon.json <<EOF
{
    "default-runtime": "nvidia",
    "runtimes": {
        "nvidia": {
            "path": "/usr/bin/nvidia-container-runtime",
            "runtimeArgs": []
        }
    }
}
EOF

pkill -SIGHUP dockerd
systemctl daemon-reload
systemctl restart docker.service

docker run --rm nvidia/cuda nvidia-smi
Fri Jul  6 07:58:32 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.67                 Driver Version: 390.67                    |
|-------------------------------+----------------------+----------------------+
| 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 K80           Off  | 00000000:00:1E.0 Off |                    0 |
| N/A   54C    P0    68W / 149W |      0MiB / 11441MiB |     98%      Default |
+-------------------------------+----------------------+----------------------+

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

nvidia-smiコマンドが実行できれば成功のため、AMIとして登録し、AMI IDを控えます。

key value
AMI ID ami-09da63a82bab00a50

Workerノードグループの作成

メインとなるノードグループはp2.xlargeを使用する為、未使用時にはノード数を0にしたい所です。しかしながらkube-dnsやcluster-autoscalerが存在するノードは削除対象となりません。その為まずt2.smallを使用するノードグループを作成します。

Getting Started with Amazon EKS の'Step 3: Launch and Configure Amazon EKS Worker Nodes'に従い作業します。

テンプレートの選択

URL https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-06-05/amazon-eks-nodegroup.yaml を指定します。

詳細の指定

key value
スタックの名前 lminfratest-worker-nodes-system

パラメータ

EKS Cluster

key value
ClusterName lminfratest
ClusterControlPlaneSecurityGroup default (sg-3d4d3f76)
Worker Node Configuration
key value
NodeGroupName ng-lminfratest-system
NodeAutoScalingGroupMinSize 1
NodeAutoScalingGroupMaxSize 1
NodeInstanceType t2.small
NodeImageId ami-dea4d5a1
KeyName ※各自のもの

Worker Network Configuration

key value
VpcId
Subnets subnet-798e2925 (172.31.32.0/20)

Getting Started with Amazon EKSの'To enable worker nodes to join your cluster'の項に従いaws-authという名前のConfigMapをデプロイします。
<ARN of instance role (not instance profile)> については作成したStackの詳細ページ > 出力 の項に表示されるNodeInstanceRoleの値を書きます。
20.png

kubectl apply -f aws-auth-cm.yaml
configmap "aws-auth" created
kubectl get pods -n kube-system -o wide
NAME                        READY     STATUS    RESTARTS   AGE       IP              NODE
aws-node-2v4vp              1/1       Running   1          4m        172.31.47.223   ip-172-31-47-223.ec2.internal
kube-dns-64b69465b4-rwczv   3/3       Running   0          1m        172.31.39.215   ip-172-31-47-223.ec2.internal
kube-proxy-xbp79            1/1       Running   0          4m        172.31.47.223   ip-172-31-47-223.ec2.internal

次にGPUノードグループを作成します。

テンプレートの選択

URL https://amazon-eks.s3-us-west-2.amazonaws.com/1.10.3/2018-06-05/amazon-eks-nodegroup.yaml を指定します。

詳細の指定

key value
スタックの名前 lminfratest-worker-nodes

パラメータ

EKS Cluster

key value
ClusterName lminfratest
ClusterControlPlaneSecurityGroup default (sg-3d4d3f76)
Worker Node Configuration
key value
NodeGroupName ng-lminfratest
NodeAutoScalingGroupMinSize 0
NodeAutoScalingGroupMaxSize 2
NodeInstanceType p2.xlarge
NodeImageId ami-09da63a82bab00a50
KeyName ※各自のもの

Worker Network Configuration

key value
VpcId
Subnets subnet-798e2925 (172.31.32.0/20)

aws-auth ConfigMapを更新します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: aws-auth
  namespace: kube-system
data:
  mapRoles: |
    - rolearn: <ARN of instance role (lminfratest-worker-nodes-system)>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
    - rolearn: <ARN of instance role (lminfratest-worker-nodes)>
      username: system:node:{{EC2PrivateDNSName}}
      groups:
        - system:bootstrappers
        - system:nodes
kubectl delete -f aws-auth-cm.yaml
configmap "aws-auth" deleted
kubectl create -f aws-auth-cm.yaml
configmap "aws-auth" created

削除→再作成が正しいやり方なのか分かっておりません。。

nvidia-device-plugin デプロイ

kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v1.10/nvidia-device-plugin.yml
kubectl get nodes "-o=custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu"
NAME                            GPU
ip-172-31-32-100.ec2.internal   1
ip-172-31-43-81.ec2.internal    1
ip-172-31-47-223.ec2.internal   <none>

cluster-autoscaler デプロイ

AWS用cluster-autoscaler をデプロイします。

Role作成

AWS用cluster-autoscaler にどのようなIAM Policyが必要か書かれているためそれに従いIAM Policyを作成します。今回は以下の内容で作成しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "autoscaling:DescribeAutoScalingGroups",
                "autoscaling:DescribeAutoScalingInstances",
                "autoscaling:DescribeTags",
                "autoscaling:DescribeLaunchConfigurations",
                "autoscaling:SetDesiredCapacity",
                "autoscaling:TerminateInstanceInAutoScalingGroup"
            ],
            "Resource": "*"
        }
    ]
}

IAM Role の一覧にある
lminfratest-worker-nodes-NodeInstanceRole-xxx
lminfratest-worker-nodes-NodeInstanceRole-system-xxx
という2つのRoleにこのIAM Policyをアタッチします。

AutoScalingGroupNameの確認

今回AutoScaleさせたいノードグループlminfratest-worker-nodesのAutoScalingGroupNameを確認します。

aws autoscaling describe-auto-scaling-groups
...
"AutoScalingGroupName": "lminfratest-worker-nodes-NodeGroup-1CRBWFYLE571G"
...

cluster-autoscaler デプロイ

lminfratest-worker-nodes-system の方にデプロイしたいため該当ノードにラベルを貼ります。

kubectl label nodes ip-172-31-47-223.ec2.internal type=system

https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/examples/cluster-autoscaler-one-asg.yaml
をダウンロードし、

- --nodes=1:10:k8s-worker-asg-1

の部分を以下のように変更します。

- --nodes=1:2:lminfratest-worker-nodes-NodeGroup-1CRBWFYLE571G

また最下部にnodeSelectorを追加します。

      volumes:
        - name: ssl-certs
          hostPath:
            #path: "/etc/ssl/certs/ca-certificates.crt"
            path: "/merged/etc/ssl/certs/ca-certificates.crt"
            #path: "/etc/ssl/certs/"
      nodeSelector:
        type: system

デプロイします。

kubectl create -f cluster-autoscaler-one-asg.yaml
serviceaccount "cluster-autoscaler" created
clusterrole.rbac.authorization.k8s.io "cluster-autoscaler" created
role.rbac.authorization.k8s.io "cluster-autoscaler" created
clusterrolebinding.rbac.authorization.k8s.io "cluster-autoscaler" created
rolebinding.rbac.authorization.k8s.io "cluster-autoscaler" created
deployment.extensions "cluster-autoscaler" created

意図したノードにデプロイされたか確認します。

kubectl get pods -n kube-system -o wide
NAME                                   READY     STATUS    RESTARTS   AGE       IP              NODE
aws-node-2v4vp                         1/1       Running   1          25m       172.31.47.223   ip-172-31-47-223.ec2.internal
aws-node-784kr                         1/1       Running   1          10m       172.31.32.100   ip-172-31-32-100.ec2.internal
aws-node-fmgsr                         1/1       Running   1          10m       172.31.43.81    ip-172-31-43-81.ec2.internal
cluster-autoscaler-868487f854-j9727    1/1       Running   2          31s       172.31.41.99    ip-172-31-47-223.ec2.internal
kube-dns-64b69465b4-rwczv              3/3       Running   0          22m       172.31.39.215   ip-172-31-47-223.ec2.internal
kube-proxy-29p84                       1/1       Running   0          10m       172.31.43.81    ip-172-31-43-81.ec2.internal
kube-proxy-sbhbv                       1/1       Running   0          10m       172.31.32.100   ip-172-31-32-100.ec2.internal
kube-proxy-xbp79                       1/1       Running   0          25m       172.31.47.223   ip-172-31-47-223.ec2.internal
nvidia-device-plugin-daemonset-bjcrr   1/1       Running   0          9m        172.31.39.188   ip-172-31-43-81.ec2.internal
nvidia-device-plugin-daemonset-dcrjl   1/1       Running   0          9m        172.31.38.47    ip-172-31-47-223.ec2.internal
nvidia-device-plugin-daemonset-mmx84   1/1       Running   0          9m        172.31.40.140   ip-172-31-32-100.ec2.internal

構築作業は以上で完了です。

挙動確認

kubectl logs -f -n kube-system cluster-autoscaler-868487f854-j9727

でログを追います。

GPU Podをデプロイすると

kubectl create -f gpu_pod.yml
pod "gpu-pod" created

GPUノードがないのでPodはPending状態です。

kubectl get pods -o wide
NAME      READY     STATUS    RESTARTS   AGE       IP        NODE
gpu-pod   0/1       Pending   0          14s       <none>    <none>

以下のようなログが出てノードが起動されます。

I0709 04:56:43.144665       1 static_autoscaler.go:244] Filtering out schedulables
I0709 04:56:43.144950       1 static_autoscaler.go:254] No schedulable pods
I0709 04:56:43.144990       1 scale_up.go:249] Pod default/gpu-pod is unschedulable
I0709 04:56:43.180682       1 scale_up.go:291] Upcoming 1 nodes
I0709 04:56:43.180952       1 scale_up.go:368] No need for any nodes in lminfratest-worker-nodes-NodeGroup-1CRBWFYLE571G
I0709 04:56:43.181117       1 scale_up.go:376] No expansion options
kubectl get nodes
NAME                            STATUS    ROLES     AGE       VERSION
ip-172-31-46-214.ec2.internal   Ready     <none>    3m        v1.10.3
ip-172-31-47-223.ec2.internal   Ready     <none>    2d        v1.10.3

GPU Podを削除します。

kubectl delete -f gpu_pod.yml
pod "gpu-pod" deleted

10分程待つとノードが削除されます。

I0709 05:12:59.620758       1 scale_down.go:584] ip-172-31-46-214.ec2.internal was unneeded for 10m0.085041466s
I0709 05:12:59.620817       1 scale_down.go:790] Scale-down: removing empty node ip-172-31-46-214.ec2.internal
I0709 05:12:59.621921       1 factory.go:33] Event(v1.ObjectReference{Kind:"ConfigMap", Namespace:"kube-system", Name:"cluster-autoscaler-status", UID:"2244ee72-80fd-11e8-8c88-0e55e8ad28ec", APIVersion:"v1", ResourceVersion:"478974", FieldPath:""}): type: 'Normal' reason: 'ScaleDownEmpty' Scale-down: removing empty node ip-172-31-46-214.ec2.internal
I0709 05:12:59.633926       1 delete.go:53] Successfully added toBeDeletedTaint on node ip-172-31-46-214.ec2.internal
I0709 05:12:59.830526       1 aws_manager.go:190] Terminating EC2 instance: i-0ddb606225444aad9
I0709 05:12:59.875145       1 factory.go:33] Event(v1.ObjectReference{Kind:"Node", Namespace:"", Name:"ip-172-31-46-214.ec2.internal", UID:"76b40978-8334-11e8-8c88-0e55e8ad28ec", APIVersion:"v1", ResourceVersion:"478975", FieldPath:""}): type: 'Normal' reason: 'ScaleDown' node removed by cluster autoscaler
kubectl get nodes
NAME                            STATUS    ROLES     AGE       VERSION
ip-172-31-47-223.ec2.internal   Ready     <none>    2d        v1.10.3

以上です。