View a markdown version of this page

CLI를 사용하여 AI/ML 워크로드용 Amazon EKS 클러스터 설정 - Amazon EKS

이 페이지 개선에 도움 주기

이 사용자 가이드에 기여하려면 모든 페이지의 오른쪽 창에 있는 GitHub에서 이 페이지 편집 링크를 선택합니다.

CLI를 사용하여 AI/ML 워크로드용 Amazon EKS 클러스터 설정

작은 정보

향후 예정된 Amazon EKS AI/ML 워크숍에 등록합니다.

이 섹션에서는 Amazon EKS에서 CLI 명령을 통해 훈련 또는 추론 워크로드를 실행하는 데 필요한 인프라를 생성하는 단계를 안내합니다. 이 단계에는 EKS 클러스터 생성, EKS 자율 모드 또는 Karpenter를 사용하는 GPU 지원 노드, Prometheus 및 Grafana를 사용하는 모니터링 스택, 모델 가중치를 위한 Amazon S3 스토리지가 포함됩니다.

이러한 기능이 EKS 클러스터에서 EC2 인스턴스를 프로비저닝하고 자동으로 규모를 조정하는 방법에 대한 자세한 내용은 EKS 자율 모드Karpenter 설명서를 참조하세요.

상위 수준 아키텍처 및 워크플로

Karpenter NodeClass 및 NodePool이 있는 EKS 클러스터, Amazon Managed Service for Prometheus에 쓰는 Grafana 및 Prometheus 모니터링 스택, 모델 가중치를 위한 Amazon S3 버킷, 번호가 매겨진 워크플로 단계를 보여주는 상위 수준 아키텍처

다이어그램은 이 섹션의 설정에 대한 AWS 상위 수준 아키텍처를 보여줍니다. 오른쪽의 번호가 매겨진 단계는 아래 단계에서 구성을 완료하는 순서를 나타냅니다.

사전 조건

  • kubectl >= 1.35. 설정 지침은 kubectl 및 eksctl 설정 섹션을 참조하세요.

  • AWS CLI >= 2.27. 설정 지침은 설치를 참조하세요.

  • Helm >= 3.14. 설정 지침은 헬름 설정을 참조하세요.

  • jq. 설정 지침은 jq 다운로드를 참조하세요.

  • eksctl >= 0.227.0. 설정 지침은 eksctl 설명서의 설치를 참조하세요.

eksctl 버전 확인:

eksctl version

0.227.0 이전 버전을 사용하는 경우 eksctl 설치 안내서에 따라 최신 릴리스로 업그레이드하세요.

환경 변수 설정

이러한 단계 동안 다음 클러스터 이름과 AWS 리전을 일관되게 유지하세요. 이를 변경하면 후속 명령이 잘못된 EKS 클러스터를 대상으로 지정할 수 있습니다.

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2

사용 가능한 모든 AZ를 사용하면 내결함성이 향상되고 GPU 용량을 확보할 가능성이 높아집니다.

export AZS=$(aws ec2 describe-availability-zones \ --region ${AWS_REGION} \ --query "AvailabilityZones[?ZoneId!='use1-az3' && ZoneId!='usw1-az2' && ZoneId!='cac1-az3'].ZoneName" \ --output text | tr '\t' ',') echo $AZS
중요

Amazon EKS는 use1-az3, usw1-az2, cac1-az3 가용 영역에서 컨트롤 플레인 배치를 지원하지 않으므로 해당 가용 영역은 제외됩니다. 이러한 영역 중 하나에 서브넷이 있는 클러스터를 생성하면 UnsupportedAvailabilityZoneException이 발생합니다.

예상 결과:

us-east-2a,us-east-2b,us-east-2c

출력의 AZ는 리전마다 다릅니다. 이 예제는 us-east-2 리전에 사용할 수 있는 AZ를 보여줍니다.

클러스터 및 GPU NodePool 생성

이 섹션에서는 다음 다이어그램에 나온 것처럼 EKS 클러스터와 GPU 지원 노드를 생성하는 두 가지 경로를 제공합니다. 가이드 전체에서 하나의 옵션만 선택하세요.

  • EKS 자율 모드 - EKS 자율 모드는 핵심 네트워킹, 스토리지, 로드 밸런싱 추가 기능 외에도 EKS 노드 모니터링 에이전트, 자동 노드 복구, 빠른 컨테이너 풀을 위한 SOCI 스냅샷터, 기본 NodeClass의 GPU 준비 상태 등 훈련 및 추론 워크로드를 위한 기능을 포함하고 관리합니다. NVIDIA 디바이스 플러그인은 EKS 자율 모드가 GPU 지원 노드에 사용하는 Bottlerocket 가속 AMI에 포함되어 있습니다.

  • 자체 관리형 Karpenter - EKS 자율 모드가 없는 EKS 클러스터에서는 훈련 및 추론 워크로드에 필요한 구성 요소를 설치하고 구성할 책임이 사용자에게 있습니다. 여기에는 네트워킹 추가 기능(VPC CNI, CoreDNS, kube-proxy), Karpenter, EKS 노드 모니터링 에이전트, NVIDIA 디바이스 플러그인 및 빠른 컨테이너 풀을 위한 SOCI 스냅샷터가 포함됩니다.

EKS 클러스터 옵션: EKS 자율 모드와 자체 관리형 Karpenter

두 가지 클러스터 옵션, 즉 NodePool이 있는 EKS 자율 모드 클러스터와 자체 관리형 Karpenter, CoreDNS, VPC CNI, NVIDIA 디바이스 플러그인, EKS Pod Identity 에이전트, 노드 모니터링 에이전트, kube-proxy, NodeClass 및 NodePool이 있는 EKS 표준 클러스터를 나란히 비교

다음 각 단계에서 경로(EKS 자율 모드, Karpenter)를 선택하고 끝까지 따릅니다. 선택한 경로의 단계를 완료하면 GPU NodePool이 있는 EKS 클러스터가 GPU 워크로드를 예약할 준비가 됩니다.

1단계: 클러스터 생성

먼저 EKS 클러스터를 생성하고 GPU 워크로드에 필요한 클러스터 구성 요소를 설치합니다.

EKS 자율 모드를 사용하면 단일 eksctl create cluster --enable-auto-mode 명령으로 GPU 워크로드에 사용할 준비가 된 EKS 클러스터를 프로비저닝할 수 있습니다.

자체 관리형 Karpenter를 사용하면 eksctl create cluster 명령이 핵심 네트워킹 추가 기능을 프로비저닝합니다. 그런 다음 Karpenter 기능 게이트를 통해 자동 노드 복구를 활성화하고, EKS 노드 모니터링 에이전트를 설치하고, NVIDIA 디바이스 플러그인을 설치하기 위한 추가 단계가 필요합니다.

EKS Auto Mode

EKS 자율 모드 클러스터 생성

eksctl create cluster \ --name=$CLUSTER_NAME \ --region=$AWS_REGION \ --enable-auto-mode \ --version=1.35 \ --zones=$AZS

이 명령은 완료까지 몇 분 정도 걸립니다. 완료되면 eksctl은 새로 프로비저닝된 클러스터와 함께 작동하도록 kubeconfig 파일을 자동으로 업데이트합니다. 클러스터가 작동하는지 확인합니다.

kubectl get pods --all-namespaces

예상 결과:

NAMESPACE NAME READY STATUS RESTARTS AGE kube-system metrics-server-55cf976ddd-cz2mw 1/1 Running 0 3m kube-system metrics-server-55cf976ddd-wrjvv 1/1 Running 0 3m

