본문 바로가기

공부/컴퓨터

[kubernetes] node에 달린 NVIDIA GPU를 Pod가 사용하지 않도록 하기

오랜만?

오래만에 글이다. ( 라고 맨날쓴다. ㅎ ) kubernetes에서 GPU를 사용하다 보니 문제가 몇가지 생겨서 이를 해결하는 방법을 찾아, 기록으로 남겨두고자 한다.

너무 기니깐.. 정리하면

  • 조건 : Kubernetes에서 Container Runtime을 Docker로 사용하고, NVIDIA GPU 사용을 위해 docker의 default-runtime을 nvidia-docker로 설정한 경우.
  • 문제 : Kubernetes에서 뜨는 Pod에서 GPU 자원을 못쓰게 하고 싶은데, docker nvidia runtime으로 인해 Container가 무조건 GPU를 보게 되는 상황.
  • 해결 : 환경 변수로 CUDA_VISIBLE_DEVICES= 값을 줘, CUDA Library 단에서 GPU 자원을 사용하지 않게 막기.

현재 상황

현재 운영 환경은 k8s master 3대, worker 8대로 총 11대가 동작중이다. 이 중 worker의 서버 상황은 아래와 같다.

  • O/S : Ubuntu 18.04
  • GPU : NVIDIA GPU
  • Container runtime : docker
  • Docker default runtime : nvidia

Container Runtime을 Docker 로 설치를 했고, nvidia-docker2도 설치 했음.  nvidia-docker를 쉽게 사용하기 위해서 /etc/docker/daemon.json 파일에 default-runtime을 nvidia로 변경해 두었다.

문제 상황

서비스를 운영하는 입장에서는 동작시킬 서비스가, 외부의 설정만으로 기능이나 동작이 쉽게 변경 될 수 있도록 만들어 주는것이 좋을 것이다. 하지만, 실제 만들때에는 "내 환경"에서 개발하므로 그런 생각들을 별로 하지 않고 개발되기가 쉽다. 지금 설명하려고 하는것도 그렇다.
AI 서비스를 개발할 때도 무조건 GPU를 사용한다는 가정하게 동작되는 경우들이 많이 있다. 학습하고 개발하는 환경에서는 GPU가 있을 것이기 때문에, 신경을 쓰지 않기 때문이다. 그러므로 GPU 사용 여부를 "외부에서 설정"하도록 개발하지 않은 경우가 훨씬 많을 것이다.
자, 그렇다면 Container에서는 GPU를 사용할 수 있지만, 그 GPU를 사용하지 않도록 하고 싶은 경우라면 어떻게 해야 할까? 지금 이야기 하고자 하는 이야기가 바로 그것이다.

문제 해결 - Docker에서는...

예에에에에전 Docker말고, 요즘 docker는 runtime을 골라서 실행 할 수 있다. nvdidia GPU를 사용하지 않은 컨테이너를 사용하고 싶다면 아래와 같이 runtimerunc를 사용하면 된다.

  • docker run -it --runtime=runc ubuntu:18.04 /bin/bash

NVIDIA GPU를 사용할 Container를 만들고 싶다면 다음과 같은 형태의 명령어를 사용하면 된다.

  • docker run -it --runntime=nvidia ubuntu:18.04 /bin/bash

아무것도 적지 않으면 /etc/docker/daemon.json 에 default-runtime으로 설정된 값이 사용되므로, 지금 우리의 상태로라면 아무것도 적지 않으면 nvidia로 사용된다. 즉, 생각 없이 그냥 docker 명령을 쓰면 nvidia-docker2 가 실행되며, NVIDIA GPU를 사용하도록 되어 있다.

즉, 필요한 경우 --runtime 옵션을 변경하면 GPU 사용을 on/off 시킬 수 있다.

문제 해결 - Kubernetes에서는...

지금 우리의 문제가 까다롭다. Kubernetes에 설정된 container runtime은 docker로 설정되어 있으며, docker의 default-runtimenvidia로 설정되어 있으므로, kubernetes에서 생각없이 컨테이너를 띄우게 되면 nvidia runtime이 실행되며, 그로 인해서 무조건 GPU를 사용하는 환경으로 셋팅되게 된다.
Kubernetes에서도 docker--runtime 옵션처럼, 필요한 runtime을 골라서 실행 시킬수는 없을까?

