- 들어가며 — 부검을 시작하며
- 1부 — 증상: 112일째 멈춰 있는 VM
- 2부 — GPU는 제대로 등록돼 있었다
- 3부 — 진짜 원인: kubelet 한 줄
- 4부 — 패스스루의 물리 법칙: 왜 "다른 노드로" 못 가나
- 5부 — 복구: 라벨 먼저, 그 다음 swap
- 마치며 — 부검의 교훈
- 참고 자료
들어가며 — 부검을 시작하며
앞 편에서 Rust로 짠 GPU 오퍼레이터가 뜻밖의 진단을 내렸습니다: GPU 노드 4개가 전부 NotReady, 즉 클러스터의 GPU 능력이 통째로 죽어 있다. 이 글은 그 죽음의 원인을 실제 클러스터에서 끝까지 추적한 부검 기록입니다. 대상은 8노드 홈랩(GPU Operator v25.3.0, KubeVirt v1.7.0)이고, 증거는 전부 실측입니다. 이 글의 배경 개념이 필요하면 GPU Operator × KubeVirt 총정리 편을 먼저 읽으면 좋습니다.
1부 — 증상: 112일째 멈춰 있는 VM
먼저 KubeVirt VM들의 상태를 봤습니다. 진짜 클러스터의 VM 위상 분포입니다:
$ kubectl get vm -A
위상 개수 비고
──────────────── ──── ─────────────────────────────
Running 3
Paused 2
Stopped 1
ErrorUnschedulable 2 ← gpu-fedora, rhel9-gpu-vm (둘 다 GPU VM!)
gpu-fedora와 rhel9-gpu-vm — GPU를 요구하는 VM 둘만 정확히 ErrorUnschedulable입니다. 그리고 얼마나 오래? kubectl get vm의 AGE는 112일. 112일 동안 스케줄되지 못한 채 방치돼 있었습니다. GPU를 안 쓰는 VM들(Running·Paused)은 멀쩡합니다. 범인은 GPU 경로 어딘가입니다.
GPU Operator 오퍼랜드 파드들의 상태도 건강하지 않았습니다:
$ kubectl get pods -n gpu-operator (상태 히스토그램)
Running 14
Error 6 ← sandbox-device-plugin, sandbox-validator 등
Terminating 4
2부 — GPU는 제대로 등록돼 있었다
흥미로운 반전: GPU 설정 자체는 흠잡을 데가 없었습니다. KubeVirt CR의 permittedHostDevices를 보면 5090과 3090이 PCI ID까지 정확히 등록돼 있습니다:
$ kubectl get kubevirt -n kubevirt -o yaml (permittedHostDevices 발췌)
pciHostDevices:
- pciVendorSelector: "10de:2b85" # NVIDIA GB202 = RTX 5090
resourceName: nvidia.com/GB202_GEFORCE_RTX_5090
externalResourceProvider: true
- pciVendorSelector: "10de:2204" # NVIDIA GA102 = RTX 3090
resourceName: nvidia.com/GA102_GEFORCE_RTX_3090
externalResourceProvider: true
sandboxWorkloads.enabled: true, driver.enabled: false(호스트 드라이버 사용)까지, VM에 GPU를 넘기기 위한 판은 다 깔려 있었습니다. 그런데도 VM은 안 떴습니다. 설정이 옳아도 노드가 없으면 소용없다 — 이게 부검의 방향을 바꿨습니다.
3부 — 진짜 원인: kubelet 한 줄
GPU VM들이 스케줄될 수 있는 유일한 노드(omen2, RTX 5090 탑재)로 내려가 kubelet 로그를 봤습니다. 범인은 화려한 GPU 문제가 아니라 이 한 줄이었습니다:
$ journalctl -u kubelet -n 5 (omen2)
E0710 15:39:51 run.go:72] "command failed" err="failed to run Kubelet:
running with swap on is not supported, please disable swap
or set --fail-swap-on flag to false"
systemd[1]: kubelet.service: Main process exited, code=exited, status=1/FAILURE
systemctl is-active kubelet → activating(즉 crash-loop). 누군가 이 노드에 32GB swapfile을 켰고, 쿠버네티스 kubelet은 기본적으로 swap이 켜져 있으면 뜨기를 거부합니다. kubelet이 죽으니 노드가 NotReady가 되고, NFD가 GPU 라벨을 지우고, device-plugin이 nvidia.com/gpu를 광고하지 못하고 — 그 위에 얹힌 모든 게 무너진 겁니다.
인과 사슬을 한 줄로 그리면 이렇습니다:
swap on → kubelet 거부(crash-loop) → 노드 NotReady
→ NFD 라벨 소멸 + device-plugin 중단
→ GPU VM 스케줄 불가(ErrorUnschedulable, 112일)
4부 — 패스스루의 물리 법칙: 왜 "다른 노드로" 못 가나
일반 파드라면 노드 하나가 죽어도 스케줄러가 다른 노드로 옮깁니다. GPU 패스스루 VM은 왜 못 옮길까요? 물리 장치에 묶여 있기 때문입니다. gpu-fedora는 nvidia.com/GB202_GEFORCE_RTX_5090이라는 리소스를 요구하는데, 그 물리 5090은 오직 omen2에만 꽂혀 있습니다. 그 노드가 죽으면 VM이 갈 곳이 없습니다.
이건 버그가 아니라 설계입니다 — 제가 GPU Operator × KubeVirt 편에서 "GPU 패스스루된 VM은 라이브 마이그레이션이 안 된다, 이건 버전과 무관한 물리 법칙"이라고 썼던 그 문장의 실물입니다. 컨테이너 GPU(MIG로 쪼개 여러 파드가 공유)와 달리, VM 패스스루는 GPU 한 장 = 노드 한 대에 강하게 결합됩니다. 밀도를 포기하는 대신 강한 격리를 얻는 트레이드오프의 그림자입니다.
5부 — 복구: 라벨 먼저, 그 다음 swap
복구에는 함정이 하나 더 있었습니다. omen2의 워크로드 라벨은 nvidia.com/gpu.workload.config=vm-passthrough였습니다. 이 상태로 그냥 kubelet을 살리면 GPU Operator가 vfio-manager를 띄워 5090을 vfio-pci 드라이버로 재바인딩합니다 — 그러면 그 노드에서 돌던 모든 CUDA/torch 작업이 즉시 죽습니다(이 GPU는 작은 모델 실험 편에서 쓰던 바로 그 카드입니다).
그래서 복구 순서가 중요합니다:
① (kubelet 살리기 전에) 라벨을 container로 바꿔 vfio 재바인딩을 막는다
kubectl label node omen2 nvidia.com/gpu.workload.config=container --overwrite
② swap을 끄고 영구화
sudo swapoff -a
sudo sed -i '/swap/s/^/#/' /etc/fstab
③ kubelet 재시작 → 노드 Ready, 5090이 nvidia.com/gpu로 파드에 노출
sudo systemctl restart kubelet
①을 먼저 하는 이유가 핵심입니다 — driver.enabled: false라 GPU Operator는 호스트 드라이버를 그대로 쓰므로, container 라벨이면 호스트의 nvidia 드라이버를 유지한 채 GPU를 파드에 노출합니다. 즉 torch도 살고 k8s도 GPU를 봅니다. (①은 이미 적용해 뒀고, ②③은 sudo가 필요해 노드 소유자의 손이 필요합니다.)
마치며 — 부검의 교훈
가장 큰 교훈은 진부하지만 매번 새롭습니다: 화려한 레이어의 장애가 대개 지루한 레이어에서 온다. GPU 패스스루·vfio·permittedHostDevices·PCI ID까지 완벽했지만, 무너뜨린 건 swap on 한 줄이었습니다. 두 번째 교훈은 패스스루의 물리학 — VM에 GPU를 통째로 주면 그 VM은 그 노드에 못 박힙니다. 밀도가 필요하면 컨테이너+MIG, 격리가 필요하면 VM 패스스루, 그 선택의 대가는 "노드가 죽으면 같이 죽는다"입니다. 그리고 이 모든 진단의 출발점이 내가 Rust로 짠 20줄짜리 오퍼레이터였다는 것 — 인프라는 직접 만져 본 만큼만 보입니다.
참고 자료
현재 단락 (1/53)
[앞 편](/blog/2026-07-11-rust-k8s-gpu-operator)에서 Rust로 짠 GPU 오퍼레이터가 뜻밖의 진단을 내렸습니다: **GPU 노드 4개가 전부 No...