본 글은 AEWS 스터디 및 AWS Workshop Amazon EKS에서 고급 Agentic AI 플랫폼 설계 및 배포 실습 내용을 정리한 글입니다.
이번 워크숍의 핵심은 EKS 위에 단순 LLM 하나 띄우는 게 아니라, 모델 서빙 + AI Gateway + 관측성 + Agent 애플리케이션까지 한 번에 구성하는 것이다.
사용자
│
▼
Open WebUI (채팅 UI)
│
▼
LiteLLM Gateway (모델 라우팅)
├─ vLLM on EKS (Qwen 3 8B / Neuron)
└─ Amazon Bedrock (Claude 4.5 Sonnet)
│
▼
Langfuse (LLM Observability)
| 모듈 | 핵심 |
|---|---|
| Module 1 | 모델 호출 - Open WebUI에서 vLLM, Bedrock 모델 사용 |
| Module 2 | 플랫폼 컴포넌트 - LiteLLM Gateway + Langfuse 관측성 |
| Module 3 | Agentic AI 앱 - LangGraph + MCP 서버로 Agent 구축 |
• 워크숍 환경은 Amazon EKS Auto Mode 기반
• 노드 프로비저닝, 스케일링, 패치를 AWS가 관리하는 방식
kubectl cluster-info
kubectl get nodes -L eks.amazonaws.com/compute-type
AI 워크로드는 일반 웹 서비스와 노드 요구사항이 다르다.
| Node Pool | 용도 |
|---|---|
| General Purpose | Open WebUI, LiteLLM, Langfuse 등 일반 서비스 |
| GPU Accelerated | GPU 기반 모델 추론 |
| AWS Neuron | Inferentia/Trainium 기반 추론 (inf2, trn1) |
kubectl get nodepools -o wide
• 모델 파일은 크기 때문에 매번 다운로드하면 Pod 기동 시간이 길어짐
• EFS 기반 공유 스토리지로 모델 캐시 재사용
kubectl get storageclass
kubectl get pv | grep efs
kubectl get pods -A | grep -E "litellm|langfuse|openwebui|vllm"
kubectl get svc -A | grep -E "litellm|langfuse|openwebui|vllm"
모든 Pod가 Running 상태여야 이후 실습이 의미 있다.
Open WebUI → LiteLLM Gateway → vLLM (self-hosted)
→ Bedrock (managed)
Open WebUI는 모델을 직접 바라보지 않는다. LiteLLM만 바라보고, LiteLLM이 백엔드를 라우팅한다.
echo "Open WebUI: http://$(kubectl get ingress -n openwebui openwebui -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
• 첫 접속 시 관리자 계정 생성
• 모델 선택 드롭다운에서 vLLM / Bedrock 모델 확인
openaiBaseApiUrl: http://litellm.litellm:4000/v1
extraEnvVars:
- name: OPENAI_API_KEY
value: ${LITELLM_API_KEY}
ollama:
enabled: false
→ Open WebUI는 LiteLLM의 OpenAI 호환 API를 사용한다.
vLLM은 EKS 위에서 직접 모델을 서빙하는 방식이다.
kubectl get pods -n vllm
kubectl get deployments -n vllm -o wide
nodeSelector:
eks.amazonaws.com/instance-family: inf2
resources:
requests:
cpu: 3
memory: 12Gi
aws.amazon.com/neuroncore: 2
limits:
aws.amazon.com/neuroncore: 2
• Neuron Core가 있는 인스턴스에만 스케줄링
• 일반 노드에 뜨면 안 되는 구조
kubectl logs -f --tail=0 -n vllm deployment/qwen3-8b-neuron
Open WebUI에서 vLLM 모델 선택 후 질문을 보내면 로그에서 확인 가능:
| 지표 | 의미 |
|---|---|
| Prompt throughput | 입력 토큰 처리 속도 |
| Generation throughput | 출력 토큰 생성 속도 |
| KV cache usage | 캐시 사용률 |
| Running / Waiting | 처리 중 / 대기 중 요청 |
Bedrock은 모델 인프라를 직접 운영하지 않고 API로 호출하는 방식이다.
Open WebUI에서 bedrock/claude-4.5-sonnet 선택 후 테스트:
Explain the concept of Kubernetes operators and provide a simple example.
| 구분 | vLLM | Bedrock |
|---|---|---|
| 운영 | 직접 | 관리형 |
| 비용 | 인스턴스 기반 | 토큰 기반 |
| 제어권 | 높음 | 제한적 |
| 운영 부담 | 높음 | 낮음 |
정답은 없다. 모델 런타임까지 책임질 수 있으면 vLLM, 빠르게 붙이고 싶으면 Bedrock.
Open WebUI에서 모델 이름 옆 + 버튼으로 여러 모델 동시 비교 가능.
What is square root of 144 divided by 29 multiplied by pi?
• 응답 속도, 설명 방식, 정확성 비교
Open WebUI의 Knowledge Base 기능으로 간단한 RAG 확인.
cat > super-secret.txt << 'EOF'
Super Secret Document
Project Codename: Nightfall
Objective: Develop an untraceable communication device.
Status: In progress
EOF
같은 모델, 같은 질문이어도 컨텍스트가 있느냐에 따라 답변이 완전히 달라진다.
Module 1이 "모델이 답한다"를 확인하는 단계라면, Module 2는 "플랫폼답게 운영할 수 있는가"를 보는 단계다.
여러 모델 Provider를 OpenAI 호환 API로 통합해주는 Gateway.
kubectl get pods -n litellm
| 컴포넌트 | 역할 |
|---|---|
| litellm | 모델 요청 라우팅 |
| postgresql | 설정/사용량 저장 |
| redis | 캐시 |
grep -A 16 "model_list:" /workshop/components/ai-gateway/litellm/values.rendered.yaml
model_list:
- model_name: bedrock/claude-4.5-sonnet
litellm_params:
model: bedrock/global.anthropic.claude-sonnet-4-5-20250929-v1:0
aws_region_name: us-west-2
- model_name: vllm/qwen3-8b-neuron
litellm_params:
model: openai/qwen3-8b-neuron
api_key: fake-key
api_base: http://qwen3-8b-neuron.vllm:8000/v1
vLLM도 LiteLLM 관점에서는 OpenAI 호환 API로 등록된다. 애플리케이션은 백엔드가 뭔지 몰라도 된다.
echo "LiteLLM: http://$(kubectl get ingress -n litellm litellm -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')/ui"
| 메뉴 | 확인 내용 |
|---|---|
| Models + Endpoints | 등록된 모델 목록 |
| Usage | 요청 수, 토큰, 비용 |
| Playground | 모델 직접 호출 테스트 |
| Virtual Keys | 앱용 API Key 생성 |
새 Bedrock 모델을 LiteLLM에 추가하는 흐름:
# 사용 가능한 모델 조회
aws bedrock list-foundation-models \
--query "modelSummaries[?contains(modelId, 'gpt-oss')].{ModelId:modelId,ModelName:modelName}" \
--output table
values에 추가:
- model_name: bedrock/gpt-oss-20b
litellm_params:
model: bedrock/openai.gpt-oss-20b-1:0
aws_region_name: us-west-2
적용:
helm upgrade litellm oci://ghcr.io/berriai/litellm-helm \
--namespace litellm \
-f /workshop/components/ai-gateway/litellm/values.rendered.yaml
kubectl rollout status deployment/litellm -n litellm
코드 수정 없이 설정 변경만으로 모델 추가 가능. 이게 Gateway의 가치다.
애플리케이션에서 LiteLLM을 호출할 때 사용하는 키.
LiteLLM UI → Virtual Keys → + Create New Key
| 이유 | 설명 |
|---|---|
| 접근 제어 | 앱별 사용 가능 모델 제한 |
| 비용 추적 | 키 단위 사용량 확인 |
| 운영 분리 | 환경/앱별 분리 |
| 폐기 용이 | 문제 키만 비활성화 |
Master Key를 앱에 직접 넣으면 안 된다. Virtual Key를 쓰는 게 맞다.
LLM 요청을 추적하는 관측성 플랫폼.
kubectl get pods -n langfuse
| 컴포넌트 | 역할 |
|---|---|
| langfuse-web | UI |
| langfuse-worker | 비동기 작업 |
| postgresql | 메타데이터 |
| clickhouse | Trace/Analytics |
| redis | 캐시 |
litellm_settings:
callbacks: ["langfuse"]
success_callback: ["langfuse"]
failure_callback: ["langfuse"]
→ LiteLLM을 거친 모든 모델 호출이 Langfuse에 기록된다.
echo "Langfuse: http://$(kubectl get ingress -n langfuse langfuse -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
| 항목 | 설명 |
|---|---|
| Input/Output | 프롬프트와 응답 |
| Latency | 응답 지연시간 |
| Token Usage | 입력/출력 토큰 수 |
| Cost | 호출 비용 |
| Error | 실패 요청 |
LLM은 HTTP 200이어도 응답 품질이 낮거나 비용이 과도할 수 있다. 그래서 일반 로그만으로는 부족하고 LLM 전용 관측성이 필요하다.
앞에서 만든 플랫폼 위에 실제 Agent 애플리케이션을 올린다.
대출 담당자가 수작업으로 처리하던 대출 신청 검토를 AI Agent가 자동화하는 시나리오.
대출 신청서 이미지 업로드
│
▼
Loan Buddy Agent (LangGraph)
├─ Image Processor MCP → 신청서에서 데이터 추출
├─ Address Validator MCP → 주소 검증
└─ Employment Validator MCP → 고용/소득 검증
│
▼
최종 승인/거절 판단 + Langfuse Trace
| 컴포넌트 | 역할 |
|---|---|
| Loan Buddy Web App | 신청서 업로드 |
| Loan Processing Agent | LangGraph 기반 워크플로우 오케스트레이션 |
| LiteLLM Gateway | 모델 호출 |
| Image Processor MCP | 이미지에서 데이터 추출 |
| Address Validator MCP | 주소 검증 |
| Employment Validator MCP | 고용/소득 검증 |
| Langfuse | Agent workflow 추적 |
Agent의 핵심 코드 구조:
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
model = ChatOpenAI(
model="bedrock/claude-4.5-sonnet",
api_key=model_key,
base_url="http://litellm.litellm.svc.cluster.local:4000"
)
graph = create_react_agent(model, tools, debug=True)
Agent는 Bedrock을 직접 호출하지 않는다. LiteLLM Gateway를 통해 호출한다. 그래야 키 관리, 관측성이 그대로 적용된다.
• S3에서 신청서 이미지를 가져와 Claude vision으로 데이터 추출
{
"name": "John Michael Doe",
"date_of_birth": "March 15, 1985",
"employer": "Tech Solutions Inc",
"annual_income": 75000,
"loan_amount": 8500,
"loan_purpose": "Home Improvement"
}
• 주소 형식 확인, 주거지 여부, 위험 점수 계산
• 고용 상태, 소득 검증, 근속 기간, 안정성 점수
cd /workshop/workshops/eks-genai-workshop/static/code/module3/credit-validation
chmod +x deploy-workshop-app.sh
./deploy-workshop-app.sh
스크립트가 하는 일:
1. S3 bucket, Region, LiteLLM Key, Langfuse Key 설정
2. workshop namespace 생성
3. ServiceAccount + Pod Identity 설정
4. Loan Buddy + MCP 서버 배포
kubectl get pods -n workshop
예상 Pod:
loan-buddy-agent-xxx
mcp-address-validator-xxx
mcp-employment-validator-xxx
mcp-image-processor-xxx
# Agent 로그 (별도 터미널)
kubectl logs -f deployment/loan-buddy-agent -n workshop
# 포트포워딩
kubectl port-forward service/loan-buddy-agent 8080:8080 -n workshop &
# 신청서 처리
curl -X POST -F "image_file=@./example1.png" \
http://localhost:8080/api/process_credit_application_with_upload \
| jq
Langfuse Tracing 메뉴에서 최근 trace 확인:
• 전체 처리 시간
• 호출된 MCP 도구와 순서
• 각 도구의 입력/출력
• 모델 호출 내용
• 토큰 사용량, 비용
• 최종 판단 근거
Agentic AI의 핵심은 "모델 답변"이 아니라 도구 호출과 의사결정 흐름의 추적 가능성이다.
| 모듈 | 핵심 |
|---|---|
| Module 1 | 모델을 직접 써본다 |
| Module 2 | 모델을 플랫폼으로 묶는다 |
| Module 3 | 플랫폼 위에 Agent를 올린다 |
• Open WebUI는 UI일 뿐, 핵심은 LiteLLM Gateway 구조
• vLLM은 자유도 높지만 운영 부담도 같이 감
• Bedrock은 빠르게 붙일 수 있지만 비용/리전 제약 확인 필요
• Langfuse 없이 LLM 운영하면 장애 분석과 비용 추적이 어려움
• MCP로 도구를 분리하면 Agent prompt가 비대해지는 문제 완화
• 결국 Agentic AI 플랫폼은 모델보다 라우팅 + 관측성 + 도구 계층이 더 중요
• 워크숍 모델명은 시점에 따라 바뀔 수 있음 → 실제 model_list 확인
• Bedrock 모델은 리전별 가용성이 다름
• Self-hosted 모델은 토큰당 비용이 바로 안 보임 → 인프라 비용 기준 산정 필요
• Virtual Key는 생성 후 즉시 저장
• 워크숍 기본 계정/패스워드는 운영에서 절대 그대로 쓰면 안 됨
# 전체 컴포넌트
kubectl get pods -A | grep -E "litellm|langfuse|openwebui|vllm"
# Open WebUI
echo "Open WebUI: http://$(kubectl get ingress -n openwebui openwebui -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
# vLLM
kubectl get pods -n vllm
kubectl logs -f --tail=0 -n vllm deployment/qwen3-8b-neuron
# LiteLLM
kubectl get pods -n litellm
echo "LiteLLM: http://$(kubectl get ingress -n litellm litellm -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')/ui"
grep -A 16 "model_list:" /workshop/components/ai-gateway/litellm/values.rendered.yaml
# Langfuse
kubectl get pods -n langfuse
echo "Langfuse: http://$(kubectl get ingress -n langfuse langfuse -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
# Loan Buddy
kubectl get pods -n workshop
kubectl logs -f deployment/loan-buddy-agent -n workshop
kubectl port-forward service/loan-buddy-agent 8080:8080 -n workshop &
# 신청서 처리
curl -X POST -F "image_file=@./example1.png" \
http://localhost:8080/api/process_credit_application_with_upload | jq