vLLM Deployment 앞에 ClusterIP Service를 두고 port-forward 없이 클러스터 내부에서 호출되는지 확인했다. Service vllm-qwen이 ClusterIP 10.43.163.137로 생성됐고, EndpointSlice vllm-qwen-xfzqz에 vLLM Pod IP 10.42.0.21이 연결된 것을 확인했다.
임시 Pod에서 내부 DNS 이름 vllm-qwen.llm.svc.cluster.local로 /v1/models와 Chat Completions를 호출해 모두 정상 응답을 받았다. /v1/models의 root가 Qwen/Qwen2.5-3B-Instruct-AWQ로 표시되어 3B AWQ checkpoint가 실제로 서빙 중임이 함께 확인됐다. 이로써 이전 단계에서 1/1 Ready와 /health 200까지만 확인했던 3B AWQ의 API 동작 검증이 완료됐다.
외부 접근 방식은 이 단계에서 결정하지 않았다.
1. Service 정의
selector는 Deployment가 붙이는 Pod label과 정확히 일치해야 한다. 이 cluster의 Pod는 app=vllm과 version=ver-0을 가진다.
port 이름을 http로 둔 것은 다음 단계의 ServiceMonitor가 port를 숫자가 아니라 이름으로 참조하기 때문이다. 이 이름이 어긋나면 Service가 정상이어도 Prometheus target이 등록되지 않는다.
2. Service와 EndpointSlice 확인
Service는 selector가 어긋나도 정상적으로 생성되고 오류를 내지 않는다. 실제로 Pod가 연결됐는지는 EndpointSlice로 확인해야 한다.
kubectl -n llm get svc vllm-qwen -o widekubectl -n llm get endpointslices -l kubernetes.io/service-name=vllm-qwen
Service와 EndpointSlice 조회 결과
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTORvllm-qwen ClusterIP 10.43.163.137 <none> 8000/TCP 6m37s app=vllm,version=ver-0NAME ADDRESSTYPE PORTS ENDPOINTS AGEvllm-qwen-xfzqz IPv4 8000 10.42.0.21 6m37s
key
값
해석
CLUSTER-IP
10.43.163.137
고정된 내부 주소 확보
EXTERNAL-IP
<none>
외부 노출 없음
SELECTOR
app=vllm,version=ver-0
대상 Pod 선택 조건
EndpointSlice
vllm-qwen-xfzqz
자동 생성된 endpoint 목록
ENDPOINTS
10.42.0.21
vLLM Pod IP가 연결됨
ENDPOINTS에 주소가 채워졌다는 것이 selector와 Pod label이 일치한다는 직접 근거다. 비어 있었다면 Service는 존재하지만 트래픽이 어디로도 가지 않는 상태이며, 이때 원인을 vLLM에서 찾기 시작하면 한참 돌아가게 된다.
kubectl get endpoints는 지원 중단 경고를 낸다
core/v1 Endpoints는 Kubernetes 1.33부터 deprecated이고 discovery.k8s.io/v1 EndpointSlice가 표준이다. 예전 명령을 써도 값은 정상이며 하위 호환을 위해 Endpoints 객체도 계속 생성되지만 아래 경고가 붙는다.
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
3. selector와 Pod label 대조
kubectl -n llm get svc vllm-qwen -o jsonpath='{.spec.selector}{"\n"}'kubectl -n llm get pod --show-labels
selector와 Pod label 조회 결과
{"app":"vllm","version":"ver-0"}NAME READY STATUS RESTARTS AGE LABELSvllm-qwen-865f7b887b-gqkfz 1/1 Running 0 26m app=vllm,pod-template-hash=865f7b887b,version=ver-0
Pod에는 ReplicaSet이 붙이는 pod-template-hash가 추가로 존재하지만, selector는 지정한 label을 모두 포함하는지만 보므로 매칭에 영향을 주지 않는다.
selector에 version이 포함되어 있다
현재 selector는 app=vllm,version=ver-0이라 version 값이 다른 Pod는 이 Service의 대상이 되지 않는다. blue/green이나 수동 canary에서는 이 성질을 이용하지만, 단순 교체를 의도하고 새 version label로 배포하면 Pod가 Ready인데도 트래픽이 가지 않는 상태가 된다.
{"id":"chatcmpl-8605b3e9b1e18dfd","object":"chat.completion","created":1785720881,"model":"qwen2.5-3b","choices":[{"index":0,"message":{"role":"assistant","content":"알겠습니다. 짧게 답변하겠습니다.","refusal":null,"annotations":null,"audio":null,"function_call":null,"reasoning":null},"logprobs":null,"finish_reason":"stop","stop_reason":null,"token_ids":null,"routed_experts":null}],"service_tier":null,"system_fingerprint":"vllm-0.26.0-b0b11f7d","usage":{"prompt_tokens":37,"total_tokens":47,"completion_tokens":10,"prompt_tokens_details":null},"prompt_logprobs":null,"prompt_token_ids":null,"prompt_text":null,"kv_transfer_params":null,"ec_transfer_params":null,"metrics":null}
key
값
해석
choices[0].message.content
알겠습니다. 짧게 답변하겠습니다.
3B AWQ 모델의 실제 응답
finish_reason
stop
토큰 상한이 아니라 정상 종료
usage.prompt_tokens
37
입력 토큰
usage.completion_tokens
10
생성 토큰
usage.total_tokens
47
합계
system_fingerprint
vllm-0.26.0-b0b11f7d
응답을 생성한 vLLM build
finish_reason이 stop이므로 max_tokens=64 상한에 걸린 것이 아니라 모델이 스스로 응답을 마쳤다. 이 호출로 Service → EndpointSlice → Pod → vLLM 경로가 끝까지 이어진 것을 확인했으며, port-forward는 사용하지 않았다.
5. 확인된 범위와 남은 결정
항목
상태
ClusterIP 고정 endpoint
확보
EndpointSlice Pod 연결
확인
내부 DNS /v1/models
확인
내부 DNS Chat Completions
확인
3B AWQ checkpoint 서빙
확인
ServiceMonitor 선행 조건
충족
외부 접근은 결정하지 않았다. vLLM의 OpenAI 호환 API는 기본적으로 인증이 없어, 공개 IP에 노출하면 누구나 GPU를 사용할 수 있고 시간당 과금이 그대로 발생한다.
선택지
상태
비고
SSH 터널
미실행
방화벽을 열지 않아도 됨
NodePort와 방화벽 소스 IP 제한
미실행
실험 종료 후 규칙 삭제 필요
Tailscale 서브넷 라우터
미실행
ClusterIP를 tailnet에서 직접 호출
LoadBalancer
검토 대상 아님
고정 IP 비용과 인증 계층이 함께 필요
Service 작업에서 실제로 시간이 걸리는 지점은 manifest 작성이 아니라 selector와 Pod label의 일치 확인이었다. EndpointSlice에 주소가 채워졌는지 먼저 보는 것이 가장 빠른 확인 경로다.