LabHub
学习 学习路径 课程

GPU Operator 与时间片

GPU 指标 — 写出暴露格式、解析它、接到告警上

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

목표

DCGM Exporter 의 노출 형식을 직접 쓰고, 파싱해 카드별 표로 바꾸고, 할당률과 사용률을 나란히 놓고, 경보 규칙과 수집 대상 오브젝트까지 만듭니다.

왜 중요한가

GPU 는 조직에서 가장 비싼 자원인데 쿠버네티스는 그 사용률을 모릅니다. 아는 것은 '이 파드에 한 장을 배정했다' 뿐입니다. 그래서 스케줄러 눈에는 꽉 찬 클러스터가 전기 요금 관점에서는 거의 놀고 있는 상태가 몇 달씩 갑니다. 이 격차를 숫자로 만들려면 쿠버네티스 API 의 할당량과 DCGM 의 사용률을 같은 화면에 올려야 하고, 그러려면 먼저 지표의 실제 모양을 알아야 합니다. 지표에서 중요한 것은 값이 아니라 라벨입니다 — 라벨이 지표를 카드와 노드와 파드에 잇고, 그 이음이 끊긴 자리에서 '임자 없이 메모리를 쥔 카드' 같은 장애가 드러납니다.

단계

  1. /root/gpumet/metrics /root/gpumet/bin /root/gpumet/out /root/gpumet/rules /root/gpumet/k8s 를 만들고 /root/gpumet/metrics/dcgm.prom 에 A100 4장이 꽂힌 노드의 /metrics 출력을 쓰세요. 지표는 다섯 가지입니다 — DCGM_FI_DEV_GPU_UTIL DCGM_FI_DEV_FB_USED DCGM_FI_DEV_FB_FREE DCGM_FI_DEV_GPU_TEMP DCGM_FI_DEV_XID_ERRORS. 지표마다 # HELP 한 줄과 # TYPE <이름> gauge 한 줄을 앞에 두고, 카드 네 장에 대한 샘플을 이어 적습니다. 샘플의 라벨은 네 가지입니다 — gpu(0부터 3), UUID(GPU-a1b2c3d4-0000-0000-0000-00000000000<번호>), device(nvidia<번호>), Hostname(lab-node-0). 값은 gpu 0 은 사용률 97 · FB_USED 38000 · FB_FREE 2760 · 온도 71 · XID 0, gpu 1 은 2 · 9000 · 31760 · 41 · 2, gpu 2 는 0 · 0 · 40760 · 33 · 0, gpu 3 은 1 · 21000 · 19760 · 40 · 0 입니다.
  2. /root/gpumet/bin/parse-metrics.sh <노출파일> 을 만드세요. 카드 하나마다 한 줄씩 <gpu> <util> <fb_used> <fb_total> <mem_pct> 다섯 칸을 빈칸으로 나눠 출력합니다. fb_total 은 FB_USED 와 FB_FREE 의 합이고, mem_pctfb_used * 100 / fb_total버림 정수입니다. 줄은 gpu 번호 오름차순이어야 하고, 인자로 받은 파일만 읽어야 합니다(경로를 안에 박아 두지 마세요). 만든 뒤 1단계 파일에 물려 출력을 /root/gpumet/out/per-gpu.txt 에 저장하세요.
  3. /root/gpumet/metrics/dcgm-k8s.prom 에 쿠버네티스 매핑을 켠 판을 쓰세요 — 1단계와 같은 다섯 지표·네 카드에 gpu 0 과 gpu 3 에만 라벨 세 개를 더합니다(namespace="gpu-metrics", gpu 0 은 pod="train-a"container="trainer", gpu 3 은 pod="infer-b"container="server"). gpu 1 과 gpu 2 에는 파드 라벨이 없습니다. 그리고 /root/gpumet/bin/find-orphan.sh <노출파일> 을 만드세요 — 파드 라벨이 없는데 FB_USED 가 1024 를 넘는 카드의 gpu 번호를 오름차순으로 한 줄에 하나씩 냅니다. 1단계가 아니라 이 새 파일에 물려 출력을 /root/gpumet/out/orphan.txt 에 저장하세요.
  4. lab-node-0status.capacitystatus.allocatable 양쪽에 nvidia.com/gpu"4" 로 넣고, 네임스페이스 gpu-metrics 를 만든 뒤 /root/gpumet/k8s/workloads.yaml 에 파드 셋(train-a infer-b idle-c)을 쓰세요 — 모두 lab-node-0 에 못 박고 각각 nvidia.com/gpu: 1 을 요구합니다. 적용해 셋 다 뜨면 /root/gpumet/out/gap.txt 에 다섯 줄을 적으세요 — PHYSICAL=(노드가 광고한 장수), ALLOCATED=(파드들이 요구해 나간 장수), ALLOC_PCT=(둘의 비율, 버림 정수), MEAN_UTIL=(3단계 노출 파일의 네 카드 평균 사용률, 버림 정수), IDLE_BUT_ALLOCATED=(파드 라벨이 붙었는데 사용률이 10 미만인 카드 수).
  5. /root/gpumet/rules/gpu-alerts.yaml 에 프로메테우스 규칙 파일을 쓰세요. 최상위에 groups 가 있고 그룹 하나의 namegpu, 그 안에 규칙 정확히 세 개입니다 — GpuXidError(DCGM_FI_DEV_XID_ERRORS 를 쓰는 식, for: 0m, severity: critical), GpuTempHigh(DCGM_FI_DEV_GPU_TEMP 를 쓰는 식, for: 10m, severity: warning), GpuAllocatedButIdle(DCGM_FI_DEV_GPU_UTIL 을 쓰는 식, for: 2h, severity: info). 규칙마다 alert expr for labels.severity annotations.summary 다섯 가지를 모두 갖추세요. summary 는 '무엇을 해야 하는가' 가 담긴 한 문장으로 씁니다.
  6. /root/gpumet/bin/check-rules.sh <규칙파일> <노출파일> 을 만드세요. 규칙 파일의 모든 expr 에서 DCGM_FI_ 로 시작하는 지표 이름을 뽑아, 그 이름의 샘플이 노출 파일에 하나도 없으면 MISSING=<이름> 을 한 줄씩 내고 1 로 끝냅니다. 전부 있으면 OK=<노출에 있는 지표 수> 를 내고 0 으로 끝냅니다. 만든 뒤 5단계 규칙과 1단계 노출 파일에 물려 출력을 /root/gpumet/out/rulecheck.txt 에 저장하세요.
  7. /root/gpumet/k8s/servicemonitor-crd.yamlservicemonitors.monitoring.coreos.com CRD 를 쓰고 적용하세요 — 그룹 monitoring.coreos.com, 버전 v1, 네임스페이스 스코프, 종류 ServiceMonitor. 스키마는 spec.selector.matchLabels(문자열 맵), spec.endpoints(배열, minItems: 1, 항목마다 port 필수 문자열, path 문자열, interval^[0-9]+(ms|s|m|h)$ 패턴을 가진 문자열)이고 spec 에는 selectorendpoints 가 모두 필수입니다. 다음으로 /root/gpumet/k8s/servicemonitor.yamlgpu-metrics 네임스페이스의 nvidia-dcgm-exporter 를 쓰고 적용하세요 (셀렉터 app: nvidia-dcgm-exporter, 엔드포인트 하나에 port: gpu-metrics, path: /metrics, interval: 15s). 끝으로 /root/gpumet/k8s/servicemonitor-bad.yaml 에 이름 dcgm-bad-interval 로 같은 것을 쓰되 interval 을 따옴표 없는 30 으로 적고 적용해, 거부 출력을 /root/gpumet/out/07-reject.txt 에 표준 오류까지 담아 저장하세요.
  8. /root/gpumet/bin/gpu-report.sh <노출파일> <규칙파일> 을 만드세요. 여섯 줄을 순서대로 냅니다 — GPUS=(노출에 나오는 카드 수), MEAN_UTIL=(평균 사용률, 버림), MAX_TEMP=(최고 온도), ORPHAN_GPUS=(파드 라벨 없이 FB_USED 가 1024 를 넘는 카드 수), XID_GPUS=(XID 오류가 0보다 큰 카드 수), ALERT_RULES=(규칙 파일의 규칙 총수). 숫자는 전부 인자로 받은 두 파일에서 계산해야 합니다. 3단계의 노출 파일과 5단계의 규칙 파일에 물려 출력을 /root/gpumet/out/report.txt 에 저장하세요.

