

 **協助改進此頁面** 

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

若要為本使用者指南貢獻內容，請點選每個頁面右側面板中的**在 GitHub 上編輯此頁面**連結。

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 Terraform 為 AI/ML 工作負載設定 Amazon EKS 叢集
<a name="ml-cluster-setup-tf"></a>

**提示**  
 [註冊](https://events.eksworkshop.com/workshops/genai/)即將舉行的 Amazon EKS AI/ML 研討會。

本節將逐步引導您建立使用 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/) 的文件。 EC2 

 **高階架構和工作流程** 

![顯示 <shared id= 的高階架構](https://docs.aws.amazon.com/zh_tw/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。如需設定說明，請參閱[安裝](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html)。
+  `jq`。 如需設定說明，請參閱[下載 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/
```

儲存庫提供兩個部署路徑。請只選擇一個，並在整個指南中使用它。
+  **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 裝置外掛程式包含在 Bottlerocket 加速 AMI 中，EKS Auto Mode 用於已啟用 GPU 的節點。
+  **自我管理 Karpenter** (`terraform/karpenter/`) ： 在沒有 EKS Auto 模式的 EKS 叢集上，Terraform 程式碼會安裝和設定訓練和推論工作負載所需的元件。這包括聯網附加元件 (VPC CNI、CoreDNS、kube-proxy)、Karpenter、EKS 節點監控代理程式、NVIDIA 裝置外掛程式，以及用於快速容器提取的 SOCI 快照器。

**重要**  
選擇 EKS Auto Mode 或自我管理 Karpenter，並在整個指南中使用它。切換中串流需要銷毀叢集並重新開始。

 **EKS 叢集選項：EKS Auto Mode 和自我管理 Karpenter** 

![兩個叢集選項的Side-by-side比較：具有 NodePool 的 EKS Auto Mode 叢集，以及具有自我管理 Karpenter、CoreDNS、VPC CNI、NVIDIA 裝置外掛程式、EKS Pod Identity Agent、Node Monitoring Agent、kube-proxy 和 NodeClass 和 NodePool 的 EKS 標準叢集](https://docs.aws.amazon.com/zh_tw/eks/latest/userguide/images/ml-cluster-setup-cli-cluster-options.png)


**Grafana 可透過 HTTP 使用預設登入資料公開存取**  
Grafana ALB `Ingress`預設為 `var.my_cidr` `0.0.0.0/0`，這會透過使用預設管理員憑證的純 HTTP 向公有網際網路公開 Grafana。自動化掃描器會在幾分鐘內探索公有負載平衡器。**您必須**`var.my_cidr`使用自己的 IP 地址覆寫來限制存取：  

```
export MY_CIDR="$(curl -s https://checkip.amazonaws.com)/32"
terraform apply -var "my_cidr=${MY_CIDR}"
```
將 source-IP 允許清單視為最低防護，而非完整防護。也請在第一次登入後變更預設的 Grafana 管理員密碼。如需更強的姿勢，請將 `alb.ingress.kubernetes.io/scheme` 變更為 `internal`（只能從您的 VPC 或連線的 VPN 內存取），然後新增 TLS 憑證。

### 部署叢集
<a name="_deploy_the_cluster"></a>

將 變更為所選路徑的 目錄，初始化 Terraform，然後套用：

這兩個變體預設為 `us-east-2`區域。若要在不同區域中部署，請在下列步驟中將 `-var "region={{region-code}}"`新增至 `terraform apply`命令，其中 {{region-code}} 是 AWS 您要部署的區域。

Terraform 程式碼使用目標區域中所有可用的可用區域，但 `use1-az3`、 和 除外`usw1-az2`，`cac1-az3`因為 [Amazon EKS 不支援在這些區域中放置控制平面](https://repost.aws/knowledge-center/eks-cluster-creation-errors)。

------
#### [ 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 會在啟用 Spot 中斷佇列的情況下安裝 Karpenter，並開啟 `NodeRepair`和 `StaticCapacity`功能閘道。它也會安裝 NVIDIA 裝置外掛程式、 AWS Load Balancer控制器和監控堆疊。

------

### 檢閱 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`指向叢集的ready-to-run命令。`model_bucket` 輸出包含模型權重的 S3 儲存貯體名稱。`node_iam_role_name` 輸出會顯示節點使用的 IAM 角色。

### 設定 kubectl
<a name="_configure_kubectl"></a>

`kubectl` 指向新的叢集。`configure_kubectl` 輸出是ready-to-run命令：

```
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 模式中，VPC CNI、kube-proxy 和 CoreDNS 會以受管元件的形式執行，而不會在 中顯示為 Pod`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`變數。

啟用 `spot-ondemand`策略，該策略使用 Spot 容量搭配隨需做為備用，以佈建產生大於 4 的 G 系列 GPU 執行個體：

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

此命令會從 `nodepools/spot-ondemand/`目錄套用 NodePool 和 NodeClass 範本。兩個路徑都使用相同的 NodePool API，但它們在 NodeClass 中的 NodePool 參考不同。

------
#### [ EKS Auto Mode ]

NodePool 參考受管 `default` NodeClass，其已選取 Bottlerocket 加速 AMI、NVIDIA 驅動程式、NVIDIA 裝置外掛程式和 SOCI 平行提取。`spot-ondemand` 策略在此路徑上沒有自己的 NodeClass。

驗證 NodePool：

```
kubectl get nodepools,nodeclasses
```

預期的輸出結果。`gpu-inf` NodePool 會加入內建 `general-purpose` 和 `system` NodePools，而這三個都參考受管 `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 會`gpu-inf``EC2NodeClass`搭配 NodePool 套用自訂。將 EKS 最佳化 AL2023 AMI 別名`EC2NodeClass`接腳，透過`FastImagePull`功能閘道啟用 SOCI，並設定 `instanceStorePolicy: RAID0`將容器化映像快取移至本機 NVMe。

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

------

兩個路徑都會顯示 的`0`節點，`gpu-inf`直到排定 GPU 工作負載為止。EKS Auto Mode 和 Karpenter 只會在待定 Pod 需要時啟動節點。

## 步驟 3：使用範例 Pod 進行測試
<a name="cluster-setup-tf-test-with-a-sample-pod"></a>

使用 Pod 測試 GPU NodePool `nvidia-smi` 設定：

```
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 佈建了具有 NVIDIA L4 GPU 和 24 GB 記憶體的 G6 執行個體。GPU 模型和記憶體會根據 Karpenter 選取的執行個體類型而有所不同。G5 執行個體具有 NVIDIA A10G GPUs (24 GB)、G6 執行個體具有 NVIDIA L4 GPUs (24 GB)，而 G6e 執行個體具有 NVIDIA L40S GPUs (48 GB)。

若要了解 Karpenter 和 Kubernetes 排程器如何協調以佈建節點並放置 Pod，請檢查 Pod 的生命週期事件：

```
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 排程序列：Pod 一開始無法排程，因為 GPU 節點不存在 (`FailedScheduling`)，Karpenter 會指定新的 NodeClaim (`Nominated`)，排程器會在節點就緒時指派 Pod (`Scheduled`)，然後提取並啟動容器映像。EKS Auto Mode 預設會在 G、P 和 Trn 執行個體上安裝和設定 SOCI （可尋求 OCI) 平行提取，而自我管理 Karpenter 路徑會透過`FastImagePull`功能閘道明確設定。

**注意**  
在自我管理的 Karpenter 叢集上，`Nominated`事件會顯示 `karpenter/compute`而非 `eks-auto-mode/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 中允許的執行個體類型和可用AZs會增加登陸容量的機會。

**注意**  
Karpenter 啟動的 Spot 執行個體不會出現在 EC2 Spot 請求主控台中。Karpenter 搭配 使用 EC2 [`CreateFleet`](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateFleet.html) API`type: instant`。執行個體會以`spot`生命週期顯示在 EC2 執行個體主控台中。

## 步驟 4：將預留容量新增至 NodePool （選用）
<a name="cluster-setup-tf-attach-odcr"></a>

雖然步驟 2 的 GPU NodePool 動態佈建 Spot 或隨需執行個體，但某些使用案例需要保證容量。您可以建立隨需容量保留 (ODCR)，以確保 GPU 容量在需要時可用。

使用 Terraform 時，單一命令會建立 ODCR，這是依標籤參考保留的自訂 NodeClass，並更新 NodePool 以包含 `reserved`做為容量類型。Terraform 使用 標記 ODCR，`nodepool=reserved-spot-ondemand`NodeClass 會依該標籤選取它。

**警告**  
下列命令會建立 ODCR，該 會立即計費並繼續計費，直到您使用 `terraform destroy`或清除指令碼將其銷毀，無論節點是否在其上執行。

使用預設值 (`g6e.4xlarge`、1 個執行個體、第一個叢集 AZ)：

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

選擇執行個體類型、計數和可用區域：

```
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`變數中啟用一個 。如果您之前在步驟 2 `spot-ondemand`中使用過 ，`reserved-spot-ondemand`命令會取代它，因為兩者都會管理相同的 `gpu-inf` NodePool。

如果您收到`InsufficientInstanceCapacity`錯誤，則無法在指定的 AZ 中完成保留。取消 Terraform 操作 (Ctrl\+C)，然後使用不同的`az`值重新執行：

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

套用後，Terraform 會將 NodePool 更新為在容量類型需求`on-demand`中包含 `spot`、 `reserved`和 。Karpenter `reserved`將 視為最具成本效益的選項，並優先啟動。一旦保留已滿，就會回到 Spot 或隨需。

在 EKS Auto Mode 路徑上，Terraform 會建立自訂 `gpu-inf` NodeClass （因為 Bundled `default` NodeClass 為唯讀），透過 標籤參考 ODCR`capacityReservationSelectorTerms`。在自我管理的 Karpenter 路徑上，Terraform 會重新套用 `gpu-inf` EC2NodeClass，並`capacityReservationSelectorTerms`新增和更新 NodePool 以包含 `reserved`。

驗證是否已建立 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 是否排定保留容量的優先順序，並回到 Spot 或隨需。部署 2 個複本部署，每個 Pod 請求 1 個 GPU。ODCR 適用於 1 個執行個體 (1 個 GPU)，因此第一個 Pod 會觸發 Karpenter 啟動預留節點。第二個 Pod 無法容納在預留節點上，並觸發 Karpenter 從 Spot 或隨需容量啟動另一個節點。

```
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`)，以便保留 GPU 並防止節點合併。

驗證在不同節點上排程的 Pod：

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

預留節點會先啟動，然後在保留已滿時啟動 Spot 或隨需節點。

清除測試部署：

```
kubectl delete deployment gpu-overflow-test
```

## 監控
<a name="cluster-setup-tf-monitoring"></a>

在步驟 1 `terraform apply`中，Terraform 已在 期間佈建完整的監控堆疊。堆疊包含 Amazon Managed Service for Prometheus (AMP) 工作區、IAM 政策和 EKS Pod Identity Associations for Prometheus remote-write and Grafana 查詢存取、kube-prometheus-stack Helm Chart (Prometheus、Grafana、kube-state-metrics、 node-exporter)，以及適用於 GPU 指標的 NVIDIA DCGM Exporter。

本節涵蓋已部署監控元件的驗證。

### 驗證監控 Pod
<a name="_verify_monitoring_pods"></a>

等待所有監控 Pod 就緒：

```
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 透過 Internet-facing AWS Application Load Balancer (ALB) 公開，僅限於您在 中設定的 CIDR`var.my_cidr`。列印負載平衡器 URL （允許 ALB 佈建一兩分鐘）：

```
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 資料來源**   
![Grafana Connections 頁面顯示列為預設資料來源的 Amazon-Managed-Prometheus](https://docs.aws.amazon.com/zh_tw/eks/latest/userguide/images/ml-cluster-setup-cli-prometheus-ds-validate.png)

1. 導覽至**向下切入 > 指標**並搜尋指標。 `up`您應該會看到叢集湊集目標的結果。

    **驗證 `up` Grafana 中的指標**   
![Grafana 向下切入指標頁面顯示綠色狀態列的向上指標，指出作用中的抓取目標](https://docs.aws.amazon.com/zh_tw/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 匯出工具 DaemonSet：

```
kubectl get daemonset dcgm-exporter -n monitoring
```

一旦 GPU 節點執行 （從步驟 2 或步驟 4)，您應該會看到一或多個就緒的 Pod。若要驗證 DCGM 指標，請導覽至 Grafana 中的**向下切入 > 指標**，並搜尋 `DCGM_`。

 **在 Grafana 中驗證 DCGM 指標** 

![由 DCGM_ 篩選的 Grafana 向下切入指標頁面，顯示 GPU 指標，包括 DCGM_FI_DEV_ECC_SBE_VOL_TOTAL、DCGM_FI_DEV_ENC_UTIL、DCGM_FI_DEV_FB_FREE 和 DCGM_FI_DEV_FB_USED](https://docs.aws.amazon.com/zh_tw/eks/latest/userguide/images/ml-cluster-setup-cli-dcgm-metrics-validate.png)


若要檢視儀表板，請導覽至**儀表板 > GPU 監控 > NVIDIA DCGM 匯出工具儀表板**。

 **Grafana 中的 NVIDIA DCGM 匯出工具儀表板** 

![Grafana NVIDIA DCGM Exporter Dashboard 顯示 GPU 使用率、GPU 平均溫度、使用的 GPU Framebuffer Mem 和 GPU Power Total 面板](https://docs.aws.amazon.com/zh_tw/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 Association。設定的工作負載 Pod `serviceAccountName: model-storage-sa`可以讀取和寫入儲存貯體。

### 驗證儲存貯體
<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
}
```

#### 從 Pod 驗證 S3 存取
<a name="cluster-setup-tf-s3-validate"></a>

使用 `model-storage-sa` ServiceAccount 使用 AWS CLI 映像執行一次性 Pod，以確認 EKS Pod 身分已連線且 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
```

等待 Pod 完成並檢查日誌：

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

發起人身分確認 Pod 透過 EKS Pod 身分擔任模型儲存角色。S3 命令會確認讀取和寫入存取。

清除測試 Pod：

```
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 和隨需容量，請將`nodepools`變數切換回`spot-ondemand`策略：

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

這`reserved`會從 NodePool 容量類型需求中刪除，並銷毀 ODCR，並將叢集、監控堆疊和 S3 儲存貯體留在原處。

**重要**  
取消保留不會終止已在其中執行的執行個體。這些執行個體會持續以標準隨需費率執行，直到終止為止。首先刪除 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
```

然後銷毀建立的所有 Terraform，包括 EKS 叢集、VPC、監控堆疊、NodePools 和 NodeClasses、S3 模型儲存貯體，以及任何 ODCR：

```
terraform destroy
```

**警告**  
模型權重 S3 儲存貯體是使用 建立`force_destroy = true`的，因此 會`terraform destroy`刪除儲存貯體以及您上傳到儲存貯體的任何模型權重。先將您想要保留的任何內容複製到另一個位置。

**注意**  
儲存庫也會提供執行上述耗盡和銷毀步驟的`scripts/cleanup.sh`協助程式，然後掃描任何以叢集名稱標記的孤立 EBS 磁碟區。從您套用的`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
```

空白結果表示沒有作用中的保留，也不會產生其他費用。