Kueue 로 빌려 쓰고, 되찾고, 앞질러 간다
이 실습은 진짜 쿠버네티스에서 돕니다
VM 안에 k3s 한 대가 떠 있고, 가짜 GPU 4장과 Kueue 가 올라 있습니다. ResourceFlavor fake-gpu 와 네임스페이스 team-a, team-b 는 미리 만들어 두었습니다. 처음 뜨는 데 3~4분 걸립니다. 세션이 끝나면 VM 과 파일은 사라집니다.
목표
cohort 안의 빌려 쓰기·되찾기·앞지르기와 큐 멈춤을 차례로 일으키고, 그 결정이 어디에 어떤 문장으로 남는지 읽습니다.
왜 중요한가
공유 GPU 클러스터에서 가장 많이 받는 문의는 「내 작업이 왜 쫓겨났나」 와 「왜 아직 안 들어가나」 입니다. 정책이 정상으로 돌아도 당사자에게는 사고로 보입니다. 이 실습에서 일부러 쫓아내고 멈춰 보면, Workload 의 조건과 visibility API 만으로 그 질문에 답할 수 있게 됩니다.
단계
- cohort
lab의 ClusterQueueteam-a·team-b와 LocalQueuelq둘을/root/gsb-share/cqs.yaml로 만듭니다. - team-a 에 1장짜리 Job
a-1·a-2·a-3을 넣고, 빌린 GPU 수를/root/gsb-share/borrow.txt에borrowed=로 적습니다. - team-b 에 2장짜리 Job
b-1을 넣고, 내보내진 team-a 의 Job 이름을/root/gsb-share/reclaim.txt에preempted=로 적습니다. - WorkloadPriorityClass
urgent(1000)를/root/gsb-share/wpc.yaml로 만들고 그 우선순위의 Joba-urgent를 team-a 에 넣어 입장시킵니다. - team-b 의 stopPolicy 를 Hold 로 바꾸고 Job
b-2를 넣어, 그 사유를/root/gsb-share/hold.txt에 담습니다. - visibility API 로 team-a 의 대기열을 받아
/root/gsb-share/pending.json에 저장합니다. - 두 큐의 장부를
/root/gsb-share/state.json에 적습니다. - team-b 의 stopPolicy 를 None 으로 되돌리고, b-2 의 사유가 쿼터 부족으로 바뀌는 것을 봅니다.
참고
kubectl get clusterqueues의 열(PENDING WORKLOADS, ADMITTED WORKLOADS)과kubectl get workloads -A를 자주 봅니다.- 내보내진 Workload 는 지워지지 않고 다시 기다립니다. 조건의 reason 이 InCohortReclamation 이면 되찾기, InClusterQueue 면 같은 큐 안의 앞지르기입니다.
- 흔한 실수 1: 4단계에서 파드의 PriorityClass 를 쓰는 것. Kueue 의 입장 순서는 WorkloadPriorityClass 라벨로 정합니다.
- 흔한 실수 2: 5단계에서 HoldAndDrain 을 고르는 것. 그러면 b-1 까지 내보내집니다.
두 큐를 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장이 이미 쓰이고 있습니다.