Kubernetes GPU 스케쥴링 ( 지금의 문제와 상관없는 하등 쓸데 없는 이야기지만 걍 정리하는 겸? 무시하고 넘기세요. ㅎ.)

그 전에 먼저 Kubernetes Node에서 GPU를 "제대로" 사용하는 방법에 대한 설명이 필요할 수 있는데, 이건 좀 더 설명이 필요하니, 여기서는 대충 정리하고, 시간이 되면 이 내용만 빼서 글을 하나 따로 정리할 예정.

  • Kubernetes에서는 Pod에 GPU를 할당할 수 있는 기능을 제공한다.
  • AMD와 NVIDIA GPU를 Pod에 할당 할 수 있다.
  • NVIDIA GPU는 nvidia-docker2를 이용해서 제공되며, docker의 default-runtime을 nvidia로 설정해야 한다.
  • GPU를 할당하고 싶은 Pod의 yaml에 spec.containers.resources.xxxxx.nvidia.com/gpu:1 등으로 GPU 갯수를 할당 가능하다
  • 다만 GPU를 쪼개서(fraction)쓰는 기능은 지원하지 않는다.
  • 그래서 대용량 메모리의 GPU를 사용한다면, resource limit 등을 설정하지 않고 쓰는게 좋다. ( 관리는 알아서... )

참고로, 나는 위 상황에서 Pod1개에 GPU 1개를 할당해야 하는 문제 때문에, Resource Limit를 걸어서 사용하고 있지 않고 있다.
관련 링크 : https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/

Kubernetes의 RuntimeClass 리소스

