안녕하세요! [1부: LLM 서빙, 왜 Ray 여야만 했을까?] 에 이어, 오늘은 본격적인 실습의 첫 단계를 시작합니다. 우리가 꿈꾸는 LLM 서빙 인프라를 구축하기 위한 가장 단단한 기반, 바로 쿠버네티스 위에 Ray 클러스터를 띄워보는 과정을 전부 보여드릴 예정입니다.
쿠버네티스 환경에서 Ray를 직접 운영하는 것은 꽤나 번거로운 일입니다. Head 파드와 Worker 파드의 생애 주기를 관리하고, 네트워크를 설정하고, 장애가 났을 때 복구하는 로직까지 모두 직접 구현해야 하죠.
이러한 번거로움을 해결하기 위해 KubeRay를 사용할 수 있습니다.
🤔 KubeRay가 뭐죠? 왜 써야 하나요?
간단히 말해 KubeRay
는 쿠버네티스 위에서 Ray 클러스터를 손쉽게 관리해주는 '자동화 관리 로봇(Operator)' 입니다.
KubeRay
를 설치하면, 우리 쿠버네티스 클러스터는 RayCluster
와 같은 새로운 리소스 종류를 이해할 수 있게 됩니다. 이것이 바로 CRD(Custom Resource Definition) 덕분입니다. KubeRay
가 RayCluster
라는 설계도(CRD)를 등록해놓았기 때문이죠. 여기서 RayCluster
CRD는 오랜 시간 실행되는 영구적인(persistent) Ray 클러스터를 만드는 KubeRay의 가장 기본 단위입니다.
그리고 우리는 "Ray 클러스터가 필요해..." 와 같이 원하는 상태를 YAML 파일로 정의해서 쿠버네티스에 제출합니다. 이 YAML 파일이 바로 설계도를 바탕으로 만들어진 실체, 즉 CR(Custom Resource) 입니다. 그러면 KubeRay
오퍼레이터가 이 CR을 읽고 알아서 Ray 클러스터를 생성하고, 노드가 죽으면 다시 살리는 등 모든 귀찮은 일을 대신 처리해줍니다.
저도 이번 프로젝트를 진행하면서 CR과 CRD의 차이를 명확하게 알게 되었습니다. 만약 쿠버네티스의 CRD와 오퍼레이터의 기본 동작 원리가 궁금하신 분이라면 heumsi님의 블로그 글을 읽어보시는 것을 강력히 추천합니다.
TL DR
우리가 YAML 파일로 작성하는 '요구사항'이 바로 CR(주문서)입니다. KubeRay 오퍼레이터(자동화 로봇)는 이 주문서를 보고, 내용 그대로 Ray 클러스터를 자동으로 만들고 문제가 생기면 알아서 관리까지 해줍니다.
이를 통해 비즈니스 로직에만 집중할 수 있습니다. 이제 직접 설치를 진행하겠습니다.
1️⃣ 단계: KubeRay 오퍼레이터 설치하기
💡 사전 준비: 이 가이드를 따라오려면
kubectl
이 설치되어 있고, 쿠버네티스 클러스터에 접속할 수 있는 환경이 필요합니다.
먼저, KubeRay
공식 저장소에 있는 YAML 파일을 사용하여 오퍼레이터를 설치합니다. 터미널에 아래 명령어를 입력하세요.
KubeRay 오퍼레이터 v1.1.0 버전을 설치합니다.
kubectl create -k "[github.com/ray-project/kuberay/ray-operator/config/default?ref=v1.1.0](https://github.com/ray-project/kuberay/ray-operator/config/default?ref=v1.1.0)"
설치가 완료되면, ray-system
네임스페이스에 오퍼레이터 파드가 정상적으로 실행 중인지 확인합니다.
kubectl get pods -n ray-system
출력 결과
NAME READY STATUS RESTARTS AGE
kuberay-operator-6d7bb9f8b4-abcde 1/1 Running 0 1m
Running
상태가 확인되면 오퍼레이터 설치가 완료된 것입니다.
2️⃣ 단계: GPU를 지원하는 Ray 클러스터 배포하기
이제 KubeRay
가 알아들을 수 있는 RayCluster
명세(YAML)를 작성하여, GPU를 사용하는 Ray 클러스터를 생성해 보겠습니다.
아래는 제가 사용하는 기본적인 RayCluster
YAML 파일입니다.
raycluster-gpu.yaml
apiVersion: ray.io/v1
kind: RayCluster
metadata:
name: raycluster-llm-serving
spec:
rayVersion: '2.9.0' # 사용하는 Ray 버전을 명시합니다.
headGroupSpec:
rayStartParams:
dashboard-host: '0.0.0.0'
template:
spec:
containers:
- name: ray-head
image: rayproject/ray-ml:2.9.0-py310-gpu
ports:
- containerPort: 6379 # Ray Head 기본 포트
- containerPort: 8265 # Ray Dashboard 포트
- containerPort: 10001
resources:
limits:
cpu: "2"
memory: "8Gi"
requests:
cpu: "2"
memory: "8Gi"
workerGroupSpecs:
- replicas: 1
minReplicas: 1
maxReplicas: 5
groupName: gpu-worker-group
rayStartParams: {}
template:
spec:
# (추가!) 특정 레이블이 있는 노드에만 파드를 할당합니다.
nodeSelector:
gpu: "true" # 'gpu=true' 레이블이 있는 노드를 타겟팅
# GPU 노드에만 파드가 스케줄링되도록 설정
tolerations:
- key: "[nvidia.com/gpu](https://nvidia.com/gpu)"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: ray-worker
image: rayproject/ray-ml:2.9.0-py310-gpu
# GPU 자원을 요청하는 핵심 부분!
resources:
limits:
cpu: "64"
memory: "128Gi"
nvidia.com/gpu: "8" # 1개의 GPU를 요청합니다. (https://nvidia.com/gpu)
requests:
cpu: "32"
memory: "64Gi"
nvidia.com/gpu: "8" # (https://nvidia.com/gpu)
이제 이 YAML 파일을 쿠버네티스에 제출해 봅시다.
kubectl apply -f raycluster-gpu.yaml
몇 분 정도 기다린 후, 파드들이 정상적으로 생성되었는지 확인합니다.
kubectl get pods
출력 결과
NAME READY STATUS RESTARTS AGE
raycluster-llm-serving-head-g9plz 1/1 Running 0 2m
raycluster-llm-serving-worker-gpu-g-1-j7c82 1/1 Running 0 2m
Head 파드 1개와 Worker 파드 1개가 Running
상태라면 쿠버네티스 위에 GPU Ray 클러스터 구축이 완료된 것입니다.
💡 안정적인 GPU 스케줄링을 위한 팁: nodeSelector
활용하기
GPU 워커 파드를 원하는 GPU 노드에 안정적으로 할당하기 위해서는 몇 가지 쿠버네티스 스케줄링 개념을 활용하는 것이 좋습니다.
resources
로 GPU 명시적으로 요청하기 (필수)
가장 기본입니다. 파드가 GPU를 필요로 한다는 사실을 쿠버네티스에 알려야 합니다.spec.containers.resources
필드에nvidia.com/gpu: "1"
과 같이 필요한 GPU 수를 명시해야만 쿠버네티스 스케줄러가 GPU가 있는 노드를 탐색하기 시작합니다.Tolerations
로 Taint가 걸린 노드 접근 허용하기
보통 GPU와 같이 특별한 하드웨어가 있는 노드에는 다른 일반 파드들이 스케줄링되지 않도록 'Taint'라는 표식을 붙여둡니다.Toleration
설정은 "나는 이 표식이 붙은 노드에 들어가도 괜찮아"라고 허용하는 역할을 합니다. 클러스터 환경에 따라 필요할 수 있습니다.nodeSelector
로 원하는 노드 콕 집어주기 (강력 추천)
Toleration
이 특정 노드에 들어갈 '자격'을 주는 것이라면,nodeSelector
는 특정 '레이블'이 붙은 노드로만 가도록 '지정'하는 강력한 방법입니다.
먼저, 파드를 할당하고 싶은 GPU 노드에 식별 가능한 레이블을 붙여줍니다.
예시: my-gpu-node-1 이라는 이름의 노드에 gpu=true 라는 레이블을 추가
kubectl label nodes my-gpu-node-1 gpu=true
그리고 위 raycluster-gpu.yaml
파일의 workerGroupSpecs.template.spec
에 nodeSelector
필드를 추가하면 됩니다.
...
template:
spec:
nodeSelector:
gpu: "true" # 이 부분!
...
이렇게 하면 KubeRay
는 워커 파드를 생성할 때 gpu=true
레이블이 붙은 노드 중에서만 찾게 되므로, 의도치 않게 다른 노드에 파드가 할당되는 것을 원천적으로 방지할 수 있습니다.
다음 장을 향하여
이제 기반이 되는 클러스터가 준비되었습니다. 다음 3부에서는 이 클러스터 위에서 온라인 서빙을 위한 RayService
와 일회성 작업을 위한 RayJob
**을 어떻게 활용하는지, 그리고 그 차이는 무엇인지 알아보겠습니다.
Top comments (0)