🪚

EKSとKarpenterの環境でNVIDIA GPUノードを立ち上げる

2023/12/14に公開

この記事は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ノードに入ておく必要があります。

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

NVIDIA Device PluginをGPUノードで実行するためには、下記のものが必要になります。

  • NVIDIA drivers
  • nvidia-container-toolkit (nvidia-docker)
  • nvidia-container-runtime(デフォルトランタイム)

このうちnvidia-dockerについては、コンテナランタイムとしてcontainerdを使う場合は不要[1]なので、残り二つをノードに導入すればよさそうです。

AWS EKSの場合は上記二つを設定済みのEKS用AMIが用意されているので、こちらを使うのが一番簡単です。

https://docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html#gpu-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で使用する準備ができました。下記のページに動作確認方法が載っているので、確認してみます。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/eks-optimized-ami.html#gpu-ami

まず、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.

Schedule GPUs | Kubernetes

マニフェストを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からこのプラグインがデフォルトで有効になっています。

https://aws.amazon.com/jp/blogs/news/planning-kubernetes-upgrades-with-amazon-eks/

参考

https://medium.com/robovision-ai/use-kubernetes-extendedresourcetoleration-to-keep-out-non-gpu-pods-c180f5c5e76d
https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/

脚注
  1. NVIDIA Docker(NVIDIA Container Toolkit)からnvidia-container-runtime + containerdに移行するために知っておくべきこと - inductor's blog ↩︎

  2. Instance Types | Karpenter でラベル一覧を確認できます。 ↩︎

Discussion