13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] Spot 인스턴스

  • [k8s] Kubernetes 클러스터 비용 절감: Karpenter로 최적화하는 방법

    [k8s] Kubernetes 클러스터 비용 절감: Karpenter로 최적화하는 방법

    Kubernetes 클러스터 비용 절감: Karpenter로 최적화하는 방법

    Kubernetes 클러스터 비용 절감이 늘 화제가 되는 이유는 단순합니다. 청구서는 노드 기준으로 쌓이는데, 실제 낭비는 파드보다 노드 배치 방식에서 더 자주 생기거든요. 저도 EKS와 사내 테스트 클러스터를 오래 운영하면서 느낀 게 하나 있습니다. CPU나 메모리 사용률이 낮다고 해서 비용 구조가 좋은 건 아니더라고요. 진짜 문제는 빈 노드가 오래 남는 구조, 너무 좁은 인스턴스 선택 조건, 스케줄링 제약 때문에 비우지 못하는 노드였습니다. 이 지점에서 Karpenter는 단순한 오토스케일러라기보다, 비용 구조를 다시 설계하게 만드는 도구에 가깝습니다.

    특히 배치 잡, 이벤트성 트래픽, 개발·스테이징처럼 부하가 흔들리는 환경에서는 “스케일이 된다”와 “비용이 내려간다”가 같은 말이 아닙니다. 노드가 빨리 늘어나는 것보다 중요한 건 어떤 노드를 어떤 제약 아래 띄우고, 언제 합치고, 언제 지울지를 운영자가 의도적으로 설계하는 일입니다. Karpenter는 그 의도를 코드로 옮기는 데 강합니다. 다만 반대로 말하면, 의도가 흐리면 자동화도 같이 흐려집니다.

    이 글은 AWS EKS에서 self-managed Karpenter를 사용하는 시나리오를 기준으로 정리했습니다. 예시 리소스는 2026년 8월 기준 공식 문서에서 안내하는 NodePool(karpenter.sh/v1)과 EC2NodeClass(karpenter.k8s.aws/v1) 형식을 따릅니다.

    Kubernetes 클러스터 비용 절감 흐름을 한눈에 보여주는 아키텍처 이미지입니다. 워크로드, Karpenter, EC2 인스턴스 선택 관계를 이해하는 데 도움이 됩니다.

    Kubernetes 클러스터 비용 절감에서 Karpenter가 비용을 줄이는 방식

    Karpenter의 핵심은 “노드 그룹을 미리 정해놓고 그중 하나를 키운다”가 아니라, 현재 Pending 상태인 파드 집합에 맞춰 필요한 노드 모양을 계산한다는 데 있습니다. 그래서 같은 오토스케일링이라도 비용 최적화 포인트가 다릅니다. Cluster Autoscaler는 노드 그룹 설계가 결과를 크게 좌우하지만, Karpenter는 파드 요청값과 스케줄링 제약이 결과를 더 직접적으로 좌우합니다.

    이 차이가 실무에서 크게 드러나는 순간은 두 가지입니다. 첫째, 작은 파드 여러 개를 위해 큰 노드가 반복 생성되는 경우입니다. 둘째, 리소스는 남는데 스케줄링 제약 때문에 노드를 지우지 못하는 경우입니다. 전자는 과도한 requests와 좁은 인스턴스 필터가 원인이고, 후자는 PDB, anti-affinity, topology spread, DaemonSet, local storage가 원인인 경우가 많습니다. 비용 절감은 결국 스케일 업보다 스케일 다운이 더 어렵다는 현실을 인정하는 데서 시작합니다.

    비용이 새는 대표 패턴

    • requests가 실제 사용량보다 과하게 커서 작은 워크로드도 큰 노드를 요구하는 경우
    • 인스턴스 타입을 몇 개만 허용해 대체 가능성이 줄어든 경우
    • 팀별 성격이 다른 워크로드가 한 NodePool에 섞여 consolidation 여지가 줄어든 경우
    • Spot이 가능한 워크로드까지 전부 On-Demand로 남겨둔 경우
    • PDB, anti-affinity, topology spread 제약 때문에 비어 보이는 노드를 실제로는 비우지 못하는 경우
    • DaemonSet이나 startupTaints 설정이 맞지 않아 Karpenter가 노드 상태를 보수적으로 해석하는 경우

    Cluster Autoscaler와 Karpenter 비교

    항목 Cluster Autoscaler Karpenter
    증설 기준 기존 노드 그룹 중 하나를 확장 Pending 파드 요구사항에 맞는 새 노드를 계산
    비용 최적화 강점 단순하고 예측 가능함 인스턴스 다양화, consolidation, Spot 혼합에 강함
    설계 실수의 영향 노드 그룹 과설계로 고정 낭비가 생김 requests·제약 조건 오설계가 즉시 비용으로 이어짐
    잘 맞는 환경 정적이고 노드 종류가 거의 변하지 않는 환경 워크로드 변동성이 크고 비용 민감도가 높은 환경
    굳이 안 써도 되는 경우 이미 충분히 단순한 구조라면 유지 가치가 있음 클러스터가 작고 항상 비슷한 부하라면 도입 복잡도가 더 클 수 있음

    Kubernetes 클러스터 비용 절감을 시작하기 전 점검

    Karpenter를 붙이기 전에 먼저 봐야 할 건 “지금 어디서 돈이 새는가”입니다. 저는 이걸 노드 문제인지, 파드 설계 문제인지, 스케줄링 제약 문제인지로 나눠서 봅니다. 이 구분이 안 되면 Karpenter를 넣고도 체감 절감이 약합니다. 노드는 잘 늘고 줄어도, 정작 비우지 못하는 파드가 계속 남아 있으면 청구서는 생각만큼 안 내려갑니다.

    1. 노드 사용률보다 먼저 파드 requests와 실제 사용량의 간격을 확인합니다.
    2. 서비스형 워크로드와 배치형 워크로드를 분리할 수 있는지 봅니다.
    3. Spot 허용 범위를 애플리케이션 단위로 정합니다. 클러스터 단위로 뭉뚱그리면 보통 여기서부터 꼬입니다.
    4. PDB, affinity, topology spread, local storage, DaemonSet이 스케일 다운을 막는지 확인합니다.
    5. 서브넷 태그, 보안 그룹 태그, IAM 권한처럼 “노드를 못 띄우는 이유”를 미리 제거합니다.

    실무에서는 아래 정도만 봐도 현재 낭비 구조가 꽤 선명하게 드러납니다. 특히 노드가 비어 보이는데도 사라지지 않는 이유를 찾는 데 유용합니다.

    kubectl top nodes
    kubectl top pods -A --containers
    kubectl get pods -A -o wide
    kubectl get pdb -A
    kubectl get daemonset -A -o wide
    kubectl describe node <node-name>

    여기서 해석 기준이 중요합니다. kubectl top nodes가 한가한데 노드 수가 줄지 않으면 CPU보다 제약 조건을 먼저 봐야 합니다. 반대로 Pending 파드가 있는데 노드가 안 뜨면 NodePool 조건, EC2NodeClass 태그, IAM, 가용 용량 조건 중 하나가 원인인 경우가 많습니다. 저는 이때 kubectl describe pod와 kubectl describe nodeclaim을 바로 같이 봅니다. 파드가 원하는 조건과 Karpenter가 실제로 계산한 조건이 어긋나는지 금방 보이거든요.

    Kubernetes 클러스터 비용 절감을 위한 Karpenter 실전 구현

    AWS EKS 기준으로 보면 비용 절감 설계는 NodePool은 정책, EC2NodeClass는 인프라 속성으로 역할을 나누는 순간부터 쉬워집니다. 제 경험상 비용이 안 내려가는 팀은 대개 이 둘을 분리해서 생각하지 않습니다. 노드 생성 정책과 서브넷·보안 그룹·AMI 전략이 뒤섞이면, 나중에 원인 추적이 정말 번거로워집니다.

    아래 예시는 self-managed Karpenter의 v1 API 기준으로, 인스턴스 타입을 몇 개 박아두는 대신 카테고리와 세대로 폭을 넓혀 놓은 구성입니다. 비용 최적화만 놓고 보면 이 방식이 보통 더 낫습니다. 너무 구체적인 타입 화이트리스트는 예측 가능성은 올려도, 가격 선택권과 대체 가능성을 스스로 포기하는 셈이거든요.

    apiVersion: karpenter.k8s.aws/v1
    kind: EC2NodeClass
    metadata:
      name: default
    spec:
      role: KarpenterNodeRole-${CLUSTER_NAME}
      amiSelectorTerms:
        - alias: al2023@latest
      subnetSelectorTerms:
        - tags:
            karpenter.sh/discovery: ${CLUSTER_NAME}
      securityGroupSelectorTerms:
        - tags:
            karpenter.sh/discovery: ${CLUSTER_NAME}
      tags:
        team: platform
        intent: general
    ---
    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: general
    spec:
      template:
        metadata:
          labels:
            workload: general
        spec:
          nodeClassRef:
            group: karpenter.k8s.aws
            kind: EC2NodeClass
            name: default
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
            - key: kubernetes.io/os
              operator: In
              values: ["linux"]
            - key: karpenter.sh/capacity-type
              operator: In
              values: ["spot", "on-demand"]
            - key: karpenter.k8s.aws/instance-category
              operator: In
              values: ["c", "m", "r"]
            - key: karpenter.k8s.aws/instance-generation
              operator: Gt
              values: ["5"]
          expireAfter: 720h
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
        consolidateAfter: 5m
        budgets:
          - nodes: "10%"
      limits:
        cpu: "200"

    이 설정에서 비용 관점으로 꼭 짚어야 할 포인트는 다섯 가지입니다.

    • requirements는 넓게 열고, 파드 쪽 제약으로 세밀하게 조정하는 편이 보통 유리합니다.
    • karpenter.sh/capacity-type에 spot과 on-demand를 함께 두면 선택지가 생기지만, 어떤 워크로드가 Spot으로 가도 되는지는 파드 수준에서 분리해줘야 합니다.
    • consolidationPolicy와 consolidateAfter는 비용 절감 체감에 직접 연결됩니다. 다만 너무 공격적으로 잡으면 짧은 변동에도 노드 교체가 잦아집니다.
    • budgets를 주지 않으면 Karpenter의 정리 속도가 운영 정책보다 앞설 수 있습니다. 특히 서비스형 워크로드에서는 노드 정리 속도를 제한하는 게 안전합니다.
    • expireAfter는 비용보다 운영 위생에 가깝습니다. 오래된 노드 청소, 드리프트 정리, 보안 패치 반영에는 유용하지만, 무조건 짧게 잡는다고 절감이 커지진 않습니다.

    적용 이후에는 리소스 생성 여부만 보지 말고, Karpenter가 왜 그 노드를 선택했는지를 확인해야 합니다.

    kubectl apply -f karpenter-nodeclass.yaml
    kubectl apply -f karpenter-nodepool.yaml
    kubectl get ec2nodeclass
    kubectl get nodepool
    kubectl describe nodepool general
    kubectl get nodeclaims
    kubectl describe nodeclaim <nodeclaim-name>

    describe nodeclaim에서 제가 먼저 보는 건 세 가지입니다. 요청 리소스 합계, 선택된 인스턴스 후보, 상태 조건입니다. 여기서 후보가 지나치게 좁으면 NodePool 요구사항이 과도한 거고, 상태가 Launched, Registered, Initialized 중 어디에서 막히는지 보면 인프라 문제인지 초기화 문제인지 구분이 빨라집니다. 이거 빨리 보이기 시작하면 운영 시간이 꽤 아껴집니다.

    Kubernetes 클러스터 비용 절감을 위한 NodePool과 EC2NodeClass 구성 이미지

    NodePool 정책과 EC2NodeClass 인프라 설정이 어떻게 연결되는지 설명하는 이미지입니다. 실전 구성에서 헷갈리는 지점을 줄여줍니다.

    워크로드를 섞지 말고 나누는 게 핵심입니다

    Karpenter 비용 최적화에서 제가 가장 크게 본 차이는 기능 자체보다 워크로드 분리였습니다. API 서버, 백그라운드 워커, 배치, CI 잡, 개발 환경을 한 NodePool에 몰아넣으면 겉보기엔 단순하지만 비용은 잘 안 내려갑니다. 이유는 간단합니다. 한쪽은 안정성을 위해 보수적인 배치를 원하고, 다른 한쪽은 싸고 빨리 비워지는 노드를 원하기 때문입니다. 요구가 다른 워크로드를 한 바구니에 넣으면 Karpenter는 결국 비싼 쪽 기준으로 움직이기 쉽습니다.

    실무에서는 보통 이렇게 나눕니다. 사용자 트래픽을 직접 받는 서비스는 On-Demand 중심, 중단 복구가 쉬운 배치나 비동기 작업은 Spot 중심입니다. 애플리케이션 설계가 재시도와 중단 복구를 갖추지 못했다면, Spot으로 절감하려는 시도는 비용 최적화가 아니라 장애 예고에 가깝습니다.

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: batch-spot
    spec:
      template:
        metadata:
          labels:
            workload: batch
        spec:
          nodeClassRef:
            group: karpenter.k8s.aws
            kind: EC2NodeClass
            name: default
          taints:
            - key: workload
              value: batch
              effect: NoSchedule
          requirements:
            - key: karpenter.sh/capacity-type
              operator: In
              values: ["spot"]
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
        consolidateAfter: 2m
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: batch-worker
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: batch-worker
      template:
        metadata:
          labels:
            app: batch-worker
        spec:
          nodeSelector:
            workload: batch
          tolerations:
            - key: workload
              operator: Equal
              value: batch
              effect: NoSchedule
          containers:
            - name: worker
              image: public.ecr.aws/docker/library/busybox:latest
              command: ["sh", "-c", "sleep 3600"]
              resources:
                requests:
                  cpu: "250m"
                  memory: "256Mi"

    이렇게 분리하면 좋은 점이 명확합니다. Spot 전용 NodePool에서는 공격적으로 합치고 지워도 되고, 서비스형 NodePool에서는 보수적으로 운영하면 됩니다. 비용 절감은 결국 “전체 클러스터를 싸게”가 아니라 싼 방식으로 돌려도 되는 워크로드를 정확히 분리하는 데서 시작합니다.

    트러블슈팅: 노드는 안 줄고 비용만 나가던 실제 시나리오

    제가 실제로 자주 본 재현 가능한 시나리오를 하나 말씀드릴게요. 야간 배치가 끝났고 kubectl top nodes도 한가한데, 아침까지 노드가 그대로 남아 있는 경우입니다. 겉으로는 “Karpenter가 consolidation을 못 하나?” 싶지만, 실제 원인은 대부분 더 아래층에 있습니다.

    • PDB가 너무 보수적으로 잡혀 있어 파드 축출 자체가 불가능한 경우
    • strict anti-affinity 또는 topology spread 제약 때문에 파드를 한 노드로 모을 수 없는 경우
    • 모든 노드에 올라가는 DaemonSet이 예상보다 큰 requests를 차지하는 경우
    • startupTaints를 Karpenter 설정에 반영하지 않아 새 노드를 계속 필요한 것으로 판단하는 경우
    • emptyDir나 local storage를 쓰는 파드 때문에 안전한 축출 판단이 늦어지는 경우

    이럴 때 저는 순서를 거의 고정합니다. 원인을 바깥에서 안쪽으로 좁혀가야 시간을 덜 씁니다.

    1. kubectl get pdb -A로 중단 가능 범위를 확인합니다.
    2. kubectl describe pod <pod-name>로 affinity, topology spread, toleration, nodeSelector를 봅니다.
    3. kubectl get daemonset -A -o wide와 각 DaemonSet의 requests를 확인합니다.
    4. kubectl get nodeclaims와 kubectl describe nodeclaim <name>로 Karpenter 판단 근거를 확인합니다.
    5. Karpenter 로그에서 provision, disruption, consolidation 관련 메시지를 확인합니다.

    운영 중 점검할 때는 아래 명령이 빠릅니다.

    export KARPENTER_NAMESPACE="kube-system"
    kubectl get node -l karpenter.sh/nodepool
    kubectl get nodeclaims
    kubectl describe nodeclaim <nodeclaim-name>
    kubectl logs -n "${KARPENTER_NAMESPACE}" -l app.kubernetes.io/name=karpenter --tail=200
    kubectl get events -A --sort-by=.lastTimestamp

    로그 해석 기준도 잡아두면 좋습니다. “found provisionable pod(s)”가 보이는데 nodeclaim 생성이 이어지지 않으면 NodePool 매칭 조건을 의심하세요. nodeclaim은 만들어졌는데 Ready까지 오래 걸리면 부트스트랩, CNI, IAM, 서브넷 라우팅처럼 노드 초기화 계층을 봐야 합니다. 노드가 한가한데 consolidation이 반복적으로 보류되면 대개 PDB나 스케줄링 제약 때문에 “비울 수 없는 상태”로 판단된 겁니다. 비용 최적화에서 중요한 건 낮은 사용률이 아니라 재배치 가능성입니다.

    운영 중 자주 쓰는 점검 명령

    kubectl get node
    kubectl get nodeclaims
    kubectl describe nodeclaim <nodeclaim-name>
    kubectl describe pod <pod-name>
    kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter --tail=200

    describe 출력에서는 Events와 Conditions를 먼저 보시면 됩니다. 생성은 됐는데 Registered 단계에서 머물면 인스턴스가 클러스터에 붙지 못하는 문제일 가능성이 크고, Initialized 단계에서 오래 걸리면 CNI나 kubelet 초기화 쪽을 봐야 합니다. 반대로 생성 시도 자체가 없으면, 그건 거의 항상 스케줄링 조건 문제입니다.

    Kubernetes 클러스터 비용 절감이 제대로 되는지 검증하는 법

    비용 절감은 노드 수만 보고 판단하면 자주 실패합니다. 저는 최소한 노드 수 변화, Pending 지속 시간, Spot 비중, 비어 있는 노드 유지 시간, 워크로드 재배치 가능성을 같이 봅니다. 노드 수가 줄어도 Pending이 늘어나면 절감이 아니라 가용성 훼손이고, Spot 비중이 늘어도 재시도 실패가 증가하면 운영비를 장애 비용으로 바꿔치기한 셈입니다.

    검증할 때 보는 체크포인트는 아래와 같습니다.

    • 배치 종료 후 불필요한 노드가 자연스럽게 정리되는지
    • 트래픽 급증 시 Pending 파드가 길게 남지 않는지
    • Spot 전환 대상 워크로드에서 재시도 실패가 늘지 않았는지
    • NodePool 분리로 조각화가 심해지지 않았는지
    • 인스턴스 타입 후보가 지나치게 좁아져 특정 시점에 노드 생성 실패가 반복되지 않는지

    실무적으로는 하나만 기억하셔도 됩니다. 비용 최적화는 평균 사용률 게임이 아니라, 배치 가능성과 축출 가능성 게임입니다. 파드를 잘 올리는 것만큼, 안전하게 옮기고 비울 수 있어야 청구서가 내려갑니다.

    Kubernetes 클러스터 비용 절감 결과를 보여주는 Karpenter 대시보드 이미지

    적용 전후 비교에 쓰기 좋은 대시보드 이미지입니다. 노드 수 변화, Spot 활용, 빈 노드 정리 흐름을 시각적으로 보여줍니다.

    선택 기준: 어떤 환경에 어떤 설정이 맞는가

    상황 추천 전략 이유 주의할 점
    운영 서비스 중심 On-Demand 우선, Spot은 보조, 보수적인 consolidation 가용성 손실 비용이 인프라 절감보다 크기 쉬움 PDB, zone spread, disruption budget을 먼저 설계해야 함
    배치·크론·비동기 워커 중심 Spot 적극 활용, 인스턴스 후보 넓게 허용 중단 허용성이 있으면 절감 여지가 큼 재시도, 체크포인트, idempotency가 없으면 오히려 위험
    개발·스테이징 환경 expireAfter와 consolidation을 적극 사용 유휴 시간이 길어 자동 정리 효과가 큼 야간 배포나 테스트 창과 충돌하지 않게 시간대 정책 필요
    멀티테넌트 클러스터 NodePool 분리, taint/label 정책 명확화 비용 책임과 워크로드 성격을 분리해야 최적화가 쉬움 과도한 분리는 조각화와 운영 복잡도를 키움
    항상 비슷한 부하의 소규모 클러스터 도입 전 단순한 고정 노드 구조와 비교 검토 Karpenter 운영 복잡도보다 절감 폭이 작을 수 있음 도구 도입 자체가 목적이 되지 않게 해야 함

    제 추천은 꽤 분명합니다. 서비스형 비중이 크면 안정형 NodePool부터 작게 시작하시고, 배치형 비중이 크면 Spot 전용 풀을 먼저 분리하시는 게 좋습니다. 둘을 동시에 크게 바꾸면 원인 추적이 어려워집니다. 비용 최적화는 한 번에 크게 먹히는 기술보다, 작은 정책을 분리해서 효과를 측정하는 운영 방식에 더 가깝습니다.

    자주 헷갈리는 포인트

    • Karpenter를 넣는다고 requests 오설계가 자동으로 해결되진 않습니다. 오히려 잘못된 requests를 더 빠르게 비용으로 바꿉니다.
    • 인스턴스 타입을 세세하게 고정하면 예측은 쉬워지지만 비용 선택권은 줄어듭니다. 특별한 이유가 없다면 계열·세대 중심 제약이 더 실용적입니다.
    • Spot 비중을 높일수록 애플리케이션의 중단 내성이 먼저 준비돼 있어야 합니다.
    • 노드 수가 줄어도 Pending과 축출 실패가 늘면 실패한 최적화입니다.
    • 조각화된 NodePool은 운영자가 통제하는 것 같아 보여도, 실제로는 consolidation 여지를 줄여 비용을 다시 올릴 수 있습니다.

    도입 전 점검 항목, 운영 체크포인트, 상황별 추천 전략을 요약한 인포그래픽 이미지입니다.

    마무리

    Kubernetes 클러스터 비용 절감은 노드를 적게 띄우는 기술이 아니라, 워크로드마다 다른 실패 허용 범위를 자원 정책으로 번역하는 일에 가깝습니다. Karpenter는 그 번역을 자동화해 주지만, 만능은 아닙니다. requests가 부정확하고, PDB가 과도하고, 스케줄링 제약이 뒤엉켜 있으면 자동화는 낭비를 더 빠르게 반복할 뿐입니다.

    그래서 저는 이렇게 권합니다. 운영 서비스가 중심이면 On-Demand 기반의 보수적 NodePool부터 시작하세요. 배치·개발 환경이 중심이면 Spot 전용 분리와 적극적인 consolidation부터 적용하시는 게 맞습니다. 둘 중 무엇이든 공통으로, 처음에는 NodePool 하나만 작게 도입해서 Pending 파드, NodeClaim 상태, 노드 정리 흐름을 관찰해 보세요. 그 단계에서 비용이 어디서 새는지, 그리고 Karpenter가 어디까지 해결해 주는지가 훨씬 또렷하게 보입니다.

    관련해서 이 블로그의 EKS 운영 가이드나 Kubernetes 스케줄링 글도 함께 읽어보시면, NodePool 분리와 requests 튜닝을 더 입체적으로 잡는 데 도움이 됩니다.

  • [k8s] Karpenter 비용 최적화: 불필요한 노드 스케일링 방지 전략

    [k8s] Karpenter 비용 최적화: 불필요한 노드 스케일링 방지 전략

    [k8s] Karpenter 비용 최적화: 불필요한 노드 스케일링 방지 전략

    Karpenter 비용 최적화는 쿠버네티스 비용 절감에서 생각보다 훨씬 큰 비중을 차지하는데요. 클러스터를 한동안 운영해보면 CPU나 메모리를 거의 안 쓰는데도 노드가 계속 살아 있고, 짧은 스파이크 때문에 큰 인스턴스가 붙었다가 한참 뒤에야 내려가는 상황을 꼭 한 번쯤 겪게 되더라고요. 저도 홈랩과 실서비스 환경에서 Karpenter를 붙인 뒤 처음엔 “오토스케일링이면 자동으로 다 좋아지겠지”라고 생각했었는데, 실제로 써보니까 설정 하나 잘못 두면 Karpenter 노드 스케일링이 오히려 비용을 키우는 방향으로 움직이더라고요.

    특히 요청값(requests) 과대 설정, 파드 배치 밀도 부족, 비어 있지 않은 애매한 노드, 지나치게 넓은 인스턴스 선택 범위가 겹치면 노드가 쉽게 늘어나는데요. 여기서 중요한 포인트는 Karpenter가 문제의 원인이 아니라 현재 스케줄링 조건에 굉장히 충실하게 반응하는 엔진이라는 거더라고요. 쉽게 말해 입력이 거칠면 결과도 거칠게 나옵니다. 이번 글에서는 제가 직접 삽질하면서 정리한 Karpenter 비용 최적화 방법을, 현장에서 바로 적용할 수 있는 기준 위주로 풀어보겠습니다.

    Karpenter 비용 최적화 아키텍처를 설명하는 쿠버네티스 노드 스케일링 다이어그램

    Karpenter가 스케줄링 수요를 받아 노드를 추가하고, 한가한 노드를 통합하는 흐름을 보여주는 개요 이미지입니다.

    Karpenter 비용 최적화가 어려운 이유

    저도 처음엔 헷갈렸는데, 많은 분이 Cluster Autoscaler(클러스터 오토스케일러)와 Karpenter를 비슷하게만 보시더라고요. 그런데 운영 관점에서는 결이 조금 다르더라고요. Karpenter는 스케줄되지 못한 파드(Pod)를 보고 그때그때 더 적절한 노드를 만들려는 성향이 강합니다. 그래서 잘 맞추면 정말 시원하게 붙고 빠지는데, 반대로 조건이 지저분하면 필요 이상으로 세분화된 노드가 생기기 쉬워요.

    쉽게 말해, 아래 네 가지가 겹치면 비용이 올라갑니다.

    • 과한 requests: 애플리케이션이 실제보다 큰 CPU/메모리 요청을 잡고 있는 경우
    • 낮은 bin packing: 비슷한 파드끼리 한 노드에 촘촘히 못 모이는 경우
    • 느슨한 인스턴스 선택: 너무 큰 타입까지 후보에 열어둔 경우
    • 애매한 축소 정책: 비어 있지 않지만 옮길 수 있는 노드가 오래 살아남는 경우

    결국 핵심은 필요한 순간에는 빨리 늘리고, 불필요해진 순간에는 최대한 덜 남기기예요. 이 균형을 못 잡으면 오토스케일링 최적화가 아니라 오토 과금이 됩니다 ㅎㅎ

    Karpenter 노드 스케일링을 줄이는 핵심 개념

    제가 실무에서 가장 먼저 보는 건 세 가지입니다. 바로 requests, disruption(중단/통합 정책), 그리고 노드 풀의 제약조건이에요.

    항목 왜 중요한가 비용 영향
    resources.requests 스케줄러가 파드 배치 가능 여부를 판단하는 기준 과대 설정 시 필요 노드 수 증가
    NodePool 제약 어떤 인스턴스 계열과 용량 타입을 쓸지 결정 너무 넓으면 큰 노드가 쉽게 선택됨
    Consolidation(통합) 덜 효율적인 노드를 더 적은 수의 노드로 재배치 잔여 노드 제거에 직접적
    DaemonSet 오버헤드 모든 노드에 공통으로 올라가는 리소스 사용량 소형 노드 전략을 무너뜨릴 수 있음

    여기서 특히 놓치기 쉬운 게 DaemonSet(데몬셋, 모든 노드에 공통 배포되는 워크로드) 오버헤드인데요. 모니터링 에이전트, 로그 수집기, CNI(컨테이너 네트워크 인터페이스) 관련 파드가 생각보다 자리를 많이 차지하더라고요. 저도 처음엔 “왜 이렇게 작은 파드 몇 개 때문에 노드가 또 붙지?” 싶었는데, 계산해보니 공통 오버헤드가 누적돼 있더라고요.

    실전 1: requests부터 먼저 다이어트하기

    Karpenter 비용 최적화에서 가장 효과가 큰 건 의외로 Karpenter 설정이 아니라 워크로드 설정 정리더라고요. 실제로 써보니까 requests를 현실화하는 것만으로도 불필요한 노드 스케일링이 확 줄었거든요. HPA(Horizontal Pod Autoscaler, 수평 확장)나 VPA(Vertical Pod Autoscaler, 수직 조정)를 쓰더라도 requests가 터무니없이 크면 소용이 없더라고요.

    1. 상위 소비 워크로드를 찾습니다.
    2. 실사용량 대비 requests가 과한지 봅니다.
    3. 갑자기 줄이지 말고 단계적으로 낮춥니다.
    4. 배포 후 Pending(스케줄 불가)이나 OOMKilled를 꼭 확인합니다.
    kubectl top pod -A --containers
    kubectl get deploy -A -o yaml
    kubectl describe pod -n your-namespace your-pod
    

    예를 들어 웹 애플리케이션이 실제로는 CPU를 크게 쓰지 않는데도 requests를 넉넉하게 잡아두면, 스케줄러는 그 값을 진실로 믿고 더 많은 노드를 요구하게 돼요. 아래처럼 현실적인 범위로 정리하면 밀도가 확 좋아집니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: web-api
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: web-api
      template:
        metadata:
          labels:
            app: web-api
        spec:
          containers:
            - name: web-api
              image: nginx:stable
              resources:
                requests:
                  cpu: "250m"
                  memory: "256Mi"
                limits:
                  cpu: "500m"
                  memory: "512Mi"
    

    물론 숫자는 서비스마다 다르게 가져가야 해요. 여기서 중요한 건 정확한 절대값이 아니라, 실제 사용 패턴과 요청값의 간격을 줄이는 것입니다. 혹시 CPU는 낮은데 메모리만 높게 유지되는 워크로드가 있다면, 그 자체가 노드 분산의 원인이 될 수 있더라고요.

    실전 2: NodePool 제약으로 큰 인스턴스 남발 막기

    다음으로 많이 효과를 본 게 NodePool(노드풀, 노드 생성 정책 묶음) 제약이에요. Karpenter는 후보군이 넓을수록 순간적으로 커 보이는 선택을 할 수 있거든요. 특히 “아무 인스턴스나 가능”처럼 열어두면 비용 최적화보다 스케줄 성공률 쪽으로 기울 수 있더라고요. 그래서 저는 업무 성격별로 NodePool을 나눠 두는 편입니다.

    아래 예시는 범용 워크로드를 위한 보수적인 제약 패턴이에요. 인스턴스 계열과 세대, 아키텍처, 용량 타입(capacity type)을 좁혀서 운영 예측 가능성을 높이는 방식이거든요.

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: general
    spec:
      template:
        spec:
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
            - key: kubernetes.io/os
              operator: In
              values: ["linux"]
            - key: karpenter.k8s.aws/instance-category
              operator: In
              values: ["c", "m"]
            - key: karpenter.k8s.aws/instance-generation
              operator: Gt
              values: ["3"]
            - key: karpenter.sh/capacity-type
              operator: In
              values: ["on-demand", "spot"]
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
        consolidateAfter: 5m
    

    여기서 중요한 포인트! 제약은 좁히되, 너무 빡빡하게는 두지 않는 것이 중요해요. 후보가 지나치게 적으면 스케줄 실패나 가격 변동 대응력 저하로 이어질 수 있더라고요. 제가 처음엔 계열을 너무 좁게 잡았다가 특정 시간대에 배치가 꼬여서 다시 완화했었습니다.

    Karpenter 비용 최적화를 위한 NodePool 요구사항 구성 이미지

    NodePool 제약조건을 통해 인스턴스 후보군을 제어하는 구성을 시각화한 이미지입니다.

    실전 3: Consolidation과 만료 정책으로 잔여 노드 줄이기

    오토스케일링 최적화에서 체감 차이가 큰 기능이 Consolidation(통합)이에요. 쉽게 말해 여러 노드에 어정쩡하게 흩어진 파드를 더 적은 노드로 옮길 수 있으면 정리하는 기능이거든요. 이걸 켜두지 않으면 트래픽이 빠진 뒤에도 애매하게 남은 노드가 오래 버텨요.

    제가 직접 해보니 아래 순서로 접근하는 게 안전했습니다.

    1. 먼저 underutilized(저활용) 통합 정책을 검토합니다.
    2. 바로 공격적으로 줄이지 말고, consolidateAfter 시간을 둡니다.
    3. PodDisruptionBudget(PDB, 파드 중단 예산) 때문에 이동이 막히는지 같이 봅니다.
    4. 배치 안정성이 확인되면 빈 노드 정리 시간을 조금 더 줄입니다.
    kubectl get nodepool
    kubectl get nodes
    kubectl get pods -A -o wide
    kubectl describe node <node-name>
    

    운영하다 보면 “왜 비어 보이는 노드가 안 내려가지?” 싶은 경우가 있는데, 대부분은 다음 중 하나더라고요.

    • PDB 때문에 파드 축출이 제한됨
    • anti-affinity(안티 어피니티, 분산 배치 규칙)가 강해서 재배치가 어려움
    • DaemonSet 오버헤드 때문에 다른 노드로 합치기 애매함
    • local storage(로컬 스토리지) 의존 파드가 있음

    즉, 통합 정책을 켠다고 끝이 아니라 파드가 정말 옮겨질 수 있는 상태인지를 같이 만들어야 해요. 이 부분에서 삽질 좀 했습니다 ㅎㅎ

    실전 4: Spot과 On-Demand를 역할별로 분리하기

    쿠버네티스 비용 절감을 노린다고 무조건 Spot(스팟, 유휴 용량 기반 저비용 인스턴스)을 늘리는 건 조금 위험해요. 비용은 줄 수 있어도 재시작 민감 워크로드가 섞이면 오히려 장애 대응 비용이 올라가더라고요. 그래서 저는 성격이 다른 워크로드를 섞지 않도록 taint/toleration(테인트/톨러레이션, 특정 노드 허용 규칙)과 NodePool 분리 전략을 권장해요.

    워크로드 유형 권장 용량 타입 이유
    배치성 잡(Job), 비동기 처리 Spot 중심 중단 허용 범위가 비교적 큼
    사용자 요청 직접 처리 API On-Demand 중심 지속성과 예측 가능성이 중요
    혼합형 워크로드 분리 운영 비용과 안정성 균형 확보

    이렇게 나누면 Karpenter가 필요한 곳에만 공격적인 비용 절감 전략을 적용하게 되는데요. 결과적으로 비용도 줄고, 왜 특정 노드가 붙었는지도 설명이 쉬워져요.

    ⚠️ 주의사항: 실제로 많이 막히는 포인트

    여기부터는 경험담에 가깝습니다. 저도 처음엔 “Karpenter가 똑똑하니까 알아서 정리해주겠지” 했었는데, 아래 네 가지에서 자주 막혔어요.

    1. requests는 줄였는데 HPA가 갑자기 민감해진 경우

    CPU requests를 낮추면 상대 사용률이 높아 보이면서 HPA가 더 빨리 반응할 수 있거든요. 즉 requests 다이어트와 HPA 목표값은 같이 봐야 합니다.

    2. PDB가 너무 보수적인 경우

    minAvailable이 높으면 consolidation이 생각보다 거의 안 움직여요. 서비스 특성을 해치지 않는 선에서 현실화가 필요합니다.

    3. anti-affinity를 습관처럼 강제한 경우

    고가용성을 위해 분산시키는 건 좋지만, 모든 워크로드에 강한 규칙을 걸면 노드가 잘 안 모여요. 꼭 필요한 곳에만 써야 합니다.

    4. DaemonSet 리소스를 잊은 경우

    노드를 잘게 쪼개 쓰려면 공통 오버헤드가 치명적이에요. 모니터링, 로깅, 보안 에이전트가 많을수록 소형 노드 전략은 불리해져요.

    이런 문제를 점검할 때는 이벤트와 노드 상태를 같이 보는 게 좋습니다.

    kubectl get events -A --sort-by=.lastTimestamp
    kubectl get pdb -A
    kubectl get daemonset -A
    kubectl top node
    
    Karpenter 비용 최적화 과정에서 노드 통합이 막히는 원인을 보여주는 대시보드 이미지

    불필요한 노드가 남아 있는 원인을 추적할 때 보는 지표와 병목 요소를 보여주는 이미지입니다.

    검증: Karpenter 비용 최적화가 잘 되었는지 확인하는 법

    설정을 바꿨으면 반드시 검증해야 해요. 저는 보통 아래 순서로 봅니다.

    1. 노드 수의 일중 변동 폭이 줄었는지 확인합니다.
    2. 트래픽 감소 후 빈 노드 정리 시간이 짧아졌는지 봅니다.
    3. 평균 노드 사용률이 올라갔는지 확인합니다.
    4. Pending 파드나 재스케줄 실패가 없는지 확인합니다.

    여기서 중요한 건 비용만 보면 안 된다는 점이에요. 비용은 줄었는데 배포 시간이 늘어나거나, 특정 시간대 지연이 늘었다면 진짜 최적화가 아니거든요. 제가 보는 체크리스트는 이렇습니다.

    • 노드당 파드 밀도 증가
    • 불필요한 대형 인스턴스 비중 감소
    • 저활용 노드 체류 시간 감소
    • 서비스 안정성 유지

    모니터링 시스템이 있다면 노드 생성/삭제 이벤트, CPU/메모리 요청 대비 사용량, Spot과 On-Demand 비중을 함께 보면 좋아요. 이거 진짜 편하더라고요. 원인과 결과가 한 화면에서 이어져 보이니까요.

    Karpenter 비용 최적화 전후 결과를 보여주는 노드 사용률 및 비용 비교 이미지

    Karpenter 비용 최적화 전후의 노드 개수와 활용률 변화를 비교하는 결과 이미지입니다.

    정리: 제가 권장하는 적용 순서

    마지막으로, 처음 적용하시는 분이라면 아래 순서가 가장 안전해요. 한 번에 다 건드리기보다 단계적으로 가는 게 좋습니다.

    1. 워크로드 requests를 현실화합니다.
    2. NodePool 제약으로 과도한 인스턴스 선택을 줄입니다.
    3. Consolidation 정책을 켜고 재배치 가능성을 점검합니다.
    4. Spot과 On-Demand를 워크로드별로 분리합니다.
    5. HPA, PDB, anti-affinity를 함께 조정합니다.

    Karpenter 비용 최적화는 설정 한 줄의 마법이라기보다, 스케줄링 입력값을 정리하는 운영 습관에 가깝습니다. 저도 처음엔 Karpenter만 만지면 될 줄 알았는데, 실제로는 애플리케이션 requests와 배치 정책이 훨씬 중요했어요. 그래서 이 주제는 결국 플랫폼 팀과 애플리케이션 팀이 같이 봐야 하더라고요.

    혹시 지금 클러스터에서 노드는 자꾸 늘어나는데 사용률은 낮은 상황이라면, 가장 먼저 requests와 PDB부터 점검해보세요. 체감 효과가 큽니다. 다음 글에서는 VPA와 HPA를 함께 사용할 때 어떤 순서로 튜닝하면 좋은지 다뤄볼 예정입니다. 이전 글에서 다룬 리소스 요청값 설계 글이 있다면 같이 참고하셔도 흐름이 잘 이어져요. 드디어 됐다 싶은 순간이 분명 옵니다 🎉

    Karpenter 비용 최적화 체크리스트와 적용 순서를 정리한 인포그래픽

    실무 적용 순서와 핵심 점검 항목을 한 장으로 정리한 요약 이미지입니다.

  • [k8s] Karpenter로 EKS 비용 절감: 실전 운영 사례와 최적화 전략

    [k8s] Karpenter로 EKS 비용 절감: 실전 운영 사례와 최적화 전략

    [인프라] Karpenter 비용 절감: EKS 중심 운영 사례와 AKS 적용 포인트

    Karpenter 비용 절감 이야기는 요즘 EKS 운영하시는 분들이라면 한 번쯤은 꼭 부딪히는 주제입니다. 저도 처음엔 단순히 노드 수만 줄이면 되겠지 싶었는데, 실제로 써보니까 그렇게 단순한 문제가 아니더라고요. 특히 트래픽이 출렁이는 서비스에서는 오토스케일링 비용이 눈에 잘 안 띄게 새고 있었습니다. 월말 청구서를 보고 나서야 “아, 이거 손봐야겠네요” 싶었던 적이 꽤 있었거든요.

    먼저 중요한 점부터 짚고 갈게요. Karpenter는 AWS EKS 환경에서 실제 많이 쓰이는 오픈소스 노드 프로비저닝(Node Provisioning) 도구입니다. 반면 AKS에서는 보통 Cluster Autoscaler(클러스터 오토스케일러)나 노드풀 전략으로 비슷한 비용 최적화를 접근합니다. 그래서 이 글은 제가 직접 많이 다뤄본 EKS + Karpenter 운영 사례를 중심으로 설명하고, 마지막에 AKS 비용 최적화에 어떻게 같은 원칙을 가져갈 수 있는지 연결해서 정리해보겠습니다.

    Karpenter 비용 절감 구조를 보여주는 EKS 전체 아키텍처 다이어그램

    이미지 설명: EKS 환경에서 Karpenter가 스케줄링 수요를 보고 노드를 빠르게 생성하고, 유휴 노드를 정리하는 흐름을 한눈에 보여주는 아키텍처입니다.

    Karpenter 최적화가 왜 비용 절감으로 바로 이어질까요?

    쉽게 말해 Karpenter는 “필요한 순간에 필요한 크기의 노드를 빠르게 붙여주는 사람”입니다. 기존 Cluster Autoscaler(클러스터 오토스케일러)는 이미 만들어둔 노드 그룹(Node Group) 안에서 증감을 고민하는 느낌이 강한데요, Karpenter는 좀 더 유연하게 인스턴스 타입(Instance Type) 선택 폭을 가져갈 수 있는 게 장점입니다.

    실제로 운영해보면 비용이 새는 지점은 대체로 비슷합니다.

    • 과하게 큰 인스턴스를 기본값처럼 쓰는 경우
    • 야간이나 비업무 시간에도 노드가 그대로 살아 있는 경우
    • Pod requests/limits 설정이 보수적으로 너무 큰 경우
    • 온디맨드(On-Demand)만 고집해서 Spot(스팟) 기회를 놓치는 경우
    • DaemonSet(데몬셋)과 시스템 파드 때문에 작은 노드가 비효율적으로 배치되는 경우

    이건 꽤 중요한데요. Karpenter 비용 절감은 단순히 “노드를 줄인다”가 아니라, 워크로드에 맞는 노드를 더 잘 고른다에 가깝습니다. 저도 처음엔 이 차이를 잘 몰라서 인스턴스 수만 보고 있었는데, 결국 핵심은 빈 시간, 빈 공간, 잘못 잡은 요청치(requests)를 줄이는 쪽이더라고요.

    EKS 비용을 줄이는 Karpenter 핵심 개념

    1. Provisioner 대신 최신 문서 기준 NodePool/EC2NodeClass 흐름 이해하기

    Karpenter는 버전이 올라가면서 리소스 모델이 바뀐 적이 있습니다. 운영 중인 버전에 따라 리소스 이름이 다를 수 있는데, 실무에서는 현재 사용 중인 차트와 CRD(Custom Resource Definition) 버전을 먼저 확인하시는 게 중요합니다. 저도 예전 예제만 보고 적용했다가 CRD mismatch(정의 불일치)로 삽질 좀 했습니다 ㅎㅎ

    2. Consolidation(통합)과 Expiration(만료) 전략

    이 기능이 꽤 큽니다. 유휴 노드가 생기면 더 적은 수의 노드로 재배치하고 남는 노드를 정리하는 식인데요. 이걸 켜두지 않으면 Karpenter를 써도 생각보다 절감폭이 안 나옵니다.

    3. 다양한 인스턴스 타입 허용

    특정 타입 하나만 고정하면 스케줄링도 경직되고 단가 최적화도 어렵습니다. 저는 CPU 아키텍처, 세대, 크기 범위를 조금 넓게 허용했을 때 훨씬 안정적이었습니다. 물론 워크로드가 특정 아키텍처에 종속되지 않는지 확인은 먼저 해야 합니다.

    4. Spot과 On-Demand 혼합

    상태 비저장(Stateless) 워크로드는 Spot을 적극 검토할 만합니다. 다만 모든 걸 Spot으로 몰면 중단(interruption)에 취약해지니, 핵심 서비스는 On-Demand 기반으로 남겨두고 배치나 비핵심 API 계층만 분리하는 식이 현실적입니다.

    항목 기존 고정 노드 그룹 Karpenter 최적화 후
    인스턴스 선택 미리 정한 몇 개 타입 위주 조건 기반으로 유연하게 선택
    유휴 노드 정리 느리거나 보수적 통합 정책으로 적극 정리
    비용 대응력 트래픽 급변 시 비효율 가능 수요 기반 대응이 쉬움
    Spot 활용 노드 그룹 설계에 묶임 워크로드별 분리 전략 수월

    실전 구현: EKS에서 Karpenter 최적화 적용한 흐름

    이제 제가 실제로 자주 쓰는 흐름으로 가보겠습니다. 아래 예시는 개념을 보여주기 위한 형태이고, 계정 구조나 네트워크 설계에 따라 이름과 태그는 바꾸셔야 합니다.

    1. EKS 클러스터 버전과 Karpenter 호환성 확인
    2. IAM 역할과 IRSA(IAM Roles for Service Accounts) 구성
    3. 서브넷/보안그룹 태그 점검
    4. EC2NodeClass와 NodePool 분리 설계
    5. 온디맨드/스팟 워크로드 분리
    6. consolidation 정책 활성화
    7. 요청치(requests) 재조정 후 실제 절감률 측정

    1. Helm(헬름)으로 Karpenter 설치

    helm repo add karpenter https://charts.karpenter.sh
    helm repo update
    
    helm upgrade --install karpenter karpenter/karpenter \
      --namespace karpenter \
      --create-namespace \
      --set settings.clusterName=my-eks-cluster \
      --set serviceAccount.create=true

    여기서 많이 놓치는 게 IAM 권한입니다. 컨트롤러는 떠 있는데 실제 인스턴스 생성이 안 되는 경우가 꽤 많거든요. 저는 처음에 “왜 파드만 Pending(대기)이지?” 싶었는데, 알고 보니 IAM 정책 누락이었습니다.

    2. EC2NodeClass 예시

    apiVersion: karpenter.k8s.aws/v1beta1
    kind: EC2NodeClass
    metadata:
      name: default
    spec:
      amiFamily: AL2
      role: KarpenterNodeRole-my-eks-cluster
      subnetSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks-cluster
      securityGroupSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks-cluster
      tags:
        intent: apps

    서브넷과 보안그룹 태그는 정말 중요합니다. 자동 검색이 안 되면 노드가 안 뜹니다. 로그만 보면 애매하게 보여서 시간을 꽤 잡아먹더라고요.

    3. NodePool 예시

    apiVersion: karpenter.sh/v1beta1
    kind: NodePool
    metadata:
      name: general
    spec:
      disruption:
        consolidationPolicy: WhenUnderutilized
        expireAfter: 720h
      template:
        spec:
          nodeClassRef:
            name: default
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
            - key: karpenter.sh/capacity-type
              operator: In
              values: ["on-demand", "spot"]
            - key: node.kubernetes.io/instance-type
              operator: In
              values: ["m5.large", "m5.xlarge", "m6i.large", "m6i.xlarge"]

    실제로 써보니까 인스턴스 타입을 너무 좁게 잡으면 오히려 스케줄링이 꼬입니다. 반대로 너무 넓히면 운영자가 예상하지 못한 조합이 나올 수 있으니, 워크로드 특성에 맞는 범위를 정하는 게 좋습니다.

    Karpenter 최적화 설정과 온디맨드 스팟 분리를 보여주는 구성도

    이미지 설명: NodePool, EC2NodeClass, IAM, 서브넷 태그가 어떻게 연결되는지와 온디맨드/스팟 분리 구조를 보여주는 구성도입니다.

    4. 스팟 전용 워크로드 분리

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: batch-worker
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: batch-worker
      template:
        metadata:
          labels:
            app: batch-worker
        spec:
          nodeSelector:
            karpenter.sh/capacity-type: spot
          containers:
            - name: worker
              image: public.ecr.aws/docker/library/busybox:latest
              command: ["sh", "-c", "sleep 3600"]
              resources:
                requests:
                  cpu: "250m"
                  memory: "256Mi"

    배치성 작업이나 재시도가 쉬운 서비스라면 이런 식으로 분리하는 게 꽤 잘 먹힙니다. 다만 데이터베이스 연결을 오래 붙잡는 작업이나 세션 민감한 서비스는 신중하셔야 합니다.

    ⚠️ Karpenter 운영에서 자주 겪는 문제와 해결법

    이 섹션은 진짜 경험 기반입니다. 문서만 보면 금방 될 것 같은데, 현장에서는 꼭 어딘가에서 막히더라고요.

    1. 파드는 Pending인데 노드가 생성되지 않는 경우

    • IAM 권한 누락 여부 확인
    • 서브넷/보안그룹 discovery 태그 확인
    • NodePool requirements가 너무 빡빡하지 않은지 확인
    • 리전에서 해당 인스턴스 타입 공급이 가능한지 확인

    특히 requirements 조건을 여러 개 겹치면 스케줄링 후보가 거의 사라집니다. 저는 아키텍처, 세대, capacity-type, zone까지 한꺼번에 강하게 제한했다가 노드가 안 뜬 적이 있었습니다.

    2. 절감이 기대보다 안 나오는 경우

    이건 거의 항상 requests 과다 설정이 원인이었습니다. Karpenter는 요청된 리소스를 기준으로 판단하니까, 애플리케이션 팀이 CPU 1core, 메모리 2Gi를 습관적으로 박아둔 상태면 빈 공간이 많아져도 노드를 줄이기 어렵습니다.

    kubectl top pods -A
    kubectl get pods -A -o wide
    kubectl describe node

    이 명령어로 실제 사용량과 요청치를 같이 보시면 감이 옵니다. 처음엔 귀찮아도 이 작업을 해두면 EKS 비용 줄이는 데 체감 차이가 큽니다.

    3. DaemonSet 때문에 작은 노드가 오히려 손해인 경우

    로깅 에이전트, 모니터링 에이전트, CNI 관련 파드가 노드마다 붙잖아요. 그래서 무조건 작은 노드가 좋은 게 아닙니다. 너무 잘게 쪼개면 시스템 오버헤드가 늘어납니다. 이 부분은 홈랩에서도 실험해봤는데, 작은 노드 여러 개보다 중간 크기 몇 개가 효율적인 구간이 분명히 있더라고요.

    4. Spot interruption 대응 부족

    스팟은 싸지만 공짜는 아닙니다. 중단 알림(interruption notice)을 고려한 설계가 없으면 장애 대응 비용이 더 커질 수 있어요. 오토스케일링 비용만 볼 게 아니라 운영 리스크까지 같이 보셔야 합니다.

    검증: 비용이 실제로 얼마나 줄었는지 어떻게 보나요?

    제가 직접 해보니 “30% 절감” 같은 숫자는 환경에 따라 차이가 큽니다. 다만 공통적으로는 아래 항목을 같이 봐야 의미가 있습니다.

    • 노드 총 가동 시간 감소 여부
    • 평균 노드 활용률 상승 여부
    • 스팟 비중 증가 여부
    • 배포 실패나 재시작 증가 여부
    • 응답 시간과 에러율 변화 여부

    저는 대시보드를 볼 때 단순 인프라 비용만 보지 않고, 서비스 안정성까지 같이 체크합니다. 비용은 줄었는데 장애가 늘면 그건 실패거든요.

    kubectl get node
    kubectl get pods -A
    kubectl logs -n karpenter deploy/karpenter

    추가로 클라우드 비용 분석 도구에서 노드 사용 추이를 월간으로 비교해보면 좋습니다. 같은 트래픽 수준에서 유휴 시간이 줄고, 피크 시간 확장 속도가 좋아졌다면 방향을 잘 잡은 겁니다.

    Karpenter 비용 절감 결과를 보여주는 노드 사용률 및 비용 대시보드

    이미지 설명: 비용 분석 대시보드에서 최적화 전후 노드 수, 활용률, 유휴 시간 변화를 비교하는 예시입니다.

    AKS 비용 최적화는 어떻게 연결하면 될까요?

    많이들 헷갈리시는데요. AKS 비용 최적화라는 관점은 분명히 중요하지만, 실무적으로는 Karpenter 자체보다 Cluster Autoscaler, 노드풀 분리, Spot VM, 요청치 정리 같은 방식으로 접근하는 게 일반적입니다. 즉, 도구는 달라도 원칙은 거의 같습니다.

    최적화 원칙 EKS AKS
    수요 기반 노드 증감 Karpenter Cluster Autoscaler 중심
    워크로드 분리 NodePool/taint/toleration Node Pool/taint/toleration
    저비용 인스턴스 활용 Spot Spot VM
    리소스 요청 최적화 필수 필수

    그러니까 “EKS는 Karpenter, AKS는 다른 도구”라고 외우기보다, 워크로드를 세분화하고 유휴 자원을 줄이며 저비용 노드를 안전하게 섞는 전략이 핵심이라고 보시면 됩니다. 이 관점으로 보면 클라우드가 달라도 사고방식이 꽤 통일됩니다.

    실무 체크리스트: 제가 마지막에 꼭 보는 항목

    1. requests/limits가 실제 사용량과 크게 어긋나지 않는가
    2. 온디맨드와 스팟 워크로드가 명확히 분리되어 있는가
    3. NodePool 조건이 과도하게 제한적이지 않은가
    4. 유휴 노드 정리 정책이 활성화되어 있는가
    5. 비용 절감 후에도 SLO(Service Level Objective)에 영향이 없는가

    이 체크리스트만 꾸준히 봐도 Karpenter 최적화 방향을 크게 벗어나지 않습니다. 사실 화려한 기능보다 이런 기본기가 더 중요하더라고요.

    EKS 비용과 AKS 비용 최적화 원칙을 비교한 요약 인포그래픽

    이미지 설명: EKS와 AKS 각각에서 적용 가능한 비용 절감 원칙을 비교 정리한 요약 인포그래픽입니다.

    정리와 다음 단계

    정리해보면 이렇습니다. Karpenter 비용 절감은 EKS에서 꽤 강력한 선택지이고, 제대로 설정하면 체감이 분명히 옵니다. 다만 만능은 아닙니다. 요청치가 엉망이거나 워크로드 분리가 안 되어 있으면 기대만큼 안 줄어듭니다. 저도 처음엔 “설치만 하면 끝나는 거 아닌가?” 싶었는데, 실제로는 관찰과 조정이 더 중요했습니다.

    그리고 AKS 쪽은 Karpenter를 억지로 끼워 맞추기보다, 같은 최적화 원칙을 AKS 네이티브 방식으로 적용하는 게 현실적입니다. 이건 꽤 중요한데요. EKS 비용, AKS 비용, 오토스케일링 비용 모두 결국은 워크로드 이해도가 좌우합니다.

    혹시 지금 클러스터 비용이 생각보다 많이 나오고 계신가요? 그렇다면 제일 먼저 requests/limits부터 다시 보세요. 그다음 노드 풀 전략, 그리고 마지막으로 자동화 정책을 손보시면 됩니다. 이전 글에서 다룬 Kubernetes 리소스 요청치 튜닝 내용이 있다면 같이 보시면 도움이 되고, 다음 글에서는 HPA(Horizontal Pod Autoscaler)와 Karpenter를 함께 쓸 때 생기는 상호작용도 정리해볼 예정입니다.

    자주 묻는 질문

    Karpenter만 설치하면 바로 비용이 줄어드나요?

    아닙니다. 요청치 설정, 워크로드 분리, 유휴 노드 정리 정책이 같이 맞아야 효과가 납니다.

    스팟만 많이 쓰면 무조건 유리한가요?

    아닙니다. 중단 가능성이 있어서 서비스 특성에 따라 온디맨드와 섞어야 합니다.

    AKS에도 같은 전략을 적용할 수 있나요?

    네, 가능합니다. 다만 도구는 다를 수 있고, 원칙은 비슷하다고 보시면 됩니다.