다시 원래대로 돌아와서 이야기 하자.  docker는 실행시킬때마다 runtime을 고를 수 있었는데, kubernetes에서 runtime을 골라서 실행 시킬 수는 없을까? Kubernetes에서도 Pod의 spec을 적을때, runtime을 고를 수 있도록 해 두었다. 해당 기능은 kubernetes v1.20에 stable로 넘어온 기능이다. ( 물론 우리가 runtime Resource를 만들 수도 있겠지만, 그 어려운걸 누가 만들겠나? 만들어진것을 잘 써야지. ㅎㅎ )

  • Kubernetes Resource 중에는 RuntimeClass 라는 리소스가 있다.
  • 이 값을 변경하면, Pod를 실행할때 어떤 runtime 을 골라서 실행할지 정할 수 있다.
  • Runtime을 지정해서 실행시키고 싶음 Pod의 yaml에 spec.runtimeClassName: xxxxxx를 주면 된다.
  • Kubernetes 홈페이지에는 CRI(Container Runtime Interface)를 구현한 몇가지 Runtime에 대해 설정해 주고 있다. ( https://kubernetes.io/docs/setup/production-environment/container-runtimes/ )
  • 홈페이지에는 containerd, CRI-O, Docker 에 대해서 설명하고 있다.

자, 그렇다면 우리가 Pod를 만들때 spec.runtimeClassName: nvidia를 사용하면 GPU를 사용할 수 있게 되고, runc를 사용하면 GPU를 사용하지 못하도록 할 수 있을까? 아쉽지만 정답은 아니다.
우리는 현재 k8s의 Container runtime으로 Docker를 사용하고 있고, Docker는 runtimeClassName 값을 존중해주지 않는다.

Dockershim 은 kubernetes와 docker를 연결해 주는 놈이다. 즉, 우리가 k8s container runtime으로 docker를 사용한다면 아무리 지..라.ㄹ..ㅂ....ㄱ...을 해 봐도 안되는거다. ( containerd 를 사용하면 되겠지만, 바꾸는게 무섭고 싫다.. 엉엉 )

참고로, 작년 말에 kubernetes가 "더 이상 docker는 지원하지 않을꺼예요" 라는 결정을 내렸다. kubernetes 1.22까지는 dockershim을 지원하며, 1.23부터는 지원하지 않을 것이라고 했다. 아... 망...
관련 링크 : https://kubernetes.io/blog/2020/12/02/dockershim-faq/

위 뉴스가 났을때 "그럼 나는 Docker를 유지한채로, 1.22까지 업그레이드해서 사용하면되지" 싶었는데, rumtime handler를 제대로 사용하려면, container runtime의 상태를 바꿔야하는 배보다.배꼽..상황이 되어 버렸다. ( docker도 내부적으로는 containerd 를 사용한다고 알고 있지만, k8s의 설정을 띡 하고 바꾸었을때 잘 된다는 보장이 없으니.. )
docker를 계속 사용해야 하는 상황이 하나 더 있는데, Kubernetes 에서 GPU를 사용하는것에 대한 가이드도 "nvidia-docker2 를 설치하고 써라~" 따위의 안내만 하고 있으니... 이건 문제 해결 상황이 아니라, 다시 문제가 생겼다.

또 다시 Kubernetes에서 GPU 사용하지 못하게 문제 해결

내 입장에서 docker를 계속 써야 하는 이유는 아래와 같다.

  1. docker로 다 구축해 놓은거, container runtime을 containerd로 변경한다는게 두려워...
  2. 아직도 여전히 docker 명령을 사용해서 이것저것 많이 해 보니깐... ( volume이라던지.. )
  3. Kubernetes에서 GPU 관련된 가이드 자체가 docker 기반으로 되어 있음...

다행히 3번은 어느정도 해결 가능할지도 모르겠다. Kubernetes 홈페이지에는 GPU를 스케쥴링에 관한 내용이 docker에 대해서만 설명되고 있지만, 2021년 2월 1일 NVIDIA에서 containerd 에서 NVIDIA GPU Opertator을 지원한다고 공개 했다.
관련 링크 : https://developer.nvidia.com/blog/announcing-containerd-support-for-the-nvidia-gpu-operator/

Helm으로 설치할 수 있도록 되어 있고, 설치를 하면, nvidia 라는 이름으로 runtime을 설정해서 사용할 수 있을듯 하다.

하지만, 내가 알기론 NVIDIA GPU Operator는 기존에 GPU를 사용하기 위해서 설정된 형태인 OS 위에, NVIDIA Driver 위에, docker 위에, nvidia-docker를 이용해서 kubernetes를 운영하는것이 아니라, OS 위에 Kubernetes위에 바로 NVIDIA Driver 가 동작하는 방식이라, 이것 역시 기존의 환경을 완전히 엎어야 하는것이므로, 할일이 태산일듯....

관련 링크 : developer.nvidia.com/blog/nvidia-gpu-operator-simplifying-gpu-management-in-kubernetes/

암튼, 나는 뒤엎는게 두려우니, 아무것도 바꾸지 않고 GPU를 사용하지 못하게 하고 싶다.

답은 CUDA 환경 변수에....

답은 간단한데 있었다.
예전에, Docker에서 GPU 사용에 대한 제약을 걸고 싶을때 사용했던 옵션을 그대로 활용하면 된다.
( 관련 링크 : https://developer.nvidia.com/blog/cuda-pro-tip-control-gpu-visibility-cuda_visible_devices/ )

  • Container를 띄울때, GPU index를 같이 주면, 해당 index의 GPU만을 사용하도록 할 수 있었다.
  • CUDA_VISIBLE_DEVICES=0 으로 하면 0번 index의 GPU만 해당 컨테이너에서 사용가능
  • CUDA_VISIBLE_DEVICES=2,3 으로 하면 2번 index, 3번 index의 GPU(총 2개)를 해당 컨테이너에서 사용가능
  • CUDA_VISIBLE_DEVICES= 으로 하면, 해당 컨테이너에는 아무런 GPU가 할당되지 않음 ( CUDA api를 사용할 경우 no CUDA-capable device is detected 출력됨 )

위 환경 변수를 주고, Container를 실행하면 된다. Container로 띄울 원래 이미지가 nvidia-smi등의 명령이 있으면 GPU 목록이 등장하기는 한다. 하지만, 실제로 CUDA를 사용하려고 할 때, cuda 라이브러리에서 에러가 발생해서 못 쓰게 된다. 즉, 실제 GPU가 잡히긴 하지만 CUDA 설정에 따라서 CUDA Library 단에서 GPU를 사용하지 못하게 막는것이다.

GPU 디바이스까지 안 잡히게 하는건 아니라, 편법으로 CUDA Library 사용시 GPU를 사용하지 못하게 하는것이다. 어차피 "GPU 자원을 사용하지 못하게 하기"가 목적이니 엎(어)치나 메치나 GPU를 못사용하게 되었으니...
다만, AI 엔진들 중, 시스템상에서 GPU Device 갯수만 보고 무조건 GPU가 있다고 가정하에 동작하게 했다면, 그 문제는 이 방법으로 해결할 수 없을 것이다.

결론은, 처음부터 상상을 잘 해서 코드를 잘 만들자. 이다.