5 분 소요

이번 블로그는 vLLM 배포를 위해 Prometheus, Grafana 및 CloudWatch를 사용한 모니터링을 구성한다.

1. 환경변수 설정

export AWS_REGION=us-west-2
export CLUSTER_NAME=vllm-trn1-eks-cluster
export MONITORING_NAMESPACE=monitoring

# Verify current namespace
kubectl config set-context --current --namespace=default

2. 모니터링 네임스페이스 생성

kubectl create namespace $MONITORING_NAMESPACE

3. Helm을 사용한 Prometheus 설치

# Add Prometheus Helm repository
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# Create custom values for Prometheus
cat > prometheus-values.yaml <<EOF
server:
  persistentVolume:
    enabled: false
  retention: "15d"
  resources:
    requests:
      cpu: 500m
      memory: 1Gi
    limits:
      cpu: 1000m
      memory: 2Gi
  global:
    scrape_interval: 15s
    evaluation_interval: 15s

alertmanager:
  enabled: true
  persistentVolume:
    enabled: false

# Enable node exporter for node metrics
nodeExporter:
  enabled: true

# Enable kube-state-metrics for Kubernetes metrics
kubeStateMetrics:
  enabled: true

# Scrape configuration
serverFiles:
  prometheus.yml:
    scrape_configs:
    - job_name: 'kubernetes-pods'
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
  
    - job_name: 'vllm-metrics'
      static_configs:
      - targets: ['vllm-service.default.svc.cluster.local:8080']
      metrics_path: '/metrics'
      scrape_interval: 10s
EOF

# Install Prometheus
helm install prometheus prometheus-community/prometheus \
  --namespace $MONITORING_NAMESPACE \
  --values prometheus-values.yaml

4. Helm을 사용한 Grafana 설치

# Add Grafana Helm repository
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# Create custom values for Grafana
cat > grafana-values.yaml <<EOF
persistence:
  enabled: false

adminPassword: "vllm-admin-2024"

service:
  type: ClusterIP

datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server.monitoring.svc.cluster.local
      access: proxy
      isDefault: true

dashboardProviders:
  dashboardproviders.yaml:
    apiVersion: 1
    providers:
    - name: 'default'
      orgId: 1
      folder: ''
      type: file
      disableDeletion: false
      editable: true
      options:
        path: /var/lib/grafana/dashboards/default

dashboards:
  default:
    kubernetes-cluster:
      gnetId: 7249
      revision: 1
      datasource: Prometheus
    kubernetes-pods:
      gnetId: 6336
      revision: 1
      datasource: Prometheus

resources:
  requests:
    cpu: 250m
    memory: 512Mi
  limits:
    cpu: 500m
    memory: 1Gi
EOF

# Install Grafana
helm install grafana grafana/grafana \
  --namespace $MONITORING_NAMESPACE \
  --values grafana-values.yaml

5. vLLM 대시보드 JSON 파일 생성

# Create vLLM dashboard JSON file with correct metric names
cat > vllm-dashboard.json <<'EOF'
{
  "id": null,
  "title": "vLLM Inference Metrics",
  "description": "Dashboard for monitoring vLLM inference performance",
  "tags": ["vllm", "inference", "llm"],
  "timezone": "browser",
  "panels": [
    {
      "id": 1,
      "title": "Total Successful Requests",
      "type": "stat",
      "targets": [
        {
          "expr": "vllm:request_success_total",
          "legendFormat": "Total Requests"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "short"
        }
      },
      "gridPos": {"h": 8, "w": 6, "x": 0, "y": 0}
    },
    {
      "id": 2,
      "title": "Running Requests",
      "type": "stat",
      "targets": [
        {
          "expr": "vllm:num_requests_running",
          "legendFormat": "Running"
        }
      ],
      "gridPos": {"h": 8, "w": 6, "x": 6, "y": 0}
    },
    {
      "id": 3,
      "title": "Waiting Requests",
      "type": "stat",
      "targets": [
        {
          "expr": "vllm:num_requests_waiting",
          "legendFormat": "Waiting"
        }
      ],
      "gridPos": {"h": 8, "w": 6, "x": 12, "y": 0}
    },
    {
      "id": 4,
      "title": "KV Cache Usage",
      "type": "gauge",
      "targets": [
        {
          "expr": "vllm:gpu_cache_usage_perc * 100",
          "legendFormat": "KV Cache Usage %"
        }
      ],
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "min": 0,
          "max": 100,
          "thresholds": {
            "steps": [
              {"color": "green", "value": 0},
              {"color": "yellow", "value": 60},
              {"color": "red", "value": 80}
            ]
          }
        }
      },
      "gridPos": {"h": 8, "w": 6, "x": 18, "y": 0}
    },
    {
      "id": 5,
      "title": "Total Prompt Tokens",
      "type": "stat",
      "targets": [
        {
          "expr": "vllm:prompt_tokens_total",
          "legendFormat": "Prompt Tokens"
        }
      ],
      "gridPos": {"h": 8, "w": 6, "x": 0, "y": 8}
    },
    {
      "id": 6,
      "title": "Total Generated Tokens",
      "type": "stat",
      "targets": [
        {
          "expr": "vllm:generation_tokens_total",
          "legendFormat": "Generated Tokens"
        }
      ],
      "gridPos": {"h": 8, "w": 6, "x": 6, "y": 8}
    },
    {
      "id": 7,
      "title": "Request Success Over Time",
      "type": "timeseries",
      "targets": [
        {
          "expr": "vllm:request_success_total",
          "legendFormat": "Total Successful Requests"
        }
      ],
      "gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}
    },
    {
      "id": 8,
      "title": "Token Generation Over Time",
      "type": "timeseries",
      "targets": [
        {
          "expr": "vllm:prompt_tokens_total",
          "legendFormat": "Prompt Tokens"
        },
        {
          "expr": "vllm:generation_tokens_total",
          "legendFormat": "Generated Tokens"
        }
      ],
      "gridPos": {"h": 8, "w": 24, "x": 0, "y": 16}
    }
  ],
  "time": {"from": "now-15m", "to": "now"},
  "refresh": "10s"
}
EOF

