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 효율성 향상을 위한 핵심 체크리스트 요약 인포그래픽입니다.

  • [Cloud] Spot Instance 활용 극대화: 비용 절감과 안정성 확보 전략

    [Cloud] Spot Instance 활용 극대화: 비용 절감과 안정성 확보 전략

    [클라우드] Spot Instance 활용 전략: 비용 절감과 안정성 확보

    Spot Instance 활용 전략은 클라우드 비용 절감을 고민하는 팀이라면 한 번쯤은 반드시 붙잡게 되는 주제입니다. 온디맨드(On-Demand, 필요 시 즉시 쓰는 기본 과금 방식)만으로 운영하면 편하긴 한데, 배치 작업이 늘고 개발 환경이 많아질수록 월말 청구서가 꽤 아프거든요. 저도 홈랩이랑 실제 운영 환경에서 비슷한 고민을 오래 했었습니다. 처음엔 “싸면 좋은 거 아닌가?” 하고 덤볐다가, 중간에 인스턴스가 회수(eviction, 클라우드 제공자가 자원을 다시 가져감)되면서 삽질 좀 했습니다 ㅎㅎ 그런데 구조를 조금만 바꾸니까, 비용 절감과 가용성 관리 두 마리 토끼를 꽤 안정적으로 잡을 수 있더라고요.

    이번 글에서는 특정 신제품이나 불확실한 기능 얘기 말고, 이미 널리 알려진 Spot 개념과 검증된 운영 패턴 위주로 정리해보겠습니다. 특히 클라우드 비용 절감, 탄력적 컴퓨팅, 가용성 관리 관점에서 어떤 워크로드에 Spot을 붙여야 하는지, 어디서 사고가 나는지, 그리고 어떻게 안전장치를 걸어야 하는지를 경험 기반으로 풀어볼게요.

    Spot Instance 활용 전략 기반 비용 최적화 아키텍처 개요

    Spot Instance와 On-Demand, Auto Scaling, 작업 큐, 모니터링이 연결된 전체 구성 예시입니다.

    1. 왜 Spot Instance 활용 전략이 중요한가

    쉽게 말해 Spot Instance는 “남는 자원을 할인해서 쓰는 방식”입니다. 다만 조건이 있죠. 클라우드 사업자가 해당 자원이 다시 필요해지면 인스턴스를 종료할 수 있습니다. 이 특징 때문에 아무 데나 넣으면 안 되고, 중단을 감당할 수 있는 구조에 넣어야 합니다.

    제가 직접 해보니, Spot은 단순히 인스턴스 타입 하나 바꿔서 끝나는 문제가 아니었습니다. 워크로드 분리, 상태 저장 위치, 종료 신호 처리, 용량 분산, 모니터링까지 같이 가야 비로소 전략이 되더라고요. 여기서 중요한 포인트는 딱 하나입니다. Spot을 믿지 말고, 시스템 설계를 믿어야 합니다.

    • 비용에 민감한 배치(Batch, 일괄 처리) 작업에 유리합니다.
    • 수평 확장(Horizontal Scaling, 인스턴스를 여러 대 늘리는 방식) 구조와 잘 맞습니다.
    • 상태 비저장(Stateless, 개별 서버에 중요한 상태를 남기지 않는 구조) 서비스에서 특히 강합니다.
    • 반대로 단일 노드 데이터베이스처럼 중단을 허용하기 어려운 워크로드에는 정말 조심해야 해요.

    2. Spot Instance 개념 설명: 쉽게 말해 어떤 자원인가

    Spot Instance는 할인 폭이 큰 대신, 언제든 회수될 수 있는 가변 자원입니다. AWS에서는 EC2 Spot Instances가 대표적이고, 다른 클라우드에도 비슷한 개념이 있습니다. 이름은 조금씩 달라도 핵심은 같습니다. 싸지만 보장되지 않는 자원입니다.

    저도 처음엔 헷갈렸는데, 아래처럼 구분하면 훨씬 이해가 잘 돼요.

    구분 On-Demand Reserved/Savings 계열 Spot
    비용 기본 장기 약정 시 절감 높은 할인 가능
    가용성 높음 높음 회수 가능성 있음
    적합한 워크로드 핵심 서비스 예측 가능한 상시 부하 배치, CI, 렌더링, 워커
    운영 난이도 낮음 중간 높음

    Spot Instance 활용 전략의 핵심은 “Spot만 쓰겠다”가 아니라, 기본 용량은 안정적인 방식으로 확보하고, 변동 용량만 Spot으로 가져가는 것입니다. 이 조합이 실제로 가장 덜 아픕니다.

    3. 어떤 워크로드에 Spot을 붙여야 하나

    여기서 방향을 잘못 잡으면 비용은 줄었는데 장애 대응 시간만 늘어납니다. 실제로 써보니까 아래 기준이 꽤 명확했습니다.

    잘 맞는 경우

    • 큐(Queue, 작업 대기열) 기반 워커
    • CI 빌드 에이전트
    • 로그 처리, 이미지 변환, 데이터 분석 배치
    • 오토스케일링이 잘 되는 웹 애플리케이션의 일부 용량
    • 개발/테스트 환경

    조심해야 하는 경우

    • 단일 인스턴스 데이터베이스
    • 세션(Session, 사용자 상태)이 로컬 메모리에 강하게 묶인 서비스
    • 종료 전 정리 시간이 거의 없는 실시간 처리
    • 라이선스나 초기화 시간이 긴 상용 소프트웨어

    혹시 이런 경험 있으신가요? CPU는 넉넉한데 서버 비용이 계속 오르는 상황이요. 그런 경우 대부분은 피크 부하 대응용 여유분이 상시 켜져 있는 패턴이 많습니다. 이런 부분을 Spot으로 치환하면 효과가 잘 납니다. 대신 핵심 트래픽 바닥 용량(base capacity)은 On-Demand나 예약형으로 두는 게 안전합니다.

    4. 실전 구현 1: Auto Scaling으로 Spot과 On-Demand 섞어 쓰기

    실전에서는 혼합 운영이 기본입니다. 예를 들어 웹 워커 풀(worker pool)을 10대로 운영한다고 치면, 최소 3대는 On-Demand로 깔고 나머지 7대는 Spot으로 채우는 식이죠. 이렇게 하면 Spot 회수가 와도 서비스 전체가 바로 흔들리지는 않습니다.

    1. 서비스를 상태 비저장으로 정리합니다. 세션은 Redis 같은 외부 저장소로 뺍니다.
    2. Auto Scaling Group을 구성합니다.
    3. 기본 용량은 On-Demand로 확보합니다.
    4. 추가 확장분은 여러 인스턴스 타입의 Spot으로 분산합니다.
    5. 로드 밸런서와 헬스 체크를 연결합니다.

    아래 예시는 개념 전달용 설정 예시입니다. 핵심은 여러 타입, 여러 가용 영역, 혼합 용량입니다.

    spot_strategy:
      base_on_demand_capacity: 3
      desired_capacity: 10
      spot_pools:
        - c6i.large
        - c5.large
        - m6i.large
        - m5.large
      availability_zones:
        - ap-northeast-2a
        - ap-northeast-2c
      allocation_strategy: capacity-optimized
    

    제가 처음에 했던 실수는 인스턴스 타입을 하나만 넣은 겁니다. 그랬더니 특정 타입 수급이 불안정할 때 대체가 안 되더라고요. 반면 타입과 가용 영역을 넓혀두면 Spot 회수 이벤트가 와도 전체 풀은 훨씬 덜 흔들립니다. 탄력적 컴퓨팅은 결국 선택지를 많이 주는 설계에서 나옵니다.

    aws ec2 create-launch-template \
      --launch-template-name web-spot-template \
      --launch-template-data '{
        "ImageId":"ami-xxxxxxxx",
        "InstanceType":"m5.large",
        "SecurityGroupIds":["sg-xxxxxxxx"],
        "IamInstanceProfile":{"Name":"ec2-app-role"}
      }'
    

    실무에서는 이걸 Terraform이나 CloudFormation 같은 IaC(Infrastructure as Code, 코드형 인프라)로 관리하는 편이 더 낫습니다. 사람이 콘솔에서 몇 번 누르다 보면 언젠가 drift(설정 불일치)가 생기거든요.

    Spot Instance 활용 전략을 위한 혼합 오토스케일링 구성 다이어그램

    기본 용량은 On-Demand로 유지하고, 확장분을 여러 Spot 풀로 분산하는 구성 예시입니다.

    5. 실전 구현 2: Spot 종료 신호 처리와 안전한 Drain

    Spot을 오래 쓰려면 종료 신호를 꼭 다뤄야 합니다. AWS의 경우 Spot interruption notice(중단 예고 알림)를 메타데이터에서 확인할 수 있는 패턴이 널리 알려져 있습니다. 이 신호를 감지하면 인스턴스는 새 작업을 받지 않고, 처리 중인 작업을 정리한 뒤 빠지도록 만들어야 합니다.

    특히 큐 기반 워커에서는 효과가 큽니다. 작업을 꺼내기 전에 락(lock)이나 가시성 타임아웃(visibility timeout)을 잘 설정해두면, 종료 중인 노드가 빠져도 다른 워커가 이어받을 수 있거든요. 이거 진짜 편하더라고요.

    #!/usr/bin/env bash
    set -euo pipefail
    
    TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
      -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
    
    while true; do
      ACTION=$(curl -sf -H "X-aws-ec2-metadata-token: ${TOKEN}" \
        http://169.254.169.254/latest/meta-data/spot/instance-action || true)
    
      if [ -n "${ACTION}" ]; then
        echo "Spot interruption detected: ${ACTION}"
        touch /tmp/stop-accepting-jobs
        systemctl stop my-worker.service
        sleep 20
        shutdown -h now
      fi
    
      sleep 5
    done
    

    웹 서비스라면 로드 밸런서에서 먼저 빼고, 워커라면 큐 소비를 중단하는 식으로 접근하시면 됩니다. 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션)를 쓰는 분들은 노드 축출(eviction)과 파드 재스케줄링 쪽도 함께 봐야 하고요. 이 부분은 다음 글에서 따로 다뤄볼 예정입니다.

    if [ -f /tmp/stop-accepting-jobs ]; then
      echo "Node is draining. Skip new job fetch."
      exit 0
    fi
    

    여기서 중요한 포인트! 작업을 중간에 잃어버리지 않도록 재시도 가능(idempotent, 여러 번 실행돼도 결과가 꼬이지 않는)하게 만드는 것이 Spot 운영의 핵심입니다.

    6. ⚠️ 주의사항과 트러블슈팅: 제가 실제로 많이 부딪힌 문제

    Spot은 싸지만, 운영 습관이 잘못 잡혀 있으면 바로 티가 납니다. 아래는 제가 직접 해보니 자주 터졌던 문제들입니다.

    문제 1. 로컬 디스크에 상태를 저장했다가 데이터 유실

    처음엔 캐시 파일 정도는 괜찮겠지 했었는데, 재시작 시 복구 시간이 길어져서 결국 더 느려졌습니다. 해결은 단순했습니다. 중요한 상태는 오브젝트 스토리지(Object Storage, 객체 저장소)나 DB, 공유 스토리지로 빼고, 인스턴스는 최대한 가볍게 만들었습니다.

    문제 2. Spot 비율을 너무 높게 잡아서 회수 이벤트 때 동시 흔들림

    비용 욕심이 과하면 바로 대가를 치릅니다. 핵심 서비스는 최소한의 On-Demand 바닥 용량을 두세요. 저는 초기에 거의 전부 Spot으로 돌렸다가 특정 시간대에 용량 확보가 안 되면서 식은땀 좀 났습니다.

    문제 3. 특정 인스턴스 패밀리만 고집

    컴퓨트 최적화(Compute Optimized)만, 또는 메모리 최적화(Memory Optimized)만 고집하면 대체 폭이 줄어듭니다. 애플리케이션이 허용한다면 비슷한 스펙 계열을 넓게 열어두는 편이 훨씬 안정적입니다.

    문제 4. 모니터링 없이 비용만 봄

    클라우드 비용 절감만 보면 반쪽짜리입니다. 회수 횟수, 재시작 빈도, 작업 실패율, 평균 처리 시간, 큐 적체량도 같이 봐야 합니다. 비용은 내려갔는데 SLA(Service Level Agreement, 서비스 수준 목표)가 망가지면 의미가 없거든요.

    • 권장 메트릭: 인스턴스 교체 횟수, 헬스 체크 실패율, 평균 대기 시간, 작업 재시도 횟수
    • 권장 로그: 종료 예고 감지 시점, drain 시작/종료 시점, 작업 재할당 기록
    • 권장 알람: 원하는 용량 미달, 큐 적체 급증, 에러율 상승

    7. 검증과 결과: 무엇을 확인해야 성공인가

    Spot을 붙이고 나면 “청구서가 줄었다”만 보면 안 됩니다. 저는 보통 아래 3가지를 같이 확인합니다.

    1. 월별 또는 주별 인프라 비용 추이
    2. 장애나 성능 저하 없이 목표 처리량을 유지했는지
    3. 회수 이벤트가 와도 자동 복구가 되는지

    가장 쉬운 검증 방법은 작은 워커 풀부터 시작하는 겁니다. 예를 들어 전체 20대 중 2대만 Spot으로 시작해보세요. 그다음 20%, 40%, 60% 식으로 천천히 올리면서 실패율과 큐 적체를 같이 봅니다. 실제로 써보니까 한 번에 크게 바꾸는 것보다 이 방식이 훨씬 덜 아픕니다.

    검증 항목 확인 포인트 성공 기준 예시
    비용 Spot 도입 전후 비교 유의미한 절감 추세 확인
    안정성 회수 시 자동 복구 여부 수동 개입 최소화
    성능 처리량, 지연 시간 기존 목표 유지
    운영성 알람, 로그, 런북 장애 대응 절차 정립
    Spot Instance 활용 전략 적용 후 비용 절감과 성공률 검증 대시보드

    비용 절감 효과와 함께 작업 성공률, 재시도 횟수, 큐 적체량을 함께 보는 검증 화면 예시입니다.

    🎉 잘 설계된 Spot Instance 활용 전략은 비용만 줄이는 게 아니라, 오히려 아키텍처를 더 탄탄하게 만드는 계기가 되기도 합니다. 중단을 전제로 설계하다 보니 시스템이 전반적으로 단단해지거든요.

    8. 정리와 FAQ: 비용 절감과 안정성 확보, 둘 다 잡으려면

    정리하면 이렇습니다. Spot은 마법의 할인 버튼이 아니라, 중단 가능성을 시스템 설계로 흡수하는 방식입니다. 저도 처음엔 할인율만 보고 달려들었다가, 종료 처리와 상태 분리의 중요성을 몸으로 배웠습니다. 근데 그 과정을 한번 넘고 나니 운영이 훨씬 유연해지더라고요.

    Spot Instance 활용 전략 운영 체크리스트와 도입 우선순위 인포그래픽

    도입 대상 선정, 혼합 비율, 종료 처리, 모니터링까지 한 장으로 정리한 체크리스트 이미지입니다.

    자주 묻는 질문

    • Q. Spot을 핵심 서비스에도 쓸 수 있나요?
      A. 가능합니다. 다만 전체를 맡기기보다는 일부 확장 용량에 제한적으로 넣는 방식이 현실적입니다.
    • Q. 가장 먼저 붙여볼 대상은 뭔가요?
      A. 배치, 워커, CI처럼 재시도가 쉬운 작업이 좋습니다.
    • Q. 가용성 관리는 어떻게 시작하면 좋을까요?
      A. 종료 예고 감지, drain, 재시도, 외부 상태 저장 이 네 가지부터 챙기시면 됩니다.

    마지막으로 추천드리고 싶은 순서는 이렇습니다.

    1. 개발/배치 환경부터 Spot을 적용합니다.
    2. 혼합 비율을 낮게 시작합니다.
    3. 중단 처리 자동화를 먼저 완성합니다.
    4. 모니터링과 런북(runbook, 대응 절차 문서)을 정리합니다.
    5. 그다음에 프로덕션 확장 용량으로 넓힙니다.

    Spot Instance 활용 전략은 결국 “싼 자원을 안전하게 쓰는 설계”입니다. 여기만 잡히면 클라우드 비용 절감이 꽤 현실적으로 보이기 시작합니다. 이전 글에서 다뤘던 오토스케일링 기본기와 함께 보시면 더 이해가 잘 되실 거고, 다음 글에서는 쿠버네티스 환경에서 Spot 노드 운영 팁을 이어서 다뤄보겠습니다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Karpenter 설치 예시

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

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

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

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

    NodePool과 EC2NodeClass 예시

    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: default
    spec:
      template:
        spec:
          nodeClassRef:
            group: karpenter.k8s.aws
            kind: EC2NodeClass
            name: default
          requirements:
            - key: kubernetes.io/arch
              operator: In
              values: ["amd64"]
            - key: kubernetes.io/os
              operator: In
              values: ["linux"]
            - key: karpenter.sh/capacity-type
              operator: In
              values: ["spot", "on-demand"]
            - key: node.kubernetes.io/instance-type
              operator: In
              values: ["c6i.large", "m6i.large", "r6i.large"]
          expireAfter: 720h
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
        consolidateAfter: 5m
      limits:
        cpu: "200"
    ---
    apiVersion: karpenter.k8s.aws/v1
    kind: EC2NodeClass
    metadata:
      name: default
    spec:
      role: KarpenterNodeRole-my-eks
      amiSelectorTerms:
        - alias: al2023@latest
      subnetSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks
      securityGroupSelectorTerms:
        - tags:
            karpenter.sh/discovery: my-eks
    

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

    테스트용 워크로드 배포

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    해석할 때 주의할 점

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

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

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

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

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

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

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

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

    마무리

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

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

    자주 묻는 질문

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

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

    Cluster Autoscaler를 꼭 버려야 하나요?

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

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

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

  • [AWS] AWS Reserved Instance 구매 실패 사례: 비용 절감의 함정

    [AWS] AWS Reserved Instance 구매 실패 사례: 비용 절감의 함정

    목차

    [AWS] AWS Reserved Instance 구매 실패 사례: 비용 절감의 함정

    AWS 예약 인스턴스 실패 이야기는 생각보다 흔합니다. 온디맨드(On-Demand, 사용한 만큼 과금) 비용이 계속 눈에 밟히니까 Reserved Instance(예약 인스턴스, 일정 기간 사용 약정을 전제로 할인받는 과금 방식)로 들어가고 싶어지거든요. 저도 처음엔 “이거 사면 바로 비용 최적화 되겠네?” 싶었는데, 실제로 운영 패턴을 제대로 안 보고 들어가면 할인이 아니라 족쇄가 되더라고요. 특히 계정이 여러 개이거나, Auto Scaling(오토 스케일링, 부하에 따라 인스턴스 수를 자동 조절)이나 Kubernetes(쿠버네티스, 컨테이너 오케스트레이션) 기반 워크로드가 섞여 있으면 더 심합니다.

    이번 글은 AWS Reserved Instance 구매 실패 사례를 중심으로, 왜 후회가 생기는지, 어디서 판단이 어긋나는지, 그리고 구매 전에 무엇을 검증해야 하는지 정리해보겠습니다. 화려한 성공담보다 이런 실패 케이스가 AWS 비용 최적화에는 훨씬 도움이 되거든요.

    AWS 예약 인스턴스 실패를 이해하기 위한 비용 구조 개요 다이어그램

    온디맨드, 예약 인스턴스, 스케일링 워크로드가 한 화면에 비교된 개요 이미지입니다.

    AWS Reserved Instance, 쉽게 말해 뭐냐면요

    쉽게 말해 Reserved Instance는 미리 사용 의사를 약정하고 할인받는 구조예요. 이름만 보면 인스턴스를 예약해서 고정 배치하는 느낌이지만, 실제로는 과금 혜택에 더 가깝습니다. 여기서 많이 헷갈려요. 저도 처음엔 “인스턴스 하나를 딱 잡아두는 건가?” 싶었는데, 실제로 써보니까 핵심은 내가 어떤 조건의 사용량을 얼마나 안정적으로 유지하느냐였습니다.

    보통 비교 대상은 아래 세 가지예요.

    옵션 특징 언제 어울리는가 주의점
    On-Demand 약정 없이 바로 사용 변동이 큰 서비스, 실험 환경 장기 운영 시 비용이 높을 수 있음
    Standard RI 강한 할인, 대신 변경 유연성 제한 오래 유지되는 고정 워크로드 예상과 다른 패턴이 나오면 후회하기 쉬움
    Convertible RI 일부 변경 유연성 제공 장기 사용은 확실하지만 타입 변경 가능성이 있을 때 무조건 단순하진 않고 검토 포인트가 많음
    Savings Plans 사용 금액 기준 할인 구조 인스턴스 타입 변경이 잦은 환경 RI보다 해석이 쉽다고 방심하면 안 됨

    여기서 중요한 포인트! 할인율만 보고 고르면 안 돼요. 운영팀 입장에서는 할인율보다도 적용 범위(scope), 변경 가능성, 워크로드 고정성이 먼저거든요.

    제가 본 대표적인 AWS 예약 인스턴스 실패 패턴

    실제 현업에서 많이 보는 실패는 화려하지 않습니다. 아주 평범한 판단 실수에서 시작해요. 그래서 더 무섭습니다 ㅎㅎ

    1. 월 사용량 평균만 보고 질렀을 때

    가장 흔한 패턴입니다. 지난 3개월 평균 EC2 비용만 보고 “이 정도는 계속 쓰겠지” 하고 Reserved Instance를 구매해요. 근데 서비스는 평균으로 안 움직이더라고요. 주간 배치가 빠지거나, 특정 프로젝트가 종료되거나, AMI 교체와 함께 인스턴스 패밀리가 바뀌면 적용률이 바로 흔들립니다.

    2. Auto Scaling 환경을 너무 고정 워크로드처럼 본 경우

    낮에는 10대, 밤에는 3대인 서비스가 있어요. 여기서 10대를 기준으로 약정해버리면 7대분은 상당 시간 놀 수 있습니다. 겉으로는 할인받는 것 같지만 실제 체감은 달라요. 그래서 AWS 비용 최적화는 최대 사용량이 아니라 최소 안정 사용량을 기준으로 봐야 합니다.

    3. 운영체제, 리전, 테넌시 조건을 대충 이해한 경우

    Reserved Instance는 그냥 “EC2면 다 맞겠지”가 아니거든요. 인스턴스 패밀리, 리전(Region), 운영체제(OS), 테넌시(Tenancy, 공유/전용 실행 방식) 같은 조건이 얽혀 있어요. 여기서 하나만 어긋나도 기대한 만큼 적용이 안 될 수 있습니다. 저도 예전에 문서 읽을 땐 다 이해한 줄 알았는데, 실제 청구서에서 보면 “어? 왜 할인 안 붙었지?” 싶었던 적이 있었어요.

    4. 조직 개편이나 아키텍처 변경을 과소평가한 경우

    이건 더 현실적인 문제입니다. 올해는 VM 중심이었는데 반년 뒤에는 ECS나 EKS 비중이 커질 수도 있거든요. 혹은 Graviton(그래비톤, AWS ARM 기반 프로세서) 전환을 하면서 인스턴스 패밀리가 바뀔 수도 있어요. 이런 전환 계획이 있는 상태에서 장기 RI를 먼저 사두면, 그때부터 Reserved Instance 후회가 시작되더라고요.

    실패 사례를 하나로 묶어보면 이런 흐름입니다

    제가 교육할 때 자주 드는 예시가 있어요. 가상의 사례지만, 실제 현장에서 너무 자주 보는 패턴이라 거의 복붙 수준입니다.

    1. 운영팀이 월 EC2 비용 증가를 확인합니다.
    2. 재무팀이나 리더가 “할인 수단 없나요?”라고 묻습니다.
    3. 팀은 지난달 사용량 기준으로 RI를 검토합니다.
    4. 가장 할인 체감이 큰 옵션만 보고 구매합니다.
    5. 두 달 뒤 서비스 구조가 바뀌거나 스케일 패턴이 바뀝니다.
    6. 청구서에서는 일부만 할인 적용되고, 나머지는 온디맨드로 계속 나갑니다.
    7. 결과적으로 “분명 할인 샀는데 왜 체감이 없지?”라는 상황이 옵니다.

    이 흐름의 핵심은 하나예요. Reserved Instance는 기술 상품이면서 동시에 재무 상품이라는 거죠. 인프라 엔지니어가 시스템 구조만 보고 결정하면 부족하고, 반대로 숫자만 보고 결정해도 실패합니다.

    AWS 비용 최적화를 위해 구매 전에 꼭 보는 체크리스트

    AWS 예약 인스턴스 실패를 줄이려면, 구매 전에 아래 항목을 먼저 점검해보세요.

    • 기준 기간을 넓게 보기: 최소 3개월, 가능하면 더 긴 추세를 봅니다.
    • 최소 안정 사용량 확인: 피크가 아니라 바닥 사용량이 중요합니다.
    • 변경 계획 확인: 마이그레이션, 패밀리 변경, 리전 이동 가능성을 체크합니다.
    • 조직 단위 적용 범위 확인: 연결 계정(Linked Account)과 결제 구조를 같이 봅니다.
    • 기존 할인 상품 중복 여부 확인: Savings Plans와 섞여 있으면 해석이 꼬일 수 있습니다.
    • 테스트 구매부터 시작: 처음부터 크게 들어가지 말고 작은 범위로 검증합니다.

    💡 팁: 개인적으로는 “절대 안 꺼지는 인스턴스가 무엇인가?”를 먼저 찾아요. 배치도 아니고, 이벤트성도 아니고, 계절성도 낮고, 다음 분기에도 구조가 안 바뀔 서비스. 바로 그 구간이 약정의 시작점이었습니다.

    실전 구현: 구매 전에 데이터부터 뽑아보겠습니다

    여기부터는 실제로 확인할 수 있는 명령어 위주로 가보겠습니다. 콘솔만 보지 말고 CLI(Command Line Interface, 명령줄 도구)로도 확인해두면 판단이 훨씬 또렷해져요.

    1. 현재 EC2 인스턴스 현황 확인

    aws ec2 describe-instances \
      --query 'Reservations[].Instances[].{Id:InstanceId,Type:InstanceType,State:State.Name,AZ:Placement.AvailabilityZone,Platform:PlatformDetails}' \
      --output table

    이 명령은 지금 어떤 타입을 얼마나 쓰는지 1차로 확인할 때 좋습니다. 여기서부터 이미 흔들리는 타입이 보여요. 개발 환경은 자주 바뀌고, 운영 환경 중에도 예상보다 자주 교체되는 게 있거든요.

    2. 현재 보유한 Reserved Instance 확인

    aws ec2 describe-reserved-instances \
      --filters Name=state,Values=active,payment-pending \
      --query 'ReservedInstances[].{Id:ReservedInstancesId,Type:InstanceType,Count:InstanceCount,Scope:Scope,Offering:OfferingClass,End:End}' \
      --output table

    이미 보유 중인 RI가 있다면 중복 구매부터 막아야 해요. 의외로 이걸 안 보고 다시 사는 경우가 있거든요. 특히 계정이 많으면 더 그렇습니다.

    AWS 예약 인스턴스 실패를 줄이기 위한 AWS CLI 점검 화면 이미지

    AWS CLI 출력과 운영 메모를 함께 보며 구매 전 상태를 점검하는 이미지입니다.

    3. Reservation Coverage 확인

    aws ce get-reservation-coverage \
      --time-period Start=2026-06-01,End=2026-07-01 \
      --granularity MONTHLY \
      --group-by Type=DIMENSION,Key=INSTANCE_TYPE

    Coverage(커버리지, 할인 적용 범위)는 “내가 얼마나 덮고 있느냐”를 보는 지표예요. 높다고 무조건 좋은 건 아니지만, 너무 낮으면 이미 약정 전략이 어긋나고 있다는 신호일 수 있습니다.

    4. Reservation Utilization 확인

    aws ce get-reservation-utilization \
      --time-period Start=2026-06-01,End=2026-07-01 \
      --granularity MONTHLY

    Utilization(유틸라이제이션, 실제 활용률)은 더 직접적이거든요. 샀는데 못 쓰고 있으면 여기서 티가 납니다. AWS Reserved Instance 구매 실패를 사후에 발견할 때도 이 지표가 꽤 유용하더라고요.

    5. 후보 상품만 먼저 조회

    aws ec2 describe-reserved-instances-offerings \
      --instance-type m6i.large \
      --product-description Linux/UNIX \
      --offering-class standard \
      --query 'ReservedInstancesOfferings[].{Id:ReservedInstancesOfferingId,Duration:Duration,FixedPrice:FixedPrice,UsagePrice:UsagePrice}' \
      --output table

    여기서도 바로 구매하지 말고, 조회만 먼저 해보세요. 실제로 써보니까 구매 버튼보다 중요한 건 비교 가능한 데이터를 만드는 과정이었어요.

    6. 구매 예시는 정말 마지막에

    aws ec2 purchase-reserved-instances-offering \
      --reserved-instances-offering-id rireg-xxxxxxxx \
      --instance-count 1

    ⚠️ 이 명령은 실제 구매를 발생시킬 수 있으니 검증이 끝난 뒤에만 사용하세요. 저는 운영 계정에서는 이 단계 전에 팀 리뷰를 꼭 한번 더 거칩니다.

    트러블슈팅: 제가 특히 많이 본 함정들

    여기부터가 진짜 중요합니다. Reserved Instance 후회는 보통 아래 지점에서 시작되더라고요.

    할인율만 보고 Standard RI로 고정해버린 경우

    서비스 구조가 안정적이라는 확신이 없으면 부담이 커요. 할인율이 좋아 보여도, 아키텍처가 바뀌면 오히려 더 비싸게 느껴질 수 있습니다. 특히 인스턴스 패밀리 전환 계획이 있으면 더 조심해야 해요.

    리전 단위와 가용 영역 단위를 혼동한 경우

    적용 범위를 잘못 이해하면, 기대한 유연성이 안 나와요. 문서를 읽을 땐 쉬워 보여도 실제 청구 해석 단계에서는 꽤 헷갈립니다. 저도 처음엔 이게 뭔가 싶었는데, 결론은 간단했어요. 구매 전에 적용 범위를 명시적으로 표로 적어두는 것이 제일 낫더라고요.

    스케줄성 워크로드를 상시 워크로드로 착각한 경우

    야간에만 도는 배치, 월말에만 치솟는 작업, 특정 행사 기간에만 늘어나는 서버는 RI 기준선으로 잡기 어려워요. 이런 건 오히려 온디맨드나 다른 할인 전략이 더 맞을 수 있습니다.

    Cost Explorer만 보고 세부 리소스를 안 본 경우

    Cost Explorer(코스트 익스플로러, 비용 분석 도구)는 좋지만 요약이 강해요. 실제 리소스 단위와 운영 변경 이력을 같이 봐야 원인을 찾을 수 있습니다. 숫자만 보면 “분명 줄었는데?” 싶고, 리소스를 보면 “아, 인스턴스 타입이 바뀌었네”가 보이는 식이었어요.

    검증: 구매 후에는 무엇을 확인해야 하나요?

    구매가 끝났다고 끝이 아니에요. 오히려 그때부터가 운영입니다. 저는 보통 아래 순서로 봅니다.

    1. 다음 청구 주기에서 Coverage와 Utilization을 다시 확인합니다.
    2. 온디맨드 비용이 실제로 줄었는지 봅니다.
    3. 대상 워크로드의 인스턴스 타입 변경 여부를 확인합니다.
    4. 향후 1~2분기 변경 계획과 충돌하는지 점검합니다.

    검증할 때는 표로 정리해두면 좋습니다.

    검증 항목 좋은 신호 나쁜 신호
    Coverage 의도한 워크로드에 안정적으로 적용됨 예상보다 낮거나 들쭉날쭉함
    Utilization 구매분이 꾸준히 소진됨 보유 RI가 놀고 있음
    온디맨드 비중 핵심 워크로드에서 감소 오히려 계속 높게 유지됨
    운영 변경 대응 타입/패턴 변화에도 전략 유지 가능 조금만 바뀌어도 손해 체감이 큼

    ✅ 여기서 원하는 그림은 단순히 “싸게 샀다”가 아니에요. 예상한 워크로드에 예상한 방식으로 할인 적용이 되고 있는가입니다.

    AWS 예약 인스턴스 실패 분석을 위한 Coverage와 Utilization 대시보드 이미지

    예약 할인 적용률과 실제 활용률을 대시보드로 검증하는 이미지입니다.

    정리: 비용 절감의 핵심은 구매가 아니라 기준선입니다

    결국 AWS Reserved Instance 구매 실패는 기술을 몰라서 생긴다기보다, 기준선을 잘못 잡아서 생기는 경우가 많아요. 할인 상품을 먼저 고르는 게 아니라, 내 워크로드 중 무엇이 정말 고정적인지부터 확인해야 해요. 제가 직접 해보니 가장 덜 후회하는 방법은 늘 비슷했습니다. 작은 범위로 시작하고, 적용 결과를 확인하고, 그다음에 넓히는 방식이었거든요.

    🎉 한 번 구조를 잡아두면 AWS 비용 최적화가 훨씬 쉬워져요. 반대로 처음 한 번 크게 잘못 사두면 꽤 오래 끌고 가야 하더라고요. 혹시 지금 RI를 검토 중이시라면, 오늘 바로 구매부터 하지 마시고 먼저 Coverage와 Utilization부터 보세요. 그게 진짜 출발점입니다.

    AWS 예약 인스턴스 실패와 대안을 정리한 비용 절감 비교 인포그래픽

    Reserved Instance, 온디맨드, 다른 할인 전략의 선택 기준을 요약한 이미지입니다.

    자주 묻는 질문

    Q1. 온디맨드 비용이 크면 바로 RI로 가야 하나요?

    아니에요. 먼저 그 비용이 지속되는 비용인지 확인해야 합니다. 일시적 피크라면 약정이 오히려 독이 될 수 있거든요.

    Q2. AWS 예약 인스턴스 실패를 가장 빨리 알아채는 방법은 뭔가요?

    Coverage와 Utilization을 주기적으로 확인하는 거예요. 샀는데 적용이 안 되거나, 보유분이 놀고 있으면 여기서 먼저 보여요.

    Q3. Reserved Instance 후회가 걱정되면 대안은 없나요?

    있어요. 다만 정답은 환경마다 달라요. 인스턴스 타입 변경이 잦거나 구조 변화가 예상되면 더 유연한 전략을 같이 검토해야 합니다. 이 부분은 다음 글에서 Savings Plans와 함께 비교해보겠습니다.

    Q4. 내부적으로 어떤 절차를 두는 게 좋을까요?

    저는 최소한 아래 3가지는 권해요.

    • 구매 전 3개월 이상 사용 추세 검토
    • 아키텍처 변경 계획 확인
    • 재무 관점과 운영 관점 동시 리뷰

    이전 글에서 다룬 클라우드 태깅 전략과 함께 보면 더 이해가 쉬울 수 있어요. 비용은 결국 리소스 가시성에서 시작하거든요.

  • [Cloud] Cloudflare Workers & R2 비용 최적화: 숨겨진 과금 피하는 실전 전략

    [Cloud] Cloudflare Workers & R2 비용 최적화: 숨겨진 과금 피하는 실전 전략

    Cloudflare Workers & R2 비용 최적화: 숨겨진 과금 피하는 실전 전략

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 많은 분들이 관심 가질 만한 주제, 바로 Cloudflare Workers & R2 비용 최적화에 대한 이야기를 풀어보려고 합니다. 클라우드를 쓰다 보면 예상치 못한 과금에 당황하는 경우가 종종 있잖아요? 저도 처음엔 Cloudflare의 매력적인 Free Tier에 혹해서 이것저것 써보다가, 나중에 대시보드를 보고 ‘어라? 이 비용은 어디서 나온 거지?’ 하고 삽질 좀 했습니다. 특히 Cloudflare Workers(클라우드플레어 워커스, 서버리스 엣지 컴퓨팅 플랫폼)와 Cloudflare R2(클라우드플레어 R2, S3 호환 오브젝트 스토리지)는 정말 강력한 조합이지만, 그만큼 숨겨진 과금 포인트들이 있거든요. 오늘은 제가 직접 겪었던 경험을 바탕으로, 어떻게 하면 이 숨겨진 과금들을 피하고 Cloudflare 비용 최적화를 할 수 있는지 실전 전략을 공유해 드릴게요.

    Cloudflare Workers와 R2를 활용한 일반적인 데이터 흐름 아키텍처 다이어그램

    Cloudflare Workers와 R2를 사용한 기본적인 데이터 흐름 아키텍처 다이어그램. 사용자의 요청이 Workers를 통해 R2에 저장된 데이터를 가져오거나 처리하는 과정을 보여줍니다.

    Cloudflare Workers와 R2: 핵심 개념 이해하기

    먼저, Cloudflare Workers와 R2가 무엇인지 간단하게 짚고 넘어갈게요. 이미 잘 아시는 분들도 계시겠지만, 비용 최적화의 기본은 서비스의 작동 방식을 정확히 이해하는 거거든요.

    • Cloudflare Workers (클라우드플레어 워커스): 쉽게 말해, 전 세계 Cloudflare 엣지 네트워크에 배포되는 서버리스 JavaScript/WebAssembly 환경이에요. 사용자의 요청에 가장 가까운 엣지에서 코드가 실행되니 응답 속도가 빠르고, 서버 관리 부담이 없어서 정말 편합니다. Free Tier도 넉넉해서 개인 프로젝트나 소규모 서비스에 많이 쓰이죠.
    • Cloudflare R2 (클라우드플레어 R2): Amazon S3(아마존 S3)와 호환되는 오브젝트 스토리지 서비스입니다. 가장 큰 특징은 바로 이그레스(Egress, 외부로 나가는 데이터 전송) 비용이 무료라는 점이에요! 이게 진짜 혁명적이거든요. 다른 클라우드 스토리지 서비스들은 데이터가 나갈 때마다 비용을 청구해서, 트래픽이 많아지면 배보다 배꼽이 커지는 경우가 많았거든요.

    이 두 서비스는 조합하면 정적 파일 호스팅, API 게이트웨이, 이미지 리사이징 등 다양한 작업을 엣지에서 효율적으로 처리할 수 있습니다. 하지만 이 매력적인 무료 정책 뒤에는 몇 가지 주의해야 할 과금 포인트들이 숨어있어요.

    실전 구현: Workers로 R2 데이터 서빙하기

    제가 자주 사용하는 시나리오 중 하나는 Workers를 통해 R2에 저장된 정적 파일(이미지, 비디오 등)을 서빙하거나, 간단한 API 게이트웨이 역할을 하는 것입니다. 한번 기본적인 구현 과정을 살펴볼까요?

    1. R2 버킷 생성

    먼저 Cloudflare 대시보드에서 R2 버킷을 생성합니다. 버킷 이름은 고유해야 해요.

    2. Workers 프로젝트 생성 및 R2 바인딩

    <code>wrangler CLI(명령줄 인터페이스)를 사용해서 Workers 프로젝트를 만들고, R2 버킷을 바인딩해줍니다. 이 과정이 제일 중요해요.

    npx wrangler generate my-r2-worker --ts
    cd my-r2-worker
    

    wrangler.toml 파일을 열어서 R2 바인딩을 추가합니다. bucket_name은 여러분이 생성한 R2 버킷 이름으로 바꿔주세요.

    name = "my-r2-worker"
    main = "src/index.ts"
    compatibility_date = "2023-10-26"
    
    [[r2_buckets]]
    binding = "MY_BUCKET" # Workers 코드에서 사용할 변수 이름
    bucket_name = "my-awesome-r2-bucket" # 실제 R2 버킷 이름
    

    3. Workers 코드 작성: R2 데이터 반환

    src/index.ts 파일에서 R2 버킷에서 파일을 읽어 반환하는 코드를 작성합니다. 여기서는 간단하게 모든 요청에 대해 index.html 파일을 반환한다고 가정해볼게요.

    interface Env {
      MY_BUCKET: R2Bucket;
    }
    
    export default {
      async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
        const url = new URL(request.url);
        const key = url.pathname.slice(1) || 'index.html'; // 요청 경로를 R2 객체 키로 사용
    
        const object = await env.MY_BUCKET.get(key);
    
        if (object === null) {
          return new Response('File Not Found', { status: 404 });
        }
    
        const headers = new Headers();
        object.writeHttpMetadata(headers);
        headers.set('etag', object.httpEtag);
    
        // R2 객체 스트림을 직접 반환하여 Workers Egress 비용 최적화
        return new Response(object.body, {
          headers,
        });
      },
    };
    

    여기서 중요한 포인트가 하나 있습니다. return new Response(object.body, { headers }); 이 부분인데요. R2에서 읽어온 object.body 스트림을 Workers가 직접 클라이언트로 전달하도록 하는 것이 중요합니다. 이렇게 하면 Workers가 데이터를 내부적으로 처리하는 시간을 최소화하고, R2 Egress Free 정책의 혜택을 최대한 받을 수 있어요. 만약 Workers가 이 데이터를 가공하거나 다른 곳으로 전달한다면, Workers Data Egress(워커스 데이터 이그레스) 비용으로 과금될 수 있거든요. 저도 처음엔 이 부분을 간과해서 예상치 못한 과금이 발생했었습니다 😅.

    Cloudflare 대시보드 Workers 서비스의 R2 버킷 바인딩 설정 화면

    Cloudflare 대시보드에서 Workers 서비스에 R2 버킷이 올바르게 바인딩된 설정 화면을 보여줍니다.

    ⚠️ 숨겨진 과금 포인트와 실전 최적화 전략

    이제 본격적으로 숨겨진 과금 포인트를 파헤쳐보고, 제가 직접 겪은 삽질 경험을 바탕으로 한 Cloudflare 비용 최적화 전략을 공유해 드릴게요.

    1. Workers Compute Duration(워커스 컴퓨트 듀레이션) 과금 최소화

    Workers는 코드가 실행되는 시간에 따라 과금되죠. Free Tier는 하루에 10만 건의 요청과 1,000,000ms(1초)의 CPU 시간이 주어지지만, 이를 초과하면 과금됩니다.

    • 불필요한 작업 줄이기: Workers 내부에서 복잡한 연산, 외부 API 호출 대기 시간 등을 최소화해야 합니다. 특히 R2에서 데이터를 읽어오는 동안 Workers가 대기하는 시간도 Compute Duration에 포함될 수 있거든요.
    • 스트리밍 활용: 위 코드 예시처럼 R2의 object.body를 직접 Response로 반환하면, Workers가 모든 데이터를 메모리에 로드할 필요 없이 스트리밍으로 처리하여 Compute Duration을 줄일 수 있어요.
    • Durable Objects(듀러블 오브젝트) 사용 주의: Durable Objects는 상태 저장형 Workers로 매우 강력하지만, 일반 Workers보다 과금 기준이 복잡하고 비쌀 수 있습니다. 꼭 필요한 경우에만 사용하고, 사용 패턴을 면밀히 분석해야 해요.

    2. R2 API Requests(R2 API 요청) 최적화

    R2는 스토리지 용량 외에도 API 요청 횟수에 따라 과금됩니다. 특히 Class A(쓰기 작업) 요청이 Class B(읽기 작업) 요청보다 비싸죠.

    • 캐싱 전략: Workers에서 R2 데이터를 읽어올 때, Cloudflare의 Cache API(캐시 API)를 적극 활용하세요. 자주 접근하는 데이터는 엣지 캐시에 저장하여 R2 API 호출 횟수를 줄일 수 있어요. 예를 들어, 이미지 파일 같은 정적 콘텐츠는 Cache API와 함께 Cache-Control 헤더를 잘 설정하면 R2 요청을 확 줄일 수 있습니다.
    • Batch 작업: 여러 객체를 한 번에 처리해야 한다면, Batch API를 사용하여 요청 횟수를 줄일 수 있는지 고려해보세요.

    3. 데이터 이그레스(Data Egress) 착시 효과 방지

    ⚠️ R2는 Egress Free가 맞지만, Workers의 Egress는 별도 과금됩니다. 제가 가장 크게 삽질했던 부분이 바로 여기인데요.

    • Workers Data Egress: Workers가 R2에서 데이터를 읽어서 클라이언트에게 전달할 때, 이 데이터는 R2의 Egress가 아닌 Workers의 Data Egress로 계산됩니다. 하지만 위에서 설명한 return new Response(object.body, { headers }); 방식처럼 R2의 스트림을 그대로 전달하면, Cloudflare는 이를 R2 Egress로 간주하여 무료 혜택을 적용해줍니다. 즉, Workers가 R2 데이터를 “거의 가공 없이” 프록시처럼 전달할 때만 R2의 Egress Free 혜택을 받는다고 생각하는 게 좋아요. 만약 Workers가 R2 데이터를 읽어서 이미지 리사이징을 하거나, JSON 형태로 가공해서 보내면, 그건 Workers Data Egress로 과금될 수 있거든요. 이 부분은 공식 문서도 좀 헷갈리게 되어 있어서, 저처럼 삽질하는 분들이 많더라고요.
    • Cloudflare CDN(콘텐츠 전송 네트워크) 활용: R2에 직접 도메인을 연결하고 Cloudflare CDN을 사용하면, R2 Egress Free 혜택을 온전히 누릴 수 있습니다. Workers는 복잡한 로직이 필요할 때만 사용하고, 단순 정적 파일 서빙은 R2 + CDN 조합이 비용 효율적이에요.

    4. 로그 분석으로 비용 낭비 요소 찾기

    Cloudflare Workers는 요청 로그를 제공합니다. 이를 통해 어떤 요청이 Compute Duration을 많이 소모하는지, 어떤 Workers가 비정상적으로 많이 호출되는지 등을 파악할 수 있어요. 저도 주기적으로 로그를 보면서 최적화 포인트를 찾아냅니다.

    Cloudflare 대시보드에서 Workers 및 R2의 상세 비용 분석 대시보드

    Cloudflare 대시보드에서 Workers의 Compute Duration, Requests, R2 스토리지 및 API 요청 수 등 자세한 비용 분석 데이터를 보여주는 화면입니다.

    검증 및 결과: 비용 모니터링의 중요성

    최적화 전략을 적용했다면, 이제 그 효과를 검증할 차례입니다. Cloudflare 대시보드의 Analytics(분석) 섹션과 Billing(청구) 섹션을 주기적으로 확인하는 것이 중요해요.

    • Workers Analytics: Workers의 요청 수, CPU 시간, 오류율 등을 자세히 볼 수 있습니다. 여기서 비정상적인 패턴이나 높은 CPU 시간을 소모하는 Workers를 찾아내 개선할 수 있어요.
    • R2 Analytics: R2의 스토리지 사용량, Class A/B API 요청 수, 데이터 전송량 등을 확인할 수 있습니다. 특히 API 요청 수가 예상보다 높다면 캐싱 전략을 다시 점검해야 합니다.
    • Billing Page: 마지막으로 청구 페이지에서 실제 과금 내역을 확인합니다. 과금 항목별로 상세 내역을 볼 수 있으니, 어떤 서비스에서 비용이 발생했는지 정확히 파악하고 다음 최적화 계획을 세울 수 있어요.

    저도 처음에는 대시보드 보는 법도 익숙지 않아서 헤맸는데, 몇 번 해보니 금방 적응되더라고요. 실제 비용이 줄어드는 걸 보면 그렇게 뿌듯할 수가 없습니다 🎉.

    Cloudflare Workers 및 R2 비용 최적화 핵심 전략 요약 인포그래픽

    Cloudflare Workers 및 R2 비용 절감을 위한 핵심 전략들을 시각적으로 요약한 인포그래픽. 캐싱, 스트리밍, 로깅, 모니터링 등의 키워드와 간략한 설명을 포함합니다.

    마무리하며: 지속적인 관심이 최고의 비용 절감 전략

    오늘은 Cloudflare Workers와 R2를 사용하면서 제가 직접 겪었던 Cloudflare 비용 최적화 경험과 실전 전략들을 공유해 드렸습니다. 핵심은 서비스의 과금 방식을 정확히 이해하고, 불필요한 자원 소모를 최소화하며, 지속적으로 모니터링하는 것입니다.

    Cloudflare는 정말 매력적인 서비스들을 많이 제공하지만, 클라우드 서비스가 다 그렇듯 ‘무료’라는 말에 혹해서 무작정 사용하다 보면 예상치 못한 비용 폭탄을 맞을 수도 있어요. 하지만 오늘 알려드린 전략들을 잘 활용하시면, Cloudflare Workers와 R2의 강력한 성능과 R2의 Egress Free라는 엄청난 장점을 비용 걱정 없이 누릴 수 있을 겁니다.

    저도 여전히 새로운 기술들을 실험하고 삽질하면서 배우고 있습니다. 여러분도 저의 경험을 발판 삼아 더 효율적인 인프라를 구축하시길 바랍니다! 다음번에는 Cloudflare의 다른 서비스, 예를 들어 KV(Key-Value Store)나 Durable Objects를 활용할 때의 비용 고려 사항에 대해 이야기해볼까 합니다. 그때까지 다들 즐거운 삽질(?) 되세요!

  • [클라우드 비용 관리] Terraform Cloud 비용 최적화: RUM 모델과 절감 전략

    [클라우드 비용 관리] Terraform Cloud 비용 최적화: RUM 모델과 절감 전략

    [클라우드 비용 관리] Terraform Cloud 비용 최적화: RUM 모델 이해 및 절감 전략

    안녕하세요, 13년차의 서버실 주인장입니다. 오늘은 인프라 자동화 좀 해봤다 하는 분들이라면 한 번쯤은 만나봤을 Terraform Cloud에 대한 이야기를 해볼까 합니다. 특히, "어? 이거 왜 이렇게 비용이 많이 나왔지?" 하고 고개를 갸웃하게 만드는 그 미스터리, 바로 RUM (Resource Usage Model) 모델과 그 비용을 최적화하는 전략에 대해 제 경험을 바탕으로 솔직하게 풀어보려고 합니다.

    처음 Terraform Cloud를 도입했을 때, 저는 그 편리함에 감탄했었죠. 원격 상태 관리(Remote State Management), 팀 협업, CI/CD 통합까지… IaC(Infrastructure as Code)의 생산성을 정말 한 단계 끌어올려 주더라고요. 근데 어느 날 청구서를 받아보니, 예상했던 것보다 훨씬 많은 금액에 깜짝 놀랐습니다. terraform apply 한두 번 했을 뿐인데 이게 무슨 일인가 싶었죠. 혹시 여러분도 이런 경험 없으신가요? ⚠️

    이건 바로 Terraform Cloud의 독특한 과금 방식인 RUM 모델 때문이거든요. 저도 삽질 좀 하면서 이 모델을 파고들었고, 그 결과 몇 가지 효과적인 비용 절감 전략을 찾을 수 있었습니다. 오늘은 그 노하우를 여러분과 공유해볼까 합니다. 자, 그럼 함께 Terraform Cloud 비용 청구의 비밀을 파헤쳐 볼까요?

    Terraform Cloud RUM 모델 개요: 리소스가 어떻게 비용으로 연결되는지 보여주는 다이어그램입니다.

    Terraform Cloud RUM (Resource Usage Model)이란 무엇인가요?

    Terraform Cloud의 비용 구조에서 가장 핵심적인 부분은 바로 RUM (Resource Usage Model, 리소스 사용 모델)입니다. 쉽게 말해, Terraform Cloud가 "관리하는 리소스의 개수"에 따라 비용을 청구하는 방식이에요.

    그럼 어떤 리소스가 RUM에 포함될까요? terraform state list 명령어를 실행했을 때 출력되는 모든 리소스가 RUM 카운트에 포함된다고 생각하시면 됩니다. 예를 들어, AWS EC2 인스턴스, S3 버킷, VPC, Subnet 등 resource "aws_instance" "my_server" 이런 식으로 HCL(HashiCorp Configuration Language)에 선언된 모든 것들이요. Terraform Cloud는 이런 리소스들을 워크스페이스(Workspace) 단위로 관리하고, 각 워크스페이스가 관리하는 리소스의 총합에 따라 과금하는 방식이에요.

    여기서 중요한 포인트는 💡 데이터 소스 (Data Source)나 로컬 리소스 (Local Resource)는 RUM 카운트에 포함되지 않는다는 점입니다! 즉, data "aws_ami" "latest"나 locals { ... } 블록은 아무리 많이 써도 비용에 영향을 주지 않아요. 이 점을 잘 활용하면 비용을 효과적으로 절감할 수 있거든요.

    Terraform Cloud 비용 절감 핵심 전략

    이제 본격적으로 Terraform Cloud 비용을 최적화하는 전략들을 알아볼 시간입니다. 제가 직접 써보고 효과를 본 방법들이니, 여러분 환경에도 적용해보시면 분명 도움이 될 거예요.

    1. 불필요한 워크스페이스 정리하기

    이건 정말 기본 중의 기본이자 가장 효과적인 방법입니다. 개발 초기 단계나 테스트 목적으로 만들었다가 방치된 워크스페이스, 혹은 더 이상 사용하지 않는 프로젝트의 워크스페이스가 있다면 과감히 정리해야 합니다. 각 워크스페이스는 관리하는 리소스 수에 따라 RUM 비용을 발생시키기 때문이죠. 😅

    저도 처음엔 테스트용으로 워크스페이스를 여러 개 만들었는데, 나중에 보니 수십 개의 워크스페이스가 활성 상태로 남아있더라고요. 이걸 정리했더니 월별 청구액이 꽤 많이 줄었습니다. 🎉

    워크스페이스를 정리할 때는 다음 단계를 따르세요:

    1. 상태 파일 백업: 혹시 모를 상황에 대비해 terraform state pull > my_backup.tfstate 명령어로 상태 파일을 로컬에 백업해두는 게 좋습니다.
    2. 리소스 삭제: 워크스페이스가 관리하는 실제 클라우드 리소스를 terraform destroy 명령어로 삭제합니다. 이 과정을 빼먹으면 클라우드 비용만 계속 나갑니다!
    3. 워크스페이스 삭제: Terraform Cloud UI나 API를 통해 워크스페이스를 삭제합니다.

    만약 수많은 워크스페이스를 일일이 확인하기 어렵다면, tfe-cli 같은 도구를 활용해서 스크립트로 자동화하는 것도 좋은 방법이에요.

    # 예시: 특정 태그를 가진 오래된 워크스페이스 목록 확인 (tfe-cli 예시)
    tfe workspace list --json | jq '.[] | select(.tags[] | contains("test")) | select(.updated-at < "2023-01-01T00:00:00Z")'
    
    # 삭제는 더 신중하게 접근해야 합니다.
    # tfe workspace delete [WORKSPACE_NAME]
    

    2. 리소스 설계 최적화: Data Sources 및 Locals 활용

    앞서 언급했듯이 Data Sources (데이터 소스)와 Locals (로컬 변수)는 RUM 카운트에 포함되지 않습니다. 이 점을 최대한 활용하여 resource 블록의 수를 줄이는 방향으로 설계를 최적화할 수 있어요.

    • Data Sources 활용: 이미 존재하는 리소스의 정보를 가져올 때 data "aws_vpc" "existing"처럼 데이터 소스를 사용하세요. 특히, 자주 바뀌지 않거나 다른 Terraform 스택에서 관리하는 리소스 정보를 가져올 때 유용합니다. 제가 해보니, AMI ID나 특정 보안 그룹 ID 같은 것들을 데이터 소스로 가져오면 resource 블록을 하나 줄일 수 있더라고요.
    • Locals 활용: 복잡한 표현식의 결과를 저장하거나, 여러 리소스에서 공통으로 사용되는 값을 정의할 때 locals 블록을 사용하면 코드 가독성도 높이고, 불필요한 리소스 선언을 피할 수 있어요.
    # RUM에 포함되지 않는 Data Source 예시
    data "aws_ami" "ubuntu" {
      most_recent = true
      filter {
        name   = "name"
        values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
      }
      owners = ["099720109477"]
    }
    
    # RUM에 포함되지 않는 Locals 예시
    locals {
      instance_type = "t3.micro"
      common_tags = {
        Project     = "MyService"
        Environment = "Development"
      }
    }
    
    # RUM에 포함되는 Resource 예시 (이것의 수가 과금의 기준이 됩니다)
    resource "aws_instance" "web_server" {
      ami           = data.aws_ami.ubuntu.id
      instance_type = local.instance_type
      tags          = local.common_tags
    }
    

    3. Sentinel Policy 활용하여 비용 낭비 방지

    Terraform Cloud의 Sentinel (센티넬)은 정책 기반 코드(Policy as Code)를 통해 인프라 변경 사항을 검증하는 강력한 도구입니다. 이 Sentinel을 활용하면 비용 낭비를 사전에 막을 수 있어요. 예를 들어:

    • 고가용성 리소스 제한: 특정 고가 리소스(예: 고성능 데이터베이스 인스턴스)의 생성을 제한하거나, 특정 환경(예: 개발 환경)에서는 특정 인스턴스 타입 이상을 생성하지 못하게 정책을 걸 수 있어요.
    • 리소스 개수 제한: 특정 타입의 리소스(예: EC2 인스턴스)가 한 워크스페이스 내에서 일정 개수 이상 생성되지 못하도록 막을 수 있거든요. 저도 실수로 count 값을 너무 높게 설정해서 수십 개의 인스턴스를 한 번에 배포할 뻔한 적이 있는데, Sentinel 덕분에 막을 수 있었죠. 휴~ 😮‍💨
    
    # 예시: EC2 인스턴스의 개수를 5개로 제한하는 Sentinel Policy (pseudo-code)
    
    # import "tfplan/v2" as tfplan
    
    # instance_count = length(tfplan.resource_changes as r, r.type is "aws_instance" and r.change.actions contains "create")
    
    # main = rule {
    #   instance_count <= 5
    # }
    

    실제 Sentinel 정책은 위 예시보다 더 복잡하지만, 핵심은 원하는 제약을 코드로 정의하여 terraform apply 전에 검증함으로써 불필요한 리소스 생성을 막는다는 거죠.

    Terraform Cloud 워크스페이스와 Sentinel 정책: 중앙 정책 적용으로 비용 낭비를 막는 방법을 보여줍니다.

    4. Run 실행 횟수 관리 (간접적 영향)

    RUM 모델 자체는 리소스 개수에 초점을 맞추지만, 상위 티어에서는 Run (실행) 횟수도 과금 요소가 될 수 있어요. 그리고 잦은 Run은 불필요한 리소스 변경으로 이어질 가능성을 높여 RUM 카운트에도 간접적으로 영향을 줄 수 있거든요.

    • 변경 사항 신중하게 검토: terraform plan 결과를 항상 꼼꼼하게 확인하고, 꼭 필요한 변경 사항만 apply 하세요.
    • CI/CD 파이프라인 최적화: 불필요한 트리거로 Run이 실행되지 않도록 CI/CD 파이프라인을 설계하는 게 중요합니다. 예를 들어, 모든 커밋마다 Run을 돌리기보다는, 특정 브랜치에 머지될 때만 실행되도록 설정하는 식이죠.

    비용 최적화 결과 확인하기

    위 전략들을 적용했다면, 이제 그 효과를 확인해야겠죠? Terraform Cloud는 자체적으로 Billing & Usage (청구 및 사용량) 대시보드를 제공합니다. 여기서 월별 RUM 사용량과 청구 금액을 확인할 수 있어요.

    제 경험상, 불필요한 워크스페이스를 정리하고 리소스 설계를 조금만 변경해도 눈에 띄게 RUM 카운트가 줄어드는 것을 볼 수 있었습니다. 처음엔 이게 뭔가 싶었는데, 막상 수치가 줄어드는 걸 보니 뿌듯하더라고요. ✅

    대시보드에서 Managed Resources (관리되는 리소스) 그래프를 꾸준히 모니터링하면서, 어떤 워크스페이스가 많은 리소스를 관리하고 있는지, 그리고 그 추이가 어떻게 변하는지 확인해보세요. 이걸 보면서 "아, 이 워크스페이스는 리소스가 너무 많네. 줄여야겠다" 같은 의사결정을 할 수 있거든요.

    Terraform Cloud Billing & Usage 대시보드: 비용 절감 전략 적용 후 월별 RUM 사용량이 감소하는 가상의 그래프입니다.

    마무리하며: 삽질을 줄이는 현명한 비용 관리

    오늘은 Terraform Cloud의 RUM 모델을 이해하고, 이를 바탕으로 비용을 최적화하는 여러 전략에 대해 이야기해봤습니다. 13년차 인프라 엔지니어로서 제가 직접 겪었던 "비용 폭탄" 경험과 그 해결 과정을 공유하면서, 여러분의 삽질을 조금이나마 줄여드리고 싶었어요. 💡

    핵심은 결국 "내가 무엇을 관리하고 있고, 그게 과금에 어떻게 영향을 미치는지 정확히 아는 것"이에요. Terraform Cloud는 정말 강력한 도구지만, 그만큼 현명하게 사용해야 예상치 못한 비용 문제로 당황하지 않을 수 있거든요.

    여러분도 이 글에서 소개한 전략들을 바탕으로 Terraform Cloud 비용을 최적화하고, 더 효율적인 IaC 환경을 구축하시길 바랍니다. 다음 글에서는 Terraform Cloud의 원격 실행 환경(Remote Operations)과 로컬 실행 환경(Local Operations)의 장단점을 비교해보는 시간을 가져볼게요. 기대해주세요! 😄

    Terraform Cloud 비용 절감 핵심 전략 요약: 주요 절감 팁을 한눈에 볼 수 있는 인포그래픽입니다.