View a markdown version of this page

Terraform を使用して AI/ML ワークロード用の Amazon EKS クラスターを設定する - Amazon EKS

このページの改善にご協力ください

このユーザーガイドに貢献するには、すべてのページの右側のペインにある「GitHub でこのページを編集する」リンクを選択してください。

Terraform を使用して AI/ML ワークロード用の Amazon EKS クラスターを設定する

ヒント

今後開催予定の Amazon EKS AI/ML ワークショップに登録してください。

このセクションでは、Terraform を使用して Amazon EKS でトレーニングまたは推論ワークロードを実行するために必要なインフラストラクチャを作成する手順について説明します。ステップには、EKS クラスターの作成、EKS Auto Mode または Karpenter を使用した GPU 対応ノードの作成、Prometheus と Grafana を使用したモニタリングスタック、モデルの重み付け用の Amazon S3 ストレージが含まれます。

これらの機能が EKS クラスターで EC2 インスタンスをプロビジョニングおよび自動スケールする方法の詳細については、EKS Auto ModeKarpenter のドキュメントを参照してください。

高レベルアーキテクチャとワークフロー

<shared id= を示す高レベルのアーキテクチャ

この図は、このセクションのセットアップの AWS 高レベルアーキテクチャを示しています。

前提条件

重要

EKS クラスター、GPU インスタンス、Application Load Balancer、Amazon Managed Service for Prometheus など、このチュートリアルで作成するリソースには料金が発生します。終了したリソースは、継続的な料金の発生を回避するために削除してください。

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

terraform --version aws --version kubectl version --client jq --version

ステップ 1: Terraform コードをダウンロードしてデプロイする

このチュートリアルでは、sample-eks-docs AWS Samples GitHub リポジトリの Terraform コードを使用します。作業ディレクトリにリポジトリのクローンを作成します:

git clone git@github.com:aws-samples/sample-eks-docs.git cd sample-eks-docs/ai-ml/set-up-cluster

リポジトリには、先ほど変更した ai-ml/set-up-cluster/ ディレクトリの下に次の構造があります。

set-up-cluster/
├── scripts/
│   └── cleanup.sh
└── terraform/
    ├── auto-mode/
    └── karpenter/

リポジトリには 2 つのデプロイパスがあります。ガイド全体で 1 つのオプションのみを選択し、それを使用します。

  • EKS Auto Mode (terraform/auto-mode/) – コアネットワーク、ストレージ、ロードバランシングアドオンに加えて、EKS Auto Mode には、EKS ノードモニタリングエージェント、自動ノード修復、高速コンテナプル用の SOCI スナップショッター、デフォルトの NodeClass の GPU 準備など、トレーニングおよび推論ワークロード向けの以下の機能が含まれており、管理されています。NVIDIA デバイスプラグインは、EKS Auto Mode が GPU 対応ノードに使用する Bottlerocket 高速 AMI に含まれています。

  • セルフマネージド Karpenter (terraform/karpenter/) – EKS Auto Mode のない EKS クラスターでは、Terraform コードがトレーニングおよび推論ワークロードに必要なコンポーネントをインストールおよび設定します。これには、ネットワーキングアドオン (VPC CNI、CoreDNS、kube-proxy)、Karpenter、EKS ノードモニタリングエージェント、NVIDIA デバイスプラグイン、高速コンテナプル用の SOCI スナップショットターが含まれます。

重要

EKS Auto Mode またはセルフマネージド Karpenter を選択し、ガイド全体で使用します。ストリームの途中で切り替えるには、クラスターを破棄して最初からやり直す必要があります。

EKS クラスターオプション: EKS Auto Mode とセルフマネージド Karpenter

NodePool を備えた EKS Auto Mode クラスターと、セルフマネージド Karpenter、CoreDNS、VPC CNI、NVIDIA デバイスプラグイン、EKS Pod Identity エージェント、Node Monitoring Agent、kube-proxy、NodeClass および NodePool を備えた EKS 標準クラスターの 2 つのクラスターオプションを並べて比較したもの
Grafana は、デフォルトの認証情報を使用して HTTP 経由でパブリックにアクセス可能です

Grafana ALB Ingress ではデフォルトで var.my_cidr0.0.0.0/0 になります。これにより、Grafana はデフォルトの管理者認証情報を使用してプレーン HTTP 経由でパブリックインターネットに公開されます。自動スキャナーは、数分以内にパブリックロードバランサーを検出します。独自の IP アドレスで var.my_cidr を上書きしてアクセスを制限する必要があります

