[CloudNeta] EKS 워크샵 스터디 (9) - 부록: 코드 살펴보기 (GenAI on EKS)

이번 배포는 워크샵이 참조하는 awslabs/ai-on-eks 공개 리포지터리에서 살펴볼 예정입니다. 먼저 코드를 살펴볼까요.

➜ tree infra/workshops/genai-on-eks
infra/workshops/genai-on-eks
├── README.md
├── genai-workshop-architecture.png
├── install.sh
└── terraform
    ├── blueprint.tfvars
    ├── grafana-dashboards/
    ├── grafana.tf
    ├── pull.tf
    └── s3-workshop.tf
base/terraform과의 관계

워크샵 폴더는 자체 모듈을 가지지 않고 베이스 모듈(infra/base/terraform/)을 통째로 복사한 뒤 워크샵 전용 파일을 덮어쓰는 방식으로 동작합니다. infra/base/terraform/은 ai-on-eks 리포가 공통으로 쓰는 EKS·Karpenter·관측 스택 베이스로, EKS, VPC, ArgoCD, Karpenter, kube-prometheus-stack, AMP, Neuron, NVIDIA GPU Operator, EFA 등 50여 개 .tf 파일이 들어 있고, 활성화 여부는 모두 variables.tf의 토글 변수로 제어됩니다. 워크샵은 자기 입맛에 맞게 토글만 잡고 베이스를 호출하는 셈입니다.

코드를 살펴봅시다

1. install.sh: 베이스를 복사해 워크샵 입맛으로 덮기

워크샵의 진입점입니다. 코드는 다음 한 줄짜리 흐름입니다.

#!/bin/bash

