13년차의 서버실

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

[태그:] Karpenter

  • [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로 클라우드 비용 절감하기

    안녕하세요, 13년차 서버실 지킴이, 인프라 엔지니어입니다.
    오늘은 Kubernetes(쿠버네티스) 환경에서 Karpenter(카펜터)를 쓰시는 분들이라면 꼭 한번쯤 겪어보셨을 ‘리소스 최적화’에 대한 이야기를 해볼게요. 특히 Karpenter처럼 비용 효율을 극대화할 수 있는 오토스케일링 도구를 사용 중이라면, Pod(파드)의 리소스 요청(requests)과 제한(limits) 설정에 따라 비용이 천차만별로 달라지거든요. 저도 홈랩에서 Karpenter를 굴리면서 삽질을 좀 했었죠. 처음엔 그냥 대충 설정했다가 불필요한 노드들이 마구 스케일업 되는 걸 보고 깜짝 놀랐습니다. “아, 이거 뭔가 잘못됐구나!” 싶었어요. 그래서 오늘은 Karpenter의 효율을 최대한 끌어올리기 위한 Kubernetes 리소스 최적화 체크리스트를 공유해보겠습니다.

    쿠버네티스 파드 리소스 요청에 따른 카펜터 오토스케일링 개념도

    파드 리소스 설정에 따라 Karpenter가 노드를 어떻게 스케일링하는지 보여주는 개념도입니다.

    개념 설명: Requests와 Limits, 그리고 Kubernetes 리소스 최적화

    Kubernetes에서 Pod의 리소스 설정은 크게 두 가지로 나뉩니다.

    • Requests (요청): Pod가 스케줄링될 때 필요한 최소한의 리소스 양이에요. 스케줄러는 이 requests 값을 기준으로 Pod를 어느 노드에 배치할지 결정합니다. 즉, Pod가 안정적으로 시작하고 실행될 수 있는 ‘보장된’ 리소스죠. 만약 노드에 이만큼의 리소스 여유가 없으면 Pod는 스케줄링이 안 되고 Pending 상태로 머물게 됩니다.
    • Limits (제한): Pod가 사용할 수 있는 최대 리소스 양을 의미해요. limits를 설정하면 Pod가 이 값을 초과하여 리소스를 사용하는 것을 막을 수 있습니다. 예를 들어, CPU limits를 설정하면 Pod가 해당 CPU 코어 이상을 사용하지 못하게 OS 레벨에서 제한하고, 메모리 limits를 초과하면 OOMKilled(Out Of Memory Killed)되어 Pod가 재시작될 수 있으니까요.

    Karpenter는 바로 이 requests 값을 기반으로 노드를 프로비저닝(provisioning)합니다. 만약 Pod들이 요청하는 리소스가 너무 적거나, 아예 requests를 설정하지 않으면 Karpenter는 최적의 노드 타입을 찾기 어려워하거나, 너무 작은 노드를 프로비저닝해서 Pod들이 제대로 실행되지 못하게 될 수 있어요. 반대로 requests가 너무 높으면 불필요하게 큰 노드들이 프로비저닝되어 비용이 낭비될 수밖에 없죠.

    저 홈랩에서 겪었던 대표적인 삽질이 바로 이 requests 값 설정 미스였어요. Pod들이 필요한 리소스는 꽤 되는데 requests를 너무 낮게 잡았더니, Karpenter가 작은 노드를 띄우고 Pod들이 노드에서 CPU Throttling(쓰로틀링, CPU 사용량 제한)에 걸리거나 OOMKilled 되는 일이 빈번했습니다. 결국 노드가 계속 스케일업 되면서 비용만 더 나가는 상황이 발생하더라고요.

    Karpenter 효율 극대화를 위한 리소스 최적화 체크리스트

    자, 그럼 본론으로 들어가서 Karpenter와 Kubernetes 리소스를 효율적으로 관리하기 위한 체크리스트를 하나씩 짚어볼게요.

    1. 모든 컨테이너에 requests 설정하기 (필수!) 💡

    가장 기본적인 원칙이자 제가 가장 강조하는 부분입니다. 모든 컨테이너에는 requests를 명시적으로 설정해야 해요. limits는 선택 사항일 수 있지만, requests는 반드시 있어야 Karpenter가 노드 용량을 정확하게 예측하고 효율적인 노드를 스케일링할 수 있습니다. requests가 없으면 Kubernetes는 기본적으로 Pod가 노드의 모든 리소스를 요청한다고 간주하거나, 아예 스케줄링 판단을 내리기 어려워집니다.

    # Pod 리소스 요청(requests)과 제한(limits) 예시
    apiVersion: v1
    kind: Pod
    metadata:
      name: my-app-pod
    spec:
      containers:
      - name: my-app-container
        image: my-repo/my-app:latest
        resources:
          requests:
            cpu: "200m"  # 0.2 vCPU 요청
            memory: "256Mi" # 256 MiB 메모리 요청
          limits:
            cpu: "500m"  # 0.5 vCPU 제한 (선택 사항)
            memory: "512Mi" # 512 MiB 메모리 제한 (선택 사항)
    

    Kubernetes Pod 정의에서 resources.requests를 명시하는 YAML 예시입니다.

    쿠버네티스 파드 리소스 요청(requests) 및 제한(limits) 적용 개념도

    Kubernetes 파드 리소스 요청(requests) 및 제한(limits) 적용 개념도입니다.

    여기서 200m는 0.2 CPU 코어를 의미하고, 256Mi는 256 메비바이트(MiB)를 의미해요. ‘m’은 밀리코어(millicore), ‘Mi’는 메비바이트입니다. 이 단위를 정확히 아는 게 중요하더라고요. 저도 처음엔 MB랑 MiB가 같은 건 줄 알았다가 삽질했었죠.

    2. requests는 실제 평균 사용량에 가깝게 설정하기 ✅

    requests는 Pod가 안정적으로 동작하는 데 필요한 최소한의 리소스여야 합니다. 너무 낮게 설정하면 Pod가 스케줄링되더라도 리소스 부족으로 성능 저하를 겪거나 OOMKilled될 수 있어요. 반대로 너무 높게 설정하면 Karpenter가 필요 이상으로 큰 노드를 프로비저닝해서 비용 낭비로 이어지니까요.

    팁: Pod의 실제 리소스 사용량을 모니터링하세요. Prometheus(프로메테우스) + Grafana(그라파나) 조합으로 Pod의 CPU 및 메모리 사용량 추이를 확인하고, 평균 사용량에 약간의 버퍼를 더한 값으로 requests를 설정하는 게 좋습니다.

    # 특정 Pod의 리소스 사용량 확인 (metrics-server 설치 필수)
    # 현재 CPU, Memory 사용량은 확인할 수 있지만, 과거 트렌드는 모니터링 툴로 봐야 합니다.
    kubectl top pod <pod-name> -n <namespace> --containers
    
    # 예시:
    # kubectl top pod my-app-pod-abcde -n default --containers
    # CONTAINER      CPU(cores)   MEMORY(bytes)
    # my-app-container   120m         180Mi
    

    kubectl top pod 명령어로 특정 파드의 현재 리소스 사용량을 확인하는 예시입니다.

    3. CPU Limits는 신중하게 설정하기 ⚠️

    CPU Limits는 조금 복잡한 이야기네요. CPU는 ‘압축 가능한(compressible)’ 리소스라서, limits를 넘어도 Pod가 바로 죽는 대신 CPU 스케줄러에 의해 사용량이 제한(throttling)됩니다. 문제는 이 CPU 스로틀링이 애플리케이션의 응답 시간을 지연시키거나 성능 문제를 일으킬 수 있다는 점이에요.

    • CPU Limits를 requests와 동일하게 설정: 가장 안전한 방법입니다. Pod가 요청한 만큼만 CPU를 사용하도록 제한하여 다른 Pod에 영향을 주지 않죠. 하지만 버스트(burst) 성능이 필요한 경우 불리할 수 있습니다.
    • CPU Limits를 requests보다 높게 설정: Pod가 requests 이상으로 CPU를 사용할 수 있도록 여유를 줍니다. 버스트 성능이 중요하거나, 평소에는 CPU 사용량이 낮지만 가끔 순간적으로 높아지는 애플리케이션에 적합해요. 이 경우, 노드의 전체 CPU 사용량이 높아지면 특정 Pod들이 CPU 스로틀링을 겪을 가능성이 있습니다.
    • CPU Limits를 아예 설정하지 않음: Pod가 노드의 남은 CPU 리소스를 최대한 활용할 수 있으니까요. 이는 CPU 사용량이 예측 불가능하거나 매우 동적인 워크로드에 유리할 수 있지만, 특정 Pod가 다른 Pod의 성능에 영향을 줄 수 있는 위험이 있습니다.

    제 경험상, 대부분의 웹 서비스나 API 서버는 CPU Limits를 requests보다 조금 높게 설정하거나 아예 설정하지 않는 게 더 나았어요. 하지만 배치 작업이나 CPU 집약적인 워크로드는 다른 Pod에 영향을 주지 않도록 requests와 limits를 동일하게 설정하는 게 좋더라고요. 결국 워크로드의 특성을 이해하고 트레이드오프(trade-off)를 고려해야 합니다.

    설정 방식 장점 단점 권장 워크로드
    requests == limits 리소스 사용 예측 가능
    CPU 스로틀링 예측 가능
    버스트 성능 제한 CPU 집약적 배치, 예측 가능한 워크로드
    requests < limits 버스트 성능 허용
    평균 사용량 최적화
    노드 과부하 시 스로틀링 가능성
    리소스 사용량 예측 어려움
    웹 서비스, API 서버 (간헐적 트래픽 증가)
    limits 미설정 최대 성능 활용
    유연한 리소스 사용
    특정 Pod의 노드 CPU 독점 가능성
    스케줄링 불확실성 증가
    탐색적 분석, 개발 환경, CPU 사용량 예측 불가능한 워크로드

    CPU 요청(requests)과 제한(limits) 설정 방식에 따른 장단점 및 권장 워크로드를 비교한 표입니다.

    4. Memory Limits는 항상 설정하기 (매우 중요!) ⚠️

    메모리는 ‘압축 불가능한(non-compressible)’ 리소스에요. Memory Limits를 초과하면 Pod는 즉시 OOMKilled되어 재시작됩니다. 이는 서비스 가용성에 직접적인 영향을 미치죠. 따라서 Memory Limits는 항상 설정하는 걸 권장합니다.

    Memory Limits는 Pod가 최대로 사용할 수 있는 메모리 양에 약간의 여유를 더한 값으로 설정하는 게 좋아요. 만약 requests와 limits를 동일하게 설정하면 Pod가 예상치 못한 메모리 스파이크(spike)에 취약해질 수 있거든요. 저도 처음엔 requests와 limits를 동일하게 설정했다가, 순간적인 메모리 사용량 증가로 Pod가 계속 죽는 경험을 했었어요. 로그를 보니 OOMKilled가 찍혀있더군요. 결국 limits를 requests보다 넉넉하게 설정해서 해결했습니다.

    5. Namespace(네임스페이스)별 LimitRange 활용 고려

    만약 클러스터에 배포되는 모든 Pod에 일일이 requests와 limits를 설정하기 어렵다면, LimitRange 리소스를 활용하는 것도 좋은 방법입니다. LimitRange는 특정 네임스페이스 내에서 Pod의 기본 리소스 requests/limits를 설정하거나, 최소/최대 값을 강제할 수 있으니까요.

    # LimitRange 예시
    apiVersion: v1
    kind: LimitRange
    metadata:
      name: pod-resource-limits
    spec:
      limits:
      - default:
          cpu: 500m
          memory: 512Mi
        defaultRequest:
          cpu: 100m
          memory: 128Mi
        type: Container
    

    LimitRange를 사용하여 네임스페이스 내 컨테이너의 기본 리소스 요청/제한을 설정하는 YAML 예시입니다.

    이렇게 설정하면 해당 네임스페이스에 배포되는 Pod들은 별도의 resources 설정을 하지 않아도 defaultRequest와 default 값이 자동으로 적용됩니다. 물론, Pod 자체에서 resources를 명시하면 그 값이 우선합니다.

    6. HPA(Horizontal Pod Autoscaler)와 함께 사용 시 유의점

    HPA는 Pod의 CPU/메모리 사용량이나 커스텀 지표를 기반으로 Pod의 개수를 자동으로 조절해요. 여기서 CPU 사용량 기반 HPA는 Pod의 requests.cpu 값을 기준으로 동작합니다. 예를 들어, HPA 설정에서 targetCPUUtilizationPercentage: 80으로 지정했다면, Pod의 실제 CPU 사용량이 requests.cpu의 80%를 초과할 때 스케일 아웃(scale-out)이 발생하죠.

    만약 requests.cpu를 너무 낮게 설정하면, Pod의 실제 CPU 사용량이 낮더라도 requests 대비 높은 비율로 계산되어 불필요하게 HPA가 스케일 아웃을 유발할 수 있어요. 반대로 너무 높게 설정하면, 실제 Pod가 힘들게 일하고 있어도 requests 대비 낮은 비율로 계산되어 스케일 아웃이 늦어질 수 있습니다.

    이 때문에 requests.cpu는 Pod의 실제 부하 패턴을 잘 반영하는 값으로 설정하는 게 중요합니다.

    검증 및 결과 확인

    자, 이렇게 Kubernetes 리소스 설정을 최적화했다면, 실제로 Karpenter가 효율적으로 노드를 스케일링하고 있는지, Pod들이 안정적으로 동작하는지 확인해야겠죠?

    1. Karpenter 노드 프로비저닝 확인: kubectl get nodes 명령어로 노드들이 예상했던 인스턴스 타입으로, 필요한 만큼만 프로비저닝되었는지 확인합니다.
    2. Pod 상태 확인: kubectl get pods로 모든 Pod가 Running 상태인지, OOMKilled나 CrashLoopBackOff 같은 문제가 없는지 확인해요.
    3. 리소스 사용량 모니터링: Prometheus, Grafana 등을 통해 Pod들의 CPU/메모리 사용량, 스로틀링 발생 여부 등을 지속적으로 모니터링합니다. 특히 노드의 Allocatable(할당 가능) 리소스와 Pod들의 requests 합계를 비교하여 노드 활용률을 확인하는 게 중요합니다.
    4. 비용 절감 효과 측정: 클라우드 비용 청구서를 통해 이전과 비교하여 실제 비용이 얼마나 절감되었는지 확인하는 게 가장 확실한 지표입니다.
    Grafana 대시보드에서 파드 및 노드 리소스 사용량과 Karpenter 스케일링 결과 모니터링 화면

    최적화 후 Grafana 대시보드에서 파드 및 노드의 리소스 사용량과 Karpenter 스케일링 결과를 확인하는 모습입니다.

    마무리

    오늘은 Karpenter의 효율을 극대화하기 위한 Kubernetes 리소스 요청/제한 최적화 체크리스트를 살펴봤습니다. 13년차 인프라 엔지니어로 제가 직접 겪었던 삽질과 해결 경험을 바탕으로 이야기해드렸는데, 도움이 되셨으면 좋겠네요.

    결론적으로, Karpenter와 같은 오토스케일링 도구의 진정한 가치를 끌어내려면 Pod의 requests와 limits 설정을 제대로 이해하고, 워크로드의 특성에 맞춰 세밀하게 튜닝하는 과정이 필수적입니다.

    • requests는 모든 컨테이너에 반드시 설정하고, 실제 평균 사용량에 가깝게 맞춰야 해요.
    • Memory Limits는 항상 설정하여 OOMKilled를 방지하고, requests보다 넉넉하게 주는 게 좋습니다.
    • CPU Limits는 워크로드 특성을 고려하여 신중하게 설정해야 합니다. 버스트 성능이 중요하다면 requests보다 높게, 안정성이 우선이라면 requests와 동일하게 설정하는 것을 고려해보세요.

    이러한 최적화 작업을 통해 불필요한 노드 스케일업을 줄이고, 클라우드 비용을 절감하면서도 안정적인 서비스를 운영할 수 있을 거예요. 당장 눈에 띄는 효과는 없을지라도, 꾸준히 모니터링하고 튜닝하는 노력이 결국 큰 비용 절감으로 이어질 거라고 확신합니다. 여러분의 Kubernetes 클러스터 운영에 이 글이 작은 도움이 되었으면 좋겠습니다! 다음에는 Karpenter NodePool(노드풀) 전략에 대해 좀 더 깊이 있게 다뤄보겠습니다.

    Kubernetes 리소스 최적화 및 Karpenter 효율성 향상 핵심 체크리스트 인포그래픽

    Kubernetes 리소스 최적화 및 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 비용 최적화: 불필요한 노드 스케일링 방지 전략

    [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 오토스케일링, 성능 벤치마크: 다양한 워크로드 환경 테스트

    [k8s] Karpenter 오토스케일링, 성능 벤치마크: 다양한 워크로드 환경 테스트

    [Kubernetes] Karpenter 성능 벤치마크로 보는 오토스케일링 테스트

    Karpenter 성능 벤치마크 이야기를 해보려고 합니다. Kubernetes(쿠버네티스) 운영을 조금만 오래 해보면, 결국 병목은 애플리케이션이 아니라 노드가 언제, 얼마나 빠르게 붙느냐에서 터지는 경우가 많거든요. 저도 홈랩과 실서비스에 가까운 테스트 환경에서 이것저것 만져보면서 느낀 게, 오토스케일링은 단순히 “늘어난다”가 아니라 얼마나 빨리 반응하고, 얼마나 낭비 없이 붙고, 붙은 뒤 얼마나 안정적으로 정리되느냐까지 같이 봐야 한다는 점이었습니다.

    특히 이번 글은 Karpenter 성능 벤치마크라는 키워드에 맞춰서, 단순 사용기가 아니라 benchmark(벤치마크) 관점으로 정리했습니다. 수치만 던지는 글이 아니라, 어떤 워크로드에서 무엇을 관찰해야 하는지, 그리고 Kubernetes 오토스케일링 성능을 볼 때 어디서 실수하기 쉬운지까지 같이 묶어보겠습니다. 혹시 노드 확장 속도 테스트를 해보려고 하는데 어디서부터 봐야 할지 막막하셨다면, 이 글이 꽤 도움이 될 겁니다.

    Karpenter 성능 벤치마크를 위한 쿠버네티스 오토스케일링 아키텍처 개요 이미지

    Karpenter, 스케줄러, 워크로드, 신규 노드 프로비저닝 흐름을 한눈에 보여주는 아키텍처 개요 이미지입니다.

    Karpenter란 무엇이고, 벤치마크에서 왜 중요할까요?

    쉽게 말해 Karpenter는 Pending Pod(스케줄되지 못하고 대기 중인 파드)를 보고, 거기에 맞는 노드를 빠르게 띄우는 방식의 노드 프로비저너입니다. 예전에는 HPA(Horizontal Pod Autoscaler, 파드 수 자동 조절)와 Cluster Autoscaler(클러스터 오토스케일러, 노드 수 자동 조절)를 묶어서 보는 경우가 많았는데요. Karpenter는 여기서 조금 더 직접적으로 “어떤 노드가 필요한지”를 계산해서 띄우는 쪽에 가깝습니다.

    제가 처음 이걸 봤을 때는 “결국 노드 늘리는 거 아닌가?” 싶었는데, 실제로 써보니까 차이가 꽤 크더라고요. 특히 아래 같은 부분이 벤치마크 포인트였습니다.

    • 프로비저닝 결정 속도: Pending Pod가 생긴 뒤 새 노드가 뜨기까지의 흐름
    • bin packing(빈 패킹, 자원 밀집 배치) 효율: CPU와 메모리 요청량에 맞춰 얼마나 낭비 없이 배치되는지
    • 워크로드 적합성: 짧게 치고 빠지는 배치성 작업과, 지속적으로 부하가 유지되는 서비스형 워크로드에서 반응이 다른지
    • 정리 속도: 부하가 빠졌을 때 불필요한 노드를 얼마나 깔끔하게 줄이는지

    여기서 중요한 포인트! Karpenter 효율성 검증은 “몇 초 빨랐다”보다, 스케줄링 실패 원인이 어디서 사라졌는지를 보는 게 더 중요합니다. 이미지 풀링(Image Pulling), CNI(Container Network Interface) 초기화, 리소스 요청값 과대설정 같은 변수가 너무 많거든요.

    벤치마크 설계: 어떤 워크로드로 테스트해야 하나요?

    벤치마크는 결국 설계가 반입니다. 제가 직접 해보니, 하나의 워크로드만 보면 결론이 자꾸 왜곡되더라고요. 그래서 최소한 아래 3종류는 나눠서 보는 걸 추천합니다.

    워크로드 유형 특징 관찰 포인트
    Burst API 짧은 시간에 요청 급증 노드 확장 속도 테스트, Pending Pod 해소 시점
    Batch Job 대량 Job이 한 번에 생성 bin packing 효율, 스케줄링 밀집도
    Steady Service 지속적인 중간 부하 과잉 프로비저닝 여부, 축소 안정성

    이렇게 나누는 이유가 있습니다. Burst API는 순간 반응 속도를 보기 좋고, Batch Job은 Karpenter가 노드를 얼마나 촘촘하게 맞춰 띄우는지 보기 좋습니다. Steady Service는 오히려 쓸데없이 많이 띄우지 않는지를 확인하기 좋습니다. 사실 운영에서는 이 마지막이 비용에 제일 크게 영향을 주더라고요.

    테스트 환경 준비: 관측 지표부터 맞춰야 합니다

    벤치마크 전에 먼저 해야 할 건 화려한 부하툴이 아니라 관측 기준 통일입니다. Prometheus(프로메테우스, 메트릭 수집), Grafana(그라파나, 시각화), 그리고 kubectl 이벤트 확인 정도는 꼭 준비해두세요. 저는 처음에 부하부터 때렸다가, 나중에 보니 노드는 떴는데 이미지 풀 때문에 파드가 늦게 뜬 거라 삽질 좀 했습니다 ㅎㅎ

    1. Kubernetes 클러스터 준비
    2. Karpenter 설치 및 기본 프로비저닝 정책 정의
    3. 메트릭 수집 도구 연결
    4. 부하 생성 도구 배치
    5. 이벤트 타임라인 기록
    helm repo add karpenter https://charts.karpenter.sh
    helm repo update
    
    kubectl create namespace karpenter
    helm upgrade --install karpenter karpenter/karpenter \
      --namespace karpenter \
      --set settings.clusterName=my-cluster

    설치 방식은 환경마다 조금 다를 수 있지만, 핵심은 설치 자체보다 Provisioner/NodePool 성격의 정책과 워크로드 리소스 요청값을 맞추는 겁니다.

    apiVersion: karpenter.sh/v1beta1
    kind: NodePool
    metadata:
      name: default
    spec:
      template:
        spec:
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
          expireAfter: 720h
      disruption:
        consolidationPolicy: WhenUnderutilized

    위 예시는 개념 예시입니다. 실제 필드나 리소스 종류는 사용 중인 Karpenter 버전에 따라 다를 수 있으니, 운영 반영 전에는 현재 문서를 꼭 맞춰보셔야 합니다. 버전 차이 무시하고 복붙했다가 안 되는 경우가 진짜 많거든요.

    Karpenter 성능 벤치마크에서 NodePool과 리소스 요청값 구성을 설명하는 이미지

    NodePool 정책, 리소스 요청/제한, 스케줄링 조건이 어떻게 연결되는지 보여주는 구성 다이어그램입니다.

    실전 구현: 다양한 워크로드로 Karpenter 성능 벤치마크 진행하기

    1. Burst API 워크로드

    짧은 시간에 Deployment(디플로이먼트) 복제 수를 확 올리거나, HPA와 부하 생성기를 함께 써서 순간 부하를 만드는 방식입니다.

    kubectl scale deployment sample-api --replicas=50
    kubectl get pods -w

    여기서 보는 건 단순히 파드가 Running 되는 순간이 아닙니다.

    • 처음 Pending이 발생한 시점
    • Karpenter가 노드 생성 결정을 내린 시점
    • 노드 Ready 시점
    • 파드가 실제 서비스 가능 상태가 된 시점

    제가 직접 해보니 이 네 구간을 나눠서 봐야 병목이 보였습니다. 노드 생성은 빨랐는데 DaemonSet 초기화 때문에 실제 서비스 반영이 늦는 경우도 있더라고요.

    2. Batch Job 워크로드

    Job을 여러 개 한 번에 던져보면 bin packing 효율을 확인하기 좋습니다.

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: cpu-batch-sample
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: worker
              image: busybox
              command: ["sh", "-c", "sleep 120"]
              resources:
                requests:
                  cpu: "500m"
                  memory: "256Mi"
    for i in $(seq 1 30); do
      kubectl create -f job.yaml
    done

    이 테스트는 생각보다 중요합니다. 왜냐하면 실제 운영에서는 API 서버보다 배치 잡이 더 난폭하게 클러스터를 흔드는 경우가 많거든요. 여기서 Karpenter가 과도하게 큰 노드를 띄우는지, 아니면 필요한 만큼만 맞춰 띄우는지를 체크하면 Karpenter 효율성 검증에 큰 도움이 됩니다.

    3. Steady Service 워크로드

    이건 부하를 오래 유지하면서 축소 동작까지 보는 테스트입니다.

    kubectl top nodes
    kubectl top pods -A
    kubectl get events -A --sort-by=.lastTimestamp

    중요한 건 “늘어나는 속도”만 보지 말고, 줄어들 때 서비스에 영향 없이 정리되는지도 같이 보셔야 합니다. 노드가 줄면서 PodDisruptionBudget(PDB, 파드 중단 예산)이나 스케줄링 제약과 충돌하면 오히려 운영 피로도가 확 올라갑니다.

    ⚠️ 실제로 많이 겪는 문제와 트러블슈팅

    이 섹션은 꼭 넣고 싶었습니다. 벤치마크가 이상하게 나오면 대부분 Karpenter 자체 문제라기보다 주변 설정 때문이더라고요.

    • 리소스 요청값이 비현실적일 때: requests가 과하게 크면 노드가 과잉 생성됩니다.
    • 이미지 풀 지연: 새 노드가 떠도 컨테이너 이미지 다운로드 때문에 체감은 느립니다.
    • DaemonSet 오버헤드 누락: CNI, 로깅 에이전트, 모니터링 에이전트 자원이 계산에서 빠지면 예상보다 적게 들어갑니다.
    • 스케줄링 제약 과다: nodeSelector, affinity, taint/toleration이 많으면 Karpenter가 선택할 수 있는 노드 풀이 좁아집니다.

    저도 처음엔 “왜 노드를 띄웠는데 Pending이 안 없어지지?” 싶었는데, 알고 보니 topology spread constraints와 리소스 요청이 같이 꼬였던 적이 있었습니다. 이럴 때는 아래 순서로 보면 빨리 풀립니다.

    1. Pending Pod describe로 실패 이유 확인
    2. Karpenter 로그에서 provisioning decision 확인
    3. 노드 Ready 이후 CNI/DaemonSet 상태 확인
    4. 이미지 풀 시간과 애플리케이션 startup probe 확인
    kubectl describe pod <pending-pod-name>
    kubectl logs -n karpenter deploy/karpenter
    kubectl get daemonsets -A
    kubectl describe node <new-node-name>

    💡 팁 하나 드리면, 노드 확장 속도 테스트를 할 때는 테스트용 이미지 크기를 최대한 줄이세요. 이미지가 크면 오토스케일링 성능이 아니라 레지스트리/네트워크 성능을 재게 됩니다.

    검증과 결과 해석: 숫자보다 흐름을 보세요

    이제 결과를 봐야죠. 다만 여기서 조심할 게 있습니다. 제가 일부러 구체 벤치마크 수치를 박지 않는 이유는, 인스턴스 타입, 네트워크, 이미지 캐시, CNI 구성에 따라 결과가 너무 달라지기 때문입니다. 대신 재현 가능한 관찰 포맷을 드리는 게 더 낫습니다.

    측정 항목 어떻게 확인하나 좋게 봐야 할 신호
    Pending 해소 시간 pod 상태 변화, 이벤트 타임라인 증가 구간이 짧고 일관적임
    노드 준비 시간 node Ready 시점 워크로드 증가 시 급격한 편차가 적음
    자원 밀집도 node/pod 사용률 비교 유휴 자원이 과도하게 남지 않음
    축소 안정성 scale-in 이후 재스케줄 상태 서비스 영향 없이 정리됨

    실제로 써보니까, Kubernetes 오토스케일링 성능은 “최대 속도”보다 “예상 가능한 속도”가 더 중요했습니다. 갑자기 한 번 엄청 빠른 결과가 나오는 것보다, 비슷한 조건에서 비슷하게 반응하는 쪽이 운영에는 훨씬 유리하거든요.

    Karpenter 성능 벤치마크 결과로 워크로드별 Pending Pod와 노드 준비 시간을 비교한 이미지

    Burst, Batch, Steady 워크로드별로 Pending Pod 해소 흐름과 노드 Ready 전환을 비교한 대시보드 이미지입니다.

    비교 정리: 어떤 환경에서 Karpenter가 특히 잘 맞을까요?

    제 기준으로는 아래 환경에서 특히 체감이 좋았습니다.

    • 트래픽 변동폭이 큰 서비스
    • 짧은 배치 작업이 자주 몰리는 플랫폼
    • 팀이 노드 타입과 비용 최적화까지 같이 보고 싶은 환경

    반대로, 부하가 거의 고정이고 노드 풀도 단순한 환경이라면 체감 이점이 크지 않을 수도 있습니다. 그래서 벤치마크는 꼭 “남들도 좋다더라”가 아니라 내 워크로드 기준으로 봐야 합니다. 여기서 중요한 포인트, 다시 한 번 말씀드리면 Karpenter 성능 벤치마크는 제품 홍보 자료처럼 보면 안 됩니다. 운영 제약, 워크로드 패턴, 팀의 관측 역량이 같이 들어가야 의미가 생깁니다.

    Karpenter 성능 벤치마크와 기존 오토스케일링 방식을 비교한 요약 이미지

    프로비저닝 방식, 반응성, 자원 활용 관점에서 Karpenter와 기존 접근을 비교한 요약 인포그래픽입니다.

    자주 묻는 질문

    Karpenter만 있으면 HPA는 필요 없나요?

    아닙니다. HPA는 파드 수를 늘리고, Karpenter는 그 파드가 올라갈 노드를 준비하는 역할에 가깝습니다. 둘은 대체 관계라기보다 연동 관계로 보는 게 맞습니다.

    벤치마크에서 가장 먼저 봐야 할 로그는 뭔가요?

    Pending Pod 이벤트와 Karpenter 컨트롤러 로그입니다. 여기서 프로비저닝 판단이 있었는지 먼저 확인해야 합니다.

    비용 절감 효과도 바로 검증할 수 있나요?

    가능은 하지만, 하루 이틀 테스트로 단정하긴 어렵습니다. 최소한 여러 워크로드 패턴을 반복해보고, 축소 정책까지 함께 봐야 의미가 있습니다.

    마무리: 빠른 확장보다 중요한 건 예측 가능한 확장입니다

    이번 글에서는 Karpenter 성능 벤치마크를 워크로드 유형별로 어떻게 봐야 하는지 정리해봤습니다. 제가 직접 해보니, 제일 중요한 건 화려한 벤치마크 숫자가 아니라 Pending 발생 → 노드 생성 결정 → 노드 Ready → 서비스 가능 이 흐름을 끊김 없이 읽어내는 것이었습니다. 드디어 됐다! 싶은 순간도 있었지만, 사실 그 전에 로그 보면서 한참 헤맨 적도 많았거든요.

    정리하자면 이렇습니다.

    • ✅ Burst, Batch, Steady 워크로드를 나눠서 봐야 합니다.
    • ✅ 노드 생성 속도만 보지 말고 이미지 풀과 DaemonSet 초기화도 같이 봐야 합니다.
    • ✅ Karpenter 효율성 검증은 절대 수치보다 일관성과 자원 활용 흐름이 중요합니다.

    다음 글에서는 HPA와 함께 붙였을 때의 상호작용, 그리고 실제 운영에서 자주 쓰는 관측 대시보드 구성도 따로 다뤄볼 예정입니다. 이전 글에서 다뤘던 클러스터 리소스 요청값 설계 내용도 같이 보시면 훨씬 이해가 쉬우실 겁니다.

  • [Kubernetes] Karpenter 프로비저닝 실패 디버깅 실제 사례 분석

    [Kubernetes] Karpenter 프로비저닝 실패 디버깅 실제 사례 분석

    [Kubernetes] Karpenter 프로비저닝 실패 디버깅 실제 사례 분석

    Karpenter 프로비저닝 실패 때문에 새 파드(Pod, 쿠버네티스에서 배포되는 최소 실행 단위)는 계속 Pending 상태인데, 오토스케일링(auto scaling, 부하에 따라 자동으로 늘고 줄어드는 기능)은 움직이지 않고, 로그만 한참 들여다보셨던 적 있으신가요? 저는 홈랩이든 업무 환경이든 이런 상황을 몇 번 겪었는데요. 처음엔 “분명 노드가 부족한데 왜 안 뜨지?” 싶었고, 실제로는 Karpenter 설정 자체보다 IAM(Role, 권한 역할), 서브넷(Subnet, 네트워크 구간) 태그, 인스턴스 제약 조건 같은 바깥 조건에서 막히는 경우가 많더라고요. 이번 글에서는 제가 실제로 디버깅할 때 밟는 순서대로, Karpenter 프로비저닝 실패를 어떻게 좁혀가는지 정리해보겠습니다.

    특히 Kubernetes 노드 문제 해결이나 Karpenter 디버깅이 익숙하지 않은 분들은, 무작정 로그부터 보는 것보다 “스케줄링 실패 → 프로비저닝 판단 → 클라우드 리소스 생성 → 노드 조인(join, 클러스터 참여)” 이 흐름으로 보면 훨씬 덜 헷갈리실 거예요. 저도 처음엔 이걸 한 덩어리로 봐서 삽질 좀 했습니다 ㅎㅎ

    Karpenter 프로비저닝 실패 흐름을 보여주는 Kubernetes 아키텍처 다이어그램

    Karpenter 프로비저닝 실패가 어느 단계에서 발생하는지 한눈에 보여주는 개요 이미지입니다.

    Karpenter 프로비저닝 실패를 볼 때 먼저 이해해야 할 흐름

    쉽게 말해 Karpenter는 “스케줄되지 못한 워크로드가 있네? 그럼 조건에 맞는 노드를 하나 띄워보자” 하고 판단하는 컴포넌트입니다. 여기서 중요한 포인트는, 단순히 노드를 많이 만드는 도구가 아니라 스케줄링 제약 조건을 해석해서 그에 맞는 노드를 계산한다는 점입니다.

    문제가 나는 지점은 보통 4군데입니다

    1. 파드 스케줄링 조건이 너무 빡빡한 경우
      예: nodeSelector, affinity, toleration, resource request가 충돌합니다.
    2. Karpenter가 적절한 노드 후보를 계산하지 못하는 경우
      예: 인스턴스 타입 제약, capacity type, 아키텍처 제약이 과합니다.
    3. 클라우드 리소스 생성 단계에서 실패하는 경우
      예: IAM 권한, 보안 그룹(Security Group), 서브넷 태그, 용량 부족 등이 걸립니다.
    4. 노드는 생성됐지만 클러스터에 조인하지 못하는 경우
      예: 부트스트랩(user data), 인증, 네트워크 경로 문제가 있습니다.

    이 네 구간만 분리해서 봐도 디버깅 난이도가 확 내려갑니다. 실제로 써보니까 “Karpenter가 고장났다”기보다는, 주변 조건 하나가 꼬여서 연쇄적으로 실패하는 경우가 더 많았습니다.

    Kubernetes 노드 문제 해결을 위한 기본 점검 순서

    제가 현장에서 가장 먼저 하는 건 거창한 분석이 아니라 증상 분리입니다. 파드가 문제인지, Karpenter 컨트롤러(controller, 제어 루프) 판단이 문제인지, 아니면 클라우드 API 호출이 막히는지부터 확인해야 하거든요.

    1. Pending 파드부터 확인

    kubectl get pods -A
    kubectl describe pod <pod-name> -n <namespace>

    여기서 꼭 보는 건 Events입니다. 예를 들어 다음 같은 메시지가 보이면 방향이 잡힙니다.

    • 0/3 nodes are available: 기존 노드에 스케줄이 안 되는 상황입니다.
    • Insufficient cpu 또는 Insufficient memory: 리소스 부족입니다.
    • node(s) had untolerated taint: 톨러레이션(toleration, 테인트 허용 설정) 문제입니다.
    • node affinity/selector mismatch: 파드 조건이 너무 좁습니다.

    혹시 여기서 이미 affinity나 nodeSelector 충돌이 보이면, Karpenter 이전에 워크로드 정의부터 손봐야 합니다. 이걸 놓치면 Karpenter 로그만 하루 종일 보게 되거든요.

    2. Karpenter 로그 확인

    kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=200
    kubectl get events -A --sort-by=.lastTimestamp

    로그에서는 이런 식의 단서를 찾습니다.

    • 프로비저닝 후보를 계산했는지
    • 요구사항(requirements) 때문에 제외된 인스턴스 타입이 있는지
    • 클라우드 제공자(provider) 호출에서 에러가 나는지
    • 생성 이후 노드 등록(register)이 안 되는지

    제가 직접 해보니, 로그를 길게 읽는 것보다 에러 키워드를 먼저 잡는 게 훨씬 빨랐습니다. 예를 들면 access denied, no subnets found, insufficient capacity, launch template 관련 문구 같은 것들이요.

    3. 노드 생성 여부와 조인 여부 분리

    kubectl get nodes
    kubectl get nodeclaims
    kubectl get nodepools
    kubectl describe nodeclaim <name>

    환경에 따라 리소스 이름은 다를 수 있지만, 핵심은 같습니다. 클라우드 인스턴스는 떴는데 노드가 안 보이는지, 아니면 인스턴스 자체가 생성되지 않았는지를 나눠서 봐야 합니다. 이 차이가 엄청 큽니다.

    Karpenter 디버깅을 위한 파드 이벤트와 로그 비교 이미지

    파드 이벤트와 Karpenter 로그를 같이 놓고 보면 어디서 막히는지 훨씬 빨리 찾을 수 있습니다.

    실전 사례: Karpenter 디버깅을 이렇게 풀었습니다

    이건 제가 자주 보는 전형적인 패턴입니다. 특정 워크로드가 배포된 뒤 Pending 상태가 길게 이어졌고, 클러스터 오토스케일링 오류 분석이 필요했던 상황이었습니다. 기존 노드는 여유가 없었고, Karpenter가 새 노드를 올려줘야 정상인데 아무 변화가 없었죠.

    증상

    • 파드는 Pending 상태 유지
    • Karpenter는 동작 중
    • 기존 노드는 리소스 부족
    • 새 노드는 추가되지 않음

    제가 먼저 확인한 파드 스펙

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sample-workload
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: sample-workload
      template:
        metadata:
          labels:
            app: sample-workload
        spec:
          nodeSelector:
            kubernetes.io/arch: amd64
          tolerations:
            - key: "workload-type"
              operator: "Equal"
              value: "batch"
              effect: "NoSchedule"
          containers:
            - name: app
              image: nginx:stable
              resources:
                requests:
                  cpu: "1"
                  memory: "1Gi"

    겉으로 보면 큰 문제 없어 보이죠. 저도 처음엔 워크로드 쪽은 괜찮다고 생각했었습니다. 근데 여기서 Karpenter 쪽 제약과 같이 봐야 했습니다.

    확인한 Karpenter 측 제약 예시

    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.sh/capacity-type
              operator: In
              values:
                - spot

    여기서 문제는 단순 설정 문법이 아니었습니다. 워크로드는 특정 톨러레이션을 요구하고, NodePool은 spot만 허용하며, 실제 가용 영역이나 계정 조건상 적절한 인스턴스 풀이 너무 좁아진 상태였던 거죠. 게다가 서브넷 태그도 일부 누락돼 있었습니다. 결국 Karpenter 입장에서는 만들 수 있는 노드 후보가 사실상 거의 없었던 셈입니다.

    정리하면 원인은 두 개였습니다

    구분 문제 내용 영향
    네트워크 일부 서브넷 태그 누락 적절한 서브넷 탐색 실패
    용량 제약 spot만 허용하고 요구사항이 좁음 프로비저닝 후보 부족

    이런 경우 진짜 헷갈립니다. 로그에는 하나의 에러만 크게 보일 때도 있지만, 실제론 조건이 두세 개 겹쳐서 실패하는 경우가 많거든요.

    제가 적용했던 점검 명령어와 확인 포인트

    아래 명령어들은 특정 클라우드에 종속되지 않게, 쿠버네티스 관점에서 먼저 확인할 수 있는 것들입니다. 이 순서대로 보면 꽤 안정적으로 원인을 줄여갈 수 있었습니다.

    1. 파드 이벤트 확인
    2. Karpenter 로그에서 프로비저닝 시도 여부 확인
    3. NodePool/NodeClaim 조건 확인
    4. 생성된 노드 존재 여부 확인
    5. 클라우드 콘솔 또는 API에서 인스턴스 생성 실패 원인 확인
    kubectl describe pod <pod-name> -n <namespace>
    kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --since=30m
    kubectl get nodepools -o yaml
    kubectl get nodeclaims -o yaml
    kubectl get nodes -o wide

    여기서 중요한 포인트! Karpenter 디버깅은 “쿠버네티스 안쪽 정보”와 “클라우드 바깥 정보”를 같이 봐야 끝이 납니다. 쿠버네티스 쪽만 보면 “왜 안 되지?”에서 끝나고, 클라우드 쪽만 보면 “인스턴스가 왜 안 뜨지?”로 끝나더라고요.

    Karpenter 프로비저닝 실패 원인인 NodePool 제약 조건 다이어그램

    NodePool 제약 조건이 겹칠수록 프로비저닝 가능한 후보가 급격히 줄어드는 상황을 설명하는 이미지입니다.

    ⚠️ 실제로 많이 만나는 Karpenter 프로비저닝 실패 원인

    이 섹션은 제가 여러 번 겪으면서 “아, 이건 체크리스트로 빼야겠다” 싶었던 것들입니다. 운영 중인 분들이라면 특히 공감하실 거예요.

    1. 파드 요구사항이 너무 강한 경우

    • nodeSelector가 특정 아키텍처나 라벨만 강제
    • podAntiAffinity가 과도하게 설정됨
    • requests가 커서 맞는 노드가 매우 제한적
    • toleration이 없어서 tainted node에 못 올라감

    처음엔 Karpenter가 노드를 안 만드는 줄 알았는데, 실제론 만들어도 스케줄이 안 맞는 상태라 계산상 제외되는 경우가 있었습니다.

    2. NodePool 요구사항이 좁은 경우

    • 특정 인스턴스 계열만 허용
    • spot만 허용
    • 특정 zone만 허용
    • 아키텍처와 운영체제 조건이 불필요하게 제한됨

    이건 비용 최적화하려다 자주 생깁니다. 저도 spot 위주로 예쁘게 짜보려다가, 정작 트래픽이 몰릴 때 후보가 없어서 자동 확장이 안 된 적이 있었거든요. 그래서 운영 환경에서는 제약은 최소화하고, 꼭 필요한 정책만 남기는 쪽이 더 안전했습니다.

    3. 클라우드 리소스 검색 실패

    • 서브넷 태그 누락
    • 보안 그룹 태그 누락
    • IAM 권한 부족

    이 부분은 쿠버네티스 YAML만 아무리 봐도 안 나옵니다. 특히 태그 기반 디스커버리(discovery, 자동 탐색)를 쓰는 경우라면, 네트워크 리소스가 올바르게 식별되는지 꼭 확인하셔야 합니다.

    4. 노드 생성 후 조인 실패

    • 부트스트랩 스크립트 문제
    • 클러스터 API 엔드포인트 접근 문제
    • 노드 인증 관련 설정 누락

    이 경우는 인스턴스는 생성되는데 kubectl get nodes에 안 보입니다. 그래서 “Karpenter가 아무것도 안 했다”고 오해하기 쉬워요. 근데 사실 한 단계는 진행된 상태인 거죠.

    문제를 줄이기 위한 운영 팁

    실제로 써보니까 아래 네 가지가 제일 효과 있었습니다.

    • NodePool 제약을 처음부터 너무 촘촘하게 만들지 않기
    • 파드 requests/limits를 현실적으로 설정하기
    • 서브넷, 보안 그룹, IAM 같은 외부 조건을 체크리스트화하기
    • 이벤트와 로그를 함께 보기

    특히 Kubernetes 노드 문제 해결이 필요한 상황에서 오토스케일링 오류 분석은 재현이 어려운 경우가 많아서, 문제가 생겼을 때 바로 볼 수 있는 로그 수집 체계를 미리 만들어두는 게 좋습니다. 이전 글에서 다뤘던 클러스터 이벤트 정리 방식이 있다면 같이 묶어보셔도 좋고요. 이 부분은 다음 글에서 더 깊게 다뤄볼 예정입니다.

    검증: 수정 후 무엇을 확인했나

    저는 설정을 손본 뒤 항상 세 가지를 확인합니다. 단순히 노드가 떠도 끝이 아니거든요.

    1. Pending 파드가 실제로 Running으로 전환되는지
    2. 새 노드가 기대한 라벨과 용량으로 조인되는지
    3. 불필요하게 과한 스케일아웃이 일어나지 않는지
    kubectl get pods -A -w
    kubectl get nodes --show-labels
    kubectl top nodes

    문제가 해결되면 보통 흐름이 이렇게 바뀝니다.

    • Pending 파드 발생
    • Karpenter가 새 노드 계산 및 생성
    • 노드가 클러스터에 조인
    • 파드가 새 노드에 스케줄됨

    드디어 됐다! 하는 순간이 여기입니다. 특히 오래 Pending이던 워크로드가 새 노드에 바로 올라가는 걸 보면, 이거 진짜 편하더라고요. 다만 성공했다고 끝내지 말고, 왜 실패했는지 기록해두는 게 다음 장애 때 훨씬 큰 도움이 됩니다.

    Karpenter 프로비저닝 실패 해결 후 Pending 파드가 Running으로 전환된 결과 이미지

    수정 전후를 비교해 Pending 파드가 Running으로 바뀌는 결과 확인 이미지입니다.

    정리: Karpenter 디버깅은 순서가 전부입니다

    Karpenter 프로비저닝 실패를 잡을 때 가장 중요한 건, 증상을 한 번에 해석하려고 하지 않는 겁니다. 저도 처음엔 로그 한 줄에서 답을 찾으려 했는데, 실제론 파드 조건, NodePool 제약, 클라우드 리소스 검색, 노드 조인 이 네 단계를 분리해서 봐야 풀리더라고요.

    혹시 지금도 Kubernetes 노드 문제 해결 때문에 막혀 계시다면, 아래 체크리스트부터 차근차근 보시면 좋겠습니다.

    • 파드 Events에 스케줄링 실패 원인이 보이는가
    • Karpenter가 실제로 프로비저닝을 시도했는가
    • NodePool 제약이 과하지 않은가
    • 서브넷, 보안 그룹, IAM 조건이 맞는가
    • 생성된 노드가 클러스터에 정상 조인하는가

    이 순서만 익혀도 Karpenter 디버깅 속도가 꽤 빨라집니다. 저도 처음엔 헷갈렸는데, 몇 번 정리해두고 나니 장애 대응 시간이 확 줄었습니다. 마무리 전에 요약 하나 더 남겨볼게요.

    Karpenter 프로비저닝 실패 디버깅 체크리스트 인포그래픽

    Karpenter 프로비저닝 실패 원인과 대응 순서를 한 장으로 정리한 요약 이미지입니다.

    FAQ: 자주 헷갈리는 포인트

    Q1. 파드가 Pending이면 무조건 Karpenter 문제인가요?

    아닙니다. 파드 스펙의 affinity, toleration, resource request가 원인인 경우도 많습니다.

    Q2. 노드가 생성되지 않으면 어디부터 봐야 하나요?

    파드 이벤트, Karpenter 로그, NodePool 조건을 먼저 보고, 그 다음 클라우드 리소스 조건을 확인하는 게 좋습니다.

    Q3. spot만 허용해도 괜찮을까요?

    가능은 하지만, 워크로드 특성과 가용성 요구사항을 고려해야 합니다. 운영에선 제약을 너무 좁히면 Karpenter 프로비저닝 실패 가능성이 올라가더라고요.

    마무리

    이번 글은 특정 버전 문법을 깊게 파기보다는, 문제가 났을 때 어떤 순서로 생각해야 하는지에 집중해봤습니다. 사실 운영에서는 정답 YAML 하나보다, 실패를 분해해서 보는 관점이 더 오래 가거든요. 다음 글에서는 Karpenter와 Cluster Autoscaler를 어떤 기준으로 나눠서 볼지, 그리고 워크로드별로 어떤 제약을 어디까지 허용할지 이어서 정리해보겠습니다.

  • [K8s] Karpenter 온프레미스 도입의 효율성 검증 및 실전 분석

    [K8s] Karpenter 온프레미스 도입의 효율성 검증 및 실전 분석

    [K8s] Karpenter 온프레미스 도입의 효율성 검증 및 실전 분석

    Karpenter 온프레미스가 얼마나 효율적일까—이 질문은 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션)를 운영하다 보면 자연스럽게 나오더라고요. 노드(Node, 작업이 실제로 돌아가는 서버)까지 더 똑똑하게 자동으로 늘리고 줄일 수 없을까 하는 생각 말입니다. 저도 홈랩과 실무에서 이런 고민을 꽤 했었어요. 처음엔 이게 뭔가 싶었는데, 막상 파고들어 보니 Karpenter는 분명 매력적이면서도, 온프레미스에서는 생각보다 전제 조건이 많더라고요.

    결론부터 말씀드리면, Karpenter 온프레미스는 “기술적으로 이름만 가져와서 붙이는 문제”가 아니라 서버 수명주기(lifecycle), 네트워크, IP 주소 할당, OS 부팅 자동화까지 전부 자동화돼 있어야 진정한 효율이 나오는 구조입니다. 이 글에서는 제가 왜 그렇게 판단했는지, 어떤 식으로 검증했고 어디서 막혔는지, 그리고 어떤 팀에 맞고 어떤 팀에는 안 맞는지 정리해보겠습니다.

    Karpenter 온프레미스 도입 검토를 위한 전체 아키텍처 개요 이미지

    온프레미스 쿠버네티스 클러스터에서 스케줄러, Pending Pod, 노드 프로비저닝 흐름을 한눈에 보여주는 개요 이미지가 들어갈 자리입니다.

    Karpenter란 무엇인가, 쉽게 말해

    쉽게 말해 Karpenter는 스케줄링되지 못한 파드(Pod, 쿠버네티스의 실행 단위)를 보고, 거기에 맞는 노드를 빠르게 만들어 붙이는 데 초점을 둔 도구입니다. 기존 Cluster Autoscaler가 노드 그룹(Node Group) 중심으로 움직였다면, Karpenter는 워크로드 요구사항 중심으로 더 유연하게 판단하는 느낌이 훨씬 강하죠.

    공식 문서 기준으로 많이 다뤄지는 프로바이더(provider, 인프라 연동 구현체)는 AWS, Azure, Alibaba Cloud 쪽입니다. 여기서 중요한 포인트! Karpenter의 핵심 아이디어는 범용적이지만, 실제 노드를 만들어내는 동작은 프로바이더 구현에 크게 의존합니다. 즉, 온프레미스에서 “Karpenter를 쓴다”는 말은 결국 내 환경에 맞는 프로비저닝 백엔드를 어떻게 연결하느냐의 문제거든요.

    Karpenter 온프레미스가 매력적으로 보이는 이유

    솔직히 말해서, 아이디어만 보면 정말 좋습니다. 저도 처음엔 “이거 진짜 편하겠는데?” 싶었거든요.

    • 빈 노드 상시 대기를 줄일 수 있습니다.
    • 워크로드별로 CPU, 메모리, 아키텍처, taint/toleration 조건을 세밀하게 나눌 수 있습니다.
    • 배치 작업(Batch Job)이나 CI 러너(Runner)처럼 들쭉날쭉한 부하에 정말 잘 맞습니다.
    • Karpenter 효율성 관점에서 보면, 과하게 큰 노드를 오래 유지하지 않아도 된다는 기대가 생기죠.

    특히 온프레미스는 클라우드처럼 분 단위 과금은 아니어도, 전력, 랙 공간, 라이선스, 예비 장비 운용비가 은근히 큽니다. 그래서 온프레미스 오토스케일링에 관심이 가는 건 아주 자연스러운 흐름이에요.

    하지만 왜 온프레미스에서는 바로 풀리지 않나

    여기서부터 현실이 시작됩니다. 퍼블릭 클라우드에서는 API 한 번으로 가상머신(VM, 가상 서버)을 만들고, 네트워크도 붙이고, IAM 권한도 맞추고, 부팅 후 조인(join)까지 이어집니다. 반면 온프레미스는 보통 이게 한 덩어리로 묶여 있지 않습니다. 제가 직접 검토해보니, 진짜 어려운 건 Karpenter 자체보다 주변 자동화의 빈틈이더라고요.

    항목 퍼블릭 클라우드 온프레미스
    서버 생성 API로 즉시 생성 가상화 플랫폼 또는 베어메탈 절차 연동 필요
    네트워크 기본 VPC/서브넷 모델 존재 VLAN, IPAM, DHCP, DNS를 따로 맞춰야 함
    부팅/조인 이미지와 userdata로 표준화 PXE, 템플릿, 클라우드이닛 대체 절차 필요
    폐기 종료 API로 간단 디스크 정리, 모니터링 해제, CMDB 반영까지 고려
    확장 속도 대체로 빠름 사내 자동화 성숙도에 따라 편차 큼

    그래서 Karpenter 사용 후기를 물어보면, 저는 항상 이렇게 답합니다. “노드를 만드는 API가 이미 예쁘게 정리된 팀이라면 검토할 만하고, 아니면 Karpenter보다 선행 과제가 더 큽니다.”

    실전 구현: 제가 검증할 때 먼저 했던 순서

    저는 Karpenter를 바로 설치해서 결론 내리지 않았습니다. 먼저 정말 노드 자동 확장이 필요한 패턴인지부터 확인했거든요. 이거 안 하고 시작하면 높은 확률로 삽질하게 됩니다.

    1. Pending Pod가 얼마나 자주 생기는지 확인합니다.
    2. 그 Pending이 CPU/메모리 부족인지, taint/toleration 문제인지 구분합니다.
    3. 노드 추가 시간이 실제 서비스 요구와 맞는지 봅니다.
    4. 온프레미스에서 노드 생성 API가 있는지, 없으면 누가 그 역할을 할지 정합니다.

    1. 병목부터 확인하기

    kubectl get pods -A --field-selector=status.phase=Pending
    kubectl get events -A --sort-by=.lastTimestamp
    kubectl describe pod -n default cpu-burner-0
    kubectl top nodes
    

    이 단계에서 FailedScheduling 이벤트를 꼭 봐야 합니다. 실제로 써보니까 “노드가 부족해서”가 아니라 노드 셀렉터(nodeSelector), 어피니티(affinity), 테인트(taint) 때문에 못 붙는 경우가 꽤 많더라고요.

    2. 의도적으로 스케줄 압박 만들기

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: cpu-burner
      namespace: default
    spec:
      replicas: 6
      selector:
        matchLabels:
          app: cpu-burner
      template:
        metadata:
          labels:
            app: cpu-burner
        spec:
          containers:
            - name: pause
              image: registry.k8s.io/pause:3.9
              resources:
                requests:
                  cpu: "4"
                  memory: "4Gi"
                limits:
                  cpu: "4"
                  memory: "4Gi"
    

    이런 식으로 일부러 Pending 상황을 만들고, 노드가 추가됐을 때 실제로 대기 시간이 얼마나 줄어드는지 봤습니다. 이 단계가 없으면 Karpenter 효율성을 논하기가 정말 어렵거든요.

    Karpenter 온프레미스 환경에서 Pending Pod와 노드 증설 흐름을 보여주는 구성 이미지

    Pending Pod 감지, 스케줄러 판단, 노드 프로비저닝 요청 흐름을 시각화한 구성 다이어그램이 들어갈 자리입니다.

    3. 지원 환경에서 기준선 만들기

    여기서 중요한 포인트가 하나 더 있습니다. Karpenter 동작 모델을 이해하려면, 우선 공식적으로 많이 다뤄지는 환경에서 기준선을 봐야 한다는 거예요. 예를 들어 AWS 계열에서는 NodePool, NodeClass 같은 리소스로 요구사항을 선언합니다. 온프레미스에서는 이 선언형 모델을 흉내 내더라도, 실제 서버 생성과 회수는 별도 자동화가 뒷받침돼야 합니다.

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: general
    spec:
      template:
        spec:
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
          expireAfter: 720h
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
    

    이 YAML 자체보다 중요한 건, 온프레미스에서는 이 선언을 받아줄 실제 인프라 제어면(control plane)이 있느냐입니다. VMware vSphere, OpenStack, Proxmox, 베어메탈 PXE 자동화, BMC 연동 같은 것들이 이미 정리돼 있어야 이야기가 될 수 있거든요.

    ⚠️ 트러블슈팅: 제가 막혔던 지점들

    여기는 정말 경험상 중요합니다. 문서만 보면 쉬워 보이는데, 실제 운영에서는 대부분 여기서 시간이 날아갑니다.

    1. 노드 생성보다 노드 조인이 더 오래 걸림

    VM이 떠도 kubelet(큐블릿, 노드를 클러스터에 붙이는 에이전트) 조인이 늦으면 의미가 없습니다. 이미지 템플릿, 컨테이너 런타임, CNI(Container Network Interface, 컨테이너 네트워크), 인증서 부트스트랩이 전부 맞아야 하거든요.

    2. IPAM이 병목이 됨

    온프레미스 오토스케일링에서 자주 빠지는 함정입니다. 서버는 생겼는데 IP가 없거나, DNS 등록이 늦거나, 방화벽 정책 반영이 늦으면 결국 Pod는 계속 Pending 상태가 됩니다. 드디어 됐다! 싶었는데 네트워크 쪽에서 막히는 경우, 진짜 많습니다.

    3. 회수(deprovision) 정책이 더 민감함

    클라우드에서는 종료가 비교적 단순하지만, 온프레미스는 로그 수집, 백업, 모니터링 타깃 해제, 자산관리 반영까지 연결될 수 있습니다. 그래서 비용 최적화보다 운영 일관성이 더 우선인 팀도 많아요.

    4. 베어메탈은 더 신중해야 함

    가상머신은 그래도 템플릿 자동화가 익숙한 편인데, 물리 서버는 전원 제어, PXE, 디스크 초기화, 펌웨어 상태까지 섞입니다. 여기서는 Karpenter보다 먼저 서버 프로비저닝 파이프라인이 안정적이어야 해요.

    검증 결과: 그래서 효율적이었나

    제 결론은 꽤 단순합니다. Karpenter 온프레미스는 “이론상 가능”과 “운영상 효율적” 사이의 간격이 생각보다 크다는 거예요.

    • 효율적인 경우: VM 생성 API가 있고, 네트워크 자동화가 있으며, 노드 조인 시간이 짧고, 워크로드 변동성이 큽니다.
    • 비효율적인 경우: 서버 준비 시간이 길고, 수동 승인 절차가 많고, IP/DNS/보안정책 반영이 느립니다.
    • 애매한 경우: 항상 켜둬야 하는 기본 용량(base capacity)이 큰데, 추가 증설 빈도는 낮은 환경입니다.

    실제로 써보니까 Karpenter 자체는 정말 똑똑한데, 온프레미스에선 그 똑똑함이 발휘될 무대가 준비돼 있어야 하더라고요. 무대가 없으면 결국 “자동화된 척하는 수동 시스템”이 되기 쉽습니다.

    Karpenter 온프레미스 검증 결과를 보여주는 대시보드 이미지

    PoC 전후로 Pending Pod 수, 노드 사용률, 증설 후 조인 시간을 비교하는 대시보드 이미지가 들어갈 자리입니다.

    대안과 비교: 굳이 Karpenter여야 하나

    이 질문도 꼭 해봐야 합니다. 저도 처음엔 Karpenter에 시선이 갔는데, 나중엔 오히려 문제 정의를 다시 하는 게 더 중요하다고 느꼈거든요.

    선택지 잘 맞는 상황 한계
    Karpenter 인프라 API와 노드 자동화가 이미 성숙한 환경 주변 자동화 미성숙 시 효과 반감
    Cluster Autoscaler 기존 노드 그룹 운영이 익숙한 환경 세밀한 유연성은 상대적으로 제한
    고정 노드 + 요청/제한 재정비 부하 패턴이 예측 가능한 환경 유휴 자원 증가 가능
    배치 전용 풀 분리 CI, 렌더링, 분석 잡이 분리 가능한 환경 운영 구조가 다소 복잡해짐

    혹시 이런 경험 있으신가요? 노드가 부족한 줄 알았는데, 알고 보니 리소스 요청값(request)이 너무 공격적으로 잡혀 있었던 경우요. 이런 환경이면 Karpenter보다 먼저 requests/limits, affinity, PDB(PodDisruptionBudget, 중단 허용 범위)를 정리하는 쪽이 체감 효과가 훨씬 크더라고요.

    정리: 제가 내린 판단

    Karpenter 온프레미스는 분명 흥미로운 주제입니다. 다만 “쿠버네티스 노드 오토스케일링 도구 하나 더 설치하면 끝” 같은 그림으로 접근하면 거의 실패합니다. 제가 회고해보면, 성공 조건은 세 가지였어요.

    1. 노드 생성 API가 일관돼 있어야 합니다.
    2. 네트워크/IPAM/보안 정책 반영이 자동화돼 있어야 합니다.
    3. 노드 조인 시간이 서비스 요구를 만족해야 합니다.

    이 세 가지가 되면 Karpenter 효율성은 충분히 검토할 가치가 있습니다. 반대로 이 중 둘 이상이 수동이면, Karpenter는 멋진 이름이고 운영은 더 복잡해질 가능성이 크거든요. 그래서 저는 온프레미스에서 Karpenter를 볼 때 항상 도구보다 자동화 성숙도를 먼저 봅니다.

    Karpenter 온프레미스 도입 적합성과 대안을 정리한 요약 인포그래픽

    Karpenter 도입 적합성 체크리스트와 대안 선택 기준을 한 장으로 요약한 인포그래픽이 들어갈 자리입니다.

    FAQ와 다음 단계

    Q1. 온프레미스에서 Karpenter를 아예 못 쓰는 건가요?

    아예 불가능하다는 뜻은 아닙니다. 다만 공식적으로 널리 소개되는 흐름은 주로 클라우드 프로바이더 중심이고, 온프레미스에서는 프로비저닝 연동을 직접 설계해야 하는 비중이 훨씬 커요.

    Q2. 어떤 팀에 추천하나요?

    가상화 API, 템플릿 배포, DHCP/DNS/IPAM, 쿠버네티스 조인이 이미 자동화된 팀이라면 충분히 검토할 만합니다.

    Q3. 어떤 팀에는 비추천인가요?

    노드 한 대 늘릴 때 승인 절차가 여러 단계거나, 보안 정책 반영이 수동이면 비추천입니다. 이 경우엔 고정 노드 전략과 워크로드 정리가 훨씬 더 현실적이거든요.

    이전 글에서 다뤘던 리소스 요청값 튜닝과 스케줄링 정책 정리도 같이 보시면 정말 도움이 됩니다. 다음 글에서는 온프레미스 오토스케일링 관점에서 Karpenter 대신 어떤 선택지가 더 실용적인지, Cluster Autoscaler와 고정 노드 전략을 비교해볼 예정입니다.

    참고할 공식 자료

  • [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년 운영 후 배운 점, 추천 도입 순서, 주의할 설정 포인트를 요약한 마무리 이미지입니다.