Kubernets

Cluster Autoscaler에서 Karpenter로 옮기다 새벽에 멘탈 나간 썰

gfrog 2026. 4. 25. 01:41
반응형

지난달에 결국 Karpenter로 갈아탔다. 팀에서 반년 넘게 "다음 분기에 해야지" 하며 미뤄왔던 숙제였는데, 예상대로 쉽지 않았다. 이 글은 자랑이 아니라 그냥 기록이다. 비슷한 고민 하는 분들에게 조금이라도 도움이 됐으면 해서 적는다.

Karpenter가 v1.0 GA 찍은 지도 벌써 한참 됐고, 최근에는 OCI provider까지 GA 나오면서 더 이상 "AWS 전용 실험 프로젝트" 소리는 못 듣는 상황이다. 그래도 막상 프로덕션에 올려보면 문서에 안 나오는 함정이 꽤 있다.

왜 옮겼나

솔직히 Cluster Autoscaler(CAS)가 못 쓸 물건은 아니다. 노드 18대 규모에서 몇 년을 잘 돌았다. 문제는 배치 잡 비중이 커지면서부터였다.

우리 팀은 데이터 파이프라인 일부가 Kubernetes Job으로 돌아가는데, 야간에 피크 오면 Spot 인스턴스 60~80대가 한 번에 올라와야 한다. CAS로는 ASG 기반이라 타입 섞어 쓰기가 애매했고, 노드 그룹을 인스턴스 패밀리별로 쪼개다 보니 결국 ASG 12개를 관리하고 있었다. 하나 바뀌면 테라폼 plan이 수십 초씩 걸리고, 신규 인스턴스 타입 추가하는 것도 은근히 귀찮았다.

Karpenter는 NodePool 하나에 requirements로 인스턴스 타입/AZ/아키텍처를 선언하면 알아서 고른다. 그리고 Consolidation 기능이 진짜다. 노드 여유 있을 때 작은 인스턴스로 묶어주는데, 이게 비용 15% 정도 줄여줬다. 이 숫자 보려고 옮긴 거다.

1주차 — PodDisruptionBudget 지옥

첫 번째 사고는 이전 주말에 터졌다. Consolidation이 너무 적극적으로 돌아서 stateful 워크로드 Pod들이 계속 재스케줄됐다. 금요일 밤 11시쯤 알림이 떠서 확인해보니, Kafka 컨슈머 그룹이 5분 간격으로 리밸런싱을 반복하고 있었다.

원인은 단순했다. 우리 PDB들이 대부분 minAvailable: 1로 설정돼 있었는데, 레플리카 3개짜리 Deployment 기준으로는 2개까지 동시에 내려도 된다는 뜻이다. Karpenter 입장에선 "아, 그럼 2개씩 옮겨도 되는구나" 하고 쾌속 Consolidation을 시전한 거다. CAS는 애초에 이 정도로 공격적이지 않았으니 문제가 안 됐던 건데, 지금 생각하면 우리 PDB 설정이 애초에 느슨했던 게 맞다.

급한 대로 아래처럼 막았다.

apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
  name: default
spec:
  disruption:
    consolidationPolicy: WhenEmptyOrUnderutilized
    consolidateAfter: 10m
    budgets:
      - nodes: "10%"
      - nodes: "0"
        schedule: "0 22 * * *"
        duration: 10h

disruption.budgets로 한 번에 이동 가능한 노드 비율을 제한하고, 피크 시간대(밤 10시~오전 8시)에는 아예 Consolidation을 멈추게 했다. 이러면 새벽에 Kafka 리밸런싱으로 페이저 받을 일은 없다. 다만 밤 사이 비용 최적화는 포기하는 셈이다. 트레이드오프.

PDB는 PDB대로 전부 다시 훑어서 stateful 계열은 maxUnavailable: 1로 바꿨다. 이쪽이 더 안전한 표현이라는 걸 이번에 배웠다.

