| 항목 | 내용 |
|---|---|
| 프로젝트 | Karpenter (kubernetes-sigs/karpenter, aws/karpenter-provider-aws) |
| 관련 릴리스 | v1.0(2025년 하반기 GA) — Disruption Budgets가 v1beta1 NodePool로 정식 도입 |
| 설계 문서 | designs/disruption-controls.md |
| 발견 출처 | Announcing Karpenter 1.0 (AWS 공식 블로그) |
| 검증 근거 | kubernetes-sigs/karpenter 저장소의 실제 설계 문서(designs/disruption-controls.md) |
Karpenter는 노드 사용률이 떨어지면 더 저렴한 인스턴스로 통합(consolidation)하거나 빈 노드를 제거해서 비용을 줄이는 클러스터 오토스케일러다. v1.0에서 Disruption Budgets가 NodePool 스펙 안의 Disruption 블록으로 정식 도입됐다 — "동시에 몇 대까지, 어느 시간대에 노드를 없앨 수 있는가"를 사용자가 세밀하게 제어할 수 있게 하는 기능이다.
Karpenter가 노드를 제거하는 이유는 크게 세 가지다: 사용률이 낮아 통합 가능(Underutilized), 파드가 하나도 없음(Empty), 노드 설정이 원하는 상태와 어긋남(Drifted, 예: AMI가 최신이 아님). 어느 경우든 노드를 제거하면 그 위의 파드들이 다른 노드로 다시 스케줄되는데, 이 과정에서 동시에 너무 많은 노드가 한꺼번에 사라지면 짧은 시간에 대량의 파드 재스케줄링이 발생해 클러스터 전체 처리량이 출렁이거나, 업무 시간대에 트래픽이 몰리는 서비스가 하필 그 순간에 재배치를 겪을 수 있다. v1.0 이전에는 이런 "언제/얼마나"에 대한 제어가 지금처럼 세밀하지 않았다.
설계 문서(disruption-controls.md)에 따르면 핵심은 Budgets 필드 하나로 "동시성 제한"과 "시간대 제한"을 함께 표현하는 것이다:
Nodes: 한 번에 종료(taintkarpenter.sh/disruption이 붙은 상태로 카운트)될 수 있는 NodeClaim 수 — 절대값(10)이나 퍼센트(10%) 둘 다 가능.Schedule/Duration(cron): 이 예산이 적용되는 시간 창. 예를 들어 "평일 업무 시간에는 노드 제거를 0으로 막고, 그 외 시간에는 동시 10대까지 허용"하는 식의 조합이 가능하다.- 여러 budget이 같은 시간에 겹치면, 그 중 가장 엄격한(가장 작은) 값이 적용된다 — 즉 합집합이 아니라 교집합 방식이다.
[변경 전] 노드 제거 시점과 동시성에 대한 세밀한 제어 없음
┌─────────────┐ 사용률 낮음 감지 ┌──────────────────┐
│ NodePool │ ──────────────────▶ │ 노드 제거 실행 │
│ (전역 설정) │ │ (시간대/동시성 무관) │
└─────────────┘ └──────────────────┘
[변경 후] NodePool.Spec.Disruption.Budgets로 시간대 × 동시성 교차 제한
┌──────────────────────────┐
│ NodePool.Spec.Disruption │
│ Budgets: │ cron 창 안에 있으면 ┌────────────────┐
│ - Nodes: "10%" │ ──────────────────────────▶ │ 가장 엄격한 값만 │
│ - Schedule: "..." (업무시간)│ │ 적용해 노드 제거 │
└──────────────────────────┘ └────────────────┘
왜 별도 CRD가 아니라 NodePool 안의 필드로 넣었는가: 설계 문서는 두 대안을 검토했다고 명시한다 — (1) 레이블 셀렉터로 여러 NodePool에 걸쳐 적용되는 독립 CRD, (2) ProviderRef처럼 NodePool이 참조하는 별도 CRD. 둘 다 기각됐다. 독립 CRD는 클러스터 범위로 적용 가능하다는 장점은 있지만 RBAC 권한 경계가 새로 생기고 필드가 중복될 위험이 있고, 참조형 CRD는 시간 기반 제어처럼 자주 바뀔 수 있는 필드를 마이그레이션하기 복잡하게 만든다. 대신 NodePool.Spec.Disruption 블록에 다 넣으면 (a) 관리할 CRD가 늘지 않고, (b) NodePool 하나에 자연히 스코프가 잡혀서 별도 셀렉터 로직이 필요 없고, (c) 앞으로 추가될 다른 disruption 관련 필드(예: terminationGracePeriodSeconds)도 같은 자리에 자연스럽게 놓일 수 있다 — "지금 필요한 것보다 넓게 설계하지 않으면서도, 나중에 확장할 자리를 미리 마련해둔다"는 실용적 선택이다.
왜 여러 budget이 겹칠 때 합집합이 아니라 교집합(가장 엄격한 값)을 택했는가: 이 기능의 목적이 "안전장치"라는 걸 감안하면, 여러 제약 중 하나라도 걸리면 그게 우선해야 한다 — 합집합(가장 느슨한 값)을 택하면 사용자가 실수로 느슨한 budget을 하나 추가하는 것만으로 다른 엄격한 budget이 무력화될 수 있다. 교집합은 "안전 방향으로만 실패한다(fail-safe)"는 원칙에 맞는다.
왜 disruption 이유(Underutilized/Empty/Drifted)별로 budget을 나눌 수 있게 했는가: 처음 구현됐을 때는 이유 구분 없이 모든 disruption이 하나의 budget을 공유했는데, 실사용에서 "만료(expiration)로 인한 교체는 막고 싶지만 drift 교정은 계속 허용하고 싶다" 같은 요구가 나왔다(GitHub Issue #2387 등에서 확인 가능). 이유별로 나누면 사용자가 "이 시간대엔 비용 최적화(통합)는 미루되, 보안 패치(drift 교정)는 계속 진행"같은 정책을 표현할 수 있다 — 다만 설계 문서 자체도 이 이유별 상호작용이 아직 문서화가 부족하다는 점을 인정하고 있어, 향후에도 계속 다듬어질 영역이다.
- 이유별 budget이 서로 다른 시간 창을 가질 때, 같은 노드가 여러 이유로 동시에 disruption 후보가 되면 어떤 budget이 우선할까?
- percentage 기반 budget(
10%)의 기준이 되는 "전체 노드 수"는 어느 시점의 스냅샷을 쓸까 — 계산 중에도 계속 바뀌는 값일 텐데? - Drift 감지가 stable이 된 이후, drift로 인한 노드 교체 빈도가 실제로 얼마나 늘었을까?
- designs/disruption-controls.md (kubernetes-sigs/karpenter)
- Announcing Karpenter 1.0 (AWS 공식 블로그)
- Mega Issue: Enhancements to Disruption Budgets #2387
- Karpenter NodePool 문서
본문 참고 자료로 충분했다.