export MY_CIDR="$(curl -s https://checkip.amazonaws.com)/32" terraform apply -var "my_cidr=${MY_CIDR}"

送信元 IP 許可リストは、完全な保護ではなく、最小限の保護手段として扱います。また、最初のログイン後にデフォルトの Grafana 管理者パスワードを変更します。体制を強化するには、alb.ingress.kubernetes.io/schemeinternal (VPC または接続された VPN 内からのみ到達可能) に変更し、TLS 証明書を追加します。

クラスターをデプロイする

選択したパスのディレクトリに移動し、Terraform を初期化して適用します。

どちらのバリアントもデフォルトで us-east-2 リージョンになります。別のリージョンにデプロイするには、次のステップで terraform apply コマンドに -var "region=region-code" を追加します。ここで、region-code はデプロイする AWS リージョンです。

Terraform コードは、ターゲットリージョン内の利用可能なすべてのアベイラビリティーゾーンを使用しますが、Amazon EKS はこれらのゾーンでのコントロールプレーン配置をサポートしていないため、use1-az3usw1-az2、および cac1-az3 は除外されます。

EKS Auto Mode
cd terraform/auto-mode terraform init terraform apply

このコマンドの完了には数分かかります。

Self-managed Karpenter
cd terraform/karpenter terraform init terraform apply

このコマンドの実行には約 15 分かかります。アドオンと Karpenter コントローラーのホスティング専用のマネージドノードグループを持つ EKS クラスターを作成します。Terraform は、スポット中断キューが有効で、NodeRepair および StaticCapacity 機能ゲートがオンになっている Karpenter をインストールします。また、NVIDIA デバイスプラグイン、AWS Load Balancer Controller、モニタリングスタックもインストールします。

Terraform 出力を確認する

適用が完了すると、Terraform は次の出力を表示します (値は設定によって異なります)。

Apply complete! Resources: 74 added, 0 changed, 0 destroyed.

Outputs:

cluster_name         = "ai-eks-docs"
configure_kubectl    = "aws eks update-kubeconfig --region us-east-2 --name ai-eks-docs --alias ai-eks-docs"
configure_model_bucket = "export MODEL_BUCKET=ai-eks-docs-models-20250612abc1"
model_bucket         = "ai-eks-docs-models-20250612abc1"
node_iam_role_name   = "ai-eks-docs-eks-auto-20250612..."
region               = "us-east-2"

configure_kubectl 出力は、クラスターに kubectl を向けるすぐに実行できるコマンドです。model_bucket 出力には、モデルの重みの S3 バケット名が含まれます。node_iam_role_name 出力には、ノードが使用する IAM ロールが表示されます。

kubectl を設定する

新しいクラスターに kubectl を向けます。configure_kubectl 出力はすぐに実行できるコマンドです。

eval "$(terraform output -raw configure_kubectl)"

クラスターを確認する

EKS Auto Mode
kubectl get pods --all-namespaces

正常な出力:

NAMESPACE NAME READY STATUS RESTARTS AGE kube-system metrics-server-5db89f9ffd-h4mlr 1/1 Running 0 3m kube-system metrics-server-5db89f9ffd-nd748 1/1 Running 0 3m monitoring kube-prometheus-stack-grafana-ff9b5fd57-dh562 3/3 Running 0 3m monitoring kube-prometheus-stack-kube-state-metrics-5dcbfdf69b-wd6qn 1/1 Running 0 3m monitoring kube-prometheus-stack-operator-548c4f4485-m5wh2 1/1 Running 0 3m monitoring kube-prometheus-stack-prometheus-node-exporter-p2z7l 1/1 Running 0 3m monitoring prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 0 3m

EKS Auto Mode では、VPC CNI、kube-proxy、および CoreDNS はマネージドコンポーネントとして実行され、kube-system ではポッドとして表示されません。

Self-managed Karpenter
kubectl get pods --all-namespaces

予想される出力には、Karpenter、CoreDNS、kube-proxy、aws-node (VPC CNI)、EKS Pod Identity Agent、EKS ノードモニタリングエージェント、および NVIDIA デバイスプラグインが含まれます:

NAMESPACE NAME READY STATUS RESTARTS AGE kube-system aws-node-bzdcz 2/2 Running 0 5m kube-system aws-node-vkbhb 2/2 Running 0 5m kube-system coredns-7dbb8998cf-9b9wk 1/1 Running 0 5m kube-system coredns-7dbb8998cf-pwtjd 1/1 Running 0 5m kube-system ebs-csi-controller-748f54b69-8h7jv 6/6 Running 0 5m kube-system ebs-csi-controller-748f54b69-v2mv8 6/6 Running 0 5m kube-system eks-node-monitoring-agent-5qw4l 1/1 Running 0 5m kube-system eks-node-monitoring-agent-7lrtm 1/1 Running 0 5m kube-system eks-pod-identity-agent-ddvlv 1/1 Running 0 5m kube-system eks-pod-identity-agent-q4g29 1/1 Running 0 5m kube-system karpenter-898ff78-cndbd 1/1 Running 0 5m kube-system karpenter-898ff78-hfbhn 1/1 Running 0 5m kube-system kube-proxy-gcfnh 1/1 Running 0 5m kube-system kube-proxy-tktcf 1/1 Running 0 5m kube-system metrics-server-5b789db597-cm9qd 1/1 Running 0 5m kube-system metrics-server-5b789db597-w57b6 1/1 Running 0 5m kube-system nvidia-device-plugin-node-feature-discovery-gc-66cb7f5dc-xvftc 1/1 Running 0 5m kube-system nvidia-device-plugin-node-feature-discovery-master-854d6b5hnqtp 1/1 Running 0 5m monitoring kube-prometheus-stack-grafana-6797bcb59f-2zlwg 3/3 Running 0 5m monitoring kube-prometheus-stack-kube-state-metrics-8446bd549c-q6gjc 1/1 Running 0 5m monitoring kube-prometheus-stack-operator-5f499b784-vf5xb 1/1 Running 0 5m monitoring kube-prometheus-stack-prometheus-node-exporter-4c6wq 1/1 Running 0 5m monitoring kube-prometheus-stack-prometheus-node-exporter-l5t25 1/1 Running 0 5m monitoring prometheus-kube-prometheus-stack-prometheus-0 2/2 Running 0 5m

NVIDIA デバイスプラグインがインストールされていることを確認します。一致する amiFamily=al2023 ラベルを持つ GPU NodePool がプロビジョニングされるまで、ゼロデバイスプラグイン Pod は表示されません:

kubectl get daemonset nvidia-device-plugin -n kube-system

正常な出力:

NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE nvidia-device-plugin 0 0 0 0 0 amiFamily=al2023 5m

ステップ 2: 動的 GPU NodePool を作成する

GPU NodePools はオプトインです。デフォルトでは、terraform apply は GPU キャパシティや GPU 請求なしでクラスターとモニタリングスタックを作成します。GPU ノードをプロビジョニングするには、戦略名を付けて nodepools 変数を渡します。

On-Demand をフォールバックとした Spot キャパシティを使用して、世代が 4 より大きい G ファミリー GPU インスタンスをプロビジョニングする spot-ondemand 戦略を有効にします:

terraform apply -var 'nodepools={"spot-ondemand"={}}'

このコマンドは、nodepools/spot-ondemand/ ディレクトリから NodePool テンプレートと NodeClass テンプレートを適用します。どちらのパスも同じ NodePool API を使用しますが、NodePool が参照する NodeClass が異なります。

EKS Auto Mode

NodePool は、Bottlerocket 高速 AMI、NVIDIA ドライバー、NVIDIA デバイスプラグイン、SOCI 並列プルを既に選択しているマネージド default NodeClass を参照します。このspot-ondemand 戦略は、このパスに独自の NodeClass を提供しません。

NodePool を検証します:

kubectl get nodepools,nodeclasses

正常な出力 gpu-inf NodePool は組み込みの general-purposesystem NodePool を結合し、3 つすべてがマネージド default NodeClass を参照します:

NAME NODECLASS NODES READY AGE nodepool.karpenter.sh/general-purpose default 0 True 12m nodepool.karpenter.sh/gpu-inf default 0 True 20s nodepool.karpenter.sh/system default 1 True 12m NAME ROLE READY AGE nodeclass.eks.amazonaws.com/default ai-eks-docs-eks-auto-... True 12m
Self-managed Karpenter

Terraform は、NodePool と一緒にカスタム gpu-inf EC2NodeClass を適用します。EC2NodeClass は、EKS 最適化 AL2023 AMI エイリアスをピン留めし、FastImagePull 機能ゲートを介して SOCI を有効にし、コンテナ化されたイメージキャッシュをローカル NVMe に移動するように instanceStorePolicy: RAID0 を設定します。

