soobook
GPU

DCGM, node-problem-detector

GPU 노드가 조용히 맛이 갔을 때 감지하고 격리

웹 서비스는 Pod이 죽으면 안다. GPU 노드는 노드가 멀쩡히 살아 있는데 GPU만 조용히 맛이 가는 일이 흔하다.

Intro

일반 웹 서비스 SRE와 GPU 인프라 SRE의 결정적 차이는 장애 단위다. 웹은 “Pod이 살아 있나, 요청을 받나”를 본다. 컨테이너가 죽으면 kubelet이 알고, readiness probe가 트래픽을 끊는다.

GPU는 다르다. 노드는 Ready 상태이고, kubelet도 정상이고, Pod도 Running인데, 그 안의 GPU 한 장만 ECC 오류로 조용히 망가져 있는 상황이 실제로 벌어진다. 학습 잡은 NCCL 통신이 멈춘 채 GPU를 붙잡고 idle하게 돌고, 쿠버네티스의 기본 신호(Pod 상태, 노드 Ready)로는 이걸 잡지 못한다.

그래서 GPU 클러스터에는 별도의 감지 계층이 필요하다. GPU가 스스로 남기는 하드웨어 오류를 읽어, 노드를 격리하고 교체하는 흐름이다. 이 흐름을 GPU가 커널에 남기는 XID 오류부터 따라가 보자.

XID Errors

XID는 NVIDIA 드라이버(NVRM)가 GPU 오류를 만났을 때 커널 로그에 남기는 에러 코드다. dmesg/var/log/syslog에 이런 줄로 찍힌다.

NVRM: Xid (0000:03:00): 79, GPU has fallen off the bus

79가 XID 번호고, 0000:03:00은 문제가 난 GPU의 PCIe 주소다. grep "NVRM: Xid" 한 줄이면 노드의 GPU 사고 이력을 훑을 수 있다. XID 코드 전체 분류와 ECC, row remapping 같은 배경은 NVIDIA Xid Errors에서 따로 다뤘으니, 여기서는 감지와 격리에 필요한 만큼만 본다.

감지 계층을 짤 때 필요한 판단은 하나다. 이 XID에서 노드를 빼야 하는가.

48(Double-Bit ECC), 64(row remap 실패), 74(NVLink 오류), 79(GPU가 버스에서 떨어짐), 95(uncontained 메모리 오류)는 “이 노드에서 잡을 더 돌리면 안 된다”는 신호라 노드를 격리해야 한다.

반대로 63(row remap 기록)과 92(단일 비트 ECC 과다)는 GPU가 스스로 복구하는 정상 과정에 가까워 노드를 뺄 필요가 없고, 94(contained 메모리 오류)는 오류가 한 프로세스에 갇혀 그 잡만 재시작하면 된다.

이후로 감지 계층은 이 격리 tier(48, 64, 74, 79, 95)를 기준으로 노드를 뺄지 정한다.

여기에 최근 변화로, 드라이버가 XID 154(GPU Recovery Action)로 “방금 그 오류에는 이런 복구 동작이 필요하다”를 직접 알려 주기 시작했다. XID 번호마다 대응을 하드코딩하는 대신 드라이버가 권하는 복구 동작을 따를 여지가 생긴 것이다.

dcgm-exporter

XID가 커널 로그에 남는다면, GPU의 상태 전반은 어떻게 볼까. 여기가 DCGM(NVIDIA Data Center GPU Manager)의 자리다. DCGM은 데이터센터 GPU를 모니터링, 진단, 관리하는 도구 모음이고, 쿠버네티스에서는 dcgm-exporter가 그 앞단을 맡는다.

dcgm-exporter는 GPU 노드마다 DaemonSet으로 한 개씩 떠서, DCGM이 읽은 GPU 텔레메트리를 :9400/metrics에 Prometheus 형식으로 노출한다. 노출하는 지표는 CSV로 고르고, 기본값에 이런 것들이 들어 있다.

지표
DCGM_FI_DEV_GPU_UTILGPU 사용률(%)
DCGM_FI_DEV_GPU_TEMPGPU 온도(C)
DCGM_FI_DEV_POWER_USAGE전력(W)
DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE프레임버퍼 메모리 사용/여유(MiB)
DCGM_FI_DEV_XID_ERRORS마지막으로 발생한 XID 번호
DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTALNVLink 대역폭