EKS 자율 모드에서 VPC CNI, kube-proxy, CoreDNS는 관리형 구성 요소로 실행되며 kube-system에 포드로 표시되지 않습니다.

Self-managed Karpenter

퍼블릭 ECR에 헬름 인증

eksctl은 Amazon Public ECR에서 Karpenter 헬름 차트를 가져옵니다. 헬름 설치 단계에서 403 오류를 방지하기 위해 클러스터 생성 전에 인증합니다.

aws ecr-public get-login-password --region us-east-1 \ | helm registry login --username AWS --password-stdin public.ecr.aws

퍼블릭 ECR은 us-east-1에서 호스팅되는 글로벌 서비스입니다. EKS 클러스터가 있는 리전에 관계없이 여기서는 --region us-east-1을 사용합니다.

예상 결과: Login Succeeded

Karpenter를 사용하여 EKS 클러스터 생성

나중에 사용할 수 있도록 환경 변수에 Karpenter 버전을 저장합니다. 최신 Karpenter 버전은 GitHub의 Karpenter 릴리스를 참조하세요.

export KARPENTER_VERSION=1.12.0
cat << EOF > /tmp/cluster-karpenter.yaml apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: ${CLUSTER_NAME} region: ${AWS_REGION} version: "1.35" tags: karpenter.sh/discovery: ${CLUSTER_NAME} availabilityZones: [$(echo $AZS | sed 's/,/, /g')] autoModeConfig: enabled: false iam: withOIDC: true karpenter: version: "${KARPENTER_VERSION}" withSpotInterruptionQueue: true managedNodeGroups: - name: system instanceType: m6i.2xlarge desiredCapacity: 2 minSize: 2 maxSize: 3 labels: node-role: system tags: karpenter.sh/discovery: ${CLUSTER_NAME} addons: - name: eks-pod-identity-agent - name: eks-node-monitoring-agent EOF eksctl create cluster -f /tmp/cluster-karpenter.yaml

이 명령은 15분 정도 소요됩니다. 이 명령은 추가 기능 및 Karpenter 컨트롤러를 호스팅하는 전용 관리형 노드 그룹이 있는 EKS 클러스터를 생성합니다. Karpenter는 스팟 중단 대기열이 활성화된 상태에서 설치되므로 스팟 중단을 처리하고 권장 사항을 재조정할 수 있습니다. autoModeConfig.enabled: false 설정에 이 클러스터가 EKS 자율 모드를 사용하지 않는다고 명시되어 있으므로 이 경로에 설치된 Karpenter 구성 요소가 노드 관리를 담당합니다.

또한 클러스터에는 EKS Pod Identity 에이전트EKS 노드 모니터링 에이전트가 EKS 추가 기능으로 설치됩니다. EKS Pod Identity는 가이드 뒷부분에서 사용됩니다. EKS 노드 모니터링 에이전트는 모든 노드에서 실행되고 커널 로그를 읽어 Karpenter 자동 노드 복구가 비정상 노드를 교체할 시기를 결정하는 데 사용하는 AcceleratedHardwareReady, KernelReady, NetworkingReady 같은 노드 조건을 설정합니다.

클러스터가 작동하는지 확인합니다.

kubectl get pods --all-namespaces

예상 출력에는 Karpenter, CoreDNS, kube-proxy, aws-node(VPC CNI), EKS Pod Identity 에이전트, EKS 노드 모니터링 에이전트가 포함됩니다.

NAMESPACE NAME READY STATUS RESTARTS AGE karpenter karpenter-567547464c-s6vkx 1/1 Running 0 3m40s karpenter karpenter-567547464c-x7gmw 1/1 Running 0 3m40s kube-system aws-node-b6gf2 2/2 Running 0 12m kube-system aws-node-lcphh 2/2 Running 0 12m kube-system coredns-7d4dcbf4fb-ccvrr 1/1 Running 0 16m kube-system coredns-7d4dcbf4fb-qbhk2 1/1 Running 0 16m kube-system eks-node-monitoring-agent-h79vm 1/1 Running 0 9m45s kube-system eks-node-monitoring-agent-tf4dw 1/1 Running 0 9m45s kube-system eks-pod-identity-agent-5jbtc 1/1 Running 0 12m kube-system eks-pod-identity-agent-rwcrc 1/1 Running 0 12m kube-system kube-proxy-p4bmq 1/1 Running 0 12m kube-system kube-proxy-v5nwr 1/1 Running 0 12m kube-system metrics-server-5b966ff79c-hr58p 1/1 Running 0 9m22s kube-system metrics-server-5b966ff79c-szs2d 1/1 Running 0 9m22s

자동 노드 복구 활성화

EKS 자율 모드는 기본적으로 자동 노드 복구를 활성화합니다. 자체 관리형 Karpenter에서는 자동 노드 복구가 NodeRepair=true 기능 게이트 뒤에 게이트되며 명시적으로 활성화되어야 합니다. 다음 명령은 Karpenter 배포를 패치하여 NodeRepair=true 기능 게이트를 추가합니다. 배포 환경을 업데이트하면 Karpenter 포드의 롤아웃이 트리거됩니다.

kubectl set env deployment/karpenter -n karpenter \ FEATURE_GATES=NodeRepair=true

예상 결과:

deployment.apps/karpenter env updated

Karpenter 포드가 롤아웃되길 기다립니다.

kubectl rollout status deployment/karpenter -n karpenter

NVIDIA 디바이스 플러그인 설치

EKS 최적화 AL2023 AMI에는 NVIDIA 디바이스 플러그인이 포함되지 않습니다(EKS 자율 모드가 사용하는 Bottlerocket AMI와 달리). 헬름을 통해 이 플러그인을 설치하면 클러스터의 포드에서 GPU 리소스를 사용할 수 있습니다.

helm repo add nvdp https://nvidia.github.io/k8s-device-plugin helm repo update
cat << 'EOF' > /tmp/nvdp-values.yaml mofedEnabled: false nodeSelector: amiFamily: al2023 gfd: enabled: true nfd: worker: tolerations: - operator: "Exists" EOF
helm install nvidia-device-plugin nvdp/nvidia-device-plugin \ --namespace kube-system \ -f /tmp/nvdp-values.yaml
  • mofedEnabled: false: AWS가 사용하지 않는 Mellanox OFED(InfiniBand) 검사를 비활성화합니다.

  • nodeSelector.amiFamily: al2023: DaemonSet 범위를 AL2023 노드로만 지정합니다(Bottlerocket에는 이미 플러그인이 내장되어 있습니다).

  • gfd.enabled: true: GPU Feature Discovery 레이블(nvidia.com/gpu.product, nvidia.com/gpu.memory 등)을 활성화합니다.

NVIDIA 디바이스 플러그인이 설치되어 있는지 확인합니다. 해당 레이블과 일치하는 GPU NodePool이 프로비저닝될 때까지 디바이스 플러그인 포드가 0이어야 합니다.

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 2m5s
주의

EKS 자율 모드 경로와 자체 관리형 Karpenter 경로 모두에서 자동 노드 복구는 NodePool이 프로비저닝한 노드에서 동일한 방식으로 작동합니다. EKS 자율 모드와 Karpenter의 자동 노드 복구는 PodDisruptionBudgets, karpenter.sh/do-not-disrupt 주석, terminationGracePeriod를 우회하는 강제 중단 방법입니다. 자동 노드 복구는 노드를 False로 설정된 AcceleratedHardwareReady 조건으로 바꾸기 전에 10분, 다른 복구 조건에서는 30분을 기다립니다.