NodePool と EC2NodeClass を検証します:

kubectl get nodepools,ec2nodeclasses

正常な出力 gpu-inf ペアは、Terraform が GPU 以外のワークロード用に作成する general-purpose NodePool と EC2NodeClass を結合します:

NAME NODECLASS NODES READY AGE nodepool.karpenter.sh/general-purpose general-purpose 1 True 14m nodepool.karpenter.sh/gpu-inf gpu-inf 0 True 25s NAME READY AGE ec2nodeclass.karpenter.k8s.aws/general-purpose True 14m ec2nodeclass.karpenter.k8s.aws/gpu-inf True 25s

GPU ワークロードがスケジュールされるまで、両方のパスに gpu-inf0 ノードが表示されます。EKS Auto Mode と Karpenter は、保留中の Pod がノードを必要とする場合にのみノードを起動します。

ステップ 3: サンプル Pod でテストする

nvidia-smi Pod を使用して GPU NodePool のセットアップをテストします:

cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: nvidia-smi labels: guide: ai-eks-docs spec: restartPolicy: OnFailure tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" containers: - name: nvidia-smi image: public.ecr.aws/amazonlinux/amazonlinux:2023-minimal command: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 1 EOF

Pod がスケジュールされ、正常に完了していることを確認します:

kubectl get pods nvidia-smi

正常な出力:

NAME         READY   STATUS      RESTARTS   AGE
nvidia-smi   0/1     Completed   0          67s

STATUS: Completed は、nvidia-smi コマンドが実行され、終了したことを意味します。Pod ログをチェックして、ノードによって検出された GPU を確認します:

kubectl logs nvidia-smi

正常な出力:

+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.159.03             Driver Version: 580.159.03     CUDA Version: 13.0     |
+-----------------------------------------+------------------------+----------------------+
| 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  NVIDIA L4                      On  |   00000000:31:00.0 Off |                    0 |
| N/A   41C    P8             13W /   72W |       0MiB /  23034MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

出力には、GPU モデル、ドライバーバージョン、CUDA バージョン、利用可能なメモリが表示されます。この例では、Karpenter は 24 GB のメモリを持つ NVIDIA L4 GPU を持つ G6 インスタンスをプロビジョニングしました。GPU モデルとメモリは、Karpenter が選択するインスタンスタイプによって異なります。G5 インスタンスには NVIDIA A10G GPU (24 GB)、G6 インスタンスには NVIDIA L4 GPU (24 GB)、G6e インスタンスには NVIDIA L40S GPU (48 GB) があります。

Karpenter と Kubernetes スケジューラがどのように連携してノードをプロビジョニングし、ポッドを配置したかを理解するには、ポッドのライフサイクルイベントを確認します。

kubectl describe pod nvidia-smi

正常な出力:

Events:
  Type     Reason            Age   From                   Message
  ----     ------            ----  ----                   -------
  Warning  FailedScheduling  75s   default-scheduler      0/2 nodes are available: 2 node(s) had untolerated taint(s).
  Normal   Nominated         74s   eks-auto-mode/compute  Pod should schedule on: nodeclaim/gpu-inf-z6q75
  Normal   Scheduled         35s   default-scheduler      Successfully assigned default/nvidia-smi to i-0eb897a8302551589
  Normal   Pulling           27s   kubelet                spec.containers{nvidia-smi}: Pulling image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal"
  Normal   Pulled            22s   kubelet                spec.containers{nvidia-smi}: Successfully pulled image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" in 5.625s (5.626s including waiting). Image size: 37440620 bytes.
  Normal   Created           22s   kubelet                spec.containers{nvidia-smi}: Container created
  Normal   Started           21s   kubelet                spec.containers{nvidia-smi}: Container started

これらのイベントは、Pod スケジューリングシーケンスを示しています。まず、GPU ノードが存在しないため、Pod はスケジュールに失敗します (FailedScheduling)。Karpenter が新しい NodeClaim をノミネートします (Nominated)。ノードが準備完了になるとスケジューラが Pod を割り当てます (Scheduled)。その後、コンテナイメージがプルされて起動します。EKS Auto Mode には、G、P、Trn インスタンスに SOCI (Seekable OCI) 並列プルがデフォルトでインストールおよび設定されており、セルフマネージド Karpenter パスは FastImagePull 機能ゲートを介してそれを明示的に設定します。

