
지난주 화요일 새벽 4시쯤, 알림이 두 개 연달아 왔다. 하나는 pod pending 알림, 하나는 P99 레이턴시 알림. 눈이 번쩍 떠져서 노트북을 켰다.
우리 팀은 몇 달 전부터 Karpenter를 v1.x로 올려두고 spot-to-spot consolidation을 활성화해서 쓰고 있다. 비용은 확실히 줄었다. 근데 그 새벽에는 대가를 치렀다. 노드 12대 중 7대가 30분 사이에 인터럽트를 받으면서 순차적으로 죽었고, 그 와중에 Karpenter는 계속 새 spot을 붙였다 떼기를 반복하고 있었다.
처음에는 그냥 spot 인터럽트인 줄 알았다
AWS spot은 원래 죽는다. 그건 알고 시작한 거니까. 그래서 처음에는 "아 오늘 그 존이 좀 빡센가 보다" 하고 넘어갈 뻔했다. 근데 로그를 보니 이상했다.
karpenter disrupting via consolidation replace, terminating 1 nodes
karpenter disrupting via consolidation replace, terminating 1 nodes
karpenter disrupting via consolidation replace, terminating 1 nodes
10분 사이에 consolidation 이벤트가 8번. spot 인터럽트가 아니라 Karpenter가 스스로 노드를 갈아치우고 있었던 거다. 그리고 새로 붙인 노드가 또 몇 분 뒤에 인터럽트를 받아서 죽었다.
이게 뭐지 싶어서 새로 프로비저닝된 인스턴스 타입을 봤더니, 특정 저가 타입 두어 개로 계속 몰리고 있었다. m5.large, m5a.large 이런 애들. 근데 그 시간대에 그 존에서 그 타입이 유독 자주 회수되고 있었던 것 같다.
PCO를 믿었는데 왜?
Karpenter는 spot 할당에 PriceCapacityOptimized (PCO) 전략을 쓴다. 무조건 최저가만 고르지 않고 가용성도 함께 본다는 얘기다. 그래서 처음 붙일 때는 나름 안정적인 인스턴스가 붙는다.
문제는 그 다음이다. consolidation이 돌면, Karpenter는 현재 노드보다 저렴한 조합이 있는지 계속 살핀다. 그러다 저가 spot이 잡히면 갈아탄다. 이게 반복되면 소위 말하는 race to the bottom, 결국 가장 싼(=가장 잘 죽는) 인스턴스로 수렴한다.
문서에는 이걸 막기 위해 single node spot-to-spot consolidation에는 최소 15개 인스턴스 타입 유연성이 필요하다고 못박아 놓았다. 우리는 그게 있는 줄 알았다. 실제로는 없었다.
NodePool 스펙을 다시 열어봤다.
requirements:
- key: karpenter.k8s.aws/instance-family
operator: In
values: ["m5", "m5a", "m6i"]
- key: karpenter.k8s.aws/instance-size
operator: In
values: ["large", "xlarge", "2xlarge"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
3 family × 3 size = 9 조합. 15개 안 된다. 그래서 Karpenter 입장에서는 single node consolidation을 판단할 때 이미 게이트가 안 열렸어야 하는데, 로그를 보면 계속 replace가 돌고 있었다. 이 부분은 아직 검증 중이다 — v1 마이너 버전 특정 릴리스에서 flexibility 체크가 우회되는 케이스가 있었다는 얘기가 팀 내부 논의에서 나왔고, 우리 클러스터가 그 케이스에 걸린 건지 아니면 우리가 뭘 잘못 세팅한 건지 아직 확답을 못 내렸다.
일단 급한 불부터 끄기
새벽 4시 반쯤. 정신을 좀 차리고 두 가지를 했다.
첫째, consolidation을 잠시 껐다. NodePool의 disruption 필드에 다음을 넣었다.
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 5m
WhenUnderutilized를 껐다는 얘기다. 유휴 노드 청소만 남기고, 저가 spot을 쫓아가는 동작을 멈춘다. 이 상태에서 클러스터는 급격히 안정됐다.
둘째, spot interruption SQS가 진짜 붙어 있는지 다시 확인했다. 문서에서 강조하는 건데, SQS 큐가 안 붙어 있으면 인터럽트 경고가 와도 Karpenter가 못 잡아서 그냥 죽는다. 우리는 붙어 있었다. 다행이었다.
다음날 낮에 다시 정리
낮에 팀원들과 앉아서 몇 가지를 바꿨다.
instance-family를 늘렸다. m5, m5a, m5n, m6i, m6a, m7i, c5, c5a, c6i 이런 식으로. size도 조금 더 열었다. 그 결과 flexibility가 20 조합을 넘겼다. AWS 블로그 예제도 이 정도 폭을 기본으로 잡는다.
그다음 WhenUnderutilized를 다시 켜되, consolidateAfter를 15분으로 늘렸다. 5분 단위로 왔다갔다 하는 게 그 새벽에 상황을 악화시켰다고 봐서, 결정 주기를 좀 느긋하게 잡았다.
노드 disruption budget도 붙였다. 동시에 사라질 수 있는 노드 수에 상한을 두는 건데, 우리는 전체 노드의 10%로 잡았다. 12대면 최대 1~2대. 새벽에 7대가 한꺼번에 흔들린 상황이 재현되면 안 되니까.
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 15m
budgets:
- nodes: "10%"
배운 것
Karpenter 쓸 때 "instance-family 몇 개 열었는지"를 그냥 취향 정도로 넘겼는데, 이게 spot-to-spot consolidation에서는 안전 장치 그 자체다. 15라는 숫자를 문서에서 봤을 때는 "왜 저렇게 많이?" 싶었는데, 그날 새벽 이후로는 "왜 저렇게 적게?"가 됐다.
그리고 비용 최적화 기능들은 대체로 조용히 이득을 준다. 대신 잘못 물리면 조용히 손해도 준다. consolidation이 얼마나 자주 도는지, 어떤 인스턴스로 옮겨가는지, 옮겨간 노드가 몇 분 만에 죽는지 — 이런 지표를 대시보드에 걸어두지 않은 게 뼈아팠다. 이번 주에 Prometheus rule 몇 개 추가하는 게 다음 작업이다.
혹시 spot-to-spot consolidation 쓰시는 분들, family 몇 개 여셨는지 궁금합니다. 15 근처면 안전한지, 아니면 더 많이 열어두시는지.
'IT > Kubernets' 카테고리의 다른 글
| cert-manager 챌린지, HTTP01과 DNS01 중에 뭘 골라야 할까 (0) | 2026.06.30 |
|---|---|
| Cilium kube-proxy replacement 갈아탔다가 LoadBalancer 클라이언트 IP 다 날린 이야기 (0) | 2026.06.30 |
| 새벽 두 시에 깨운 Karpenter, disruption budget 시간 윈도우로 막은 이야기 (0) | 2026.06.29 |
| ingress-nginx EOL 통보를 받고 Gateway API로 옮긴 두 달의 기록 (0) | 2026.06.28 |
| Velero restic에서 Kopia로 옮기는 법 (0) | 2026.06.27 |