13년차의 서버실

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

[태그:] EKS

  • [k8s] Cluster Autoscaler에서 Karpenter로 마이그레이션 결정 기준 및 전환 전략

    [k8s] Cluster Autoscaler에서 Karpenter로 마이그레이션 결정 기준 및 전환 전략

    [Kubernetes] Cluster Autoscaler에서 Karpenter로 마이그레이션: 언제, 어떻게 전환할까요?

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 Kubernetes(쿠버네티스) 환경에서 노드 오토스케일링(Auto Scaling)을 고민하는 분들을 위한 이야기를 해볼까 합니다. 특히 EKS (Amazon Elastic Kubernetes Service)를 운영하면서 Cluster Autoscaler (CA)의 한계를 느끼고 Karpenter로의 마이그레이션을 고민하는 분들이라면, 제가 직접 겪었던 경험과 삽질 끝에 얻은 노하우가 도움이 될 거예요.

    클라우드 환경에서 Kubernetes 클러스터를 운영하다 보면, 워크로드(Workload)의 변화에 따라 노드를 유연하게 늘리고 줄이는 것이 정말 중요합니다. 비용 최적화는 물론이고, 서비스 안정성에도 직결되거든요. 처음에는 Cluster Autoscaler(클러스터 오토스케일러)가 만능인 줄 알았는데, 막상 써보니 아쉬운 점들이 꽤 있더라고요. 특히 급작스러운 트래픽 증가나 스팟 인스턴스(Spot Instance) 활용 시 Karpenter가 보여주는 퍼포먼스는 저를 깜짝 놀라게 했습니다.

    오늘은 Cluster Autoscaler에서 Karpenter로의 전환을 언제 고려해야 하는지, 그리고 실제 전환은 어떻게 진행해야 하는지에 대한 저만의 결정 기준과 전략을 솔직하게 공유해 드릴게요. 삽질 과정도 가감 없이 보여드릴 테니, 함께 살펴보시죠!

    Karpenter가 도입된 Kubernetes 클러스터의 노드 오토스케일링 아키텍처 개요도

    Cluster Autoscaler와 Karpenter: 핵심 개념 차이

    본격적인 마이그레이션 이야기를 하기 전에, 두 오토스케일링 도구가 어떻게 다른지 간단히 짚고 넘어갈게요. 쉽게 말해 접근 방식 자체가 다릅니다.

    Cluster Autoscaler (CA): 그룹 기반 오토스케일링

    • 작동 방식: Cluster Autoscaler는 AWS Auto Scaling Group (ASG)을 기반으로 작동합니다. 즉, 미리 정의된 ASG의 최소/최대 인스턴스 개수 범위 내에서 노드를 추가하거나 줄이죠.
    • 장점: 비교적 설정이 간단하고, 오랫동안 사용되어 안정성이 검증되었습니다. 기존 EC2 인스턴스 관리 방식에 익숙하다면 접근하기 쉽습니다.
    • 단점:
      • 느린 스케일 아웃(Scale Out) 속도: ASG가 새 인스턴스를 프로비저닝(Provisioning)하는 데 시간이 걸립니다. 워크로드 요청이 급증할 때 노드 부족 현상이 생길 수 있어요.
      • 비효율적인 리소스 활용: 특정 파드(Pod)에 필요한 리소스 타입을 정확히 맞추기 어렵습니다. ASG는 특정 인스턴스 타입이나 몇 가지 인스턴스 타입 묶음으로 구성되니까요. 이 때문에 kube-scheduler (쿠베 스케줄러)가 파드를 노드에 배치하지 못하는 ‘스케줄링 불가(unschedulable)’ 상태가 발생할 수 있습니다.
      • 스팟 인스턴스 활용 제약: 스팟 인스턴스를 활용하더라도, ASG의 인스턴스 풀(Pool)이 제한적이라 유연성이 떨어집니다.

    Karpenter: 파드 기반 오토스케일링

    • 작동 방식: Karpenter는 스케줄링되지 않은 파드를 직접 감지하고, 해당 파드에 가장 적합한 EC2 인스턴스를 직접 프로비저닝합니다. ASG를 거치지 않고 EC2 RunInstances API를 직접 호출해서 노드를 띄우는 거죠.
    • 장점:
      • 압도적인 스케일 아웃 속도: 필요한 노드를 거의 실시간으로 생성하기 때문에 워크로드 변화에 매우 빠르게 대응합니다. 제가 직접 써보니 CA보다 훨씬 빨랐습니다.
      • 최적의 리소스 활용: 파드가 요구하는 CPU, 메모리, GPU 등 리소스에 맞춰 가장 효율적인 EC2 인스턴스 타입을 선택합니다. 스팟 인스턴스 활용도 극대화해서 비용 절감 효과가 커요.
      • 단순한 관리: ASG를 직접 관리할 필요 없이, Karpenter의 Provisioner (프로비저너) 설정 하나로 노드 프로비저닝 정책을 관리할 수 있습니다.
    • 단점:
      • 초기 학습 곡선: 새로운 개념과 설정이 필요해서 처음에는 좀 낯설 수 있습니다.
      • 클라우드 종속성: 현재는 AWS EKS에 특화되어 있습니다 (다른 클라우드 지원도 개발 중이지만, 현재로선 AWS가 주력입니다).

    Karpenter로의 마이그레이션 결정 기준: 언제 전환해야 할까요?

    제가 13년차 인프라 엔지니어로서 여러 환경을 경험해보니, 마이그레이션은 항상 신중해야 하더라고요. 무조건 좋다고 따라가는 것보다, 우리 서비스에 어떤 이점이 있을지 명확히 파악하는 게 중요합니다. 다음 질문들에 해당한다면 Karpenter로의 전환을 적극적으로 고려해볼 때입니다.

    • 잦은 스케일링 이벤트와 느린 노드 확장 속도에 불만이 있다:

      특히 이벤트 기반(Event-driven) 서비스나 예측 불가능한 트래픽 패턴을 가진 서비스라면 CA의 스케일 아웃 속도가 병목이 될 수 있습니다. "파드가 Pending(대기) 상태인데 노드가 왜 이렇게 늦게 뜨지?" 라는 질문을 자주 하셨다면 Karpenter가 답이 될 수 있습니다.

    • 클라우드 비용 최적화가 시급하다:

      CA는 ASG가 정해준 인스턴스 타입 내에서만 노드를 띄우다 보니, 파드의 리소스 요구사항과 정확히 일치하는 인스턴스를 찾기 어렵습니다. Karpenter는 파드에 딱 맞는 최적의 인스턴스를 찾아 스팟 인스턴스까지 적극적으로 활용하기 때문에, 불필요한 리소스 낭비를 줄여줍니다. 저희 홈랩에서도 스팟 인스턴스 활용률이 훨씬 높아지면서 비용이 꽤 절감됐습니다.

    • 노드 타입 관리가 복잡하다고 느낀다:

      CA를 사용하면 다양한 워크로드를 위해 여러 ASG를 만들고 관리해야 할 때가 많습니다. Karpenter는 Provisioner (프로비저너) 하나로 다양한 인스턴스 타입을 유연하게 관리할 수 있어 운영 부담이 줄어듭니다.

    • 특정 리소스(GPU, 고성능 CPU 등) 요구사항이 있는 파드가 많다:

      머신러닝(Machine Learning) 워크로드처럼 특정 GPU나 고성능 CPU를 요구하는 파드들이 있다면, Karpenter가 해당 파드에 맞는 노드를 즉시 프로비저닝해서 스케줄링 효율을 극대화할 수 있습니다.

    Karpenter로의 전환 전략: 실전 구현

    이제 Karpenter로 마이그레이션하는 실제 과정을 단계별로 살펴보겠습니다. 저는 EKS 환경을 기준으로 설명드릴게요.

    단계 1: Karpenter 설치 및 IAM 권한 설정

    Karpenter는 AWS API를 직접 호출해서 EC2 인스턴스를 생성하고 관리해야 하므로, 적절한 IAM(Identity and Access Management) 권한이 필수입니다. 공식 문서에 따라 IAM Role과 Service Account를 생성하고, Karpenter 컨트롤러를 설치합니다. 이 부분은 공식 문서가 워낙 잘 되어 있어서 그대로 따라가시면 됩니다. 핵심은 Karpenter가 EC2, IAM, SSM 등의 리소스에 접근할 수 있는 권한을 부여하는 것입니다.

    설치 후에는 Karpenter Controller Pod가 잘 뜨는지 확인해야 합니다.

    kubectl get pods -n karpenter
    # 예시 출력:
    # NAME                         READY   STATUS    RESTARTS   AGE
    # karpenter-controller-xxxxx   1/1     Running   0          5m
    

    단계 2: Provisioner 설정

    Karpenter의 핵심은 바로 Provisioner 리소스입니다. 어떤 종류의 노드를, 어떤 조건으로 생성할지 여기에 정의합니다. 처음에는 너무 복잡하게 생각하지 마시고, 가장 기본적인 설정으로 시작하는 것을 추천합니다.

    Karpenter Provisioner YAML 설정 예시와 주요 파라미터 설명

    Karpenter Provisioner YAML 설정 예시와 주요 파라미터 설명

    apiVersion: karpenter.sh/v1beta1
    kind: Provisioner
    metadata:
      name: default
    spec:
      # Karpenter가 사용할 AMI Family를 정의합니다. EKS 최신 Optimized AMI를 사용하세요.
      # (예: AL2, AL2023, Bottlerocket, Ubuntu)
      # Karpenter는 여기에 지정된 AMI Family의 최신 EKS Optimized AMI를 자동으로 선택합니다.
      # official EKS AMIs: https://docs.aws.amazon.com/eks/latest/userguide/retrieve-ami-id.html
      amiFamily: AL2 # Amazon Linux 2 (EKS Optimized AMI) 또는 AL2023
    
      # 인스턴스 프로비저닝에 대한 요구사항을 정의합니다.
      # 여기서는 amd64 아키텍처의 온디맨드 또는 스팟 인스턴스를 사용하도록 설정했습니다.
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["on-demand", "spot"] # 스팟 인스턴스 활용을 적극 권장합니다!
        - key: kubernetes.io/arch
          operator: In
          values: ["amd64"]
        - key: karpenter.k8s.aws/instance-category
          operator: In
          values: ["c", "m", "r"] # 컴퓨팅, 메모리, 범용 인스턴스 카테고리
        - key: karpenter.k8s.aws/instance-family
          operator: In
          values: ["c5", "c6i", "m5", "m6i", "r5", "r6i"] # 자주 사용하는 인스턴스 패밀리
        - key: karpenter.k8s.aws/instance-size
          operator: NotIn
          values: ["nano", "micro"] # 너무 작은 인스턴스는 제외
    
      # 노드가 어떤 서브넷(Subnet)에 생성될지 태그(Tag)로 정의합니다.
      # EKS 클러스터가 사용하는 서브넷 태그와 일치해야 합니다.
      subnetSelector:
        karpenter.sh/discovery: "your-eks-cluster-name" # EKS 클러스터 이름으로 교체
    
      # 노드가 어떤 보안 그룹(Security Group)을 사용할지 태그로 정의합니다.
      securityGroupSelector:
        karpenter.sh/discovery: "your-eks-cluster-name" # EKS 클러스터 이름으로 교체
    
      # 노드에 적용될 테인츠(Taints)를 정의합니다.
      # 여기에 테인트가 있으면, 해당 테인트를 허용하는 파드만 스케줄링됩니다.
      # - key: my-app
      #   effect: NoSchedule
    
      # 노드 템플릿입니다. SSH 키페어, IAM 인스턴스 프로파일 등을 지정할 수 있습니다.
      providerRef:
        name: default
    
      # 노드 삭제 정책 (consolidation).
      # 기본적으로 파드가 없거나, 더 효율적인 노드로 대체될 수 있으면 노드를 삭제합니다.
      consolidation:
        enabled: true
        # consolidateAfter: 5m # 노드 생성 후 최소 5분은 유지 (선택 사항)
    
      # 노드 TTL(Time To Live) 설정. 노드가 일정 시간 동안 유휴 상태이거나
      # 최대 수명을 넘으면 삭제됩니다.
      ttlSecondsAfterEmpty: 300 # 노드에 파드가 없으면 300초(5분) 후 삭제
      ttlSecondsUntilExpired: 604800 # 노드는 최대 7일(604800초)까지만 유지 (수명 관리)
    
    ---
    apiVersion: karpenter.k8s.aws/v1beta1
    kind: AWSNodeTemplate
    metadata:
      name: default
    spec:
      # Karpenter가 사용할 IAM 인스턴스 프로파일을 지정합니다.
      # EKS 노드에 필요한 권한이 포함된 프로파일이어야 합니다.
      # 보통 `eksctl create iamserviceaccount` 등으로 생성됩니다.
      instanceProfile: "KarpenterNodeInstanceProfile-your-cluster-name" # 실제 프로파일 이름으로 교체
    
      # SSH 키페어를 지정하여 노드에 SSH 접속이 가능하도록 합니다. (선택 사항)
      # sshName: your-ssh-key-name
    
      # 노드에 추가적인 태그를 붙일 수 있습니다.
      tags:
        environment: production
        managed-by: karpenter
        karpenter.sh/cluster-name: "your-eks-cluster-name" # 클러스터 이름으로 교체
    

    위 YAML을 적용하면 Karpenter가 이제 파드를 감지하고 노드를 프로비저닝할 준비가 됩니다. 여기서 중요한 포인트는 requirements 섹션입니다. 어떤 인스턴스 타입을 사용할지, 스팟(Spot)을 쓸지 온디맨드(On-Demand)를 쓸지 등 Karpenter의 동작 방식을 결정하는 핵심 설정입니다. 저는 스팟 인스턴스를 적극적으로 활용해서 비용을 절감하는 방향으로 설정했습니다.

    단계 3: Cluster Autoscaler (CA) 노드 그룹 점진적 비활성화

    Karpenter가 잘 작동하는지 확인하면서, 기존 CA가 관리하던 노드 그룹(ASG)을 점진적으로 스케일 다운(Scale Down)해야 합니다. 한 번에 모든 노드 그룹을 비활성화하면 서비스 중단 위험이 있으니, 워밍업(Warm-up) 기간을 두는 것이 좋습니다.

    1. 새로운 파드를 배포하거나 기존 파드를 재시작하여 Karpenter가 새 노드를 잘 띄우는지 관찰합니다.
    2. Karpenter가 프로비저닝한 노드가 충분히 확보되었다고 판단되면, CA가 관리하는 ASG의 desired capacity를 0으로 설정합니다.
    3. ASG의 최소/최대 인스턴스 수도 0으로 설정하여 CA가 더 이상 노드를 관리하지 않도록 합니다.
    # EKS 노드 그룹 목록 확인 (eksctl 사용 예시)
    eksctl get nodegroup --cluster your-eks-cluster-name
    
    # 특정 노드 그룹의 desired capacity를 0으로 설정
    # (eksctl 명령은 ASG도 함께 조정합니다)
    eksctl scale nodegroup --cluster your-eks-cluster-name --name your-nodegroup-name --nodes 0 --nodes-min 0 --nodes-max 0
    
    # 또는 AWS CLI로 ASG 직접 수정 (ASG 이름 확인 필요)
    # aws autoscaling update-auto-scaling-group --auto-scaling-group-name your-asg-name --desired-capacity 0 --min-size 0 --max-size 0
    

    이렇게 하면 CA가 관리하던 노드들은 서서히 드레인(Drain)되고 종료되며, Karpenter가 그 역할을 완전히 넘겨받게 됩니다. 이 과정에서 파드들이 새로운 Karpenter 노드로 잘 재스케줄링되는지 꼼꼼히 확인해야 합니다.

    주의사항 및 트러블슈팅: 삽질 경험

    제가 Karpenter를 도입하면서 겪었던 몇 가지 삽질 경험과 주의사항을 공유합니다. 이걸 미리 아셨다면 여러분은 저보다 훨씬 적게 고생하실 거예요. 😅

    • ⚠️ IAM 권한은 정말 중요합니다!

      Karpenter가 EC2 인스턴스를 직접 다루기 때문에 IAM 권한이 조금만 잘못되어도 노드가 뜨지 않습니다. 특히 ec2:RunInstances, ec2:TerminateInstances, iam:PassRole 등의 권한이 제대로 부여되었는지 반드시 확인하세요. 저는 iam:PassRole을 빼먹어서 한참 헤맸습니다. Karpenter Controller 로그를 확인하면 어떤 권한이 부족한지 명확하게 나옵니다.

      # Karpenter Controller 로그 확인
      kubectl logs -f -n karpenter $(kubectl get pods -n karpenter -l app.kubernetes.io/name=karpenter -o name)
      
    • Pod Disruption Budget (PDB) 고려:

      Karpenter는 노드 통합(Consolidation) 기능을 통해 불필요한 노드를 효율적으로 제거합니다. 이때 PDB (Pod Disruption Budget)가 설정된 파드들은 Karpenter의 노드 종료 작업을 방해할 수 있습니다. PDB가 너무 엄격하면 Karpenter가 노드를 줄이지 못하고 비용이 낭비될 수 있으니, 워크로드의 특성에 맞춰 PDB를 적절히 설정해야 합니다.

    • Initial Scale-up Delay (초기 스케일업 지연) 착시:

      Karpenter는 ASG를 사용하지 않고 바로 EC2 인스턴스를 띄우기 때문에, 처음 노드가 올라올 때까지의 시간이 CA보다 오히려 길게 느껴질 수 있습니다. 하지만 이는 인스턴스 부팅 및 EKS 클러스터 조인(Join) 과정이 포함되기 때문이고, 실제 “스케줄링되지 않은 파드”를 “노드에 할당”하는 과정은 훨씬 빠릅니다. 이 차이를 이해하고 인내심을 가져야 합니다. 🙂

    • Spot Instance Interruptions (스팟 인스턴스 중단) 대비:

      Karpenter는 스팟 인스턴스를 적극 활용하여 비용을 절감하지만, 스팟 인스턴스는 언제든지 AWS에 의해 중단될 수 있습니다. 중요한 스테이트풀(Stateful) 워크로드에는 스팟 인스턴스를 사용하지 않거나, 중단에 강한 아키텍처(예: 분산 시스템, 재시작 가능한 작업)를 구성하는 것이 중요합니다.

    검증 및 결과 확인

    Karpenter 마이그레이션이 성공적으로 이루어졌는지 확인하는 방법은 다음과 같습니다.

    1. 노드 상태 확인: kubectl get nodes 명령으로 노드가 잘 뜨고 준비(Ready) 상태인지 확인합니다. Karpenter가 띄운 노드에는 특정 레이블(Label)이나 태그가 붙어있으니 쉽게 구분할 수 있습니다.
      kubectl get nodes -l karpenter.sh/provisioner-name=default
      # 예시 출력:
      # NAME                                           STATUS   ROLES    AGE   VERSION
      # ip-10-0-x-x.ap-northeast-2.compute.internal    Ready    <none>   5m    v1.28.x
      
    2. 파드 스케줄링 확인: 새로운 파드를 배포하거나, 기존 파드의 리소스 요청을 늘려서 Karpenter가 노드를 빠르게 프로비저닝하고 파드를 스케줄링하는지 확인합니다.
    3. Karpenter Provisioner 상태 확인:
      kubectl describe provisioner default
      # 이 명령을 통해 Karpenter가 어떤 노드를 프로비저닝하고 있는지,
      # 그리고 어떤 이벤트가 발생했는지 상세히 볼 수 있습니다.
      
    4. AWS EC2 콘솔 확인: EC2 콘솔에서 인스턴스가 Karpenter에 의해 생성되고 종료되는 것을 직접 관찰합니다. 태그를 통해 Karpenter가 관리하는 인스턴스인지 쉽게 알 수 있습니다.
    5. 비용 모니터링: AWS Cost Explorer를 통해 마이그레이션 전후의 비용 변화를 모니터링합니다. 특히 스팟 인스턴스 활용률이 높아지면서 비용이 얼마나 절감되었는지 확인하는 것이 중요합니다.
    Karpenter 도입 후 EKS 클러스터의 노드 및 파드 스케일링 성능 대시보드

    Karpenter 도입 후 EKS 클러스터의 노드 및 파드 스케일링 성능 대시보드

    Cluster Autoscaler vs. Karpenter: 비교 및 선택 기준

    제가 경험한 바를 바탕으로 두 오토스케일러의 핵심 차이점을 표로 정리해봤습니다. 어떤 상황에서 어떤 도구가 더 유리한지 판단하는 데 도움이 될 거예요.

    구분 Cluster Autoscaler (CA) Karpenter
    작동 방식 Auto Scaling Group (ASG) 기반 스케줄링되지 않은 파드 직접 감지, EC2 API 호출
    노드 프로비저닝 속도 상대적으로 느림 (ASG의 인스턴스 생성 시간) 매우 빠름 (파드 요구사항에 맞춰 즉시 생성)
    리소스 효율성 ASG 인스턴스 타입 내에서 선택, 비효율 가능성 파드 요구사항에 가장 적합한 인스턴스 선택, 높은 효율성
    비용 최적화 ASG 설정에 의존, 스팟 활용 제한적 스팟 인스턴스 적극 활용, 비용 절감 효과 큼
    관리 복잡성 여러 ASG 관리 필요 Provisioner 리소스 하나로 관리, 단순함
    주요 사용 사례 워크로드 변동이 적고 안정적인 환경, 온디맨드 위주 잦은 스케일링, 비용 최적화, 스팟 인스턴스 적극 활용 환경
    트러블슈팅 난이도 ASG 문제, CA 로그 Karpenter Controller 로그, IAM 권한, Provisioner 설정
    Kubernetes Cluster Autoscaler와 Karpenter의 주요 특징 및 마이그레이션 결정 기준 비교

    Kubernetes Cluster Autoscaler와 Karpenter의 주요 특징 및 마이그레이션 결정 기준 비교

    마무리하며: 더 나은 클러스터 운영을 위한 선택

    Cluster Autoscaler에서 Karpenter로의 마이그레이션은 단순히 도구를 바꾸는 것을 넘어, Kubernetes 클러스터의 리소스 관리 효율성과 비용 최적화를 한 단계 끌어올리는 중요한 결정입니다. 제가 직접 경험해보니, 특히 워크로드 변동성이 크고 스팟 인스턴스 활용을 통해 비용을 절감하고자 하는 환경이라면 Karpenter가 단연코 훌륭한 선택지였습니다.

    물론 새로운 도구를 도입하는 데는 초기 설정과 학습 비용이 따릅니다. 하지만 Karpenter가 제공하는 유연성과 효율성을 고려하면 충분히 투자할 가치가 있다고 생각합니다. 이 글이 여러분의 Karpenter 마이그레이션 여정에 작은 등불이 되었기를 바랍니다. 혹시 더 궁금한 점이나 제가 겪지 못했던 다른 삽질 경험이 있다면 댓글로 공유해주세요. 함께 배우고 성장하는 것이 인프라 엔지니어의 숙명이 아니겠습니까! 다음번에는 Karpenter의 고급 기능이나 다른 클라우드 환경에서의 활용 방안에 대해서도 이야기해볼 수 있으면 좋겠네요. 🎉

  • [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 오토스케일링 문제 해결: 노드 프로비저닝 실패 디버깅 가이드

    [Kubernetes] Karpenter 문제 해결: 노드 프로비저닝 실패 디버깅 가이드

    Karpenter 문제 해결 때문에 밤에 이벤트 로그 붙잡고 계신 적, 한 번쯤 있으시죠? 저도 처음엔 “오토스케일링은 붙여놓으면 알아서 되겠지” 했다가, 정작 트래픽이 몰리는 순간 Pod(파드, 쿠버네티스의 최소 배포 단위)는 Pending(펜딩, 스케줄 대기)인데 노드는 안 뜨고, Karpenter 로그는 뭔가 말은 많은데 핵심은 안 보이는 상황을 여러 번 겪었습니다. 특히 Kubernetes 노드 프로비저닝이 실패하면 서비스 지연이 바로 드러나기 때문에, 이 구간은 감으로 보면 안 되더라고요.

    이번 글은 AWS 환경에서 Karpenter를 운영할 때 자주 만나는 오토스케일링 실패 패턴을 기준으로 정리했습니다. 제가 홈랩과 실무 환경에서 직접 부딪히며 정리한 흐름이라, 문서만 읽었을 때보다 훨씬 빠르게 원인을 좁히는 데 도움이 되실 겁니다. 핵심은 하나입니다. Karpenter 디버깅은 “Pending Pod → 스케줄링 조건 → Karpenter 로그 → 클라우드 리소스” 순서로 봐야 한다는 점입니다.

    Karpenter 문제 해결을 위한 오토스케일링 아키텍처 개요 이미지

    Pending Pod, Karpenter controller, AWS 인프라 리소스가 어떻게 연결되는지 보여주는 전체 흐름도입니다.

    Karpenter 문제 해결, 왜 이렇게 헷갈릴까요?

    쉽게 말해 Karpenter는 “Pod가 요구하는 조건에 맞춰 새 노드를 빠르게 계산하고 띄워주는 컨트롤러”입니다. 그런데 실제로는 중간에 확인할 레이어가 꽤 많습니다. Kubernetes scheduler(스케줄러), Karpenter controller(컨트롤러), IAM(권한 체계), subnet/security group(서브넷/보안 그룹), instance type(인스턴스 타입) 조건, 그리고 quota(할당량)까지 다 연결돼 있거든요.

    저도 처음엔 Karpenter 로그만 뒤졌습니다. 근데 실제로 써보니까 문제의 출발점은 의외로 Pod 스펙인 경우가 많더라고요. 예를 들어 CPU 요청량이 과하게 잡혀 있거나, 특정 아키텍처만 허용했거나, taint/toleration(테인트/톨러레이션) 조합이 안 맞으면 Karpenter가 아무리 열심히 계산해도 답이 없습니다.

    • Pod 조건 문제: requests/limits, nodeSelector, affinity, toleration 충돌
    • Karpenter 설정 문제: NodePool(노드풀) 또는 기존 Provisioner(프로비저너) 제약이 너무 빡빡함
    • AWS 리소스 문제: IAM 누락, 태그 미설정, 서브넷 발견 실패
    • 용량 문제: 가용 영역 용량 부족, 인스턴스 타입 제한 과다, 계정 할당량 부족

    여기서 중요한 포인트! Karpenter 문제 해결은 “왜 노드가 안 떴지?”가 아니라 “왜 이 Pod를 만족하는 노드를 계산하지 못했지?”로 질문을 바꾸면 훨씬 빨라집니다.

    Karpenter 디버깅의 기본 원리

    제가 실제로 보는 순서는 거의 고정입니다. 삽질 좀 했습니다 ㅎㅎ 결국 순서를 정해두는 게 제일 낫더라고요.

    1. Pending 상태의 Pod를 확인합니다.
    2. 해당 Pod의 이벤트와 스케줄링 실패 메시지를 읽습니다.
    3. Karpenter controller 로그에서 같은 시점의 에러를 확인합니다.
    4. NodePool/EC2NodeClass 또는 기존 Provisioner/AWSNodeTemplate 조건을 검토합니다.
    5. AWS 측 리소스 발견, 권한, 용량 문제를 점검합니다.

    이 흐름을 안 지키면 로그는 잔뜩 보는데 정작 원인을 놓치기 쉽습니다. 특히 이벤트 메시지 한 줄이 전체 방향을 정해주는 경우가 많아요.

    Karpenter 디버깅 1단계: Pending Pod부터 확인하기

    가장 먼저 해야 할 일은 “안 뜨는 노드”가 아니라 “대기 중인 Pod”를 보는 겁니다. 아래 명령어부터 시작해 보세요.

    kubectl get pods -A --field-selector=status.phase=Pending
    kubectl describe pod -n <namespace> <pod-name>
    kubectl get events -A --sort-by=.lastTimestamp

    여기서 제가 제일 먼저 보는 건 다음 세 가지입니다.

    • Insufficient cpu/memory: 리소스 요청량이 현재/예상 노드와 맞지 않는 경우
    • node affinity mismatch: nodeSelector 또는 affinity 조건이 과도한 경우
    • untolerated taint: 노드 taint를 Pod가 허용하지 않는 경우

    예를 들어 이런 Pod 스펙은 흔히 문제를 만듭니다.

    apiVersion: v1
    kind: Pod
    metadata:
      name: inflate
    spec:
      nodeSelector:
        kubernetes.io/arch: amd64
      containers:
        - name: pause
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
          resources:
            requests:
              cpu: "2"
              memory: "2Gi"

    이 자체는 틀린 YAML은 아닙니다. 다만 NodePool이 arm64만 허용하거나, 너무 작은 인스턴스 타입만 열어놨다면 Karpenter는 새 노드를 못 만듭니다. 결국 Kubernetes 노드 프로비저닝 실패처럼 보이지만, 시작점은 Pod 조건이죠.

    Karpenter 디버깅 2단계: 컨트롤러 로그에서 에러 패턴 찾기

    그다음은 Karpenter 로그입니다. 저는 보통 아래처럼 확인합니다.

    kubectl logs -n karpenter deploy/karpenter --since=30m
    kubectl logs -n karpenter deploy/karpenter --since=30m | grep -i "error"
    kubectl logs -n karpenter deploy/karpenter --since=30m | grep -i "provision"

    로그에서 자주 보는 키워드는 대략 이렇습니다.

    로그/증상 의미 우선 확인할 것
    no instance type met the scheduling requirements 조건에 맞는 인스턴스 타입이 없음 NodePool requirements, Pod requests
    insufficient capacity 가용 영역 또는 타입의 실시간 용량 부족 타입 범위 확대, AZ 분산
    access denied / unauthorized IAM 권한 문제 controller role, instance profile
    subnet/security group not found AWS 리소스 발견 실패 태그, selector 조건

    처음엔 이게 뭔가 싶었는데, 로그를 “읽는” 게 아니라 “분류”하기 시작하니까 속도가 확 달라지더라고요. 특히 access denied 계열은 쿠버네티스 문제가 아니라 거의 AWS 권한 문제였습니다.

    Karpenter 디버깅 과정에서 컨트롤러 로그와 NodePool을 점검하는 이미지

    Karpenter 컨트롤러 로그, NodePool 조건, EC2 관련 리소스를 함께 점검하는 디버깅 흐름 예시입니다.

    실전 구현: NodePool/Provisioner 조건 점검하기

    운영 중인 Karpenter 버전에 따라 리소스 이름이 다를 수 있습니다. 최근 계열은 NodePool/EC2NodeClass 조합을 많이 쓰고, 예전 문서에서는 Provisioner/AWSNodeTemplate 예제가 남아 있습니다. 그래서 제 습관은 “내 클러스터에 실제로 어떤 CRD가 있는지 먼저 보는 것”입니다.

    kubectl api-resources | grep -i karpenter
    kubectl get nodepools
    kubectl get ec2nodeclasses
    kubectl get provisioners

    예를 들어 NodePool이 너무 제한적으로 잡혀 있으면 문제가 생깁니다.

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: default
    spec:
      template:
        spec:
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
            - key: kubernetes.io/os
              operator: In
              values: ["linux"]

    여기서 흔한 실수는 이런 겁니다.

    • 인스턴스 패밀리 조건을 너무 좁게 설정
    • 특정 가용 영역만 허용
    • spot(스팟)만 허용했는데 해당 시점에 용량이 부족
    • daemonset(데몬셋) 오버헤드를 고려하지 않고 너무 작은 타입만 허용

    제가 직접 해보니 조건을 처음부터 정교하게 만드는 것보다, 일단 넉넉하게 열고 정상 프로비저닝을 확인한 뒤 점진적으로 좁히는 방식이 훨씬 덜 고생합니다.

    권장 점검 명령어

    kubectl describe nodepool <nodepool-name>
    kubectl get ec2nodeclass <class-name> -o yaml
    kubectl get provisioner <name> -o yaml

    설정에서 확인할 항목은 다음과 같습니다.

    1. 아키텍처 제약이 Pod와 맞는지
    2. 용량 타입(on-demand/spot) 제약이 과하지 않은지
    3. 서브넷/보안 그룹 선택 조건이 실제 AWS 태그와 일치하는지
    4. 노드 만료, 통합(consolidation) 정책이 과도하게 공격적이지 않은지

    ⚠️ 오토스케일링 실패를 만드는 대표 원인 5가지

    이 섹션은 제가 실제로 많이 봤던 순서대로 적겠습니다. Karpenter 디버깅에서 제일 시간을 많이 잡아먹는 부분이기도 합니다.

    1. IAM(권한 체계) 누락

    컨트롤러가 EC2 인스턴스를 생성하거나 조회할 권한이 없으면 노드가 당연히 안 뜹니다. 로그에 access denied, unauthorized 류 메시지가 보이면 거의 이쪽입니다. EKS에서 IRSA(IAM Roles for Service Accounts, 서비스어카운트용 IAM 역할)를 쓰는 경우, role annotation과 정책 연결 상태를 다시 확인해 보세요.

    2. Subnet/Security Group 태그 문제

    Karpenter는 보통 태그 기반으로 서브넷과 보안 그룹을 찾습니다. 근데 태그가 빠져 있거나 selector 조건과 안 맞으면 리소스를 발견하지 못합니다. 저는 이걸 네트워크 문제로 착각해서 한참 헤맸었는데, 사실은 태그 오타 하나였던 적도 있습니다.

    3. 인스턴스 타입 제한 과다

    예를 들어 특정 세대, 특정 패밀리, 특정 크기만 허용해 놓으면 실시간 수용 용량이 없을 때 바로 막힙니다. 운영에서는 후보군을 넓혀야 합니다. 특히 spot만 쓰는 환경은 더 그렇습니다.

    4. Pod 요청량 과다 또는 DaemonSet 오버헤드 미반영

    실제로 써보니까 작은 워크로드 하나만 보지 말고, CNI(컨테이너 네트워크 인터페이스), 로그 에이전트, 모니터링 에이전트 같은 데몬셋 자원까지 같이 봐야 하더라고요. 남는 것 같아도 실제 스케줄 가능 용량은 더 작습니다.

    5. AWS 계정 할당량 또는 AZ 용량 이슈

    설정이 멀쩡해도 특정 가용 영역에서 타입 수급이 안 될 수 있습니다. 이럴 땐 Karpenter 자체 버그보다 클라우드 용량 이슈일 가능성도 큽니다. 그래서 저는 가능한 한 여러 AZ와 여러 타입을 열어둡니다.

    kubectl get events -A --sort-by=.lastTimestamp
    kubectl describe pod -n <namespace> <pod-name>
    kubectl describe nodepool <nodepool-name>

    설정 예시: 디버깅용으로 조건을 단순화해보기

    문제가 길어질 때는 조건을 줄여서 정상 동작 여부부터 확인하는 게 좋습니다. 아래처럼 Pod를 단순화해서 테스트해 보세요.

    apiVersion: v1
    kind: Pod
    metadata:
      name: karpenter-debug
    spec:
      containers:
        - name: pause
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"

    이 Pod조차 스케줄이 안 되면, 애플리케이션 문제가 아니라 Karpenter 또는 인프라 레이어 문제로 보는 게 맞습니다. 반대로 이건 뜨는데 실제 서비스 Pod만 안 뜬다면, 그때는 requests/affinity/toleration을 더 자세히 보시면 됩니다.

    Kubernetes 노드 프로비저닝 점검을 위한 YAML 설정과 kubectl 디버깅 이미지

    디버깅용 Pod YAML, kubectl describe 출력, NodePool 설정을 함께 비교하는 장면을 표현한 이미지입니다.

    검증 방법: 노드가 정말 의도대로 프로비저닝됐는지 확인

    노드가 하나 떴다고 끝이 아닙니다. 저는 아래 순서로 꼭 확인합니다.

    1. 새 노드가 생성됐는지 확인
    2. Pending Pod가 Running으로 전환됐는지 확인
    3. 노드 라벨, taint, allocatable 자원이 기대와 맞는지 확인
    4. 불필요하게 큰 인스턴스가 뜨지 않았는지 확인
    kubectl get nodes
    kubectl get pods -A -o wide
    kubectl describe node <node-name>
    kubectl top nodes

    확인 포인트는 단순합니다.

    • Pod가 실제로 새 노드에 배치됐는가
    • 노드 라벨이 workload 조건과 맞는가
    • CPU/메모리 여유가 너무 적거나 너무 많지 않은가
    • 오토스케일링 실패가 반복되지 않는가

    드디어 됐다! 싶은 순간이 여기인데요, 여기서 끝내면 안 됩니다. 몇 분 후 consolidation이나 만료 정책 때문에 노드가 정리되면서 다시 Pending이 생길 수도 있거든요. 그래서 최소한 짧은 부하 테스트나 재배포 한 번은 꼭 해보시는 걸 권합니다.

    새 노드 생성 이후 Pending Pod가 Running으로 바뀌고 자원 사용량이 안정화된 결과 화면 예시입니다.

    운영 팁: 제가 실제로 적용하는 Karpenter 문제 해결 체크리스트

    혹시 이런 경험 있으신가요? 로그는 많은데 뭘 먼저 봐야 할지 몰라서 계속 왔다 갔다 하게 되는 상황이요. 그래서 저는 아래 체크리스트를 만들어 두고 그대로 봅니다.

    체크 항목 질문 판단 기준
    Pod 이벤트 왜 Pending인가? 스케줄링 실패 메시지가 명확해야 함
    Karpenter 로그 프로비저닝 계산을 했는가? instance type, subnet, 권한 관련 힌트 확인
    설정 제약 NodePool/Provisioner가 너무 좁은가? AZ, 타입, 아키텍처, 용량 타입 검토
    AWS 리소스 태그와 권한이 맞는가? subnet/security group 발견 가능해야 함
    검증 한 번 뜬 뒤 안정적으로 유지되는가? 재배포/부하 시 반복 확인

    이 체크리스트만 잘 지켜도 Karpenter 문제 해결 시간이 꽤 줄어듭니다. 그리고 이전 글에서 다룬 클러스터 리소스 요청량 설계 내용과 같이 보시면 더 이해가 쉬우실 겁니다. 다음 글에서는 Karpenter와 Cluster Autoscaler를 어떤 기준으로 나눠 볼지 정리해보려고 합니다.

    Karpenter 문제 해결 체크리스트를 요약한 인포그래픽 이미지

    Karpenter 문제 해결 순서를 한 장으로 요약한 체크리스트 인포그래픽입니다.

    정리: Karpenter 문제 해결은 순서가 전부입니다

    오늘 내용의 핵심은 복잡하지 않습니다. 오토스케일링 실패가 보이면 바로 AWS 콘솔부터 열지 말고, 먼저 Pending Pod 이벤트를 확인하세요. 그다음 Karpenter 로그, 그리고 NodePool/Provisioner 제약, 마지막으로 IAM과 네트워크 태그를 보는 흐름이 가장 효율적이었습니다.

    저도 처음엔 Karpenter가 너무 똑똑해서 알아서 다 해줄 줄 알았는데, 실제로는 “조건을 어떻게 열고, 어떤 로그를 먼저 읽느냐”가 훨씬 중요하더라고요. 특히 Kubernetes 노드 프로비저닝 이슈는 한 군데만 봐서는 잘 안 풀립니다. 대신 순서를 지키면 생각보다 금방 풀립니다.

    혹시 지금도 Karpenter 디버깅 중이시라면, 오늘 소개한 명령어 세트부터 그대로 실행해 보세요. 그리고 Pod 이벤트 한 줄, Karpenter 로그 한 줄만 정확히 읽어도 방향이 확 잡힐 겁니다. 이거 진짜 편하더라고요.

  • [Kubernetes] Karpenter vs Cluster Autoscaler 비교 분석

    [Kubernetes] Karpenter vs Cluster Autoscaler 비교 분석

    [Kubernetes] Karpenter vs Cluster Autoscaler 비교 분석

    Karpenter vs Cluster Autoscaler 이야기는 Kubernetes(쿠버네티스) 운영하시는 분들이 한 번쯤 꼭 부딪히는 주제입니다. 파드(Pod)가 늘어나는데 노드(Node)가 제때 안 붙으면 서비스가 밀리고, 반대로 너무 빨리 늘어나면 비용이 튀거든요. 저도 처음엔 HPA(Horizontal Pod Autoscaler, 파드 수평 확장)만 잘 걸어두면 끝인 줄 알았는데, 실제로 운영해보니 워크로드 확장과 노드 확장은 완전히 다른 문제더라고요. 특히 EKS 같은 클라우드 환경에서 직접 써보니까 Karpenter와 Cluster Autoscaler는 비슷해 보여도 철학이 꽤 다릅니다.

    이번 글에서는 Karpenter vs Cluster Autoscaler를 실무 관점에서 비교해보겠습니다. 단순히 기능 목록만 나열하는 게 아니라, 어떤 워크로드에서 무엇이 더 잘 맞는지, 설치할 때 어디서 삽질하는지, 검증은 어떻게 해야 하는지까지 정리해보겠습니다. 혹시 지금 Kubernetes 오토스케일링 구성을 만지시는 중이라면 꽤 바로 도움이 되실 겁니다.

    Cluster Autoscaler와 Karpenter가 각각 어떤 방식으로 노드를 늘리고 줄이는지 한눈에 보여주는 개요 다이어그램이 들어갈 자리입니다.

    Kubernetes 오토스케일링이 왜 생각보다 어렵냐면요

    쉽게 말해, Kubernetes 오토스케일링은 두 층으로 나뉩니다. 하나는 파드 수를 늘리는 것이고, 다른 하나는 그 파드를 올릴 노드를 확보하는 것입니다. HPA가 앞단이라면, Karpenter나 Cluster Autoscaler는 뒷단이라고 보시면 됩니다.

    • HPA: CPU, 메모리, 커스텀 메트릭 기준으로 파드 수를 늘리고 줄립니다.
    • Cluster Autoscaler: 기존 노드 그룹(Node Group, 노드 묶음)의 크기를 늘리거나 줄입니다.
    • Karpenter: 스케줄링되지 못한 파드를 보고, 필요한 조건에 맞는 노드를 더 직접적으로 프로비저닝(Provisioning, 자원 생성)합니다.

    여기서 중요한 포인트가 하나 있습니다. Cluster Autoscaler는 미리 정의된 노드 그룹 중심으로 생각하고, Karpenter는 파드 요구사항 중심으로 생각합니다. 이 차이가 운영 난이도, 비용 최적화, 확장 속도에 꽤 큰 영향을 줍니다.

    Karpenter vs Cluster Autoscaler: 핵심 개념 차이

    처음 문서를 보면 둘 다 자동으로 노드를 늘려주는 도구라서 별 차이 없어 보이는데요, 실제로 써보면 운영 감각이 다릅니다. 제가 직접 비교하면서 느낀 건 아래 표 하나로 많이 정리되더라고요.

    항목 Cluster Autoscaler Karpenter
    기본 동작 방식 기존 노드 그룹 크기 조절 파드 요구사항에 맞춰 노드 직접 생성
    확장 기준 스케줄 불가 파드를 보고 적절한 노드 그룹 선택 스케줄 불가 파드의 리소스 조건을 바로 계산
    노드 유연성 노드 그룹 설계에 크게 의존 인스턴스 타입 선택 폭이 넓음
    운영 모델 ASG/Managed Node Group 중심 Provisioner/NodePool 성격의 선언적 관리
    장점 구조가 익숙하고 보수적 운영에 적합 유연성, 속도, 비용 최적화 측면에서 강점
    주의점 노드 그룹이 많아지면 복잡도 증가 초기 설계와 제약조건 관리가 중요

    정리하면 이렇습니다. Cluster Autoscaler는 전통적인 방식입니다. 이미 만들어 둔 노드 그룹이 있고, 그 그룹의 min/max 범위 안에서 증감시키죠. 반면 Karpenter는 필요한 자원을 그때그때 맞춰 뽑아내는 쪽에 가깝습니다. 그래서 워크로드 패턴이 자주 바뀌거나, 다양한 인스턴스 타입을 섞어 쓰고 싶을 때 체감이 꽤 납니다.

    언제 Karpenter가 유리하고, 언제 Cluster Autoscaler가 편한가

    이건 솔직히 정답이 하나는 아닙니다. 운영팀 성향, 클러스터 규모, 클라우드 의존도에 따라 달라지거든요. 저도 처음엔 무조건 신형이 좋아 보였는데, 몇 번 굴려보니 케이스가 나뉘더라고요.

    Cluster Autoscaler가 잘 맞는 경우

    • 이미 Managed Node Group 기반 운영 체계가 안정적으로 잡혀 있는 경우
    • 보안, 승인, 변경 관리 때문에 노드 타입을 엄격히 통제해야 하는 경우
    • 여러 팀이 공용으로 쓰는 클러스터라서 예측 가능한 증설 패턴이 중요한 경우
    • 기존 운영팀이 노드 그룹 기반 모델에 익숙한 경우

    Karpenter가 잘 맞는 경우

    • 워크로드 확장 패턴이 들쭉날쭉해서 빠른 대응이 필요한 경우
    • 다양한 인스턴스 타입 조합으로 비용 최적화를 하고 싶은 경우
    • 스케줄링 요구사항이 세밀해서 노드 그룹을 많이 쪼개기 싫은 경우
    • 빈번한 배치 작업, CI 러너, 이벤트성 트래픽처럼 탄력성이 중요한 경우

    제 경험상 노드 그룹이 많아질수록 Cluster Autoscaler는 운영 피로도가 올라갑니다. 반대로 Karpenter는 초반 설계만 잘못 잡으면 예상치 못한 노드 생성 패턴 때문에 당황하게 되더라고요. 그러니까 둘 중 뭐가 무조건 더 좋다기보다, 운영 모델에 맞는 선택이 더 중요합니다.

    실전 구현 1: Cluster Autoscaler 구성 흐름

    먼저 Cluster Autoscaler부터 보겠습니다. 예시는 EKS 환경 기준으로 적겠습니다. 다른 클라우드에서도 개념은 비슷합니다. 핵심은 오토스케일 대상 노드 그룹이 먼저 존재해야 한다는 점입니다.

    1. 오토스케일 가능한 노드 그룹을 준비합니다.
    2. 노드 그룹의 최소/최대 크기를 정합니다.
    3. Cluster Autoscaler를 클러스터에 배포합니다.
    4. 스케줄 불가 파드가 생기면 해당 노드 그룹 크기를 늘립니다.
    helm repo add autoscaler https://kubernetes.github.io/autoscaler
    helm repo update
    
    helm upgrade --install cluster-autoscaler autoscaler/cluster-autoscaler \
      --namespace kube-system \
      --set autoDiscovery.clusterName=my-cluster \
      --set awsRegion=ap-northeast-2 \
      --set rbac.serviceAccount.create=true

    설치 자체는 그렇게 복잡하지 않습니다. 근데 실제로는 IAM(Role, 권한 역할), 오토디스커버리 태그, 노드 그룹 min/max 값 같은 주변 설정에서 삽질을 많이 하게 됩니다. 특히 노드 그룹 태그가 안 맞으면 파드가 Pending(펜딩, 스케줄 대기) 상태로 남아 있는데도 노드가 안 늘어납니다. 저도 처음엔 로그만 한참 봤네요.

    kubectl -n kube-system logs deploy/cluster-autoscaler
    kubectl get pods -A
    kubectl describe pod <pending-pod-name>

    여기서 보는 포인트는 간단합니다. 파드가 왜 스케줄링되지 않았는지, 그리고 Cluster Autoscaler가 어떤 노드 그룹을 확장 후보로 판단했는지입니다.

    실전 구현 2: Karpenter 구성 흐름

    Karpenter는 접근 방식이 다릅니다. 미리 노드 그룹을 세세하게 많이 만들기보다, 어떤 종류의 노드를 허용할지 정책으로 정의하는 느낌에 가깝습니다. 그래서 처음엔 구조가 낯선 감이 있더라고요. 저도 처음엔 이게 뭔가 싶었는데, 한번 감을 잡고 나니 꽤 편하더라고요.

    1. Karpenter 컨트롤러를 설치합니다.
    2. 클라우드 권한과 네트워크 조건을 연결합니다.
    3. 어떤 노드를 만들 수 있는지 정책을 정의합니다.
    4. 스케줄 불가 파드가 생기면 그 요구사항을 기반으로 노드를 생성합니다.
    helm repo add karpenter https://charts.karpenter.sh
    helm repo update
    
    helm upgrade --install karpenter karpenter/karpenter \
      --namespace karpenter \
      --create-namespace
    apiVersion: karpenter.sh/v1alpha5
    kind: Provisioner
    metadata:
      name: default
    spec:
      requirements:
        - key: kubernetes.io/arch
          operator: In
          values:
            - amd64
        - key: kubernetes.io/os
          operator: In
          values:
            - linux
      limits:
        resources:
          cpu: "1000"
      ttlSecondsAfterEmpty: 30

    요즘 Karpenter 관련 리소스 모델은 환경과 시점에 따라 구성이 조금씩 달라질 수 있어서, 실제 배포 전에는 현재 사용하는 배포 가이드와 CRD(Custom Resource Definition, 사용자 정의 리소스 정의)를 반드시 맞춰보셔야 합니다. 여기서는 비교 분석이 핵심이라, 파드 요구사항 기반으로 노드를 뽑아낸다는 운영 개념에 집중해서 보시면 됩니다.

    Karpenter 정책 기반 노드 생성 흐름을 보여주는 Kubernetes 오토스케일링 이미지

    Karpenter가 파드의 요구 리소스를 읽고 적절한 노드를 동적으로 생성하는 흐름을 설명하는 구성 이미지 자리입니다.

    워크로드 확장 테스트: 직접 확인하는 방법

    설치보다 중요한 게 검증입니다. 오토스케일링은 붙어 있다고 끝이 아니거든요. 원하는 타이밍에, 원하는 방식으로, 과하지 않게 확장되는지를 봐야 합니다. 저는 보통 부하 테스트용 디플로이먼트(Deployment)를 하나 띄워두고 확인합니다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: inflate
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: inflate
      template:
        metadata:
          labels:
            app: inflate
        spec:
          containers:
            - name: inflate
              image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
              resources:
                requests:
                  cpu: "1"
                  memory: "1Gi"
                limits:
                  cpu: "1"
                  memory: "1Gi"
    kubectl apply -f inflate.yaml
    kubectl scale deployment inflate --replicas=20
    kubectl get pods -w
    kubectl get nodes -w

    이 테스트에서 봐야 할 건 세 가지입니다.

    • 확장 속도: Pending 파드가 얼마나 빨리 해소되는지
    • 적합성: 필요한 크기의 노드가 제대로 붙는지
    • 정리 속도: 부하가 끝난 뒤 빈 노드가 얼마나 깔끔하게 제거되는지

    실제로 써보니까 Karpenter는 워크로드 확장에 좀 더 민감하게 반응하는 느낌이 있었고, Cluster Autoscaler는 노드 그룹 구조를 잘 짜놨을 때 안정감이 있었습니다. 드디어 됐다! 싶은 순간이 오긴 오는데, 그 전에 로그와 이벤트를 많이 보게 됩니다 ㅎㅎ

    워크로드 확장 과정에서 Pending 파드가 노드 증설로 해결되는 Kubernetes 오토스케일링 이미지

    파드가 Pending 상태가 되었다가 새 노드가 생성되고 Running으로 전환되는 과정을 단계별로 보여주는 이미지 자리입니다.

    ⚠️ 제가 실제로 자주 겪었던 트러블슈팅 포인트

    이 섹션이 제일 중요할 수도 있습니다. 문서대로만 보면 금방 될 것 같지만, 현실은 그렇지 않더라고요.

    1. 파드는 Pending인데 노드가 안 늘어나는 경우

    • 리소스 요청값(requests)이 비현실적으로 큰지 확인합니다.
    • 노드 선택 조건(nodeSelector, affinity, taint/toleration)이 너무 빡빡한지 봅니다.
    • Cluster Autoscaler라면 대상 노드 그룹 태그와 범위를 확인합니다.
    • Karpenter라면 허용된 인스턴스 조건과 서브넷/보안 그룹 연결을 확인합니다.

    처음엔 저도 컨트롤러 문제인 줄 알았는데, 알고 보니 파드 스펙 자체가 너무 까다로운 경우가 꽤 있었습니다. 특히 GPU나 특정 아키텍처를 요구하는 워크로드는 더 그렇습니다.

    2. 노드는 늘어나는데 생각보다 비효율적인 경우

    이건 비용 문제로 바로 이어집니다. Cluster Autoscaler는 노드 그룹 단위로 움직이기 때문에, 작은 파드 몇 개 때문에 상대적으로 큰 노드가 붙는 상황이 생기곤 합니다. Karpenter도 제약 조건을 널널하게 열어두면 예상보다 다양한 노드가 만들어지곤 해요. 그래서 요청 리소스(requests) 튜닝이 정말 중요합니다.

    3. 축소(scale down)가 너무 늦거나 안 되는 경우

    • PodDisruptionBudget(PDB, 파드 중단 예산) 때문에 축소가 막힐 수 있습니다.
    • DaemonSet(데몬셋)과 로컬 스토리지 사용 여부도 확인해야 합니다.
    • 노드가 비어 보여도 eviction(축출) 조건 때문에 남아 있을 수 있습니다.

    이 부분은 진짜 많이 놓칩니다. 저도 한동안 왜 비용이 안 줄지 봤더니, 특정 시스템 파드가 노드 정리를 막고 있더라고요.

    검증과 결과: 무엇을 기준으로 비교해야 하나

    Karpenter vs Cluster Autoscaler를 비교할 때 단순히 “누가 더 빠르냐”만 보면 아쉽습니다. 운영에서는 아래 기준으로 보시는 걸 추천드립니다.

    검증 항목 확인 포인트
    확장 지연 시간 Pending 파드 발생 후 Running까지 걸리는 체감 시간
    리소스 적합도 워크로드 요구사항에 맞는 노드가 붙는지 여부
    운영 복잡도 노드 그룹/정책 관리 난이도, 변경 영향 범위
    비용 효율성 불필요한 큰 노드가 자주 붙는지, 축소가 잘 되는지
    장애 대응성 예외 상황에서 로그와 원인 파악이 쉬운지

    제가 여러 번 테스트하면서 느낀 결론은 이렇습니다.

    • 보수적이고 예측 가능한 운영이 중요하면 Cluster Autoscaler가 편합니다.
    • 유연한 워크로드 확장과 세밀한 자원 최적화가 중요하면 Karpenter가 매력적입니다.
    • 둘 다 잘 쓰려면 결국 파드 스펙, 요청 리소스, 스케줄링 제약조건을 먼저 정리해야 합니다.
    kubectl top pods -A
    kubectl top nodes
    kubectl get events --sort-by=.lastTimestamp
    kubectl describe node <node-name>

    이 명령어들만 잘 봐도 현재 Kubernetes 오토스케일링이 어디에서 막히는지 감이 꽤 옵니다. 대시보드만 보지 마시고 이벤트(event)와 describe 출력도 꼭 같이 보세요. 진짜 차이가 여기서 보이거든요.

    Karpenter vs Cluster Autoscaler 검증 결과를 보여주는 Kubernetes 대시보드 이미지

    노드 수 변화, 파드 Pending 해소, 리소스 사용률 등을 한눈에 보여주는 결과 검증용 대시보드 이미지 자리입니다.

    실무 선택 가이드: 그래서 무엇을 고르면 되냐

    질문을 많이 받는 부분이라 아주 현실적으로 정리해보겠습니다.

    1. 기존에 노드 그룹 체계가 잘 잡혀 있고 변경 리스크를 줄이고 싶다면 Cluster Autoscaler부터 가는 게 안전합니다.
    2. 새 클러스터를 설계하거나, 워크로드 종류가 다양하고 변동폭이 크다면 Karpenter를 적극 검토할 만합니다.
    3. 어떤 도구를 쓰든 HPA, requests/limits, affinity 정책을 먼저 정리하지 않으면 효과가 반감됩니다.
    4. 운영팀이 디버깅하기 쉬운 구조인지도 꼭 보셔야 합니다. 기술적으로 좋아 보여도 팀이 못 다루면 오래 못 갑니다.

    여기서 중요한 포인트! Karpenter vs Cluster Autoscaler의 승부는 기능표가 아니라 운영 방식에서 갈립니다. 홈랩에서 실험할 때도 그렇고 실제 서비스에서도 그렇고, 결국 오래 버티는 건 팀이 이해하고 통제할 수 있는 구조였습니다.

    정리와 FAQ

    정리하자면, Cluster Autoscaler는 익숙하고 보수적인 선택지입니다. Karpenter는 더 유연하고 워크로드 중심적인 선택지이고요. 어느 쪽이든 워크로드 확장의 핵심은 파드와 노드의 관계를 제대로 이해하는 데 있습니다. 저도 처음엔 단순히 “자동으로 늘려준다” 정도로 생각했었는데, 실제로 써보니까 오토스케일링은 설계의 문제더라고요.

    다음 글에서는 HPA와 VPA(Vertical Pod Autoscaler, 수직 오토스케일러)를 함께 붙였을 때 어떤 점을 조심해야 하는지 다뤄볼 예정입니다. 이전 글에서 다뤘던 Ingress(인그레스, 외부 트래픽 진입점)와 서비스 분리 구조를 같이 보시면 더 이해가 잘 되실 겁니다.

    자주 묻는 질문

    • Q. 둘을 동시에 써도 되나요?
      A. 환경과 목적에 따라 가능은 하지만, 노드 프로비저닝 책임이 겹치지 않도록 설계를 분명히 나누는 게 중요합니다.
    • Q. Kubernetes 오토스케일링에서 가장 먼저 봐야 할 건 뭔가요?
      A. 파드 requests/limits와 스케줄링 제약조건입니다. 여기서 꼬이면 어떤 오토스케일러를 써도 답답합니다.
    • Q. 초보 운영자에게는 뭐가 더 쉬운가요?
      A. 이미 노드 그룹 개념이 익숙하다면 Cluster Autoscaler가 더 이해하기 쉬운 편입니다. 다만 장기적으로는 Karpenter의 유연성이 매력적일 수 있습니다.
    Karpenter vs Cluster Autoscaler 핵심 차이를 요약한 비교 인포그래픽

    두 오토스케일러의 장단점, 추천 시나리오, 선택 기준을 한 장으로 정리한 마무리용 인포그래픽 자리입니다.

    ✅ 결론만 짧게 말하면 이렇습니다. 안정성과 익숙함이면 Cluster Autoscaler, 유연성과 최적화면 Karpenter입니다. 다만 둘 다 만능은 아니고, 결국 좋은 오토스케일링은 좋은 워크로드 설계에서 시작합니다. 이거 진짜 중요합니다.

  • [k8s] EKS 운영 비용 최적화 전략: 클라우드 지출 효율 높이기

    [k8s] EKS 운영 비용 최적화 전략: 클라우드 지출 효율 높이기

    [클라우드] EKS 비용 최적화 전략: 클라우드 지출 효율 높이기

    EKS 비용 최적화는 AWS를 오래 운영할수록 더 민감해지는 주제입니다. 처음엔 서비스만 잘 뜨면 된다고 생각했는데, 어느 순간 청구서를 보면 CPU는 놀고 있고 노드는 과하게 떠 있고, 로그 저장 비용까지 슬금슬금 올라가더라고요. 저도 홈랩과 실제 운영 환경을 오가면서 비슷한 패턴을 여러 번 봤습니다. 특히 AWS EKS 운영을 하다 보면 워크로드는 늘지 않았는데 비용만 올라가는 구간이 꼭 옵니다. 그 시점부터는 성능 튜닝보다 먼저 해야 할 일이 비용 구조를 보는 일입니다.

    이번 글에서는 제가 직접 현장에서 자주 적용했던 방법들을 공유하려고 합니다. Kubernetes(쿠버네티스) 클러스터에서 어디서 돈이 새는지, 어떤 순서로 손봐야 하는지 정리해볼 거예요. 무작정 인스턴스를 내리는 이야기가 아니라, 서비스 안정성을 최대한 해치지 않으면서 Kubernetes 비용 절감과 클라우드 비용 관리를 같이 가져가는 방법에 가깝습니다.

    EKS 비용 최적화 구조를 보여주는 아키텍처 다이어그램

    EKS 비용 구조를 노드, 스토리지, 네트워크, 관측 영역으로 나눠 보여주는 개요 이미지입니다.

    1. 왜 EKS 비용은 생각보다 빨리 커질까요?

    쉽게 말해 EKS는 “컨테이너만 쓰는 서비스”가 아닙니다. 실제 청구는 여러 층에서 발생하거든요. 컨트롤 플레인(Control Plane, 클러스터 제어 영역), EC2 노드(Node, 워커 서버), EBS(Elastic Block Store, 블록 스토리지), 데이터 전송, 로드밸런서, 로그 수집까지 다 합쳐져서 나옵니다. 그래서 Pod(파드) 몇 개 줄였다고 체감이 바로 안 오는 경우도 많습니다.

    • 노드 과할당: 요청 리소스(request)가 실제 사용량보다 과하게 잡혀서 빈 서버를 계속 유지합니다.
    • 상시 고정 용량: 피크 시간 기준으로 노드를 잡아두고 하루 종일 유지합니다.
    • 분산 실패: 비슷한 워크로드가 여러 노드에 흩어져서 스케일 인(scale-in)이 안 됩니다.
    • 스토리지/로그 방치: 안 쓰는 볼륨, 긴 로그 보관 기간이 누적됩니다.
    • 네트워크 비용 누락: NAT Gateway, AZ 간 트래픽, Load Balancer 비용이 생각보다 큽니다.

    여기서 중요한 포인트! EKS 비용 최적화는 “할인 상품 먼저 사기”보다 현재 사용 패턴을 정상화하는 게 우선입니다. 저도 처음엔 Savings Plans(세이빙 플랜)부터 볼까 했었는데, 실제로는 잘못된 리소스 요청값부터 정리하는 게 훨씬 효과가 컸습니다.

    2. EKS 비용을 볼 때 꼭 나눠야 하는 4가지 축

    제가 비용 분석할 때는 항상 네 가지로 쪼갭니다. 이렇게 나누면 어디를 먼저 줄여야 할지가 보입니다.

    영역 무엇을 보나 자주 나오는 문제 우선 대응
    Compute EC2 노드, Fargate 사용량 낮은 사용률, 과한 노드 수 오토스케일링, Bin Packing
    Storage EBS, 스냅샷, 로그 저장 미사용 볼륨, 긴 보관 기간 수명주기 관리, 정리 자동화
    Network Load Balancer, NAT, 트래픽 불필요한 외부 통신 경로 엔드포인트, 경로 단순화
    Observability 로그, 메트릭, 트레이싱 수집 과다, 샘플링 없음 필드 축소, 보관 기간 재설계

    이 표를 기준으로 보면, 같은 “클라우드 비용 관리”라도 접근 방식이 완전히 달라집니다. 노드 문제를 로그 보관 기간으로 해결할 수는 없으니까요.

    3. 실전 1단계: 먼저 현재 사용량을 수치로 확인합니다

    비용 최적화는 감으로 하면 거의 실패합니다. 저도 예전에 “이 노드가 제일 비싸 보이네” 하고 줄였다가 배치 작업이 밀린 적이 있었습니다. 삽질 좀 했습니다 ㅎㅎ 그래서 지금은 반드시 사용량과 요청값을 같이 봅니다.

    1. namespace(네임스페이스)별로 어떤 팀/서비스가 많이 쓰는지 확인합니다.
    2. CPU/메모리 실제 사용량과 requests/limits 차이를 봅니다.
    3. 노드별 유휴율과 스케일 인 가능 여부를 확인합니다.
    4. Spot(스팟) 전환 가능한 워크로드를 분류합니다.
    kubectl top nodes
    kubectl top pods -A --sort-by=cpu
    kubectl top pods -A --sort-by=memory
    kubectl get pods -A -o wide
    kubectl describe node <node-name>

    여기에 metrics-server(메트릭 서버)나 Prometheus(프로메테우스, 모니터링 시스템)가 붙어 있으면 더 좋습니다. 핵심은 “실사용량 대비 요청값이 얼마나 부풀어 있는가”입니다. 실제로 써보니까 메모리 request를 넉넉하게 잡아둔 서비스들이 클러스터 비용을 조용히 밀어올리는 경우가 정말 많더라고요.

    4. 실전 2단계: Node Group과 Autoscaling 구조부터 손봅니다

    AWS EKS 운영에서 비용이 가장 크게 흔들리는 영역은 대체로 노드입니다. 그래서 저는 여기부터 들어갑니다. 대표적으로 Managed Node Group(관리형 노드 그룹)과 Karpenter(카펜터, 워크로드 기반 노드 프로비저닝 도구), Cluster Autoscaler(클러스터 오토스케일러)를 조합해 구조를 정리합니다.

    운영 팁을 하나 말씀드리면, 모든 워크로드를 한 종류 노드에 태우는 건 나중에 꼭 비용 문제로 돌아옵니다. On-Demand(온디맨드)와 Spot을 분리하고, 시스템 워크로드와 일반 애플리케이션 워크로드를 구분해두는 게 좋습니다.

    EKS 비용 최적화를 위한 온디맨드와 스팟 노드 분리 구성 이미지

    시스템 워크로드는 안정적인 온디맨드 노드에, 일반 서비스는 스팟 노드에도 분산하는 구성 예시입니다.

    추천 접근 방식

    • 기반 시스템용 소규모 온디맨드 노드 그룹 유지
    • 일반 웹/API 워크로드는 오토스케일링 대상 그룹으로 분리
    • 중단 허용 가능한 배치/잡(Job)은 스팟 전용으로 이동
    • Pod Disruption Budget(파드 중단 예산)과 anti-affinity를 같이 점검
    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: sample-api
      namespace: production
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: sample-api
      minReplicas: 2
      maxReplicas: 10
      metrics:
        - type: Resource
          resource:
            name: cpu
            target:
              type: Utilization
              averageUtilization: 70

    HPA(Horizontal Pod Autoscaler, 수평 파드 오토스케일러)를 붙일 때도 request 값이 엉망이면 오토스케일 기준이 제대로 안 먹히더라고요. 그래서 request/right-sizing(라이트사이징, 적정 용량 조정)을 먼저 하고 HPA를 적용하는 순서가 훨씬 안정적입니다.

    5. 실전 3단계: requests/limits를 줄이는 게 진짜 핵심입니다

    많은 분들이 EKS 비용 최적화라고 하면 인스턴스 타입부터 바꾸는데, 제가 직접 해보니 제일 먼저 손대야 할 건 Deployment(디플로이먼트) 리소스 설정이었습니다. 특히 초기값을 넉넉하게 잡아놓고 그대로 운영하는 팀이 많거든요.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sample-api
    spec:
      replicas: 3
      template:
        spec:
          containers:
            - name: app
              image: example/sample-api:stable
              resources:
                requests:
                  cpu: "250m"
                  memory: "512Mi"
                limits:
                  cpu: "500m"
                  memory: "1Gi"

    여기서 중요한 건 숫자 자체보다 실측 기반으로 줄였는가입니다. CPU는 낮은데 메모리만 치솟는 애플리케이션도 있고, 반대도 있습니다. VPA(Vertical Pod Autoscaler, 수직 파드 오토스케일러)를 참고용으로 쓰거나, 일정 기간 메트릭을 보고 수동 조정해도 충분히 효과를 볼 수 있습니다.

    제가 보통 보는 체크포인트는 이렇습니다.

    • 평균 사용량이 requests의 절반 이하로 오래 유지되는가
    • OOMKilled(메모리 부족 종료) 이력이 있는가
    • 배치 시간대와 일반 시간대 사용 패턴이 다른가
    • limits 때문에 CPU throttling(스로틀링, CPU 제한)이 심한가

    이 단계만 잘해도 Kubernetes 비용 절감 효과가 꽤 크게 납니다. 왜냐하면 스케줄러가 더 촘촘하게 파드를 배치할 수 있어서, 결과적으로 노드 수가 줄어들 가능성이 커지거든요.

    6. 실전 4단계: 로그, 스토리지, 네트워크도 같이 줄여야 합니다

    노드만 줄이고 끝내면 반쪽짜리입니다. CloudWatch Logs(클라우드워치 로그), EBS, Load Balancer, NAT 비용도 무시 못 하거든요. 처음엔 이게 뭔가 싶었는데, 실제 청구를 뜯어보면 로그 저장 비용이 꽤 눈에 띄는 환경도 있습니다.

    aws logs describe-log-groups
    aws ec2 describe-volumes --filters Name=status,Values=available
    aws elbv2 describe-load-balancers
    • 로그: 디버그 로그 상시 수집 금지, 보관 기간을 서비스 성격에 맞게 분리
    • 스토리지: 미사용 EBS와 오래된 스냅샷 주기 점검
    • 네트워크: 내부 통신은 가능하면 Internal Load Balancer와 VPC Endpoint 활용
    • 이미지: 컨테이너 이미지 크기를 줄여 배포 시간과 캐시 비효율 감소
    EKS 비용 최적화에 포함되는 로그 보관과 EBS 정리 운영 다이어그램

    로그 보관 기간 조정, 미사용 볼륨 점검, 네트워크 경로 단순화 흐름을 설명하는 이미지입니다.

    특히 NAT Gateway 비용은 조용히 커집니다. 외부 API 호출이 많은 워크로드나 잘못된 라우팅 구조가 있으면 생각보다 빨리 누적되더라고요. 혹시 청구서에서 네트워크 비용이 이상하게 높다면, 애플리케이션보다 먼저 통신 경로를 의심해보셔도 좋습니다.

    7. ⚠️ 제가 실제로 자주 본 문제와 트러블슈팅

    비용 줄이다가 장애 내면 말짱 도루묵이죠. 그래서 아래 항목은 꼭 같이 보셔야 합니다.

    1) 스팟 노드만 늘렸더니 서비스가 불안정해진 경우

    해결은 단순합니다. 상태 저장성(Stateful) 워크로드나 핵심 시스템 컴포넌트는 온디맨드에 남기고, 스팟은 중단 허용 가능한 서비스 위주로 태워야 합니다.

    2) request를 너무 낮췄더니 HPA가 과민반응하는 경우

    CPU 기준 HPA는 request 영향을 받습니다. request를 급격히 낮추면 스케일 아웃이 너무 빨라질 수 있습니다. 그래서 한 번에 크게 줄이지 말고, 며칠 단위로 관찰하면서 조정하는 게 안전합니다.

    3) Bin Packing이 안 돼서 노드가 안 줄어드는 경우

    Topology Spread Constraints(토폴로지 분산 제약), anti-affinity, DaemonSet(데몬셋) 자원 점유 때문에 흔히 생깁니다. 저도 처음엔 오토스케일러가 이상한 줄 알았는데, 실제로는 배치 정책 때문에 노드가 비워지지 않더라고요.

    4) 로그를 줄였더니 장애 분석이 어려워진 경우

    모든 로그를 다 버리면 안 됩니다. 애플리케이션 로그는 줄이더라도 감사 로그, 에러 로그, 핵심 접근 로그는 남겨야 합니다. 비용과 가시성(observability, 관측 가능성) 사이에서 선을 잘 잡아야 합니다.

    8. 검증 방법: 비용이 정말 줄었는지 어떻게 확인할까?

    최적화는 적용보다 검증이 더 중요합니다. 저는 보통 2주에서 4주 단위로 비교합니다. 하루 이틀 데이터만 보면 배치, 이벤트 트래픽, 배포 타이밍 때문에 판단이 왜곡되거든요.

    1. 변경 전/후 노드 수와 평균 사용률을 비교합니다.
    2. namespace별 requests 합계를 기록합니다.
    3. CloudWatch 또는 Prometheus에서 CPU/메모리 추세를 봅니다.
    4. AWS Cost Explorer에서 서비스별 비용 추세를 확인합니다.
    5. 장애, 재시작, 응답 지연이 늘지 않았는지 같이 체크합니다.
    EKS 비용 최적화 전후를 비교하는 대시보드 이미지

    변경 전후 비용 추이, 노드 수, CPU/메모리 사용률을 함께 보여주는 검증용 대시보드 이미지입니다.

    검증할 때는 이렇게 보시면 됩니다.

    • 비용은 줄었는데 재시작이 늘었다면 과최적화일 수 있습니다.
    • 노드 수는 같아도 여유 자원이 늘었다면 다음 단계 최적화 여지가 생긴 겁니다.
    • 로그 비용만 줄었다면 Compute 최적화는 아직 덜 된 상태일 수 있습니다.

    드디어 됐다! 싶은 순간은 보통 “같은 트래픽인데 노드 수가 줄고, 장애 지표는 그대로일 때”입니다. 이게 가장 건강한 절감입니다.

    9. 정리: EKS 비용 최적화는 할인보다 구조가 먼저입니다

    오늘 내용을 한 줄로 정리하면 이겁니다. EKS 비용 최적화는 인스턴스 가격표를 보는 작업이 아니라, 워크로드 배치 구조와 리소스 설정을 바로잡는 작업입니다. 저도 처음엔 할인 모델만 찾았었는데, 실제로 써보니까 request 조정, 오토스케일링 구조 정리, 로그/스토리지 수명주기 관리가 훨씬 먼저더라고요.

    • 먼저 실제 사용량을 측정합니다.
    • 그다음 requests/limits를 다듬습니다.
    • 오토스케일링과 스팟 전략을 분리 적용합니다.
    • 스토리지, 로그, 네트워크 비용까지 같이 봅니다.
    • 마지막으로 Cost Explorer와 모니터링으로 검증합니다.
    EKS 비용 최적화 실행 우선순위를 요약한 인포그래픽

    사용량 측정부터 검증까지의 실행 순서를 요약한 체크리스트형 인포그래픽입니다.

    혹시 지금 EKS 청구서를 보고 “분명 서비스는 안 늘었는데 왜 이러지?” 싶으셨다면, 오늘 소개한 순서대로 한 번만 점검해보세요. 차이가 확실할 겁니다. 다음 글에서는 Karpenter와 Cluster Autoscaler를 어떤 기준으로 나눠 쓰는지, 그리고 스팟 운영 시 장애 반경을 어떻게 줄이는지 이어서 다뤄볼 예정입니다. 이전 글의 VPC 설계 내용과도 연결해서 보시면 더 이해가 쉬우실 거예요. 🎉

  • [Kubernetes] Karpenter 오토스케일러 1년 사용 후기: 비용 절감과 성능 최적화 회고

    [Kubernetes] Karpenter 오토스케일러 1년 사용 후기: 비용 절감과 성능 최적화 회고

    [Kubernetes] Karpenter 오토스케일러 1년 사용 후기

    Karpenter 오토스케일러 후기를 한 줄로 먼저 말씀드리면, EKS 운영에서 노드 증설 속도와 비용 통제가 동시에 좋아졌습니다. 저도 처음엔 “Cluster Autoscaler(클러스터 오토스케일러)로도 되는데 굳이?” 싶었거든요. 그런데 서비스가 늘고, 배치 작업이 섞이고, 팀별로 워크로드 특성이 달라지니까 기존 방식이 점점 답답해지더라고요. 특히 Kubernetes 오토스케일링을 노드 그룹 중심으로만 운영하면 빈 자원이 남아도는 순간이 꽤 자주 생깁니다. 실무에서 결국 비용은 숫자로 돌아오니까요.

    제가 지난 1년 동안 홈랩과 업무 환경에서 비슷한 패턴으로 실험하고 운영해보니, Karpenter는 “노드를 미리 크게 잡아두는 습관”을 줄여주는 쪽에 강점이 있었습니다. 다만 마법은 아닙니다. 설정을 대충 하면 오히려 인스턴스 종류가 너무 넓게 열리거나, 반대로 너무 좁아서 스케일이 안 붙는 일도 생깁니다. 오늘 글에서는 제가 실제로 겪었던 삽질까지 포함해서, 왜 도입했고 어떻게 운영했고 무엇을 조심해야 하는지 정리해보겠습니다.

    Karpenter 오토스케일러 후기용 EKS 아키텍처 개요 이미지

    Amazon EKS 환경에서 Karpenter가 대기 중인 Pod를 보고 적절한 EC2 노드를 프로비저닝하는 전체 흐름을 보여주는 아키텍처 이미지입니다.

    Karpenter란 무엇인가: Kubernetes 오토스케일링을 더 세밀하게

    쉽게 말해 Karpenter는 Pod(파드)의 요구사항을 보고 바로 노드를 만들어주는 노드 프로비저너(node provisioner, 노드 생성기)입니다. 기존 Cluster Autoscaler는 미리 만들어둔 Auto Scaling Group(오토 스케일링 그룹)이나 Managed Node Group(관리형 노드 그룹)을 기준으로 증설을 판단합니다. 반면 Karpenter는 “지금 이 파드가 CPU, 메모리, 아키텍처, 스팟 여부를 이렇게 요구하네? 그럼 여기에 맞는 노드를 하나 올리자”에 더 가깝습니다.

    처음엔 이게 뭔가 싶었는데, 실제로 써보니까 포인트가 명확하더라고요. 노드 그룹을 여러 개 미리 쪼개서 관리하던 부담이 줄어듭니다. 그리고 워크로드에 맞는 인스턴스 타입을 더 유연하게 고를 수 있어서 AWS EKS 비용 절감 관점에서 꽤 체감이 됐습니다.

    항목 Cluster Autoscaler Karpenter
    기준 노드 그룹 중심 파드 요구사항 중심
    유연성 사전 정의된 그룹 범위 내 인스턴스 선택 폭이 넓음
    비용 최적화 그룹 설계에 크게 의존 빈 자원 축소에 유리
    운영 포인트 노드 그룹 관리 요구사항과 제약 조건 설계

    왜 도입했는가: 노드 프로비저닝 병목이 보이기 시작했습니다

    제가 Karpenter를 본격적으로 보기 시작한 이유는 세 가지였습니다.

    • 배치와 실시간 서비스가 같은 클러스터에 섞이면서 노드 성격이 달라졌습니다.
    • Managed Node Group을 늘릴수록 운영 포인트가 많아졌습니다.
    • 스팟(Spot)과 온디맨드(On-Demand)를 상황에 따라 유연하게 섞고 싶었습니다.

    예전에는 팀별로 노드 그룹을 하나씩 나누면 깔끔해 보였거든요. 근데 시간이 지나면 태그, 라벨, 테인트(Taint, 특정 워크로드 제한), AMI, 서브넷, 보안 그룹까지 관리 포인트가 계속 늘어납니다. 결국 “지금 이 파드가 왜 여기 못 올라가지?”를 찾는 시간이 길어지더라고요. Karpenter는 그 문제를 완전히 없애주진 않지만, 적어도 노드 프로비저닝을 워크로드 관점으로 다시 정리하게 만들어줍니다.

    실전 구현: EKS에 Karpenter 붙이면서 제가 잡았던 기준

    여기서 중요한 포인트! Karpenter를 처음 붙일 때는 욕심내서 모든 워크로드를 한 번에 옮기지 않는 게 좋습니다. 저도 처음엔 한 번에 바꾸려다가 삽질 좀 했습니다 ㅎㅎ 작은 NodePool부터 시작해서 점진적으로 옮기는 게 훨씬 안전했습니다.

    1. 기본 EKS 클러스터와 워커 노드를 준비합니다.
    2. Karpenter용 IAM 권한과 인터럽션 처리 큐를 구성합니다.
    3. Karpenter 컨트롤러를 설치합니다.
    4. NodeClass와 NodePool을 최소 범위로 선언합니다.
    5. 특정 워크로드만 먼저 태워서 동작을 검증합니다.

    1. 컨트롤러 설치 전 확인할 것

    • 클러스터의 OIDC provider가 연결되어 있는지
    • 서브넷과 보안 그룹에 Karpenter가 찾을 수 있는 태그가 있는지
    • 기존 CNI, CoreDNS, metrics-server 같은 필수 애드온이 정상인지

    특히 태그는 정말 중요합니다. 이거 하나 빠지면 로그만 보고 한참 헤맵니다.

    2. 설치 예시

    export CLUSTER_NAME=my-eks
    export AWS_REGION=ap-northeast-2
    export KARPENTER_NAMESPACE=karpenter
    
    helm repo add eks https://aws.github.io/eks-charts
    helm repo update
    
    helm upgrade --install karpenter eks/karpenter \
      --namespace ${KARPENTER_NAMESPACE} \
      --create-namespace \
      --set settings.clusterName=${CLUSTER_NAME} \
      --set settings.interruptionQueue=${CLUSTER_NAME} \
      --set serviceAccount.create=true

    환경마다 IAM Role for Service Account(IRSA, 서비스어카운트용 IAM 역할) 구성 방식은 다를 수 있습니다. 그래서 설치 커맨드 자체보다도, 컨트롤러가 EC2 인스턴스 생성과 조회 권한을 제대로 갖고 있는지를 먼저 보는 게 중요합니다.

    3. NodeClass / NodePool 예시

    아래 예시는 운영에서 자주 쓰는 방향을 단순화한 것입니다. 핵심은 “너무 넓지도, 너무 좁지도 않게” 시작하는 겁니다.

    apiVersion: karpenter.k8s.aws/v1beta1
    kind: EC2NodeClass
    metadata:
      name: default
    spec:
      amiFamily: AL2
      subnetSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks
      securityGroupSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks
      role: KarpenterNodeRole-my-eks
    ---
    apiVersion: karpenter.sh/v1beta1
    kind: NodePool
    metadata:
      name: general
    spec:
      template:
        metadata:
          labels:
            workload: general
        spec:
          nodeClassRef:
            name: default
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
            - key: karpenter.sh/capacity-type
              operator: In
              values: ["spot", "on-demand"]
            - key: node.kubernetes.io/instance-type
              operator: In
              values: ["m5.large", "m5.xlarge", "m6i.large", "m6i.xlarge"]
      disruption:
        consolidationPolicy: WhenUnderutilized
      limits:
        cpu: "100"

    제가 직접 해보니 처음부터 인스턴스 타입을 수십 개 열어두는 것보다, 검증된 계열 몇 개로 시작하는 편이 좋았습니다. 나중에 확장하는 건 쉽지만, 처음부터 범위를 넓히면 왜 그 타입이 선택됐는지 추적이 어려워지거든요.

    Karpenter 오토스케일러 후기의 NodePool 구성 이미지

    EC2NodeClass와 NodePool이 연결되어 서브넷, 보안 그룹, 인스턴스 타입, capacity type을 결정하는 구조를 보여주는 구성 이미지입니다.

    4. 테스트용 워크로드로 검증

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: inflate
    spec:
      replicas: 6
      selector:
        matchLabels:
          app: inflate
      template:
        metadata:
          labels:
            app: inflate
        spec:
          nodeSelector:
            workload: general
          containers:
            - name: inflate
              image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
              resources:
                requests:
                  cpu: "500m"
                  memory: "512Mi"

    이렇게 요청 리소스(requests, 예약 자원)를 명시한 테스트 파드를 올려보면 스케줄링이 안 되는 순간 Karpenter가 새 노드를 붙이는 흐름을 직관적으로 볼 수 있습니다. 드디어 됐다! 하는 순간이 여기서 나옵니다.

    운영하면서 체감한 장점: 비용 절감과 성능 최적화 포인트

    Karpenter 오토스케일러 후기에서 가장 많이 물어보는 게 “그래서 돈이 진짜 줄었나요?”인데요, 제 경우엔 AWS EKS 비용 절감 효과를 숫자 하나로 단정하긴 어려웠습니다. 워크로드 패턴이 계속 바뀌니까요. 다만 분명했던 변화는 있었습니다.

    • 대기 중인 파드 처리 속도가 좋아졌습니다. 노드 그룹 단위보다 반응이 직관적이었습니다.
    • 빈 자원이 남는 노드가 줄었습니다. 특히 야간 배치 종료 후 차이가 컸습니다.
    • 스팟과 온디맨드 혼합 전략을 잡기 쉬웠습니다.
    • 팀별 노드 그룹 난립을 줄이면서 운영 복잡도가 낮아졌습니다.

    성능 최적화라는 게 꼭 CPU 사용률 그래프만 예쁘게 만드는 건 아니더라고요. 스케줄링 지연이 줄고, 워크로드 특성에 맞는 노드가 더 빨리 생기고, 과하게 큰 노드를 덜 쓰게 되는 것 자체가 운영 품질 향상입니다. 실제로 써보니까 이 부분이 더 크게 다가왔습니다.

    ⚠️ 주의사항과 트러블슈팅: 여기서 많이 막혔습니다

    이 섹션은 좀 중요합니다. Karpenter는 잘 되면 정말 편한데, 안 될 때는 로그와 이벤트를 꼼꼼히 봐야 하거든요.

    1. 서브넷/보안 그룹 태그 누락

    가장 흔했습니다. 컨트롤러는 떠 있는데 노드가 안 생깁니다. 원인은 대개 discovery 태그 누락이었습니다.

    kubectl logs -n karpenter deploy/karpenter
    kubectl describe nodepool general
    kubectl get events -A --sort-by=.lastTimestamp

    해결: 서브넷과 보안 그룹 선택 조건에 맞는 태그가 있는지 다시 확인했습니다. 이건 문서보다 실제 AWS 리소스 태그 화면이 더 빠르더라고요.

    2. 인스턴스 요구 조건을 너무 빡빡하게 잡음

    저도 처음엔 특정 세대, 특정 사이즈만 고집했습니다. 그러면 순간적으로 가용한 인스턴스가 없을 때 스케일이 멈춥니다. 특히 스팟만 강제하면 더 민감해집니다.

    • 인스턴스 패밀리를 1개만 두지 말 것
    • 사이즈를 한 단계 이상 열어둘 것
    • 스팟만 고정하지 말고 온디맨드 fallback을 고려할 것

    3. DaemonSet(데몬셋) 자원 계산을 과소평가

    이거 진짜 많이 놓칩니다. 노드 하나가 올라오면 CNI, 로그 에이전트, 모니터링 에이전트 같은 DaemonSet이 먼저 자리를 먹거든요. 파드 request를 딱 맞춰 잡아두면 생각보다 노드가 더 필요해집니다.

    해결: 시스템 DaemonSet이 차지하는 CPU/메모리를 감안해서 워크로드 request를 다시 계산했습니다. 제가 직접 해보니 이 조정만으로도 불필요한 추가 스케일링이 꽤 줄었습니다.

    4. Consolidation(통합 축소) 설정이 너무 공격적일 때

    유휴 노드를 잘 정리해주는 기능은 좋지만, 워크로드 패턴이 들쭉날쭉하면 노드가 자주 교체될 수 있습니다. 배치가 짧게 반복되는 환경에서는 오히려 흔들릴 수 있겠더라고요.

    해결: 처음엔 보수적으로 운영하고, 패턴이 보인 뒤에 consolidation 정책을 조정했습니다. 이건 정답이 하나가 아니라 워크로드 성격을 타는 부분입니다.

    검증과 결과 확인: 무엇을 보면 잘 되고 있다고 판단할까

    Karpenter 오토스케일러 후기를 쓰면서 가장 조심한 부분이 여기입니다. 비용 수치나 처리량 수치를 함부로 적으면 안 되거든요. 그래서 저는 운영에서 다음 지표를 기준으로 판단했습니다.

    1. Pending Pod가 줄어드는 속도
    2. 피크 시간 이후 유휴 노드가 정리되는지
    3. 스팟 중단 이후 재스케줄링이 안정적인지
    4. 특정 노드 그룹에 과도하게 몰리던 패턴이 줄었는지
    kubectl get nodes
    kubectl get pods -A -o wide
    kubectl top nodes
    kubectl top pods -A
    kubectl describe pod <pending-pod-name>

    제가 실제로 써보니까, 가장 눈에 띄는 건 노드 수 자체보다도 대기 중인 파드가 오래 머무르지 않는 것이었습니다. 클러스터가 바빠질 때 운영자가 덜 초조해집니다. 이건 체감이 꽤 큽니다.

    AWS EKS 비용 절감과 Karpenter 오토스케일링 결과 이미지

    파드 증가 시 노드가 빠르게 늘고, 부하가 줄면 유휴 노드가 정리되는 모습을 대시보드 형태로 보여주는 결과 이미지입니다.

    정리 표: 어떤 환경에 특히 잘 맞았나

    환경 Karpenter 적합도 이유
    배치와 웹 서비스 혼합 높음 워크로드별 노드 요구사항 차이가 큼
    스팟 적극 활용 높음 capacity type 전략을 유연하게 설계 가능
    소규모 고정 트래픽 보통 정적 노드 그룹만으로도 충분할 수 있음
    규제가 강한 고정 인스턴스 정책 보통 이하 유연성 장점이 줄어듦

    자주 묻는 질문: 도입 전에 많이 받았던 질문

    Q1. Cluster Autoscaler 대신 무조건 Karpenter가 답인가요?

    그건 아닙니다. 워크로드가 단순하고 노드 그룹이 적다면 기존 방식도 충분히 안정적입니다. 다만 노드 그룹이 많아지고 Kubernetes 오토스케일링 설계가 복잡해질수록 Karpenter의 장점이 커졌습니다.

    Q2. 스팟만 써도 되나요?

    중단 허용성이 높은 워크로드라면 가능하지만, 핵심 서비스는 온디맨드 fallback을 함께 두는 게 마음이 편합니다. 저도 처음엔 공격적으로 갔다가 다시 섞어서 운영했습니다.

    Q3. 어떤 팀이 먼저 도입해보면 좋을까요?

    배치 작업, CI 러너, 일시적인 이벤트성 워크로드가 있는 팀부터 추천드립니다. 효과가 빨리 보입니다.

    Kubernetes 오토스케일링 비교를 보여주는 Karpenter 요약 이미지

    Cluster Autoscaler와 Karpenter의 차이점, 추천 사용 시나리오, 운영 포인트를 한 장에 요약한 비교 인포그래픽 이미지입니다.

    마무리: 1년 써보니 결국 설계가 반이었습니다

    Karpenter는 분명 좋은 도구입니다. 하지만 설치했다고 끝나는 종류는 아니었습니다. 어떤 인스턴스를 허용할지, 어떤 워크로드를 어디에 태울지, 스팟과 온디맨드를 어떻게 섞을지, consolidation을 얼마나 공격적으로 둘지 같은 정책 설계가 훨씬 중요했습니다. 저도 처음엔 도구가 다 알아서 해줄 줄 알았는데, 실제로 써보니까 “잘 설계된 제약 조건”이 핵심이더라고요.

    그래도 1년 기준으로 돌아보면, Karpenter 오토스케일러 후기는 꽤 긍정적입니다. 노드 프로비저닝이 더 민첩해졌고, 불필요한 여유 자원을 줄이는 방향으로 운영 습관이 바뀌었습니다. 혹시 지금 EKS에서 노드 그룹이 점점 복잡해지고 있다면, 작은 워크로드 하나부터 붙여보셔도 좋겠습니다. 다음 글에서는 HPA(Horizontal Pod Autoscaler, 파드 수평 확장)와 Karpenter를 같이 운영할 때 생기는 타이밍 이슈도 다뤄볼 예정입니다. 이전 글의 EKS 운영 체크리스트와 함께 보시면 흐름이 더 잘 잡히실 겁니다.

    1년 운영 후 배운 점, 추천 도입 순서, 주의할 설정 포인트를 요약한 마무리 이미지입니다.

  • [k8s] Karpenter 오토스케일로 EKS 비용 절감 검증하는 방법

    [k8s] Karpenter 오토스케일로 EKS 비용 절감 검증하는 방법

    Karpenter 오토스케일로 EKS 비용 절감 검증하는 방법

    Karpenter 오토스케일을 검토하시는 분들은 비슷한 지점에서 막히곤 합니다. HPA로 파드 수는 잘 늘고 줄어드는데, 월말 청구서를 보면 노드가 생각보다 오래 남아 있어서 클라우드 비용 절감 체감이 약하거든요. 특히 AWS EKS를 운영하다 보면 파드 오토스케일링과 노드 비용 최적화가 완전히 같은 문제가 아니라는 점을 금방 느끼게 됩니다.

    저도 초반에는 “HPA만 잘 잡으면 끝나는 거 아닌가?” 싶었는데, 실제로는 노드 단위 빈 공간이 더 큰 병목이었습니다. 이럴 때 Karpenter 오토스케일은 파드 요구사항에 맞춰 노드를 더 유연하게 만들고, 유휴 노드를 정리하는 데 강점이 있습니다. 이번 글에서는 Kubernetes 오토스케일링 관점에서 Karpenter가 왜 비용 최적화에 유리한지, 그리고 AWS EKS 비용 최적화 효과를 어떤 지표로 검증해야 하는지 정리해보겠습니다.

    Karpenter 오토스케일 기반 EKS 아키텍처 개요 이미지

    Karpenter가 스케줄링되지 못한 파드를 감지하고 적절한 EC2 노드를 만들고, 유휴 노드를 정리하는 흐름을 보여주는 이미지입니다.

    Karpenter 오토스케일이 비용 절감에 유리한 이유

    쉽게 말해 Karpenter는 파드가 실제로 필요로 하는 리소스에 맞춰 노드를 바로 만들어주는 도구입니다. 기존 Cluster Autoscaler도 노드 수를 늘리고 줄일 수 있지만, 보통 미리 정의한 노드 그룹을 중심으로 확장하다 보니 리소스가 남는 구간이 생기기 쉽습니다.

    반면 Karpenter는 Pending 상태의 파드를 보고 CPU, 메모리, 아키텍처, 용량 유형(온디맨드/스팟) 같은 조건에 맞는 인스턴스를 더 유연하게 고릅니다. 운영에서 보면 이 차이가 꽤 큽니다. 특정 인스턴스 몇 개만 반복해서 늘리는 대신, 워크로드 특성에 맞춰 c 계열, m 계열, r 계열을 섞고 스팟까지 활용하기 쉬워지거든요.

    항목 Cluster Autoscaler Karpenter
    확장 기준 기존 노드 그룹 중심 파드 요구사항 중심
    인스턴스 선택 유연성 상대적으로 제한적 높음
    빈 리소스 최소화 노드 그룹 크기에 영향 받음 상황별 최적화에 유리
    스팟 활용 구성 복잡도 있음 정책 설계가 비교적 직관적
    비용 절감 포인트 노드 수 축소 노드 크기와 종류까지 최적화

    핵심은 단순히 노드를 빨리 만드는 데 있지 않습니다. Karpenter 오토스케일의 진짜 장점은 불필요하게 큰 노드를 오래 붙잡고 있지 않게 만드는 구조에 있습니다. 결국 비용은 시간당 단가와 실행 시간의 곱이라서, 둘을 같이 줄여야 체감이 나더라고요.

    Karpenter 오토스케일 도입 전에 먼저 봐야 할 비용 지표

    Karpenter를 붙였는데도 “그래서 얼마가 절감됐지?”를 설명하지 못하면 운영팀 설득이 어렵습니다. 감으로 보면 꼭 해석이 꼬입니다. 최소한 아래 지표는 같은 기간 기준으로 같이 보는 편이 좋습니다.

    • 노드 평균 사용률: CPU와 메모리 요청량 대비 실제 사용률
    • 유휴 시간: 야간 또는 비업무 시간에 노드가 얼마나 오래 남는지
    • 파드 Pending 빈도: 확장 지연이 발생하는지
    • 인스턴스 타입 다양성: 특정 타입에 과도하게 묶여 있는지
    • 스팟 비중: 안정성과 비용 균형이 맞는지

    메트릭은 CloudWatch, Prometheus, Grafana 조합으로 많이 보고, 비용은 AWS Cost Explorer 또는 CUR로 맞춰보면 됩니다. 포인트는 하나입니다. 메트릭과 비용 데이터를 반드시 같은 기간으로 맞춰서 비교해야 한다는 점입니다.

    Kubernetes 오토스케일링 구성, 이렇게 잡으면 시작이 편합니다

    실전 구현 자체는 아주 복잡하지 않습니다. 다만 EKS 클러스터가 이미 있어야 하고, IRSA 또는 EKS Pod Identity 같은 AWS 권한 구성이 가능해야 합니다. 그리고 Karpenter가 파드 요청값을 기준으로 판단하므로 워크로드의 resources.requests가 비어 있으면 기대한 최적화가 잘 나오지 않습니다. 이 부분은 정말 중요합니다.

    1. EKS 클러스터 이름과 리전을 환경 변수로 설정합니다.
    2. Karpenter 컨트롤러용 IAM 역할과 중단 이벤트용 큐를 준비합니다.
    3. Helm으로 Karpenter를 설치합니다.
    4. NodePool과 EC2NodeClass를 정의합니다.
    5. 워크로드의 requests/limits를 다시 점검합니다.
    export CLUSTER_NAME=my-eks
    export AWS_DEFAULT_REGION=ap-northeast-2
    export KARPENTER_NAMESPACE=karpenter
    export KARPENTER_VERSION=1.14.0
    export KARPENTER_IAM_ROLE_ARN=arn:aws:iam::123456789012:role/KarpenterControllerRole
    
    aws eks update-kubeconfig --name $CLUSTER_NAME --region $AWS_DEFAULT_REGION
    kubectl config current-context
    kubectl get nodes
    

    클러스터 연결부터 먼저 확인해두세요. 여기서 컨텍스트가 꼬이면 다른 클러스터에 배포하는 사고가 의외로 자주 납니다. 한 번만 겪어도 이 단계가 왜 중요한지 바로 느껴지더라고요.

    Karpenter 설치 예시

    helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
      --version $KARPENTER_VERSION \
      --namespace $KARPENTER_NAMESPACE \
      --create-namespace \
      --set settings.clusterName=$CLUSTER_NAME \
      --set settings.interruptionQueue=$CLUSTER_NAME \
      --set serviceAccount.annotations.eks\.amazonaws\.com/role-arn=$KARPENTER_IAM_ROLE_ARN \
      --wait
    
    kubectl -n $KARPENTER_NAMESPACE get pods
    

    설치가 끝나면 컨트롤러 파드가 정상 기동하는지 먼저 확인합니다. 여기서 CrashLoopBackOff가 보이면 IAM 권한, 서브넷 태그, 보안 그룹 태그, 이벤트 큐 설정부터 보는 게 빠릅니다.

    Karpenter 오토스케일 설정과 NodePool 연결 구조 이미지

    Karpenter 컨트롤러, NodePool, EC2NodeClass, 워크로드 파드가 어떻게 연결되는지 보여주는 구성 다이어그램입니다.

    NodePool과 EC2NodeClass 예시

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: default
    spec:
      template:
        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: node.kubernetes.io/instance-type
              operator: In
              values: ["c6i.large", "m6i.large", "r6i.large"]
          expireAfter: 720h
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
        consolidateAfter: 5m
      limits:
        cpu: "200"
    ---
    apiVersion: karpenter.k8s.aws/v1
    kind: EC2NodeClass
    metadata:
      name: default
    spec:
      role: KarpenterNodeRole-my-eks
      amiSelectorTerms:
        - alias: al2023@latest
      subnetSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks
      securityGroupSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks
    

    여기서 핵심은 세 가지입니다. 첫째, NodePool에는 nodeClassRef가 필요합니다. 둘째, 최근 EKS 기준으로는 AL2보다 AL2023 계열 AMI 선택이 더 자연스럽습니다. 셋째, consolidation을 켜야 유휴 노드 정리가 제대로 일어납니다. 실제 비용 절감은 스케일 아웃보다 이 정리 단계에서 더 크게 체감되는 경우가 많습니다.

    테스트용 워크로드 배포

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: inflate
    spec:
      replicas: 0
      selector:
        matchLabels:
          app: inflate
      template:
        metadata:
          labels:
            app: inflate
        spec:
          terminationGracePeriodSeconds: 0
          containers:
            - name: inflate
              image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
              resources:
                requests:
                  cpu: 1
    
    kubectl apply -f inflate.yaml
    kubectl scale deployment inflate --replicas 5
    kubectl get pods -w
    

    이렇게 테스트하면 Pending 상태였던 파드 수요를 Karpenter가 보고 새 노드를 띄우는 흐름을 확인할 수 있습니다. 이후 replicas를 다시 줄이거나 배포를 삭제해보면 노드 통합 정리까지 관찰할 수 있습니다. 작은 워크로드부터 붙여보는 방식이 운영 리스크도 낮고 결과도 해석하기 편합니다.

    AWS EKS 비용 최적화를 위한 운영 포인트

    이 섹션은 꼭 짚고 넘어가야 합니다. Karpenter를 도입했다고 자동으로 클라우드 비용 절감이 되진 않습니다. 정책을 어떻게 잡느냐에 따라 결과가 꽤 다르게 나옵니다.

    • requests를 현실적으로 작성: 과장된 요청값은 큰 노드를 계속 부릅니다.
    • 스팟과 온디맨드 혼합: 서비스 성격에 따라 풀을 나누는 편이 안정적입니다.
    • 워크로드 분리: API 서버 계열과 배치 계열을 같은 NodePool에 몰지 않는 게 좋습니다.
    • 스케일 인 여유 시간 확인: 너무 공격적으로 줄이면 재기동이 잦아질 수 있습니다.
    • PodDisruptionBudget 검토: 축소가 막혀 노드가 안 내려가는 경우가 생각보다 많습니다.

    처음부터 모든 워크로드를 하나의 NodePool에 넣으면 정책 충돌이 생기기 쉽습니다. 최소한 안정성 우선 풀과 비용 우선 풀 정도는 분리해두면 운영이 훨씬 편합니다. 이거 진짜 편하더라고요.

    ⚠️ 실제로 많이 겪는 문제와 해결법

    문서만 보면 금방 될 것 같아도 운영 환경에서는 자잘한 함정이 많습니다. 특히 권한, 태그, 요청값 같은 기본 설정에서 많이 막힙니다.

    1. 노드가 아예 생성되지 않는 경우

    대부분은 IAM 또는 태그 문제입니다. 서브넷과 보안 그룹에 karpenter.sh/discovery 태그가 빠져 있으면 인프라 리소스를 찾지 못합니다.

    kubectl describe nodepool default
    kubectl -n karpenter logs deploy/karpenter
    

    로그에서 subnet, security group, instance profile, access denied 관련 메시지를 먼저 확인하면 원인이 빨리 보입니다.

    2. 노드는 생기는데 비용이 생각보다 안 줄어드는 경우

    이 경우도 자주 나옵니다. 원인은 대개 세 가지입니다.

    • 파드 requests가 과하게 큼
    • PodDisruptionBudget 때문에 빈 노드를 못 비움
    • 스팟 허용 범위가 너무 좁음

    Karpenter가 덜 똑똑해서가 아니라 입력값이 비효율적인 경우가 많습니다. 결국 스케줄러와 오토스케일러도 선언된 요청값을 기준으로 움직이니까요.

    3. 스팟 중단 대응이 불안한 경우

    스팟은 비용 면에서 매력적이지만 서비스 성격에 따라 조심해야 합니다. 중단 알림을 받았을 때 드레이닝과 재스케줄링이 자연스럽게 되도록 준비가 필요합니다. 중요한 서비스는 무조건 스팟으로 몰기보다 온디맨드와 섞는 편이 안전합니다.

    Karpenter 오토스케일 비용 절감 결과 대시보드 이미지

    파드 수 증가에 따라 노드가 늘고, 유휴 시간대에 통합 정리로 노드 수가 줄어드는 메트릭 대시보드 예시입니다.

    Karpenter 오토스케일 비용 절감 효과, 어떻게 검증해야 할까

    여기서 중요한 건 “노드 수가 줄었다”로 끝내지 않는 겁니다. 노드 수는 줄었는데 더 비싼 타입을 오래 썼다면 의미가 없거든요. 저는 아래 순서로 비교하면 결과 해석이 가장 깔끔했습니다.

    1. 도입 전 2주와 도입 후 2주를 비교합니다.
    2. 같은 요일, 같은 시간대 기준으로 워크로드 패턴을 맞춥니다.
    3. EC2 비용, 노드 평균 개수, 평균 vCPU 사용률을 함께 봅니다.
    4. 배포 실패, Pending 시간, 재스케줄링 지연이 늘지 않았는지 확인합니다.
    5. 스팟 비중 증가가 장애로 이어지지 않았는지 점검합니다.

    특히 비용 / 요청 처리량 같은 지표가 설명력이 좋습니다. 같은 트래픽을 처리하는데 필요한 EC2 비용이 줄었는지 보면, 단순 총액보다 훨씬 설득력이 있습니다. 팀 내 공유 문서에도 이 지표를 같이 적어두면 의사결정이 빨라집니다.

    kubectl top nodes
    kubectl top pods -A
    aws ce get-cost-and-usage \
      --time-period Start=2026-07-01,End=2026-07-15 \
      --granularity DAILY \
      --metrics UnblendedCost \
      --group-by Type=DIMENSION,Key=SERVICE
    

    위 명령은 비용 추세를 보는 기본 예시입니다. 참고로 Cost Explorer의 종료일은 보통 마지막 날짜 다음 날로 잡아야 비교가 깔끔합니다. 운영에서는 CUR를 Athena로 조회하거나 Grafana에 연결해 시계열로 보는 쪽이 더 편합니다.

    해석할 때 주의할 점

    • 도입 직후 며칠은 캐시, 배포 주기, 스팟 수급 영향으로 흔들릴 수 있습니다.
    • 트래픽 자체가 줄어서 비용이 감소한 것을 Karpenter 효과로 착각하면 안 됩니다.
    • RI나 Savings Plans 영향도 함께 봐야 합니다.

    결국 비교 기준은 같은 부하 조건이어야 합니다. 이 기준만 지켜도 해석 오류가 꽤 줄어듭니다.

    정리: 이런 환경이라면 Karpenter 도입 가치가 큽니다

    다음 조건에 해당하면 Karpenter 도입 효과가 비교적 잘 나옵니다.

    • 트래픽 변동폭이 크다
    • 워크로드 종류가 다양하다
    • EKS에서 인스턴스 타입 선택 폭을 넓게 가져갈 수 있다
    • 스팟 활용 여지가 있다
    • 현재 노드 유휴 시간이 길다

    반대로 워크로드가 매우 단순하고 부하가 거의 고정되어 있다면 체감 효과가 크지 않을 수도 있습니다. 그래서 무조건 도입부터 하기보다, 현재 비효율이 어디서 생기는지 먼저 확인하는 편이 맞습니다. 그다음 작은 서비스에 시범 적용하고 결과를 비교하면 훨씬 덜 위험합니다.

    Karpenter 오토스케일과 기존 오토스케일링 비교 인포그래픽

    노드 그룹 기반 확장과 파드 요구사항 기반 확장의 차이를 한눈에 요약한 비교 이미지입니다.

    마무리

    이번 글의 핵심은 간단합니다. Karpenter 오토스케일은 단순한 증설 도구라기보다, AWS EKS 비용 최적화를 운영 레벨에서 밀어주는 도구에 가깝습니다. 다만 효과는 설치 여부보다 requests 품질, NodePool 정책, 스팟 전략, 검증 방식에 더 크게 좌우됩니다.

    지금 EKS 비용이 애매하게 새고 있다고 느끼신다면, 전면 도입부터 하기보다 작은 워크로드에 먼저 붙여보세요. 그리고 이전 글의 HPA/VPA 튜닝 가이드도 함께 보시면 파드 스케일링과 노드 스케일링을 한 흐름으로 이해하는 데 도움이 됩니다. 다음 글에서는 스팟과 온디맨드 혼합 정책을 어떻게 설계해야 장애 없이 비용을 줄일 수 있는지 이어서 다뤄보겠습니다.

    자주 묻는 질문

    Karpenter만 도입하면 비용이 바로 줄어드나요?

    아닙니다. requests 값, NodePool 정책, 스팟 허용 범위가 같이 맞아야 의미 있는 절감이 나옵니다.

    Cluster Autoscaler를 꼭 버려야 하나요?

    꼭 그렇진 않습니다. 다만 인스턴스 선택 유연성과 운영 단순성이 중요하다면 Karpenter가 더 잘 맞는 환경이 많습니다.

    비용 절감 효과는 무엇으로 확인하나요?

    총 EC2 비용만 보지 말고, 같은 트래픽 대비 비용, 노드 평균 사용률, 유휴 시간, Pending 감소 여부를 함께 보시면 됩니다.