| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- aws vpc cni
- eks upgrade
- ClusterIP
- soci
- nodeport
- Terraform
- EmptyDir
- Instance Store
- k8s authentication
- hashicorp
- Fargate
- HostPath
- externaldns
- CoreDNS
- eksctl
- k8s scheduler
- EKS
- 쿠버네티스 네트워크
- serviceaccount
- kube-proxy
- EKS Architecture
- ipamd
- EKS vLLM
- kubernetes networking
- aws-node
- gateway api
- Node group
- auto mode
- pv/pvc
- cni binary
- Today
- Total
ksc0204 님의 블로그
AEWS 4기 9주차(2) - Inference on Amazon EKS(실습) 본문
주관적인 해석이 포함되어 있어 사실과 다르거나 오류가 있을 수 있으니 참고용으로만 읽어주시기 바랍니다.
실습 환경 구성
# Git Repo Clone
git clone https://github.com/awslabs/ai-on-eks.git
cd ai-on-eks/infra/workshops/genai-on-eks
# blueprint.tfvars 수정
name = "genai-workshop"
region = "us-west-2"
# 설치 스크립트 실행
chmod +x install.sh
./install.sh

# kubectl 구성
aws eks --region us-west-2 update-kubeconfig --name genai-workshop
실습 환경 확인

Service Quotas 증설

On-Demand 용량 예약

왜 워크샵 인프라가 사전에 구축되어 있어야 할까?
GenAI on Amazon EKS 워크샵에서는 실습을 시작하기 전에 AWS 인프라 환경이 미리 구성되어 있습니다.
처음에는 단순히 실습 편의성을 위한 구성처럼 보일 수 있지만, 실제로는 생성형 AI 환경 특성상 사전 인프라 구성이 매우 중요합니다.
특히 GPU 기반 Kubernetes 환경은 일반적인 웹 애플리케이션 환경보다 훨씬 복잡하며, 네트워크·보안·모니터링·스토리지까지 함께 고려해야 하기 때문입니다.
워크샵에서 사전 구성된 인프라
워크샵에서는 아래와 같은 인프라가 미리 준비되어 있습니다.
| 구성 요소 | 역할 |
|---|---|
| Amazon VPC | EKS 및 GPU 노드를 위한 전용 네트워크 환경 |
| Amazon EKS Auto Mode | Kubernetes 클러스터 자동 운영 및 확장 |
| Self Managed Add-ons | Prometheus / Grafana 기반 모니터링 환경 |
| Amazon Managed Prometheus | 메트릭 저장 및 장기 보관 |
1. Amazon VPC가 미리 구성되어야 하는 이유
Kubernetes 환경에서는 네트워크 구성이 굉장히 중요합니다.
특히 생성형 AI 환경에서는:
- GPU Node 통신
- Pod 간 통신
- 외부 API 접근
- 모델 다운로드
- 모니터링 트래픽
등 다양한 네트워크 흐름이 동시에 발생합니다.
따라서:
- 퍼블릭 서브넷
- 프라이빗 서브넷
- NAT Gateway
- 라우팅 테이블
- 보안 그룹
등이 사전에 안정적으로 구성되어 있어야 합니다.
즉, Kubernetes 클러스터가 안정적으로 통신할 수 있는 네트워크 기반을 먼저 준비해야 하는 것입니다.
2. EKS Auto Mode가 미리 구성되어야 하는 이유
생성형 AI 환경에서는 GPU 리소스 사용량이 계속 변합니다.
예를 들어:
- 추론 요청이 갑자기 증가
- 대형 모델 로딩
- GPU 메모리 부족
- Pod 증가
등의 상황이 자주 발생합니다.
이때 Kubernetes 클러스터가 자동으로:
- GPU Node 생성
- Node 확장
- 리소스 최적화
- 필요 없는 노드 제거
등을 수행해야 합니다.
EKS Auto Mode는 이러한 복잡한 Kubernetes 운영 작업을 AWS가 자동으로 처리해주는 기능입니다.
사용자는 AI 모델 운영에 집중하고, 클러스터 운영은 AWS가 자동으로 처리하는 구조
따라서 실습 전에 Auto Mode 기반 환경이 준비되어 있어야 GPU 기반 실습을 안정적으로 진행할 수 있습니다.
3. 모니터링 환경이 사전에 필요한 이유
생성형 AI 환경에서는 단순히 애플리케이션이 실행되는 것만으로는 충분하지 않습니다.
특히 GPU 서버는 비용이 매우 비싸기 때문에, 현재 리소스 사용량을 지속적으로 모니터링해야 합니다.
워크샵에서는 다음과 같은 모니터링 구성 요소가 미리 설치되어 있습니다.
- Kube Prometheus Stack
- Grafana Operator
- Amazon Managed Prometheus
이를 통해:
- GPU 사용률
- Memory 사용량
- Pod 상태
- Node 상태
- 추론 서버 상태
등을 실시간으로 확인할 수 있습니다.
AI 플랫폼에서는 "실행"보다 "관찰 가능성(Observability)"이 훨씬 중요합니다.
4. Amazon Managed Prometheus가 필요한 이유
Prometheus는 Kubernetes 환경에서 가장 많이 사용하는 메트릭 수집 도구입니다.
하지만 직접 운영하려면:
- 스토리지 관리
- 백업
- 고가용성 구성
- 메트릭 보관
- 스케일링
등을 모두 직접 처리해야 합니다.
생성형 AI 환경에서는 GPU 메트릭과 추론 메트릭이 매우 빠르게 증가하기 때문에, 대규모 메트릭 저장 환경이 필요합니다.
Amazon Managed Prometheus는 이러한 메트릭 저장 및 확장 작업을 AWS가 대신 관리해줍니다.
대규모 Kubernetes 메트릭을 안정적으로 저장하기 위한 AWS 관리형 모니터링 서비스
왜 실습 전에 미리 준비해야 할까?
이번 워크샵은 단순 Kubernetes 실습이 아니라, GPU 기반 생성형 AI 플랫폼 환경을 다루는 실습입니다.
만약 실습 시작부터 사용자가 직접:
- VPC 생성
- GPU Node 설정
- EKS 설치
- 모니터링 구성
- Prometheus 구축
등을 모두 수행한다면, 실제 AI 워크로드 실습 전에 너무 많은 시간이 소모됩니다.
따라서 AWS에서는 실습 핵심인:
- vLLM 배포
- GPU 추론
- Ray 기반 분산 처리
- LLM 운영
등에 집중할 수 있도록 기본 인프라를 미리 준비해둔 것입니다.
EKS 클러스터 연결하기
워크샵에서는 genai-workshop 이라는 Amazon EKS 클러스터가 사전에 프로비저닝되어 있습니다.
실습을 시작하기 전에 먼저 로컬 환경의 kubectl 이 해당 EKS 클러스터와 통신할 수 있도록 연결 작업을 수행해야 합니다.
Kubernetes에서는 kubectl이 kubeconfig 정보를 기반으로 어떤 클러스터와 통신할지 결정합니다. 따라서 가장 먼저 kubeconfig 설정을 업데이트하는 과정이 필요합니다.
aws eks update-kubeconfig --name genai-workshop --region us-west-2
명령어 설명
해당 명령어는 현재 AWS 계정에 존재하는 EKS 클러스터 정보를 조회한 뒤, 로컬 환경의 kubeconfig 파일에 클러스터 접속 정보를 자동으로 추가합니다.
kubectl이 genai-workshop EKS 클러스터와 통신할 수 있도록 연결 정보를 등록하는 작업
옵션 설명
| 옵션 | 설명 |
|---|---|
| aws eks | AWS EKS 관련 명령 실행 |
| update-kubeconfig | kubectl 접속 정보 업데이트 |
| --name | 연결할 EKS 클러스터 이름 지정 |
클러스터 연결 확인
클러스터 연결이 정상적으로 완료되었다면, 현재 Kubernetes Pod 상태를 조회하여 연결 여부를 확인할 수 있습니다.
kubectl get pods --all-namespaces

명령어 설명
현재 클러스터 전체 Namespace에서 실행 중인 Pod 목록을 조회합니다.
여기서 정상적으로 Pod 목록이 출력된다면:
- kubectl 연결 성공
- EKS 인증 성공
- Kubernetes API 접근 성공
상태라고 볼 수 있습니다.
옵션 설명
| 옵션 | 설명 |
|---|---|
| get pods | Pod 목록 조회 |
| --all-namespaces | 모든 Namespace 대상 조회 |
기본 클러스터 구성 살펴보기
EKS Auto Mode 내장 NodePool 구성
이번 워크샵에서는 사용자가 직접 NodeGroup을 생성하는 방식이 아니라, EKS Auto Mode의 내장 NodePool 기능을 사용합니다.
즉 AWS가 Kubernetes 워크로드 상태를 분석하여 적절한 EC2 인스턴스를 자동으로 생성하고 관리하는 구조입니다.
기본적으로 Auto Mode 환경에서는 다음 두 가지 NodePool이 제공됩니다.
- General-Purpose NodePool
- System NodePool
General-Purpose NodePool

General-Purpose NodePool은 실제 애플리케이션과 일반 워크로드를 실행하기 위한 영역입니다.
예를 들어:
- API 서버
- 웹 애플리케이션
- LLM 추론 서버
- 일반 CPU 기반 워크로드
등이 실행될 수 있습니다.
General-Purpose NodePool 특징
- Auto Mode에서 자동 관리되는 NodePool
- Bottlerocket OS 기반으로 구성
- AutoMode가 자동으로 EKS 최적화 AMI 선택
- 워크로드 요구사항에 따라 적절한 EC2 인스턴스 자동 생성
- CPU 기반 워크로드를 위한 지능형 스케줄링 수행
- amd64 아키텍처만 사용
- 온디맨드 EC2 인스턴스만 사용
Bottlerocket OS란?
Bottlerocket은 AWS가 Kubernetes 환경을 위해 만든 경량 Linux 운영체제입니다.
불필요한 패키지를 최소화하여:
- 보안 강화
- 부팅 속도 향상
- 컨테이너 최적화
- 운영 단순화
등의 장점을 제공합니다.
왜 amd64만 사용할까?
일반 애플리케이션 환경에서는 아직 amd64 기반 컨테이너 이미지가 가장 많이 사용됩니다.
따라서 General-Purpose NodePool은 호환성과 안정성을 위해 amd64 아키텍처만 사용합니다.
System NodePool

