배포 자습서: Kubernetes에서 vLLM 실행하기
배포 자습서: Kubernetes에서 vLLM 실행하기 (Using Kubernetes)
Kubernetes에서 vLLM을 배포하면 머신러닝 모델을 확장성 있고 효율적으로 서빙할 수 있어요. 이 가이드는 네이티브 Kubernetes로 vLLM을 배포하는 과정을 처음부터 차근차근 안내해요.
배포 방식은 이것만 있는 게 아니에요. 다음 도구들로도 Kubernetes에 vLLM을 배포할 수 있어요:
- Helm
- NVIDIA Dynamo
- InftyAI/llmaz
- llm-d
- KAITO
- KServe
- Kthena
- KubeRay
- kubernetes-sigs/lws
- meta-llama/llama-stack
- substratusai/kubeai
- vllm-project/AIBrix
- vllm-project/production-stack
CPU 배포 (Deployment with CPUs)
!!! note 여기서 CPU를 쓰는 건 데모·테스트 목적일 뿐이에요. 성능은 GPU와 비교할 수 없을 정도로 차이가 나요.
먼저 Hugging Face 모델을 내려받고 저장하기 위한 Kubernetes PVC와 Secret을 만들어요:
??? console "Config"
```bash
cat <<EOF |kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vllm-models
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
resources:
requests:
storage: 50Gi
---
apiVersion: v1
kind: Secret
metadata:
name: hf-token-secret
type: Opaque
stringData:
token: "REPLACE_WITH_TOKEN"
EOF
```
여기서 token 필드는 Hugging Face 액세스 토큰을 담아요. 토큰 생성 방법은 Hugging Face 문서에서 확인할 수 있어요.
다음으로 vLLM 서버를 Kubernetes Deployment와 Service로 띄워요. 프로세서 아키텍처에 맞는 vLLM 이미지를 골라야 해요:
??? console "Config"
```bash
VLLM_IMAGE=public.ecr.aws/q9t5s3a7/vllm-cpu-release-repo:latest # use this for x86_64
VLLM_IMAGE=public.ecr.aws/q9t5s3a7/vllm-arm64-cpu-release-repo:latest # use this for arm64
cat <<EOF |kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-server
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: vllm
template:
metadata:
labels:
app.kubernetes.io/name: vllm
spec:
containers:
- name: vllm
image: $VLLM_IMAGE
command: ["/bin/sh", "-c"]
args: [
"vllm serve meta-llama/Llama-3.2-1B-Instruct"
]
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token-secret
key: token
ports:
- containerPort: 8000
volumeMounts:
- name: llama-storage
mountPath: /root/.cache/huggingface
volumes:
- name: llama-storage
persistentVolumeClaim:
claimName: vllm-models
---
apiVersion: v1
kind: Service
metadata:
name: vllm-server
spec:
selector:
app.kubernetes.io/name: vllm
ports:
- protocol: TCP
port: 8000
targetPort: 8000
type: ClusterIP
EOF
```
로그를 통해 vLLM 서버가 성공적으로 시작했는지 확인할 수 있어요(모델을 내려받는 데 몇 분 걸릴 수 있어요):
kubectl logs -l app.kubernetes.io/name=vllm
...
INFO: Started server process [1]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
GPU 배포 (Deployment with GPUs)
전제 조건: GPU가 있는 Kubernetes 클러스터가 실행 중이어야 해요.
-
vLLM용 PVC, Secret, Deployment 생성하기
PVC는 모델 캐시를 저장하는 데 쓰이며 선택 사항이에요. hostPath나 다른 스토리지 옵션을 써도 돼요:
Yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mistral-7b namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 50Gi storageClassName: default volumeMode: FilesystemSecret도 선택 사항이고, gated 모델에 접근할 때만 필요해요. gated 모델을 쓰지 않는다면 이 단계는 건너뛰어도 돼요.
apiVersion: v1 kind: Secret metadata: name: hf-token-secret namespace: default type: Opaque stringData: token: "REPLACE_WITH_TOKEN"다음으로 vLLM 모델 서버를 실행할 Deployment 파일을 만들어요. 아래 예시는
Mistral-7B-Instruct-v0.3모델을 배포해요.NVIDIA GPU와 AMD GPU 두 가지 예시가 있어요.
NVIDIA GPU:
Yaml
apiVersion: apps/v1 kind: Deployment metadata: name: mistral-7b namespace: default labels: app: mistral-7b spec: replicas: 1 selector: matchLabels: app: mistral-7b template: metadata: labels: app: mistral-7b spec: volumes: - name: cache-volume persistentVolumeClaim: claimName: mistral-7b # vLLM needs to access the host's shared memory for tensor parallel inference. - name: shm emptyDir: medium: Memory sizeLimit: "2Gi" containers: - name: mistral-7b image: vllm/vllm-openai:latest command: ["/bin/sh", "-c"] args: [ "vllm serve mistralai/Mistral-7B-Instruct-v0.3 --trust-remote-code --enable-chunked-prefill --max-num-batched-tokens 1024" ] env: - name: HF_TOKEN valueFrom: secretKeyRef: name: hf-token-secret key: token ports: - containerPort: 8000 resources: limits: cpu: "10" memory: 20G nvidia.com/gpu: "1" requests: cpu: "2" memory: 6G nvidia.com/gpu: "1" volumeMounts: - mountPath: /root/.cache/huggingface name: cache-volume - name: shm mountPath: /dev/shm livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 60 periodSeconds: 5AMD GPU:
MI300X 같은 AMD ROCm GPU를 쓴다면 아래
deployment.yaml을 참고하세요.Yaml
apiVersion: apps/v1 kind: Deployment metadata: name: mistral-7b namespace: default labels: app: mistral-7b spec: replicas: 1 selector: matchLabels: app: mistral-7b template: metadata: labels: app: mistral-7b spec: volumes: # PVC - name: cache-volume persistentVolumeClaim: claimName: mistral-7b # vLLM needs to access the host's shared memory for tensor parallel inference. - name: shm emptyDir: medium: Memory sizeLimit: "8Gi" hostNetwork: true hostIPC: true containers: - name: mistral-7b image: rocm/vllm:rocm6.2_mi300_ubuntu20.04_py3.9_vllm_0.6.4 securityContext: seccompProfile: type: Unconfined runAsGroup: 44 capabilities: add: - SYS_PTRACE command: ["/bin/sh", "-c"] args: [ "vllm serve mistralai/Mistral-7B-v0.3 --port 8000 --trust-remote-code --enable-chunked-prefill --max-num-batched-tokens 1024" ] env: - name: HF_TOKEN valueFrom: secretKeyRef: name: hf-token-secret key: token ports: - containerPort: 8000 resources: limits: cpu: "10" memory: 20G amd.com/gpu: "1" requests: cpu: "6" memory: 6G amd.com/gpu: "1" volumeMounts: - name: cache-volume mountPath: /root/.cache/huggingface - name: shm mountPath: /dev/shm전체 예시와 샘플 yaml 파일은 https://github.com/ROCm/k8s-device-plugin/tree/master/example/vllm-serve에서 볼 수 있어요.
-
vLLM용 Kubernetes Service 만들기
mistral-7bDeployment를 노출할 Kubernetes Service 파일을 만들어요:Yaml
apiVersion: v1 kind: Service metadata: name: mistral-7b namespace: default spec: ports: - name: http-mistral-7b port: 80 protocol: TCP targetPort: 8000 # The label selector should match the deployment labels & it is useful for prefix caching feature selector: app: mistral-7b sessionAffinity: None type: ClusterIP -
배포하고 테스트하기
kubectl apply -f <filename>로 deployment와 service 구성을 적용하세요:kubectl apply -f deployment.yaml kubectl apply -f service.yaml배포를 테스트하려면 다음
curl명령을 실행해요:curl http://mistral-7b.default.svc.cluster.local/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "mistralai/Mistral-7B-Instruct-v0.3", "prompt": "San Francisco is a", "max_tokens": 7, "temperature": 0 }'서비스가 올바르게 배포되었다면 vLLM 모델로부터 응답을 받을 수 있어요.
gRPC 서빙 (Serving with gRPC)
vLLM은 --grpc 플래그를 넘기면 HTTP 대신 gRPC로 모델을 서빙할 수 있어요. 이를 위해선 선택적 gRPC 의존성이 필요해요:
pip install vllm[grpc]
--grpc를 쓰면 서버는 표준 gRPC Health Checking Protocol(grpc.health.v1.Health)을 노출해요. 이는 Kubernetes 1.24부터 지원되는 네이티브 gRPC 프로브와 통합돼요.
gRPC로 배포하려면 vllm serve 명령에 --grpc를 추가하고, httpGet 프로브를 grpc 프로브로 바꾸세요:
containers:
- name: mistral-7b
image: vllm/vllm-openai:latest
command: ["/bin/sh", "-c"]
args: [
"pip install vllm[grpc] && vllm serve mistralai/Mistral-7B-Instruct-v0.3 --grpc --port 50051 --trust-remote-code"
]
ports:
- containerPort: 50051
livenessProbe:
grpc:
port: 50051
initialDelaySeconds: 120
periodSeconds: 10
readinessProbe:
grpc:
port: 50051
initialDelaySeconds: 120
periodSeconds: 5
!!! note
gRPC 헬스 서비스는 프로브가 실행될 때마다 엔진 상태를 확인해요. 엔진이 unhealthy하거나 서버가 종료되는 중이면 프로브는 NOT_SERVING을 반환해요.
grpcurl로 헬스 서비스를 직접 확인할 수도 있어요:
grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check
트러블슈팅 (Troubleshooting)
Startup/Readiness 프로브 실패, 컨테이너 로그에 "KeyboardInterrupt: terminated"
서버 시작에 필요한 시간보다 startup·readiness 프로브의 failureThreshold가 너무 낮으면 Kubernetes 스케줄러가 컨테이너를 죽일 수 있어요. 이런 일이 일어났음을 알려주는 신호는 두 가지예요:
- 컨테이너 로그에 "KeyboardInterrupt: terminated"가 있다
kubectl get events에Container $NAME failed startup probe, will be restarted메시지가 보인다
이를 완화하려면 failureThreshold를 높여 모델 서버가 서빙을 시작할 시간을 더 줘요. 이상적인 failureThreshold를 찾으려면 매니페스트에서 프로브를 제거한 뒤, 모델 서버가 서빙 준비가 될 때까지 걸리는 시간을 측정하면 돼요.
마무리 (Conclusion)
Kubernetes로 vLLM을 배포하면 GPU 자원을 활용해 ML 모델을 효율적으로 확장·관리할 수 있어요. 위 단계를 따라 하면 자신의 Kubernetes 클러스터 안에서 vLLM 배포를 설정하고 테스트할 수 있을 거예요. 문제가 생기거나 제안이 있다면 문서에 기여해 주셔도 좋아요.
출처: 공식문서
더 알아보기 (Learn more)
- Docker 배포 자습서: Using Docker
- Nginx 로드밸런서 구성: Using Nginx
- gRPC 프로브 상세: Configure Liveness, Readiness and Startup Probes
- GPU 스케줄링: Schedule GPUs