쿠버네티스
블로그 불러오는 중...
문의 보내기
남겨주면 블로그 주인에게 바로 전달돼요.
쿠버네티스
오늘은 아마존 EKS 에서 생성형 AI 실습을 진행해 본다. 링크는 아래와 같다. https://catalog.workshops.aws/genai-on-eks/ko-KR/50-getting-started
나는 자습형(직접 내 AWS 계정)에 실습 환경을 구축해서 진행하게 처리했다. 링크 를 클릭하여 스택을 프로비저닝 할 수 있다.
git clone https://github.com/awslabs/ai-on-eks.git cd ai-on-eks/infra/workshops/genai-on-eks
terraform/blueprint.tfvars 파일을 편집하고 원하는 AWS 리전을 설정하세요:
name = "genai-workshop" region = "us-east-2" # 사용할 리전으로 업데이트하세요
chmod +x install.sh ./install.sh
aws eks update-kubeconfig --name genai-workshop --region us-east-2
위와 같은 형태로 기본 인프라를 세팅한다.

다음과 같은 형태로 용량 예약을 하지 않으면 프로젝트를 진행할 수 없다.

해당 EC2를 생성하였는데, 쿼타를 올리지 않으면 아래와 같은 에러와 함께 예약이 불가능하다.

Running On-Demand G and VT instances 를 8로 늘리는 쿼타 업그레이드 요청을 해야 한다.

https://us-east-2.console.aws.amazon.com/servicequotas/home/services/ec2/quotas 이 링크로 들어가면 해당 쿼타를 수정할 수 있다. 수정하면 바로 되는 것이 아닌 AWS 에서 수동으로 신청하는 지 시간이 하루 이상 소요되었다.
기본 인프라는 다음과 같다고 한다.
먼저 실습에서는 vLLM 을 설치하여 오픈소스 모델들을 서빙할 수 있는데 그것을 따라해 볼 예정이다.
vLLM 은 더 효율적인 GPU 메모리 활용을 통해 생성형 AI 애플리케이션의 성능을 최적화하도록 특별히 설계된 여러 인기 있는 오픈소스 추론 및 서빙 엔진 중 하나라고 한다.
구성이 완료되면 다음과 같은 형태의 명령어로 환경변수를 셋팅한다.
GPU 기능을 테스트하기 전에, 온디맨드 용량 예약(ODCR)을 사용하도록 GPU NodeClass를 구성해야 한다. 아래 명령어를 통해 확인하고 적용한다.
# Extract region from kubectl context
AWS_REGION=$(kubectl config get-contexts | grep '*' | awk '{print $2}' | cut -d':' -f4)
echo "Using AWS Region: $AWS_REGION"
# Get the ODCR ID from the current region
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"
# Create a patch file
mkdir -p manifests/100-introduction
cat <<EOF > manifests/100-introduction/patch-gpu-nodeclass.yaml
apiVersion: eks.amazonaws.com/v1
kind: NodeClass
metadata:
name: gpu
spec:
capacityReservationSelectorTerms:
- id: $ODCR_ID
EOF
# Apply the patch
kubectl patch nodeclass gpu --patch-file manifests/100-introduction/patch-gpu-nodeclass.yaml --type=merge
# Verify the patch was applied
kubectl get nodeclass gpu -o yaml | grep -A 3 capacityReservationSelectorTerms# 배포를 위한 환경 변수 설정
# AWS_REGION은 워크숍 환경에서 이미 설정되어 있습니다
# 계정 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 버킷에 있는 모델 파일 나열
aws s3 ls s3://${S3_BUCKET_NAME}/Ministral-3-8B-Instruct-2512/ --recursive아래와 같이 S3 에 들어 있는 것을 확인 해 본다.

