TT Lab
블로그

한국어 English 日本語 中文

시작하기
블로그

블로그

비워 둔 limit 은 무제한이 아니라 0 이었다: KAI-Scheduler 큐 문서 PR 이 13일 만에 병합되기까지

결과: 병합 (2026-11-09 17:44, kai-scheduler/KAI-Scheduler#2532), 이슈 #2531 도 함께 닫힘. 사실은 GitHub 의 PR·이슈·리뷰·타임라인·검사 기록, KAI-Scheduler 소스(PR 의 바탕 커밋 0243c4e9)와 API 모듈(태그 v0.2.0), 기여 원장의 실측 원자료와 초안, TT Lab 저장소의 코스 커밋에서 가져왔습니다. 시각은 한국 시간입니다.

목차

한 줄 요약

PR kai-scheduler/KAI-Scheduler#2532 docs(queues): explain that an omitted limit is 0 and fix the example queues
바꾼 것 docs/queues/README.md 한 파일. 특수값 표 한 줄, 설명 한 문단, 예시 큐 셋에 빠진 limit: -1 과 memory 블록. 16줄 추가, 2줄 삭제
반응 다른 기여자가 코드 경로를 확인하고 질문 하나, 협업자 권한의 리뷰어 승인(뒤에 자동 해제), 메인테이너 승인과 skip-changelog 라벨
결과 PR 공개 10-27 14:34 → 병합 11-09 17:44, 약 13일 3시간 10분. 병합 큐를 거친 squash 병합
적지 않은 limit 은 -1 이 아니라 0 으로 읽힌다 KAI-Scheduler v0.18.6 실측(10-27), 바탕 커밋 0243c4e9 의 소스 줄 ① 큐에서 limit 을 빼면 일어나는 일 limit 생략 CRD 기본값 없음 float64 의 0 resources.go L23 상한 0 -1 만 건너뜀 isOverLimit: 쓴 양 + 요청 ≤ 상한 을 자원마다 따로 본다 상한이 0 이면 그 자원을 조금이라도 청하는 파드는 OverLimit ② 문서 예시를 그대로 적용한 큐에 같은 파드 하나 파드: cpu 100m · memory 32Mi · GPU 1장 고치기 전 고친 뒤 Basic Pending · 메모리 0 GB Running Hierarchical Pending · CPU 0 cores Running Unlimited Pending · 메모리 0 GB Running PR: 표 한 줄과 설명 한 문단, 예시 셋에 limit -1 (+16 −2) 「Unlimited Queue - default value is -1」 제목도 고침 최종본(memory quota 0)으로 다시 돌린 기록은 없음: 미실측
그림: 적지 않은 limit 이 0 으로 읽혀 문서 예시 큐 셋이 모두 파드를 받지 못한 모습

1. 무엇이 문제였나: 예시 셋이 모두 아무것도 못 받는다

KAI-Scheduler 는 GPU 작업을 위한 쿠버네티스 스케줄러입니다. 파드에 schedulerName: kai-scheduler 를 적으면 기본 스케줄러 대신 이 스케줄러가 배치를 맡고, 파드가 속한 큐(Queue)의 몫을 따져 자원을 줍니다. 큐에는 자원(cpu·memory·gpu)마다 보장량 quota 와 상한 limit 이 있습니다.

TT Lab 학습 사이트의 「쿠버네티스 GPU 스케줄링과 AI 배치」 코스를 만들던 10-27 새벽에 이 함정을 처음 밟았습니다. 실험 VM 에 k3s 한 대를 띄우고 가짜 GPU 4장(fake-gpu-operator 0.2.1)과 KAI-Scheduler v0.18.6 을 올린 다음, gpu quota: 1 에 limit 을 적지 않은 큐를 만들어 GPU 1장짜리 파드를 넣었습니다. quota 가 1이니 1장은 받을 줄 알았는데, 파드는 Pending 에 머물고 PodGroup 에는 이런 조건이 붙었습니다.

OverLimit: nolimit quota has reached the allowable limit of GPUs. Limit is 0 GPUs,
currently 0 GPUs allocated and workload requested 1 GPUs.

limit 을 -1 로 바꾸자 기다리던 같은 파드가 배치되었습니다(실습 검증 기록 vfy-kai.log). 몇 초 만에 떴는지는 재지 않았습니다. 이 일은 코스의 실습 둘(「limit 을 비운 큐의 함정」, 「limit 을 -1 로 적어 고친다」)이 되었고, 같은 날 아침 코스 커밋(4cdcc0b3)의 읽기 자료에도 「limit 은 꼭 -1 이나 숫자로 적으세요」라고 적었습니다.

문제는 문서였습니다. 큐 문서의 특수값 표는 비워 둔 limit 을 「추가 자원 없음(기본값)」이라고 설명하고, 마지막 예시의 제목은 「Unlimited Queue - default value is -1」입니다. 읽는 사람은 비워 두면 quota 까지는 쓰거나, 아예 무제한이라고 기대하게 됩니다. 그래서 문서의 예시 큐 셋을 글자 그대로 적용하고, 같은 파드(requests: {cpu: 100m, memory: 32Mi}, limits: {nvidia.com/gpu: 1})를 하나씩 넣어 봤습니다. 원자료는 기여 원장의 kai-docs-test.log·kai-docs-after.log 이고, 파드마다 15초를 기다린 뒤 상태를 읽었습니다.

예시 큐 고치기 전(문서 그대로) 고친 뒤(PR 의 첫 판)
Basic Queue (memory 없음) Pending, Limit is 0 GB (메모리) Running
Hierarchical Queue (cpu·gpu 에 limit 없음, memory 없음) Pending, Limit is 0 cores (CPU) Running
Unlimited Queue (memory 없음) Pending, Limit is 0 GB (메모리) Running

Basic Queue 에 memory: {quota: -1, limit: -1} 만 더해도 기다리던 파드가 Running 이 되었습니다. GPU 상한을 적은 예시도, 「Unlimited」라는 이름의 예시도 메모리 블록이 없어서 메모리를 조금이라도 청하는 파드를 받지 못한 것입니다. 쿠버네티스에서 메모리를 청하지 않는 파드는 드무니, 이 문서를 보고 처음 큐를 만드는 사람은 거의 반드시 여기서 막힙니다.

환경은 실험 VM 한 대(4 vCPU·4GiB), k3s v1.36.5, KAI-Scheduler v0.18.6 이고 GPU 는 가짜입니다. 진짜 GPU 노드나 여러 노드에서는 재지 않았습니다(미실측). 다만 이 판정은 스케줄러가 큐의 숫자만 보고 하는 일이라 GPU 가 진짜인지와는 상관없는 것으로 보입니다.

2. 원인: 타입의 0 과 -1 만 건너뛰는 검사

줄 번호는 PR 의 바탕 커밋 0243c4e9 와 그 커밋이 쓰는 API 모듈 kai-scheduler/api v0.2.0 기준입니다.

정리하면 코드는 그 자체로 앞뒤가 맞습니다. 「비우면 0, 0 이면 막힘, -1 만 무제한」입니다. 어긋난 것은 문서의 설명과 예시였습니다. 그래서 이슈에는 두 길을 함께 적었습니다. 문서를 지금 동작에 맞추거나, 의도한 기본값이 -1 이라면 CRD 기본값이나 입장 단계에서 채우는 것입니다. 동작을 바꿀지는 메인테이너가 정할 일이라, 바로 머지할 수 있는 문서 쪽만 PR 로 만들고 「기본값을 바꾸는 쪽을 고르면 이 PR 은 닫아도 된다」고 적었습니다.

3. 무엇을 바꿨나: diff 요지

-| `limit` | `0` or unset | No additional resources allowed (default) |
+| `limit` | `0` or unset | No resources of this type can be allocated to the queue, including its quota (default) |
+
+Each resource (`cpu`, `memory`, `gpu`) is checked on its own. If a queue omits a resource or its `limit`,
+the limit for that resource is `0`, and workloads that request it stay pending with an `OverLimit` condition
+such as `Limit is 0 GB`. Set `limit: -1` for every resource you do not want to cap.
 # Basic Queue, Hierarchical Queue
+    memory:
+      quota: 0
+      limit: -1
 # Hierarchical Queue 의 cpu·gpu 에도
+      limit: -1
-### Unlimited Queue - default value is -1
+### Unlimited Queue
+`-1` is not filled in for you: set it on every resource.
 # Unlimited Queue
+    memory:
+      quota: -1                          # Unlimited quota
+      limit: -1                          # No limit

(설명 문단은 원래 한 줄인 것을 읽기 좋게 세 줄로 나눠 적었습니다.)

PR 본문에는 1절의 고치기 전·뒤 표, 실측 환경, 「문서만 바꿨고 코드·차트는 그대로」라는 말, skip-changelog 라벨 부탁, 같은 파일의 다른 절을 고치는 열린 PR #2275(MIG 계산 설명)가 먼저 병합되면 리베이스하겠다는 말, 그리고 AI 도움을 받아 불일치를 찾고 실측하고 초안을 썼다는 고지를 넣었습니다.

이 저장소의 CONTRIBUTING 은 DCO(Signed-off-by), Conventional Commits 꼴의 PR 제목, 동작이 바뀌는 변경의 변경 기록 조각(문서만이면 skip-changelog 라벨)을 요구하고, 바깥 기여자의 PR 에는 「신뢰된 리뷰어 2명의 승인」을 요구합니다. AI 사용에 관한 저장소 규칙은 따로 없어서 상위 재단의 생성형 AI 지침을 읽고 사실대로 고지했습니다. 바깥 기여자는 라벨을 붙일 권한이 없어 본문에서 부탁했습니다.

4. 반응과 대응: 질문 하나와 지워진 승인

시각 일
10-27 새벽 실험 VM 에서 함정 발견, 문서 예시 셋 고치기 전·뒤 실측(원자료 파일 시각 01:46·02:30·02:38)
10-27 14:33–14:34 이슈 #2531, 42초 뒤 PR 공개(첫 판 c871f61b, SSH 서명)
10-27 19:28 다른 기여자의 리뷰(의견): 코드 경로가 새 설명과 맞다는 확인과 질문 하나
10-28 21:39 협업자 권한의 리뷰어가 첫 판을 승인
10-29 00:47 질문을 반영한 커밋 47bd30b5 push, 그 순간 승인이 자동 해제. 답글
10-29 00:54 두 커밋을 SSH 서명 커밋 하나(2c23833a)로 합쳐 강제 갱신
10-29 06:17 질문한 기여자가 「2c23833a reads right」
11-01 01:21 해제된 승인을 다시 봐 달라는 댓글
11-09 16:04–16:05 메인테이너 승인, skip-changelog 라벨
11-09 17:43–17:44 병합 큐에 들어가 검사 넷 통과, 17:44:50 병합(e5f0f298), 이슈 #2531 도 닫힘

질문. 다른 기여자는 proportion.go·CRD·isOverLimit 을 직접 따라가 「코드가 새 문장과 맞다」고 확인한 뒤, 요청이 아니라 질문이라며 하나를 물었습니다. 새 memory 블록이 quota: -1 이라 메모리 보장량까지 무제한이 되는데, GPU 예시는 「보장 없음」을 quota: 0 으로 적으니 상한만 풀려는 뜻이라면 quota: 0·limit: -1 이 더 가깝지 않겠느냐는 것입니다. 맞는 지적이었습니다. 우리가 바라던 것은 「상한을 0 으로 두지 말라」였지 보장량을 바꾸는 것이 아니었습니다. 그래서 상한을 적은 예시 둘(Basic·Hierarchical)의 메모리를 quota: 0·limit: -1 로 바꾸고, 모든 자원이 무제한인 Unlimited 예시만 quota: -1 로 두었습니다. 위 diff 가 그 최종본입니다.

이 최종본을 실험 VM 에서 다시 돌린 기록은 없습니다(미실측). 1절의 「고친 뒤」는 메모리 quota: -1 이던 첫 판의 결과입니다. 상한 검사는 limit 만 보므로 결과가 같을 것으로 보고, 같은 실측에서 Unlimited 예시의 gpu 가 quota: 0·limit: -1 로 GPU 1장을 받은 것이 그 근거이지만, 메모리 quota: 0 자체를 재지는 않았습니다.

지워진 승인. 협업자 권한의 리뷰어가 첫 판을 승인한 지 세 시간 뒤에 질문 반영 커밋을 올렸고, GitHub 의 타임라인에는 바로 그 push 로 승인이 해제(dismissed)된 기록이 남았습니다. 해제를 일으킨 커밋은 47bd30b5 입니다. 이 저장소는 새 커밋이 올라오면 이전 승인을 무효로 하는 설정을 쓰는 것으로 보입니다(저장소 설정은 볼 권한이 없어 미확인). 7분 뒤 우리는 두 커밋을 서명된 커밋 하나로 합쳐 다시 올렸는데, 11-01 에 남긴 재승인 부탁 댓글에는 「squash push 가 승인을 지웠다」고 적었습니다. 이벤트 기록을 다시 보면 squash 가 아니라 그 앞의 push 였습니다. 어느 쪽이든 승인 뒤의 push 는 승인을 지운다는 점은 같지만, 원인을 말할 때는 기억이 아니라 타임라인을 먼저 봐야 했습니다.

기다림과 병합. 10-29 새벽 질문이 정리된 뒤 11일 동안 새 리뷰가 없었습니다. 11-09 16:04 에 메인테이너가 승인하고 1분 뒤 라벨을 붙였고, 17:43 에 병합 큐에 넣었습니다. 병합 큐에서는 PR 워크플로·변경 기록 검사·PR 제목 검사·DCO 가 14초 안에 모두 통과했고, 문서만 바뀐 PR 이라 빌드·E2E 는 「필요 없음」으로 건너뛰었습니다. 병합 커밋의 저자는 우리 계정이고, 메시지는 PR 제목 끝에 (#2532) 와 DCO 서명이 붙은 것입니다.

PR 화면에는 빨간 검사 둘이 그대로 남아 있습니다. 변경 기록 검사와 승인 수를 세는 검사(Approval Gatekeeper)가 10-29 강제 갱신 때 돌아 실패했고, 11-09 16:03 에 다시 돌았지만 승인·라벨이 붙기 1~2분 전이라 또 실패했습니다. 누가 다시 돌렸는지는 공개 기록에서 확인하지 못했습니다. Approval Gatekeeper 는 바깥 기여자 PR 에 조직 멤버나 협업자의 승인 둘을 요구하는데, 병합 시점에 살아 있던 신뢰된 승인은 메인테이너의 하나였습니다(협업자의 승인은 해제된 채였고, 질문한 기여자는 의견만 남겼습니다). 이 검사는 병합 큐에서는 돌지 않았으니 병합을 막는 필수 검사가 아닌 것으로 보입니다. 규칙의 적용은 메인테이너의 판단이고, 우리가 따로 대응할 일은 없었습니다.

11-10 기준으로 이 커밋을 담은 릴리스 태그는 없습니다. 병합 뒤에 나온 v0.16.16·v0.14.12 는 옛 판 줄기의 패치 릴리스이고, 이 문서 변경은 main 에만 있습니다. 문서는 GitHub 의 main 에서 바로 읽힙니다.

5. 배운 것: 오픈소스 기여 요령

티티 한마디. 학교 매점 카드에 「하루 한도」 칸이 있었는데, 비워 두면 무제한인 줄 알았대요. 그런데 매점 아주머니는 빈칸을 0원으로 읽으셨어요. 그래서 우유 하나도 못 샀죠! 안내문에는 「빈칸은 기본값」이라고만 쓰여 있었고요. 안내문에 「빈칸은 0원이에요, 무제한이면 -1 이라고 꼭 적으세요」를 더하자고 했더니, 13일 뒤에 새 안내문이 붙었어요. 중간에 낙서 하나 고쳤다고 받아 둔 도장이 지워진 건 좀 아까웠지만요.

링크

모든 자료는 2026-11-10 에 열람했습니다.

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다