注記

セルフマネージド Karpenter クラスターでは、Nominated イベントは eks-auto-mode/compute の代わりに karpenter/compute を表示します。

NodeClaim は、Karpenter が特定のノードをプロビジョニングするために作成するリクエストです。インスタンスタイプ、キャパシティタイプ、AZ、ノードの準備が整っているかどうかが表示されます:

kubectl get nodeclaims

正常な出力:

NAME            TYPE        CAPACITY   ZONE         NODE                  READY   AGE
gpu-inf-z6q75   g6.xlarge   spot       us-east-2a   i-0eb897a8302551589   True    5m

インスタンスタイプと AZ は異なります。世代が 4 より大きい G ファミリーインスタンスが対象です。

ヒント

ノードが表示されない場合は、容量不足エラーを確認します。

kubectl get events | grep InsufficientCapacityError

Karpenter は、使用できないサービスを 3 分間キャッシュします。NodePool で許可されたインスタンスタイプと AZ を拡張すると、ランディングキャパシティの可能性が高まります。

注記

Karpenter によって起動された Spot インスタンスは EC2 Spot Requests コンソールに表示されません。Karpenter は type: instant で EC2 CreateFleet API を使用します。インスタンスは、spot ライフサイクルとともに EC2 インスタンスコンソールに表示されます。

ステップ 4: NodePool にリザーブドキャパシティを追加する (オプション)

ステップ 2 の GPU NodePool は Spot インスタンスまたは On-Demand インスタンスを動的にプロビジョニングしますが、一部のユースケースでは保証されたキャパシティが必要です。オンデマンドキャパシティ予約 (ODCR) を作成して、GPU キャパシティが必要なときに使用可能になるようにできます。

Terraform では、1 つのコマンドで ODCR が作成され、予約をタグで参照するカスタム NodeClass が作成され、NodePool が更新されてキャパシティタイプ reserved として含まれます。Terraform は nodepool=reserved-spot-ondemand で ODCR にタグ付けし、NodeClass はそのタグでそれを選択します。

警告

次のコマンドは、ノードが実行されているかどうかにかかわらず、terraform destroy またはクリーンアップスクリプトを使用して破棄するまで、すぐに請求が開始され、請求が継続される ODCR を作成します。

デフォルト (g6e.4xlarge、1 個のインスタンス、最初のクラスター AZ) を使用します。

terraform apply -var 'nodepools={"reserved-spot-ondemand"={reservation={}}}'

インスタンスタイプ、カウント、AZ を選択します。

terraform apply -var 'nodepools={"reserved-spot-ondemand"={reservation={instance_type="g6e.2xlarge",instance_count=1,az="us-east-2a"}}}'

reservation オブジェクトは以下のフィールドをサポートします:

  • instance_type – 予約する GPU インスタンスタイプ。デフォルト: g6e.4xlarge

  • instance_count – 予約するインスタンスの数。デフォルト: 1

  • az – 予約のアベイラビリティーゾーン。デフォルト: "" (最初のクラスター AZ を使用します)。

重要

spot-ondemand および reserved-spot-ondemand 戦略は相互に排他的です。nodepools 変数では、最大 1 個を有効にできます。以前にステップ 2 で spot-ondemand を使用した場合は、どちらも同じ gpu-inf NodePool を管理するため、reserved-spot-ondemand コマンドによって置き換えられます。

InsufficientInstanceCapacity エラーが発生した場合、指定された AZ で予約を満たすことはできません。Terraform オペレーション (Ctrl+C) をキャンセルしてから、別の az 値で再実行します:

terraform apply -var 'nodepools={"reserved-spot-ondemand"={reservation={instance_type="g6e.4xlarge",az="us-east-2b"}}}'

適用後、Terraform は NodePool を更新して、キャパシティタイプの要件に reservedspoton-demand を含めます。Karpenter は、reserved を最もコスト効率の高いオプションとして扱い、最初に起動します。予約がいっぱいになると、スポットまたはオンデマンドにフォールバックします。

EKS Auto Mode パスで、Terraform は capacityReservationSelectorTerms を介してタグで ODCR を参照するカスタム gpu-inf NodeClass (バンドルされた default NodeClass が読み取り専用であるため) を作成します。セルフマネージド Karpenter パスで、Terraform は gpu-inf EC2NodeClass を capacityReservationSelectorTerms で再適用し、reserved を含めるように NodePool を更新します。