여기서 헬스 모니터링에 곧장 쓰이는 지표가 DCGM_FI_DEV_XID_ERRORS다. 이 값은 그 GPU에서 마지막으로 난 XID 번호를 담는다. 앞의 표에서 정한 “노드 격리 tier” XID가 여기 찍히면 알림을 울리면 된다.

한 가지 함정은 ECC 관련 지표(DCGM_FI_DEV_ECC_DBE_VOL_TOTAL 등)와 retired page 지표가 기본 CSV에서 주석 처리돼 있다는 점이다. GPU 메모리 오류를 지표로 추적하려면 이 줄들을 CSV에서 켜 줘야 한다.

배포는 DaemonSet과 ServiceMonitor 한 쌍이면 된다.

apiVersion: apps/v1
kind: DaemonSet
metadata: { name: dcgm-exporter, namespace: gpu-operator }
spec:
  selector: { matchLabels: { app: dcgm-exporter } }
  template:
    metadata: { labels: { app: dcgm-exporter } }
    spec:
      nodeSelector: { nvidia.com/gpu.present: "true" }
      containers:
        - name: dcgm-exporter
          image: nvcr.io/nvidia/k8s/dcgm-exporter:4.5.3-4.8.2-ubuntu22.04
          ports: [ { name: metrics, containerPort: 9400 } ]
          env:
            - { name: DCGM_EXPORTER_KUBERNETES, value: "true" }
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata: { name: dcgm-exporter, namespace: gpu-operator }
spec:
  selector: { matchLabels: { app: dcgm-exporter } }
  endpoints:
    - { port: metrics, interval: 15s }

그다음 XID 격리 tier에 알림을 건다.

groups:
  - name: gpu-health
    rules:
      - alert: GPUFatalXid
        expr: DCGM_FI_DEV_XID_ERRORS == 48
              or DCGM_FI_DEV_XID_ERRORS == 79
              or DCGM_FI_DEV_XID_ERRORS == 95
        for: 2m
        labels: { severity: critical }
        annotations:
          summary: "노드 {{ $labels.Hostname }} GPU {{ $labels.gpu }}에서 치명 XID {{ $value }}"
      - alert: GPUHighTemp
        expr: DCGM_FI_DEV_GPU_TEMP > 85
        for: 5m
        labels: { severity: warning }

dcgmi diag

지표가 상시 관측이라면, dcgmi diag는 능동 진단이다. GPU에 실제로 부하를 걸어 문제를 끌어내는 검사로, 노드를 잡에 넣기 전이나 의심스러운 노드를 뺀 뒤에 돌린다. 레벨이 넷이고 강도와 시간이 다르다.

레벨이름8-GPU 노드 소요내용
r1Short2.5초 미만배포, 환경 점검
r2Medium10.5분 미만+ PCIe, NVLink, 메모리, 대역폭
r3Long35분 미만+ 스트레스, 전력, NCCL 테스트
r4Extra Long2.25시간 미만+ memtest, EDPp 펄스

r1은 부팅 직후 빠른 sanity check로, r3 이상은 실제로 잡을 돌리기 전 노드 검수나 사고 후 격리 판정에 쓴다. 이와 별개로 DCGM은 부하를 걸지 않는 상시 헬스 워치(active health check)도 갖고 있어서, PCIe, NVLink, 메모리, 온도, 전력 같은 서브시스템 상태를 pass/warn/fail로 뽑아 준다. dcgm-exporter의 DCGM_EXP_GPU_HEALTH_STATUS 지표가 이 판정을 그대로 노출해, 원시 지표를 재조합하지 않고 DCGM의 헬스 결론에 바로 알림을 걸 수 있다.

node-problem-detector

지금까지가 DCGM 경로(지표 기반)라면, 다른 한 축이 node-problem-detector(NPD)다. 쿠버네티스 SIG 프로젝트로, GPU 노드마다 DaemonSet으로 떠서 노드 레벨 문제를 감지해 apiserver에 올린다. GKE, AKS에서는 기본으로 깔려 있다.