System NodePool은 Kubernetes 클러스터 자체를 운영하기 위한 시스템 전용 영역입니다.
보통 다음과 같은 시스템 컴포넌트가 실행됩니다.
- CoreDNS
- kube-proxy
- Amazon VPC CNI
- Karpenter
- 모니터링 에이전트
System NodePool 특징
- Auto Mode에서 자동 관리되는 NodePool
- Bottlerocket OS 기반 구성
- AutoMode가 자동으로 EKS 최적화 AMI 선택
- 클러스터 시스템 컴포넌트 전용 영역
- CriticalAddonsOnly taint 적용
- amd64 / arm64 아키텍처 모두 지원
CriticalAddonsOnly taint란?
System NodePool에는 다음과 같은 taint가 적용됩니다.
CriticalAddonsOnly=true:NoSchedule
이 설정은 일반 애플리케이션 Pod가 시스템 노드에 스케줄링되지 않도록 제한하는 역할을 수행합니다.
즉:
시스템 Pod와 애플리케이션 Pod를 분리하여 클러스터 안정성을 유지하기 위한 구조
라고 볼 수 있습니다.
왜 arm64도 지원할까?
최근 Kubernetes 시스템 컴포넌트들은 ARM 기반 Graviton 인스턴스도 많이 사용합니다.
System NodePool은 비용 효율성과 유연성을 위해 amd64와 arm64를 모두 지원합니다.
EKS 애드온 개요
EKS Auto Mode 클러스터는 Kubernetes 운영에 필요한 핵심 구성 요소들을 자동으로 관리합니다.
즉 사용자가 직접:
- 네트워크 플러그인 설치
- 스토리지 드라이버 구성
- DNS 설정
- GPU 플러그인 설치
등을 처리하지 않아도 됩니다.
Auto Mode가 필수 구성 요소를 자동으로 설치하고 관리합니다.
내장된 AutoMode 컴포넌트
EKS Auto Mode는 다음과 같은 핵심 Kubernetes 컴포넌트를 자동 관리합니다.
| 컴포넌트 | 역할 |
|---|---|
| CoreDNS | 클러스터 내부 DNS 처리 |
| kube-proxy | Pod 네트워크 및 Service 트래픽 처리 |
| VPC CNI | AWS VPC 기반 Pod 네트워크 연결 |
| EKS Pod Identity Agent | Pod IAM 권한 연결 |
| EBS CSI Driver | EBS 기반 Persistent Volume 지원 |
| NVIDIA Device Plugin | GPU 리소스 관리 |
NVIDIA Device Plugin
GPU 기반 워크로드에서는 Kubernetes가 GPU를 인식할 수 있어야 합니다.
NVIDIA Device Plugin은:
- GPU 리소스 감지
- Kubernetes GPU Resource 등록
- GPU Pod 스케줄링 지원
등을 수행합니다.
Auto Mode에서는 Bottlerocket GPU OS 이미지에 해당 기능이 기본 포함되어 있습니다.
자체 관리형 애드온(Self Managed Add-ons)
워크샵에서는 기본 Auto Mode 기능 외에도 모니터링을 위한 자체 관리형 애드온이 추가로 설치되어 있습니다.
Kube Prometheus Stack
Prometheus 기반 Kubernetes 모니터링 스택입니다.
다음 기능들을 제공합니다.
- 메트릭 수집
- 시계열 데이터 저장
- 알림(Alert) 구성
- Grafana Dashboard 제공
Grafana Operator
Grafana를 Kubernetes 환경에서 자동으로 관리하기 위한 Operator입니다.
이를 통해:
- Grafana 자동 배포
- Dashboard 자동 구성
- Datasource 자동 연결
등을 손쉽게 관리할 수 있습니다.
모니터링 스택 Pod 확인
현재 monitoring Namespace에서 실행 중인 Pod를 확인합니다.
kubectl get pods -n monitoring -o wide

명령어 설명
- -n monitoring : monitoring Namespace 조회
- -o wide : Pod가 실행 중인 Node 정보까지 출력
즉, 모니터링 관련 Pod가 어떤 Node에서 실행 중인지 확인하는 명령어입니다.
System NodePool에서 실행 중인지 확인
다음 명령어는 monitoring Namespace의 Pod가 실제로 어떤 NodePool에서 실행 중인지 확인하는 과정입니다.
kubectl get pods -n monitoring -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.nodeName}{"\n"}{end}' | while read pod node; do
echo "Pod: $pod -> Node: $node ($(kubectl get node $node -o jsonpath='{.metadata.labels.karpenter\.sh/nodepool}' 2>/dev/null || echo 'unknown'))"
done

명령어 흐름 설명
- monitoring Namespace의 Pod 이름 조회
- Pod가 실행 중인 Node 확인
- 해당 Node의 NodePool Label 조회
- General-Purpose / System NodePool 여부 출력
모니터링 Pod가 실제로 시스템 전용 NodePool에서 동작하는지 검증하는 과정이라고 볼 수 있습니다.
모델 다운로드 작업 확인
워크샵 환경에서는 AI 모델을 추론 서버에 배포하기 전에 먼저 모델 파일을 다운로드하는 작업(Job)이 실행됩니다.
이번 실습에서는 Hugging Face 또는 S3에 저장된 모델 파일을 클러스터 내부로 다운로드하기 위한 model-download Job 이 구성되어 있습니다.
이 작업은 GPU 연산이 필요한 추론 작업이 아니라, 단순 파일 다운로드 및 저장 작업이기 때문에 General-Purpose NodePool에서 실행됩니다.
- GPU 리소스 불필요
- 단순 네트워크 다운로드 작업
- CPU 기반 워크로드
- General-Purpose NodePool에서 실행
- System NodePool toleration 없음
모델 다운로드 Job 확인
먼저 현재 실행 중인 모델 다운로드 Job 상태를 확인해보겠습니다.
kubectl get job model-download -o wide

명령어 설명
- kubectl get job : Kubernetes Job 조회
- model-download : 조회 대상 Job 이름
- -o wide : 추가 상세 정보 출력
Job은 Kubernetes에서 일회성 작업(Batch 작업)을 수행할 때 사용하는 리소스입니다.
예를 들어:
- 모델 다운로드
- 데이터 전처리
- 백업 작업
- DB 마이그레이션
등에 자주 사용됩니다.
즉 model-download Job은 LLM 모델 파일을 클러스터 내부로 가져오기 위한 초기 준비 작업이라고 볼 수 있습니다.
어떤 NodePool에서 실행되었는지 확인
다음 명령어를 통해 model-download Job Pod가 실제로 어떤 NodePool에서 실행 중인지 확인할 수 있습니다.
kubectl get pod -l job-name=model-download -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.nodeName}{"\n"}{end}' | while read pod node; do
echo "Pod: $pod -> Node: $node ($(kubectl get node $node -o jsonpath='{.metadata.labels.karpenter\.sh/nodepool}' 2>/dev/null || echo 'general-purpose'))"
done
명령어 흐름 설명
- job-name=model-download Label을 가진 Pod 조회
- 해당 Pod가 실행 중인 Node 이름 확인
- Node의 karpenter.sh/nodepool Label 조회
- Pod가 어떤 NodePool에서 실행 중인지 출력
왜 General-Purpose NodePool에서 실행될까?
model-download 작업은 단순 모델 파일 다운로드 작업이기 때문에 GPU 리소스가 필요하지 않습니다.
또한 System NodePool에서 실행되기 위한 toleration도 설정되어 있지 않습니다.
따라서 Kubernetes Scheduler는 해당 Pod를 일반 워크로드용 NodePool인 General-Purpose NodePool에 배치하게 됩니다.
- GPU가 필요 없는 CPU 작업
- System NodePool toleration 없음
- 일반 워크로드로 분류
- General-Purpose NodePool에 자동 스케줄링
Kubernetes Scheduler 관점에서 보면
Kubernetes Scheduler는 Pod의 요구사항을 기반으로 어떤 Node에 배치할지 결정합니다.
대표적으로 다음 요소들을 고려합니다.
- CPU / Memory 요청량
- GPU 요청 여부
- Node Label
- Taint / Toleration
- Affinity / Anti-Affinity
이번 model-download Job은 GPU 요청이 없고, 특별한 시스템 toleration도 없기 때문에 가장 일반적인 NodePool에 배치된 것입니다.
즉 Kubernetes는 Pod의 요구사항을 분석하여 적절한 NodePool에 자동으로 워크로드를 배치합니다.
EKS Auto Mode 기반 GPU 인프라 최적화
이번 섹션에서는 EKS Auto Mode 환경에서 고성능 LLM 추론을 위한 GPU 인프라가 어떻게 구성되는지 살펴보겠습니다.
특히:
- GPU NodePool 구성
- SOCI 기반 이미지 가속화
- GPU Node 자동 프로비저닝
- NVIDIA Device Plugin 자동 구성
등을 통해 대규모 생성형 AI 환경을 효율적으로 운영하는 방법을 이해할 수 있습니다.
- GPU 워크로드 기반 자동 노드 생성
- LLM 추론용 GPU 인프라 최적화
- SOCI를 통한 초고속 컨테이너 시작
- Bottlerocket 기반 GPU 최적화 환경
- 운영 복잡성 최소화
EKS Auto Mode GPU 노드 프로비저닝
LLM 추론 환경에서는 GPU 리소스가 필수적입니다.
하지만 GPU 서버는 매우 비싸고 운영 복잡성도 높기 때문에, 필요할 때만 자동으로 생성되는 구조가 중요합니다.
EKS Auto Mode는 GPU 워크로드를 감지하면 자동으로 GPU Node를 생성하고 제거합니다.
즉 사용자는:
- 직접 EC2 생성
- GPU 드라이버 설치
- Karpenter 설정
- NodeGroup 관리
등을 직접 수행하지 않아도 됩니다.
GPU 인프라 운영은 AWS가 자동 처리하고, 사용자는 AI 모델 운영에만 집중할 수 있는 구조
GPU NodePool 및 NodeClass 확인
먼저 현재 구성된 GPU NodePool 설정을 확인해보겠습니다.
kubectl get nodepool/gpu

다음으로 GPU NodeClass 설정을 확인합니다.
kubectl get nodeclass/gpu

NodePool과 NodeClass 차이
| 구성 요소 | 역할 |
|---|---|
| NodePool | 어떤 워크로드용 노드를 생성할지 정의 |
| NodeClass | 실제 EC2/스토리지/AMI 구성 정의 |
즉:
- NodePool = 노드 정책
- NodeClass = 실제 인프라 설정
이라고 이해하면 쉽습니다.
Auto Mode GPU NodePool 구성
GPU NodePool에는 LLM 추론 환경을 위한 여러 최적화가 포함되어 있습니다.
| 구성 항목 | 설명 |
|---|---|
| 인스턴스 타입 | GPU 지원 인스턴스(g6e.2xlarge) |
| 운영체제 | EKS 최적화 Bottlerocket AMI |
| SOCI Snapshotter | 대용량 이미지 가속화 자동 활성화 |
| Taint | GPU 워크로드 전용 스케줄링 |
| Label | karpenter.sh/nodepool=gpu |
| 아키텍처 | amd64 전용 |
| 스토리지 | SOCI 최적화 NVMe 스토리지 사용 |
GPU Node에 Taint를 사용하는 이유
GPU 인스턴스는 비용이 매우 높습니다.
따라서 일반 CPU 워크로드가 GPU Node에 잘못 배치되지 않도록 제한해야 합니다.
GPU NodePool에는 다음과 같은 taint가 적용됩니다.
nvidia.com/gpu:NoSchedule
즉 GPU 사용 권한(Toleration)이 있는 Pod만 GPU Node에 배치됩니다.
GPU 리소스를 필요한 워크로드에만 사용하여 비용 낭비를 방지하는 구조
SOCI Snapshotter란?
LLM 환경에서는 컨테이너 이미지 크기가 매우 큽니다.
예를 들어:
- vLLM 이미지
- PyTorch CUDA 이미지
- Transformers 기반 이미지
등은 수 GB ~ 수십 GB 크기를 가질 수 있습니다.
기존 방식은 컨테이너 이미지를 모두 다운로드한 뒤 실행했지만, SOCI는 필요한 레이어를 병렬로 빠르게 가져와 컨테이너 시작 속도를 크게 향상시킵니다.
- 대용량 컨테이너 이미지 시작 속도 향상
- 병렬 Pull 및 압축 해제 지원
- GPU Node 시작 시간 단축
- LLM 추론 서버 빠른 배포
- NVMe 스토리지 기반 고성능 처리
SOCI(Seekable OCI)와 SOCI Snapshotter
MLOps 환경에서 SOCI Snapshotter가 특별히 더 중요하게 다뤄지는 이유는 용량이 큰 컨테이너 이미지 크기와 비싼 GPU 유휴 비용이라는 두 가지 치명적인 문제를 동시에 해결해주기 때문입니다.
- CUDA 및 PyTorch/TensorFlow 엔진 및 LLM 모델이 이미지로 되어 있을 경우 배포까지 상당 시간 소요(네트워크 트래픽 및 디스크 I/O 과부하 발생)
- 트래픽 증가 시 AutoScaling 과정에서 배포가 늦어지는 이슈 발생(서비스 장애 발생)
- 이미지 다운로드 소요 시간으로 인해 GPU 유휴 상태가 길어져 비용 낭비 발생
일반적으로 컨테이너를 실행하려면 이미지 전체를 레지스트리(ECR 등)에서 내려받고 압축을 풀어야 합니다. 특히 수 GB에 달하는 대용량 이미지는 네트워크 대역폭을 많이 소모하고, 실제 애플리케이션이 배포되기까지 오랜 시간이 소요됩니다.
SOCI는 이미지의 압축을 풀지 않고도 필요한 데이터만 골라서 읽을 수 있도록(Seekable) 하여, 컨테이너가 즉시 시작될 수 있게 돕습니다.
| 특징 | 일반적인 방식 (Gzip) | SOCI 적용 방식 |
| 다운로드 | 이미지 전체 다운로드 후 실행 | 인덱스 참조 후 필요 부분만 선별 다운로드 |
| 기동 속도 | 이미지 크기에 비례 (느림) | 크기와 상관없이 매우 빠름 (즉시 실행) |
| 적합한 사례 | 일반적인 경량 컨테이너 | 대용량 이미지, 급격한 트래픽 변동 대응 |
작동 원리
SOCI는 이미지 옆에 별도의 인덱스(SOCI Index) 파일을 생성합니다.
- 지연 로딩(Lazy Loading): 전체 이미지를 받지 않고, 프로세스 실행에 당장 필요한 파일들만 인덱스를 보고 원격지에서 즉시 가져옵니다.
- 인덱스 아티팩트: 기존 이미지를 수정하지 않고, 옆에 별도의 레이어로 인덱스 정보를 저장하므로 기존 워크플로우를 해치지 않습니다.
- FUSE 기반(Filesystem in Userspace): containerd의 stargz-snapshotter와 유사한 방식으로 동작하며, 원격의 압축 데이터를 로컬 파일 시스템처럼 마운트하여 필요한 부분만 탐색(Seek)합니다.
사용 방법 (Workflow)
EKS에서 SOCI를 적용하려면 보통 다음과 같은 단계를 거칩니다.
- SOCI Index 생성: 컨테이너 이미지를 빌드한 후, AWS에서 제공하는 SOCI Snapshotter CLI를 사용하여 인덱스를 생성하고 저장소에 푸시합니다.
- Snapshotter 활성화: EKS 노드(EC2)에서 soci-snapshotter를 실행하도록 설정합니다. (Fargate는 AWS가 알아서 처리해 주므로 인덱스만 있으면 됩니다.)
주요 기능
- 지연 로딩(Lazy Loading) 제어: 컨테이너 내부 프로세스가 특정 파일(예: /usr/bin/app)을 읽으려고 할 때, soci-snapshotter가 이를 가로채서 ECR 레지스트리에서 해당 파일이 포함된 부분만 원격으로 요청해 가져옵니다.
- ZIB(Znative Index Builder): SOCI 인덱스를 분석하여 압축된 레이어 내에서 정확한 오프셋(위치)을 찾아냅니다.
- 하이브리드 모드: 컨테이너는 즉시 시작되지만, 백그라운드에서는 나머지 이미지 데이터들을 조용히 계속 내려받아 결국 로컬 캐시를 완성합니다.
EKS 노드 Work Flow(with.soci-snapshotter)

