LabHub
배우기 러닝패스 코스

GPU Operator 와 타임슬라이싱 · GPU 는 무엇으로 보나 · 이론

DCGM Exporter 의 지표와 그 지표가 말하지 않는 것

LabHub 에서 이어서 보기

한 줄 요약

GPU 관측의 재료는 DCGM Exporter 가 /metrics 로 내놓는 평문 텍스트이고, 그 텍스트에서 가장 중요한 것은 값이 아니라 라벨이다. 라벨이 지표를 카드와 호스트와 파드에 이어 주며, 그 이음이 있어야 비로소 "할당은 다 나갔는데 아무도 안 쓰고 있다" 는 문장을 숫자로 증명할 수 있다.

왜 이게 필요했나

GPU 는 조직에서 가장 비싼 자원인데, 가장 안 보이는 자원이기도 하다. cpu·memory 는 kubelet 이 스스로 세어 주고 kubectl top 으로 바로 보이는데, GPU 는 쿠버네티스가 사용률을 모른다. 쿠버네티스가 아는 것은 "이 파드에 한 장을 배정했다" 뿐이고, 그 한 장이 100%로 돌고 있는지 꺼져 있는지는 관심 밖이다.

그래서 아주 흔한 상태가 생긴다. 노드의 nvidia.com/gpu allocatable 이 전부 소진되어 새 잡은 계속 Pending 인데, 정작 카드들의 실제 사용률은 한 자릿수인 상태다. 스케줄러 관점에서 이 클러스터는 꽉 찬 클러스터이고, 전기와 감가상각 관점에서는 거의 놀고 있는 클러스터다. 두 숫자가 다른 시스템에 살고 있어서 아무도 나란히 보지 않으면 이 상태는 몇 달씩 간다.

할당률은 쿠버네티스 API 에서 나오고 사용률은 DCGM 에서 나온다. 이 모듈이 하려는 일은 그 둘을 같은 화면에 올리는 것이다.

어떻게 동작하나

DCGM Exporter 는 노드마다 데몬셋으로 뜨고, NVIDIA 의 DCGM 라이브러리로 카드에서 값을 읽어 프로메테우스 노출 형식으로 내놓는다. 공식 문서에 실린 실제 출력은 이렇게 생겼다.

# HELP DCGM_FI_DEV_GPU_TEMP GPU temperature (in C).# TYPE DCGM_FI_DEV_GPU_TEMP gaugeDCGM_FI_DEV_GPU_TEMP{gpu="0",UUID="GPU-34319582-d595-d1c7-d1d2-179bcfa61660",device="nvidia0",Hostname="ub20-a100-k8s"} 61DCGM_FI_DEV_FB_FREE{gpu="0",UUID="GPU-34319582-d595-d1c7-d1d2-179bcfa61660",device="nvidia0",Hostname="ub20-a100-k8s"} 35690DCGM_FI_DEV_FB_USED{gpu="0",UUID="GPU-34319582-d595-d1c7-d1d2-179bcfa61660",device="nvidia0",Hostname="ub20-a100-k8s"} 4845DCGM_FI_DEV_XID_ERRORS{gpu="0",UUID="GPU-34319582-d595-d1c7-d1d2-179bcfa61660",device="nvidia0",Hostname="ub20-a100-k8s"} 0DCGM_FI_PROF_GR_ENGINE_ACTIVE{gpu="0",UUID="GPU-34319582-d595-d1c7-d1d2-179bcfa61660",device="nvidia0",Hostname="ub20-a100-k8s"} 0.995630

여기서 붙잡을 것이 셋이다.

첫째, 이름은 DCGM 필드 이름 그대로다. DCGM_FI_DEV_ 로 시작하는 것은 장치 수준의 값(온도·메모리·XID 오류·클럭)이고, DCGM_FI_PROF_ 로 시작하는 것은 프로파일링 값(그래픽 엔진 활성 비율, 텐서 파이프 활성 비율, DRAM 활성 비율)이다. 사용률을 보려고 흔히 쓰는 DCGM_FI_DEV_GPU_UTIL 은 "샘플링 순간에 커널이 돌고 있었는가" 에 가까운 거친 숫자이고, DCGM_FI_PROF_GR_ENGINE_ACTIVE 는 그 구간 동안 엔진이 실제로 얼마나 활성이었는가를 0에서 1 사이 비율로 준다. 한 자리를 오래 놀게 만드는 잡은 앞 숫자로는 바빠 보이고 뒤 숫자로는 한가해 보인다. 용량 계획을 할 때 어느 숫자를 쓰는지가 결론을 바꾼다.

둘째, 메모리는 두 지표로 나온다. DCGM_FI_DEV_FB_USEDDCGM_FI_DEV_FB_FREE 다(단위는 MiB). 전체 메모리 지표를 따로 찾지 말고 둘을 더해서 총량을 얻는 것이 보통이다. 이 둘이 중요한 이유는 GPU 장애의 큰 몫이 "사용률은 0인데 메모리는 잡혀 있다" 는 모양으로 나타나기 때문이다. 죽은 프로세스가 컨텍스트를 놓지 않은 것이고, 그 카드는 살아 있는 채로 아무에게도 안 돌아간다.

셋째, 라벨이 이 텍스트의 절반이다. gpu 는 노드 안에서의 인덱스, UUID 는 카드의 전역 식별자, device 는 장치 노드 이름, Hostname 은 노드다. 여기에 exporter 의 쿠버네티스 매핑을 켜면(환경변수 DCGM_EXPORTER_KUBERNETES, 플래그 -k) 그 카드를 쥐고 있는 파드 정보가 라벨로 더 붙는다. 이 매핑이 있어야 "누가 이 카드를 잡고 있나" 라는 질문에 답할 수 있고, 없으면 지표는 카드 이야기만 할 뿐 사람 이야기를 못 한다.

