Rootless Podman 운영 · CDI 로 GPU 붙이기 · 이론
CDI 스펙 읽고 쓰기
한 줄 요약
CDI 는 "이 장치를 컨테이너에 넣으려면 무엇을 해 줘야 하는가"를 YAML 로 적어 둔 명세다. 런타임 훅이라는 블랙박스를 표준 파일로 바꿨다.
왜 이게 필요했나
예전에 도커에서 GPU 를 쓰려면 nvidia-container-runtime 이라는 런타임 훅을 끼워야 했다. 컨테이너 시작 직전에 그 훅이 실행되어 드라이버 라이브러리를 마운트하고 장치 노드를 추가했다. 동작은 했지만 문제가 있었다.
- 무엇을 하는지 밖에서 알 수 없다. 훅 안의 로직이 블랙박스다.
- 벤더마다 자기 훅을 만든다. NVIDIA, AMD, FPGA, InfiniBand 가 각자 다른 방식.
- 런타임을 교체해야 한다. runc 대신 nvidia-container-runtime 을 쓰도록 설정을 바꿔야 했다.
CDI 는 이것을 선언적 명세로 바꿨다. /etc/cdi/*.yaml 에 "이 장치 이름을 요청하면 이런 디바이스 노드와 이런 마운트와 이런 환경변수를 추가하라" 를 적어 두면, CDI 를 지원하는 런타임(podman, containerd, CRI-O)이 그대로 실행한다.
어떻게 동작하나
스펙 구조
cdiVersion: "0.6.0"kind: nvidia.com/gpudevices: - name: "0" containerEdits: deviceNodes: - path: /dev/nvidia0 - path: /dev/nvidiactl - path: /dev/nvidia-uvm mounts: - hostPath: /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.550.90.07 containerPath: /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.550.90.07 options: ["ro", "nosuid", "nodev", "bind"] env: - NVIDIA_VISIBLE_DEVICES=0 hooks: - hookName: createContainer path: /usr/bin/nvidia-ctk args: ["nvidia-ctk", "hook", "update-ldcache"] - name: all containerEdits: deviceNodes: - path: /dev/nvidia0 - path: /dev/nvidiactl핵심 규칙들.
kind는<벤더>/<클래스>형식이다.nvidia.com/gpu,amd.com/gpu,intel.com/fpga. 벤더 부분은 도메인 형식이어야 한다.- 장치 이름은
kind=name으로 참조한다.nvidia.com/gpu=0,nvidia.com/gpu=all. containerEdits는 그 장치를 붙일 때 OCI 스펙에 더할 내용이다. deviceNodes, mounts, env, hooks 네 가지가 대표적이다.- 스펙 파일은
/etc/cdi또는/var/run/cdi에 둔다. rootless 라면 사용자 경로를 지정할 수도 있다.
실제 사용
# 스펙 생성 (실제 GPU 가 있는 호스트에서)sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml# 등록된 장치 확인nvidia-ctk cdi list# nvidia.com/gpu=0# nvidia.com/gpu=all# 컨테이너에서 사용podman run --rm --device nvidia.com/gpu=all nvidia/cuda:12.4.1-base nvidia-smidocker 의 --gpus all 에 대응하는 것이 --device nvidia.com/gpu=all 이다. 최신 podman 은 --gpus 도 호환 플래그로 받지만 CDI 표기가 정식이다.
rootless 에서도 GPU 가 된다. GPU 드라이버는 커널 공간에서 동작하므로 컨테이너의 권한 수준과 성능은 무관하다. 시스템 전역 /etc/cdi/nvidia.yaml 을 읽을 수 있으면 그대로 쓰고, 안 되면 사용자 공간에 스펙을 생성해 그 디렉터리를 지정하면 된다.
containerd 쪽 등록
쿠버네티스에서는 containerd 가 CDI 를 읽는다. config.toml 에 활성화 설정이 필요하고, 런타임을 별도로 등록하는 옛 방식도 여전히 쓰인다.
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia] runtime_type = "io.containerd.runc.v2"[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia.options] BinaryName = "/usr/bin/nvidia-container-runtime" SystemdCgroup = true그리고 그 런타임을 쓰는 워크로드는 RuntimeClass 로 선택한다.
apiVersion: node.k8s.io/v1kind: RuntimeClassmetadata: name: nvidiahandler: nvidiadefault_runtime_name 을 nvidia 로 바꾸지 않는 것이 요령이다. 그러면 GPU 를 안 쓰는 파드까지 그 런타임을 거치게 된다. RuntimeClass 로 필요한 워크로드만 지정하는 편이 안전하다.
현장에서 만나는 모습
드라이버 업데이트 후 GPU 가 안 잡힌다. 십중팔구 CDI 스펙 재생성(nvidia-ctk cdi generate)을 잊은 것이다. 스펙에는 라이브러리 파일의 버전이 박힌 경로가 들어 있어서, 드라이버가 올라가면 그 경로가 사라진다.
컨테이너 안에서 nvidia-smi 는 되는데 CUDA 프로그램이 실패한다. 필요한 라이브러리 중 일부만 마운트된 경우다. 스펙의 mounts 목록을 확인해야 한다.
다음 실습에서 할 것
/etc/cdi/ 에 CDI 스펙을 직접 작성한다. 실제 GPU 는 없으므로 스펙의 문법과 배치, 그리고 --device 인자 형식을 채점한다. 현장에서 실수가 나는 지점이 정확히 거기다.