13년차의 서버실

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

[태그:] EKS 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에도 같은 전략을 적용할 수 있나요?

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