MIG 를 켜면 같은 지표가 카드 수준과 GPU 인스턴스 수준 양쪽으로 나오고, 인스턴스 줄에는 GPU_I_PROFILE·GPU_I_ID 라벨이 더 붙는다. 그래서 MIG 클러스터에서 지표를 아무 생각 없이 sum() 하면 같은 값을 두 번 더한다. 대시보드의 합계가 물리 장수보다 큰 클러스터는 거의 이 이유다.

수집 경로와 경보

지표를 프로메테우스가 긁어 가려면 대상 정의가 필요하다. Prometheus Operator 를 쓰는 곳이라면 그 정의가 ServiceMonitor 커스텀 리소스다. 어느 서비스의 어느 포트를 얼마 간격으로 어느 경로에서 긁을지를 적어 두면 오퍼레이터가 프로메테우스 설정을 대신 만들어 준다. 중요한 것은 이것이 스키마를 가진 API 오브젝트라는 점이다. interval30s 가 아니라 30 으로 적으면 API 서버가 타입 불일치로 거절한다. 설정 파일이었다면 조용히 무시됐을 오타가 적용 시점에 걸린다.

경보로 걸 만한 것은 값이 크다고 걸지 말고 사람이 할 일이 정해지는 것으로 고른다.

| 경보 | 근거 지표 | 왜 사람이 움직여야 하나 |
| --- | --- | --- |
| XID 오류 발생 | DCGM_FI_DEV_XID_ERRORS | 하드웨어·드라이버 사건이다. 카드를 빼거나 노드를 비워야 한다 |
| 온도 임계 초과 | DCGM_FI_DEV_GPU_TEMP | 쿨링 문제이고, 방치하면 클럭이 내려가 성능이 조용히 준다 |
| 임자 없는 메모리 점유 | DCGM_FI_DEV_FB_USED 와 파드 라벨의 부재 | 죽은 프로세스가 카드를 잡고 있다. 노드를 손봐야 한다 |
| 할당됐는데 놀고 있음 | 할당(API)과 DCGM_FI_DEV_GPU_UTIL 의 격차 | 비용 문제다. 잡 주인에게 돌려 달라고 해야 한다 |

마지막 줄이 이 모듈의 핵심이고, 단일 지표로는 만들 수 없는 경보다. 한쪽은 쿠버네티스 API 에서, 다른 쪽은 exporter 에서 와야 한다.

현장에서 만나는 모습

첫째, 사용률 대시보드가 처음 켜지는 날 조직이 놀란다. 평균 사용률 15%, 그런데 대기열은 길다. 대개 원인은 기술이 아니라 습관이다. 사람들이 노트북을 띄워 놓고 퇴근하고, 잡 하나가 카드 한 장을 통째로 잡고 데이터 전처리를 CPU 로 하고 있다. 숫자를 보여 주기 전에는 아무도 자기가 그러고 있는 줄 모른다.

둘째, 시분할을 켜면 지표가 헷갈린다. 타임슬라이싱은 자원 이름의 개수만 늘릴 뿐 카드를 나누지 않는다. 그래서 슬롯 다섯 개를 쓰는 노드의 지표는 여전히 카드 하나짜리 줄로 나온다. 파드별로 사용률을 쪼개 보고 싶어도 그런 값은 없다. 이 사실을 모르고 "슬롯당 사용률" 을 만들려다 시간을 버리는 일이 흔하다. 시분할 노드에서는 카드 단위 지표와 파드 단위 할당을 따로 보고, 둘을 곱해 해석하지 않아야 한다.

셋째, 라벨 카디널리티가 조용히 문제가 된다. 지표마다 UUID 와 파드 이름이 붙으므로, 짧게 살다 죽는 파드가 많은 클러스터에서는 시계열이 계속 새로 생긴다. 파드 이름이 들어간 시계열을 장기 보관 대상에 그대로 넣으면 저장소가 먼저 무너진다. 장기 보관은 카드 단위로 줄여 두고 파드 단위는 짧게 가져가는 식의 분리가 필요하다.

넷째, 이 환경의 정직한 한계. 실습 파드에는 GPU 도 DCGM 도 프로메테우스도 없다. 그래서 다음 실습에서는 노출 형식 텍스트를 직접 만들어 그것을 재료로 삼는다. 이것이 흉내로 보일 수 있지만, 실제 운영에서 하는 일의 큰 몫도 정확히 이것이다 — 남이 준 /metrics 덤프를 읽고, 파싱하고, 규칙이 참조하는 이름이 실제로 나오는지 대조하는 일. PromQL 자체는 프로메테우스 없이 실행할 수 없으므로 실행하지 않고, 규칙 파일의 구조와 참조하는 지표 이름의 존재까지만 판정한다.

참고 문서

다음 실습에서 할 것

DCGM Exporter 의 노출 형식 텍스트를 직접 쓴다. 지표 다섯 가지를 네 장의 카드에 대해 라벨까지 갖춰 적고, 그것을 파싱해 카드별 표로 바꾸는 도구를 만든다. 쿠버네티스 매핑을 켠 판을 따로 만들어 파드 라벨이 없는데 메모리를 쥐고 있는 카드를 찾아내고, 같은 클러스터의 할당량을 API 에서 세어 사용률과 나란히 놓는다. 경보 규칙 파일을 쓰고, 규칙이 참조하는 지표 이름이 실제 노출에 나오는지 대조하는 점검기를 만든다. 마지막으로 ServiceMonitor CRD 를 직접 정의해 API 서버에 넣고, 잘못 적은 interval 이 어떻게 거절당하는지 본다.