2단계: 동적 GPU NodePool 생성

온디맨드를 폴백으로 사용하는 스팟 용량을 사용하여 4세대 보다 상위의 G 패밀리 GPU 인스턴스를 동적으로 프로비저닝하는 NodePool을 정의합니다. EKS 자율 모드 경로와 Karpenter 경로 모두 동일한 NodePool API를 사용합니다. 유일한 차이점은 이 API가 가리키는 NodeClass입니다. EKS 자율 모드에서 번들링된 default NodeClass는 이미 올바른 AMI를 선택하고 SOCI 병렬 풀을 구성하므로, NodePool은 사용자가 생성하는 유일한 객체입니다. 자체 관리형 Karpenter에서는 AMI를 고정하고 SOCI를 튜닝하는 사용자 지정 EC2NodeClass도 필요합니다.

EKS Auto Mode

EKS 자율 모드에서 번들링된 default NodeClass는 사전 설치된 NVIDIA 드라이버, NVIDIA 디바이스 플러그인, SOCI 병렬 풀을 포함하는 GPU 인스턴스용 Bottlerocket AMI를 자동으로 선택합니다. default NodeClass를 참조하는 NodePool을 적용하기만 하면 됩니다.

cat << 'EOF' | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs spec: nodeClassRef: group: eks.amazonaws.com kind: NodeClass name: default taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: eks.amazonaws.com/instance-category operator: In values: ["g"] - key: eks.amazonaws.com/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF

이 NodePool은 4세대 보다 상위의 G 패밀리 GPU 인스턴스(G5, G6e, G7e 등)를 프로비저닝합니다. nvidia.com/gpu:NoSchedule 테인트는 GPU 적격 포드만 이러한 노드에 예약되도록 합니다.

Self-managed Karpenter

자체 관리형 Karpenter에는 기본 NodeClass가 포함되지 않습니다. 먼저 EKS 최적화 NVIDIA AL2023 AMI 별칭을 고정하고, FastImagePull 특성 게이트를 통해 SOCI를 활성화하고, 컨테이너 이미지 캐시를 로컬 NVMe로 이동하도록 instanceStorePolicy: RAID0를 구성하는 EC2NodeClass를 생성합니다. 그런 다음 이를 참조하는 NodePool을 생성합니다.

EC2NodeClass 생성

cat << EOF | kubectl apply -f - apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: gpu-inf labels: guide: ai-eks-docs spec: role: "eksctl-KarpenterNodeRole-${CLUSTER_NAME}" amiSelectorTerms: - alias: al2023@latest subnetSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} securityGroupSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} tags: karpenter.sh/discovery: ${CLUSTER_NAME} instanceStorePolicy: RAID0 userData: | MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="BOUNDARY" --BOUNDARY Content-Type: application/node.eks.aws --- apiVersion: node.eks.aws/v1alpha1 kind: NodeConfig spec: featureGates: FastImagePull: true containerd: config: | [plugins."io.containerd.snapshotter.v1.soci"] [plugins."io.containerd.snapshotter.v1.soci".blob] max_concurrent_downloads_per_image = 20 concurrent_download_chunk_size = "16mb" max_concurrent_unpacks_per_image = 12 discard_unpacked_layers = true --BOUNDARY-- EOF

instanceStorePolicy: RAID0는 로컬 NVMe 디스크를 RAID-0 배열로 어셈블합니다. al2023@latest AMI 별칭은 EKS 최적화 AL2023 AMI로 확인됩니다. Karpenter는 GPU 인스턴스 유형을 시작할 때 사전 설치된 NVIDIA 드라이버가 포함된 AL2023_x86_64_NVIDIA 가속 변형을 자동으로 선택합니다.

FastImagePull 특성 게이트는 이미지 계층을 동시에 다운로드 및 압축 해제하는 SOCI 스냅샷터 병렬 풀 모드를 활성화합니다. 이는 G, P, Trn 인스턴스 패밀리에서의 EKS 자율 모드 동작과 일치합니다. containerd.config 블록은 ECR 호스팅 이미지를 위한 SOCI 스냅샷터를 튜닝합니다.

  • max_concurrent_downloads_per_image: 20은 이미지당 최대 20개의 계층 병렬 다운로드를 허용합니다. Bottlerocket에서는 3, AL2023에서는 20이 기본값입니다. ECR에 권장되는 값입니다.

  • concurrent_download_chunk_size: "16mb"는 HTTP 범위 요청을 통해 병렬로 다운로드된 각 계층을 16MB 청크로 분할합니다. GET 범위를 지원하는 레지스트리에 권장됩니다(ECR은 지원).

  • max_concurrent_unpacks_per_image: 12는 한 번에 최대 12개의 계층을 압축 해제합니다. Bottlerocket에서는 1, AL2023에서는 12가 기본값입니다.

  • discard_unpacked_layers: true는 압축을 푼 후 압축된 계층 BLOB을 삭제하여 디스크 공간을 절약합니다.

더 많은 SOCI 튜닝 옵션(이미지당 동시 다운로드, 청크 크기 등)은 Karpenter SOCI 블루프린트를 참조하세요.

EC2NodeClass 검증:

kubectl get ec2nodeclass gpu-inf

예상 출력: READY True. False인 경우 kubectl describe ec2nodeclass gpu-inf를 실행하고 누락된 서브넷 또는 보안 그룹 태그가 있는지 조건을 확인합니다.

GPU NodePool 생성

cat << EOF | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs amiFamily: al2023 spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: gpu-inf taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand"] - key: karpenter.k8s.aws/instance-category operator: In values: ["g"] - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF

노드 템플릿의 amiFamily: al2023 레이블은 NVIDIA 디바이스 플러그인 DaemonSet가 이러한 노드를 선택하는 데 사용하는 레이블입니다.

NodePool이 생성되었는지 확인합니다.

kubectl get nodepool gpu-inf

예상 결과:

NAME NODECLASS NODES READY AGE gpu-inf default 0 True 8s

자체 관리형 Karpenter 경로에서는 NODECLASS 열에 gpu-inf 대신 default가 표시됩니다.

3단계: 샘플 포드로 테스트

nvidia-smi 포드를 사용하여 GPU NodePool 설정을 테스트합니다.

cat << EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: nvidia-smi labels: 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: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 1 restartPolicy: OnFailure EOF

포드가 예약되고 성공적으로 완료되었는지 확인합니다.

kubectl get pods

예상 결과:

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

상태: 완료됨은 nvidia-smi 명령이 실행되고 종료되었음을 의미합니다. 포드 로그를 확인하여 노드에서 감지된 GPU를 확인합니다.

kubectl logs nvidia-smi

예상 결과:

+-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 580.126.09 Driver Version: 580.126.09 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 RTX PRO 6000 Blac... On | 00000000:2B:00.0 Off | 0 | | N/A 30C P0 81W / 600W | 0MiB / 97887MiB | 0% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+