- EKS 동작 순서
1. Kubelet이 파드 생성을 요청합니다.
2. containerd가 이미지를 확인하고, SOCI 인덱스가 있는지 체크합니다.
3. 인덱스가 있다면 soci-snapshotter가 개입하여 이미지 전체를 받기 전에 가상 마운트를 생성합니다.
4. 컨테이너 런타임(runc)이 0.x초 만에 컨테이너를 실행합니다.
5. 앱이 실행되는 동안 필요한 데이터만 실시간으로 원격지에서 로드합니다.
왜 ML 워크로드에서 중요할까?
생성형 AI 환경에서는 GPU Pod가 자주 생성되고 제거됩니다.
만약 컨테이너 이미지 다운로드 시간이 오래 걸리면:
- 오토스케일링 지연
- 추론 서버 시작 지연
- 응답 속도 저하
등의 문제가 발생할 수 있습니다.
SOCI는 이러한 문제를 해결하기 위한 컨테이너 가속화 기술입니다.
Bottlerocket 기반 GPU 최적화
EKS Auto Mode는 GPU 워크로드용 Bottlerocket AMI를 자동 선택합니다.
이 GPU 전용 Bottlerocket 이미지에는:
- NVIDIA Driver
- Container Runtime GPU 지원
- NVIDIA Device Plugin
등이 기본 포함되어 있습니다.
즉 사용자가 직접:
- GPU 드라이버 설치
- NVIDIA Plugin 설치
- CUDA Runtime 구성
등을 직접 하지 않아도 됩니다.
GPU Node가 생성되는 즉시 바로 GPU 사용 가능한 상태로 준비되는 구조
GPU 온디맨드 용량 예약(ODCR) 구성 및 GPU 기능 테스트
GPU 기반 생성형 AI 환경에서는 GPU 인스턴스를 안정적으로 확보하는 것이 매우 중요합니다.
특히:
- GPU 수요 증가
- 리전별 GPU 부족 현상
- 대규모 AI 워크로드 증가
등으로 인해 필요한 시점에 GPU 인스턴스를 즉시 확보하지 못하는 경우가 발생할 수 있습니다.
이를 해결하기 위해 AWS에서는 ODCR(On-Demand Capacity Reservation) 기능을 제공합니다.
ODCR은 필요한 GPU 인스턴스를 미리 예약해두어 언제든 안정적으로 사용할 수 있도록 보장하는 기능입니다.
왜 GPU 용량 예약이 필요할까?
GPU 인스턴스는 일반 EC2보다 공급량이 제한적입니다.
특히:
- g6 계열
- p5 계열
- Trainium 계열
같은 AI 전용 인스턴스는 수요가 매우 높습니다.
만약 Auto Scaling 시점에 GPU 용량이 부족하면:
- GPU Node 생성 실패
- LLM 추론 서버 시작 실패
- 오토스케일링 지연
- 서비스 장애 가능성
등의 문제가 발생할 수 있습니다.
- GPU 인스턴스 사전 확보
- AI 워크로드 안정성 향상
- GPU 부족 상황 방지
- LLM 서비스 안정적 운영
- Auto Scaling 성공률 향상
현재 AWS Region 확인
먼저 현재 Kubernetes Context에서 AWS Region 정보를 추출합니다.
AWS_REGION=$(kubectl config get-contexts | grep '*' | awk '{print $2}' | cut -d':' -f4)
echo "Using AWS Region: $AWS_REGION"
명령어 설명
- kubectl config get-contexts : 현재 Kubernetes Context 조회
- grep '*' : 현재 활성 Context 필터링
- cut -d':' -f4 : ARN에서 Region 추출
만약 Region이 정상 출력되지 않는다면 수동으로 설정할 수 있습니다.
export AWS_REGION="us-west-2"
ODCR ID 조회
다음 명령어를 통해 현재 리전에 생성된 활성 상태의 Capacity Reservation 정보를 조회합니다.
ODCR_ID=$(aws ec2 describe-capacity-reservations \
--region $AWS_REGION \
--filters "Name=state,Values=active" \
--query "CapacityReservations[0].CapacityReservationId" \
--output text)
echo "Found ODCR ID: $ODCR_ID"
명령어 설명
- describe-capacity-reservations : Capacity Reservation 조회
- state=active : 활성 상태만 조회
- CapacityReservationId : 예약 ID 추출
GPU NodeClass에 ODCR 연결
이제 GPU NodeClass가 ODCR을 사용하도록 설정합니다.
즉 Auto Mode가 GPU Node를 생성할 때, 미리 예약된 GPU 용량을 우선 사용하도록 구성하는 과정입니다.
Patch 파일 생성
mkdir -p manifests/100-introduction
cat < manifests/100-introduction/patch-gpu-nodeclass.yaml
apiVersion: eks.amazonaws.com/v1
kind: NodeClass
metadata:
name: gpu
spec:
capacityReservationSelectorTerms:
- id: $ODCR_ID
EOF
Patch 적용
kubectl patch nodeclass gpu \
--patch-file manifests/100-introduction/patch-gpu-nodeclass.yaml \
--type=merge
적용 여부 확인
kubectl get nodeclass gpu -o yaml | grep -A 3 capacityReservationSelectorTerms

정상 적용되면 GPU NodeClass가 예약된 GPU Capacity를 사용하게 됩니다.
Auto Mode가 GPU Node를 생성할 때 예약된 GPU 인스턴스를 우선 사용하도록 구성하는 과정
GPU 기능 테스트
이제 실제 GPU 워크로드를 배포하여 EKS Auto Mode가 GPU Node를 자동 생성하는 과정을 확인해보겠습니다.
nvidia-smi 테스트 Pod 배포
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nvidia-smi
spec:
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
nodeSelector:
karpenter.sh/nodepool: gpu
containers:
- name: nvidia-smi
image: public.ecr.aws/amazonlinux/amazonlinux:2023-minimal
command: ["nvidia-smi"]
resources:
limits:
nvidia.com/gpu: 1
restartPolicy: OnFailure
EOF

이 Pod 구성의 핵심 포인트
| 설정 | 의미 |
|---|---|
| tolerations | GPU Node taint 허용 |
| nodeSelector | GPU NodePool 지정 |
| nvidia.com/gpu:1 | GPU 1개 요청 |
| nvidia-smi | GPU 상태 확인 명령 |
GPU Node 자동 생성 과정 관찰
다음 명령어를 통해 GPU Node 생성 과정을 실시간으로 확인할 수 있습니다.
kubectl get nodes -w
kubectl get pod nvidia-smi -w
-w 옵션은 Watch 모드로, 리소스 상태 변화를 실시간 모니터링합니다.


실제로 발생하는 흐름
- GPU Pod 생성 요청
- GPU Node 없음 → Pod Pending 상태
- Auto Mode가 GPU Node 생성 시작
- Bottlerocket GPU Node 부팅
- NVIDIA Driver 자동 구성
- GPU Node Ready 상태 전환
- Pod가 GPU Node에 스케줄링
- nvidia-smi 실행
일반적으로 GPU Node가 Ready 상태가 되기까지 약 30초 ~ 1분 정도 소요됩니다.
GPU Node가 없더라도 Pod 요청만으로 Auto Mode가 자동 생성해주는 구조
GPU 상태 확인
마지막으로 nvidia-smi 결과를 확인합니다.
kubectl logs nvidia-smi

