LabHub
배우기 러닝패스 코스

GPU Operator 와 타임슬라이싱 · GPU 노드라는 사실은 라벨로 적혀 있다 · 이론

Node Feature Discovery — 발견 결과가 라벨이 되기까지

LabHub 에서 이어서 보기

한 줄 요약

GPU Operator 는 노드에 꽂힌 카드를 직접 보지 않고 노드 라벨을 보며, 그 라벨은 Node Feature Discovery(NFD)와 GPU Feature Discovery(GFD)와 오퍼레이터 자신이 각각 나눠 붙인다. 그래서 라벨이 없으면 오류도 경고도 없이 그냥 아무 일도 일어나지 않는다.

왜 이게 필요했나

GPU 노드를 다루는 소프트웨어는 최소한 세 가지를 알아야 한다. 이 노드에 NVIDIA 카드가 있는가, 몇 장이고 어떤 모델인가, 드라이버는 어느 판인가. 이걸 알아내는 방법은 원래 노드에 들어가서 lspci 를 치고 nvidia-smi 를 돌리는 것이었다. 노드가 셋이면 할 만하고 삼백이면 불가능하다.

쿠버네티스가 이 문제에 준 답은 "하드웨어 사실을 노드 오브젝트의 라벨로 올려 두자" 였다. 한 번 라벨이 되고 나면 그 뒤는 전부 쿠버네티스의 일상 도구로 풀린다. 스케줄러의 nodeSelector·nodeAffinity 가 그 라벨을 읽고, 데몬셋의 nodeSelector 가 그 라벨로 배치를 정하고, 사람은 kubectl get node -L 로 한눈에 본다. 하드웨어를 조회하는 문제가 라벨을 조회하는 문제로 바뀐 것이 이 설계의 전부다.

NFD 는 이 일을 하는 쿠버네티스 SIG 프로젝트다. 구조는 둘로 나뉜다. 각 노드에서 데몬셋으로 도는 nfd-worker 가 CPU·커널·PCI·USB·메모리·저장장치·네트워크 같은 갈래로 사실을 모아 보고하고, nfd-master 가 그 보고를 받아 노드 오브젝트에 라벨을 쓴다. 워커가 직접 노드를 고치지 않고 마스터를 거치게 한 것은 노드 오브젝트를 고칠 권한을 한 곳에 모으기 위해서다.

어떻게 동작하나

라벨은 접두사로 출처가 드러난다. 이 한 가지만 기억해도 화면을 읽는 속도가 달라진다.

| 접두사 | 붙이는 주체 | 예 |
| --- | --- | --- |
| feature.node.kubernetes.io/ | nfd-worker 의 보고를 받은 nfd-master | pci-10de.present, kernel-version.full, system-os_release.ID |
| nvidia.com/gpu.*, nvidia.com/cuda.* | gpu-feature-discovery | gpu.product, gpu.count, gpu.memory, cuda.driver.major |
| nvidia.com/gpu.deploy.* | GPU Operator | gpu.deploy.driver, gpu.deploy.container-toolkit |

NVIDIA 문서는 GPU 워커 노드를 feature.node.kubernetes.io/pci-10de.present=true 라벨의 존재로 식별한다고 적고 있다. 0x10de 는 NVIDIA 에 배정된 PCI 벤더 ID 다. NFD 의 PCI 라벨은 기본적으로 <class>_<vendor> 형식의 장치 이름을 쓰지만, GPU Operator 는 벤더만 보도록 설정해 쓴다.

GFD 가 붙이는 라벨은 더 구체적이다. 공식 문서의 출력 예를 그대로 옮기면 이런 모양이다.

{  "nvidia.com/cuda.driver.major": "450",  "nvidia.com/cuda.driver.minor": "80",  "nvidia.com/cuda.driver.rev": "02",  "nvidia.com/cuda.runtime.major": "11",  "nvidia.com/cuda.runtime.minor": "0",  "nvidia.com/gpu.compute.major": "8",  "nvidia.com/gpu.count": "1",  "nvidia.com/gpu.family": "ampere",  "nvidia.com/gpu.memory": "40537",  "nvidia.com/gpu.product": "A100-SXM4-40GB"}

드라이버 판이 major·minor·rev 세 조각으로 쪼개져 있는 것에 주목할 만하다. 라벨 하나에 450.80.02 를 통째로 넣지 않은 이유는 비교 때문이다. nodeAffinity 의 Gt·Lt 연산자는 값을 정수로 읽으므로, 조각으로 나눠 두어야 "드라이버 550 이상인 노드" 같은 조건을 적을 수 있다.

라벨 값에는 문법 제약이 있다. 63자 이하, 영숫자로 시작하고 끝나며, 가운데에는 -·_·. 만 올 수 있다. 그런데 드라이버가 알려 주는 장치 이름은 NVIDIA A100-SXM4-40GB 처럼 공백이 들어 있다. 그대로 넣으려 하면 API 서버가 Invalid value 로 거절한다. 그래서 GFD 는 이름을 다듬어 붙이고, 우리가 화면에서 보는 NVIDIA-A100-SXM4-40GB 는 그 정규화의 결과다. 이 사실을 모르면 "문서에 적힌 모델 이름으로 조건을 걸었는데 아무 노드도 안 맞는다" 는 자리에서 한참을 보낸다.