출력에는 GPU 모델, 드라이버 버전, CUDA 버전, 사용 가능한 메모리가 표시됩니다. 이 예제에서 Karpenter는 메모리가 96GB인 NVIDIA RTX PRO 6000 Blackwell GPU가 있는 G7e 인스턴스를 프로비저닝했습니다. 30C는 현재 GPU 온도이고 P0는 GPU가 최고 성능 상태(유휴 상태이지만 준비됨)임을 의미합니다. 81W/600W는 현재 전력 소비 대 최대 전력 용량을 보여주고, 0MiB/97887MiB는 현재 사용된 GPU 메모리와 사용 가능한 총 GPU 메모리를 보여줍니다. 포드가 방금 nvidia-smi를 실행하고 종료했으므로 GPU를 사용하는 워크로드가 없어 메모리는 0이고 전원은 유휴 상태입니다. NVIDIA GPU 드라이버 버전(580.126.09)은 Bottlerocket AMI에서 가져오고, CUDA 버전(13.0)은 컨테이너 이미지에서 가져옵니다. GPU 모델과 메모리는 Karpenter가 선택하는 인스턴스 유형에 따라 달라집니다. G5 인스턴스에는 NVIDIA A10G GPU(24GB)가 있고, G6e 인스턴스에는 NVIDIA L40S GPU(48GB)가 있으며, G7e 인스턴스에는 NVIDIA RTX PRO 6000 GPU(96GB)가 있습니다.

노드를 프로비저닝하고 포드를 배치하기 위해 Karpenter와 Kubernetes 스케줄러가 어떻게 조정되는지 이해하려면 포드의 수명 주기 이벤트를 확인하세요.

kubectl describe po nvidia-smi

예상 결과:

Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 60s default-scheduler 0/2 nodes are available: 2 node(s) had untolerated taint(s). no new claims to deallocate, preemption: 0/2 nodes are available: 2 Preemption is not helpful for scheduling. Normal Nominated 59s eks-auto-mode/compute Pod should schedule on: nodeclaim/gpu-inf-vxcnj Normal Scheduled 24s default-scheduler Successfully assigned default/nvidia-smi to i-0fb17a09bc4203164 Warning FailedCreatePodSandBox 21s kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "7f85e25b220c8fb245187758dbbbc8efb3d40f3e49e13054404880daf4c3b2f0": plugin type="aws-cni" name="aws-cni" failed (add): add cmd: failed to setup network policy Normal Pulling 7s kubelet spec.containers{nvidia-smi}: Pulling image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" Normal Pulled 5s kubelet spec.containers{nvidia-smi}: Successfully pulled image "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" in 1.237s (1.237s including waiting). Image size: 37442701 bytes. Normal Created 5s kubelet spec.containers{nvidia-smi}: Container created Normal Started 5s kubelet spec.containers{nvidia-smi}: Container started

이러한 이벤트는 포드 예약 시퀀스를 보여줍니다. 처음에 포드가 GPU 노드가 없어 예약에 실패하고(FailedScheduling), Karpenter가 새 NodeClaim을 지명하고(Nominated), 노드가 준비되면 스케줄러가 포드를 할당한 다음(Scheduled) 컨테이너 이미지를 가져와 시작합니다. EKS 자율 모드는 G, P, Trn 인스턴스에 SOCI(Seekable OCI) 병렬 풀이 설치되고 바로 사용할 수 있도록 구성된 상태로 제공됩니다. SOCI 병렬 풀로 인해 컨테이너 이미지를 2초 이내에(1.237초) ECR에서 가져왔습니다.

NodeClaim은 Karpenter가 특정 노드를 프로비저닝하기 위해 생성하는 요청입니다. 이 요청은 인스턴스 유형, 용량 유형, AZ, 노드 준비 여부를 보여줍니다.

kubectl get nodeclaims

예상 NodeClaim 출력:

NAME            TYPE          CAPACITY    ZONE         NODE                  READY   AGE
gpu-inf-xxxxx   g7e.2xlarge   spot        us-east-2a   i-0xxxxxxxxxxxx       True    2m

인스턴스 유형과 AZ는 다양합니다. 4세대 보다 상위의 G 패밀리 인스턴스라면 사용할 수 있습니다.

kubectl describe pod nvidia-smiFailedCreatePodSandBox 경고는 일시적이며 예상된 것입니다. VPC CNI는 노드 조인 후 비동기적으로 초기화되고 kubelet이 자동으로 재시도합니다. 포드가 ContainerCreating 상태에 남아 있는 경우 kubectl describe node <node-name>를 사용하여 노드 이벤트를 확인합니다.

작은 정보

노드가 표시되지 않으면 용량 부족 오류를 확인합니다.

kubectl get events | grep InsufficientCapacityError

Karpenter는 사용할 수 없는 오퍼링을 3분 동안 캐싱합니다. NodePool에서 허용되는 인스턴스 유형과 AZ를 확장하면 랜딩 용량이 늘어납니다.

참고

Karpenter가 시작한 스팟 인스턴스는 EC2 스팟 요청 콘솔에 표시되지 않습니다. Karpenter는 type: instant와 함께 EC2 CreateFleet API를 사용합니다. 인스턴스는 spot 수명 주기와 함께 EC2 인스턴스 콘솔에 표시됩니다.

4단계: NodePool에 예약 용량 추가(선택 사항)

스팟/온디맨드 폴백에 예약 용량을 먼저 사용하려면 ODCR을 생성하여 NodeClass에 연결한 다음 2단계의 동적 NodePool을 업데이트하여 reserved 용량도 허용합니다. 예약 API 직접 호출은 두 경로에서 동일하지만, EKS 자율 모드와 자체 관리형 Karpenter가 사용하는 NodeClass 종류는 서로 다르기 때문에 NodeClass 연결이 다릅니다.

주의

다음 명령을 사용하면 aws ec2 cancel-capacity-reservation --capacity-reservation-id <id>로 예약을 취소할 때까지 예약된 인스턴스 유형에 요금이 부과됩니다.

용량 예약 생성:

CR_AZ="us-east-2a" INSTANCE_TYPE="g6e.4xlarge" aws ec2 create-capacity-reservation \ --instance-type $INSTANCE_TYPE \ --instance-platform Linux/UNIX \ --availability-zone "$CR_AZ" \ --instance-count 1 \ --instance-match-criteria open \ --end-date-type unlimited

InsufficientInstanceCapacity 오류가 발생하면 CR_AZ를 다른 AZ로 변경하고 다시 시도하세요.

용량 예약 ID를 조회하고 다음 단계를 위해 쉘 변수에 저장합니다.

CAPACITY_RESERVATION_ID=$(aws ec2 describe-capacity-reservations \ --filters "Name=state,Values=active" "Name=instance-type,Values=${INSTANCE_TYPE}" \ --query 'CapacityReservations[0].CapacityReservationId' \ --output text \ --region ${AWS_REGION}) echo "Capacity reservation ID: ${CAPACITY_RESERVATION_ID}"

그런 다음 경로에서 NodeClass 및 NodePool 변경 사항을 적용합니다.

EKS Auto Mode

EKS 자율 모드에서 번들링된 default NodeClass는 읽기 전용이므로 예약을 참조하는 사용자 지정 NodeClass를 생성한 다음 NodeClass를 가리키도록 NodePool을 업데이트하고 reserved 용량을 capacity-type 목록에 추가합니다.

NODE_ROLE=$(kubectl get nodeclass default -o jsonpath='{.spec.role}') cat << EOF | kubectl apply -f - apiVersion: eks.amazonaws.com/v1 kind: NodeClass metadata: name: gpu-inf labels: guide: ai-eks-docs spec: role: "$NODE_ROLE" subnetSelectorTerms: - tags: alpha.eksctl.io/cluster-name: "$CLUSTER_NAME" kubernetes.io/role/internal-elb: "1" securityGroupSelectorTerms: - tags: aws:eks:cluster-name: "$CLUSTER_NAME" capacityReservationSelectorTerms: - id: "$CAPACITY_RESERVATION_ID" EOF

