DevOps

Karpenter + Spot 인스턴스 설계(끝)

YoonJong 2026. 5. 19. 08:34
728x90
반응형

운영으로 넘어갔을 시 Spot 100%를 사용할 수 있을까?

 

Spot 인스턴스는 AWS의 남는 서버 자원을 저렴하게 빌리는 방식이다. 

가격은 On-Demand 대비 60~70% 저렴하지만, AWS가 해당 자원이 필요해지면 2분 전 통보 후 회수한다.

dev 환경에서는 잠깐 서비스가 내려가더라도 큰 문제가 없지만, 운영 환경에서는 상황이 다르다.

가장 위험한 시나리오는 특정 AZ에서 동일 인스턴스 타입 Spot이 동시에 대량 회수되는 경우다.

이때 Karpenter가 즉시 새 노드 프로비저닝을 시작하지만, 노드 부팅과 Pod 재배치까지 약 60초가 소요된다.

Spot 100% 환경이라면 이 60초 동안 서비스 Pod가 하나도 없는 상태가 된다.

더 나쁜 경우는 Spot 재고 자체가 없는 상황이다.

Karpenter가 새 노드를 만들려고 시도하지만 AWS가 해당 인스턴스 타입 Spot 용량이 없다고 응답하면, Pod는 Pending 상태로 무한 대기하게 된다.

인스턴스 타입을 하나만 지정했을 때 이 위험이 가장 크다.

--

 

핵심 아이디어는 간단하다.

minReplicas(최소 Pod 수)는 항상 On-Demand에, 트래픽 증가분만 Spot으로 처리한다.

이 문제를 해결하는 방법이 On-Demand를 베이스로 깔아두는 혼합형 구성이다.

 

On-Demand는 AWS가 용량을 항상 보장하고 회수하지 않는다.

따라서 minReplicas Pod들을 On-Demand에 올려두면 Spot이 전부 사라져도 서비스는 유지된다.

Spot은 트래픽이 급증해서 HPA가 Pod를 늘릴 때만 투입되는 버퍼 역할을 한다.

 

# 운영 환경에서 100% spot은 위험, 최소 On-Demand 기반 노드 확보 필요
  # 현재 dev - Spot 100%
  values = ["spot"]

  # prod - On-Demand 기본 + Spot 혼합
  values = ["on-demand", "spot"]
  # + weight 조정으로 On-Demand 우선
  
  # 예시
  - On-Demand 노드 3대 + Spot 노드(가변)
  -> AWS가 Spot을 전부 회수해도 On-demand는 살아있어서 서비스 유지 가능
  
  # [prod 목표]
  service-ondemand (weight: 100) ← 베이스, 한도 작게
  service-spot     (weight: 10)  ← 버스트, 한도 크게
  batch-spot       (weight: 80)  ← 배치 전용 (그대로)
  neo4j-ondemand   (weight: 50)  ← Neo4j 전용 (그대로)
  
  # weight가 하는 일
  -> Karpenter가 새 노드를 만들 때 어떤 NodePool을 선택할지 결정.
  
  새 Pod 스케줄 필요
         ↓
  Karpenter: 어떤 NodePool에 노드 만들지?
         ↓
  weight 100 service-ondemand 먼저 선택
         ↓
  On-Demand 노드 생성 → Pod 배치
         ↓
  limits.cpu 16코어 도달 → 더 이상 못 만듦/ limits: On-Demand가 무한정 늘어나는 것을 방지
         ↓
  weight 10 service-spot으로 넘어감
         ↓
  Spot 노드 생성 → 초과 Pod 배치
  
  -----------------------------------------------------------
  
    limits.cpu 계산:
    24서비스 × minReplicas 2 × requests.cpu 0.25
  = 12코어 (최소) → 여유 있게 16코어로 설정
  = m6g.xlarge(4코어) 기준 노드 3~4대
  
  -----------------------------------------------------------
  
  
  # ① [신규 추가] service-ondemand NodePool
  resource "kubernetes_manifest" "karpenter_nodepool_service_ondemand" {
    manifest = {
      apiVersion = "karpenter.sh/v1"
      kind       = "NodePool"
      metadata = {
        name = "service-ondemand"
      }
      spec = {
        weight = 100  # service-spot(10)보다 높아서 먼저 채워짐
        template = {
          metadata = {
            labels = {
              "nodegroup"     = "service"
              "capacity-type" = "on-demand"
            }
          }
          spec = {
            requirements = [
              {
                key      = "kubernetes.io/arch"
                operator = "In"
                values   = ["arm64"]
              },
              {
                key      = "karpenter.sh/capacity-type"
                operator = "In"
                values   = ["on-demand"]  # On-Demand 고정
              },
              {
                key      = "karpenter.k8s.aws/instance-category"
                operator = "In"
                values   = ["m"]
              },
              {
                key      = "karpenter.k8s.aws/instance-cpu"
                operator = "In"
                values   = ["4"]
              },
            ]
            nodeClassRef = {
              group = "karpenter.k8s.aws"
              kind  = "EC2NodeClass"
              name  = "default"
            }
            expireAfter = "8760h"  # 1년 (On-Demand는 교체 최소화)
          }
        }
        limits = {
          cpu = "16"  # minReplicas 전체 수용 (12코어) + 여유
        }
        disruption = {
          consolidationPolicy = "WhenEmptyOrUnderutilized"
          consolidateAfter    = "10m"  # prod는 여유 있게
        }
      }
    }
  }

  # ② [기존 수정] service-spot NodePool - 버스트 전용으로 역할 변경
  resource "kubernetes_manifest" "karpenter_nodepool_spot" {
    manifest = {
      apiVersion = "karpenter.sh/v1"
      kind       = "NodePool"
      metadata = {
        name = "service-spot"
      }
      spec = {
        weight = 10  # 100 → 10 (On-Demand 한도 찬 뒤에 사용)
        template = {
          # ... 나머지는 동일
        }
        limits = {
          cpu = "100"  # 20 → 100 (HPA 최대까지 버스트 가능하게)
        }
        disruption = {
          consolidationPolicy = "WhenEmptyOrUnderutilized"
          consolidateAfter    = "10m"  # 1m → 10m
        }
      }
    }
  }
728x90
반응형

'DevOps' 카테고리의 다른 글

CSI, CRD, CRI, CNI  (0) 2026.05.17
Kubernetes Architecture  (0) 2026.05.16
Karpenter + Spot 조합을 알아보자 (2)  (0) 2026.05.15
Karpenter + Spot 조합을 알아보자 (1)  (0) 2026.05.13
노드에 파드 할당하기  (0) 2026.05.13