Skip to Content
Blog좀 더 똑똑하게 consolidation하기 — Balanced 모드

좀 더 똑똑하게 consolidation하기 — Balanced 모드

Karpenter consolidation은 무엇을 하는가에서 이어집니다.

새로 추가된 Balanced 정책

어떻게 보수적으로 노드를 제거할 수 있을까요?

기준선 — WhenEmptyOrUnderutilized가 하는 일

Balanced를 이해하려면 기존에 WhenEmptyOrUnderutilized가 수행되는 방식을 먼저 봐야 합니다.

IsEmpty비어있지 않음스코어 없음파드가 다 들어감대체 1대 필요더 싸다후보 노드Consolidatable = trueEmptiness정책을 읽지 않는다시뮬레이션파드가 어디로 가나삭제가격 검사 없음가격 필터launchPrice < 현재가교체대체는 최대 1대예산이유별 허용량이 다시조인다
WhenEmptyOrUnderutilized — 빈 노드는 Emptiness가, 나머지는 시뮬레이션이 처리한다. 삭제 경로에는 가격 검사가 아예 없고, 교체 경로만 엄격한 가격 부등식을 통과해야 한다

“파드들을 전부 다른 노드에 배치할 수 있을까?”, 그리고 “새로운 노드로 교체하면 저렴해?” 두 개의 조건으로만 필터링을 한다면, 10원 아끼려고 파드 40개를 옮겨버리는 케이스가 발생합니다.

  • init 단계에서 부하가 더 발생되는 경우나, PDB로 보호를 하고 있어도 서비스의 안정성이 더 떨어지는 것들이죠.

Balanced가 얹는 게이트

Balanced는 이런 심한 Drift를 방지하기 위해 v1.14.0에서 추가됐습니다. WhenEmptyOrUnderutilized가 놓치는 걸 잡고, Drift를 조금 더 조심스럽게 수행하기로 했습니다. 기존에는 삭제 대상으로 잡힌 노드여도, 검증을 한 차례 더 수행하여 “절감을 수행하는 것” 자체의 비용을 따집니다.

Balanced 승인 집합 ⊂ WhenEmptyOrUnderutilized 승인 집합

애초에 추가 검증을 한 단계 더 추가한 만큼, WhenEmptyOrUnderutilized가 10개를 지운다고 해도, Balanced 모드는 10개를 초과해 삭제하는 일은 없을 겁니다.

score ≥ 0.5score < 0.5ScoreMoveNodePool별로 채점승인예산 단계로거부메트릭만 · 이벤트 없음
Balanced가 하는 일은 채점 하나뿐이다 — ScoreMove가 NodePool별로 점수를 내고 0.5를 넘는 것만 통과시킨다

추가된 검증 과정입니다. 기존의 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 묶음

savingsTotalCost는 달러이고, disruptionCostTotalDisruptionCost는 사실상 파드 개수입니다.

분모가 작고 분자가 클수록 점수가 높으니, 적은 파드를 옮기면서 절감액이 큰 통합이 가장 잘 통과합니다.

균질한 풀에 평균 파드밀도를 가정하면 score ≈ 절감률 × (평균 밀도 / 그 노드의 밀도)로 줄어듭니다. 평균 밀도 노드라면 50% 이상 싸지는 교체만 통과하고, 한산한 노드는 쉬워지며 빽빽한 노드는 보호됩니다.

WhenEmptyOrUnderutilized 가 만든 커맨드m8g.4xl → m8g.2xl한 단계 다운사이징savings$0.441/hcost노드 1 + 파드 4 = 5.0m8g.2xl → m8g.xl빽빽한 노드savings$0.221/hcost노드 1 + 파드 11 = 12.0m8g.2xl ×2 → r8g.2xl묶어서 접기savings$0.314/hcost노드 2 + 파드 18 = 20.0m8g.xl → m8g.large푼돈 절감savings$0.110/hcost노드 1 + 파드 8 = 9.0score = (savings / 풀 총비용) ÷ (cost / 풀 총 disruption cost)풀 총비용 $4.414/h · 풀 총 disruption cost 50.0 · 가격은 ap-northeast-2 온디맨드거부승인 임계 1/k = 0.500.51.0승인 1건 · 거부 3건Balanced 는 만들지 않는다 — 받아서 거를 뿐이다