kubernetes.io/role/internal-elb: "1" 태그는 프라이빗 서브넷에서만 노드가 시작되도록 합니다.

ODCR 지원 NodeClass를 사용하고 reserved를 용량 유형으로 포함하도록 NodePool을 업데이트합니다.

cat << EOF | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs spec: nodeClassRef: group: eks.amazonaws.com kind: NodeClass name: gpu-inf taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand", "reserved"] - key: eks.amazonaws.com/instance-category operator: In values: ["g"] - key: eks.amazonaws.com/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF
Self-managed Karpenter

자체 관리형 Karpenter의 경우 capacityReservationSelectorTerms를 추가하여 2단계에서 생성한 EC2NodeClass를 다시 적용합니다. 필드 이름과 셰이프는 다른 탭에 표시된 EKS 자율 모드 NodeClass와 일치합니다.

cat << EOF | kubectl apply -f - apiVersion: karpenter.k8s.aws/v1 kind: EC2NodeClass metadata: name: gpu-inf labels: guide: ai-eks-docs spec: role: "eksctl-KarpenterNodeRole-${CLUSTER_NAME}" amiSelectorTerms: - alias: al2023@latest subnetSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} securityGroupSelectorTerms: - tags: karpenter.sh/discovery: ${CLUSTER_NAME} tags: karpenter.sh/discovery: ${CLUSTER_NAME} instanceStorePolicy: RAID0 capacityReservationSelectorTerms: - id: "$CAPACITY_RESERVATION_ID" userData: | MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="BOUNDARY" --BOUNDARY Content-Type: application/node.eks.aws --- apiVersion: node.eks.aws/v1alpha1 kind: NodeConfig spec: featureGates: FastImagePull: true containerd: config: | [plugins."io.containerd.snapshotter.v1.soci"] [plugins."io.containerd.snapshotter.v1.soci".blob] max_concurrent_downloads_per_image = 20 concurrent_download_chunk_size = "16mb" max_concurrent_unpacks_per_image = 12 discard_unpacked_layers = true --BOUNDARY-- EOF

2단계와 유일하게 달라진 것은 새 capacityReservationSelectorTerms 필드입니다. 다른 모든 필드는 동일하게 유지됩니다.

reserved를 용량 유형으로 포함하도록 NodePool 업데이트:

cat << EOF | kubectl apply -f - apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inf spec: template: metadata: labels: guide: ai-eks-docs amiFamily: al2023 spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: gpu-inf taints: - key: nvidia.com/gpu effect: NoSchedule requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot", "on-demand", "reserved"] - key: karpenter.k8s.aws/instance-category operator: In values: ["g"] - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["4"] - key: kubernetes.io/arch operator: In values: ["amd64"] limits: cpu: 1000 memory: 5000Gi EOF

Karpenter는 reserved를 가장 비용 효율적인 옵션으로 취급하고 먼저 시작합니다. 예약이 가득 차면 스팟 또는 온디맨드로 대체합니다.

변경 사항을 적용한 후 Karpenter가 예약 용량의 우선 순위를 지정하고 스팟 또는 온디맨드로 대체하는지 확인합니다. 포드당 GPU 1개를 요청하는 2-복제본 배포를 배포합니다. ODCR은 인스턴스 1개용이므로 첫 번째 포드는 Karpenter가 예약 노드를 시작하도록 트리거합니다. 두 번째 포드는 예약된 노드에 맞을 수 없으며 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 테스트 포드와 달리 이 배포는 포드를 계속 실행(sleep infinity)하므로 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-59b97944fb-lq56c   1/1     Running   0          2m42s   192.168.186.240   i-057692590480155da   <none>           <none>
gpu-overflow-test-59b97944fb-z4zcx   1/1     Running   0          2m42s   192.168.130.64    i-0521ecd1849fa0578   <none>           <none>

두 포드가 각각 다른 노드에서 실행 중입니다.

NodeClaim에서 용량 유형을 확인합니다.

kubectl get nodeclaims

예상 결과:

NAME            TYPE          CAPACITY    ZONE         NODE                  READY   AGE
gpu-inf-shg5w   g6e.xlarge    reserved    us-east-2a   i-0ea91fdeef65b8cb6   True    2m2s
gpu-inf-ssnqf   g7e.2xlarge   spot        us-east-2b   i-00ccf7ce65cf3f6ca   True    112s

예약 노드가 먼저 시작된 후 예약이 가득 차면 스팟 또는 온디맨드 노드가 시작됩니다.

테스트 배포 정리:

kubectl delete deployment gpu-overflow-test

모니터링

클러스터, 노드, GPU 지표를 수집하는 모니터링 스택을 Amazon Managed Service for Prometheus(AMP)에 설치하고 Grafana로 시각화합니다. kube-prometheus-stack 헬름 차트는 Prometheus를 배포하여 지표를 스크레이프하고 AMP에 원격으로 쓰며, 대시보드용 자체 관리형 Grafana를 배포합니다. NVIDIA DCGM Exporter는 GPU별 지표(사용률, 메모리, 온도, 전력, NVLink, 텐서 활동)를 추가합니다.

GPU 노드에는 nvidia.com/gpu:NoSchedule 테인트가 있기 때문에 Prometheus, Grafana, 연산자는 기본적으로 비 GPU 노드에 랜딩합니다. node-exporter와 DCGM Exporter는 모두 GPU 노드에서 실행되므로 플릿 전체에서 호스트 및 GPU 지표를 스크레이프할 수 있습니다.

새 터미널을 연 경우 클러스터 이름과 리전을 설정합니다.

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2

AMP 워크스페이스 생성

지표를 저장할 AMP 워크스페이스를 생성합니다.

aws amp create-workspace \ --alias "amp-ws-${CLUSTER_NAME}" \ --region ${AWS_REGION}

워크스페이스 ID를 가져옵니다.

AMP_WORKSPACE_ID=$(aws amp list-workspaces \ --alias "amp-ws-${CLUSTER_NAME}" \ --query 'workspaces[0].workspaceId' \ --output text \ --region ${AWS_REGION}) echo "AMP Workspace ID: ${AMP_WORKSPACE_ID}"

원격 쓰기 엔드포인트를 가져옵니다.

AMP_ENDPOINT=$(aws amp describe-workspace \ --workspace-id ${AMP_WORKSPACE_ID} \ --query 'workspace.prometheusEndpoint' \ --output text \ --region ${AWS_REGION}) echo "AMP Endpoint: ${AMP_ENDPOINT}"

IAM 정책 및 EKS Pod Identity 연결 생성

Prometheus가 지표를 원격으로 쓰고 Grafana가 이를 쿼리하도록 허용하는 IAM 정책을 생성합니다.

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) AMP_POLICY_ARN=$(aws iam create-policy \ --policy-name "${CLUSTER_NAME}-amp-grafana-policy" \ --policy-document "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Sid\": \"AllowAMPReadWrite\", \"Effect\": \"Allow\", \"Action\": [\"aps:ListWorkspaces\", \"aps:DescribeWorkspace\", \"aps:GetMetricMetadata\", \"aps:GetSeries\", \"aps:QueryMetrics\", \"aps:RemoteWrite\", \"aps:GetLabels\"], \"Resource\": \"arn:aws:aps:${AWS_REGION}:${ACCOUNT_ID}:workspace/*\"}, {\"Sid\": \"AllowCloudWatchMetrics\", \"Effect\": \"Allow\", \"Action\": [\"cloudwatch:DescribeAlarmsForMetric\", \"cloudwatch:ListMetrics\", \"cloudwatch:GetMetricData\", \"cloudwatch:GetMetricStatistics\"], \"Resource\": \"*\"}]}" \ --query 'Policy.Arn' \ --output text) echo "AMP Policy ARN: ${AMP_POLICY_ARN}"

