EKSとKarpenterの環境でNVIDIA GPUノードを立ち上げる
この記事はKubernetes Advent Calendar 2023 の14日目の記事です。
マネージドKubernetesであるEKSとクラスターオートスケーラーのKarpenterを使っている環境で、GPUノードを扱う方法を書いていきます。
下記のバージョンを使用しています。
- Kubernetes v1.28
- Karpenter v0.33
- NVIDIA Device Plugin v0.14.3
- ArgoCD v2.9.3
GPUノードを扱うために必要なこと
KubernetesでNVIDIAのGPUノードを扱うためには、NVIDIA Device PluginをGPUノードに入ておく必要があります。
NVIDIA Device PluginをGPUノードで実行するためには、下記のものが必要になります。
- NVIDIA drivers
- nvidia-container-toolkit (nvidia-docker)
- nvidia-container-runtime(デフォルトランタイム)
このうちnvidia-dockerについては、コンテナランタイムとしてcontainerdを使う場合は不要[1]なので、残り二つをノードに導入すればよさそうです。
AWS EKSの場合は上記二つを設定済みのEKS用AMIが用意されているので、こちらを使うのが一番簡単です。
さらに、Karpenterを使う場合はデフォルトでこのAMIがノードの選択肢に入るような設定になっているため、AMIに関して新しい設定をする必要はありません。
KarpenterのNodePoolを作成
KarpenterでGPUノードを立ち上げるためのNodePoolを作成します。Karpenterがノードに自動的に付けてくれるラベル[2]をspec.requirementes
に指定することで、NVIDIAのGPUインスタンスのみが起動するように設定できます。
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: gpu-nvidia
spec:
requirements:
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["g", "p"]
- key: karpenter.k8s.aws/instance-generation
operator: Gt
values: ["2"]
- key: karpenter.k8s.aws/instance-gpu-manufacturer
operator: In
values: ["nvidia"] # instance-categoryだけの指定だとAMDのGPUインスタンスであるg4ad等が候補になってしまうので、nvidiaのみに絞る
- key: kubernetes.io/os
operator: In
values: ["linux"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
taints:
- key: nvidia.com/gpu
value: "true"
effect: "NoSchedule"
limits:
resources:
cpu: 1k
memory: 1000Gi
nodeClassRef:
name: default
disruption:
consolidationPolicy: WhenUnderutilized
expireAfter: 720h
taints
を設定することで、このNodePoolで起動するノードに同じtains
設定が付与されます。そして対応するtolerations
を持つPodだけがそのノードにスケジュールされます。
nvidia-device-pluginをインストール
nvidia-device-pluginはhelmでインストールする方法があります。今回はArgoCDのApplication経由でhelm chartsをインストールしてみます。
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nvidia-device-plugin
namespace: argocd
spec:
project: default
source:
chart: nvidia-device-plugin
repoURL: https://nvidia.github.io/k8s-device-plugin
targetRevision: 0.14.3
helm:
# nodeSelectorによってNVIDIA GPUのノードだけにDaemonSetのPodがスケジュールされるようにする
valuesObject:
nodeSelector:
karpenter.k8s.aws/instance-gpu-manufacturer: nvidia
destination:
server: "https://kubernetes.default.svc"
namespace: nvidia-device-plugin
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
nvidia-device-pluginはDaemonSetとして入りますが、デフォルトだとGPU以外のノードにもスケジュールされてしまいます。KarpenterがノードにつけるNVIDIA GPU固有のラベルkarpenter.k8s.aws/instance-gpu-manufacturer: nvidia
をnodeSelectorに設定することで、NVIDIAのGPUノードだけにDaemonSetが入るようにしています。
動作確認
さて、ここまででGPUをPodで使用する準備ができました。下記のページに動作確認方法が載っているので、確認してみます。
まず、Podのマニフェストを作成します。cudaイメージのtag
はこちらのものに書き換えます。
apiVersion: v1
kind: Pod
metadata:
name: nvidia-smi
spec:
restartPolicy: OnFailure
containers:
- name: nvidia-smi
image: nvidia/cuda:tag
args:
- "nvidia-smi"
resources:
limits:
nvidia.com/gpu: 1
resourcesに関してはlimitsだけにNVIDIA GPUの拡張リソースnvidia.com/gpu
を設定します。limitsだけで良い理由はこちらに書いてあります。
GPUs are only supposed to be specified in the limits section, which means:
- You can specify GPU limits without specifying requests, because Kubernetes will use the limit as the request value by default.
- You can specify GPU in both limits and requests but these two values must be equal.
- You cannot specify GPU requests without specifying limits.
マニフェストをapplyしてしばらく待つと、KarpenterによってGPUノードが立ち上がります。Podのログを確認します。
$ kubectl logs nvidia-smi
==========
== CUDA ==
==========
CUDA Version 12.2.0
Container image Copyright (c) 2016-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
This container image and its contents are governed by the NVIDIA Deep Learning Container License.
By pulling and using the container, you accept the terms and conditions of this license:
https://developer.nvidia.com/ngc/nvidia-deep-learning-container-license
A copy of this license is made available in this container at /NGC-DL-CONTAINER-LICENSE for your convenience.
Fri Oct 13 14:25:51 2023
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 470.182.03 Driver Version: 470.182.03 CUDA Version: 12.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Tesla T4 On | 00000000:00:1E.0 Off | 0 |
| N/A 42C P8 11W / 70W | 0MiB / 15109MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
こんなログが出ています。GPUを使えているようです。
Podのtolerationsが自動で付与される仕組み
さて、ここで一つ疑問が湧きました。KarpenterのNodePoolにtaintsを設定しているので、GPUを使いたいPodには対応するtolerationsを設定する必要があります。そうでなければ目当てのNodePoolを使ってくれません。動作確認したときにapplyしたPodのマニフェストにはtolerationsが書かれていませんが、GPUノードは立ち上がってきました。何故でしょうか。Podをdescribeしてみます。
$ kubectl describe pod nvidia-smi
...
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
nvidia.com/gpu:NoSchedule op=Exists
...
tolerationsにnvidia.com/gpu:NoSchedule op=Exists
が存在しています。自動で付与されていそうな挙動です。
これは、Admission ControllerのExtendedResourceToleration
プラグインによるものです。今回の例では、NVIDIA GPUの拡張リソースnvidia.com/gpu
を設定したPodが作成されるときに、ExtendedResourceToleration
プラグインが自動で下記のtolerationsをPodに付与します。
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
便利ですね。EKSではKubernetes v1.19からこのプラグインがデフォルトで有効になっています。
参考
Discussion