mkdir -p manifests/200-inference
# Run:AI 스트리머가 포함된 vLLM 배포 매니페스트 다운로드
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
# envsubst를 사용하여 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
# 대체가 제대로 작동했는지 확인
cat manifests/200-inference/vllm-s3-deployment.yml | grep -A 2 "model=s3://"
# 배포 적용
kubectl apply -f manifests/200-inference/vllm-s3-deployment.yml해당 부분에서도 에러가 발생하여 karpenter 가 못띄우는 문제가 있었다.
kubectl patch nodepool gpu --type='json' -p='[
{
"op": "replace",
"path": "/spec/template/spec/requirements",
"value": [
{
"key": "karpenter.sh/capacity-type",
"operator": "In",
"values": ["reserved"]
},
{
"key": "node.kubernetes.io/instance-type",
"operator": "In",
"values": ["g6e.2xlarge"]
},
{
"key": "topology.kubernetes.io/zone",
"operator": "In",
"values": ["us-east-2a"]
}
]
}
]'로 gpu를 고정하게 끔 셋팅해버렸더니, gpu 노드가 올라왔다.
vLLM 으로 LLM 테스트를 하기 위해 포트를 열고 curl 을 날려 본다.
kubectl port-forward svc/vllm-serve-svc 8000:8000
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export S3_BUCKET_NAME="genai-models-${AWS_ACCOUNT_ID}"
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
다음과 같이 LLM 응답이 정상으로 오는 것을 확인 하였다.
OpenWebUI는 LLM 을 chatgpt 처럼 UI 에서 실제 대화를 쉽게 주고 받을 수 있게 해주는 툴이다. 설치해서 시도해 본다
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
kubectl apply -f manifests/200-inference/openwebui.yml
## 확인
kubectl get pods -l app=open-webui -w하기 명령어를 입력하고 기다려 본다.
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}"다 완료 되면, 로드밸런서 URL 이 나오게 된다.
다음과 같이 조금 완벽하지 못한 텍스트가 나온다. 프롬프트를 구체적으로 주지 않고 뜬금없이 보내서 다음과 같은 이상한 결과도 나오는 것이겠지만, 성능이 확실히 부족한 것 같다.
그래도 vLLM 을 이용해 서비스를 올려보았다. 해당 형태로 서빙 할 수 있다는 것이 놀라웠다. 회사에서는 Ollama 를 사용하고 있는데, 해당 Ollama 보다는 확실히 성능이 vLLM 이 좋은 것 같다.
여러 시간을 GPU 노드를 자동으로 만드는 데에 시간을 사용했는데, 안 되면 중간에 나처럼 예약한 것을 최대한 생성하게 유도하게끔 변경을 하기를 바란다.
우리 실습 환경에서는 이미 grafana 가 설치된 상태의 테라폼으로 진행하였기 때문에 이미 grafana 가 떠 있다. NVIDIA DCGM Exporter 설치해야 하는데 해당 서비스는 다음과 같다.
NVIDIA Data Center GPU Manager(DCGM) Exporter 는 NVIDIA GPU의 메트릭을 노출하는 도구입니다. GPU 활용도, 메모리 사용량, 온도, 전력 소비 및 기타 성능 지표와 같은 필수 메트릭을 수집합니다. 이러한 메트릭은 Prometheus 형식으로 내보내져 Kubernetes 환경에서 GPU 워크로드를 모니터링하는 데 이상적입니다.
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"
# Prometheus 스크레이핑을 위한 파드 주석
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9400"
# 파드 레이블
podLabels:
app.kubernetes.io/name: "dcgm-exporter"
# OOM 문제 방지를 위한 리소스 제한
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설치 한다.
# NVIDIA Helm 저장소 추가
helm repo add gpu-helm-charts https://nvidia.github.io/dcgm-exporter/helm-charts
helm repo update
# monitoring 네임스페이스에 DCGM Exporter 설치
helm install dcgm-exporter gpu-helm-charts/dcgm-exporter \
-n monitoring \
-f manifests/200-inference/values.yaml
# DCGM Exporter 배포 확인
kubectl wait pods --for=jsonpath='{.status.phase}'=Running -l "app.kubernetes.io/name=dcgm-exporter" -n monitoring --timeout=300s포트포워딩 하여 curl 로 테스트를 한다
# 메트릭 엔드포인트 조회(다른 쉘에서 실행)
curl -sL http://127.0.0.1:9400/metrics
다음과 같은 메트릭 정보들이 나오는 것을 볼 수 있다.
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로드밸런서 URL 을 가지고 온다. 로드밸런서 생성까지 5분 이내의 시간이 소요되므로 기다려 본다
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}"접속하여, nvidia 로 검색해서 대시보드를 확인 해 본다.

