13년차의 서버실

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

[태그:] 오토스케일링

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

  • [인프라] Knative 성능 벤치마크, 실제 트래픽 증가에 따른 서빙 체크 포인트

    [인프라] Knative 성능 벤치마크, 실제 트래픽 증가에 따른 서빙 체크 포인트

    [인프라] Knative 성능 벤치마크, 실제 트래픽 증가에 따른 서빙 체크 포인트

    Knative 성능 벤치마크를 직접 해보려는 분들은 대개 비슷한 고민을 하더라고요. 평소엔 조용한 서비스인데, 특정 시간에 요청이 몰리면 Knative Serving이 어디까지 버텨주는지, 그리고 서버리스(Serverless)의 자동 확장이 정말 실전에 맞게 움직이는지 확인하고 싶은 거죠. 저도 홈랩에서 처음 테스트했을 때는 “오토스케일링이 알아서 되겠지” 하고 가볍게 봤다가, 콜드 스타트와 동시성 때문에 삽질 좀 했습니다 ㅎㅎ

    특히 운영 입장에서 중요한 건 숫자 하나가 아니라 트래픽 증가 구간에서 어떤 컴포넌트가 먼저 흔들리는지를 아는 겁니다. CPU가 먼저 차는지, Activator가 병목이 되는지, Ingress 레이어에서 지연이 생기는지 봐야 하거든요. 이번 글에서는 제가 실제로 자주 쓰는 방식대로, 과장된 수치 없이 Knative 성능 벤치마크를 어떻게 설계하고 해석하면 되는지 정리해보겠습니다.

    Knative 성능 벤치마크를 위한 Knative Serving 아키텍처와 트래픽 흐름 다이어그램

    Knative Serving의 요청 흐름과 오토스케일링 관련 컴포넌트를 한눈에 보는 구성도입니다.

    1. 왜 Knative Serving 성능 테스트가 까다로운가

    쉽게 말해 Knative는 단순한 Deployment 하나를 때리는 구조가 아닙니다. 요청은 보통 Route, Revision, Queue-Proxy, 그리고 상황에 따라 Activator를 거치게 됩니다. 그래서 같은 애플리케이션 코드를 올려도 일반 Kubernetes Deployment와 응답 패턴이 꽤 다르게 나옵니다.

    • Scale to Zero: 유휴 상태에선 파드를 0으로 줄였다가 다시 띄웁니다.
    • Concurrency 기반 확장: CPU만 보는 게 아니라 요청 동시성도 핵심 기준입니다.
    • Revision 단위 배포: 새 설정이 들어가면 새 리비전이 생기므로 비교 테스트가 편합니다.
    • 네트워크 경로 증가: 인그레스와 프록시 홉이 늘어나면 지연 해석이 달라집니다.

    여기서 중요한 포인트! Knative 성능 벤치마크는 “최대 RPS가 얼마냐”만 보는 테스트가 아닙니다. 트래픽이 늘어날 때 응답 시간이 어떻게 변하는지, 그리고 자동 확장이 몇 초 안에 따라붙는지를 함께 봐야 의미가 있습니다.

    2. 벤치마크 전에 잡아야 할 기준

    제가 직접 해보니, 테스트 전에 기준을 안 잡으면 결과가 거의 쓸모가 없더라고요. 처음엔 저도 그냥 부하 도구부터 돌렸었는데, 나중에 보니까 어떤 값이 애플리케이션 때문인지, 어떤 값이 플랫폼 때문인지 분리가 안 되더라고요.

    2-1. 최소한 이 네 가지는 고정하세요

    1. 애플리케이션 응답 형태: CPU 바운드인지, I/O 바운드인지 구분합니다.
    2. 동시성 목표: containerConcurrency를 명시할지 결정합니다.
    3. 오토스케일 기준: target concurrency, min/max scale 범위를 정합니다.
    4. 측정 구간: 콜드 스타트 포함 여부와 워밍 상태를 분리합니다.

    실무에서는 보통 두 번 봅니다. 한 번은 콜드 스타트 포함 시나리오, 한 번은 이미 파드가 떠 있는 상태죠. 이 둘을 섞으면 해석이 틀어집니다.

    2-2. 어떤 지표를 보면 되나

    지표 왜 중요한가 해석 팁
    RPS/Throughput 전체 처리량 확인 상승하다가 평평해지면 병목 가능성이 큽니다.
    P95, P99 Latency 꼬리 지연 확인 평균보다 훨씬 중요할 때가 많습니다.
    Pod Count 확장 반응 확인 요청 증가와 함께 자연스럽게 늘어나는지 봅니다.
    Error Rate 품질 저하 감지 5xx가 생기면 네트워크/백엔드 구간을 같이 확인합니다.

    3. 실전용 테스트 환경 구성

    이 글에서는 가장 단순한 HTTP echo 계열 서비스 대신, 약간의 대기 시간을 줄 수 있는 애플리케이션을 두고 확인하는 방식을 권장합니다. 이유는 간단합니다. 너무 가벼운 앱은 네트워크 오버헤드만 보이고, 너무 무거운 앱은 앱 병목만 보여서 Knative 특성이 잘 안 드러나거든요.

    아래 예시는 Knative Service 리소스입니다. 실제 테스트에선 여러분이 보유한 검증된 컨테이너 이미지를 넣으시면 됩니다.

    apiVersion: serving.knative.dev/v1
    kind: Service
    metadata:
      name: benchmark-app
    spec:
      template:
        metadata:
          annotations:
            autoscaling.knative.dev/min-scale: "0"
            autoscaling.knative.dev/max-scale: "10"
            autoscaling.knative.dev/target: "10"
        spec:
          containerConcurrency: 10
          containers:
            - image: gcr.io/knative-samples/helloworld-go:latest
              ports:
                - containerPort: 8080
              env:
                - name: TARGET
                  value: "knative-benchmark"

    배포는 일반적인 kubectl 흐름으로 진행하면 됩니다.

    kubectl apply -f knative-service.yaml
    kubectl get ksvc benchmark-app
    kubectl get revision
    kubectl get pods -w

    테스트 전에는 라우트 URL을 먼저 확인해 두세요.

    kubectl get ksvc benchmark-app -o jsonpath='{.status.url}'
    Knative 성능 벤치마크 설정에서 오토스케일링 파라미터를 설명하는 구성 이미지

    서비스 정의에서 동시성, 최소/최대 스케일, 타깃 값을 어떻게 잡는지 보여주는 이미지입니다.

    4. 트래픽 증가 시나리오를 이렇게 나누면 해석이 쉬워집니다

    제가 실제로 써보니까 한 번에 큰 부하를 주는 것보다, 단계형(step load)으로 올리는 편이 훨씬 유용했습니다. 트래픽 증가 구간에서 어느 시점부터 지연이 튀는지 보여주기 좋거든요.

    1. 기본 응답 확인: 단일 요청으로 정상 동작과 헤더를 확인합니다.
    2. 저부하 구간: 낮은 동시성으로 워밍 상태 지연을 봅니다.
    3. 중간 부하 구간: 오토스케일이 개입하기 시작하는지 봅니다.
    4. 고부하 구간: P95/P99 지연과 에러율 변화를 봅니다.
    5. 유휴 복귀: 다시 요청을 끊고 scale to zero 동작을 확인합니다.

    간단한 부하 테스트는 hey 같은 도구로도 충분합니다. 특별히 복잡한 시나리오가 아니라면 시작은 이걸로 해보세요.

    URL=$(kubectl get ksvc benchmark-app -o jsonpath='{.status.url}')
    hey -z 30s -c 10 "$URL"
    hey -z 30s -c 30 "$URL"
    hey -z 30s -c 50 "$URL"

    좀 더 긴 시나리오를 만들고 싶다면 k6 같은 도구를 쓰는 것도 좋습니다. 아래는 단계적으로 가상 사용자 수를 올리는 예시입니다.

    import http from 'k6/http';
    import { sleep } from 'k6';
    
    export const options = {
      stages: [
        { duration: '30s', target: 5 },
        { duration: '30s', target: 20 },
        { duration: '30s', target: 50 },
        { duration: '30s', target: 0 }
      ]
    };
    
    export default function () {
      http.get(__ENV.TARGET_URL);
      sleep(1);
    }
    k6 run -e TARGET_URL="$URL" load-test.js

    여기서 핵심은 숫자를 크게 만드는 게 아닙니다. 같은 서비스 정의로 트래픽 증가 패턴을 반복 가능하게 재현하는 게 훨씬 더 중요합니다.

    5. ⚠️ 제가 자주 겪었던 문제와 해결 방법

    이 부분이 사실 제일 중요합니다. 테스트는 돌렸는데 결과가 이상하게 튀는 경우가 많거든요. 저도 처음엔 앱 코드 문제인 줄 알았는데, 실제로는 Knative 레이어나 클러스터 리소스 설정이 원인이었던 적이 꽤 있었습니다.

    5-1. 콜드 스타트와 워밍 테스트를 섞어버린 경우

    가장 흔한 실수입니다. 첫 요청 지연이 큰데 그걸 전체 성능 저하로 오해하는 거죠. 해결은 단순합니다.

    • 콜드 스타트 측정은 유휴 상태 이후 첫 요청만 따로 봅니다.
    • 워밍 성능은 미리 몇 번 호출한 뒤 별도 구간에서 측정합니다.
    • Knative 성능 벤치마크 결과표도 두 항목으로 나누는 편이 좋습니다.

    5-2. containerConcurrency를 기본값에만 맡긴 경우

    애플리케이션이 요청당 메모리를 많이 쓰거나, 반대로 가벼운 API인데 너무 보수적으로 잡혀 있으면 확장 패턴이 왜곡됩니다. 특히 Queue-Proxy를 거치는 구조에서는 동시성 설정이 생각보다 체감에 크게 들어옵니다.

    5-3. 클러스터 노드 자원이 먼저 부족한 경우

    이건 홈랩에서 진짜 자주 나옵니다. Knative가 못 버틴 게 아니라, 노드가 새 파드를 올릴 여유가 없는 겁니다. CPU 요청량(requests)과 제한(limits), 이미지 풀링 시간, 스토리지 성능을 같이 봐야 합니다.

    5-4. Ingress 레이어의 영향

    Ingress 설정에 따라 지연 차이가 보일 수 있습니다. 그래서 가능하면 애플리케이션 로그만 보지 말고, 인그레스 컨트롤러와 Knative 관련 컨트롤 플레인 상태도 같이 체크하세요.

    kubectl get pods -A
    kubectl top pods -A
    kubectl describe ksvc benchmark-app
    kubectl logs -l serving.knative.dev/service=benchmark-app --all-containers=true
    Knative 성능 벤치마크 결과에서 파드 수 증가와 지연 시간 변화를 보는 대시보드 이미지

    트래픽 상승에 따라 파드 수와 지연 시간이 어떻게 변하는지 같이 보는 대시보드 예시입니다.

    6. 결과는 어떻게 읽어야 하나

    벤치마크 결과를 볼 때 제가 제일 먼저 보는 건 평균이 아니라 P95/P99 Latency입니다. 평균은 멀쩡한데 꼬리 지연만 확 튀는 경우가 꽤 많거든요. 사용자는 그 느린 요청을 체감합니다.

    보통 해석은 이렇게 가져가면 됩니다.

    관찰 현상 의심 지점 다음 액션
    RPS는 유지되는데 P95만 상승 오토스케일 반응 지연 또는 큐잉 증가 target concurrency와 min scale 검토
    고부하에서 5xx 발생 앱 리소스 부족, 인그레스, 백엔드 의존성 애플리케이션 로그와 노드 상태 확인
    첫 요청만 매우 느림 콜드 스타트 scale to zero 정책과 워밍 전략 분리 검토
    파드 수가 기대보다 안 늘어남 오토스케일 설정 또는 클러스터 자원 한계 autoscaler 설정과 스케줄링 상태 확인

    제가 직접 해보니 좋은 결과란 “숫자가 무조건 높은 상태”가 아니라, 트래픽 증가에 따라 파드 수와 지연이 예측 가능하게 움직이는 상태더라고요. 운영은 재현성과 설명 가능성이 정말 중요하거든요.

    7. 검증 체크리스트와 운영 관점 팁

    실전 반영 전에 아래 체크리스트를 한 번 돌려보시면 좋습니다. 여기서 중요한 포인트! Knative 성능 벤치마크는 한 번 하고 끝내는 문서가 아니라, 리비전이 바뀔 때마다 비교 기준으로 남겨야 합니다.

    • ✅ 동일한 이미지와 동일한 요청 패턴으로 반복 테스트했는가
    • ✅ 콜드 스타트와 워밍 상태를 분리했는가
    • ✅ P95/P99, 에러율, 파드 수를 함께 기록했는가
    • ✅ 노드 자원 부족과 애플리케이션 병목을 구분했는가
    • ✅ Revision 단위로 결과를 보관했는가

    추가로, 이전 글에서 다뤘던 Kubernetes 리소스 요청량 설계와 함께 보시면 훨씬 이해가 빠릅니다. 다음 글에서는 Knative Serving에서 min scale과 scale to zero를 운영 비용 관점에서 어떻게 조정하는지 이어서 다뤄볼 예정입니다.

    8. 자주 묻는 질문

    Q1. 부하 도구는 무엇을 써야 하나요?

    가볍게 시작할 땐 hey면 충분합니다. 요청 패턴을 더 세밀하게 만들고 싶으면 k6가 편하더라고요.

    Q2. Scale to Zero는 벤치마크에서 꺼야 하나요?

    목적에 따라 다릅니다. 운영 현실을 보고 싶다면 켠 상태도 꼭 측정해야 합니다. 다만 워밍 성능과 섞지는 마세요.

    Q3. 수치가 환경마다 너무 다르게 나오는데 정상인가요?

    네, 정상입니다. 클러스터 크기, 네트워크, 인그레스 종류, 이미지 크기, 앱 특성이 모두 영향을 줍니다. 그래서 절대값보다 비교 기준이 중요합니다.

    Knative 성능 벤치마크 결과 해석 체크리스트와 비교 요약 인포그래픽

    콜드 스타트, 워밍 성능, 파드 확장, 지연 구간을 한 번에 정리한 요약 인포그래픽입니다.

    9. 마무리

    이번 글에서는 실전 기준으로 Knative 성능 벤치마크를 어떻게 설계하고, 트래픽 증가 상황에서 무엇을 봐야 하는지 정리해봤습니다. 처음엔 저도 “오토스케일이 되니까 그냥 잘 되겠지” 싶었는데, 실제로 써보니까 동시성 설정, 콜드 스타트 분리, 인그레스 영향, 클러스터 자원 상태를 같이 봐야 결과가 말이 되더라고요.

    결국 중요한 건 화려한 숫자보다 반복 가능한 테스트 절차입니다. 여러분 환경에서도 먼저 작은 단계형 테스트부터 시작해 보세요. 드디어 됐다! 싶은 순간이 오면, 그다음엔 리비전 비교와 비용 최적화까지 연결하시면 됩니다. 운영 관점에서 보면 이 흐름이 제일 오래 갑니다. 혹시 비슷한 삽질 하신 적 있으신가요? 그런 경험이 오히려 제일 좋은 기준이 되더라고요.