One label went down and the pod was gone
한국어 원문으로 표시합니다.
목표
GPU Operator 가 펼치는 데몬셋 다섯 개를 노드 라벨로 배치하고, 노드 하나에서 오퍼랜드 하나만 빼 보고, '있어야 할 오퍼랜드가 다 있는가' 와 '순서가 어긋난 노드가 있는가' 를 판정하는 도구를 만듭니다.
왜 중요한가
GPU Operator 를 설치하면 파드가 열 개 넘게 뜹니다. 그 파드들은 하나의 프로그램이 아니라 오퍼레이터가 ClusterPolicy 를 읽고 펼친 데몬셋 여러 개입니다. 드라이버, 컨테이너 툴킷, 장치 플러그인, GPU Feature Discovery, DCGM exporter, 검증기, MIG 매니저가 각자 자기 데몬셋을 갖고 있고, 각 데몬셋은 nodeSelector 로 nvidia.com/gpu.deploy.<이름> 라벨을 봅니다. 이 구조를 알면 운영에서 할 수 있는 일이 달라집니다 — 노드 한 대만 오퍼랜드에서 빼는 것도, 드라이버가 이미 깔린 노드에 드라이버만 안 올리는 것도, 문제가 난 노드 하나를 격리하는 것도 라벨 한 줄입니다. 모르면 반대가 됩니다. 라벨 하나가 잘못 붙어 툴킷 없이 장치 플러그인만 도는 노드가 생기면, 자원은 광고되고 스케줄러는 성공하는데 워크로드만 장치를 못 잡습니다. 아무도 오류를 내지 않으니 몇 시간이 그냥 갑니다.
단계
/root/gpuops에서 작업합니다(export KUBECONFIG=/root/.kube/config). 네임스페이스gpu-operator를 만드세요. lab-node-0(GPU 노드, 드라이버 없음)에feature.node.kubernetes.io/pci-10de.present=true,nvidia.com/gpu.present=true와 함께nvidia.com/gpu.deploy.driver=true,nvidia.com/gpu.deploy.container-toolkit=true,nvidia.com/gpu.deploy.device-plugin=true,nvidia.com/gpu.deploy.gpu-feature-discovery=true,nvidia.com/gpu.deploy.dcgm-exporter=true를 붙이세요. lab-node-1(GPU 노드, 드라이버가 호스트에 이미 깔려 있음)에는 같은 라벨을 붙이되nvidia.com/gpu.deploy.driver만false로 둡니다. lab-node-2(GPU 없음)에는nvidia.com/gpu.deploy.로 시작하는 라벨을 하나도 붙이지 마세요. 그리고/root/gpuops/out/roster.txt에 다섯 줄을 적으세요 —<오퍼랜드이름>=<그 오퍼랜드를 켜는 라벨 키>꼴로driver·container-toolkit·device-plugin·gpu-feature-discovery·dcgm-exporter다섯 개입니다./root/gpuops/k8s/toolkit-ds.yaml에 데몬셋nvidia-container-toolkit-daemonset을 쓰세요 — 네임스페이스gpu-operator, 파드 라벨과 셀렉터는app: nvidia-container-toolkit-daemonset,nodeSelector는nvidia.com/gpu.deploy.container-toolkit: "true", 컨테이너 이미지는nvcr.io/nvidia/k8s/container-toolkit:v1.16.2, 메모리 요청은128Mi입니다. 적용한 뒤desiredNumberScheduled가 2 가 되고 파드가 lab-node-0 과 lab-node-1 에 하나씩 뜨는 것을 확인하세요./root/gpuops/k8s/driver-ds.yaml에 데몬셋nvidia-driver-daemonset을 쓰세요 — 같은 네임스페이스, 파드 라벨과 셀렉터는app: nvidia-driver-daemonset, nodeSelector 는nvidia.com/gpu.deploy.driver: "true", 이미지는nvcr.io/nvidia/driver:550.90.07-ubuntu22.04, 메모리 요청은512Mi입니다. 적용한 뒤 GPU 노드가 둘인데도 이 데몬셋만 한 노드에 뜨는 것을 확인하고,/root/gpuops/out/driver.txt에 두 줄을 적으세요 —DESIRED=<숫자>와SKIPPED=<빠진 노드 이름>.- 데몬셋 둘을 더 만드세요.
/root/gpuops/k8s/device-plugin-ds.yaml의nvidia-device-plugin-daemonset은 nodeSelectornvidia.com/gpu.deploy.device-plugin: "true", 이미지nvcr.io/nvidia/k8s-device-plugin:v0.16.2, 메모리 요청128Mi입니다./root/gpuops/k8s/gfd-ds.yaml의gpu-feature-discovery는 nodeSelectornvidia.com/gpu.deploy.gpu-feature-discovery: "true", 같은 이미지, 같은 메모리 요청입니다. 둘 다 파드 라벨과 셀렉터는app: <데몬셋 이름>입니다. 적용한 뒤 두 데몬셋의desiredNumberScheduled가 각각 2 인 것을 확인하세요. /root/gpuops/k8s/dcgm-ds.yaml에 데몬셋nvidia-dcgm-exporter를 쓰세요 — nodeSelectornvidia.com/gpu.deploy.dcgm-exporter: "true", 이미지nvcr.io/nvidia/k8s/dcgm-exporter:3.3.7-3.5.0-ubuntu22.04, 메모리 요청128Mi, 파드 라벨과 셀렉터는app: nvidia-dcgm-exporter입니다. 적용해 두 노드에 뜨는 것을 확인한 다음, lab-node-1 의nvidia.com/gpu.deploy.dcgm-exporter를false로 바꾸고 파드가 사라지는 것을 확인하세요./root/gpuops/out/optout.txt에 두 줄을 적으세요 —BEFORE=<바꾸기 전 desiredNumberScheduled>와AFTER=<바꾼 뒤 값>./root/gpuops/operand-audit.sh를 만드세요. 클러스터의 모든 노드를 돌며, 그 노드에nvidia.com/gpu.deploy.<이름>라벨이true로 붙어 있는 오퍼랜드마다 그 데몬셋의 파드가 그 노드에서 Running 인지 봅니다. 없으면<노드> MISSING <오퍼랜드이름>을 한 줄 내고, 그 노드에 빠진 것이 하나도 없으면<노드> OK를 한 줄 냅니다. 하나라도 빠졌으면 종료 코드 1, 아니면 0 입니다. 오퍼랜드 이름과 데몬셋 이름의 짝은 1단계의 다섯 개입니다. 만든 뒤 지금 클러스터에 돌려 출력을/root/gpuops/out/audit.txt에 저장하세요. 노드 이름을 스크립트 안에 적어 두지 마세요 — 채점기가 노드를 하나 더 만들어 놓고 부릅니다.- 누군가 lab-node-2 에
nvidia.com/gpu.deploy.device-plugin=true만 손으로 붙였다고 칩시다. 그 라벨을 실제로 붙이고(다른deploy라벨은 붙이지 않습니다) 장치 플러그인 파드가 그 노드에 뜨는 것을 확인하세요. 그리고/root/gpuops/order-check.sh <노드이름>을 만드세요 — 그 노드에 장치 플러그인 파드는 Running 인데 컨테이너 툴킷 파드가 없으면device-plugin-without-toolkit한 줄과 종료 코드 1, 그 밖의 경우에는ok한 줄과 0 입니다. 세 노드에 차례로 돌려/root/gpuops/out/order.txt에<노드> <결과>꼴로 세 줄을 적으세요. /root/gpuops/out/matrix.txt에 노드마다 한 줄씩 세 줄을 적으세요. 형식은<노드> driver=<yes|no> toolkit=<yes|no> device-plugin=<yes|no> gfd=<yes|no> dcgm=<yes|no>이고,yes는 그 오퍼랜드의 데몬셋 파드가 그 노드에서 Running 이라는 뜻입니다. 줄 순서는 lab-node-0, lab-node-1, lab-node-2 입니다. 그리고 네 번째 줄에TOTAL_PODS=<gpu-operator 네임스페이스에서 Running 인 데몬셋 파드 총수>를 적으세요. 숫자와 yes/no 는 API 에 물어서 채우세요 — 앞 단계의 기억으로 적으면 어긋납니다.
참고
export KUBECONFIG=/root/.kube/config로 시작합니다. 노드는 lab-node-0/1/2 셋이고 모든 산출물은/root/gpuops아래, 오브젝트는 네임스페이스gpu-operator에 둡니다.- 이 환경에는 GPU 도 GPU Operator 도 없습니다. 그래서 오퍼레이터가 만들어 낼 데몬셋을 여러분이 직접 씁니다. 대신 데몬셋 컨트롤러와 스케줄러는 진짜라, 라벨에 따라 파드가 생기고 사라지는 것은 실물입니다.
- 파드는 실제로 실행되지 않고 Ready 로 위조됩니다. 드라이버가 정말 설치됐는지는 볼 수 없고 보지도 않습니다 — 이 실습이 판정하는 것은 '어느 노드에 무엇이 떴는가' 뿐입니다.
- 데몬셋을 적용하거나 라벨을 바꾼 직후에는 컨트롤러가 반응할 시간이 몇 초 필요합니다. 숫자가 예전 같으면 잠시 뒤 다시 보세요.
- 흔한 실수: nodeSelector 의 값에서 따옴표를 빼는 것. 라벨 값은 문자열이라
"true"로 적어야 합니다. - 흔한 실수: 판정을
desiredNumberScheduled로만 하는 것. 라벨은 맞는데 파드가 못 뜬 노드는 그 숫자에 안 잡힙니다. - GPU Operator: Getting Started · Installing the NVIDIA GPU Operator · DaemonSet
오퍼랜드를 켜고 끄는 라벨을 노드마다 다르게 둔다
/root/gpuops 에서 작업합니다(export KUBECONFIG=/root/.kube/config). 네임스페이스 gpu-operator 를 만드세요. lab-node-0(GPU 노드, 드라이버 없음)에 feature.node.kubernetes.io/pci-10de.present=true, nvidia.com/gpu.present=true 와 함께 nvidia.com/gpu.deploy.driver=true, nvidia.com/gpu.deploy.container-toolkit=true, nvidia.com/gpu.deploy.device-plugin=true, nvidia.com/gpu.deploy.gpu-feature-discovery=true, nvidia.com/gpu.deploy.dcgm-exporter=true 를 붙이세요. lab-node-1(GPU 노드, 드라이버가 호스트에 이미 깔려 있음)에는 같은 라벨을 붙이되 nvidia.com/gpu.deploy.driver 만 false 로 둡니다. lab-node-2(GPU 없음)에는 nvidia.com/gpu.deploy. 로 시작하는 라벨을 하나도 붙이지 마세요. 그리고 /root/gpuops/out/roster.txt 에 다섯 줄을 적으세요 — <오퍼랜드이름>=<그 오퍼랜드를 켜는 라벨 키> 꼴로 driver·container-toolkit·device-plugin·gpu-feature-discovery·dcgm-exporter 다섯 개입니다.
오퍼레이터는 하나이고 오퍼랜드는 여럿입니다 — ClusterPolicy 를 읽은 오퍼레이터가 드라이버·툴킷·장치 플러그인·GFD·DCGM exporter 같은 데몬셋으로 펼칩니다. 각 데몬셋의 nodeSelector 가 nvidia.com/gpu.deploy.<이름> 라벨을 보므로, 노드 하나에서 오퍼랜드 하나만 빼는 일이 라벨 한 줄로 됩니다. 공식 문서는 드라이버를 특정 노드에만 안 올리는 방법으로 nvidia.com/gpu.deploy.driver=false 를 적고 있습니다. kubectl label node <이름> <키>=<값> --overwrite 로 여러 개를 한 번에 붙일 수 있습니다.
첫 오퍼랜드를 라벨로 배치한다
/root/gpuops/k8s/toolkit-ds.yaml 에 데몬셋 nvidia-container-toolkit-daemonset 을 쓰세요 — 네임스페이스 gpu-operator, 파드 라벨과 셀렉터는 app: nvidia-container-toolkit-daemonset, nodeSelector 는 nvidia.com/gpu.deploy.container-toolkit: "true", 컨테이너 이미지는 nvcr.io/nvidia/k8s/container-toolkit:v1.16.2, 메모리 요청은 128Mi 입니다. 적용한 뒤 desiredNumberScheduled 가 2 가 되고 파드가 lab-node-0 과 lab-node-1 에 하나씩 뜨는 것을 확인하세요.
데몬셋은 '노드마다 하나' 가 아니라 '조건에 맞는 노드마다 하나' 입니다. 그 조건이 nodeSelector 이고, GPU Operator 는 여기에 자기 라벨을 걸어 두어 노드 단위로 오퍼랜드를 켜고 끕니다. desiredNumberScheduled 는 데몬셋 컨트롤러가 '이 데몬셋이 떠야 한다고 판단한 노드 수' 입니다 — 라벨이 맞는 노드가 늘거나 줄면 이 숫자가 따라 움직입니다. kubectl -n gpu-operator get ds -o wide 로 한눈에 봅니다.
드라이버만 빠지는 노드
/root/gpuops/k8s/driver-ds.yaml 에 데몬셋 nvidia-driver-daemonset 을 쓰세요 — 같은 네임스페이스, 파드 라벨과 셀렉터는 app: nvidia-driver-daemonset, nodeSelector 는 nvidia.com/gpu.deploy.driver: "true", 이미지는 nvcr.io/nvidia/driver:550.90.07-ubuntu22.04, 메모리 요청은 512Mi 입니다. 적용한 뒤 GPU 노드가 둘인데도 이 데몬셋만 한 노드에 뜨는 것을 확인하고, /root/gpuops/out/driver.txt 에 두 줄을 적으세요 — DESIRED=<숫자> 와 SKIPPED=<빠진 노드 이름>.
GPU 노드가 둘인데 드라이버 데몬셋은 하나만 뜹니다. 라벨 값이 "true" 가 아니면 nodeSelector 가 맞지 않기 때문입니다 — false 라고 적는 것과 라벨을 아예 지우는 것은 사람에게는 다르게 읽히지만 nodeSelector 에게는 똑같습니다. 이것이 '이 노드에는 이미 드라이버가 깔려 있다' 를 표현하는 방법입니다. 드라이버는 커널 모듈을 올리는 오퍼랜드라 호스트에 이미 깔린 드라이버와 겹치면 안 됩니다. 값은 명령의 출력을 보고 적지 말고 -o jsonpath 로 받아 적으세요.
장치 플러그인과 GFD 를 함께 올린다
데몬셋 둘을 더 만드세요. /root/gpuops/k8s/device-plugin-ds.yaml 의 nvidia-device-plugin-daemonset 은 nodeSelector nvidia.com/gpu.deploy.device-plugin: "true", 이미지 nvcr.io/nvidia/k8s-device-plugin:v0.16.2, 메모리 요청 128Mi 입니다. /root/gpuops/k8s/gfd-ds.yaml 의 gpu-feature-discovery 는 nodeSelector nvidia.com/gpu.deploy.gpu-feature-discovery: "true", 같은 이미지, 같은 메모리 요청입니다. 둘 다 파드 라벨과 셀렉터는 app: <데몬셋 이름> 입니다. 적용한 뒤 두 데몬셋의 desiredNumberScheduled 가 각각 2 인 것을 확인하세요.
장치 플러그인은 kubelet 에게 nvidia.com/gpu 자원을 광고하는 오퍼랜드이고, GFD 는 그 노드의 GPU 사실을 라벨로 바꿔 붙이는 오퍼랜드입니다. 둘은 같은 이미지에서 나오지만 하는 일이 달라 데몬셋이 따로 있고, 따로 있으니 라벨도 따로 있습니다. 드라이버를 뺀 노드에도 이 둘은 떠야 합니다 — 드라이버가 호스트에 이미 있으니까요. 이름이 nvidia- 로 시작하지 않는 오퍼랜드도 있다는 점에 주의하세요.
라벨을 내리자 파드가 사라진다
/root/gpuops/k8s/dcgm-ds.yaml 에 데몬셋 nvidia-dcgm-exporter 를 쓰세요 — nodeSelector nvidia.com/gpu.deploy.dcgm-exporter: "true", 이미지 nvcr.io/nvidia/k8s/dcgm-exporter:3.3.7-3.5.0-ubuntu22.04, 메모리 요청 128Mi, 파드 라벨과 셀렉터는 app: nvidia-dcgm-exporter 입니다. 적용해 두 노드에 뜨는 것을 확인한 다음, lab-node-1 의 nvidia.com/gpu.deploy.dcgm-exporter 를 false 로 바꾸고 파드가 사라지는 것을 확인하세요. /root/gpuops/out/optout.txt 에 두 줄을 적으세요 — BEFORE=<바꾸기 전 desiredNumberScheduled> 와 AFTER=<바꾼 뒤 값>.
데몬셋 컨트롤러는 nodeSelector 와 노드 라벨을 계속 맞춰 봅니다. 라벨이 조건에서 벗어나면 그 노드의 파드를 지웁니다 — 데몬셋을 고치지 않았는데 파드가 사라지는 이유입니다. 운영에서 노드 한 대만 오퍼랜드에서 빼고 싶을 때 쓰는 손잡이가 정확히 이것입니다. 공식 문서에는 노드의 모든 오퍼랜드를 한 번에 빼는 nvidia.com/gpu.deploy.operands=false 도 있습니다. 숫자 두 개는 라벨을 바꾸기 전과 후 에 각각 받아 두어야 합니다 — 나중에 한 번에 적을 수 없습니다.
있어야 할 오퍼랜드가 다 있는지 판정하는 도구
/root/gpuops/operand-audit.sh 를 만드세요. 클러스터의 모든 노드를 돌며, 그 노드에 nvidia.com/gpu.deploy.<이름> 라벨이 true 로 붙어 있는 오퍼랜드마다 그 데몬셋의 파드가 그 노드에서 Running 인지 봅니다. 없으면 <노드> MISSING <오퍼랜드이름> 을 한 줄 내고, 그 노드에 빠진 것이 하나도 없으면 <노드> OK 를 한 줄 냅니다. 하나라도 빠졌으면 종료 코드 1, 아니면 0 입니다. 오퍼랜드 이름과 데몬셋 이름의 짝은 1단계의 다섯 개입니다. 만든 뒤 지금 클러스터에 돌려 출력을 /root/gpuops/out/audit.txt 에 저장하세요. 노드 이름을 스크립트 안에 적어 두지 마세요 — 채점기가 노드를 하나 더 만들어 놓고 부릅니다.
이 판정은 '데몬셋이 몇 개 원하는가' 가 아니라 '이 노드에 실제로 떠 있는가' 로 해야 합니다. 둘은 다릅니다 — 라벨은 맞는데 테인트나 자원 때문에 파드가 못 뜨는 노드가 실제로 생깁니다. 노드마다 kubectl 을 부르면 노드가 늘 때마다 느려집니다. 노드 목록과 파드 목록을 각각 한 번씩 받아 두고 jq 로 걸러 내면 API 호출은 두 번으로 끝납니다. jq 로 라벨을 읽을 때 // 를 쓰면 값이 false 인 라벨과 없는 라벨이 구분되지 않습니다.
말이 안 되는 조합을 찾아낸다
누군가 lab-node-2 에 nvidia.com/gpu.deploy.device-plugin=true 만 손으로 붙였다고 칩시다. 그 라벨을 실제로 붙이고(다른 deploy 라벨은 붙이지 않습니다) 장치 플러그인 파드가 그 노드에 뜨는 것을 확인하세요. 그리고 /root/gpuops/order-check.sh <노드이름> 을 만드세요 — 그 노드에 장치 플러그인 파드는 Running 인데 컨테이너 툴킷 파드가 없으면 device-plugin-without-toolkit 한 줄과 종료 코드 1, 그 밖의 경우에는 ok 한 줄과 0 입니다. 세 노드에 차례로 돌려 /root/gpuops/out/order.txt 에 <노드> <결과> 꼴로 세 줄을 적으세요.
컨테이너 툴킷이 런타임을 등록해 주어야 GPU 컨테이너가 장치를 볼 수 있습니다. 장치 플러그인만 떠 있으면 자원은 광고되는데 그 자원을 받은 파드는 장치를 못 잡습니다 — 스케줄러는 성공했다고 하는데 워크로드는 실패하는, 가장 찾기 어려운 조합입니다. 그래서 '무엇이 떠 있나' 가 아니라 '무엇과 무엇이 함께 떠 있나' 를 봐야 합니다. 스크립트는 인자로 받은 노드만 보고, 판단은 두 데몬셋의 파드 수로 합니다. 항상 1 을 내는 스크립트는 안 됩니다 — 채점기가 멀쩡한 노드로도 부릅니다.
노드와 오퍼랜드의 표를 실제 상태에서 뽑는다
/root/gpuops/out/matrix.txt 에 노드마다 한 줄씩 세 줄을 적으세요. 형식은 <노드> driver=<yes|no> toolkit=<yes|no> device-plugin=<yes|no> gfd=<yes|no> dcgm=<yes|no> 이고, yes 는 그 오퍼랜드의 데몬셋 파드가 그 노드에서 Running 이라는 뜻입니다. 줄 순서는 lab-node-0, lab-node-1, lab-node-2 입니다. 그리고 네 번째 줄에 TOTAL_PODS=<gpu-operator 네임스페이스에서 Running 인 데몬셋 파드 총수> 를 적으세요. 숫자와 yes/no 는 API 에 물어서 채우세요 — 앞 단계의 기억으로 적으면 어긋납니다.
이 표가 곧 '지금 이 클러스터의 GPU 스택이 어떤 모양인가' 입니다. 사고가 났을 때 가장 먼저 만드는 것이 이 표이고, 표를 만드는 명령을 갖고 있으면 30초면 끝납니다. kubectl -n gpu-operator get pods -o json 한 번이면 필요한 것이 모두 들어 있습니다 — .spec.nodeName 과 .metadata.labels.app 과 .status.phase 를 함께 보세요. lab-node-2 에는 7단계에서 붙인 라벨 때문에 장치 플러그인 하나만 떠 있어야 합니다.