라벨을 읽는 쪽도 규칙이 있다. nodeSelector 는 키와 값이 정확히 같은지만 보지만, nodeAffinityIn·NotIn·Exists·DoesNotExist·Gt·Lt 를 쓸 수 있다. 여기에 사람들이 자주 틀리는 구조가 하나 있다.

그리고 NotIn그 키가 아예 없는 노드도 통과시킨다. "A10 만 빼고 GPU 노드에" 를 NotIn 하나로 적으면 GPU 가 없는 노드까지 후보가 된다. Exists 를 함께 걸어 범위를 먼저 좁히는 것이 관용구가 된 이유다.

이름 끝의 IgnoredDuringExecution 도 그냥 붙은 말이 아니다. requiredDuringSchedulingIgnoredDuringExecution스케줄할 때만 조건을 요구하고, 이미 뜬 파드는 조건이 깨져도 쫓아내지 않는다. 그래서 라벨을 잘못 지워도 돌던 워크로드는 멀쩡하고, 다음에 새로 뜨는 파드부터 조용히 Pending 이 된다. 사고가 몇 시간 뒤에 드러나는 구조다.

라벨의 소유권

NFD 가 붙인 라벨은 NFD 의 것이다. 워커가 주기적으로 다시 보고하고 마스터가 노드를 그 보고에 맞추므로, 사람이 손으로 고친 값은 다음 주기에 조용히 되돌아간다. 처음 겪으면 "라벨이 자꾸 살아난다" 는 유령을 쫓게 되지만, 알고 보면 당연한 동작이다 — 발견된 사실을 사람이 덮어쓸 수 있으면 그 라벨을 믿을 이유가 없어진다.

그래서 사람이 정하는 사실은 다른 키에 적는다. 팀 소유, 워크로드 등급, 유지보수 창구 같은 것은 자기 회사 접두사를 쓴 라벨에 적고, 발견 라벨은 읽기만 한다. 반대로 NFD 에게 사람의 규칙을 시키고 싶으면 NodeFeatureRule 로 규칙을 선언해 NFD 가 붙이게 만든다 — 그러면 그 라벨도 NFD 의 것이 되어 일관되게 관리된다.

현장에서 만나는 모습

첫째, 아무 일도 안 일어난다. GPU Operator 를 설치했는데 오퍼랜드가 한 개도 안 뜨는 신고의 가장 흔한 원인은 pci-10de.present 라벨이 없는 것이다. NFD 를 따로 설치했다가 라벨 접두사 설정이 달라졌거나, 노드가 새로 들어왔는데 nfd-worker 데몬셋이 그 노드에 못 떴거나. 어느 쪽이든 오류 로그는 없다. 조건이 맞지 않으면 배치가 일어나지 않고, 일어나지 않은 일은 로그를 남기지 않기 때문이다.

둘째, 모델 이름이 미묘하게 다르다. nvidia.com/gpu.product 는 GFD 의 정규화를 거친 값이고 MIG 를 켜면 NVIDIA-H100-80GB-HBM3-MIG-1g.10gb 처럼 접미가 붙는다. 타임슬라이싱을 켜면 또 다른 접미가 붙는다. 그래서 모델 이름으로 조건을 걸 때는 문서의 이름이 아니라 지금 클러스터의 라벨 값을 봐야 한다.

셋째, 라벨 점검기를 갖고 다니는 팀이 빠르다. 노드가 GPU 노드라고 주장하면서 모델·장수·메모리 중 하나가 비어 있거나, 숫자여야 할 값이 숫자가 아니거나 하는 일은 실제로 생긴다. 이걸 사람 눈으로 잡는 대신 작은 스크립트로 잡아 두면, 노드가 새로 들어올 때마다 30초 만에 확인이 끝난다. 중요한 것은 그 스크립트가 노드 목록을 그때그때 받아 오는 것이다. 노드 이름을 박아 둔 점검기는 증설한 날부터 거짓말을 시작한다.

넷째, 이 환경의 정직한 한계. 실습 환경에는 GPU 도 NFD 도 GFD 도 없다. 그래서 '발견' 은 여러분이 라벨로 세운다. 대신 라벨 값 검증, nodeAffinity 평가, 스케줄러의 배치 결정, 노드 추가는 kwok 이 띄운 진짜 API 서버와 진짜 스케줄러가 한다 — 흉내가 아니라 실물이다.

참고 문서

다음 실습에서 할 것

kwok 이 띄운 진짜 API 서버 위에서 세 출처의 라벨을 직접 세운다. 공백이 든 장치 이름을 넣어 API 서버에게 거절당해 보고, 그 이름을 라벨 값으로 다듬는 도구를 만든다. In·NotIn·Exists·Gt 와 term 목록으로 파드가 어느 노드로 가는지 스케줄러에게 직접 확인받고, 라벨을 지운 뒤에도 돌던 파드가 남는 것과 새 파드만 Pending 이 되는 것을 눈으로 본다. 마지막으로 발견 결과를 되돌리는 동기화기와, 새 노드가 들어와도 거짓말하지 않는 라벨 규약 점검기를 만든다.