Prometheus와 Grafana의 모니터링 네임스페이스 및 서비스 계정을 생성합니다.

kubectl create namespace monitoring kubectl create serviceaccount amp-iamproxy-ingest-service-account -n monitoring kubectl create serviceaccount grafana-sa -n monitoring

EKS Pod Identity 연결을 생성하여 서비스 계정을 IAM 정책에 연결합니다.

eksctl create podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name amp-iamproxy-ingest-service-account \ --role-name "${CLUSTER_NAME}-amp-ingest-role" \ --permission-policy-arns ${AMP_POLICY_ARN} \ --region ${AWS_REGION} eksctl create podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name grafana-sa \ --role-name "${CLUSTER_NAME}-grafana-role" \ --permission-policy-arns ${AMP_POLICY_ARN} \ --region ${AWS_REGION}

두 EKS Pod Identity 연결이 모두 생성되었는지 확인합니다.

eksctl get podidentityassociation --cluster ${CLUSTER_NAME} --region ${AWS_REGION}

예상 출력은 monitoring 네임스페이스에 amp-iamproxy-ingest-service-accountgrafana-sa를 모두 포함해야 합니다.

kube-prometheus-stack 설치

헬름 리포지토리를 추가합니다.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update

이 값 파일에는 Prometheus, Grafana, 연산자를 위한 nodeSelector가 생략되어 있습니다. GPU 노드의 nvidia.com/gpu:NoSchedule 테인트가 GPU 노드에서 이들을 차단하기 때문에 이들은 기본적으로 시스템 또는 범용 풀에 랜딩합니다. node-exporter는 와일드카드 톨러레이션을 사용하므로 GPU 노드를 포함한 모든 노드에서 실행되어 플릿 전체의 지표를 수집합니다.

값 파일을 생성합니다.

cat << EOF > /tmp/kube-prometheus-values.yaml prometheus: serviceAccount: create: false name: amp-iamproxy-ingest-service-account prometheusSpec: serviceAccountName: amp-iamproxy-ingest-service-account remoteWrite: - url: "${AMP_ENDPOINT}api/v1/remote_write" sigv4: region: "${AWS_REGION}" queueConfig: maxSamplesPerSend: 1000 maxShards: 200 capacity: 2500 retention: 5h scrapeInterval: 30s evaluationInterval: 30s podMonitorSelectorNilUsesHelmValues: false serviceMonitorSelectorNilUsesHelmValues: false alertmanager: enabled: false grafana: enabled: true serviceAccount: create: false name: grafana-sa grafana.ini: auth.sigv4: enabled: true sidecar: datasources: defaultDatasourceEnabled: false plugins: - grafana-amazonprometheus-datasource additionalDataSources: - name: Amazon-Managed-Prometheus type: grafana-amazonprometheus-datasource access: proxy url: "${AMP_ENDPOINT}" isDefault: true jsonData: sigV4Auth: true defaultRegion: "${AWS_REGION}" sigV4Region: "${AWS_REGION}" editable: true dashboardProviders: dashboardproviders.yaml: apiVersion: 1 providers: - name: default orgId: 1 folder: 'GPU Monitoring' type: file disableDeletion: false editable: true options: path: /var/lib/grafana/dashboards/default dashboards: default: nvidia-dcgm: gnetId: 25261 revision: 1 datasource: - name: DS_PROMETHEUS value: Amazon-Managed-Prometheus vllm: gnetId: 25263 revision: 1 datasource: - name: DS_PROMETHEUS value: Amazon-Managed-Prometheus prometheus-node-exporter: tolerations: - operator: Exists EOF

변수가 올바르게 채워졌는지 확인합니다.

grep -E "url:|region:|tolerations:" /tmp/kube-prometheus-values.yaml

