- 들어가며 — 파드를 하나씩 스케줄링하면 생기는 일
- Kubernetes가 직접 나선 이유
- v1.35 — 첫 조각: Workload API와 Permit 게이트
- v1.36 — Workload는 템플릿, PodGroup은 런타임
- PodGroup 스케줄링 사이클 — 그룹을 원자적으로 평가한다
- 알고리즘의 정직한 한계
- 토폴로지 인지 스케줄링 — 첫 이터레이션
- 워크로드 인지 선점
- DRA와의 결합 — 256개 벽이 열렸다
- Job 컨트롤러 통합 — 조건이 꽤 까다롭다
- 켜는 법
- 그래서 Volcano와 Kueue를 걷어내야 하나
- 지금 이걸 쓰면 안 되는 이유
- 그럼에도 왜 봐야 하나
- 마치며
- 참고 자료
들어가며 — 파드를 하나씩 스케줄링하면 생기는 일
분산 학습 잡을 Kubernetes에 올려 본 사람이면 아는 실패 모드가 있습니다. 워커 4개가 다 떠야 학습이 시작되는데, 기본 스케줄러는 파드를 하나씩 독립적으로 배치합니다. GPU가 3개분만 남아 있으면 파드 3개가 노드를 잡고 앉아서, 나머지 1개를 영원히 기다립니다. GPU 3개는 점유된 채 아무 일도 하지 않습니다. 잡 두 개가 동시에 이러면 서로가 서로의 자원을 물고 늘어지는 교착까지 갑니다.
KEP-4671의 동기 문단은 이 상황을 담백하게 씁니다 — 병렬 애플리케이션은 실행을 시작하려면 모든 파드 간 통신이 필요하고, 진행하려면 배리어나 all-reduce 같은 지속적 통신이 필요하다. 그래서 모든 파드를 최대한 같은 시점에 시작해야 하고, 아니면 비싼 컴퓨트가 놀거나 애플리케이션이 통신 타임아웃으로 죽는다.
해법은 오래전부터 있었습니다. 갱 스케줄링(gang scheduling), 즉 "전부 아니면 전무" 배치입니다. 문제는 그 해법이 항상 클러스터 바깥에 있었다는 점입니다. Volcano를 깔거나, Kueue를 붙이거나, coscheduling 플러그인을 얹거나. 이 생태계 쪽 이야기는 Kubernetes AI 학습 파이프라인 — Volcano, Training Operator, Kueue 편에서 이미 다뤘습니다.
이 글은 다른 이야기입니다. 2026년 4월 22일 나온 Kubernetes v1.36(코드명 ハル / Haru, 총 70개 개선 — Stable 18, Beta 25, Alpha 25)에서, 갱 스케줄링이 kube-scheduler 자체로 들어오는 작업이 두 번째 단계에 접어들었습니다. 그리고 그 과정에서 여섯 달 전에 나온 API가 통째로 갈렸습니다.
Kubernetes가 직접 나선 이유
바깥에 잘 돌아가는 구현이 이미 있는데 왜 코어가 나서느냐. KEP-4671이 직접 답합니다 — 갱 스케줄링은 kube-scheduler 바깥에서 최소 네 번 구현됐고, 일부 컨트롤러는 클러스터마다 다른 스케줄러에 이식되려고 여러 갱 스케줄러를 동시에 지원하기 시작했다는 것입니다.
여기서 문제가 드러납니다. 갱 스케줄링을 원하는 쪽은 워크로드 컨트롤러(Job, JobSet, LeaderWorkerSet, MPIJob, TrainJob)인데, 이들이 각자 Volcano용 코드와 Kueue용 코드를 따로 들고 다녀야 합니다. 표준 인터페이스가 없으니까요. KEP는 세 가지를 노립니다 — 모든 Kubernetes 배포판에서 갱 스케줄링을 쓸 수 있게 하고, 컨트롤러가 표준·커스텀 스케줄러 양쪽에 같은 방식으로 요청할 수 있게 하고, 클러스터 오토스케일러 같은 다른 컴포넌트도 워크로드의 요구사항을 이해할 수 있게 한다.
마지막 항목이 은근히 중요합니다. 지금 오토스케일러는 "파드 3개가 Pending"이라는 것만 보지, "이 3개는 4개짜리 갱의 일부라서 1개를 더 못 띄우면 3개도 의미가 없다"는 걸 모릅니다. 워크로드가 API로 표현되면 그걸 읽을 수 있습니다.
그리고 토폴로지 이야기가 붙습니다. 갱이 필요한 워크로드는 대개 갱 멤버들이 토폴로지상 가까이 붙어 있어야 성능이 나옵니다. 기존 파드 어피니티는 배치에 영향을 주긴 하지만, KEP의 지적대로 갱을 스케줄링 단위로 보지 않고, 파드 집합에 대해 상호 배타적인 여러 배치 후보를 효율적으로 시도하지도 않습니다. 어피니티는 파드 한 개 관점의 도구입니다.
v1.35 — 첫 조각: Workload API와 Permit 게이트
v1.35(2025년 12월 17일)가 첫 묶음을 냈습니다. scheduling.k8s.io/v1alpha1에 Workload 리소스가 생겼습니다.
apiVersion: scheduling.k8s.io/v1alpha1
kind: Workload
metadata:
name: training-job-workload
namespace: some-ns
spec:
podGroups:
- name: workers
policy:
gang:
# The gang is schedulable only if 4 pods can run at once
minCount: 4
파드는 workloadRef로 여기에 연결됐습니다. 동작은 이랬습니다.
- 파드가 생기면 스케줄러가
PreEnqueue에서 붙잡아 둔다 — Workload 객체가 있고, 그 안에 해당 파드 그룹이 있고, 대기 중인 파드 수가minCount에 도달할 때까지. - 충분히 모이면 배치를 시도하되, 바로 바인딩하지 않고
Permit게이트에서 대기시킨다. - 그룹 전체(최소
minCount)에 유효한 배치를 찾았으면 게이트를 열어 한꺼번에 바인딩하고, 타임아웃(5분) 안에 일부만 스케줄됐으면 그룹의 파드를 전부 거절해 큐로 돌려보내고 잡아 뒀던 자원을 푼다.
같이 들어온 게 opportunistic batching입니다. 이건 Workload API도, 사용자의 명시적 opt-in도 필요 없고, v1.35에서 베타로 기본 켜져 있습니다. 스케줄링 요구사항이 동일한 파드들(컨테이너 이미지, 리소스 요청, 어피니티 등)을 알아보고, 한 파드에 대한 실현 가능성 계산을 뒤따르는 동일 파드들에 재사용해 처리 속도를 올립니다. 갱을 이루는 파드는 보통 서로 똑같으니 궁합이 맞습니다. 다만 스케줄러가 배치를 찾는 데 쓰는 모든 필드가 파드 간 동일해야 하고, 일부 기능을 쓰면 정확성을 위해 배칭이 꺼집니다. 여러분의 kube-scheduler 설정이 암묵적으로 배칭을 끄고 있지 않은지 확인하라고 문서가 굳이 당부합니다.
v1.35 블로그는 이게 첫 구현이라고 못박으면서, 갱 전체를 한 사이클에 처리하는 스케줄링 단계와 워크로드 수준 선점을 다음으로 예고했습니다. 정확히 그게 v1.36에 왔습니다.
v1.36 — Workload는 템플릿, PodGroup은 런타임
v1.36은 scheduling.k8s.io/v1alpha2를 내놓으면서 이전 v1alpha1을 완전히 대체했습니다. 여섯 달 만에 API가 갈린 겁니다. 이유는 KEP에 자세히 적혀 있고, 납득이 갑니다.
원래 설계는 PodGroup을 Workload 스펙 안에 박아 넣었습니다. 그런데 Workload는 오래 사는 설정-의도(configuration-intent)인 반면 PodGroup은 일시적인 스케줄링 단위입니다. 런타임 실행 단위를 영속적 정의 객체에 묶으면 관심사 분리가 깨집니다. 게다가 확장성 문제가 정면으로 걸립니다 — PodGroup이 많은 큰 Workload는 etcd의 1.5MB 객체 크기 제한에 쉽게 닿고, PodGroup 하나의 상태를 갱신하려고 거대한 중앙 Workload 객체를 read-modify-write 해야 하니 경합이 생깁니다.
그래서 갈랐습니다. 이제 Workload는 정적 템플릿입니다.
apiVersion: scheduling.k8s.io/v1alpha2
kind: Workload
metadata:
name: training-job-workload
namespace: some-ns
spec:
# v1alpha1의 podGroups -> podGroupTemplates 로 이름이 바뀜
podGroupTemplates:
- name: workers
# v1alpha1의 policy -> schedulingPolicy 로 이름이 바뀜
schedulingPolicy:
gang:
minCount: 4
그리고 컨트롤러가 이 템플릿에서 런타임 PodGroup을 찍어 냅니다.
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: training-job-workers-pg
namespace: some-ns
spec:
podGroupTemplateRef:
workload:
workloadName: training-job-workload
podGroupTemplateName: workers
schedulingPolicy:
gang:
minCount: 4
status:
conditions:
- type: PodGroupScheduled
status: "True"
lastTransitionTime: 2026-04-03T00:00:00Z
파드 쪽 연결 필드도 바뀌었습니다. workloadRef가 사라지고 schedulingGroup이 들어왔습니다.
apiVersion: v1
kind: Pod
metadata:
name: worker-0
namespace: some-ns
spec:
schedulingGroup:
podGroupName: training-job-workers-pg
주목할 점은 .metadata.ownerReferences는 여전히 "진짜" 워크로드 객체(예: Job)를 가리킨다는 것입니다. podGroupTemplateRef는 이게 어느 템플릿에서 나왔는지를 말할 뿐입니다. 소유권과 출처가 분리돼 있습니다.
이렇게 하면 스케줄러 로직도 단순해집니다. PodGroup 하나에 스케줄러가 필요한 정보가 다 들어 있으니, Workload 객체를 watch하거나 파싱할 이유가 없습니다.
PodGroup 스케줄링 사이클 — 그룹을 원자적으로 평가한다
v1.36의 진짜 알맹이는 여기입니다. 파드별로 순차적으로 자원을 평가·예약하면 교착 위험이 있으니, 스케줄러가 그룹을 하나의 단위로 평가하는 전용 사이클을 갖게 됐습니다.
스케줄러가 큐에서 PodGroup 멤버를 하나 꺼내면, 정책과 무관하게 그 그룹의 나머지 대기 파드들을 모아 결정론적으로 정렬한 뒤 원자적 사이클을 돕니다.
- 클러스터 상태의 스냅샷을 한 번 뜬다 — 그룹 전체를 평가하는 동안 경쟁 상태를 막고 일관성을 확보하기 위해.
- PodGroup 스케줄링 알고리즘으로 그룹의 모든 파드에 대한 유효한 노드 배치를 찾는다. 이때 필터링·스코어링 단계는 기존 파드 기반 로직을 그대로 쓴다.
- 결과를 그룹 전체에 원자적으로 적용한다.
- 성공하면 스케줄 가능한 멤버 파드들이 함께 바인딩 단계로 넘어가고, 남은 파드들은 큐로 돌아가 자원이 나기를 기다린다.
- 실패하면 그룹 전체가 스케줄 불가로 처리되고, 아무 파드도 바인딩되지 않은 채 백오프 후 재시도한다.
여기 조용하지만 운영상 중요한 단서가 하나 붙습니다. 이미 스케줄된 파드가 있는 PodGroup에 파드가 추가되면, 사이클은 기존 파드를 감안해 새 파드를 평가합니다. 그리고 이미 노드에 배정된 파드는 계속 돌아갑니다 — 이후 사이클에서 그룹이 요구사항을 못 채우더라도 스케줄러가 그 파드들을 배정 해제하거나 축출하지 않습니다. 즉 "전부 아니면 전무"는 바인딩 시점의 원자성이지, 그룹 수명 내내 유지되는 불변식이 아닙니다.
minCount 게이팅 자체는 남아 있습니다. 스케줄러는 여전히 PreEnqueue에서 파드를 붙잡아 두고, 실제 스케줄링 단계만 새 PodGroup 사이클에 전적으로 의존합니다. 알고리즘 실행 중에 스케줄 가능한 파드 수가 minCount를 만족하는지 확인하고, 클러스터가 최소치를 못 받쳐 주면 아무것도 바인딩하지 않습니다.
알고리즘의 정직한 한계
이 부분이 이 글에서 제일 중요합니다. 그리고 칭찬할 만하게도, 이건 제 관찰이 아니라 Kubernetes 프로젝트가 자기 블로그에 "Limitations"라는 제목으로 직접 써 놓은 내용입니다.
첫 버전 PodGroup 스케줄링 사이클의 한계는 이렇습니다.
- 동종(homogeneous) 파드 그룹 — 모든 파드의 스케줄링 요구사항이 동일하고 어피니티·안티어피니티·토폴로지 분산 제약 같은 파드 간 의존성이 없는 경우 — 에 대해서는, 배치가 존재한다면 알고리즘이 찾아낼 것으로 기대된다.
- 이종(heterogeneous) 파드 그룹에 대해서는, 배치가 존재해도 찾는다는 보장이 없다. 해답이 자명해 보이는 경우조차도.
- 파드 간 의존성이 있는 그룹에 대해서도 보장이 없다.
거기에 더해, 그룹 내부 의존성(예: 인터-파드 어피니티로 한 파드의 스케줄 가능성이 다른 멤버에 달린 경우)에서는 결정론적 처리 순서 때문에 클러스터 상태와 무관하게 배치를 못 찾을 수 있습니다. KEP는 같은 얘기를 이렇게 표현합니다 — 기본 알고리즘은 그룹의 파드를 다른 순서로 처리했다면 발견할 수 있었을 유효한 배치를 못 찾을 수 있다.
읽고 나면 적용 범위가 선명해집니다. 지금 이 기능이 제대로 겨냥하는 건 워커가 전부 똑같은 PyTorch DDP 스타일 학습 잡입니다. 리더와 워커 스펙이 다른 LeaderWorkerSet, 헤테로 GPU 풀, 토폴로지 분산 제약이 얽힌 워크로드는 아직 보장 밖입니다. 이건 버그가 아니라 알파의 스코프입니다.
토폴로지 인지 스케줄링 — 첫 이터레이션
v1.36은 PodGroup에 토폴로지 제약을 직접 걸 수 있게 했습니다.
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: topology-aware-workers-pg
spec:
schedulingPolicy:
gang:
minCount: 4
schedulingConstraints:
topology:
- key: topology.kubernetes.io/rack
스케줄러는 rack 제약에 맞는 노드 조합들을 시도하고, PodGroup이 자원을 얼마나 효율적으로 쓰는지와 해당 도메인 안에 몇 개의 파드를 넣을 수 있는지를 기준으로 최적 배치를 고릅니다. 이를 위해 PodGroup 사이클에 배치(placement) 기반 알고리즘 3단계가 붙었습니다 — 후보 배치 생성(PlacementGenerate 확장점), 각 후보에 그룹 전체가 실제로 들어가는지 검증, 실현 가능한 배치들을 스코어링해 최적 선택(PlacementScore 확장점).
한계도 명시돼 있습니다. 현재 토폴로지 인지 스케줄링은 제약을 만족시키려고 파드 선점을 유발하지 않습니다. 다음 릴리스에서 워크로드 인지 선점과 통합할 계획이라고 합니다. 그리고 다중 토폴로지 레벨, 소프트 제약(선호), DRA와의 깊은 통합, basic 정책과 함께 쓸 때의 견고함은 앞으로의 과제로 남아 있습니다. 즉 지금 쓸 수 있는 건 단일 레벨 하드 제약 하나입니다.
워크로드 인지 선점
PodGroup이 스케줄될 수 없을 때 쓰는 새 선점 메커니즘도 들어왔습니다. 기존 파드별 선점과 다른 점은 PodGroup 전체를 하나의 선점자 단위로 본다는 것입니다. 노드마다 따로 희생자를 찾는 대신 클러스터 전역을 검색해서, 여러 노드에서 동시에 파드를 밀어내 그룹 전체가 들어갈 공간을 만듭니다. 이게 없으면 갱 선점은 이상해집니다 — 노드별로 조금씩 밀어내 봐야 갱 전체가 들어갈 만한 공간이 안 생기니까요.
PodGroup API에 두 개념이 추가됐습니다.
- priority — PodGroup을 이루는 개별 파드의 우선순위를 덮어쓴다.
- disruptionMode — 그룹 안의 파드가 개별적으로 선점될 수 있는지, 아니면 전부 아니면 전무로 함께 선점돼야 하는지를 지정한다.
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
name: victim-pg
spec:
priorityClassName: high-priority
priority: 1000
disruptionMode: PodGroup
여기 함정이 있습니다. v1.36에서 이 두 필드는 워크로드 인지 선점 메커니즘에서만 존중됩니다. 기존 파드별 사이클의 기본 선점을 포함한 다른 교란(disruption) 경로는 이 필드를 무시합니다. 다른 교란 소스로 확장하는 건 앞으로의 희망사항입니다. 그러니 disruptionMode: PodGroup을 걸어 놨다고 해서 여러분의 갱이 모든 축출로부터 통째로 보호된다고 읽으면 안 됩니다.
DRA와의 결합 — 256개 벽이 열렸다
DRA는 v1.34에서 GA가 됐고(DRA 자체는 Kubernetes DRA와 GPU 스케줄링 편 참고), 이제 PodGroup이 ResourceClaimTemplate의 복제 단위가 될 수 있습니다.
기존에는 ResourceClaimTemplate을 참조하면 파드마다 ResourceClaim이 하나씩 생성됐습니다. 여러 파드가 같은 디바이스를 공유하려면 ResourceClaim을 이름으로 직접 참조해야 했고, 그러려면 그 클레임을 사용자가 직접 만들어 관리해야 했습니다. 이제 PodGroup의 spec.resourceClaims가 참조하는 ResourceClaimTemplate에 대해서는, 그룹에 파드가 몇 개든 그룹 전체에 하나의 ResourceClaim이 생성됩니다. 파드의 spec.resourceClaims 항목이 소속 PodGroup의 것과 일치하면, 그 파드의 클레임은 그룹용으로 생성된 ResourceClaim으로 해소되고 파드 개별 클레임은 만들어지지 않습니다.
더 실질적인 건 이겁니다. 예전엔 kube-scheduler가 ResourceClaim의 status.reservedFor에 개별 파드만 나열할 수 있었고, 그 필드는 256개 제한이 있었습니다. 이제 status.reservedFor에 PodGroup 참조 하나가 들어가면 256개를 훨씬 넘는 파드를 대표할 수 있습니다. 대규모 워크로드에서 디바이스를 고카디널리티로 공유하는 길이 열린 겁니다. 수백~수천 파드짜리 학습 잡에서 이건 이론적 개선이 아니라 실제로 막혀 있던 벽입니다.
Job 컨트롤러 통합 — 조건이 꽤 까다롭다
지금까지의 얘기를 다 읽고 "그래서 Workload랑 PodGroup을 내가 직접 만들어야 하나" 싶다면, v1.36의 답은 "Job이라면 아니오"입니다. WorkloadWithJob 피처 게이트를 켜면 Job 컨트롤러가 알아서 Workload와 런타임 PodGroup을 만들고, Job이 만드는 모든 파드에 .spec.schedulingGroup을 설정하고, 생성된 객체의 소유자를 Job으로 지정해 Job 삭제 시 GC되게 합니다.
다만 첫 이터레이션을 예측 가능하게 유지하려고 조건을 좁게 잡았습니다. Job이 아래를 전부 만족할 때만 통합이 작동합니다.
.spec.parallelism이 1보다 크다.spec.completionMode가Indexed다.spec.completions가.spec.parallelism과 같다- 파드 템플릿에
schedulingGroup이 아직 설정돼 있지 않다
논리는 명확합니다 — 각 파드가 안정적 정체성을 갖고(Indexed), 갱 크기가 admission 시점에 고정돼 있고(parallelism == completions), 다른 컨트롤러가 이미 스케줄링 책임을 가져가지 않았어야 합니다. 조건을 못 채우는 Job은 예전처럼 파드별로 스케줄됩니다.
마지막 조건이 특히 사려 깊습니다. 여러분이 파드 템플릿에 schedulingGroup을 직접 설정해 뒀다면(예: 상위 컨트롤러가 워크로드를 관리 중이라면) Job 컨트롤러는 템플릿에 손대지 않고 자기 Workload/PodGroup도 만들지 않습니다. 즉 이미 외부 배치 시스템을 쓰는 클러스터에서 이 게이트를 켜도 안전합니다.
빠진 것도 분명합니다. 현재 제약은 이 통합을 정적·인덱스드·완전 병렬 Job으로 한정합니다. 탄력적(elastic) Job과 다른 빌트인 컨트롤러 지원은 KEP-5547에서 추적 중입니다.
켜는 법
전부 v1.36에서 알파입니다. 전제 조건부터.
- Workload/PodGroup API —
kube-apiserver와kube-scheduler양쪽에GenericWorkload게이트를 켜고,scheduling.k8s.io/v1alpha2API 그룹을 활성화한다.
그다음 원하는 기능별로.
- 갱 스케줄링 —
kube-scheduler에GangScheduling - 토폴로지 인지 스케줄링 —
kube-scheduler에TopologyAwareWorkloadScheduling - 워크로드 인지 선점 —
kube-scheduler에WorkloadAwarePreemption(GangScheduling도 함께 켜야 함) - DRA ResourceClaim 지원 —
kube-apiserver,kube-controller-manager,kube-scheduler,kubelet전부에DRAWorkloadResourceClaims - Job 컨트롤러 통합 —
kube-apiserver와kube-controller-manager에WorkloadWithJob
게이트가 컴포넌트마다 다르게 걸린다는 점을 눈여겨보세요. 이 목록만 봐도 관리형 클러스터(EKS·GKE·AKS)에서는 당장 만져 볼 수 없다는 결론이 나옵니다. 알파 게이트를 컨트롤 플레인에 켤 수 있어야 하니, kubeadm이나 kind로 세운 테스트 클러스터가 현실적인 실험장입니다.
그래서 Volcano와 Kueue를 걷어내야 하나
아니오. 그리고 이건 제 의견이 아니라 KEP-4671의 Non-Goals에 적힌 내용입니다 — kube-scheduler에 공정성이나 다중 워크로드 큐를 들여오는 것은 목표가 아니며, Kueue와 Volcano.sh가 계속 그것을 제공할 것이다.
이게 경계선을 정확히 그어 줍니다. 코어가 가져가는 건 "이 파드 묶음을 함께, 원자적으로, 토폴로지를 고려해 배치하라"는 메커니즘입니다. 반면 "어느 팀에 쿼터를 얼마나 주고, 대기열에서 누굴 먼저 꺼내고, 빌려준 자원을 언제 회수하나" 같은 정책은 여전히 Kueue와 Volcano의 영역입니다. 실제로 대부분의 조직이 갱 스케줄러를 도입한 이유는 후자입니다. 갱 스케줄링만 필요했던 팀은 생각보다 적습니다.
같은 Non-Goals에 이런 것들도 있습니다 — 컨트롤러에서 파드 생성 책임을 뺏지 않는다, 클러스터 오토스케일링과 갱 스케줄링의 통합은 (아직) 아니다, 워크로드 수준 선점은 (이 KEP의) 목표가 아니다, 서로 다른 스케줄러 간 자원 경합(교착 가능성 포함)은 다루지 않는다. 마지막 항목이 의미심장합니다 — kube-scheduler와 Volcano를 한 클러스터에서 같이 돌릴 때 생기는 문제는 여전히 여러분의 문제입니다.
지금 이걸 쓰면 안 되는 이유
기술 자체보다 이 문단이 실무자에게 더 값어치가 있을 겁니다.
전부 알파입니다. opportunistic batching만 베타(기본 켜짐)이고, Workload API·갱 스케줄링·토폴로지·선점·DRA 연동·Job 통합은 모두 알파입니다.
API가 이미 한 번 깨졌고, 또 깨질 예정입니다. v1.35의 v1alpha1은 v1.36에서 v1alpha2로 완전히 대체됐습니다. podGroups는 podGroupTemplates가 됐고, policy는 schedulingPolicy가 됐고, 파드의 workloadRef는 schedulingGroup이 됐고, 런타임 상태는 별도 객체로 나갔습니다. 여섯 달 만에요.
그리고 KEP에 적힌 v1.37 계획을 보면 한 번 더 갑니다. API를 v1beta1로 승격시키면서, 동시에 계획된 알파 기능들의 DisruptionMode 관련 하위 호환 불가 변경을 위해 v1alpha3를 새로 만들어 v1alpha2를 대체한다는 것입니다. 업그레이드 시 필요한 조치가 KEP에 명시돼 있습니다 — v1alpha2 리소스를 전부 삭제한 뒤 업그레이드해야 하고(v1.37에서 미지원), v1alpha3에서 v1alpha2로의 역변환은 다운그레이드 시 지원되지 않습니다. 게다가 GangScheduling 게이트가 GenericWorkload로 병합되므로 업그레이드할 때 설정에서 GangScheduling을 빼야 하고, v1.36으로 다운그레이드하면 다시 켜야 합니다. (v1.37은 2026년 8월 26일 예정이고, 여기 적은 건 전부 KEP에 적힌 계획입니다. 릴리스 전까지 바뀔 수 있습니다.)
알고리즘 보장이 좁습니다. 앞서 본 대로 동종 그룹 밖에서는 배치를 못 찾을 수 있습니다.
KEP가 스스로 든 리스크도 있습니다 — 바인딩 단계의 경쟁 구간이 표준 스케줄링보다 길어지고, 객체가 늘어나니 API 호출량과 etcd 객체 수가 늘고, Workload와 PodGroup 두 객체 간 일관성 문제가 생깁니다. GC와 admission 컨트롤러로 완화한다지만 공짜는 아닙니다.
그럼에도 왜 봐야 하나
방향이 분명하기 때문입니다. Kubernetes는 지금까지 "파드가 스케줄링의 최소 단위"라는 전제 위에 서 있었고, 그 전제가 AI 워크로드에서 깨졌습니다. 지난 몇 년간의 대응은 전부 바깥에서 우회하는 것이었습니다. 이제 코어가 워크로드를 일급 시민으로 인정하는 중입니다.
v1.37 로드맵을 보면 어디로 가는지 읽힙니다 — Workload/PodGroup API의 베타 승격과 함께 탄력적 잡을 위한 minCount 가변성 도입, JobSet이나 LWS 기반 분산 추론 같은 복잡한 AI 워크로드를 위한 다중 레벨 계층 구조, 토폴로지 인지 스케줄링과 워크로드 인지 선점의 베타 승격, 그리고 실제 워크로드 컨트롤러들을 위한 통합 컨트롤러 연동 API. 프로젝트는 이 우선순위와 구현 순서가 바뀔 수 있다고 덧붙입니다.
지금 여러분이 할 일은 마이그레이션이 아닙니다. 테스트 클러스터에 kind로 v1.36을 띄우고 게이트를 켜서, 여러분의 학습 잡 모양이 Job 컨트롤러 통합 조건(Indexed, parallelism == completions)에 맞는지, 여러분의 파드 그룹이 "동종"에 해당하는지 확인해 보는 것입니다. 만약 둘 다 맞다면 여러분은 이 기능의 정확한 타깃이고, 베타가 오면 꽤 이른 시점에 Volcano 의존을 덜어 낼 수 있습니다. 둘 중 하나라도 어긋난다면 — 그리고 쿼터·큐잉이 필요하다면 — 앞으로도 한동안 Kueue와 Volcano가 답입니다. 그건 실패가 아니라 KEP가 설계한 그대로입니다.
마치며
정리하면 이렇습니다. 갱 스케줄링은 kube-scheduler 바깥에서 최소 네 번 구현됐고, Kubernetes는 v1.35에서 Workload API로 이 기능을 코어로 들이기 시작해 v1.36에서 구조를 바로잡았습니다. Workload는 정적 템플릿, PodGroup은 런타임 객체, 그리고 그룹 전체를 스냅샷 위에서 원자적으로 평가하는 PodGroup 스케줄링 사이클. 여기에 토폴로지 제약, 워크로드 단위 선점, PodGroup 단위 ResourceClaim 공유(256개 벽 해제), Job 컨트롤러 자동 통합의 첫 이터레이션이 얹혔습니다.
대신 전부 알파이고, API는 여섯 달 만에 한 번 깨졌고 한 번 더 깨질 계획이고, 알고리즘 보장은 동종 파드 그룹으로 좁고, 공정성과 큐잉은 애초에 목표가 아닙니다. 그러니 이건 "Volcano 대체재"가 아니라 "Volcano가 서 있을 표준 바닥"입니다. 바닥이 생기는 걸 지켜보되, 아직 그 위에 프로덕션을 올리진 마십시오.
참고 자료
- Kubernetes v1.36: Advancing Workload-Aware Scheduling (공식 블로그, 2026-05-13)
- Kubernetes v1.35: Introducing Workload Aware Scheduling (공식 블로그, 2025-12-29)
- Kubernetes v1.36: ハル (Haru) — 릴리스 공지 (2026-04-22)
- KEP-4671 — Workload API and gang scheduling
- KEP-5732 — Topology-aware scheduling
- KEP-5710 — Workload-aware preemption
- KEP-5729 — DRA ResourceClaim support for workloads
- KEP-5547 — Workload API support in Job controller
- Kubernetes v1.34: DRA has graduated to GA
- Kubernetes AI 학습 파이프라인 — Volcano, Training Operator, Kueue (관련 글)
- Kubernetes DRA와 GPU 스케줄링 (관련 글)
- 분산 학습 플랫폼 비교 (관련 글)
현재 단락 (1/160)
분산 학습 잡을 Kubernetes에 올려 본 사람이면 아는 실패 모드가 있습니다. 워커 4개가 다 떠야 학습이 시작되는데, 기본 스케줄러는 파드를 하나씩 독립적으로 배치합니다. ...