정상 동작 시 다음 정보가 출력됩니다.
- GPU 모델 정보
- CUDA Driver 버전
- GPU 메모리 사용량
- 온도 및 전력 사용량
- 실행 중인 프로세스
- GPU Pod 생성 시 Auto Mode 자동 확장
- GPU Node 자동 생성
- Bottlerocket GPU 환경 자동 구성
- NVIDIA Driver 자동 활성화
- GPU 리소스 자동 연결
- ODCR 기반 안정적 GPU 확보
```
왜 vLLM을 사용할까?
생성형 AI 환경에서는 단순히 모델을 실행하는 것보다 얼마나 빠르고 효율적으로 추론(Inference)을 처리할 수 있는지가 매우 중요합니다.
특히 LLM 서비스에서는:
- 많은 동시 요청 처리
- GPU 메모리 최적화
- 빠른 응답 속도
- 비용 효율성
등이 핵심 요소가 됩니다.
이러한 문제를 해결하기 위해 등장한 대표적인 추론 엔진이 바로 vLLM 입니다.
vLLM은 GPU 메모리를 더 효율적으로 사용하여 LLM 추론 성능을 극대화하기 위해 만들어진 오픈소스 추론 엔진입니다.
LLM 추론에서 발생하는 문제
일반적인 PyTorch 기반 LLM 추론 환경에서는 다음과 같은 문제가 발생합니다.
| 문제 | 영향 |
|---|---|
| GPU 메모리 낭비 | 동시 요청 수 제한 |
| 비효율적인 배치 처리 | GPU 활용률 저하 |
| 긴 응답 지연 시간 | 사용자 경험 저하 |
| 대형 모델 메모리 부족 | 추론 실패 가능성 |
특히 여러 사용자가 동시에 요청을 보내는 프로덕션 환경에서는 GPU 리소스를 얼마나 효율적으로 사용하는지가 매우 중요합니다.
vLLM의 핵심 장점
vLLM은 이러한 문제를 해결하기 위해 다양한 최적화 기능을 제공합니다.
- GPU 메모리 최적화
- 연속 배칭(Continuous Batching)
- PagedAttention 기반 KV Cache 관리
- 고성능 CUDA 최적화
- OpenAI 호환 API 지원
- 대규모 동시 요청 처리
1. 뛰어난 추론 성능
vLLM은 일반적인 PyTorch 기반 추론보다 훨씬 높은 처리량을 제공합니다.
공식 벤치마크 기준으로:
vLLM 성능 향상
- 최대 24배 높은 처리량
- GPU 활용률 극대화
- 낮은 응답 지연 시간
- 동시 요청 처리 성능 향상
Continuous Batching
기존 추론 엔진은 고정된 요청 단위로 배치를 처리하는 경우가 많습니다.
하지만 vLLM은 실시간으로 들어오는 요청을 동적으로 배치 처리합니다.
이를 통해 GPU가 쉬지 않고 계속 작업할 수 있어 GPU 활용률이 크게 향상됩니다.
2. GPU 메모리 최적화
LLM 추론에서 가장 중요한 요소 중 하나는 바로 KV Cache 메모리 관리입니다.
기존 방식에서는 KV Cache가 비효율적으로 관리되어 GPU 메모리 낭비가 많이 발생했습니다.
vLLM은 이를 해결하기 위해 PagedAttention 기술을 사용합니다.
PagedAttention이란?
운영체제의 가상 메모리 페이징 방식처럼 KV Cache를 작은 블록 단위로 관리하는 기술입니다.
이를 통해:
- 메모리 단편화 감소
- 동적 메모리 할당
- GPU 메모리 낭비 최소화
- 더 큰 Batch 처리 가능
등의 장점을 얻을 수 있습니다.
- GPU 메모리 사용량 최대 60% 절감
- 더 긴 Context Window 지원
- 더 많은 동시 요청 처리
- 더 큰 모델 운영 가능
3. 프로덕션 환경에 최적화
vLLM은 실제 서비스 환경에서 사용하기 쉽도록 다양한 기능을 제공합니다.
OpenAI 호환 API
vLLM은 OpenAI API 형식을 그대로 지원합니다.
즉 기존 OpenAI 기반 애플리케이션을 쉽게 자체 LLM 환경으로 이전할 수 있습니다.
Streaming 지원
응답을 한 번에 모두 생성하지 않고, 토큰 단위로 실시간 스트리밍할 수 있습니다.
따라서 사용자 입장에서 훨씬 빠른 응답처럼 느껴집니다.
분산 추론 지원
Tensor Parallelism을 통해 여러 GPU를 사용하는 대규모 모델도 실행할 수 있습니다.
vLLM만 사용할 수 있을까?
물론 vLLM만이 유일한 선택지는 아닙니다.
대표적인 다른 추론 엔진으로는:
- TensorRT-LLM
- TGI(Text Generation Inference)
- Triton Inference Server
- DeepSpeed-MII
등이 존재합니다.
하지만 vLLM은:
- 쉬운 사용성
- 높은 성능
- 강력한 커뮤니티
- OpenAI 호환성
등의 이유로 현재 가장 많이 사용되는 LLM 추론 엔진 중 하나입니다.
vLLM 기반 모델 로딩 및 서빙 구조 이해하기
이번 섹션에서는 Amazon EKS 환경에서 vLLM을 이용해 LLM 모델을 실제로 로딩하고 서빙하는 과정을 살펴봅니다.
단순히 컨테이너를 실행하는 것이 아니라, GPU 노드 프로비저닝부터 모델 다운로드, GPU 메모리 로딩, HTTP API 서비스 오픈까지 전체 흐름을 이해하는 것이 핵심입니다.
LLM 추론 환경에서는 “모델을 얼마나 빠르고 안정적으로 로딩할 수 있는가” 가 전체 서비스 성능에 매우 큰 영향을 줍니다.
vLLM을 사용한 Ministral 모델 배포
이번 섹션에서는 Amazon EKS 환경에서 vLLM 기반 Ministral-3-8B-Instruct-2512 모델을 실제로 배포하는 과정을 살펴봅니다.
이번 워크샵에서는 모델과 가중치가 이미 Amazon S3에 업로드되어 있으며, vLLM은 Run:AI Streamer를 사용해 S3에서 직접 모델을 스트리밍 방식으로 로딩합니다.
즉, 대용량 모델 파일을 로컬 스토리지에 복사하지 않고 S3에서 바로 GPU 환경으로 로딩하는 구조입니다.
사용 모델 소개
이번 실습에서는 Ministral-3-8B-Instruct-2512 모델을 사용합니다.
| 항목 | 설명 |
|---|---|
| 모델명 | Ministral-3-8B-Instruct-2512 |
| 파라미터 수 | 8B (80억) |
| 용도 | Instruction 기반 LLM 추론 |
| 배포 방식 | vLLM + Run:AI Streamer |
| 저장 위치 | Amazon S3 |
모델 파일은 워크샵 시간을 줄이기 위해 이미 S3 버킷에 업로드되어 있습니다.
환경 변수 설정
먼저 현재 AWS 계정 ID를 조회하여 S3 버킷 이름을 구성합니다.
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity \
--query Account --output text)
export S3_BUCKET_NAME="genai-models-${AWS_ACCOUNT_ID}"
echo "AWS Account ID: $AWS_ACCOUNT_ID"
echo "AWS Region: $AWS_REGION"
echo "S3 Bucket: $S3_BUCKET_NAME"

S3에 저장된 모델 파일 확인
다음 명령어를 통해 S3 버킷 안에 저장된 모델 파일들을 확인할 수 있습니다.
aws s3 ls \
s3://${S3_BUCKET_NAME}/Ministral-3-8B-Instruct-2512/ \
--recursive

즉 모델에 필요한 모든 파일이 S3에 저장되어 있는 상태입니다.
주요 모델 파일 설명
consolidated.safetensors
LLM의 실제 가중치(weight)가 저장된 핵심 파일입니다.
- 모델 파라미터 저장
- GPU 메모리 로딩 대상
- SafeTensors 포맷 사용
- 보안성과 로딩 속도 향상
SafeTensors는 Python pickle 기반 포맷보다 더 안전하고 빠른 최신 모델 저장 형식입니다.
params.json
모델 아키텍처 정보를 저장하는 설정 파일입니다.
- 레이어 수
- Attention Head 수
- Hidden Size
- Hyperparameter 정보
tokenizer.json
사용자 입력 텍스트를 토큰으로 변환하는 파일입니다.
- Vocabulary 정보
- 텍스트 분리 규칙
- 토큰 매핑 정보
Tokenizer는 사람이 읽는 문장을 모델이 이해할 수 있는 숫자 토큰으로 변환하는 역할
왜 SafeTensors를 사용할까?
| 포맷 | 특징 | 사용 환경 |
|---|---|---|
| SafeTensors | 보안성 우수 빠른 로딩 Zero-Copy 지원 |
프로덕션 환경 대규모 LLM 배포 |
| PyTorch (.pt) | 연구 환경 중심 유연성 높음 |
실험/연구 환경 |
| TensorFlow SavedModel | TensorFlow 표준 형식 | TensorFlow 프로덕션 |
Run:AI Streamer란?
이번 실습의 핵심 기술 중 하나는 Run:AI Streamer입니다.
기존 방식에서는:
- S3 → 로컬 디스크 전체 다운로드
- 전체 모델 저장
- GPU 메모리 로딩
과정이 필요했습니다.
하지만 Run:AI Streamer는:
- S3에서 직접 스트리밍
- 필요한 데이터 우선 로딩
- 병렬 다운로드
- 빠른 추론 시작
을 제공합니다.
Run:AI Streamer 장점
- 로컬 스토리지 불필요
- 빠른 모델 시작
- 스토리지 비용 절감
- 빠른 Auto Scaling
- 병렬 모델 로딩 지원
vLLM 배포 매니페스트 다운로드
mkdir -p manifests/200-inference
curl -o manifests/200-inference/vllm-s3-deployment.yml \
https://raw.githubusercontent.com/aws-samples/sample-genai-on-eks/refs/tags/v2.3.1/manifests/100-vllm/vllm-deployment.yml
이 YAML에는:
- vLLM Deployment
- GPU 설정
- S3 모델 경로
- Run:AI Streamer 설정
- Inference API 설정
이 포함되어 있습니다.
S3 버킷 변수 치환
envsubst < manifests/200-inference/vllm-s3-deployment.yml \
> /tmp/vllm-temp.yml && \
mv /tmp/vllm-temp.yml \
manifests/200-inference/vllm-s3-deployment.yml
envsubst는 YAML 내부의:
${S3_BUCKET_NAME}
변수를 실제 값으로 치환합니다.
모델 경로 확인
cat manifests/200-inference/vllm-s3-deployment.yml \
| grep -A 2 "model=s3://"
예시:
--model=s3://genai-models-123456789012/Ministral-3-8B-Instruct-2512/
vLLM 배포 적용
kubectl apply -f manifests/200-inference/vllm-s3-deployment.yml
배포가 시작되면:
- GPU Pod 생성 요청
- EKS Auto Mode GPU Node 생성
- SOCI 기반 이미지 다운로드
- S3 모델 스트리밍
- GPU 메모리 로딩
- vLLM API 서버 시작
과정이 자동 수행됩니다.
GPU 노드 자동 생성 확인
kubectl wait node \
--for=condition=Ready \
-l karpenter.sh/nodepool=gpu

GPU 노드가 존재하지 않으면 EKS Auto Mode가 자동으로 GPU 노드를 생성합니다.
GPU 노드는 필요할 때만 생성되므로 GPU 비용 최적화에 매우 유리합니다.
배포 상태 모니터링
Pod 상태 확인
kubectl wait pods \
--for=jsonpath='{.status.phase}'=Running \
-l model=mistral \
--timeout=300s
로그 확인
kubectl logs -l model=mistral -f

핵심 vLLM 설정값 설명
| 옵션 | 설명 |
|---|---|
| --model | S3 모델 경로 |
| --load-format=runai_streamer | S3 스트리밍 로딩 활성화 |
| --gpu_memory_utilization=0.90 | GPU 메모리 90% 사용 |
| --max-model-len=2048 | 최대 입력 토큰 길이 |
| --tensor-parallel-size=1 | GPU 병렬 처리 수 |
| --max-num-seqs=256 | 동시 처리 최대 요청 수 |
| --enable-auto-tool-choice | 자동 Tool Calling 활성화 |
vLLM 모델과 직접 상호작용하기
이제 이전 단계에서 배포한 Ministral-3-8B-Instruct-2512 모델과 실제로 통신해보겠습니다.
이번 단계에서는:
- 포트 포워딩 설정
- curl 기반 API 테스트
- Open WebUI 배포
- 브라우저 기반 LLM 채팅 테스트
를 진행합니다.
즉, GPU 위에서 실행 중인 vLLM 모델을 실제 서비스처럼 직접 호출해보는 단계입니다.
포트 포워딩 설정
현재 vLLM 서비스는 Kubernetes 내부 네트워크에서 동작하고 있습니다.
따라서 로컬 PC에서 접근하려면 포트 포워딩(port-forward)이 필요합니다.
kubectl port-forward svc/vllm-serve-svc 8000:8000

무슨 의미일까?
| 포트 | 설명 |
|---|---|
| Local 8000 | 내 PC에서 접근하는 포트 |
| Service 8000 | Kubernetes 내부 vLLM 서비스 포트 |
localhost:8000
↓
Kubernetes Service
↓
vLLM Pod
↓
GPU LLM 모델
포트 포워딩이 동작 중인 터미널은 계속 열어둔 상태로 유지해야 합니다.
curl로 모델 테스트하기
이제 OpenAI API 형식으로 LLM에게 직접 요청을 보내보겠습니다.
환경 변수 설정
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity \
--query Account --output text)
export S3_BUCKET_NAME="genai-models-${AWS_ACCOUNT_ID}"
Completion 요청 보내기
curl -s http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d "{
\"model\": \"s3://${S3_BUCKET_NAME}/Ministral-3-8B-Instruct-2512/\",
\"prompt\": \"San Francisco is a city that has\",
\"max_tokens\": 7,
\"temperature\": 0
}" | jq
요청 구성 설명
| 옵션 | 설명 |
|---|---|
| model | 사용할 모델 경로 |
| prompt | 모델에게 전달할 입력 문장 |
| max_tokens | 최대 생성 토큰 수 |
| temperature | 생성 다양성 조절 |

즉 모델이 문장을 이어서 생성합니다.
San Francisco is a city that has
Open WebUI란?
이제 CLI 환경이 아닌 웹 브라우저 기반 UI 환경으로 접속해보겠습니다.
Open WebUI는:
- LLM 채팅 인터페이스 제공
- OpenAI API 호환
- ChatGPT 스타일 UI 제공
- 자체 호스팅 가능
- 다양한 모델 연결 가능
합니다.
쉽게 말하면 내 Kubernetes 환경에 직접 구축하는 ChatGPT 스타일 UI라고 이해하면 됩니다.
Open WebUI 매니페스트 다운로드
curl -o manifests/200-inference/openwebui.yml \
https://raw.githubusercontent.com/aws-samples/sample-genai-on-eks/refs/tags/v2.3.1/manifests/200-ray/openwebui.yml
cat manifests/200-inference/openwebui.yml
YAML 내부에는 아래 오브젝트들이 포함되어 있습니다.
- Deployment
- Service
- Ingress(ALB)
- LoadBalancer 설정
보안 설정 (권장)
기본 설정은:
0.0.0.0/0
즉 모든 IP 접근 허용 상태입니다.
실제 운영 환경에서는 본인 IP만 허용하는 것이 안전합니다.
내 공인 IP 확인
https://checkip.amazonaws.com/
Ingress 수정
alb.ingress.kubernetes.io/inbound-cidrs: YOUR-IP/32
예시:
alb.ingress.kubernetes.io/inbound-cidrs: 123.45.67.89/32
Open WebUI 배포
kubectl apply -f manifests/200-inference/openwebui.yml
배포가 완료되면:
- Open WebUI Pod 생성
- Service 생성
- AWS ALB 생성
- Public Endpoint 연결
이 자동 수행됩니다.
Pod 상태 확인
kubectl get pods -l app=open-webui -w

Open WebUI URL 가져오기
export OPENWEBUI_URL=$(kubectl get ingress open-webui-ingress \
-o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
aws elbv2 wait load-balancer-available \
--load-balancer-arns \
$(aws elbv2 describe-load-balancers \
--query 'LoadBalancers[?DNSName==`'"$OPENWEBUI_URL"'`].LoadBalancerArn' \
--output text)
echo "Open WebUI URL: http://${OPENWEBUI_URL}"

로드밸런서 생성 시간
- 일반적으로 2~3분 소요
- AWS ALB 자동 생성
- Health Check 완료 후 활성화
브라우저에서 모델 사용하기
이제 브라우저에서 Open WebUI URL로 접속하면 ChatGPT와 유사한 형태의 UI가 나타납니다.
여기서:
- 질문 입력
- 대화 테스트
- LLM 응답 확인
- Prompt 실험
- 추론 속도 확인
등을 자유롭게 수행할 수 있습니다.
즉, 지금까지 구축한 EKS + GPU + vLLM 환경이 실제 ChatGPT 스타일 서비스 형태로 동작하게 됩니다.

LLM 추론을 위한 관찰 가능성(Observability) 구성
생성형 AI 및 LLM 환경에서는 단순히 모델을 실행하는 것만큼이나 모니터링(Observability)이 매우 중요합니다.
특히 GPU 기반 추론 환경에서는:
- GPU 사용률
- 메모리 사용량
- Pod 상태
- Node 상태
- 추론 지연 시간(Latency)
- 전력 사용량
- 온도 및 스로틀링
등을 지속적으로 확인해야 안정적인 운영이 가능합니다.
EKS Auto Mode 환경에서 LLM 추론 워크로드를 모니터링하기 위한 Observability Stack 구성을 살펴보겠습니다.
모니터링 스택 구성 요소
워크샵 환경에서는 monitoring 네임스페이스에 다양한 모니터링 구성 요소가 배포되어 있습니다.
전체 모니터링 Pod 확인
kubectl get pods -n monitoring
이 명령을 통해 현재 실행 중인:
- Prometheus
- Grafana
- Node Exporter
- Kube State Metrics
- AlertManager
등의 상태를 확인할 수 있습니다.

Kube Prometheus Stack 확인
Kube Prometheus Stack은 Kubernetes 환경에서 가장 많이 사용되는 오픈소스 기반 모니터링 스택입니다.
Prometheus Pod 확인
kubectl get pods \
-l "app.kubernetes.io/name=prometheus" \
-n monitoring

Node Exporter 확인
kubectl get pods \
-l "app.kubernetes.io/name=prometheus-node-exporter" \
-n monitoring

Kube State Metrics 확인
kubectl get pods \
-l "app.kubernetes.io/name=kube-state-metrics" \
-n monitoring

각 구성 요소 역할
| 구성 요소 | 역할 |
|---|---|
| Prometheus | 메트릭 수집 및 저장 |
| Node Exporter | 노드 CPU/Memory/Disk 메트릭 수집 |
| Kube State Metrics | Kubernetes 객체 상태 메트릭 생성 |
| Grafana | 메트릭 시각화 |
| AlertManager | 이상 상황 알림 처리 |
즉, Prometheus가 데이터를 수집하고 Grafana가 이를 시각화하는 구조입니다.
Amazon Managed Prometheus(AMP) 통합
이 워크샵에서는 AWS 관리형 Prometheus 서비스인 Amazon Managed Prometheus(AMP)를 사용합니다.
AMP 아키텍처
Prometheus Agent
↓
Remote Write
↓
Amazon Managed Prometheus
↓
Grafana Visualization
주요 특징
- 고가용성(HA) 자동 제공
- 자동 스케일링 지원
- 장기 메트릭 저장 가능
- AWS IAM 기반 인증 제공
- Prometheus 직접 운영 불필요
AMP 사용 장점
| 항목 | 장점 |
|---|---|
| 운영 부담 감소 | 스토리지/백업/HA 관리 불필요 |
| 확장성 | 대규모 메트릭 처리 가능 |
| 보안 | IAM 인증 기반 접근 제어 |
| 비용 효율성 | 수집된 메트릭 기준 과금 |
Grafana 스택 확인
Grafana는 수집된 메트릭을 대시보드 형태로 시각화하는 역할을 수행합니다.
Grafana Pod 확인
kubectl get pods \
-l "app.kubernetes.io/name=grafana" \
-n monitoring

Grafana Operator 확인
kubectl get pods \
-l "app.kubernetes.io/name=grafana-operator" \
-n monitoring

Grafana 설정 확인
kubectl get Grafana external-grafana \
-n monitoring \
-o yaml

Grafana는 이미:
- Amazon Managed Prometheus 연결
- IAM 인증 구성
- 기본 Dashboard 구성
이 완료된 상태입니다.
NVIDIA DCGM Exporter란?
GPU 기반 LLM 환경에서는 GPU 메트릭 수집이 매우 중요합니다.
이를 위해 NVIDIA에서는 DCGM(Data Center GPU Manager) Exporter를 제공합니다.
수집 가능한 GPU 메트릭
- GPU 사용률(Utilization)
- GPU 메모리 사용량
- GPU 온도
- 전력 소비량
- GPU 클럭 속도
- GPU 오류 상태
LLM 추론 서버에서는 GPU 상태 모니터링이 매우 중요하기 때문에 DCGM Exporter는 사실상 필수 구성 요소에 가깝습니다.
DCGM Exporter values.yaml 생성
먼저 Helm 설치를 위한 values.yaml 파일을 생성합니다.
mkdir -p manifests/200-inference
cat << EOF > manifests/200-inference/values.yaml
serviceMonitor:
enabled: true
additionalLabels:
release: kube-prometheus-stack
interval: 30s
honorLabels: true
service:
enable: true
type: ClusterIP
port: 9400
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9400"
nodeSelector:
karpenter.sh/nodepool: gpu
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9400"
podLabels:
app.kubernetes.io/name: "dcgm-exporter"
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
extraEnv:
- name: "DCGM_EXPORTER_LISTEN"
value: ":9400"
- name: "DCGM_EXPORTER_KUBERNETES"
value: "true"
EOF
핵심 설정 설명
| 설정 | 설명 |
|---|---|
| nodeSelector | GPU 노드에서만 실행 |
| tolerations | GPU taint 허용 |
| ServiceMonitor | Prometheus 자동 수집 |
| resources | OOM 방지 리소스 제한 |
DCGM Exporter 설치
# NVIDIA Helm Repository 추가
helm repo add gpu-helm-charts \
https://nvidia.github.io/dcgm-exporter/helm-charts
helm repo update
# DCGM Exporter 설치
helm install dcgm-exporter \
gpu-helm-charts/dcgm-exporter \
-n monitoring \
-f manifests/200-inference/values.yaml
# Pod 실행 대기
kubectl wait pods \
--for=jsonpath='{.status.phase}'=Running \
-l "app.kubernetes.io/name=dcgm-exporter" \
-n monitoring \
--timeout=300s
설치가 완료되면:
- GPU 메트릭 수집 시작
- Prometheus 자동 스크래핑
- Grafana 시각화 가능
상태가 됩니다.
GPU 메트릭 확인하기
이제 실제 GPU 메트릭이 수집되는지 확인해보겠습니다.
DCGM Exporter Pod 이름 조회
NAME=$(kubectl get pods \
-l "app.kubernetes.io/name=dcgm-exporter" \
-n monitoring \
-o "jsonpath={ .items[0].metadata.name}")
Port Forward 설정
kubectl port-forward \
-n monitoring \
$NAME 9400:9400
메트릭 조회
curl -sL http://127.0.0.1:9400/metrics

정상 동작 시:
- GPU Utilization
- GPU Memory
- GPU Temperature
- Power Draw
등의 Prometheus 메트릭이 출력됩니다.
Grafana - 대시보드 구성
mkdir -p manifests/300-observability
cat <<EOF > manifests/300-observability/grafana-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana-ingress
namespace: monitoring
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/healthcheck-path: /api/health
alb.ingress.kubernetes.io/healthcheck-interval-seconds: '10'
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: '9'
alb.ingress.kubernetes.io/healthy-threshold-count: '2'
alb.ingress.kubernetes.io/unhealthy-threshold-count: '10'
alb.ingress.kubernetes.io/success-codes: '200-302'
alb.ingress.kubernetes.io/load-balancer-name: grafana-ingress
alb.ingress.kubernetes.io/inbound-cidrs: 0.0.0.0/0
labels:
app: grafana-ingress
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kube-prometheus-stack-grafana
port:
number: 3000
EOF
kubectl apply -f manifests/300-observability/grafana-ingress.yaml
export GRAFANA_URL=$(kubectl get ingress/grafana-ingress -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
aws elbv2 wait load-balancer-available --load-balancer-arns $(aws elbv2 describe-load-balancers --query 'LoadBalancers[?DNSName==`'"$GRAFANA_URL"'`].LoadBalancerArn' --output text)
echo "Grafana is ready and available at: http://${GRAFANA_URL}"
# Get Grafana credentials
export GRAFANA_PASSWORD=$(kubectl get secret -n monitoring kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 --decode)
echo -e "\nGrafana Credentials:"
echo " Username: admin"
echo " Password: ${GRAFANA_PASSWORD}"
echo "http://${GRAFANA_URL}/connections/datasources"





vLLM Service Monitoring 구성
cat << EOF | kubectl apply -f -
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: mistral-monitor
namespace: monitoring
labels:
release: kube-prometheus-stack # Important for Prometheus operator discovery
spec:
namespaceSelector:
matchNames:
- default
selector:
matchLabels:
model: mistral
endpoints:
- port: http
interval: 30s
path: /metrics
EOF
kubectl get grafanadashboard vllm-dashboard -n monitoring
# Grafana URL 확인
export GRAFANA_URL=$(kubectl get ingress/grafana-ingress -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
echo "Your Grafana URL is: $GRAFANA_URL"
# 먼저, 올바른 모델 이름 가져오기
export MODEL_NAME=$(curl -s http://localhost:8000/v1/models | jq -r '.data[0].id')
# 메트릭을 채우기 위해 여러 요청 보내기
for i in {1..10}; do
curl -s http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_NAME\",
\"prompt\": \"Tell me about artificial intelligence in\",
\"max_tokens\": 50,
\"temperature\": 0.7
}" | jq '.choices[0].text' && echo "Request $i completed"
sleep 2
done



# vLLM 실습 환경 정리
# Delete vLLM Service Monitor
kubectl delete servicemonitor/mistral-monitor -n monitoring
# Uninstall the DCGM Prometheus Exporter
helm uninstall dcgm-exporter -n monitoring
kubectl delete -f manifests/200-inference/vllm-s3-deployment.yml
kubectl delete -f manifests/200-inference/openwebui.yml
Ray를 활용한 LLM 추론 확장
mkdir -p manifests/400-ray
curl -o manifests/400-ray/vllm-s3-configmap.yml https://raw.githubusercontent.com/aws-samples/sample-genai-on-eks/refs/tags/v2.3.1/manifests/200-ray/vllm-s3-configmap.yaml
cat manifests/400-ray/vllm-s3-configmap.yml
kubectl apply -f manifests/400-ray/vllm-s3-configmap.yml
vLLM Serve ConfigMap 구성 살펴보기
이번 섹션에서는 vllm_serve.py 스크립트를 포함한 ConfigMap 구성을 살펴보겠습니다. 이 ConfigMap은 Kubernetes 환경에서 애플리케이션 코드와 배포 설정을 분리하여 관리할 수 있도록 도와주며, Ray Serve 기반의 분산 LLM 추론 시스템을 구성하는 핵심 역할을 수행합니다.
ConfigMap을 사용하는 이유
Kubernetes에서는 애플리케이션 코드나 설정 파일을 컨테이너 이미지 내부에 직접 넣는 대신, ConfigMap으로 외부 분리하여 관리하는 방식을 자주 사용합니다.
이를 통해 다음과 같은 장점을 얻을 수 있습니다.
- 애플리케이션 코드와 배포 설정 분리
- 코드 수정 시 이미지 재빌드 최소화
- 운영 환경별 설정 관리 용이
- 배포 자동화 및 GitOps 구성에 적합
즉:
“LLM 서빙 로직을 Kubernetes 설정과 함께 유연하게 관리하기 위한 구조”
vLLM Serve 구조 개요
해당 Python 스크립트는 다음 기능들을 포함합니다.
| 구성 요소 | 설명 |
|---|---|
| FastAPI | OpenAI API 호환 REST API 제공 |
| Ray Serve | 분산 추론 서비스 운영 |
| vLLM Engine | 고성능 GPU 기반 LLM 추론 처리 |
| AsyncLLMEngine | 비동기 추론 처리 |
| OpenAIServingChat | OpenAI 형식 응답 처리 |
1. 애플리케이션 설정
스크립트는 먼저 FastAPI 기반 API 서버와 Ray Serve 통합 환경을 초기화합니다.
여기서는 다음 기능들이 함께 설정됩니다.
- FastAPI 웹 서버 구성
- Ray Serve 연동
- vLLM 엔진 초기화
- 로깅(logging) 설정
로깅은 특히 중요합니다.
LLM 추론 환경에서는:
- 모델 로딩 시간
- GPU 사용량
- 요청 처리 상태
- 에러 발생 여부
등을 추적해야 하기 때문에 상세 로그 구성이 필수입니다.
2. VLLMDeployment 클래스
핵심 로직은 VLLMDeployment 클래스에 구현되어 있습니다.
이 클래스는 Ray Serve의 데코레이터를 사용하여 분산 추론 서비스로 동작합니다.
@serve.deployment
@serve.ingress(app)
class VLLMDeployment:
- @serve.deployment → Ray Serve 배포 객체
- @serve.ingress(app) → FastAPI 라우팅 연결
“FastAPI API 서버와 Ray 분산 추론 시스템을 연결하는 핵심 클래스”
모델 초기화
생성자에서는 다음 항목들이 초기화됩니다.
- 모델 경로
- 텐서 병렬 처리 설정
- 최대 시퀀스 길이
- GPU 메모리 사용량
- 토크나이저 설정
또한 모델 성능 최적화를 위해:
- bfloat16 사용
- Chunked Prefill 활성화
- 메모리 최적화
등이 적용됩니다.
3. API 엔드포인트 구성
vLLM 서빙 시스템은 OpenAI API와 호환되는 인터페이스를 제공합니다.
/v1/models
현재 사용 가능한 모델 정보를 반환합니다.
GET /v1/models
OpenAI API와 동일한 형식으로 모델 목록을 확인할 수 있습니다.
/v1/chat/completions
실제 LLM 추론 요청을 처리하는 핵심 엔드포인트입니다.
POST /v1/chat/completions
지원 기능:
- 스트리밍 응답
- 비스트리밍 응답
- OpenAI Chat Completion 호환
- 도구 호출(Function Calling)
즉 기존 OpenAI SDK나 애플리케이션을 거의 수정 없이 사용할 수 있습니다.
4. 모델 서빙 인프라
AsyncEngineArgs 구성
vLLM 엔진은 AsyncEngineArgs를 사용하여 구성됩니다.
여기에는:
- GPU 메모리 사용량
- 배치 크기
- 시퀀스 제한
- Chunked Prefill
- 토크나이저 설정
등이 포함됩니다.
AsyncLLMEngine
실제 모델 추론은 AsyncLLMEngine이 수행합니다.
특징:
- 비동기 처리
- 높은 동시성 지원
- GPU 효율 최적화
- Continuous Batching 지원
이를 통해 GPU 활용률을 극대화할 수 있습니다.
OpenAIServingChat
응답 결과는 OpenAIServingChat을 통해 OpenAI API 형식으로 변환됩니다.
따라서:
- OpenAI SDK
- LangChain
- LlamaIndex
- Open WebUI
등과 자연스럽게 연동할 수 있습니다.
5. 환경 변수 기반 구성
배포 시 다양한 설정을 환경 변수로 제어할 수 있습니다.
| 환경 변수 | 설명 |
|---|---|
| MODEL_ID | 모델 경로 지정 |
| TENSOR_PARALLEL_SIZE | GPU 병렬 처리 크기 |
| MAX_NUM_SEQS | 동시 처리 가능한 최대 시퀀스 수 |
| MAX_MODEL_LEN | 최대 입력 길이 설정 |
Tensor Parallel이 중요한 이유
대형 모델은 하나의 GPU 메모리에 모두 올라가지 않는 경우가 많습니다.
이때 Tensor Parallel을 사용하면:
- 모델을 여러 GPU에 분산
- 병렬 연산 수행
- 추론 속도 향상
- 더 큰 모델 운영 가능
등의 장점을 얻을 수 있습니다.
전체 구조 정리
최종적으로 구성되는 구조는 다음과 같습니다.
Client
↓
FastAPI
↓
Ray Serve
↓
vLLM Engine
↓
GPU Inference
“Ray Serve 기반 분산 시스템 위에서 vLLM이 GPU 추론을 수행하고, FastAPI가 OpenAI 호환 API 형태로 제공하는 구조”
KubeRay Operator 및 RayService 배포하기
이번 섹션에서는 Kubernetes 환경에서 Ray 기반 LLM 추론 시스템을 운영하기 위한 KubeRay Operator와 RayService를 배포해보겠습니다.
기본적으로 Ray는 Kubernetes의 Deployment와 Service만으로도 실행할 수 있지만, 프로덕션 환경에서는 KubeRay Operator를 사용하는 것이 훨씬 효율적입니다.
왜 KubeRay Operator를 사용하는가?
KubeRay Operator는 Ray 클러스터를 Kubernetes 방식으로 쉽게 운영할 수 있도록 도와주는 전용 Operator입니다.
특히 생성형 AI 및 LLM 환경에서는 다음과 같은 장점이 매우 중요합니다.
| 기능 | 설명 |
|---|---|
| 무중단 업그레이드 | 모델 업데이트 시 서비스 중단 최소화 |
| 고가용성 | 외부 Redis 기반 Global Control Service 지원 |
| 자동 수명주기 관리 | Ray 클러스터 생성/삭제 자동화 |
| Kubernetes 네이티브 통합 | CRD(Custom Resource Definition) 기반 관리 |
| 자동 스케일링 | 워크로드 기반 확장 지원 |
| 모니터링 단순화 | 운영 상태 및 메트릭 확인 용이 |
즉:
“Kubernetes 환경에서 Ray 기반 AI 서비스를 안정적으로 운영하기 위한 관리 플랫폼”
KubeRay Operator 설치하기
Helm을 사용하여 KubeRay Operator를 설치합니다.
# Ray Helm 저장소 추가
helm repo add kuberay https://ray-project.github.io/kuberay-helm/
# KubeRay Operator 설치
helm install kuberay-operator kuberay/kuberay-operator --version 1.1.0
# Operator 상태 확인
kubectl wait pods --for=jsonpath='{.status.phase}'=Running \
-l app.kubernetes.io/name=kuberay-operator \
--timeout=300s

S3 Mount Point CSI 드라이버 기반 스토리지 구성
이번 실습에서는 대형 모델 파일을 효율적으로 제공하기 위해 S3 Mount Point CSI Driver를 사용합니다.
이를 통해 Kubernetes Pod가 S3 버킷의 모델 파일을 마치 로컬 파일 시스템처럼 접근할 수 있습니다.
스토리지 리소스 확인
# Persistent Volume 확인
kubectl get pv mistral-model-pv -o wide
# Persistent Volume Claim 확인
kubectl get pvc mistral-model-pvc -o wide
# PV 상세 정보 확인
kubectl describe pv mistral-model-pv

S3 CSI 드라이버 핵심 구성
| 항목 | 설명 |
|---|---|
| CSI Driver | s3.csi.aws.com |
| S3 Bucket | genai-models-xxxxxx |
| 모델 경로 | Ministral-3-8B-Instruct-2512/ |
| Access Mode | ROX(ReadOnlyMany) |
| 용량 | 20Gi |
왜 S3 CSI 드라이버를 사용하는가?
LLM 모델 파일은 보통 수 GB~수십 GB 크기입니다.
기존 방식처럼:
- 모델 다운로드
- 로컬 디스크 저장
- 노드별 복사
를 수행하면 매우 비효율적입니다.
S3 CSI Driver를 사용하면:
- S3 모델 직접 접근
- 로컬 저장소 최소화
- 빠른 스케일링
- 스토리지 비용 절감
등의 장점을 얻을 수 있습니다.
RayService 구성 배포하기
이제 RayService YAML을 다운로드하고 배포합니다.
# RayService YAML 다운로드
curl -o manifests/400-ray/ray-vllm-s3-service.yml \
https://raw.githubusercontent.com/aws-samples/sample-genai-on-eks/refs/tags/v2.3.2/manifests/200-ray/vllm-s3-service.yaml
# 배포 적용
kubectl apply -f manifests/400-ray/ray-vllm-s3-service.yml
배포 상태 모니터링
RayService 배포 후에는 Pod 상태와 노드 생성 과정을 모니터링합니다.
kubectl get pods -w

RayService 구조 이해하기
RayService는 크게:
- Head Node
- Worker Node
로 구성됩니다.
Head Node 역할
Head Node는 Ray 클러스터의 중앙 관리 역할을 수행합니다.
주요 기능:
- 스케줄링
- Worker 관리
- Task 분배
- 클러스터 상태 관리
Head Node 구성 특징
| 항목 | 설명 |
|---|---|
| CPU | 2 Core |
| Memory | 12GB |
| NodePool | 기본 범용 노드풀 |
| 역할 | 클러스터 관리 전용 |
워크숍 환경 특징
워크숍에서는 비용 절감을 위해:
- Head Node
- Worker Node
를 동일 GPU 인스턴스에서 실행합니다.
하지만 실제 운영 환경에서는:
- Head Node → 범용 CPU 인스턴스
- Worker Node → GPU 전용 인스턴스
로 분리하는 것이 권장됩니다.
Worker Node 역할
Worker Node는 실제 AI 추론 작업을 수행합니다.
즉:
- vLLM 실행
- GPU 추론 처리
- 토큰 생성
- 배치 처리
등이 Worker Node에서 수행됩니다.
Worker Node 구성
| 항목 | 설명 |
|---|---|
| GPU | 1개 |
| Memory | 28GB |
| 인스턴스 | g6e.2xlarge |
| 스케일링 | 1개 고정(min/max 1) |
공통 구성 요소
AWS Deep Learning Container(DLC)
Head와 Worker 모두 AWS Deep Learning Container 이미지를 사용합니다.
해당 이미지에는 다음이 포함됩니다.
- Ray
- vLLM
- CUDA
- GPU 드라이버
- Python 런타임
- AWS SDK
즉:
“LLM 추론에 필요한 환경이 이미 최적화되어 포함된 컨테이너”
Persistent Volume Mount
Head와 Worker 모두:
/models
경로에 동일한 PVC를 마운트합니다.
이를 통해 S3에 저장된 모델 파일을 직접 사용할 수 있습니다.
전체 아키텍처 흐름
S3 Bucket
↓
S3 CSI Driver
↓
Persistent Volume
↓
Ray Worker Node
↓
vLLM GPU Inference
즉:
“S3 모델 저장소를 Kubernetes Volume처럼 연결하여 Ray 기반 GPU 추론 시스템이 직접 모델을 읽는 구조”
Ray 접속 확인




export OPENWEBUI_URL=$(kubectl get ingress open-webui-ingress -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
aws elbv2 wait load-balancer-available --load-balancer-arns $(aws elbv2 describe-load-balancers --query 'LoadBalancers[?DNSName==`'"$OPENWEBUI_URL"'`].LoadBalancerArn' --output text)
echo "Open WebUI is ready and available at: http://${OPENWEBUI_URL}"
Ray PodMonitor 생성
cat << EOF | kubectl apply -f -
---
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
labels:
release: kube-prometheus-stack
name: ray-head-monitor
namespace: monitoring
spec:
jobLabel: ray-head
namespaceSelector:
matchNames:
- default
selector:
matchLabels:
ray.io/node-type: head
podMetricsEndpoints:
- port: metrics
relabelings:
- action: replace
sourceLabels:
- __meta_kubernetes_pod_label_ray_io_cluster
targetLabel: ray_io_cluster
- port: as-metrics # autoscaler 메트릭
relabelings:
- action: replace
sourceLabels:
- __meta_kubernetes_pod_label_ray_io_cluster
targetLabel: ray_io_cluster
- port: dash-metrics # dashboard 메트릭
relabelings:
- action: replace
sourceLabels:
- __meta_kubernetes_pod_label_ray_io_cluster
targetLabel: ray_io_cluster
---
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: ray-workers-monitor
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
jobLabel: ray-workers
namespaceSelector:
matchNames:
- default
selector:
matchLabels:
ray.io/node-type: worker
podMetricsEndpoints:
- port: metrics
relabelings:
- sourceLabels: [__meta_kubernetes_pod_label_ray_io_cluster]
targetLabel: ray_io_cluster
EOF
# Grafana 대시보드 확인
kubectl get grafanadashboards -n monitoring | grep ray


Amazon EKS에서의 에이전트 AI
이번 섹션에서는 Strands Agents SDK를 사용하여 Amazon EKS 환경에서 Agent AI를 어떻게 구축하고 운영하는지 살펴보겠습니다.
기존 LLM 애플리케이션은 단순히 질문에 대한 답변만 생성하는 형태였다면, 에이전트 기반 AI는 다음과 같은 추가 기능을 수행할 수 있습니다.
- 이전 대화 기억
- 상황 판단 및 의사결정
- 외부 도구 호출
- API 연동
- 멀티턴 대화 처리
- 실시간 정보 조회
즉 단순한 챗봇이 아니라, "스스로 판단하고 행동하는 AI 시스템"이라고 볼 수 있습니다.
왜 Amazon EKS에서 Agent AI를 실행할까?
에이전트 시스템은 일반적인 웹 애플리케이션보다 훨씬 복잡한 실행 구조를 가집니다.
예를 들어:
- LLM 호출
- 외부 API 연동
- 도구 실행
- 상태 유지
- 멀티 컨테이너 운영
- GPU 기반 추론 처리
등이 동시에 이루어질 수 있습니다.
Amazon EKS는 이러한 복잡한 AI 워크로드를 안정적으로 운영하기 위한 Kubernetes 기반 관리형 플랫폼입니다.
- 자동 확장(Auto Scaling)
- 고가용성 구성
- Pod 자동 복구
- GPU 워크로드 지원
- 보안 네트워크 구성
- 운영 자동화
즉:
"에이전트 로직은 AI가 담당하고, 인프라 운영은 EKS가 담당한다"
모듈 구성
이번 모듈은 크게 두 단계로 구성됩니다.
1. Strands Agents 배포
- Strands SDK 기반 에이전트 생성
- LLM과 Tool 연결
- 시간/날씨 API 연동
- 에이전트 컨테이너 배포
- Amazon EKS 환경에서 실행
2. 에이전트 테스트
- 시간 조회 요청
- 날씨 조회 요청
- 지역 기반 질문 처리
- 멀티턴 대화 테스트
- 도구 호출 동작 확인
Agent AI 실행 흐름 이해하기
에이전트 기반 AI는 일반적인 LLM과 다르게 추론 → 도구 호출 → 결과 재해석 과정을 반복할 수 있습니다.
- 사용자가 질문 입력
- LLM이 문제 해결 방법 판단
- 필요 시 외부 Tool 호출
- 추가 데이터 수집
- LLM이 최종 답변 생성
- 사용자에게 응답 반환
에이전트 시스템 핵심 구성요소
1. 사용자 요청 (Entry Point)
모든 작업은 사용자의 질문으로부터 시작됩니다.
서울 현재 날씨 알려줘
에이전트는 이 요청을 단순 텍스트 생성이 아니라, "실제 정보 조회 작업"으로 해석합니다.
2. 추론 루프 (Reasoning Loop)
LLM은 먼저 다음을 판단합니다.
- 어떤 정보가 필요한가?
- 외부 도구 호출이 필요한가?
- 현재 정보만으로 답변 가능한가?
필요하다면 Tool 사용 단계로 이동합니다.
3. Tool 사용
에이전트는 등록된 도구를 호출하여 추가 데이터를 가져옵니다.
예시:
- 날씨 API
- 시간 API
- DB 조회
- 내부 서비스 호출
- 검색 시스템
예를 들어:
서울 날씨 조회 API 호출
같은 작업이 자동으로 수행됩니다.
4. LLM 재추론
도구 결과를 받은 후, LLM은 다시 한 번 응답을 생성합니다.
즉:
"도구 결과를 기반으로 자연어 답변 생성"
과정을 수행합니다.
5. 최종 응답 반환
최종적으로 사용자는 다음과 같은 자연스러운 응답을 받게 됩니다.
Agent AI의 핵심 특징
| 기능 | 설명 |
|---|---|
| Memory | 이전 대화 기억 |
| Tool Calling | 외부 기능 실행 |
| Reasoning | 상황 기반 판단 |
| Multi-turn | 연속 대화 지원 |
| Scalability | EKS 기반 확장 가능 |
```html id="82k731"
Strands Agents SDK란?
Strands Agents SDK는 AI Agent 개발을 단순화하기 위해 만들어진 모델 중심(Model-Centric) 에이전트 프레임워크입니다.
기존 에이전트 프레임워크들은 복잡한 워크플로우 구성과 상태 관리 로직이 필요했지만, Strands는 최신 LLM이 이미 가지고 있는:
- 추론(Reasoning)
- 계획(Planning)
- 도구 호출(Tool Calling)
- 반영(Reflection)
- 멀티턴 대화
능력을 최대한 활용하여 훨씬 단순한 방식으로 에이전트를 구축할 수 있도록 설계되었습니다.
왜 이름이 Strands일까?
Strands는 DNA의 두 가닥(Strands)처럼 에이전트의 핵심 요소인:
- 모델(Model)
- 도구(Tools)
를 연결한다는 의미를 가지고 있습니다.
"LLM의 지능 + 외부 기능 실행"
을 자연스럽게 결합해주는 프레임워크라고 이해할 수 있습니다.
Strands의 주요 특징
| 기능 | 설명 |
|---|---|
| 경량 구조 | 복잡한 워크플로우 없이 간단한 에이전트 구성 가능 |
| 모델 독립성 | 특정 LLM에 종속되지 않음 |
| 멀티 모델 지원 | Bedrock, OpenAI, Anthropic, Ollama 등 지원 |
| MCP 통합 | 외부 서비스 및 컨텍스트 유지 가능 |
| 다중 에이전트 | 여러 Agent 협업 가능 |
| 프로덕션 지원 | 실제 운영 환경에 적합 |
LiteLLM 기반 멀티 모델 지원
Strands는 LiteLLM을 통해 다양한 모델 제공자를 지원합니다.
- Amazon Bedrock
- OpenAI
- Anthropic
- Llama
- Ollama
또한 개발자가 직접 사용자 정의 모델 공급자를 추가할 수도 있습니다.
"에이전트 로직은 그대로 유지하면서, 사용하는 LLM만 자유롭게 교체 가능"한 구조를 제공합니다.
Strands의 핵심 구성 요소
Strands 에이전트는 기본적으로 아래 3가지 요소로 구성됩니다.
1. Model
자연어를 이해하고 추론하는 LLM 엔진입니다.
예:
GPT
Claude
Mistral
Llama
Bedrock 모델 등
2. Tools
에이전트가 외부 작업을 수행하기 위해 호출하는 기능입니다.
예를 들어:
- 날씨 API 호출
- 시간 조회
- DB 검색
- 웹 검색
- AWS 서비스 호출
3. Prompt
에이전트의 역할과 행동을 정의하는 자연어 지침입니다.
너는 사용자 질문에 답변하는 AI 비서다.
필요 시 날씨 도구를 사용해라.
Strands Agent Loop 이해하기
Strands는 최신 LLM의 기본 추론 능력을 적극 활용합니다.
기본 흐름은 다음과 같습니다.
- 사용자 질문 입력
- 모델이 문제 해결 방법 판단
- 필요 시 Tool 호출
- Tool 결과 수집
- 최종 응답 생성
- 사용자에게 반환
이 과정에서:
- Tool 호출
- 추론
- 컨텍스트 유지
- 반복적 사고
가 자동으로 처리됩니다.
모델 배포 상태 확인
이번 실습에서는 RayServe + vLLM 기반 추론 서버를 사용합니다.
먼저 RayService 상태를 확인합니다.
kubectl get RayService
정상적으로 실행 중이라면 다음과 비슷한 결과가 출력됩니다.
NAME SERVICE STATUS NUM SERVE ENDPOINTS
vllm Running 2
만약 Running 상태가 아니라면, 이전에 진행한:
- RayService 배포
- vLLM 구성
- GPU Node 구성
단계를 먼저 완료해야 합니다.
자동 Tool Calling을 위한 주요 설정
에이전트가 자동으로 도구를 사용할 수 있도록, vLLM 실행 시 다음 옵션이 포함되어 있습니다.
1. 자동 Tool 선택 활성화
--enable-auto-tool-choice
모델이 상황에 따라:
- 어떤 Tool이 필요한지
- 언제 Tool을 호출할지
스스로 판단할 수 있도록 활성화합니다.
2. Tool Parser 설정
--tool-call-parser=mistral
Mistral 모델 형식에 맞는 Tool Calling 파서를 사용합니다.
이를 통해 모델이 함수 호출 생성, Tool 인자 구성, 응답 포맷 처리를 정상적으로 수행할 수 있습니다.
Strands Agents SDK를 활용한 EKS 에이전트 구축 및 배포
이번 실습에서는 Strands Agents SDK를 사용하여 Amazon EKS 환경에 AI 에이전트를 구축하고 배포하는 방법을 학습합니다. 에이전트는 시간 조회 및 날씨 확인 기능을 수행할 수 있으며, vLLM 기반 Mistral 모델과 연동되어 동작합니다.
1. Strands 에이전트 프로젝트 생성
먼저 Strands 에이전트 코드를 저장할 디렉토리를 생성합니다.
# 에이전트 코드를 위한 디렉토리 생성
mkdir -p manifests/600-strands-agent
워크샵에서 제공하는 Strands 에이전트 샘플 코드를 다운로드합니다.
# strands-agent 애플리케이션 다운로드
curl -o manifests/600-strands-agent/strands.zip \
https://ws-assets-prod-iad-r-pdx-f3b3f9f1a7d6a3d0.s3.us-west-2.amazonaws.com/029d6c4e-4775-41c9-85ff-9f5360f32a15/strands.zip
다운로드한 ZIP 파일의 압축을 해제합니다.
# 코드 압축 해제
unzip manifests/600-strands-agent/strands.zip \
-d manifests/600-strands-agent
2. 프로젝트 구조
압축을 해제하면 다음과 같은 주요 파일들이 생성됩니다.
- strands-agent.py : Strands SDK 기반 에이전트 코드
- Dockerfile : 컨테이너 이미지 빌드 설정
- requirements.txt : Python 패키지 의존성 목록
3. 모델 연결 구성
Strands 에이전트는 OpenAI 호환 API 형태로 배포된 vLLM 기반 Mistral 모델과 연결됩니다.
model = OpenAIModel(
client_args={
"api_key": "xxxxxxxxxxxx",
"base_url": os.getenv("MODEL_ENDPOINT")
},
model_id=os.getenv("MODEL_ID"),
params={
"max_tokens": 1000,
"temperature": 0.7,
}
)
주요 설정 항목
| 설정 | 설명 |
|---|---|
| base_url | Kubernetes 내부 vLLM API 엔드포인트 |
| model_id | Mistral 모델 경로 |
| temperature | 응답 창의성 조절 값 |
| max_tokens | 최대 응답 토큰 수 |
4. 도구(Tool) 구현
Strands SDK는 @tool 데코레이터를 사용하여 에이전트 기능을 확장합니다.
시간 조회 도구
@tool
def current_time(location: str) -> str:
"""Get the current time for a location."""
# geopy를 사용하여 좌표 찾기
# TimezoneFinder를 사용하여 시간대 계산
# 현재 시간 반환
날씨 조회 도구
@tool
def current_weather(location: str) -> str:
"""Get the current weather for a location."""
# Open-Meteo API 사용
# 위치를 좌표로 변환
# 현재 날씨 반환
5. FastAPI와 에이전트 통합
FastAPI를 사용하여 HTTP 기반 REST API 형태로 에이전트를 제공합니다.
@app.post("/agent")
async def agent_endpoint(request: QueryRequest):
"""Endpoint that uses time and weather agent"""
agent = Agent(
model=model,
tools=[current_time, current_weather]
)
response = agent(request.query)
return {
"status": "success",
"response": response
}
특징
- 요청마다 새로운 에이전트 인스턴스 생성
- 시간/날씨 도구 자동 선택
- REST API 기반 구조
- 무상태(stateless) 방식 처리
6. Amazon ECR 리포지토리 생성
컨테이너 이미지를 저장할 ECR 리포지토리를 생성합니다.
# ECR 리포지토리 생성
aws ecr create-repository \
--repository-name strands-weather-agent
# ECR URI 가져오기
export ECR_REPO=$(aws ecr describe-repositories \
--repository-names strands-weather-agent \
--query 'repositories[0].repositoryUri' \
--output text)
echo "Your ECR repository URI is: $ECR_REPO"
7. Docker 이미지 빌드 및 푸시
Docker 이미지를 빌드하고 Amazon ECR로 업로드합니다.
# ECR 로그인
aws ecr get-login-password --region $AWS_REGION | \
docker login \
--username AWS \
--password-stdin \
$(echo $ECR_REPO | cut -d'/' -f1)
# Docker 이미지 빌드
docker build -t strands-weather-agent manifests/600-strands-agent
# 태그 지정
docker tag strands-weather-agent:latest $ECR_REPO:latest
# 이미지 푸시
docker push $ECR_REPO:latest
8. Kubernetes 배포 매니페스트 생성
EKS에서 실행할 Deployment 및 Service 매니페스트를 생성합니다.
mkdir -p manifests/600-strands-agent/
cat < manifests/600-strands-agent/vllm-deployment-agents.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: strands-weather-agent
spec:
replicas: 1
selector:
matchLabels:
app: strands-weather-agent
template:
metadata:
labels:
app: strands-weather-agent
spec:
containers:
- name: strands-weather-agent
image: $ECR_REPO:latest
ports:
- containerPort: 8000
env:
- name: MODEL_ENDPOINT
value: "http://vllm-serve-svc:8000/v1"
- name: MODEL_ID
value: "/models/Ministral-3-8B-Instruct-2512"
---
apiVersion: v1
kind: Service
metadata:
name: strands-weather-agent
spec:
selector:
app: strands-weather-agent
ports:
- port: 80
targetPort: 8000
type: ClusterIP
EOF
9. 에이전트 배포
Kubernetes에 에이전트를 배포합니다.
kubectl apply \
-f manifests/600-strands-agent/vllm-deployment-agents.yaml
Pod가 Running 상태가 될 때까지 대기합니다.
kubectl wait pods \
--for=jsonpath='{.status.phase}'=Running \
-l app=strands-weather-agent \
--timeout=300s
10. 전체 아키텍처 흐름
- 사용자가 API 요청 전송
- FastAPI 엔드포인트가 요청 수신
- Strands Agent 생성
- Mistral 모델(vLLM)과 연결
- 필요 시 시간/날씨 Tool 호출
- 최종 응답 생성 후 반환
11. 핵심 장점
| 기능 | 설명 |
|---|---|
| Tool Calling | LLM이 외부 함수 자동 호출 |
| OpenAI 호환 API | 기존 애플리케이션과 쉽게 연동 가능 |
| FastAPI 기반 | 경량 REST API 서버 제공 |
| EKS 배포 | 확장성과 고가용성 확보 |
| vLLM 연동 | GPU 기반 고성능 추론 지원 |