전체 AMP 엔드포인트 URL(https://aps-workspaces…​로 시작), 리전, node-exporter tolerations: 줄이 표시되어야 합니다. 비어 있는 것이 있으면 변수를 다시 내보내고 파일을 다시 생성합니다.

차트를 설치합니다.

helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ --namespace monitoring \ -f /tmp/kube-prometheus-values.yaml

포드가 실행 중인지 확인합니다.

kubectl get pods -n monitoring

예상 결과:

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

스택은 다음 구성 요소를 배포합니다.

  • Prometheus(StatefulSet): 지표를 스크레이프하고 AMP에 원격으로 씁니다.

  • Grafana: AMP 데이터 소스로 사전 구성된 대시보드 및 시각화

  • kube-state-metrics: Kubernetes 객체 상태에 대한 지표를 생성합니다(포드 상태, 리소스 요청/제한, NodeClaim 상태)

  • node-exporter(DaemonSet, 노드당 하나): 호스트 수준 지표(CPU, 메모리, 디스크, 네트워크) 수집

  • 연산자: Prometheus 및 Alertmanager 사용자 지정 리소스 관리

이 설정에서는 Alertmanager가 비활성화됩니다.

Grafana에 액세스

별도의 터미널을 열고 포트 포워딩을 통해 Grafana에 액세스합니다.

kubectl port-forward svc/kube-prometheus-stack-grafana 3000:80 -n monitoring

브라우저에서 http://localhost:3000을 엽니다. 다음 명령에서 사용자 이름 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 연결 페이지
  2. 드릴다운 > 지표로 이동하여 up 지표를 검색합니다. 클러스터의 스크레이프 대상의 결과가 표시되어야 합니다.

    Grafana에서 up 지표 검증

    활성 스크레이프 대상을 나타내는 녹색 상태 표시줄이 있는 위쪽 지표를 보여주는 Grafana 드릴다운 지표 페이지

up에 결과가 표시되면 파이프라인(클러스터 → Prometheus → AMP → Grafana)이 작동하는 것입니다.

GPU 지표용 DCGM Exporter 배포

kube-prometheus-stack은 노드 수준 CPU 및 메모리 지표를 수집하지만 GPU 지표는 수집하지 않습니다. NVIDIA DCGM Exporter는 GPU 사용률, 메모리 사용량, 온도, 전력 소비, NVLink 대역폭, 텐서 활동을 추가합니다.

helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts helm repo update

경로의 GPU 노드 선택기 키를 설정합니다. EKS 자율 모드와 자체 관리형 Karpenter는 GPU 제조업체에 서로 다른 레이블 키를 사용합니다.

EKS Auto Mode
GPU_NODE_SELECTOR_KEY="eks.amazonaws.com/instance-gpu-manufacturer"
Self-managed Karpenter
GPU_NODE_SELECTOR_KEY="karpenter.k8s.aws/instance-gpu-manufacturer"

DCGM Exporter 값 파일을 생성합니다.

cat << EOF > /tmp/dcgm-exporter-values.yaml resources: requests: memory: "512Mi" cpu: "100m" limits: memory: "1Gi" cpu: "500m" serviceMonitor: enabled: true additionalLabels: release: kube-prometheus-stack nodeSelector: ${GPU_NODE_SELECTOR_KEY}: nvidia tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" customMetrics: | # Clocks DCGM_FI_DEV_SM_CLOCK, gauge, SM clock frequency (in MHz). DCGM_FI_DEV_MEM_CLOCK, gauge, Memory clock frequency (in MHz). # Temperature DCGM_FI_DEV_MEMORY_TEMP, gauge, Memory temperature (in C). DCGM_FI_DEV_GPU_TEMP, gauge, GPU temperature (in C). # Power DCGM_FI_DEV_POWER_USAGE, gauge, Power draw (in W). DCGM_FI_DEV_TOTAL_ENERGY_CONSUMPTION, counter, Total energy consumption since boot (in mJ). # PCIe DCGM_FI_PROF_PCIE_TX_BYTES, counter, Number of bytes transmitted through PCIe TX (in KB) via NVML. DCGM_FI_PROF_PCIE_RX_BYTES, counter, Number of bytes received through PCIe RX (in KB) via NVML. DCGM_FI_DEV_PCIE_REPLAY_COUNTER, counter, Total number of PCIe retries. # Utilization (the sample period varies depending on the product) DCGM_FI_DEV_GPU_UTIL, gauge, GPU utilization (in %). DCGM_FI_DEV_MEM_COPY_UTIL, gauge, Memory utilization (in %). DCGM_FI_DEV_ENC_UTIL, gauge, Encoder utilization (in %). DCGM_FI_DEV_DEC_UTIL, gauge, Decoder utilization (in %). # Errors and violations DCGM_FI_DEV_XID_ERRORS, gauge, Value of the last XID error encountered. DCGM_EXP_XID_ERRORS_COUNT, gauge, Value of count of XID errors encountered. DCGM_FI_DEV_POWER_VIOLATION, counter, Throttling duration due to power constraints (in us). DCGM_FI_DEV_THERMAL_VIOLATION, counter, Throttling duration due to thermal constraints (in us). DCGM_FI_DEV_SYNC_BOOST_VIOLATION, counter, Throttling duration due to sync-boost constraints (in us). DCGM_FI_DEV_BOARD_LIMIT_VIOLATION, counter, Throttling duration due to board limit constraints (in us). DCGM_FI_DEV_LOW_UTIL_VIOLATION, counter, Throttling duration due to low utilization (in us). DCGM_FI_DEV_RELIABILITY_VIOLATION, counter, Throttling duration due to reliability constraints (in us). # Memory usage DCGM_FI_DEV_FB_FREE, gauge, Framebuffer memory free (in MiB). DCGM_FI_DEV_FB_USED, gauge, Framebuffer memory used (in MiB). # Retired pages DCGM_FI_DEV_RETIRED_SBE, counter, Total number of retired pages due to single-bit errors. DCGM_FI_DEV_RETIRED_DBE, counter, Total number of retired pages due to double-bit errors. DCGM_FI_DEV_RETIRED_PENDING, counter, Total number of pages pending retirement. # NVLink DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL, counter, Total number of NVLink bandwidth counters for all lanes. DCGM_FI_PROF_NVLINK_TX_BYTES, counter, The rate of data transmitted over NVLink not including protocol headers in bytes per second. DCGM_FI_PROF_NVLINK_RX_BYTES, counter, The rate of data received over NVLink not including protocol headers in bytes per second. # DCP metrics DCGM_FI_PROF_GR_ENGINE_ACTIVE, gauge, Ratio of time the graphics engine is active (in %). DCGM_FI_PROF_SM_ACTIVE, gauge, The ratio of cycles an SM has at least 1 warp assigned (in %). DCGM_FI_PROF_SM_OCCUPANCY, gauge, The ratio of number of warps resident on an SM (in %). DCGM_FI_PROF_PIPE_TENSOR_ACTIVE, gauge, Ratio of cycles the tensor (HMMA) pipe is active (in %). DCGM_FI_PROF_DRAM_ACTIVE, gauge, Ratio of cycles the device memory interface is active sending or receiving data (in %). DCGM_FI_DEV_CLOCK_THROTTLE_REASONS, gauge, Current clock throttle reasons (bitmask of DCGM_CLOCKS_THROTTLE_REASON_*). DCGM_FI_DEV_GPU_NVLINK_ERRORS, gauge, Identifies a GPU NVLink error type returned by DCGM_FI_DEV_GPU_NVLINK_ERRORS. ## NVLink DCGM_FI_DEV_NVLINK_BANDWIDTH_L0, counter, The number of bytes of active NVLink rx or tx data including both header and payload. ## Remapped rows DCGM_FI_DEV_UNCORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for uncorrectable errors. DCGM_FI_DEV_CORRECTABLE_REMAPPED_ROWS, counter, Number of remapped rows for correctable errors. DCGM_FI_DEV_ROW_REMAP_FAILURE, gauge, whether remapping of rows has failed. ## Profiling metrics DCGM_FI_PROF_PIPE_FP64_ACTIVE, gauge, Ratio of cycles the fp64 pipes are active (in %). DCGM_FI_PROF_PIPE_FP32_ACTIVE, gauge, Ratio of cycles the fp32 pipes are active (in %). DCGM_FI_PROF_PIPE_FP16_ACTIVE, gauge, Ratio of cycles the fp16 pipes are active (in %). # ECC DCGM_FI_DEV_ECC_SBE_VOL_TOTAL, counter, Total number of single-bit volatile ECC errors. DCGM_FI_DEV_ECC_DBE_VOL_TOTAL, counter, Total number of double-bit volatile ECC errors. EOF

customMetrics 필드는 NVLink 대역폭, 텐서 활동, PCIe 처리량, ECC 오류, 열 스로틀링을 포함하는 확장 지표 세트로 DCGM Exporter의 기본 지표 세트를 재정의합니다. 추론 워크로드의 경우 이러한 지표는 GPU 컴퓨팅 유닛이 완전히 활용되는지, 낮은 배치 크기로 인해 요청 사이에 GPU가 유휴 상태인지, CPU와 GPU 간 데이터 전송에 병목 현상이 있는지, 열 스로틀링으로 인해 지연 시간이 급증하는지, 더 큰 배치를 위한 GPU 메모리 헤드룸이 얼마나 남아 있는지 이해하는 데 도움이 됩니다.

DCGM Exporter를 설치합니다.

helm install dcgm-exporter gpu-helm-charts/dcgm-exporter \ --namespace monitoring \ -f /tmp/dcgm-exporter-values.yaml

tolerations를 사용하면 2단계에서 프로비저닝한 GPU 테인트 노드에서 Exporter를 실행할 수 있습니다. release: kube-prometheus-stack 레이블이 있는 serviceMonitor를 통해 Prometheus가 이를 자동으로 검색하고 스크레이프할 수 있습니다.

DCGM Exporter DaemonSet를 확인합니다.

kubectl get daemonset dcgm-exporter -n monitoring

GPU 노드가 실행되면 준비된 포드가 하나 표시됩니다. 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 Dashboard로 이동합니다.

Grafana의 NVIDIA DCGM Exporter Dashboard

GPU 사용률, GPU 평균 온도, 사용된 GPU Framebuffer Mem, GPU Power Total 패널을 보여주는 Grafana NVIDIA DCGM Exporter Dashboard

모델 가중치 S3 버킷

모델 가중치를 저장할 Amazon S3 버킷을 생성하고 워크로드 포드가 읽고 쓸 수 있도록 EKS Pod Identity 연결을 구성합니다.

새 터미널을 연 경우 클러스터 이름과 리전을 설정합니다.

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2

S3 버킷을 생성합니다.

이름 충돌을 방지하려면 무작위 접미사로 버킷을 생성합니다.

BUCKET_SUFFIX=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' \n') MODEL_BUCKET="${CLUSTER_NAME}-models-${BUCKET_SUFFIX}" aws s3 mb s3://${MODEL_BUCKET} --region ${AWS_REGION}