ODCR が作成されたことを確認します:

aws ec2 describe-capacity-reservations \ --filters "Name=state,Values=active" "Name=tag:nodepool,Values=reserved-spot-ondemand" \ --query 'CapacityReservations[0].{Id:CapacityReservationId,State:State,InstanceType:InstanceType,AvailableCount:AvailableInstanceCount}' \ --output table \ --region $(terraform output -raw region)

NodeClass が ODCR を参照していることを確認します:

EKS Auto Mode
kubectl get nodeclasses gpu-inf -o yaml | grep 'id: cr-.*'

正常な出力:

id: cr-xxxxxxxxxxxxxxxxx
Self-managed Karpenter
kubectl get ec2nodeclasses gpu-inf -o yaml | grep 'id: cr-.*'

正常な出力:

id: cr-xxxxxxxxxxxxxxxxx

NodePool の準備が整っていることを確認します:

kubectl get nodepools gpu-inf

正常な出力:

NAME      NODECLASS   NODES   READY   AGE
gpu-inf   gpu-inf     0       True    30s

変更を適用したら、Karpenter がリザーブドキャパシティを優先し、スポットまたはオンデマンドにフォールバックすることを確認します。ポッドごとに 1 GPU をリクエストする 2 レプリカデプロイをデプロイします。ODCR は 1 個のインスタンス (1 GPU) 用であるため、最初のポッドは Karpenter をトリガーしてリザーブドノードを起動します。2 番目のポッドはリザーブドノードに収まらず、Karpenter がスポットキャパシティまたはオンデマンドキャパシティから別のノードを起動するようにトリガーします。

cat << 'EOF' | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: gpu-overflow-test labels: guide: ai-eks-docs spec: replicas: 2 selector: matchLabels: app: gpu-overflow-test template: metadata: labels: app: gpu-overflow-test guide: ai-eks-docs spec: tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - name: nvidia-smi image: public.ecr.aws/amazonlinux/amazonlinux:2023-minimal command: ["sh", "-c", "nvidia-smi && sleep infinity"] resources: limits: nvidia.com/gpu: 1 EOF

実行して終了したステップ 3 の nvidia-smi テスト Pod とは異なり、このデプロイでは Pod の実行 (sleep infinity) が維持されるため、Pod は GPU を保持し、ノードが統合されるのを防ぎます。

異なるノードでスケジュールされたポッドを確認します。

kubectl get pods -l app=gpu-overflow-test -o wide

正常な出力:

NAME                                 READY   STATUS    RESTARTS   AGE     IP            NODE                  NOMINATED NODE   READINESS GATES
gpu-overflow-test-55d55ff5b9-dvg52   1/1     Running   0          4m42s   10.0.75.210   i-08741a36089ff2088   <none>           <none>
gpu-overflow-test-55d55ff5b9-hw4m9   1/1     Running   0          4m43s   10.0.82.49    i-0f50cdbacb2017202   <none>           <none>

NodeClaims でキャパシティタイプを確認します。

kubectl get nodeclaims

正常な出力:

NAME            TYPE          CAPACITY   ZONE         NODE                  READY   AGE
gpu-inf-vw99m   g6e.4xlarge   reserved   us-east-2c   i-0f50cdbacb2017202   True    6m
gpu-inf-s65s6   g6.xlarge     spot       us-east-2b   i-08741a36089ff2088   True    5m59s

予約ノードが最初に起動され、その後に予約がいっぱいになるとスポットノードまたはオンデマンドノードが起動されます。

テストデプロイをクリーンアップする:

kubectl delete deployment gpu-overflow-test

モニタリング

Terraform は、ステップ 1 の terraform apply 中に完全なモニタリングスタックをプロビジョニング済みです。スタックには、Amazon Managed Service for Prometheus (AMP) ワークスペース、IAM ポリシー、EKS Pod Identity Associations for Prometheus リモート書き込みおよび Grafana クエリアクセス、kube-prometheus-stack Helm チャート (Prometheus、Grafana、kube-state-metrics、node-exporter)、GPU メトリクス用の NVIDIA DCGM Exporter が含まれます。

このセクションでは、デプロイされたモニタリングコンポーネントの検証について説明します。

モニタリングポッドの検証

すべてのモニタリングポッドの準備が整うまで待ちます。

