상태: 초안 (2026-10-07 갱신). 공식 문서와 논문으로 확인한 서술이 바탕이고, **3절 끝의 두 실측(갱 스케줄링 비교, 코호트 차용과 선점)**은 일회용 단일 노드 클러스터에서 직접 잰 값입니다. 나머지 절은 아직 실측하지 않았습니다(미실측). 글 끝의 「확인한 버전과 날짜」와 「확인하지 못한 것」을 먼저 읽으십시오. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」으로 표시합니다.
멀티테넌트 GPU 스케줄링과 용량 계획
GPU 클러스터를 여러 팀이 나눠 쓰려면 세 가지가 필요합니다. 팀별 몫을 보장하되 남는 자원은 빌려 쓰게 하는 쿼터, 분산 학습처럼 여럿이 함께 떠야 하는 작업을 위한 갱 스케줄링, 서빙을 지키면서 배치 작업을 밀어내는 선점 정책입니다. 기본 스케줄러는 이 중 어느 것도 직접 제공하지 않습니다. 이 글은 보완 수단의 설계 차이와 격리 한계, 용량을 계산하는 순서를 다룹니다.
이 글을 읽고 나면 면접에서 설명할 수 있는 것
- 기본 스케줄러의 GPU 모델(정수 확장 자원)이 갱 스케줄링과 공정 공유에 부족한 이유, 그리고 Kueue는 승인 계층이고 Volcano는 스케줄러라는 차이.
- time-slicing, MIG, MPS가 나누는 것과 격리하지 못하는 것, 신뢰 수준별 선택.
- 요청률과 지연 목표에서 GPU 수를 역산하는 순서(처리량, 동시성, 키-값(KV) 캐시, 여유율, 장애 대비)와 사용률 지표의 함정.
1. 기본 스케줄러의 한계
- 정수 확장 자원: 디바이스 플러그인 문서는 확장 자원이 정수로만 지원되고 과약정할 수 없으며 컨테이너 간에 공유할 수 없다고 적습니다. 요청과 한도는 같아야 합니다.
- 파드 단위 결정: kube-scheduler는 파드 하나씩 필터링과 점수로 노드를 고릅니다. GPU 8개짜리 작업의 파드 8개 중 6개만 자리를 얻으면 6개가 GPU를 쥔 채 나머지를 기다립니다. 갱 스케줄링이 필요한 이유입니다.
- 네이티브 갱 스케줄링은 아직 기본이 아닙니다: Kubernetes 1.37에서
PodGroupAPI(scheduling.k8s.io/v1beta1)와GenericWorkload게이트가 베타이지만 기본 꺼짐입니다. 문서는 최소 개수(minCount) 보장이 초기 배치에만 해당한다고 적습니다. - 큐와 공정성이 없습니다:
ResourceQuota는requests.nvidia.com/gpu처럼 네임스페이스 총량만 제한하며 대기열, 차용, 반환이 없습니다. - 선점은 파드 단위입니다: 희생 파드는 정상 종료 유예 시간을 가지므로 자리가 비는 데 시간이 걸리고, PodDisruptionBudget 준수는 최선 노력입니다.
preemptionPolicy: Never는 다른 파드를 선점하지 않는다는 뜻이지 자신이 선점당하지 않는다는 뜻이 아닙니다.
2. Kueue: 승인 계층
flowchart LR
J["Job (queue-name 레이블)"] --> LQ["LocalQueue (네임스페이스)"]
LQ --> CQ["ClusterQueue (쿼터, cohort)"]
CQ -- "쿼터 예약과 승인" --> W["Workload 승인, 파드 생성"]
W --> KS["kube-scheduler가 노드 배치"]
KS --> DP["디바이스 플러그인 또는 DRA"]
Kueue 문서는 파드를 노드에 배치하는 일은 kube-scheduler가, 오토스케일은 cluster-autoscaler가 맡는다고 적습니다. Kueue는 작업을 언제 시작(파드 생성)하고 언제 멈출지를 결정합니다.
| 개념 | 역할 |
|---|---|
| ResourceFlavor | 노드 그룹의 특성(GPU 모델 등)을 레이블과 톨러레이션으로 표현 |
| ClusterQueue | 자원 풀. 쿼터(nominalQuota, borrowingLimit, lendingLimit), 선점, 공정 공유 규칙 |
| LocalQueue | 팀 네임스페이스에서 작업을 제출하는 지점 |
| cohort | 미사용 쿼터를 서로 빌려 주는 ClusterQueue 묶음 |
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata: {name: team-a}
spec:
cohortName: all-teams
queueingStrategy: BestEffortFIFO
preemption: {reclaimWithinCohort: LowerPriority, withinClusterQueue: LowerPriority}
resourceGroups:
- coveredResources: ["nvidia.com/gpu"]
flavors:
- name: gpu-pool
resources: [{name: nvidia.com/gpu, nominalQuota: 8, borrowingLimit: 4}]
Job은 kueue.x-k8s.io/queue-name 레이블로 LocalQueue를 고르고, 우선순위는 파드와 분리된 WorkloadPriorityClass(레이블 kueue.x-k8s.io/priority-class)로 줍니다. 선점은 우선순위 기반의 기본 방식과, 지배 자원 점유율(DRS)을 비교하는 공정 공유 방식이 있습니다. 최신 v0.20.0(2026-09-30)은 v1beta1을 더 이상 제공하지 않고 v1beta2만 제공합니다. Deployment, StatefulSet, LeaderWorkerSet도 지원하므로 서빙과 학습을 함께 관리할 수 있습니다. 멀티클러스터 디스패치인 MultiKueue는 베타입니다.
waitForPodsReady는 제한 시간 안에 모든 파드가 준비되지 않으면 작업을 중단하고 대기열로 되돌립니다. 문서는 이를 시간 제한 기반 구현이라 부르며 순차 승인 때문에 승인이 느려질 수 있다고 밝힙니다. 이 글은 네이티브 갱 스케줄링과 구분해 다룹니다.
3. Volcano와 선택 기준
Volcano는 별도 배치 스케줄러입니다. PodGroup의 minMember가 갱 단위이고, Queue는 weight(상대 몫), capability(상한), guarantee, reclaimable을 가집니다. 스케줄러는 enqueue, allocate, preempt, reclaim, backfill 액션을 돌리며 gang, priority, drf(지배 자원 공정성, DRF), proportion, binpack 같은 플러그인을 설정 파일의 계층으로 구성합니다. v1.15.0에서 갱 인식 선점이 알파로 들어왔고 최신 안정 라인은 v1.15.3(2026-09-30)입니다. NVIDIA의 KAI Scheduler도 별도 스케줄러로 계층 큐, 시간 기반 공정 공유, 토폴로지 인식을 내세웁니다(최신 버전은 확인하지 못했습니다).
| 기준 | Kueue | Volcano |
|---|---|---|
| 위치 | 승인 컨트롤러, kube-scheduler 유지 | 별도 스케줄러 |
| 갱 | 시간 제한 방식 | PodGroup 최소 개수 보장 |
| 공정 공유 | cohort 차용, DRS 공정 공유 | 큐 가중치, DRF, proportion |
기본 스케줄러를 두고 팀별 쿼터와 차용이 목적이면 Kueue가, 갱과 큐를 스케줄러 안에서 강하게 쓰려면 Volcano나 KAI가 후보입니다. 둘을 같은 파드에 겹쳐 쓰는 구성의 지원 범위는 확인하지 못했습니다.
실측: 파드 4개가 함께 떠야 하는 작업 둘이 경쟁하면
1절의 「파드 단위 결정」이 실제로 교착을 만드는지, 두 도구가 이를 어떻게 푸는지 직접 재 보았습니다.
환경과 방법. 4 vCPU, 4GiB 가상 머신 안의 일회용 단일 노드 k3s(v1.36.5)에서, 노드 상태에 확장 자원 example.com/gpu 8개를 광고했습니다(실제 GPU 는 없고 파드는 sleep 만 합니다). 작업 하나는 파드 4개이고 파드마다 자원 2개를 요청하므로 작업 하나가 8개를 다 씁니다. 작업 A, B 를 동시에 제출하면 수요 16, 공급 8 입니다. 「한 작업의 파드 4개가 모두 Running 이 된 시각」을 재고, 받아들여진 작업을 지운 뒤 대기하던 작업이 4개 모두 뜨기까지의 시간도 쟀습니다(파드 종료 유예 0초, 반복 3회). Kueue v0.20.0, Volcano v1.15.3 를 각 매니페스트로 설치했습니다.
| 방식 | 결과 |
|---|---|
| 기본 스케줄러(파드를 A, B 번갈아 생성) | 60초 동안 A 2개, B 2개만 실행되고 멈춤. 두 작업 모두 4개를 채우지 못해 교착(대기 사유: 확장 자원 부족, 선점할 파드 없음) |
| Kueue(작업을 일시 중지 상태로 제출) | 한 작업만 받아들여져 2.1초(3회 모두) 안에 4개 실행, 다른 작업은 파드가 아예 생성되지 않음. 받아들여진 작업을 지우면 3.0~3.6초 뒤 대기 작업 4개 실행 |
Volcano(minAvailable: 4) |
한 작업만 3.3~4.1초 안에 4개 실행, 다른 작업은 실행 0개. 받아들여진 작업을 지우면 3.6~4.2초 뒤 대기 작업 4개 실행 |
- 교착은 실제로 일어납니다. 파드가 번갈아 생성되면 기본 스케줄러는 두 작업에 자원을 절반씩 나눠 주고, 남은 파드는 영원히 자리를 얻지 못합니다. 자원을 쥔 채로 서로를 기다리는 모양입니다. 파드 생성 순서가 한 작업씩 몰아서 오면 걸리지 않을 수 있어서, 이는 최악에 가까운 순서에서의 결과입니다(순서를 바꿔 시험하지는 않았습니다).
- 두 도구 모두 「전부 아니면 전무」로 동작했습니다. 일부만 실행된 순간은 한 번도 관찰되지 않았습니다. 방식은 달라서, Kueue 는 작업 단위로 쿼터를 확인해 받아들일 때까지 파드를 만들지 않고(작업이 일시 중지 상태), Volcano 는 파드 그룹의 최소 개수를 채울 수 있을 때만 스케줄러가 한꺼번에 자리를 줍니다.
- 받아들여진 쪽이 매번 같지는 않았습니다. 처음 시험은 B, 이후 세 번은 A 가 먼저 받아들여졌습니다. 같은 우선순위에서 제출이 거의 동시에 일어나면 순서에 기대면 안 됩니다. 순서가 중요하면 우선순위를 명시합니다.
- 설치 비용. 두 도구 모두 매니페스트 적용부터 준비 완료까지 약 20초였습니다(이미지를 처음 받는 시간 포함, 각 1회).
- 시험의 오류(기록). 처음 시험에서 Volcano 의 대기 시간이 35초로 나왔는데,
sleep이 종료 신호를 무시해 파드가 30초 유예 시간을 다 쓰고 끝난 시간이 섞인 값이었습니다. 유예를 0초로 두고 다시 재서 위의 3~4초를 얻었습니다. 선점이나 반환 시간을 잴 때는 종료 유예가 시간을 지배한다는 1절의 서술(희생 파드의 정상 종료 유예)과 같은 이야기입니다.
이 값은 노드 하나, 가짜 자원, 작업 2개, 이미지가 이미 있는 가벼운 컨테이너라는 조건에서만 의미가 있습니다. 노드가 많아지면 스케줄러 처리량과 위치 선택이 비용을 좌우합니다. 또 Kueue 가 교착을 피한 것은 쿼터(자원 8개)가 실제 가용 자원과 맞았기 때문으로 보입니다. Kueue 문서는 쿼터와 실제 가용 자원이 맞지 않으면 교착할 수 있다고 적습니다. 프로덕션의 복구 시간 척도는 분 단위입니다(아래 외부 근거의 Philly 2~3분 타임아웃과 Kueue 30분 기본 제한 시간). 이 글의 2~4초는 경쟁이 없는 정상 경로의 값입니다.
실측 2: 코호트 차용, 반환, 우선순위 선점
위 환경(단일 노드 k3s, 확장 자원 8개, sleep 작업, 종료 유예 0초)에서 이번에는 파드 하나(자원 1개)짜리 작업을 여러 개 올려 큐 정책을 확인했습니다. Kueue v0.20.0 이고, 팀 둘(team-a, team-b)이 쿼터 4개씩 가지며 서로 남는 쿼터를 빌릴 수 있는 코호트입니다.
| 상황 | 결과 |
|---|---|
reclaimWithinCohort: Never. team-a 가 8개를 올려 쿼터 4에 team-b 몫 4를 더 빌림 |
8개 모두 3.7초 안에 실행. 이어 team-b 가 자기 쿼터 4를 요구하면 60초 동안 0개(쫓겨난 작업 0건). team-a 작업 4개를 끝내자 3.1초 뒤 team-b 4개 실행 |
reclaimWithinCohort: Any. 같은 상황 |
team-a 8개 1.8초. team-b 4개가 4.2초 안에 실행되고 team-a 는 4개로 줄었으며 쫓겨난 작업이 4건(이벤트 Preempted to accommodate a workload). team-b 를 끝내자 team-a 8개가 3.0초 뒤 복귀 |
withinClusterQueue: LowerPriority, 쿼터 4, 낮은 우선순위 4개 실행 중 |
높은 우선순위 2개가 3.0초 안에 실행되고 낮은 우선순위는 2개만 남음. 쫓겨난 작업은 일시 중지 상태로 돌아가 대기. 높은 우선순위를 지우자 낮은 우선순위 4개가 3.0초 뒤 복귀 |
Volcano v1.15.3 기본 설정(actions: "enqueue, allocate, backfill"), 큐 둘 qa, qb(가중치 같음, reclaimable: true) |
qa 가 8개를 다 쓴 뒤 qb 가 4개를 요구하면 45초 동안 0개. 기본 설정에는 reclaim 액션이 없기 때문 |
같은 Volcano 에서 actions 에 reclaim 추가 |
qb 4개가 10.3초 안에 실행되고 qa 는 4개로 줄었음(이벤트 Evict ... reclaim). qb 를 지우자 qa 8개가 1.8초 뒤 복귀 |
- 차용한 몫을 돌려받는 것은 기본 동작이 아닙니다. Kueue 는
reclaimWithinCohort를Never로 두면 빌려 간 쪽이 스스로 끝낼 때까지 기다리고(여기서는 60초 넘게 대기), Volcano 는 기본 스케줄러 설정에reclaim이 없어 선점이 일어나지 않았습니다. 5절의 「서빙이 차용분을 되찾으면 배치가 밀려난다」는 설계는 이 설정을 켜야 성립합니다. - 되찾는 시간은 몇 초에서 십여 초였고, 종료 유예가 0 이라서 그렇습니다. 실제 학습 작업은 체크포인트를 저장하며 종료하므로 유예만큼 더해집니다(위 시험의 오류 기록과 같은 이유).
- 쫓겨난 작업은 사라지지 않고 큐로 돌아와 자원이 비면 모두 다시 실행되었습니다(3.0초, 1.8초).
- 작은 작업을 대량으로 올릴 때는 Kueue 가 더 느렸습니다. 자원 8개, 작업 하나가 자원 1개를 1초 쓰는 작업 N개를 한 번에 제출해 전부 끝나기까지를 쟀습니다.
| 작업 수 | 기본 스케줄러(확장 자원 8로 제한) | Kueue(큐 하나, 쿼터 8) | 이상적 값 |
|---|---|---|---|
| 100 | 55.0초 | 74.0초(+35%) | 약 12초 |
| 400 | 231.0초 | 약 343초(+48%) | 약 50초 |
Kueue 의 400개는 30초마다 진행을 찍은 별도 시험(약 1.3개/초로 일정하게 완료)에서 잰 값입니다. 처음 시험은 완료 개수를 세는 방식의 오류로 값을 얻지 못했습니다. 두 방식 모두 이상적 값(자원 8개가 쉬지 않을 때)의 5~7배라서, 시간을 지배한 것은 스케줄러가 아니라 이 4 vCPU VM 에서 파드를 만들고 지우는 비용이었고, Kueue 는 작업 하나당 약 0.2~0.3초의 승인 처리를 더했습니다(계산). 소규모에서 이 정도 차이는 파드 시작 시간에 묻히지만, 짧은 작업을 초당 수십 개씩 올리는 워크로드라면 큐 계층이 처리량 한도가 될 수 있다는 뜻입니다.
- 한계. 작업 수가 수십 개 이하(대량 시험은 최대 400개)이고 노드가 하나이며 가짜 자원입니다. 큐 공정성의 장기 동작(차용 한도, 우선순위가 섞인 대기열)은 재지 않았습니다(미실측).
4. 토폴로지 인식 배치
노드 안 NVLink와 노드 사이 InfiniBand(IB) 근접성이 학습 통신 속도를 좌우합니다. Kueue의 Topology Aware Scheduling(TAS)은 v0.14부터 베타이며 기본 켜짐입니다. 노드 레이블로 블록과 랙 같은 계층을 Topology에 정의해 ResourceFlavor.spec.topologyName으로 연결하고, 파드 어노테이션 podset-required-topology나 podset-preferred-topology로 요구 수준을 정합니다. 모든 파드와 노드를 추적하므로 메모리 사용량이 늘어난다고 문서가 밝힙니다. Volcano는 HyperNode(문서 예시 API는 topology.volcano.sh/v1alpha1)로 티어를 정의하고 작업에 하드 또는 소프트 제약을 줍니다. 다중 노드 NVLink는 NVIDIA DRA 드라이버의 ComputeDomain이 공식 지원 기능입니다.
5. 우선순위와 선점 설계
서빙에는 보장 쿼터와 높은 우선순위를, 배치 학습에는 낮은 우선순위와 차용 쿼터를 줍니다. 서빙이 차용분을 되찾으면(reclaimWithinCohort) 배치가 밀려나므로 배치는 체크포인트로 중단을 견뎌야 합니다. 희생 파드의 종료 유예 시간은 재스케줄 지연이 되니 체크포인트 저장 시간만 남기고 줄입니다. 또한 Kubernetes 문서에 따르면 스케줄러는 DRA 자원을 쓰는 파드를 선점하지 못합니다.
6. GPU 공유와 격리의 한계
| 방식 | 나누는 것 | 격리 |
|---|---|---|
| time-slicing | 시간 | 메모리·장애 격리 없음, 모든 프로세스에 동등한 시간 배분 |
| MIG(Multi-Instance GPU) | 하드웨어 분할 | 메모리와 장애 격리, 최대 7개 인스턴스 |
| MPS(Multi-Process Service) | 스트리밍 멀티프로세서(SM) 동시 실행 | 치명적 오류가 같은 GPU의 다른 클라이언트에 영향을 줄 수 있음 |
time-slicing은 replicas만큼 자원을 부풀려 광고하며, DCGM(Data Center GPU Manager) 익스포터는 이 모드에서 컨테이너별 지표를 연결하지 못합니다(GPU Operator 문서). MIG는 Ampere 세대 이후 일부 GPU(A100, H100, B200 등)에서 지원되고 확인한 NVIDIA 지원 목록에 소비자용 GPU는 없었으며, 구성을 바꾸려면 해당 GPU에서 사용자 워크로드가 없어야 합니다. MPS는 드라이버 r610부터 정적 SM 분할을 켜면 부분 격리가 되고, 모니터링 도구는 클라이언트 사용량을 MPS 서버 프로세스로 합산합니다. 그래서 서로 신뢰하지 않는 테넌트에는 MIG나 전용 노드를, 같은 팀의 가벼운 작업에는 time-slicing이나 MPS를 씁니다. Kueue 쿼터는 회계 장치이지 보안 경계가 아닙니다.
7. Dynamic Resource Allocation(DRA)의 현재 상태
DRA는 ResourceClaim, DeviceClass, ResourceSlice로 장치를 속성(공통 표현 언어(CEL) 표현식)으로 요청하게 합니다. 기능 게이트 데이터(2026-10-05 확인)는 다음과 같습니다.
| 기능 | 단계 |
|---|---|
| 핵심 DRA | stable (1.34, 게이트는 1.35에 잠김, 공식 페이지는 "Stable since v1.35"로 표기) |
| 우선순위 목록, 관리자 접근 | stable (1.36) |
| 기존 확장 자원 이름 매핑, 장치 테인트 | stable (1.37) |
| 분할 가능 장치, 소비 가능 용량, 바인딩 조건 | beta (1.36) |
| 워크로드 단위 ResourceClaim | beta (1.37, 기본 꺼짐) |
| 노드 할당 가능 자원, 호환 그룹 | alpha |
NVIDIA DRA 드라이버(kubernetes-sigs/dra-driver-nvidia-gpu, v0.5.0, 2026-08-19)의 README(main)는 ComputeDomain을 공식 지원이라 하고, GPU 할당은 공식 지원이 아니며 차트에서 기본 꺼져 있다고 적습니다. 문서 사이트는 MIG, time-slicing, MPS를 지원 기능으로 나열하나 단계를 밝히지 않아 어느 쪽이 최신인지 확인하지 못했습니다. Kueue v0.20 릴리스 노트에는 DRA 장치 가용성 검사가 알파 게이트로 있고, Volcano v1.15는 큐 쿼터에 DRA 자원 계산을 넣었습니다.
8. 용량 계획
λ(요청률), 입출력 길이, 지연 목표 -> 복제본 1개의 실측 처리량과 동시성
-> 처리량 기준 대수, 동시성(Little 법칙) 기준 대수, 키-값(KV) 캐시 한도 확인
-> 큰 쪽 선택 -> 여유율 -> 장애 대비(N+k) -> 병렬화(텐서 병렬, TP) 단위로 올림
설명용 예시입니다. 기준점은 필자의 실험 환경에서 잰 값으로, 24GB급 소비자용 GPU에서 Qwen3.8-27B(NVFP4)가 동시 4요청 합산 138 tok/s이고 KV 캐시가 약 57,000토큰입니다. 요청률 0.5 req/s, 입력 1,500토큰, 출력 400토큰을 가정합니다.
- 처리량: 0.5×400=200 tok/s ÷ 138 = 1.45이므로 2대.
- 동시성: 요청당 138÷4=34.5 tok/s이면 응답 시간 약 11.6초이고 Little 법칙으로 0.5×11.6≈5.8개가 동시에 진행됩니다. 복제본당 동시 4개 한도라 최소 2대(8칸)이며 칸 사용률은 약 72%입니다.
- KV 확인: 6×(1,500+400)=11,400토큰으로 57,000 이내입니다.
- 부하 변동 여유율 30%와 장애 대비 1대를 더해 3대로 잡습니다.
모델이 GPU 여러 장에 걸치면(텐서 병렬 TP) 복제 단위가 장 수만큼 커지므로 대수에 TP 크기를 곱하고, 노드 하나가 죽으면 복제본 단위로 용량이 줄어듭니다.
사용률 지표의 함정: NVML(NVIDIA Management Library)의 utilization.gpu는 샘플 구간에서 커널이 하나라도 실행된 시간의 비율이라 SM을 얼마나 쓰는지는 알려 주지 않습니다. DCGM의 SM_ACTIVE, PIPE_TENSOR_ACTIVE 같은 프로파일링 지표가 더 세밀합니다. 비용 관점에서는 할당된 GPU 시간 대비 실작업 시간(할당률)과, 서빙 지표(대기열 길이, KV 캐시 점유율, 목표 지연을 지킨 처리량)를 함께 봅니다.
소규모 공유에서 흔히 겪는 함정
필자의 실험 환경은 소비자용 GPU(6~24GB) 위주이고 다중 노드 학습이나 Kueue는 쓰지 않으므로, 이 절은 대규모 멀티테넌시의 증거가 아니라 소규모 공유에서 겪기 쉬운 함정을 일반화한 것입니다.
- time-slicing으로 메모리는 나뉘지 않습니다:
replicas를 늘리고failRequestsGreaterThanOne: true로 한 파드가 여러 몫을 요청하지 못하게 막아도 GPU 메모리(VRAM)는 나뉘지 않습니다. 한 파드가 VRAM을 다 쓰면 나머지가 메모리 부족(OOM)으로 죽습니다. 적용은 한 노드씩 하는 편이 안전합니다. 여러 노드에 한꺼번에 걸었다가 컨테이너 런타임 설정 문제로 GPU를 오래 쓰지 못하는 사고가 날 수 있기 때문입니다. - 한 카드를 나눠 쓴 서비스가 작업 단위 장애를 만듭니다: 서로 다른 서비스가 한 카드를 나눠 쓰면 OOM이 호출 일부를 실패시킵니다. 작업 하나가 수십 번의 호출로 이루어지면 호출 실패율이 작업 단위로 증폭되어 완성되는 작업이 거의 없어질 수 있습니다. 원리는 산술로 확인됩니다(설명용: 호출당 5%, 30번이면 성공률 약 21%). 카드 수에 맞게 서비스 수를 정리하고, 사용자가 직접 쓰는 서비스를 배치성 생산 작업보다 앞에 둔다는 우선순위를 기록해 두는 편이 좋습니다.
- 정적 배치: GPU가 노드당 하나이면 복제본이 겹칠 수 없으므로
nodeSelector로 노드를 고정하고 배포 전략을Recreate로 둡니다. 메모리 사용률 설정(vLLM의 GPU 메모리 사용률 등)은 드라이버나 WSL 같은 환경이 미리 점유하는 메모리 때문에 기본값으로 시작하지 못할 수 있어 낮춰야 하는 경우가 있습니다. - 신뢰하지 않는 파드의 GPU 쿼터는 0: 사용자 코드를 돌리는 네임스페이스의
ResourceQuota에requests.nvidia.com/gpu: "0"을 넣어 GPU 요청을 막습니다. 아래 「직접 해 보기」에서 이 동작을 확인합니다.
직접 해 보기
읽기 전용이거나 임시 네임스페이스에서 끝납니다. 이 글을 쓰며 실행해 검증하지는 않았습니다.
# 읽기 전용: 노드별 GPU 광고량과 할당 상태
kubectl get node -o custom-columns='NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu'
kubectl describe node NODE | grep -A8 "Allocated resources"
# 임시: 쿼터 0에서 거절, 쿼터 해제 후 Pending 사유 확인 (정리 포함)
pod() { cat <<EOF
apiVersion: v1
kind: Pod
metadata: {name: want-gpu}
spec:
containers:
- {name: c, image: busybox, command: ["sleep","60"], resources: {limits: {nvidia.com/gpu: $1}}}
EOF
}
kubectl create ns lab-gpu
kubectl -n lab-gpu create quota gpu-zero --hard=requests.nvidia.com/gpu=0
pod 1 | kubectl -n lab-gpu create --dry-run=server -f -
kubectl -n lab-gpu delete quota gpu-zero
pod 99 | kubectl -n lab-gpu create -f -
kubectl -n lab-gpu describe pod want-gpu | tail -8
kubectl delete ns lab-gpu
첫 요청은 쿼터 초과로 거절되고, 두 번째 파드는 Pending으로 남아 GPU 부족을 이벤트로 알려야 합니다. 어느 쪽도 GPU를 점유하지 않습니다. Kueue 실습은 다른 사람이 함께 쓰는 클러스터가 아니라 일회용 클러스터(kind 등)에서만 하십시오. 설치 명령(kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.20.0/manifests.yaml)은 클러스터 전체에 사용자 정의 리소스(CRD)를 추가합니다.
면접에서 나올 만한 질문
- 기본 스케줄러로 분산 학습을 돌리면 무엇이 문제입니까? 파드 단위로 배치되어 일부만 GPU를 쥔 채 교착될 수 있습니다. Volcano
PodGroup, 베타인 네이티브PodGroup, 또는 Kueue의 시간 제한 방식으로 보완하며, 마지막은 시간 제한 기반 구현임을 압니다. - Kueue와 Volcano를 어떻게 고릅니까? Kueue는 kube-scheduler를 유지한 채 쿼터, 차용, 공정 공유를 얹을 때 적합합니다. Volcano는 스케줄러를 새로 운영하더라도 갱, 큐, 토폴로지를 강하게 쓸 때 고릅니다.
- 서빙을 지키며 배치 학습을 돌리려면? 서빙에 보장 쿼터와 높은 우선순위를 주고 배치는 cohort에서 차용하게 합니다. 회수 시 선점되므로 체크포인트와 종료 유예를 설계합니다.
- GPU 공유 방식과 격리 한계는? time-slicing은 메모리·장애 격리가 없고, MIG는 하드웨어 격리이나 지원 GPU가 제한되며, MPS는 오류가 전파될 수 있습니다. 신뢰하지 않는 테넌트에는 MIG나 전용 노드를 씁니다. DRA는 이를 속성 기반으로 표현하는 방향이며 기능별 성숙도가 다릅니다.
- 요청률과 지연 목표에서 GPU 수를 어떻게 구합니까? 처리량, Little 법칙의 동시성, KV 캐시 한도를 각각 계산해 큰 쪽을 고르고 여유율, N+k, 병렬화 단위로 올립니다. 사용률은
utilization.gpu만 보지 않고 SM 활성도와 서빙 지표를 함께 봅니다.
흔한 오해
- Kueue는 스케줄러가 아닙니다. 파드를 노드에 배치하지 않습니다.
- time-slicing으로 메모리가 나뉘지 않습니다. 광고된 개수는 격리가 아니라 동시 접근 허용 수입니다.
- GPU 사용률 100%는 포화가 아닙니다. 커널이 하나라도 돌았다는 뜻일 수 있습니다.
- 네이티브 갱 스케줄링이 베타로 들어왔다고 Kueue나 Volcano가 불필요해지지 않았습니다. 기본 꺼짐이고 큐와 공정 공유는 따로입니다.
참고 자료
- https://kubernetes.io/docs/concepts/scheduling-eviction/gang-scheduling/
- https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
- https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
- https://kubernetes.io/docs/concepts/scheduling-eviction/dynamic-resource-allocation/
- https://github.com/kubernetes/website/tree/main/content/en/docs/reference/command-line-tools-reference/feature-gates
- https://kueue.sigs.k8s.io/docs/overview/
- https://kueue.sigs.k8s.io/docs/concepts/cluster_queue/
- https://kueue.sigs.k8s.io/docs/concepts/preemption/
- https://kueue.sigs.k8s.io/docs/concepts/topology_aware_scheduling/
- https://kueue.sigs.k8s.io/docs/concepts/multikueue/
- https://kueue.sigs.k8s.io/docs/tasks/manage/setup_wait_for_pods_ready/
- https://github.com/kubernetes-sigs/kueue/releases/tag/v0.20.0
- https://volcano.sh/en/docs/podgroup/
- https://volcano.sh/en/docs/queue/
- https://volcano.sh/en/docs/network_topology_aware_scheduling/
- https://github.com/volcano-sh/volcano/releases/tag/v1.15.0
- https://github.com/kai-scheduler/KAI-Scheduler
- https://github.com/kubernetes-sigs/dra-driver-nvidia-gpu
- https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html
- https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html
- https://docs.nvidia.com/datacenter/tesla/mig-user-guide/supported-gpus.html
- https://docs.nvidia.com/deploy/mps/architecture.html
- https://docs.nvidia.com/deploy/nvml-api/api/structnvmlUtilization__t.html
- https://docs.nvidia.com/datacenter/dcgm/latest/user-guide/feature-overview.html
외부 근거
직접 재지 못한 규모와 교착의 실제 빈도는 아래 자료로 확인했습니다. 논문 수치는 PDF 원문에서 직접 확인했고, 요약만 있는 수치는 쓰지 않았습니다.
| 주장 | 자료 | 확인한 내용 |
|---|---|---|
| 일부만 자원을 얻고 기다리는 문제는 프로덕션 스케줄러가 실제로 다뤘다 | Jeon 외, USENIX ATC 2019, Analysis of Large-Scale Multi-Tenant GPU Clusters for DNN Training Workloads(arXiv 1901.05758) | 마이크로소프트 Philly 스케줄러는 2~3분 타임아웃 안에 필요한 GPU 를 다 못 얻으면 얻은 자원을 반납하고 2분 백오프 뒤 재시도. 75일, 96,260개 작업 추적에서 대기의 대부분이 단편화 지연(8 GPU 초과 작업은 97.9%). 교착의 발생 빈도를 잰 것은 아님 |
| 시간 제한 방식은 진짜 갱 스케줄링이 아니다 | Kueue KEP-349(All-or-nothing), waitForPodsReady 문서 |
쿼터와 가용 자원이 맞지 않으면 교착할 수 있고, 기본 제한 시간 30분, 시간 초과 시 60초부터 두 배씩 최대 3,600초 백오프. 순차 승인은 처리량을 희생해 신뢰성을 얻는다고 스스로 적음 |
| Volcano 의 갱 | Volcano 문서의 gang 플러그인, PodGroup | 최소 실행 개수가 충족될 때만 모든 파드를 스케줄하는 전부 아니면 전무 방식 |
| 큐 대기 시간의 규모 | Kueue 저장소의 성능 시험 설정(test/performance/scheduler) |
큐 30개, 쿼터를 크게 초과 구독한 부하에서 전체 약 351초(한도 425초), 승인까지 평균 대기는 소형 작업 약 215초. 설정 파일의 기대값이며 승인 처리 지연이 아니므로 이 글의 2~4초와 비교할 수 없음 |
| 대규모 처리량 | Google Cloud 블로그, 13만 노드 GKE 클러스터(2025-11-22) | 파드 생성과 바인딩 약 1,000개/초, 39,000개 파드 선점에 93초(벤더 시험) |
| Volcano 의 규모 | Volcano 성능 튜닝 가이드(2026-05-26 갱신) | Kind 환경에서 파드 10,000개(1개짜리 작업 10,000개)에 약 250초, 웹훅 조정 뒤 약 180초(계산상 초당 약 40개에서 56개). 웹훅 타임아웃 10초에서 파드 생성 요청의 98.7%가 실패했다고 서술 |
| DRA 단계 | Kubernetes 기능 게이트 문서 | 핵심 DRA 안정(stable) 단계. 위 7절의 표 |
외부 자료 중 갱 교착의 발생 빈도를 직접 잰 것은 없습니다. 「MLaaS in the Wild」(NSDI 2022)는 PDF 가 크기 제한을 넘어 열지 못했고, 그 수치는 쓰지 않았습니다.
확인한 버전과 날짜
2026-10-05 기준 Kubernetes 1.37.1, Kueue v0.20.0(2026-09-30), Volcano v1.15.3(2026-09-30), NVIDIA DRA 드라이버 v0.5.0(2026-08-19)입니다. 필자의 실험 환경은 Kubernetes 1.34.10, GPU Operator v26.3.3입니다. 갱 스케줄링 실측은 별도의 일회용 k3s v1.36.5 에서 했습니다.
확인하지 못한 것: 노드가 많은 규모에서의 Kueue, Volcano 비교(시험은 노드 하나, 작업 수십 개 이하)와 큐 공정성의 장기 동작, KAI Scheduler의 최신 버전과 성숙도, Kueue와 Volcano를 함께 쓰는 구성의 지원 범위, NVIDIA DRA 드라이버의 GPU 할당 기능 단계(README와 문서 사이트가 다름), Kueue TAS와 NVLink 라벨의 구체적 연동, 위 명령의 실제 출력입니다. 용량 계산은 설명용 산술이며 부하 시험으로 대체해야 합니다.