2023년 1월 이후에 생성된 S3 버킷은 서버 측 암호화(AES256)와 퍼블릭 액세스 차단이 기본적으로 활성화되어 있습니다.

S3 액세스를 위한 EKS Pod Identity 구성

model-storage-sa ServiceAccount를 default 네임스페이스에 생성하고, 모델 버킷으로 범위가 지정된 IAM 정책과 이를 연결하는 EKS Pod Identity 연결을 생성합니다. serviceAccountName: model-storage-sa를 설정하는 워크로드 포드는 버킷을 읽고 쓸 수 있습니다.

kubectl create serviceaccount model-storage-sa

IAM 정책 생성:

POLICY_ARN=$(aws iam create-policy \ --policy-name "${CLUSTER_NAME}-model-storage-policy" \ --policy-document "{\"Version\": \"2012-10-17\", \"Statement\": [{\"Effect\": \"Allow\", \"Action\": [\"s3:GetObject\", \"s3:PutObject\", \"s3:ListBucket\", \"s3:DeleteObject\"], \"Resource\": [\"arn:aws:s3:::${MODEL_BUCKET}\", \"arn:aws:s3:::${MODEL_BUCKET}/*\"]}]}" \ --query 'Policy.Arn' \ --output text) echo "Policy ARN: ${POLICY_ARN}"
참고

이 정책은 검증 단계를 위한 s3:PutObjects3:DeleteObject를 부여합니다. 모델 가중치만 읽는 프로덕션 추론 포드의 경우 최소 권한을 따르려면 s3:PutObjects3:DeleteObject를 제거합니다.

EKS Pod Identity 연결을 생성합니다. eksctl은 올바른 신뢰 정책을 사용하여 IAM 역할을 생성하고 ServiceAccount에 연결합니다.

eksctl create podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace default \ --service-account-name model-storage-sa \ --role-name "${CLUSTER_NAME}-model-storage-role" \ --permission-policy-arns ${POLICY_ARN} \ --region ${AWS_REGION}

연결을 확인합니다.

eksctl get podidentityassociation --cluster ${CLUSTER_NAME} --region ${AWS_REGION}

출력의 default 네임스페이스에 model-storage-sa 연결이 포함되어야 합니다.

model-storage-sa ServiceAccount를 사용하여 AWS CLI 이미지로 일회성 포드를 실행하여 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-model-storage-role/eks-ai-eks-docs-model-s-..."
}

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

=== S3 List Test ===
2026-05-04 12:00:00         19 test.txt

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

호출자 자격 증명은 포드가 EKS Pod Identity를 통해 ${CLUSTER_NAME}-model-storage-role 역할을 수임했음을 확인합니다. S3 명령은 읽기 및 쓰기 액세스를 확인합니다.

테스트 포드를 정리합니다.

kubectl delete pod s3-test

다음 단계

클러스터가 준비되면 모델 로드 및 제공으로 이동하여 대규모 언어 모델을 배포하고 추론 엔드포인트와 상호 작용할 수 있습니다.

정리

작은 정보

이 가이드의 다음 섹션을 계속 진행하려면 전체 정리를 건너뛰세요. 완료한 경우에만 정리를 실행합니다.

export CLUSTER_NAME=ai-eks-docs export AWS_REGION=us-east-2
kubectl delete pod nvidia-smi --ignore-not-found kubectl delete deployment gpu-overflow-test --ignore-not-found

ODCR을 생성했다면 먼저 취소합니다.

INSTANCE_TYPE="g6e.4xlarge" CAPACITY_RESERVATION_ID=$(aws ec2 describe-capacity-reservations \ --filters "Name=state,Values=active" "Name=instance-type,Values=${INSTANCE_TYPE}" \ --query 'CapacityReservations[0].CapacityReservationId' \ --output text \ --region ${AWS_REGION}) aws ec2 cancel-capacity-reservation --capacity-reservation-id ${CAPACITY_RESERVATION_ID}
중요

예약을 취소해도 실행 중인 인스턴스는 종료되지 않습니다. 인스턴스는 종료될 때까지 표준 온디맨드 요금으로 계속됩니다. 취소하기 전에 먼저 배포를 삭제하여 예약된 노드를 드레이닝합니다.

IAM 정책 ARN을 조회합니다.

AMP_POLICY_ARN=$(aws iam list-policies \ --scope Local \ --query "Policies[?PolicyName=='${CLUSTER_NAME}-amp-grafana-policy'].Arn" \ --output text) echo "AMP Policy ARN: ${AMP_POLICY_ARN}"

AMP 워크스페이스 ID를 조회합니다.

AMP_WORKSPACE_ID=$(aws amp list-workspaces \ --alias "amp-ws-${CLUSTER_NAME}" \ --query 'workspaces[0].workspaceId' \ --output text \ --region ${AWS_REGION}) echo "AMP Workspace ID: ${AMP_WORKSPACE_ID}"

DCGM Exporter 헬름 릴리스를 제거합니다.

helm uninstall dcgm-exporter -n monitoring

kube-prometheus-stack 헬름 릴리스를 제거합니다.

helm uninstall kube-prometheus-stack -n monitoring

Prometheus 수집 서비스 계정의 EKS Pod Identity 연결을 삭제합니다.

eksctl delete podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name amp-iamproxy-ingest-service-account \ --region ${AWS_REGION}

Grafana 서비스 계정의 EKS Pod Identity 연결을 삭제합니다.

eksctl delete podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace monitoring \ --service-account-name grafana-sa \ --region ${AWS_REGION}

Prometheus와 Grafana가 사용하는 IAM 정책을 삭제합니다.

aws iam delete-policy --policy-arn ${AMP_POLICY_ARN}

AMP 워크스페이스를 삭제합니다.

aws amp delete-workspace --workspace-id ${AMP_WORKSPACE_ID} --region ${AWS_REGION}

모니터링 네임스페이스를 삭제합니다.

kubectl delete namespace monitoring

모델 버킷 이름을 조회합니다.

MODEL_BUCKET=$(aws s3api list-buckets \ --query "Buckets[?starts_with(Name, '${CLUSTER_NAME}-models-')].Name | [0]" \ --output text) echo "Model bucket: ${MODEL_BUCKET}"

IAM 정책 ARN을 조회합니다.

POLICY_ARN=$(aws iam list-policies \ --scope Local \ --query "Policies[?PolicyName=='${CLUSTER_NAME}-model-storage-policy'].Arn" \ --output text) echo "Policy ARN: ${POLICY_ARN}"

S3 모델 버킷과 버킷의 모든 객체를 삭제합니다.

aws s3 rb s3://${MODEL_BUCKET} --force

EKS Pod Identity 연결을 삭제합니다.

eksctl delete podidentityassociation \ --cluster ${CLUSTER_NAME} \ --namespace default \ --service-account-name model-storage-sa \ --region ${AWS_REGION}

IAM 정책을 삭제합니다.

aws iam delete-policy --policy-arn ${POLICY_ARN}

Kubernetes ServiceAccount를 삭제합니다.

kubectl delete serviceaccount model-storage-sa
kubectl delete nodepool gpu-inf --ignore-not-found kubectl delete nodeclass gpu-inf --ignore-not-found kubectl delete ec2nodeclass gpu-inf --ignore-not-found eksctl delete cluster --name=$CLUSTER_NAME --region=$AWS_REGION