운영으로 넘어갔을 시 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
}
}
}
}'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 |