[k8s 구축] 7. GPU 서버 JupyterLab 구축

필자는 GPU 서버를 전용으로 사용하면서도 k8s 클러스터로 구성하려 한다.
k8s에서는 이런 상황을 위해 taint 기능을 제공한다.
taint 기능을 사용하면 노드별로 제약조건을 생성할 수 있다.
이를 통해 서비스를 어떤 노드에 배포할 지 선택적으로 구성할 수 있게 된다.

1. label 및 taint 설정

아래 커맨드를 통해 각 gpu 노드별로 gpu label과 ID를 부여하고 taint를 설정한다.

gpu=true와 gpu-id=1 label은 gpu 노드 중 일부를 선택해 서비스를 배포하기 위한 것이다.
파드 배포 시 nodeSelector나 nodeAffinity 등을 통해 gpu 노드를 선택할 수 있다.

gpu=true:NoSchedule은 노드에 gpu taint를 부여한다.
파드 배포 시 gpu taint에 대해 toleration을 지정하지 않는 경우 스케줄링되지 않도록(NoSchedule) 한다.

toleration만 사용하는 경우 모든 gpu 노드에 대해 파드를 배포하나, label로 gpu 노드를 특정하는 경우 해당 GPU 노드에만 서비스가 배포된다.
label은 선택, toleration은 허가 행위라 보면 된다.

$ kubectl label nodes <gpu 노드 이름> gpu=true gpu-id=1
$ kubectl taint nodes <gpu 노드 이름> gpu=true:NoSchedule

아래는 10개의 웹 서버를 클러스터에 자동으로 배포한 모습이다.
gpu 노드에는 웹 서버가 하나도 배포되지 않은 것을 확인할 수 있다.

2. NVIDIA GPU Operator설치

k8s에서 GPU를 통합하여 다룰 수 있도록 마스터 노드에 NVIDIA GPU Operator를 설치한다. [Docs]
Helm을 통해 설치되기에 Helm이 없는 경우 다음 커맨드로 Helm을 먼저 설치한다.

$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 \
    && chmod 700 get_helm.sh \
    && ./get_helm.sh
$ helm repo add nvidia https://helm.ngc.nvidia.com/nvidia \
    && helm repo update
$ helm install --wait --generate-name \
    -n gpu-operator --create-namespace \
    nvidia/gpu-operator \
    --version=v25.3.0

3. JupyterLab 배포

JupyterLab은 안타깝게도 sudoer가 없는 상태로 제공된다.
다음의 Dockerfile을 만들어 sudoer가 포함된 커스텀 이미지를 사용할 수도 있으나 필자는 기성 이미지를 사용하기로 하였다.

# Dockerfile 내용
FROM quay.io/jupyter/pytorch-notebook:cuda12-python-3.12.10

USER root

# sudo 설치 및 jovyan에 패스워드 없는 sudo 권한 부여
RUN apt-get update && apt-get install -y sudo && \
    echo "jovyan ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

USER jovyan
docker build -t jupyter-cuda-sudo .

다음과 같이 yaml 파일을 작성해 배포하면 된다.
참고로 jupyterlab 비밀번호 해시값은 파이썬에서 미리 만들어 넣어주면 된다.

필자는 cuda가 이미 설치된 pytorch-cuda12 이미지를 사용하였다.
이미지는 이 링크에서 제공하는 걸 사용해야 한다.

$ python -c "from notebook.auth import passwd; print(passwd('사용할 비밀번호'))"
---
# GPU 노드 전용 JupyterLab 배포 (Deployment)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jupyterlab-gpu
  namespace: jupyter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jupyterlab-gpu
  template:
    metadata:
      labels:
        app: jupyterlab-gpu
    spec:
      nodeSelector:
        gpu: "true"     # 사용자 정의 GPU 노드 레이블
        gpu-id: "1"     # 특정 GPU 장치 식별 레이블
      tolerations:
      - key: "gpu"      # gpu taint 정보
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"
      containers:
      - name: jupyterlab
        image: quay.io/jupyter/pytorch-notebook:cuda12-python-3.12.10
        resources:
          limits:
            nvidia.com/gpu: 1  # NVIDIA GPU 리소스 할당
        args: ["start-notebook.sh", "--NotebookApp.token=''", "--LabApp.password='<비밀번호 해시값>'"]
        ports:
        - containerPort: 8888
        volumeMounts:
        - name: hdd-storage
          mountPath: /home/jovyan/work
      volumes:
      - name: hdd-storage
        hostPath:
          path: /storage/hdd1
          type: Directory

---
# MetalLB 로드밸런서 서비스 노출
apiVersion: v1
kind: Service
metadata:
  name: jupyterlab-service
  namespace: jupyter
spec:
  type: ClusterIP  # 웹 서비스이므로 MetalLB를 사용하지 않고 IngressController를 사용함
  ports:
  - port: 80
    targetPort: 8888
  selector:
    app: jupyterlab-gpu

---
# Ingress 리소스 구성 (Nginx Controller 기준)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jupyterlab-ingress
  namespace: jupyter
spec:
  ingressClassName: nginx
  rules:
  - host: jupyter.my-site.com  # 위의 100.100.100.100에 대응하는 도메인네임을 사용한다.
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: jupyterlab-service
            port:
              number: 80
$ kubectl create namespace jupyter
$ kubectl apply -f deploy.yaml -n jupyter

4. CUDA 테스트

배포가 잘 되었다면 다음 코드가 정상적으로 실행될 것이다.

import torch

print("CUDA 사용 가능 여부:", torch.cuda.is_available())
print("CUDA 버전:", torch.version.cuda)
print("사용 가능한 GPU 개수:", torch.cuda.device_count())
print("현재 선택된 GPU:", torch.cuda.current_device())
print("GPU 이름:", torch.cuda.get_device_name(torch.cuda.current_device()))

Series Navigation<< [k8s 구축] 6. k8s 대시보드 설치[k8s 구축] 8. 로컬 PV 설정 >>

댓글 남기기