2주차 — Spot 인터럽션 감지가 안 먹힘

두 번째 삽질. Spot 인스턴스 회수 알림을 Karpenter가 못 받고 있었다. AWS EventBridge에서 SQS로 쏴주는 이벤트를 Karpenter controller가 구독해야 하는데, 이게 설치 가이드에 안 나와서 그냥 넘어갔다가 당했다.

증상은 이랬다. Spot 회수 2분 전에 AWS가 알려주는데, Karpenter가 이걸 못 받으니까 새 노드 미리 띄워둘 시간이 없었다. Pod이 회수된 뒤에야 "어? 노드 없네?" 하고 허겁지겁 새 노드 만들기 시작. 당연히 워크로드 끊김. P99 레이턴시가 300ms 넘기면서 알림 떴다.

해결은 Helm values에 아래를 넣고, 해당 SQS 큐/이벤트 규칙을 Terraform으로 만들어주는 거였다.

# helm values
settings:
  interruptionQueue: karpenter-interruption
  featureGates:
    spotToSpotConsolidation: true

Terraform 모듈은 terraform-aws-modules/karpenter/aws 쪽에 enable_irsa, enable_spot_termination 같은 플래그가 있어서 이걸 true로 주면 알아서 IAM이랑 SQS 다 만들어준다. 처음부터 모듈 쓸걸. 수동으로 IRSA 붙이느라 시간 꽤 썼다.

참고로 spotToSpotConsolidation은 v1.0 이후로 feature gate에서 안정화 단계로 바뀌는 과정이니까 쓰는 버전에 따라 설정 위치가 다를 수 있다. 우리는 1.1 기준이다.

지금도 남아있는 찝찝함

다 옮기고 한 달 정도 운영 중이다. 비용은 예상대로 줄었고, 알림 빈도도 안정적이다. 근데 몇 가지는 아직 해결을 못 했다.

첫째, 인스턴스 타입 다양성이 커지니까 관측성 도구 설정이 빡세진다. 특정 인스턴스에서만 네트워크 지연이 튀는 경우가 있는데, 이걸 어떻게 일관되게 추적할지 아직 답이 없다. Prometheus 라벨에 node.kubernetes.io/instance-type 붙여서 쪼개 보고는 있는데, 카디널리티 폭발이 걱정이다.

둘째, 신입 온보딩이 어려워졌다. CAS 시절엔 "노드 그룹 이 이름이 이 용도"라고 설명하면 됐는데, 이제는 NodePool + requirements + taints의 조합을 이해해야 한다. 내부 문서를 다시 써야 하는데 미루고 있다.

셋째, GitOps와의 궁합. ArgoCD로 NodePool을 관리하려고 했는데, Karpenter가 제공하는 Validating Webhook이 가끔 sync 타이밍 꼬이는 증상이 있었다. 지금은 그냥 Terraform에서 Helm chart로 Karpenter를 배포하고, NodePool은 Kubernetes manifest로 별도 관리하는 어정쩡한 구조로 운영 중이다.

혹시 지금 옮길지 고민하는 분이 있다면

두 가지만 전하고 싶다.

먼저, PDB 점검이 먼저다. Karpenter 설치보다 PDB 설정 전수 검사가 우선이다. 느슨한 PDB는 CAS 시절엔 안 찔리는데 Karpenter에서는 바로 문제로 튀어나온다.

그리고 disruption.budgets의 schedule은 넣는 걸 추천한다. 피크 시간에 Consolidation 안 돌게 하는 것만으로도 심야 알림 빈도가 체감상 절반으로 줄었다. 비용은 좀 포기하는 대신 팀의 수면 시간을 샀다고 생각한다.

아직 검증 중인 것도 많고, 더 나은 패턴이 있을 수도 있다. 혵시 다른 방식으로 운영하는 분 있으면 코멘트 남겨주세요. 특히 Karpenter + ArgoCD 깔끔하게 엮은 분들은 정말 궁금하다.

반응형