참고

DCGM Exporter 의 노출 형식을 직접 쓴다

/root/gpumet/metrics /root/gpumet/bin /root/gpumet/out /root/gpumet/rules /root/gpumet/k8s 를 만들고 /root/gpumet/metrics/dcgm.prom 에 A100 4장이 꽂힌 노드의 /metrics 출력을 쓰세요. 지표는 다섯 가지입니다 — DCGM_FI_DEV_GPU_UTIL DCGM_FI_DEV_FB_USED DCGM_FI_DEV_FB_FREE DCGM_FI_DEV_GPU_TEMP DCGM_FI_DEV_XID_ERRORS. 지표마다 # HELP 한 줄과 # TYPE <이름> gauge 한 줄을 앞에 두고, 카드 네 장에 대한 샘플을 이어 적습니다. 샘플의 라벨은 네 가지입니다 — gpu(0부터 3), UUID(GPU-a1b2c3d4-0000-0000-0000-00000000000<번호>), device(nvidia<번호>), Hostname(lab-node-0). 값은 gpu 0 은 사용률 97 · FB_USED 38000 · FB_FREE 2760 · 온도 71 · XID 0, gpu 1 은 2 · 9000 · 31760 · 41 · 2, gpu 2 는 0 · 0 · 40760 · 33 · 0, gpu 3 은 1 · 21000 · 19760 · 40 · 0 입니다.