# Copy the base into the folder
mkdir -p ./terraform/_LOCAL
cp -r ../../base/terraform/* ./terraform/_LOCAL
cp ./terraform/s3-workshop.tf ./terraform/_LOCAL/
cp ./terraform/pull.tf ./terraform/_LOCAL/
cp ./terraform/grafana.tf ./terraform/_LOCAL/
cp -r ./terraform/grafana-dashboards ./terraform/_LOCAL/
cp ./terraform/blueprint.tfvars ./terraform/_LOCAL/

cd terraform/_LOCAL
source ./install.sh

요약하면:

  1. terraform/_LOCAL/을 만든다 (작업 디렉터리)
  2. 베이스 모듈을 통째로 복사한다 (../../base/terraform/*)
  3. 워크샵 전용 4개 파일(s3-workshop.tf·pull.tf·grafana.tf·grafana-dashboards/)과 blueprint.tfvars를 덮어쓴다
  4. _LOCAL 안에 들어가 베이스의 install.sh를 실행한다
왜 이 구조인가

베이스를 직접 수정하지 않고 덮어쓰기 패턴으로 가는 이유는, 같은 베이스 위에 여러 워크샵·청사진을 얹어 운영하기 위해서입니다. 워크샵 전용 파일 4개와 토글 변수 파일 1개만 보면 "이 워크샵이 베이스와 다른 점"이 한눈에 들어옵니다.

2. 베이스 install.sh: 실제 terraform apply의 실행자

_LOCAL/install.sh는 베이스 모듈이 제공하는 실행 스크립트로, module을 단계별로 target apply한 뒤 마지막에 전체 apply하는 방식입니다.

targets=(
  "module.vpc"
  "module.eks"
  "module.karpenter"
  "module.argocd"
)

terraform init -upgrade

TERRAFORM_COMMAND="terraform apply -auto-approve"
if [ -f "../blueprint.tfvars" ]; then
  TERRAFORM_COMMAND="$TERRAFORM_COMMAND -var-file=../blueprint.tfvars"
fi

for target in "${targets[@]}"; do
  $TERRAFORM_COMMAND -target="$target"
done

# Final apply to catch any remaining resources
$TERRAFORM_COMMAND

핵심 포인트는 두 가지입니다.

3. blueprint.tfvars: 워크샵 토글의 전부

워크샵이 베이스 모듈을 자신만의 모양으로 만드는 부분은 이 파일 한 장에 다 들어 있습니다.

# Cluster
name   = "genai-workshop"
region = "us-east-2"

# EKS
enable_eks_auto_mode = true

enable_cluster_addons = {
  metrics-server                  = false
  amazon-cloudwatch-observability = false
}

# Observability
enable_kube_prometheus_stack    = true
enable_grafana_operator         = true
enable_amazon_prometheus        = true
enable_nvidia_dcgm_exporter     = false
kube_prometheus_stack_namespace = "monitoring"
grafana_service_port            = 3000
grafana_admin_password          = "notforproductionuse"

# Model Storage (S3)
enable_s3_models_storage     = true
s3_models_bucket_create      = false
s3_models_additional_buckets = ["genai-models-*"]
s3_models_sync_sa            = "model-storage-sa"
분류 설정 의미
클러스터 name = "genai-workshop", region = "us-east-2" 클러스터 이름·리전 고정
EKS enable_eks_auto_mode = true 노드 정의를 EKS에 위임 (Karpenter NodePool 수동 선언 없음)
EKS metrics-server, amazon-cloudwatch-observability 모두 false 관측 스택은 Prometheus/Grafana 축으로 단일화
관측 kube_prometheus_stack, grafana_operator, amazon_prometheus 모두 true 3층(셀프호스트 수집 + AMP 원격 쓰기 + Grafana Operator)
관측 nvidia_dcgm_exporter = false 워크샵 데모 단순화를 위해 비활성 (실 운영에서는 true가 권장)
스토리지 enable_s3_models_storage = true, s3_models_sync_sa = "model-storage-sa" 모델 가중치를 S3로 두고 Pod에서 마운트
grafana_admin_password = "notforproductionuse"

변수 값 자체가 "운영용 아님"을 명시합니다. 데모/실습 외에는 절대 그대로 쓰면 안 되고, AWS Secrets Manager 또는 SSM Parameter Store에서 주입하는 패턴으로 바꿔야 합니다.

Auto Mode가 만드는 차이

Part 1의 EKSworkshop AI/ML 트랙이 neuron·aiml 같은 Karpenter NodePool을 직접 선언했던 것과 정반대로, 이 워크샵은 Auto Mode를 켜고 노드 정의를 EKS에 위임합니다. NodePool requirement처럼 사전 제약이 없으므로 비용 알람과 인스턴스 사용 리포트가 사후 가드레일 역할을 합니다.

4. s3-workshop.tf: 모델 가중치를 위한 S3 + CSI 드라이버

워크샵 전용으로 추가되는 14개 리소스로, "모델 가중치를 어디에 두고 Pod에서 어떻게 마운트할지"를 풀어둔 파일입니다.

S3 (6개)

리소스 역할
aws_s3_bucket.model_storage 모델 저장 버킷
aws_s3_bucket_versioning.model_storage 버저닝 활성
aws_s3_bucket_server_side_encryption_configuration.model_storage AES256 SSE
aws_s3_bucket_public_access_block.model_storage 모든 공개 액세스 차단
aws_s3_bucket_lifecycle_configuration.model_storage 30일 후 IA 전환, 미완료 멀티파트 7일 정리
aws_s3_object.model_prefix Ministral 모델 폴더(prefix) 생성

IAM (3개)

리소스 역할
aws_iam_role.s3_csi_driver_role Mountpoint S3 CSI 드라이버용 Role
aws_iam_policy.s3_csi_driver_policy ListBucket/GetObject/PutObject/DeleteObject
aws_iam_role_policy_attachment.s3_csi_driver 위 정책을 Role에 연결

EKS 통합 (3개)

리소스 역할
aws_eks_addon.s3_csi_driver AWS Mountpoint S3 CSI 드라이버 애드온
aws_eks_pod_identity_association (×2) S3 CSI 컨트롤러·드라이버에 Role 매핑

Kubernetes (3개)

리소스 역할
kubectl_manifest.s3_sync_service_account model-storage-sa ServiceAccount
kubectl_manifest.mistral_model_pv S3를 백엔드로 한 PV
kubectl_manifest.mistral_model_pvc PV에 바인딩되는 PVC
PV/PVC가 S3를 가리킨다는 의미

Mountpoint S3 CSI 드라이버는 S3 버킷을 마치 POSIX 파일 시스템처럼 마운트해 줍니다. 워크샵의 vLLM Pod는 PVC를 마운트하는 평범한 Deployment지만, 그 뒤가 S3라는 점이 인프라/모델 라이프사이클을 분리하는 핵심입니다.

5. pull.tf: 모델 가중치를 S3로 끌어오는 Job

kubectl_manifest.mistral_model_download 하나의 리소스로 구성된 파일입니다. 하는 일은 명확합니다.

왜 인프라 코드 안에 모델 다운로드를 묶었나

Terraform이 적용된 직후 한 번만 동작해야 하는 작업이라 Job으로 묶었습니다. 다만 본문에서 강조했듯 모델 라이프사이클은 Terraform 바깥에 두는 게 운영 환경에서는 안전합니다. 워크샵 수준에서 "한 번에 모든 게 떠 있는 상태"를 만드는 편의 장치로 보세요.

6. grafana.tf: vLLM·Ray·DCGM 대시보드 등록

이 파일은 AWS 리소스를 만들지 않습니다. Grafana Operator 기반으로 kubectl manifest만 생성합니다.

1) Grafana 인스턴스

리소스 역할
kubectl_manifest.external_grafana 기존 kube-prometheus-stack의 Grafana를 가리키는 Grafana CRD

2) 대시보드 ConfigMap (5개)

grafana-dashboards/ 폴더의 JSON 파일을 ConfigMap으로 묶습니다.

ConfigMap 대시보드
vllm-grafana-dashboard-config vLLM 추론 메트릭
ray-grafana-default-dashboard-config Ray 기본
ray-grafana-serve-dashboard-config Ray Serve 라우터
ray-grafana-serve-deployment-dashboard-config Ray Serve deployment
dcgm-dashboard-config NVIDIA DCGM (GPU 메트릭)

3) GrafanaDashboard CRD (5개)

Grafana Operator가 위 ConfigMap을 읽어 실제 Grafana에 대시보드로 띄워 줍니다.

조건부 데이터소스 변환

enable_amazon_prometheus = true일 때, 대시보드 JSON 안의 Prometheus 데이터소스 참조가 Amazon Managed Prometheus를 가리키도록 변환됩니다. 셀프호스트 Prometheus를 쓰는 다른 환경에 그대로 옮겨도 데이터소스만 갈아 끼우면 동작합니다.

DCGM 대시보드는 등록되지만 메트릭은 비어 있을 수 있음

blueprint.tfvars에서 enable_nvidia_dcgm_exporter = false로 두었기 때문에, DCGM 대시보드는 떠 있어도 메트릭이 들어오지 않습니다. 실 운영 도입 시 가장 먼저 켜야 할 토글입니다.

구동해봅시다

사전 도구 버전

도구 최소 버전
AWS CLI ≥ 2.32.8
Terraform ≥ 1.3.2
kubectl 최신
Docker 최신

배포

리포 클론 후 infra/workshops/genai-on-eks/로 들어가 install.sh를 실행합니다. 약 20~25분이 걸립니다.

git clone https://github.com/awslabs/ai-on-eks.git
cd ai-on-eks/infra/workshops/genai-on-eks
./install.sh

배포가 끝났다면 kubeconfig를 갱신합니다.

aws eks update-kubeconfig --region us-east-2 --name genai-workshop

비용 가드와 정리

GPU 노드가 따라붙는 워크샵이라 켜둔 만큼 비용이 발생합니다. 실습이 끝나면 반드시 정리합니다.

cd terraform/_LOCAL
./cleanup.sh

베이스의 cleanup.sh는 단순 terraform destroy가 아니라 다음을 순서대로 처리합니다.

  1. RayJob/RayService 선삭제 (Ingress/Service가 남으면 destroy가 멈추므로)
  2. Auto Mode 클러스터라면 aws eks update-cluster-config로 내장 NodePool을 비활성 (노드 드레인 안전 확보)
  3. 모든 nodepool 리소스 삭제
  4. kubectl_manifest.* 리소스 일괄 destroy (LB Controller 제외)
  5. 나머지 리소스 destroy
  6. PVC가 만든 EBS 볼륨을 태그 기반으로 추가 삭제
Auto Mode + Karpenter 정리의 함정

베이스 cleanup.sh는 EKS 클러스터 삭제와 VPC 라우트 삭제가 동시에 일어나면서 노드가 네트워크에서 고립되는 시나리오를 방지하기 위해 노드 드레인을 먼저 강제합니다. 직접 terraform destroy만 던지면 종종 막히는 지점이라 이 스크립트를 그대로 쓰는 편이 안전합니다.