kubectl wait --for=condition=Ready pod --all -n monitoring --timeout=300s kubectl get pods -n monitoring

正常な出力:

NAME                                                       READY   STATUS    RESTARTS   AGE
kube-prometheus-stack-grafana-7c58f54f77-rftrj             3/3     Running   0          5m
kube-prometheus-stack-kube-state-metrics-d68dcbc84-5smxq   1/1     Running   0          5m
kube-prometheus-stack-operator-5895df479f-ttm47            1/1     Running   0          5m
kube-prometheus-stack-prometheus-node-exporter-t9q7s       1/1     Running   0          5m
kube-prometheus-stack-prometheus-node-exporter-x6vfb       1/1     Running   0          5m
prometheus-kube-prometheus-stack-prometheus-0              2/2     Running   0          5m

Grafana にアクセスする

Grafana は、var.my_cidr で設定した CIDR に制限された、インターネット向け AWS Application Load Balancer (ALB) を介して公開されます。ロードバランサー URL を出力します (ALB がプロビジョニングするには 1〜2 分かかります):

echo "http://$(kubectl get ingress kube-prometheus-stack-grafana -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"

ブラウザで URL を開きます。以下のコマンドからユーザー名 admin とパスワードを使用してログインします。

kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo

メトリクスパイプラインを検証する

メトリクスパイプラインがエンドツーエンドで機能していることを確認するには:

  1. [接続] > [データソース] に移動し、Amazon-Managed-Prometheus がデフォルトのデータソースとしてリストされていることを確認します。

    Grafana で AMP データソースを検証する

    Amazon-Managed-Prometheus がデフォルトのデータソースとしてリストされている Grafana Connections ページ
  2. [ドリルダウン] > [メトリクス] に移動し、up メトリクスを検索します。クラスターのスクレイプターゲットの結果が表示されます。

    Grafana で up メトリクスを検証する

    アクティブなスクレイプターゲットを示す緑色のステータスバーが付いたアップメトリクスが表示された Grafana ドリルダウンメトリクスページ

up が結果を表示する場合、パイプライン (クラスター → Prometheus → AMP → Grafana) は機能しています。

DCGM GPU メトリクスを検証する

DCGM Exporter DaemonSet は GPU ノードで実行され、GPU 使用率、メモリ、温度、消費電力、NVLink 帯域幅、テンソルアクティビティメトリクスをレポートします。

DCGM Exporter DaemonSet を確認します。

kubectl get daemonset dcgm-exporter -n monitoring

GPU ノードが実行されると (ステップ 2 またはステップ 4 から)、1 つ以上の準備完了の Pod が表示されます。DCGM メトリクスを検証するには、Grafana で [ドリルダウン] > [メトリクス] に移動し、DCGM_ を検索します。

Grafana で DCGM メトリクスを検証する

DCGM_FI_DEV_ECC_SBE_VOL_TOTAL、DCGM_FI_DEV_ENC_UTIL、DCGM_FI_DEV_FB_FREE、DCGM_FI_DEV_FB_USED などの GPU メトリクスを示す DCGM_ でフィルタリングされた Grafana ドリルダウンメトリクスページ

ダッシュボードを表示するには、[ダッシュボード] > [GPU モニタリング] > [NVIDIA DCGM Exporter ダッシュボード] に移動します。

Grafana の NVIDIA DCGM Exporter ダッシュボード

GPU 使用率、GPU 平均温度、GPU Framebuffer Mem Used、GPU Power Total パネルを示す Grafana NVIDIA DCGM Exporter ダッシュボード

モデルの重み S3 バケット

Terraform は、モデルの重みを保存するための Amazon S3 バケット、default 名前空間の model-storage-sa ServiceAccount、バケットを対象とする IAM ポリシー、およびそれらをリンクする EKS Pod Identity の関連付けを既に作成しています。serviceAccountName: model-storage-sa を設定したワークロード Pod は、バケットを読み書きできます。

バケットを検証する

Terraform 出力からバケット名を取得します:

MODEL_BUCKET=$(terraform output -raw model_bucket) echo ${MODEL_BUCKET}

バケットが存在することを確認します:

aws s3api head-bucket --bucket ${MODEL_BUCKET}

正常な出力:

{
    "BucketArn": "arn:aws:s3:::ai-eks-docs-models-20250612abc1",
    "BucketRegion": "us-east-2",
    "AccessPointAlias": false
}