다음과 같이 온도 등을 모니터링이 가능하다.

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
EOFmonitoring > vLLM Metrics 로 찾아간다. 이미 대시보드는 구성되어 있고 그리고 아까 만든 웹 UI 에 여러가지 테스트를 진행하면서 메트릴을 확인 해 본다.

메트릭은 아래와 같은 것을 나타낸다고 한다.
vLLM Iterations Token:
vLLM이 수행한 처리 반복 횟수를 추적합니다
시간 경과에 따른 모델의 계산 작업량을 모니터링하는 데 도움이 됩니다
처리 패턴 및 최적화 기회를 이해하는 데 유용합니다
vLLM Generations Tokens:
모델이 생성한 토큰의 총 수를 보여줍니다
모델 출력 볼륨과 처리량을 추적하는 데 도움이 됩니다
용량 계획 및 사용량 모니터링에 중요합니다
vLLM Time Per Output Token:
각 토큰을 생성하는 데 걸리는 평균 시간을 측정합니다
모델 효율성에 대한 주요 성능 지표입니다
성능 병목 현상이나 저하를 식별하는 데 도움이 됩니다
vLLM Time to First Token Counter:
요청 수신과 첫 번째 토큰 생성 사이의 지연 시간을 추적합니다
사용자 경험과 응답성에 중요한 메트릭입니다
초기 응답 시간 성능을 모니터링하는 데 도움이 됩니다
vLLM Time in Queue Requests:
요청이 처리되기 전에 대기하는 시간을 보여줍니다
시스템 부하 및 잠재적 병목 현상을 나타냅니다
용량 계획 및 확장 결정에 유용합니다
vLLM Request Prompt Tokens:
요청의 입력 토큰 수를 모니터링합니다
입력 워크로드 패턴을 이해하는 데 도움이 됩니다
프롬프트 엔지니어링 및 리소스 할당 최적화에 유용합니다
vLLM Request Inference Time:
추론 처리에 소요되는 총 시간을 측정합니다
전체 시스템 성능에 대한 주요 메트릭입니다
처리 병목 현상 및 최적화 요구 사항을 식별하는 데 도움이 됩니다
vLLM Num Preemptions Total:
요청 선점 횟수를 추적합니다
리소스 경합 및 스케줄링 효율성을 나타냅니다
시스템 안정성 및 리소스 관리를 이해하는 데 중요합니다
여러가지 실습을 해 보았고 정말 좋은 실습이였던 것 같다.
모니터링, 그리고 LLM 서빙에 대해서 어느정도 알아볼 수 있었다.
쿼타나 GPU 가 매칭이 되게 하는 부분에서 많은 시간을 소요했으나, 실제 서비스에서는 해당 부분에서 시간을 소모할 일은 크게 없어 보인다.
# 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 서비스 삭제 (배포된 경우)
kubectl delete -f manifests/400-ray/ray-vllm-s3-service.yml 2>/dev/null || true
# OpenWebUI 삭제 (배포된 경우)
kubectl delete -f manifests/400-ray/openwebui.yml 2>/dev/null || true
# Grafana Ingress 삭제 (배포된 경우)
kubectl delete -f manifests/300-observability/grafana-ingress.yaml 2>/dev/null || true예약 된 인스턴스를 제거해야 한다. 예약은 사용하지 않아도 비용이 발생된다.
작업을 눌러 취소를 클릭한다.
아래와 같이 Canceled 로 변경되는지 확인한다.

cd ai-on-eks/infra/workshops/genai-on-eks/terraform/_LOCAL
./cleanup.sh중간에 프로메테우스가 잘 안지워지면,
kubectl -n argocd patch application kube-prometheus-stack \ --type=merge \ -p '{"metadata":{"finalizers":[]}}' 명령어를 통해 finalizer 를 변경하여 삭제될 수 있게 한다.
감사합니다.