6. vLLM 대시보드를 Kubernetes로 가져오기

# Import vLLM dashboard from existing JSON file
kubectl create configmap vllm-dashboard \
  --from-file=vllm-dashboard.json \
  -n $MONITORING_NAMESPACE

7. Grafana에 대시보드 마운트

# Mount the vLLM dashboard ConfigMap into Grafana pod
kubectl patch deployment grafana -n $MONITORING_NAMESPACE --type='json' -p='[
  {
    "op": "add",
    "path": "/spec/template/spec/volumes/-",
    "value": {
      "name": "vllm-dashboard",
      "configMap": {
        "name": "vllm-dashboard"
      }
    }
  },
  {
    "op": "add",
    "path": "/spec/template/spec/containers/0/volumeMounts/-",
    "value": {
      "name": "vllm-dashboard",
      "mountPath": "/var/lib/grafana/dashboards/default/vllm-dashboard.json",
      "subPath": "vllm-dashboard.json"
    }
  }
]'

8. 지표 수집을 위한 vLLM 디플로이먼트 업데이트

# Update the vLLM deployment to expose metrics
kubectl annotate deployment vllm-deployment -n default \
  prometheus.io/scrape=true \
  prometheus.io/port=8080 \
  prometheus.io/path=/metrics

9. CloudWatch 대시보드 생성

cat > cloudwatch-dashboard.json <<EOF
{
  "widgets": [
    {
      "type": "metric",
      "properties": {
        "metrics": [
          ["AWS/ContainerInsights", "pod_cpu_utilization", "PodName", "vllm-pod", "Namespace", "default", "ClusterName", "$CLUSTER_NAME"],
          [".", "pod_memory_utilization", ".", ".", ".", ".", ".", "."]
        ],
        "period": 300,
        "stat": "Average",
        "region": "$AWS_REGION",
        "title": "vLLM Pod Resource Utilization"
      }
    },
    {
      "type": "metric",
      "properties": {
        "metrics": [
          ["AWS/ContainerInsights", "pod_network_rx_bytes", "PodName", "vllm-pod", "Namespace", "default", "ClusterName", "$CLUSTER_NAME"],
          [".", "pod_network_tx_bytes", ".", ".", ".", ".", ".", "."]
        ],
        "period": 300,
        "stat": "Average",
        "region": "$AWS_REGION",
        "title": "vLLM Network Traffic"
      }
    }
  ]
}
EOF

# Create the dashboard
aws cloudwatch put-dashboard \
  --dashboard-name "vLLM-EKS-Monitoring" \
  --dashboard-body file://cloudwatch-dashboard.json \
  --region $AWS_REGION


10. 모니터링 컴포넌트 확인

# Check all monitoring components
kubectl get pods -n $MONITORING_NAMESPACE
kubectl get pods -n amazon-cloudwatch

11. Grafana 대시보드 접근

# Access Grafana
kubectl port-forward -n $MONITORING_NAMESPACE svc/grafana 3000:80 &
echo "Grafana: http://localhost:3000 (admin/vllm-admin-2024)"

포트 포워딩이 실행되면 아래 링크를 클릭해 Grafana 대시보드에 접근할 수 있다.

Grafana 대시보드 열기

다음 자격 증명을 사용한다.

  • 사용자 이름: admin
  • 비밀번호: vllm-admin-2024

그런 다음 dashboards/vLLM Inference Metrics로 이동해 추론 지표, 요청 처리량 및 KV 캐시 사용량을 모니터링한다.

그림 1. Grafana vLLM Inference Metrics 대시보드

12. Prometheus 대시보드 접근

# Access Prometheus
kubectl port-forward -n $MONITORING_NAMESPACE svc/prometheus-server 9090:80 &
echo "Prometheus: http://localhost:9090"

포트 포워딩이 실행되면 아래 링크를 클릭해 Prometheus 대시보드에 접근할 수 있다.

Prometheus 대시보드 열기

13. Prometheus 쿼리 실행

특정 쿼리를 입력해 Prometheus 대시보드에서 vLLM 지표를 직접 살펴볼 수 있다. 다음은 확인해 볼 만한 vLLM 지표다.

  • vllm:prompt_tokens_total - 처리한 프롬프트 토큰의 총수
  • vllm:time_to_first_token_seconds_sum - 모든 요청에서 첫 번째 토큰이 생성될 때까지 걸린 누적 시간

그림 2. Prometheus vLLM 쿼리

Prometheus Expression Browser에 이 쿼리를 입력하면 vLLM 배포의 실시간 지표를 확인할 수 있다. 이는 디버깅, 사용자 정의 알림 생성 또는 추가 대시보드 구축에 유용하다.

14. 관측 가능성 아키텍처 요약

이제 모니터링 구성에 다음 항목이 포함된다.

  1. Prometheus: 지표 수집 및 알림
  2. Grafana: 시각화 및 대시보드
  3. CloudWatch Container Insights: AWS 네이티브 모니터링
  4. 사용자 정의 대시보드: vLLM 전용 지표 시각화

15. 다음 단계

종합적인 모니터링 구성을 마쳤다. 다음으로 성능 테스트 섹션에서 부하가 걸린 상태의 성능과 스케일링 동작을 검증한다.

댓글남기기