좀 더 똑똑하게 consolidation하기 — Balanced 모드
Karpenter consolidation은 무엇을 하는가에서 이어집니다.
새로 추가된 Balanced 정책
어떻게 보수적으로 노드를 제거할 수 있을까요?
기준선 — WhenEmptyOrUnderutilized가 하는 일
Balanced를 이해하려면 기존에 WhenEmptyOrUnderutilized가 수행되는 방식을 먼저 봐야 합니다.
“파드들을 전부 다른 노드에 배치할 수 있을까?”, 그리고 “새로운 노드로 교체하면 저렴해?” 두 개의 조건으로만 필터링을 한다면, 10원 아끼려고 파드 40개를 옮겨버리는 케이스가 발생합니다.
- init 단계에서 부하가 더 발생되는 경우나, PDB로 보호를 하고 있어도 서비스의 안정성이 더 떨어지는 것들이죠.
Balanced가 얹는 게이트
Balanced는 이런 심한 Drift를 방지하기 위해 v1.14.0에서 추가됐습니다.
WhenEmptyOrUnderutilized가 놓치는 걸 잡고, Drift를 조금 더 조심스럽게 수행하기로 했습니다.
기존에는 삭제 대상으로 잡힌 노드여도, 검증을 한 차례 더 수행하여 “절감을 수행하는 것” 자체의 비용을 따집니다.
Balanced 승인 집합 ⊂ WhenEmptyOrUnderutilized 승인 집합애초에 추가 검증을 한 단계 더 추가한 만큼, WhenEmptyOrUnderutilized가 10개를 지운다고 해도, Balanced 모드는 10개를 초과해 삭제하는 일은 없을 겁니다.
추가된 검증 과정입니다.
기존의 WhenEmptyOrUnderutilized 말고 WhenEmpty 조건에서부터 삭제 대상으로 잡히는 Empty 노드는 볼 필요가 없고
ScoreMove라는 검증을 통해 0.5점 이상이 기록되어야 삭제를 수행합니다.
score = (savings / disruptionCost) ÷ (TotalCost / TotalDisruptionCost) ≥ 1/k = 0.5
savings 삭제 노드 가격 합 − 생성 노드 가격
disruptionCost 후보들의 RescheduleDisruptionCost 합
TotalCost 그 NodePool의 총비용
TotalDisruptionCost 그 NodePool에 속한 모든 노드의 disruption cost 합공식에 들어가는 변수가 좀 많지만, 조금 더 간단하게 풀어보겠습니다.
그 조건이 실제로 뜻하는 것
식에 담겨있는 변수들은 크게 두 가지 관점에서 볼 수 있습니다.
score = (savings / disruptionCost) ÷ (TotalCost / TotalDisruptionCost)
└─ 이 액션의 효율 ─┘ └─ 풀의 평균 효율 ─┘| 위치 | 항목 | 정체 | 커지는 경우 |
|---|---|---|---|
| 분자 | savings | 삭제면 후보 가격 전액 교체면 −대체 노드 최저가 | 순수 삭제 |
| 분자 | TotalDisruptionCost | 풀 전체 노드의 비용 합 | 파드가 빽빽한 풀 |
| 분모 | TotalCost | 풀 총비용 | 큰 타입 · 온디맨드 |
| 분모 | disruptionCost | 이 커맨드 후보들의 합 | 빽빽한 노드 · MultiNode 묶음 |
savings와 TotalCost는 달러이고, disruptionCost와 TotalDisruptionCost는 사실상 파드 개수입니다.
분모가 작고 분자가 클수록 점수가 높으니, 적은 파드를 옮기면서 절감액이 큰 통합이 가장 잘 통과합니다.
균질한 풀에 평균 파드밀도를 가정하면 score ≈ 절감률 × (평균 밀도 / 그 노드의 밀도)로 줄어듭니다.
평균 밀도 노드라면 50% 이상 싸지는 교체만 통과하고, 한산한 노드는 쉬워지며 빽빽한 노드는 보호됩니다.
① Balanced 는 통합을 만들지 않는다 — 위 경로가 만든 커맨드가 그대로 올라온다. 얼마를 아끼고(savings) 그러려고 무엇을 건드리는지(노드 + 파드)가 붙어 있다
언제 효과가 있을까?
노드 교체가 잦다.
- 기존에 “더 싸기만 하면 Drift”에서 추가된 안전한(보수적인) 절감입니다. 파드가 많이 떠 있는 노드도 교체를 실시한다.
- 옮길 파드 수를 같이 고려해 아무 노드나 교체하지 않습니다.
삭제냐 교체냐
앞에서 계속 나온 삭제와 교체를 세부적으로 살펴보겠습니다.
절감 가능한 노드로 분류된 노드들은 시뮬레이션 한 번으로 셋 중 하나 — 아무것도 안 하거나(no-op), 그냥 지우거나(delete), 대신 한 대를 띄우고 지우거나(replace) 결정합니다.
시뮬레이션 — 새 노드가 몇 대 필요한가
no-op: 아무것도 하지 않음
delete: 그저 삭제함
replace: 교체 대상은 신규 노드일까? 기존 노드일까?
후보로 선정된 노드를 클러스터에서 가상으로 지우고, 파드를 다시 스케줄해본 뒤 새로 띄워야 할 노드가 몇 대인가를 계산해봅니다.
파드가 하나라도 갈 곳이 없으면 그 자리에서 끝납니다.
얼마짜리를 몇 대?
시뮬레이션이 요구한 새 노드 대수가 그대로 형태를 정합니다.
신규로 필요한 노드가 0대면 Replacements를 비운 채 반환(곧장 삭제)하고,
1대면 가격 필터를 거쳐 새로 띄울 노드가 지울 노드들의 가격 합보다 싼지 가격을 채웁니다.
launchPrice < Σ Price(후보들)분류가 된 이후에
| Option | Type | Description |
|---|---|---|
| Reason | 어떤 분류인가 | Empty · Drifted · Underutilized |
| Decision | 어떻게 처리할 것인가 | delete · replace · no-op |
시뮬레이션을 거치는 두 갈래에서는 네 조합이 전부 정상적으로 나옵니다.
| Reason | Decision | 언제 이렇게 되나 | 가격 검사 |
|---|---|---|---|
Drifted | delete | 파드가 남은 노드에 전부 들어감 | X |
Drifted | replace | 받아줄 자리가 없어 새 노드가 필요함 | X |
Underutilized | delete | 여러 대를 지우고 기존 노드가 흡수함 | X |
Underutilized | replace | 4xlarge 한 대를 2xlarge 한 대로 | O — 엄격히 더 싸야 합니다 |
Empty는 빈 노드라 옮길 파드가 없어 대체 시뮬레이션 자체를 하지않습니다.
어떻게 관측하나
Unconsolidatable 이벤트의 message가 사유를 그대로 말합니다.
| message | 뜻 |
|---|---|
NodePool %q has consolidation policy WhenEmpty, but node is not empty | 정책이 막았다 |
Can't replace with a cheaper node | 가격 부등식에서 탈락 |
Can't remove without creating %d candidates | 대체가 2대 이상 필요 |
Node %q has buffer pods | Capacity Buffers가 잡고 있다 |
NodePool %q has consolidation disabled | consolidateAfter가 nil |
제약이 하나 있습니다 — 위 사유들은 후보가 1대일 때만 발행됩니다. MultiNode가 왜 실패했는지는 --log-level debug의 판정 로그를 봐야 합니다.
Balanced는 승인만 이벤트를 남기고 거부는 메트릭만 남깁니다(balanced.go:218-219). 거부 사유는 karpenter_consolidation_score와 debug 로그의 consolidation score 줄로 봅니다.