① Balanced 는 통합을 만들지 않는다 — 위 경로가 만든 커맨드가 그대로 올라온다. 얼마를 아끼고(savings) 그러려고 무엇을 건드리는지(노드 + 파드)가 붙어 있다

언제 효과가 있을까?

노드 교체가 잦다.

  • 기존에 “더 싸기만 하면 Drift”에서 추가된 안전한(보수적인) 절감입니다. 파드가 많이 떠 있는 노드도 교체를 실시한다.
  • 옮길 파드 수를 같이 고려해 아무 노드나 교체하지 않습니다.

삭제냐 교체냐

앞에서 계속 나온 삭제와 교체를 세부적으로 살펴보겠습니다. 절감 가능한 노드로 분류된 노드들은 시뮬레이션 한 번으로 셋 중 하나 — 아무것도 안 하거나(no-op), 그냥 지우거나(delete), 대신 한 대를 띄우고 지우거나(replace) 결정합니다.

시뮬레이션 — 새 노드가 몇 대 필요한가

no-op: 아무것도 하지 않음 delete: 그저 삭제함 replace: 교체 대상은 신규 노드일까? 기존 노드일까?

후보로 선정된 노드를 클러스터에서 가상으로 지우고, 파드를 다시 스케줄해본 뒤 새로 띄워야 할 노드가 몇 대인가를 계산해봅니다.

파드가 하나라도 갈 곳이 없으면 그 자리에서 끝납니다.

얼마짜리를 몇 대?

시뮬레이션이 요구한 새 노드 대수가 그대로 형태를 정합니다.

가상으로 제거새 노드 0대새 노드 1대더 싸다후보 노드없앨 대상 N대시뮬레이션새 노드가 몇 대 필요한가삭제 CommandReplacements 없음가격 필터launchPrice < 현재가교체 CommandReplacements 1대바로 삭제cordon · drain · delete기동 후 삭제Initialized 대기
갈림목은 시뮬레이션이 요구한 새 노드 대수 하나다 — 0대면 곧장 삭제, 1대면 가격 필터를 거쳐 교체. 2대 이상이면 커맨드 자체가 만들어지지 않는다

신규로 필요한 노드가 0대면 Replacements를 비운 채 반환(곧장 삭제)하고, 1대면 가격 필터를 거쳐 새로 띄울 노드가 지울 노드들의 가격 합보다 싼지 가격을 채웁니다.

launchPrice < Σ Price(후보들)

분류가 된 이후에

OptionTypeDescription
Reason어떤 분류인가Empty · Drifted · Underutilized
Decision어떻게 처리할 것인가delete · replace · no-op
판별 없이0대1대 · 더 쌈2대+ · 못 넣음Empty빈 노드Drifted스펙과 어긋난 노드Underutilized합칠 수 있는 노드시뮬레이션새 노드가 몇 대?deleteReplacements 없음replaceReplacements 1대no-op커맨드 안 만듦
분류가 Decision을 정하지 않는다. 시뮬레이션을 거치는 두 갈래에서 삭제·교체가 갈리고, Empty만 판별 없이 삭제로 고정된다

시뮬레이션을 거치는 두 갈래에서는 네 조합이 전부 정상적으로 나옵니다.

ReasonDecision언제 이렇게 되나가격 검사
Drifteddelete파드가 남은 노드에 전부 들어감X
Driftedreplace받아줄 자리가 없어 새 노드가 필요함X
Underutilizeddelete여러 대를 지우고 기존 노드가 흡수함X
Underutilizedreplace4xlarge 한 대를 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 podsCapacity Buffers가 잡고 있다
NodePool %q has consolidation disabledconsolidateAfternil

제약이 하나 있습니다 — 위 사유들은 후보가 1대일 때만 발행됩니다. MultiNode가 왜 실패했는지는 --log-level debug의 판정 로그를 봐야 합니다.

Balanced승인만 이벤트를 남기고 거부는 메트릭만 남깁니다(balanced.go:218-219). 거부 사유는 karpenter_consolidation_score와 debug 로그의 consolidation score 줄로 봅니다.