

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

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

# Terraform を使用して AI/ML ワークロード用の Amazon EKS クラスターを設定する
<a name="ml-cluster-setup-tf"></a>

**ヒント**  
 今後開催予定の Amazon EKS AI/ML ワークショップに[登録](https://events.eksworkshop.com/workshops/genai/)してください。

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

これらの機能が EKS クラスターで EC2 インスタンスをプロビジョニングおよび自動スケールする方法の詳細については、[EKS Auto Mode](https://docs.aws.amazon.com/eks/latest/userguide/automode.html) と [Karpenter](https://karpenter.sh/docs/) のドキュメントを参照してください。

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

![<shared id= を示す高レベルのアーキテクチャ](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/ml-cluster-setup-tf-architecture.png)


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

## 前提条件
<a name="cluster-setup-tf-prerequisites"></a>

**重要**  
EKS クラスター、GPU インスタンス、Application Load Balancer、Amazon Managed Service for Prometheus など、このチュートリアルで作成するリソースには料金が発生します。終了したリソースは、継続的な料金の発生を回避するために削除してください。
+ Terraform >= 1.15.0。セットアップ手順については、「[Terraform のインストール](https://developer.hashicorp.com/terraform/install)」を参照してください。
+  `kubectl` >= 1.36。セットアップ手順については、「[`kubectl` および `eksctl` のセットアップ](install-kubectl.md)」を参照してください。
+  AWS CLI >= 2.27。セットアップ手順については、「[AWS CLI の最新バージョンのインストールまたは更新](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html)」を参照してください。
+  `jq`。セットアップ手順については、「[Download jq](https://jqlang.github.io/jq/download/)」を参照してください。

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

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

## ステップ 1: Terraform コードをダウンロードしてデプロイする
<a name="cluster-setup-tf-deploy"></a>

このチュートリアルでは、[sample-eks-docs](https://github.com/aws-samples/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/`) – コア[ネットワーク、ストレージ、ロードバランシングアドオン](https://docs.aws.amazon.com/eks/latest/userguide/eks-add-ons.html#addon-consider-auto)に加えて、EKS Auto Mode には、EKS ノードモニタリングエージェント、自動ノード修復、高速コンテナプル用の [SOCI](https://github.com/awslabs/soci-snapshotter) スナップショッター、デフォルトの 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 つのクラスターオプションを並べて比較したもの](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/ml-cluster-setup-cli-cluster-options.png)


**Grafana は、デフォルトの認証情報を使用して HTTP 経由でパブリックにアクセス可能です**  
Grafana ALB `Ingress` ではデフォルトで `var.my_cidr` が `0.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/scheme` を `internal` (VPC または接続された VPN 内からのみ到達可能) に変更し、TLS 証明書を追加します。

### クラスターをデプロイする
<a name="_deploy_the_cluster"></a>

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

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

Terraform コードは、ターゲットリージョン内の利用可能なすべてのアベイラビリティーゾーンを使用しますが、[Amazon EKS はこれらのゾーンでのコントロールプレーン配置をサポートしていない](https://repost.aws/knowledge-center/eks-cluster-creation-errors)ため、`use1-az3`、`usw1-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 出力を確認する
<a name="_review_the_terraform_outputs"></a>

適用が完了すると、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 を設定する
<a name="_configure_kubectl"></a>

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

```
eval "$(terraform output -raw configure_kubectl)"
```

### クラスターを確認する
<a name="_verify_the_cluster"></a>

------
#### [ 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 を作成する
<a name="cluster-setup-tf-create-gpu-nodepool"></a>

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-purpose` と `system` 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-inf` の `0` ノードが表示されます。EKS Auto Mode と Karpenter は、保留中の Pod がノードを必要とする場合にのみノードを起動します。

## ステップ 3: サンプル Pod でテストする
<a name="cluster-setup-tf-test-with-a-sample-pod"></a>

`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`](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateFleet.html) API を使用します。インスタンスは、`spot` ライフサイクルとともに EC2 インスタンスコンソールに表示されます。

## ステップ 4: NodePool にリザーブドキャパシティを追加する (オプション)
<a name="cluster-setup-tf-attach-odcr"></a>

ステップ 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 を更新して、キャパシティタイプの要件に `reserved`、`spot`、`on-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
```

### リザーブドキャパシティが最初に使用され、Spot フォールバックがあることを確認する
<a name="cluster-setup-tf-step4-validate"></a>

変更を適用したら、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
```

## モニタリング
<a name="cluster-setup-tf-monitoring"></a>

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 が含まれます。

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

### モニタリングポッドの検証
<a name="_verify_monitoring_pods"></a>

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

```
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 にアクセスする
<a name="cluster-setup-tf-grafana"></a>

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
```

### メトリクスパイプラインを検証する
<a name="_verify_the_metrics_pipeline"></a>

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

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

    **Grafana で AMP データソースを検証する**   
![Amazon-Managed-Prometheus がデフォルトのデータソースとしてリストされている Grafana Connections ページ](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/ml-cluster-setup-cli-prometheus-ds-validate.png)

1. **[ドリルダウン] > [メトリクス]** に移動し、`up` メトリクスを検索します。クラスターのスクレイプターゲットの結果が表示されます。

    **Grafana で `up` メトリクスを検証する**   
![アクティブなスクレイプターゲットを示す緑色のステータスバーが付いたアップメトリクスが表示された Grafana ドリルダウンメトリクスページ](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/ml-cluster-setup-cli-prometheus-metrics-validate.png)

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

### DCGM GPU メトリクスを検証する
<a name="_validate_dcgm_gpu_metrics"></a>

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 ドリルダウンメトリクスページ](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/ml-cluster-setup-cli-dcgm-metrics-validate.png)


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

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

![GPU 使用率、GPU 平均温度、GPU Framebuffer Mem Used、GPU Power Total パネルを示す Grafana NVIDIA DCGM Exporter ダッシュボード](http://docs.aws.amazon.com/ja_jp/eks/latest/userguide/images/ml-cluster-setup-cli-dcgm-dashboard.png)


## モデルの重み S3 バケット
<a name="cluster-setup-tf-model-bucket"></a>

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

### バケットを検証する
<a name="_verify_the_bucket"></a>

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
}
```

#### ポッドからの S3 アクセスを検証する
<a name="cluster-setup-tf-s3-validate"></a>

`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
```

## 次のステップ
<a name="cluster-setup-tf-next-steps"></a>

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

## クリーンアップ
<a name="cluster-setup-tf-cleanup"></a>

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

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

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

### クラスターを破棄せずにキャパシティ予約をキャンセルする
<a name="cluster-setup-tf-cleanup-cancel-reservation"></a>

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

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

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

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

### クラスターとすべての Terraform マネージドリソースを破棄する
<a name="cluster-setup-tf-cleanup-destroy"></a>

破棄する前に 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 の確認プロンプトをスキップします。

### 予約が削除されたことを確認する
<a name="cluster-setup-tf-cleanup-verify"></a>

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

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

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