NPD가 문제를 알리는 방식은 두 가지다.

  • NodeCondition: 노드를 못 쓰게 만드는 영구적 문제. 해소될 때까지 남는다. 스케줄러가 이걸 보고 노드를 피한다.
  • Event: 영향이 제한적인 일시적 문제. 기록만 남긴다.

문제를 감지하는 데몬도 여러 종류인데, GPU 헬스에 쓰는 건 둘이다.

  • SystemLogMonitor: 커널 로그를 tail하며 정규식으로 매칭한다. 여기에 NVRM: Xid 패턴을 걸면 XID 오류를 조건으로 올릴 수 있다.
  • CustomPluginMonitor: 사용자가 짠 스크립트를 주기적으로 돌려, 종료 코드로 조건을 정한다. nvidia-smidcgmi 헬스 체크를 감싸거나 특정 XID를 grep하는 데 쓴다.

CustomPluginMonitor로 치명 XID를 잡는 예를 보면 이렇다.

{
  "plugin": "custom",
  "pluginConfig": { "invoke_interval": "30s", "timeout": "5s" },
  "source": "gpu-xid-monitor",
  "conditions": [
    { "type": "GPUProblem", "reason": "GPUHealthy", "message": "GPU is healthy" }
  ],
  "rules": [
    { "type": "permanent", "condition": "GPUProblem", "reason": "FatalXidError",
      "path": "/config/check_gpu_xid.sh", "timeout": "5s" }
  ]
}
#!/bin/bash
# 격리 tier XID(48 64 74 79 95)가 커널 로그에 있으면 문제로 판정
if dmesg | grep -qE "NVRM: Xid.*: (48|64|74|79|95),"; then
  echo "Fatal GPU XID detected"; exit 1
fi
echo "OK"; exit 0

스크립트가 1을 반환하면 NPD가 GPUProblem=True NodeCondition을 세운다. 여기서 중요한 사실은, NPD는 여기까지만 한다는 것이다. 조건을 세울 뿐 노드를 cordon하거나 drain하지 않는다.

Auto-Isolation

그래서 감지와 격리가 갈린다. 감지 컴포넌트(NPD, DCGM)는 신호만 내고, 실제로 노드를 비우는 건 별도 컴포넌트다. 전체 흐름을 이으면 이렇게 된다.

XID가 커널 로그에 남으면 NPD가 감지해 NodeCondition을 세우고, dcgm-exporter의 지표는 Prometheus 알림으로 사람을 부른다. 감지는 여기까지이고, 그 신호를 받아 별도 컨트롤러가 노드를 cordon하고 drain한 뒤 Cluster Autoscaler가 노드를 교체한다. 감지 단계와 격리 단계를 서로 다른 컴포넌트가 맡는다

격리를 실제로 수행하는 후보는 몇 가지다. DeschedulerRemovePodsViolatingNodeTaints 전략은 스케줄러의 TaintNodesByCondition과 짝지어, NodeCondition이 만든 taint를 위반하는 Pod을 노드에서 밀어낸다. Node Health Check Operator(medik8s NHC)는 조건을 보고 자동 remediation을 돌린다. 아니면 조건을 감시하다 cordon+drain을 실행하는 커스텀 컨트롤러를 직접 두기도 한다. 비워진 노드는 Cluster Autoscaler가 걷어 내고 새 노드로 갈아 끼운다.

여기에 더해, GPU를 개별로 빼는 더 얕은 경로도 있다. NVIDIA device plugin은 GPU마다 헬스를 추적하다가 XID 같은 오류를 감지하면 그 GPU 리소스를 unhealthy로 표시한다. 그러면 그 GPU가 스케줄 가능한 용량에서 빠지고, 붙어 있던 Pod은 실패하고 재배치된다. 다만 NVIDIA 스스로 이 헬스 체크가 아직 포괄적이지 않다고 밝히고 있어서, 노드 전체를 비우는 격리는 여전히 NPD와 drain 컨트롤러를 얹어 처리한다.

이 조각들을 한 번에 까는 게 NVIDIA GPU Operator다. 드라이버, container-toolkit, device-plugin, GPU Feature Discovery, dcgm-exporter, MIG Manager, 그리고 스택을 노드마다 검증하는 validator까지 오퍼레이터로 묶어 관리한다. dcgm-exporter를 손으로 DaemonSet으로 깔든, GPU Operator로 통째로 깔든, 딱히 상관없긴하다.

References