model-storage-sa ServiceAccount を使用して AWS CLI イメージで 1 回限りのポッドを実行し、EKS Pod Identity が正しく設定され、S3 アクセスが機能することを確認します。

cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: s3-test labels: guide: ai-eks-docs spec: serviceAccountName: model-storage-sa containers: - name: aws-cli image: public.ecr.aws/aws-cli/aws-cli:2.27.0 command: - sh - -c - | echo "=== Caller Identity ===" aws sts get-caller-identity echo "" echo "=== S3 Write Test ===" echo "pod identity works" | aws s3 cp - s3://${MODEL_BUCKET}/test.txt echo "" echo "=== S3 List Test ===" aws s3 ls s3://${MODEL_BUCKET}/ echo "" echo "=== S3 Delete Test ===" aws s3 rm s3://${MODEL_BUCKET}/test.txt restartPolicy: Never EOF

ポッドが完了するのを待ち、ログを確認します。

kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/s3-test --timeout=300s kubectl logs s3-test

正常な出力:

=== Caller Identity ===
{
    "UserId": "AROA...:eks-ai-eks-docs-model-s-...",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/ai-eks-docs-models-.../eks-ai-eks-docs-..."
}

=== S3 Write Test ===
upload: - to s3://ai-eks-docs-models-20250612abc1/test.txt

=== S3 List Test ===
2026-07-15 12:00:00         19 test.txt

=== S3 Delete Test ===
delete: s3://ai-eks-docs-models-20250612abc1/test.txt

発信者 ID は、EKS Pod Identity を介してポッドがモデルストレージロールを引き受けたことを確認します。S3 コマンドは読み取りおよび書き込みアクセスを確認します。

テストポッドをクリーンアップします。

kubectl delete pod s3-test

次のステップ

クラスターの準備ができたら、「モデルのロードと提供」に進み、大規模な言語モデルをデプロイし、推論エンドポイントとやり取りできます。

クリーンアップ

ヒント

このガイドの次のセクションに進む場合は、完全なクリーンアップをスキップします。完了時にのみ実行します。

テストワークロードを削除して、Pod が GPU ノードを保持しないようにします:

kubectl delete pod nvidia-smi --ignore-not-found kubectl delete deployment gpu-overflow-test --ignore-not-found

ODCR をリリースして Spot キャパシティと On-Demand キャパシティにフォールバックするだけの場合は、nodepools 変数を spot-ondemand 戦略に切り替えます:

terraform apply -var 'nodepools={"spot-ondemand"={}}'

これにより、NodePool のキャパシティタイプの要件から reserved がドロップされ、ODCR が破棄され、クラスター、モニタリングスタック、S3 バケットがそのまま残ります。

重要

予約をキャンセルしても、既に実行中のインスタンスは終了しません。これらのインスタンスは、終了するまで標準の On-Demand レートで実行され続けます。上記のように、GPU ワークロードを最初に削除して、予約がリリースされる前に予約済みノードがドレインされるようにします。

破棄する前に Karpenter マネージドノードをドレインして、処理中のノードのライフサイクルによって破棄がブロックされないようにします。ドレインを妨げる PodDisruptionBudgets を削除してから、NodeClaims を削除します。

kubectl delete pdb -A --all --ignore-not-found kubectl delete nodeclaim --all --wait=true --timeout=900s

次に、EKS クラスター、VPC、モニタリングスタック、NodePools と NodeClasses、S3 モデルバケット、ODCR など、Terraform が作成したすべてのリソースを破棄します。

terraform destroy
警告

モデルの重み S3 バケットは force_destroy = true で作成されるため、terraform destroy はアップロードしたモデルの重みとともにバケットを削除します。保持したいものは、最初に別の場所にコピーしてください。

注記

リポジトリには、上記のドレインおよび破棄ステップを実行してから、クラスター名でタグ付けされた孤立した EBS ボリュームをスイープする scripts/cleanup.sh ヘルパーも提供されています。適用元の terraform/<mode>/ ディレクトリ内から実行し、--auto-approve を渡して Terraform の確認プロンプトをスキップします。

クラスターにアクティブなキャパシティ予約が残っていないことを確認します。

aws ec2 describe-capacity-reservations \ --filters "Name=state,Values=active" "Name=tag:nodepool,Values=reserved-spot-ondemand" \ --query 'CapacityReservations[].CapacityReservationId' \ --output text

結果が空の場合、予約はアクティブではなく、それ以上の料金はかかりません。