LabHub
배우기 러닝패스 코스

GPU Operator 와 타임슬라이싱 · 오퍼랜드는 라벨이 켜는 만큼만 뜬다 · 이론

오퍼레이터와 오퍼랜드 — 데몬셋 여섯 개를 노드마다 켜고 끄는 법

LabHub 에서 이어서 보기

한 줄 요약

GPU Operator 가 띄우는 파드 열몇 개는 하나의 프로그램이 아니라 오퍼레이터가 ClusterPolicy 를 읽고 펼친 데몬셋 여러 개이고, 각 데몬셋은 nvidia.com/gpu.deploy.<이름> 라벨을 nodeSelector 로 보기 때문에 노드 단위로 켜고 끌 수 있다.

왜 이게 필요했나

쿠버네티스에서 GPU 를 쓰려면 노드에 갖춰져야 하는 것이 여럿이다. 커널 모듈을 올리는 드라이버, 컨테이너 런타임에 GPU 런타임을 등록하는 컨테이너 툴킷, kubelet 에게 nvidia.com/gpu 자원을 알리는 장치 플러그인, 하드웨어 사실을 라벨로 바꾸는 GPU Feature Discovery, 지표를 내보내는 DCGM Exporter, 설치가 제대로 됐는지 확인하는 검증기, MIG 를 쓰면 MIG 매니저까지.

이걸 손으로 하면 노드마다 순서를 지켜 가며 설치·업그레이드·롤백을 해야 한다. 오퍼레이터 패턴은 여기에 "원하는 상태를 한 오브젝트에 적어 두면 컨트롤러가 맞춰 준다" 를 넣었다. 그 오브젝트가 ClusterPolicy 이고, 컨트롤러가 그것을 읽어 만들어 내는 것들이 오퍼랜드 다.

용어를 한 번 정리하면 나머지가 쉬워진다. 오퍼레이터는 ClusterPolicy 를 지켜보는 컨트롤러 파드 하나다. 오퍼랜드는 오퍼레이터가 만들어 관리하는 워크로드들, 즉 위에서 나열한 데몬셋들이다. 사고가 났을 때 "오퍼레이터가 죽었다" 와 "오퍼랜드가 안 떴다" 는 완전히 다른 문제이고, 볼 곳도 다르다.

어떻게 동작하나

오퍼랜드는 거의 전부 데몬셋이다. 데몬셋은 "모든 노드에 하나씩" 이 아니라 "조건에 맞는 노드마다 하나씩" 이고, 그 조건이 nodeSelector 다. GPU Operator 는 여기에 자기 라벨을 걸어 둔다.

| 오퍼랜드 | 켜는 라벨 |
| --- | --- |
| 드라이버 | nvidia.com/gpu.deploy.driver |
| 컨테이너 툴킷 | nvidia.com/gpu.deploy.container-toolkit |
| 장치 플러그인 | nvidia.com/gpu.deploy.device-plugin |
| GPU Feature Discovery | nvidia.com/gpu.deploy.gpu-feature-discovery |
| DCGM Exporter | nvidia.com/gpu.deploy.dcgm-exporter |
| 검증기 | nvidia.com/gpu.deploy.operator-validator |

이 구조에서 나오는 실무 손잡이가 셋이다.

하나, 노드 전체를 오퍼랜드에서 뺀다. 공식 문서는 nvidia.com/gpu.deploy.operands=false 라벨로 그 노드에 오퍼랜드가 배치되지 않게 하는 방법을 적고 있다.

kubectl label nodes $NODE nvidia.com/gpu.deploy.operands=false

둘, 오퍼랜드 하나만 뺀다. 드라이버만 올리지 않으려면 nvidia.com/gpu.deploy.driver=false 다. 라벨을 false 로 두는 것과 아예 지우는 것은 사람에게는 다르게 읽히지만 nodeSelector 에게는 똑같다 — 값이 정확히 "true" 가 아니면 조건이 맞지 않는다. 다만 false 라고 적어 두는 쪽이 "일부러 뺐다" 를 남기므로 운영에서는 이쪽을 쓴다.

셋, 클러스터 전체에서 오퍼랜드를 끈다. 이건 라벨이 아니라 ClusterPolicy(또는 Helm 값) 쪽이다. 드라이버가 호스트에 이미 깔려 있는 환경이면 driver.enabled=false, GPU 런타임이 이미 등록되어 있으면 toolkit.enabled=false 다.

라벨을 내리면 데몬셋 컨트롤러가 그 노드의 파드를 지운다. 데몬셋을 고친 적이 없는데 파드가 사라지는 이 동작이 처음에는 놀랍지만, 컨트롤러가 조건과 실제를 계속 맞추고 있다는 것을 알면 당연하다. 반대로 라벨을 올리면 몇 초 안에 파드가 생긴다.

