Code Place에 vLLM 기반 AI 조교 기능을 배포하려면 GPU가 있는 노드에서 추론 서버가 안정적으로 실행되어야 했다. 로컬이나 단일 Docker 환경에서는 GPU가 인식되면 큰 문제가 없을 것처럼 보였지만, k3s 위에서는 전혀 달랐다.
호스트에서 nvidia-smi가 정상으로 보이는데도 Pod 안에서는 CUDA 라이브러리를 찾지 못했다. 어떤 경우에는 스케줄러가 GPU 리소스를 보지 못했고, 어떤 경우에는 RuntimeClass를 지정했는데도 기대한 런타임으로 뜨지 않는 것처럼 보였다.
결국 문제는 "GPU 서버가 있다"가 아니라 "쿠버네티스가 그 GPU를 워크로드에 올바르게 전달하고 있는가"였다. 이 글은 Code Place AI 기능을 k3s 위에서 실행하기 위해 GPU 워크로드 조건을 하나씩 확인한 기록이다.
처음 보인 문제
진행하면서 만난 오류들은 처음에는 서로 다른 문제처럼 보였다.
libcuda.so.1 not foundTriton is installed but 0 active driver(s) foundNo CUDA runtime is foundInsufficient nvidia.com/gpu- RuntimeClass를 지정했는데도 기대한 환경으로 실행되지 않는 상황
처음 보면 어떤 것은 라이브러리 문제 같고, 어떤 것은 드라이버 문제 같고, 어떤 것은 스케줄링 문제처럼 보였다. 그런데 계층별로 확인하니 전부 같은 축으로 정리됐다. 호스트의 GPU 자원이 컨테이너까지 제대로 전달되지 않고 있었다.
원인을 확인한 과정
가장 먼저 한 일은 문제를 하나의 원인으로 단정하지 않는 것이었다. GPU가 보이지 않는다는 결과는 같아도, 실제로 막히는 위치는 여러 군데일 수 있었다.
flowchart TB
Host["Host Driver\nnvidia-smi"]
Runtime["NVIDIA Container Runtime"]
Containerd["k3s containerd\nruntime handler"]
RuntimeClass["RuntimeClass"]
DevicePlugin["NVIDIA Device Plugin\nnvidia.com/gpu"]
Pod["vLLM Pod"]
Host --> Runtime --> Containerd --> RuntimeClass --> Pod
DevicePlugin --> Pod
호스트 레이어
드라이버가 정상인지, nvidia-smi가 보이는지, CUDA 버전과 드라이버 버전이 최소 조건을 맞추는지 확인했다. 여기서 문제가 있으면 쿠버네티스로 올라가기 전에 이미 막힌다.
컨테이너 런타임 레이어
nvidia-container-runtime이 설치되어 있는지, NVIDIA Container Toolkit이 정상적으로 구성되어 있는지 확인했다. 호스트에서 컨테이너 실행 시 GPU 장치를 넘길 수 있어야 다음 단계로 갈 수 있었다.
k3s / containerd 레이어
k3s가 쓰는 containerd 설정에 NVIDIA runtime이 등록되어 있는지 확인했다. RuntimeClass가 실제 runtime handler를 가리키는지도 함께 확인했다. k3s는 일반 Kubernetes 설치와 다르게 runtime 설정 경로와 반영 방식이 다를 수 있어서, 설정을 어디에 넣었는지도 중요했다.
Kubernetes 리소스 레이어
device plugin이 GPU를 nvidia.com/gpu로 광고하는지 봤다. 스케줄러가 그 리소스를 보고 있어야 Pod의 GPU request/limit가 의미가 있다.
워크로드 레이어
마지막으로 Pod가 올바른 RuntimeClass를 쓰는지, 컨테이너 이미지와 실행 방식이 CUDA 환경을 기대하는 구조와 맞는지, vLLM이 시작 시점에 GPU와 CUDA 라이브러리를 정상 인식하는지 확인했다.
이렇게 나누고 나니 각 에러가 어느 레이어의 신호인지 더 명확하게 구분됐다.
오류를 다시 확인했다
이 오류는 처음 보면 컨테이너 이미지에 CUDA 라이브러리가 빠진 것처럼 보였다. 하지만 쿠버네티스 GPU 환경에서는 꼭 이미지 문제만 뜻하지 않았다.
오히려 더 자주 의미하는 것은 호스트의 NVIDIA 라이브러리와 장치가 컨테이너에 제대로 주입되지 않았다는 점이었다.
그래서 이 메시지를 보고는 아래 순서로 확인했다.
- 해당 Pod가 정말 NVIDIA runtime으로 떠 있는가
- k3s/containerd 설정에 NVIDIA runtime handler가 등록되어 있는가
- RuntimeClass 이름과 handler 이름이 실제 설정과 맞는가
- 컨테이너가 GPU 장치를 인식할 수 있는 상태인가
이 일을 겪은 뒤에는 GPU 관련 에러를 곧바로 애플리케이션 라이브러리 문제로 단정하지 않는다.
해결 방안
RuntimeClass와 device plugin은 둘 다 필요했지만 역할은 달랐다.
RuntimeClass는 Pod를 어떤 런타임 설정으로 실행할지 정하는 단서였다. 호스트에 NVIDIA runtime이 있어도, Pod가 그 런타임을 통해 실행되지 않으면 GPU 장치와 관련 라이브러리를 제대로 받지 못할 수 있었다.
반면 device plugin은 쿠버네티스가 노드 GPU를 자원으로 인식하게 만드는 역할에 가까웠다. device plugin이 없거나 정상 동작하지 않으면 스케줄러는 nvidia.com/gpu 리소스를 제대로 보지 못할 수 있었다.
특히 Insufficient nvidia.com/gpu 같은 메시지는 단순한 GPU 부족으로만 해석하면 안 된다. device plugin이 자원을 광고하지 못하는지, 이미 다른 Pod가 GPU를 점유하고 있는지, 노드 제약 조건이나 taint/toleration이 관련되어 있는지 함께 확인해야 했다.
적용 후 달라진 점
컨테이너가 GPU를 인식하고 vLLM이 실행되기 시작하면 문제가 끝난 것처럼 느껴질 수 있다. 하지만 실제로는 그때부터가 시작이었다. GPU를 인식한다는 것과 추론 서버를 안정적으로 운영할 수 있다는 것은 다른 문제였다.
그 다음에는 vLLM 설정, 메모리 사용량, 동시 요청 처리, 컨텍스트 길이 같은 문제가 다시 나타났다. 다만 이번 작업에서는 그 이전 단계인 GPU 추론 워크로드가 쿠버네티스 위에서 실행되기 위한 조건을 정리한 점이 더 중요했다.
정리한 기준
이후로는 GPU 추론 서버를 확인하는 기준이 달라졌다. 예전에는 GPU가 있는 서버에서 Docker로 모델이 실행되면 큰 단계 하나는 넘었다고 생각했지만, 지금은 그렇게 판단하지 않는다.
실제로 우선 확인하게 된 것은 이런 것들이다.
- GPU 워크로드를 특정 노드에 안정적으로 배치할 수 있는가
- runtime 설정이 재시작 후에도 일관되게 유지되는가
- 스케줄러가 GPU 자원을 제대로 이해하고 있는가
- Pod가 어떤 조건을 맞춰야 GPU를 쓰는지 설명할 수 있는가
쿠버네티스에서 GPU를 쓴다는 말은 여러 전제를 포함했다. GPU가 있는 노드가 있다는 사실, 호스트에서 nvidia-smi가 된다는 사실, Pod가 Running이라는 사실은 모두 필요하지만 충분조건은 아니었다.
마무리
이번 경험은 vLLM을 실행했다는 결과보다, 그 경로를 하나씩 확인하며 GPU 추론 워크로드가 동작하기 위한 조건을 정리했다는 점이 더 의미 있었다.
이후에는 GPU 관련 문제가 발생하면 애플리케이션 로그만 확인하지 않는다. 호스트, runtime, k3s containerd, device plugin, Pod spec을 순서대로 분리해서 확인한다. 이 순서가 생긴 뒤에는 비슷한 문제를 훨씬 빠르게 좁힐 수 있었다.