프로메테우스 노출 형식은 한 줄이 이름{라벨="값",…} 숫자 입니다. 주석 두 줄(# HELP·# TYPE)은 지표마다 한 번씩만 나오고, 그 뒤에 라벨만 다른 샘플이 여러 줄 이어집니다. 메모리 총량 지표가 따로 없다는 것에 주의하세요 — FB_USED 와 FB_FREE 를 더해야 총량이 나오고, 이 표에서는 네 장 모두 합이 같습니다. 손으로 20줄을 치는 대신 셸 반복문으로 만들어도 됩니다.

노출 텍스트를 카드별 표로 바꾼다

/root/gpumet/bin/parse-metrics.sh <노출파일> 을 만드세요. 카드 하나마다 한 줄씩 <gpu> <util> <fb_used> <fb_total> <mem_pct> 다섯 칸을 빈칸으로 나눠 출력합니다. fb_total 은 FB_USED 와 FB_FREE 의 합이고, mem_pctfb_used * 100 / fb_total버림 정수입니다. 줄은 gpu 번호 오름차순이어야 하고, 인자로 받은 파일만 읽어야 합니다(경로를 안에 박아 두지 마세요). 만든 뒤 1단계 파일에 물려 출력을 /root/gpumet/out/per-gpu.txt 에 저장하세요.

라벨 문자열에서 gpu="…" 를 뽑아 카드를 가르고, 지표 이름으로 값을 모읍니다. 정규식 하나면 이름{라벨} 값 을 세 조각으로 가를 수 있습니다. 버림 정수는 파이썬의 // 로 바로 나옵니다 — 반올림하면 채점기가 잡습니다. 채점기는 이 스크립트를 다른 값이 든 파일에도 물려 봅니다. 그러니 1단계 파일의 숫자를 답으로 박아 두면 안 됩니다.

파드 라벨을 붙이고 임자 없는 점유를 찾는다

/root/gpumet/metrics/dcgm-k8s.prom 에 쿠버네티스 매핑을 켠 판을 쓰세요 — 1단계와 같은 다섯 지표·네 카드에 gpu 0 과 gpu 3 에만 라벨 세 개를 더합니다(namespace="gpu-metrics", gpu 0 은 pod="train-a"container="trainer", gpu 3 은 pod="infer-b"container="server"). gpu 1 과 gpu 2 에는 파드 라벨이 없습니다. 그리고 /root/gpumet/bin/find-orphan.sh <노출파일> 을 만드세요 — 파드 라벨이 없는데 FB_USED 가 1024 를 넘는 카드의 gpu 번호를 오름차순으로 한 줄에 하나씩 냅니다. 1단계가 아니라 이 새 파일에 물려 출력을 /root/gpumet/out/orphan.txt 에 저장하세요.

죽은 프로세스가 컨텍스트를 놓지 않으면 메모리는 잡혀 있는데 그 카드를 쥔 파드는 없습니다. 쿠버네티스는 그 카드를 비었다고 보고 새 파드를 보내는데, 그 파드는 메모리 부족으로 실패합니다. 판정에 쓸 신호는 두 가지입니다 — pod 라벨의 유무와 FB_USED 의 값. 라벨이 붙은 줄과 안 붙은 줄이 같은 카드에 섞이지 않게 주의하세요. 이 실습에서는 카드 단위로 일관됩니다. 채점기는 다른 값이 든 파일에도 물려 봅니다.

할당률과 사용률을 같은 화면에 올린다

lab-node-0status.capacitystatus.allocatable 양쪽에 nvidia.com/gpu"4" 로 넣고, 네임스페이스 gpu-metrics 를 만든 뒤 /root/gpumet/k8s/workloads.yaml 에 파드 셋(train-a infer-b idle-c)을 쓰세요 — 모두 lab-node-0 에 못 박고 각각 nvidia.com/gpu: 1 을 요구합니다. 적용해 셋 다 뜨면 /root/gpumet/out/gap.txt 에 다섯 줄을 적으세요 — PHYSICAL=(노드가 광고한 장수), ALLOCATED=(파드들이 요구해 나간 장수), ALLOC_PCT=(둘의 비율, 버림 정수), MEAN_UTIL=(3단계 노출 파일의 네 카드 평균 사용률, 버림 정수), IDLE_BUT_ALLOCATED=(파드 라벨이 붙었는데 사용률이 10 미만인 카드 수).

이 단계의 요점은 두 숫자가 다른 시스템에서 온다는 것입니다. 앞 세 줄은 쿠버네티스 API 에서, 뒤 두 줄은 노출 파일에서 나옵니다. 어느 쪽도 혼자서는 '비싸게 놀고 있다' 를 말하지 못합니다. 할당량은 kubectl get pods -n <ns> -o json 의 limits 를 더해서 셉니다. 평균은 반올림하지 말고 버림으로 계산하세요.

사람이 움직여야 하는 것만 경보로 건다

/root/gpumet/rules/gpu-alerts.yaml 에 프로메테우스 규칙 파일을 쓰세요. 최상위에 groups 가 있고 그룹 하나의 namegpu, 그 안에 규칙 정확히 세 개입니다 — GpuXidError(DCGM_FI_DEV_XID_ERRORS 를 쓰는 식, for: 0m, severity: critical), GpuTempHigh(DCGM_FI_DEV_GPU_TEMP 를 쓰는 식, for: 10m, severity: warning), GpuAllocatedButIdle(DCGM_FI_DEV_GPU_UTIL 을 쓰는 식, for: 2h, severity: info). 규칙마다 alert expr for labels.severity annotations.summary 다섯 가지를 모두 갖추세요. summary 는 '무엇을 해야 하는가' 가 담긴 한 문장으로 씁니다.

경보는 값이 크다고 거는 것이 아니라 사람이 할 일이 정해질 때 겁니다. XID 는 하드웨어 사건이라 노드를 비워야 하고, 온도는 쿨링 문제이고, 놀고 있는 할당은 비용 문제라 잡 주인에게 연락해야 합니다. 세 규칙의 for 가 다른 이유도 그것입니다 — XID 는 한 번이면 즉시, 사용률은 한참 지켜본 뒤에. 이 파일은 클러스터에 적용하지 않습니다. 다음 단계의 점검기가 읽을 재료입니다.

규칙이 부르는 지표가 실제로 나오는지 대조한다

/root/gpumet/bin/check-rules.sh <규칙파일> <노출파일> 을 만드세요. 규칙 파일의 모든 expr 에서 DCGM_FI_ 로 시작하는 지표 이름을 뽑아, 그 이름의 샘플이 노출 파일에 하나도 없으면 MISSING=<이름> 을 한 줄씩 내고 1 로 끝냅니다. 전부 있으면 OK=<노출에 있는 지표 수> 를 내고 0 으로 끝냅니다. 만든 뒤 5단계 규칙과 1단계 노출 파일에 물려 출력을 /root/gpumet/out/rulecheck.txt 에 저장하세요.

경보 규칙의 지표 이름을 한 글자 틀리면 그 경보는 영원히 울리지 않습니다. 프로메테우스는 없는 지표를 오류로 보지 않고 빈 결과로 보기 때문입니다. 그래서 이 대조는 배포 전에 걸어 둘 값어치가 있습니다. DCGM_FI_DEV_GPU_UTILIZATION 같은 그럴듯한 오타가 실제로 자주 납니다. 노출 파일에 있는 이름은 이름{ 로 시작하는 줄에서 뽑으면 되고, 주석 줄의 이름을 세면 안 됩니다. 채점기는 일부러 틀린 규칙 파일도 물려 봅니다.

수집 대상을 스키마 있는 오브젝트로 만든다

/root/gpumet/k8s/servicemonitor-crd.yamlservicemonitors.monitoring.coreos.com CRD 를 쓰고 적용하세요 — 그룹 monitoring.coreos.com, 버전 v1, 네임스페이스 스코프, 종류 ServiceMonitor. 스키마는 spec.selector.matchLabels(문자열 맵), spec.endpoints(배열, minItems: 1, 항목마다 port 필수 문자열, path 문자열, interval^[0-9]+(ms|s|m|h)$ 패턴을 가진 문자열)이고 spec 에는 selectorendpoints 가 모두 필수입니다. 다음으로 /root/gpumet/k8s/servicemonitor.yamlgpu-metrics 네임스페이스의 nvidia-dcgm-exporter 를 쓰고 적용하세요 (셀렉터 app: nvidia-dcgm-exporter, 엔드포인트 하나에 port: gpu-metrics, path: /metrics, interval: 15s). 끝으로 /root/gpumet/k8s/servicemonitor-bad.yaml 에 이름 dcgm-bad-interval 로 같은 것을 쓰되 interval 을 따옴표 없는 30 으로 적고 적용해, 거부 출력을 /root/gpumet/out/07-reject.txt 에 표준 오류까지 담아 저장하세요.

이것이 CRD 로 다루는 이득입니다 — 설정 파일이었다면 조용히 무시됐을 오타가 적용 시점에 걸립니다. 구조적 스키마에서 minItems 는 배열 길이의 하한, pattern 은 문자열의 정규식 제약입니다. YAML 에서 따옴표 없는 30 은 정수로 읽히고 "30s" 는 문자열로 읽힙니다 — 그 차이가 이 단계의 전부입니다. CRD 를 적용한 뒤 API 서버가 새 타입을 받아들이기까지 한두 번의 왕복이 걸립니다.

여섯 줄짜리 GPU 현황 보고서를 계산해 낸다

/root/gpumet/bin/gpu-report.sh <노출파일> <규칙파일> 을 만드세요. 여섯 줄을 순서대로 냅니다 — GPUS=(노출에 나오는 카드 수), MEAN_UTIL=(평균 사용률, 버림), MAX_TEMP=(최고 온도), ORPHAN_GPUS=(파드 라벨 없이 FB_USED 가 1024 를 넘는 카드 수), XID_GPUS=(XID 오류가 0보다 큰 카드 수), ALERT_RULES=(규칙 파일의 규칙 총수). 숫자는 전부 인자로 받은 두 파일에서 계산해야 합니다. 3단계의 노출 파일과 5단계의 규칙 파일에 물려 출력을 /root/gpumet/out/report.txt 에 저장하세요.

앞 단계에서 만든 두 스크립트가 이미 절반을 하고 있습니다 — 여기서는 그 계산들을 한 도구로 모읍니다. 카드 수는 gpu 라벨의 서로 다른 값의 개수이지 샘플 줄 수가 아닙니다. 평균은 반올림하지 말고 버림으로 내세요. 채점기는 이 스크립트를 다른 노출 파일과 다른 규칙 파일에도 물려 봅니다 — 그래서 숫자를 박아 두면 떨어집니다. 파드 라벨이 붙은 카드와 안 붙은 카드가 섞여 있으니 라벨 유무는 카드 단위로 모아 판정하세요.