순서가 있다

오퍼랜드들은 서로를 전제한다. 대략 이런 사슬이다.

드라이버      →  커널 모듈이 올라가고 /dev 에 장치가 생긴다컨테이너 툴킷  →  런타임에 nvidia 런타임이 등록된다장치 플러그인  →  kubelet 에 nvidia.com/gpu 자원을 광고한다GFD           →  모델·장수·드라이버 판을 라벨로 붙인다검증기        →  앞의 것들이 실제로 되는지 확인한다

그래서 라벨을 손으로 붙일 때 사슬을 깨뜨리는 조합이 만들어진다. 가장 고약한 것이 툴킷 없이 장치 플러그인만 도는 노드다. 장치 플러그인은 자원을 광고하고, 스케줄러는 그 숫자를 보고 파드를 보내고, 파드는 스케줄에 성공한다. 그런데 런타임이 GPU 를 컨테이너에 붙여 주지 않으니 워크로드만 장치를 못 잡는다. 어디에서도 오류가 나지 않는데 결과만 틀린 고장이라, 이런 조합을 찾아내는 점검을 갖고 있느냐가 대응 시간을 가른다.

현장에서 만나는 모습

첫째, "GPU 노드인데 파드가 안 떠요". 확인 순서는 라벨 → 데몬셋 desiredNumberScheduled → 그 노드의 파드다. 세 자리가 각각 다른 것을 말한다. 라벨이 없으면 아무 일도 안 일어난 것이고, 라벨은 맞는데 desiredNumberScheduled 가 낮으면 셀렉터나 테인트가 어긋난 것이고, 숫자는 맞는데 파드가 없으면 그 노드에서 못 뜬 것이다.

둘째, 숫자만 보면 속는다. desiredNumberScheduled데몬셋 컨트롤러가 떠야 한다고 판단한 노드 수다. 톨러레이트되지 않는 테인트가 있는 노드는 아예 후보에서 빠지므로 이 숫자에 잡히지도 않는다. 그래서 "있어야 할 오퍼랜드가 다 있는가" 를 판정할 때는 숫자가 아니라 그 노드에서 실제로 Running 인 파드를 봐야 한다.

셋째, 노드 한 대만 격리하는 손버릇. GPU 하나가 이상한 노드가 나오면 노드를 통째로 cordon 하는 대신 오퍼랜드만 내리는 선택지가 있다. 라벨 한 줄로 그 노드의 오퍼랜드를 멈추고, 워크로드는 자원이 광고되지 않으니 자연히 다른 노드로 간다. 클러스터를 흔들지 않고 한 대만 떼어 내는 방법이다.

넷째, 드라이버가 이미 깔린 노드. 온프레미스에서 이미지에 드라이버를 구워 두는 팀이 많다. 그런 노드에 드라이버 오퍼랜드가 뜨면 커널 모듈을 두 번 올리려 들어 좋을 것이 없다. 클러스터 전체가 그렇다면 driver.enabled=false, 일부 노드만 그렇다면 그 노드에 nvidia.com/gpu.deploy.driver=false. 공식 문서는 GPU Operator 가 호스트에 미리 설치된 드라이버의 수명 주기는 관리하지 않는다고 분명히 적고 있다 — 그 노드의 드라이버 업그레이드는 여러분의 몫이라는 뜻이다.

다섯째, 이 환경의 정직한 한계. 실습 환경에는 GPU 도 GPU Operator 도 없어 ClusterPolicy 를 적용할 수 없다. 그래서 오퍼레이터가 펼쳤을 데몬셋을 직접 쓴다. 대신 데몬셋 컨트롤러와 스케줄러는 진짜라, 라벨에 따라 파드가 생기고 사라지는 것과 테인트가 있는 노드가 후보에서 빠지는 것은 흉내가 아니라 실물이다. 드라이버가 정말 설치됐는지는 볼 수 없고, 그래서 보지도 않는다.

참고 문서

다음 실습에서 할 것

오퍼레이터가 펼쳤을 데몬셋 다섯 개를 라벨로 직접 배치한다. GPU 노드가 둘인데 드라이버만 한 노드에 뜨는 것을 보고, 라벨 하나를 내려 파드가 사라지는 것을 확인하고, 라벨만 손으로 붙여 툴킷 없이 장치 플러그인만 도는 노드를 일부러 만든다. 그리고 '있어야 할 오퍼랜드가 다 있는가' 와 '순서가 어긋난 노드가 있는가' 를 판정하는 도구를 만들어, 노드가 하나 더 들어와도 거짓말하지 않는지 확인받는다.