Strands 에이전트 동작 및 테스트 방법
Amazon EKS에 Strands 에이전트를 성공적으로 배포한 후에는 실제로 정상 동작하는지 테스트해야 합니다.
이 섹션에서는 Kubernetes에 배포된 Strands Weather Agent와 상호작용하고, 요청이 내부적으로 어떻게 처리되는지 설명합니다.
1. 포트 포워딩 설정
먼저 Kubernetes 내부 서비스에 로컬 환경에서 접근하기 위해 kubectl port-forward를 사용합니다.
kubectl port-forward svc/strands-weather-agent 8080:80
포트 매핑 구조
| 로컬 포트 | Kubernetes 서비스 | 서비스 포트 |
|---|---|---|
| 8080 | strands-weather-agent | 80 |
이제 브라우저 또는 curl 명령을 통해 http://localhost:8080으로 접근하면 Kubernetes 내부의 에이전트 서비스와 통신할 수 있습니다.
2. 시간 조회 요청 테스트
다음 curl 명령을 사용하면 특정 지역의 현재 시간을 조회할 수 있습니다.
curl -s -X POST http://localhost:8080/agent \
-H "Content-Type: application/json" \
-d '{"query": "달라스의 시간은 어떻게 되나요?"}' \
| jq -r '.response.message.content[0].text'
예상 동작
- LLM이 시간 조회 요청임을 판단
- current_time() Tool 자동 호출
- 달라스 시간대 계산
- 자연어 형태로 응답 생성
3. 날씨 조회 요청 테스트
이번에는 날씨 조회 기능을 테스트합니다.
curl -s -X POST http://localhost:8080/agent \
-H "Content-Type: application/json" \
-d '{"query": "덴버의 날씨는 어떤가요?"}' \
| jq -r '.response.message.content[0].text'
예상 동작
- LLM이 날씨 관련 질문으로 판단
- current_weather() Tool 호출
- Open-Meteo API를 통해 날씨 정보 조회
- 자연스러운 답변 생성
4. 에이전트 내부 요청 흐름
사용자 입장에서는 단일 요청처럼 보이지만, 실제 내부에서는 두 번의 LLM 호출이 발생합니다.
1단계 : 첫 번째 LLM 호출
사용자의 질문을 받은 후, LLM은 어떤 Tool을 사용해야 하는지 판단합니다.
- 시간 조회인지 판단
- 날씨 조회인지 판단
- Tool 호출 구조 생성
이 단계에서는 최종 답변을 생성하지 않습니다.
2단계 : Tool 실행
에이전트가 실제 Tool 함수를 호출하여 외부 데이터를 수집합니다.
- 현재 시간 계산
- 날씨 API 호출
- 위치 좌표 변환
3단계 : 두 번째 LLM 호출
Tool이 반환한 실제 데이터를 기반으로 최종 자연어 응답을 생성합니다.
- 도구 결과 정리
- 대화형 응답 생성
- 최종 사용자 응답 반환
5. 왜 두 번 호출할까?
이 구조는 단순한 텍스트 생성이 아니라 추론 + 행동(Action)을 수행하기 위해 필요합니다.
| 단계 | 역할 |
|---|---|
| 첫 번째 호출 | 무엇을 해야 하는지 판단 |
| Tool 실행 | 실제 데이터 수집 |
| 두 번째 호출 | 수집된 데이터를 기반으로 응답 생성 |
이를 통해 에이전트는 단순한 챗봇이 아니라, 실제 정보를 조회하고 활용하는 Agentic AI 형태로 동작할 수 있습니다.
6. 테스트 종료
테스트가 완료되면 포트 포워딩을 종료해야 합니다.
Ctrl + C
포트 포워딩을 중지하면 로컬 머신과 Kubernetes 서비스 간 연결이 종료됩니다.
7. 전체 아키텍처 흐름
- 사용자가 curl 요청 전송
- FastAPI 에이전트 엔드포인트 수신
- LLM이 Tool 사용 여부 판단
- 시간 또는 날씨 Tool 호출
- 외부 데이터 수집
- LLM이 최종 자연어 응답 생성
- 사용자에게 응답 반환
8. 정리
이번 단계에서는 EKS에 배포된 Strands AI 에이전트와 실제로 상호작용하는 방법을 학습했습니다.
- Kubernetes Service 포트 포워딩
- curl 기반 에이전트 테스트
- 시간 및 날씨 Tool 호출 확인
- LLM의 이중 호출 구조 이해
- Agentic AI 실행 흐름 학습
이러한 구조는 생성형 AI를 넘어 실제 행동(Action)을 수행하는 차세대 AI 에이전트 시스템의 핵심 패턴입니다.
'AWS 4기' 카테고리의 다른 글
| AEWS 4기 7주차(2) - EKS Upgrade(Workshop 실습) (0) | 2026.05.01 |
|---|---|
| AEWS 4기 7주차(1) - EKS Upgrade (0) | 2026.05.01 |
| AEWS 4기 6주차(3) - EKS CI/CD(SaaS Tier 전략, ArgoCD Worflow) (0) | 2026.04.23 |
| AEWS 4기 6주차(2) - EKS CI/CD(Flux CD + Helm Chart) (0) | 2026.04.23 |
| AEWS 4기 6주차(1) - EKS CI/CD(flux cd, tofu-controller) (0) | 2026.04.22 |