TT Lab
블로그 시작하기
배우기 러닝패스 코스

쿠버네티스 GPU 스케줄링과 AI 배치

Kueue 로 빌려 쓰고, 되찾고, 앞질러 간다

TT Lab 에서 이어서 보기

이 실습은 진짜 쿠버네티스에서 돕니다

VM 안에 k3s 한 대가 떠 있고, 가짜 GPU 4장과 Kueue 가 올라 있습니다. ResourceFlavor fake-gpu 와 네임스페이스 team-a, team-b 는 미리 만들어 두었습니다. 처음 뜨는 데 3~4분 걸립니다. 세션이 끝나면 VM 과 파일은 사라집니다.

목표

cohort 안의 빌려 쓰기·되찾기·앞지르기와 큐 멈춤을 차례로 일으키고, 그 결정이 어디에 어떤 문장으로 남는지 읽습니다.

왜 중요한가

공유 GPU 클러스터에서 가장 많이 받는 문의는 「내 작업이 왜 쫓겨났나」 와 「왜 아직 안 들어가나」 입니다. 정책이 정상으로 돌아도 당사자에게는 사고로 보입니다. 이 실습에서 일부러 쫓아내고 멈춰 보면, Workload 의 조건과 visibility API 만으로 그 질문에 답할 수 있게 됩니다.

단계

  1. cohort lab 의 ClusterQueue team-a·team-b 와 LocalQueue lq 둘을 /root/gsb-share/cqs.yaml 로 만듭니다.
  2. team-a 에 1장짜리 Job a-1·a-2·a-3 을 넣고, 빌린 GPU 수를 /root/gsb-share/borrow.txt 에 borrowed= 로 적습니다.
  3. team-b 에 2장짜리 Job b-1 을 넣고, 내보내진 team-a 의 Job 이름을 /root/gsb-share/reclaim.txt 에 preempted= 로 적습니다.
  4. WorkloadPriorityClass urgent(1000)를 /root/gsb-share/wpc.yaml 로 만들고 그 우선순위의 Job a-urgent 를 team-a 에 넣어 입장시킵니다.
  5. team-b 의 stopPolicy 를 Hold 로 바꾸고 Job b-2 를 넣어, 그 사유를 /root/gsb-share/hold.txt 에 담습니다.
  6. visibility API 로 team-a 의 대기열을 받아 /root/gsb-share/pending.json 에 저장합니다.
  7. 두 큐의 장부를 /root/gsb-share/state.json 에 적습니다.
  8. team-b 의 stopPolicy 를 None 으로 되돌리고, b-2 의 사유가 쿼터 부족으로 바뀌는 것을 봅니다.

참고

두 큐를 cohort 로 묶는다

ClusterQueue team-a, team-b(둘 다 cohortName: lab, 플레이버 fake-gpu 에서 cpu 2·memory 2Gi·nvidia.com/gpu 2, preemption.reclaimWithinCohort: Any, preemption.withinClusterQueue: LowerPriority)와 각 네임스페이스의 LocalQueue lq 를 「/root/gsb-share/cqs.yaml」 한 파일에 적어 적용하세요.

ResourceFlavor fake-gpu 는 미리 만들어 두었습니다. cohortName 이 같아야 서로 빌려 쓸 수 있습니다.

놀던 쿼터를 빌려 쓴다

team-a 에 GPU 1장짜리 Job a-1, a-2, a-3 을 넣으세요. 쿼터는 2장이지만 team-b 가 놀고 있어 셋 다 입장합니다. ClusterQueue team-a 의 장부에서 빌린 GPU 수를 읽어 「/root/gsb-share/borrow.txt」 에 borrowed= 한 줄로 적으세요.

ClusterQueue status 의 flavorsReservation(또는 flavorsUsage) 안에 자원마다 total 과 borrowed 가 있습니다.

주인이 돌아오면 되찾는다

team-b 에 GPU 2장짜리 Job b-1 을 넣으세요. team-b 는 자기 몫 2장을 되찾고, team-a 의 Job 하나가 내보내집니다. 내보내진 Job 의 이름을 「/root/gsb-share/reclaim.txt」 에 preempted= 한 줄로 적으세요.

내보내진 Workload 의 조건에 이유(InCohortReclamation)가 남습니다. 그 Workload 의 주인(ownerReferences)이 Job 입니다.

같은 큐 안에서 급한 작업이 앞질러 간다

WorkloadPriorityClass urgent(value 1000)를 「/root/gsb-share/wpc.yaml」 로 만들고, 그 우선순위를 붙인 GPU 1장짜리 Job a-urgent 를 team-a 에 넣으세요. team-a 는 쿼터가 꽉 차고 빌릴 곳도 없어, 우선순위가 낮은 team-a 의 작업 하나가 내보내지고 a-urgent 가 입장해야 합니다.

우선순위는 Job 의 라벨 kueue.x-k8s.io/priority-class 로 붙입니다. 이번에 남는 이유는 InClusterQueue 입니다.

정비 전에는 큐를 멈춘다

ClusterQueue team-b 의 stopPolicy 를 Hold 로 바꾸고, GPU 1장짜리 Job b-2 를 team-b 에 넣으세요. b-2 는 입장하지 못하고 b-1 은 계속 돕니다. b-2 의 Workload 가 남긴 사유를 「/root/gsb-share/hold.txt」 에 담으세요.

kubectl patch 로 spec.stopPolicy 를 바꿉니다. Hold 는 새 입장만 막고 이미 입장한 작업은 건드리지 않습니다.

대기열의 순서를 묻는다

visibility API 로 ClusterQueue team-a 의 대기 중인 Workload 목록을 받아 응답 그대로 「/root/gsb-share/pending.json」 에 저장하세요.

kubectl get --raw 로 /apis/visibility.kueue.x-k8s.io/v1beta2/clusterqueues/<이름>/pendingworkloads 를 부릅니다. 응답의 items 에 이름과 positionInClusterQueue 가 있습니다.

두 큐의 장부를 정리한다

두 ClusterQueue 의 장부를 「/root/gsb-share/state.json」 에 {"team-a": {...}, "team-b": {...}} 꼴로 적으세요. 큐마다 admitted(입장한 Workload 수), pending(기다리는 수), borrowed_gpu(빌려 쓰는 GPU 수) 세 키입니다.

status.admittedWorkloads, status.pendingWorkloads, 그리고 flavorsReservation 의 nvidia.com/gpu borrowed 입니다.

멈춤을 풀어도 쿼터는 그대로다

team-b 의 stopPolicy 를 None 으로 되돌리세요. b-2 는 이제 멈춤이 아니라 「쿼터 부족」 때문에 기다립니다. 두 사유가 어떻게 다른지 Workload 의 조건에서 확인하세요.

되돌린 뒤 b-2 의 QuotaReserved 메시지를 다시 읽어 보세요. cohort 전체 4장이 이미 쓰이고 있습니다.