스케일링 전략
지원 버전: Kubernetes 1.28+, KEDA 2.14+, VPA 1.0+ 마지막 업데이트: 2026년 2월 21일
< 이전: GitOps 자동화 | 목차 | 다음: 운영 알림 구성 >
개요
EKS 클러스터의 효율적인 스케일링은 성능, 비용, 안정성의 균형을 맞추는 핵심 운영 역량입니다. 이 문서에서는 CPU/메모리 외의 커스텀 메트릭을 활용한 HPA, 이벤트 드리븐 스케일링을 위한 KEDA, 리소스 최적화를 위한 VPA, 그리고 비용 효율적인 Spot 인스턴스 활용 전략을 다룹니다.
학습 목표
- Prometheus 메트릭 기반 HPA 커스텀 메트릭 구성
- KEDA를 활용한 다양한 이벤트 소스 기반 스케일링
- VPA와 HPA의 효과적인 조합 전략 이해
- Pod Deletion Cost를 활용한 스케일링 우선순위 제어
- Spot 인스턴스의 안전한 활용 및 Fallback 전략
1. HPA Custom Metrics
기본 CPU/메모리 메트릭 외에 RPS, 큐 깊이, 비즈니스 메트릭 등을 기반으로 스케일링할 수 있습니다.
1.1 Prometheus Adapter 설치
Helm values.yaml:
# prometheus-adapter-values.yaml
prometheus:
url: http://prometheus.monitoring.svc
port: 9090
replicas: 2
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# 커스텀 메트릭 규칙
rules:
default: false
# 외부 메트릭 (External Metrics)
external: []
# 리소스 메트릭 (기본 CPU/Memory 대체)
resource:
cpu:
containerQuery: |
sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>}[3m])) by (<<.GroupBy>>)
nodeQuery: |
sum(rate(container_cpu_usage_seconds_total{<<.LabelMatchers>>, id='/'}[3m])) by (<<.GroupBy>>)
resources:
overrides:
namespace:
resource: namespace
node:
resource: node
pod:
resource: pod
containerLabel: container
memory:
containerQuery: |
sum(container_memory_working_set_bytes{<<.LabelMatchers>>}) by (<<.GroupBy>>)
nodeQuery: |
sum(container_memory_working_set_bytes{<<.LabelMatchers>>,id='/'}) by (<<.GroupBy>>)
resources:
overrides:
namespace:
resource: namespace
node:
resource: node
pod:
resource: pod
containerLabel: container
# 커스텀 메트릭 규칙
custom:
# HTTP RPS 메트릭
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace:
resource: namespace
pod:
resource: pod
name:
matches: "^(.*)_total$"
as: "${1}_per_second"
metricsQuery: |
sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)
# HTTP 요청 지연시간
- seriesQuery: 'http_request_duration_seconds_bucket{namespace!="",pod!=""}'
resources:
overrides:
namespace:
resource: namespace
pod:
resource: pod
name:
matches: "^(.*)_bucket$"
as: "${1}_p99"
metricsQuery: |
histogram_quantile(0.99, sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (le, <<.GroupBy>>))
# 활성 연결 수
- seriesQuery: 'nginx_connections_active{namespace!="",pod!=""}'
resources:
overrides:
namespace:
resource: namespace
pod:
resource: pod
name:
matches: "^(.*)$"
as: "${1}"
metricsQuery: |
sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
# 큐 깊이 (Redis)
- seriesQuery: 'redis_queue_length{namespace!="",service!=""}'
resources:
overrides:
namespace:
resource: namespace
service:
resource: service
name:
matches: "^(.*)$"
as: "${1}"
metricsQuery: |
sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
# ServiceMonitor 생성
serviceMonitor:
enabled: true
namespace: monitoring
# PodDisruptionBudget
podDisruptionBudget:
enabled: true
minAvailable: 1
# 높은 가용성 설정
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app.kubernetes.io/name: prometheus-adapter
topologyKey: kubernetes.io/hostnameHelm 설치:
# Prometheus Adapter Helm repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 설치
helm upgrade --install prometheus-adapter prometheus-community/prometheus-adapter \
-n monitoring \
-f prometheus-adapter-values.yaml
# 설치 확인
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq .
kubectl get --raw /apis/external.metrics.k8s.io/v1beta1 | jq .1.2 RPS 기반 HPA
초당 요청 수(RPS)를 기반으로 Pod를 스케일링합니다.
애플리케이션 메트릭 노출:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
spec:
containers:
- name: myapp
image: myapp:v1.0.0
ports:
- containerPort: 8080
name: http
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
---
# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: myapp
namespace: production
labels:
release: prometheus
spec:
selector:
matchLabels:
app: myapp
endpoints:
- port: http
path: /metrics
interval: 15s
namespaceSelector:
matchNames:
- productionPromQL 쿼리 테스트:
# Prometheus에서 직접 쿼리 테스트
curl -s "http://prometheus.monitoring:9090/api/v1/query" \
--data-urlencode 'query=sum(rate(http_requests_total{namespace="production",service="myapp"}[2m]))' | jq
# 예상 결과: 전체 RPS
# {"status":"success","data":{"resultType":"vector","result":[{"metric":{},"value":[1234567890,"150.5"]}]}}
# Pod당 RPS
curl -s "http://prometheus.monitoring:9090/api/v1/query" \
--data-urlencode 'query=sum(rate(http_requests_total{namespace="production",service="myapp"}[2m])) by (pod)' | jqRPS 기반 HPA:
# hpa-rps.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-rps
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 50
metrics:
# 커스텀 메트릭: Pod당 RPS
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100" # Pod당 100 RPS 목표
# 스케일링 동작 세부 설정
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # 즉시 스케일 업
policies:
- type: Percent
value: 100 # 현재 replicas의 100%까지 증가 가능
periodSeconds: 15
- type: Pods
value: 4 # 또는 한 번에 최대 4개 추가
periodSeconds: 15
selectPolicy: Max # 두 정책 중 큰 값 선택
scaleDown:
stabilizationWindowSeconds: 300 # 5분 안정화 기간
policies:
- type: Percent
value: 10 # 15초마다 최대 10% 감소
periodSeconds: 15
- type: Pods
value: 2 # 또는 한 번에 최대 2개 감소
periodSeconds: 60
selectPolicy: Min # 두 정책 중 작은 값 선택 (보수적)1.3 CloudWatch External Metrics
CloudWatch 메트릭을 HPA에서 사용하기 위해 CloudWatch Exporter를 설치합니다.
CloudWatch Exporter 설정:
# cloudwatch-exporter-values.yaml
serviceAccount:
create: true
name: cloudwatch-exporter
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/CloudWatchExporterRole
config: |
region: ap-northeast-2
metrics:
# SQS 큐 메트릭
- aws_namespace: AWS/SQS
aws_metric_name: ApproximateNumberOfMessagesVisible
aws_dimensions:
- QueueName
aws_statistics:
- Average
aws_tag_select:
resource_type_selection: "sqs:queue"
resource_id_dimension: QueueName
# RDS 메트릭
- aws_namespace: AWS/RDS
aws_metric_name: DatabaseConnections
aws_dimensions:
- DBInstanceIdentifier
aws_statistics:
- Average
# ALB 메트릭
- aws_namespace: AWS/ApplicationELB
aws_metric_name: RequestCount
aws_dimensions:
- LoadBalancer
- TargetGroup
aws_statistics:
- Sum
period_seconds: 60
# Lambda 메트릭
- aws_namespace: AWS/Lambda
aws_metric_name: Invocations
aws_dimensions:
- FunctionName
aws_statistics:
- SumExternal Metrics 기반 HPA:
# hpa-external.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: sqs-worker
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: sqs-worker
minReplicas: 1
maxReplicas: 100
metrics:
# External 메트릭: SQS 큐 메시지 수
- type: External
external:
metric:
name: sqs_approximate_number_of_messages_visible
selector:
matchLabels:
queue_name: "my-queue"
target:
type: AverageValue
averageValue: "10" # Pod당 10개 메시지 처리 목표
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 10
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 601.4 HPA Behavior 상세 설정
스케일링 속도 제어:
# hpa-behavior-detailed.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-detailed
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 5
maxReplicas: 100
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"
behavior:
# 스케일 업 정책
scaleUp:
# 안정화 기간: 이 시간 동안 가장 높은 권장 replica 수 유지
stabilizationWindowSeconds: 0
policies:
# 정책 1: 현재 replicas의 100%까지 15초마다 증가
- type: Percent
value: 100
periodSeconds: 15
# 정책 2: 한 번에 최대 10개 Pod 추가
- type: Pods
value: 10
periodSeconds: 15
# 정책 선택 방식: Max (더 공격적), Min (더 보수적), Disabled
selectPolicy: Max
# 스케일 다운 정책
scaleDown:
# 안정화 기간: 300초 동안 가장 높은 권장 replica 수 유지
# -> 일시적 부하 감소에 과민 반응 방지
stabilizationWindowSeconds: 300
policies:
# 정책 1: 60초마다 현재 replicas의 10% 감소
- type: Percent
value: 10
periodSeconds: 60
# 정책 2: 120초마다 최대 5개 Pod 감소
- type: Pods
value: 5
periodSeconds: 120
# 정책 선택 방식: Min (보수적으로 감소)
selectPolicy: MinBehavior 동작 설명:
ScaleUp (부하 증가 시):
┌─────────────────────────────────────────────────────────────┐
│ 현재: 10 replicas, 목표: 25 replicas │
│ │
│ Policy 1 (Percent 100%): 10 * 2 = 20 (15초 후) │
│ Policy 2 (Pods 10): 10 + 10 = 20 (15초 후) │
│ │
│ selectPolicy: Max → 20개로 스케일 업 │
│ │
│ 15초 후: 20 replicas │
│ Policy 1: 20 * 2 = 40 (> 25, 제한됨) → 25 │
│ Policy 2: 20 + 10 = 30 (> 25, 제한됨) → 25 │
│ │
│ 30초 후: 25 replicas (목표 달성) │
└─────────────────────────────────────────────────────────────┘
ScaleDown (부하 감소 시):
┌─────────────────────────────────────────────────────────────┐
│ 현재: 25 replicas, 목표: 10 replicas │
│ stabilizationWindow: 300초 │
│ │
│ T+0: 메트릭 감소 감지, 300초 대기 시작 │
│ T+300: 안정화 기간 종료 │
│ │
│ Policy 1 (Percent 10%): 25 * 0.9 = 22 (60초 후) │
│ Policy 2 (Pods 5): 25 - 5 = 20 (120초 후) │
│ │
│ selectPolicy: Min │
│ - T+360: 22개 (Policy 1 적용) │
│ - T+420: 20개 (더 작은 값 선택) │
│ - ...계속 감소... │
│ - 최종: 10 replicas │
└─────────────────────────────────────────────────────────────┘1.5 테스트 및 검증
# HPA 상태 확인
kubectl get hpa myapp-detailed -n production -o wide
# HPA 이벤트 확인
kubectl describe hpa myapp-detailed -n production
# 커스텀 메트릭 확인
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/http_requests_per_second" | jq
# 부하 테스트
kubectl run -it --rm load-generator --image=busybox:1.28 \
--restart=Never -- /bin/sh -c \
"while sleep 0.01; do wget -q -O- http://myapp.production:80/; done"
# 스케일링 이벤트 모니터링
kubectl get hpa -n production -w
# HPA 메트릭 상세 확인
kubectl get hpa myapp-detailed -n production -o yaml2. KEDA 이벤트 드리븐 스케일링
KEDA(Kubernetes Event-driven Autoscaling)는 다양한 이벤트 소스를 기반으로 워크로드를 스케일링합니다.
상세 내용은 KEDA 가이드를 참조하세요.
2.1 KEDA 아키텍처 요약
┌────────────────────────────────────────────────────────────────────┐
│ KEDA │
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ Metrics Server │ │ KEDA Operator │ │ Admission │ │
│ │ (custom.metrics)│ │ (ScaledObject) │ │ Webhooks │ │
│ └────────┬─────────┘ └────────┬─────────┘ └───────────────┘ │
│ │ │ │
│ │ ┌──────────────────┴──────────────────┐ │
│ │ │ Event Sources │ │
│ │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌───────┐ │ │
│ │ │ │ SQS │ │Kafka│ │Prom │ │Postgres│ │ │
│ │ │ └─────┘ └─────┘ └─────┘ └───────┘ │ │
│ │ └─────────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────┐ ┌─────────────┐ │
│ │ HPA │◀──────────│ ScaledObject│ │
│ └────┬────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Deployment │ │
│ │ (replicas) │ │
│ └─────────────┘ │
└────────────────────────────────────────────────────────────────────┘2.2 RPS ScaledObject (Prometheus Trigger)
# scaledobject-rps.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: myapp-rps
namespace: production
spec:
scaleTargetRef:
name: myapp
kind: Deployment
apiVersion: apps/v1
minReplicaCount: 3
maxReplicaCount: 100
# 0으로 스케일 다운 허용 여부
# idleReplicaCount: 0 # 트래픽 없을 때 0으로
# 쿨다운 기간 (초)
cooldownPeriod: 300
# 폴링 간격 (초)
pollingInterval: 15
# 고급 설정
advanced:
horizontalPodAutoscalerConfig:
name: myapp-rps-hpa
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
triggers:
# Prometheus 트리거: RPS 기반
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
metricName: http_requests_per_second
query: |
sum(rate(http_requests_total{namespace="production",service="myapp"}[2m]))
threshold: "500" # 전체 500 RPS 초과 시 스케일 업
activationThreshold: "50" # 50 RPS 이상일 때만 활성화
# 추가 트리거: CPU 기반 (백업)
- type: cpu
metricType: Utilization
metadata:
value: "70"2.3 PostgreSQL 세션 기반 스케일링
데이터베이스 연결 수를 기반으로 애플리케이션을 스케일링합니다.
# scaledobject-postgres.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: api-postgres
namespace: production
spec:
scaleTargetRef:
name: api-server
kind: Deployment
minReplicaCount: 2
maxReplicaCount: 20
pollingInterval: 30
cooldownPeriod: 300
triggers:
- type: postgresql
metadata:
host: mydb.cluster-xxx.ap-northeast-2.rds.amazonaws.com
port: "5432"
userName: keda_user
dbName: myapp
sslmode: require
# 활성 연결 수 쿼리
query: |
SELECT count(*) FROM pg_stat_activity
WHERE datname = 'myapp'
AND state = 'active'
targetQueryValue: "10" # Pod당 10개 활성 연결 목표
activationTargetQueryValue: "5"
authenticationRef:
name: postgres-auth
---
# TriggerAuthentication
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: postgres-auth
namespace: production
spec:
secretTargetRef:
- parameter: password
name: postgres-credentials
key: password2.4 SQS 큐 기반 스케일링
# scaledobject-sqs.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: sqs-processor
namespace: production
spec:
scaleTargetRef:
name: sqs-worker
kind: Deployment
minReplicaCount: 0 # 큐가 비어있으면 0으로
maxReplicaCount: 50
pollingInterval: 10
cooldownPeriod: 60
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.ap-northeast-2.amazonaws.com/111122223333/my-queue
queueLength: "5" # 메시지 5개당 1 replica
awsRegion: ap-northeast-2
# Dead Letter Queue 포함
# queueURLFromEnv: SQS_QUEUE_URL
authenticationRef:
name: aws-credentials
---
# Pod Identity 기반 인증
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: aws-credentials
namespace: production
spec:
podIdentity:
provider: aws2.5 Cron 기반 스케일링
예측 가능한 트래픽 패턴에 맞춰 미리 스케일링합니다.
# scaledobject-cron.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: myapp-cron
namespace: production
spec:
scaleTargetRef:
name: myapp
kind: Deployment
minReplicaCount: 3
maxReplicaCount: 100
triggers:
# 업무 시간 (09:00-18:00 KST)
- type: cron
metadata:
timezone: Asia/Seoul
start: 0 9 * * 1-5 # 평일 09:00
end: 0 18 * * 1-5 # 평일 18:00
desiredReplicas: "20"
# 점심 시간 피크 (11:30-13:30 KST)
- type: cron
metadata:
timezone: Asia/Seoul
start: 30 11 * * 1-5 # 평일 11:30
end: 30 13 * * 1-5 # 평일 13:30
desiredReplicas: "40"
# 야간 (18:00-09:00)
- type: cron
metadata:
timezone: Asia/Seoul
start: 0 18 * * 1-5 # 평일 18:00
end: 0 9 * * 2-6 # 다음날 09:00
desiredReplicas: "5"
# 주말
- type: cron
metadata:
timezone: Asia/Seoul
start: 0 0 * * 0,6 # 토/일 00:00
end: 59 23 * * 0,6 # 토/일 23:59
desiredReplicas: "3"
# 메트릭 기반 (Cron보다 우선)
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
query: |
sum(rate(http_requests_total{service="myapp"}[2m]))
threshold: "1000"2.6 복합 트리거 (AND/OR 로직)
# scaledobject-composite.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: myapp-composite
namespace: production
spec:
scaleTargetRef:
name: myapp
kind: Deployment
minReplicaCount: 3
maxReplicaCount: 100
# 복합 트리거 공식
# formula: "(trigger0 || trigger1) && trigger2" # 향후 지원 예정
triggers:
# 트리거 0: CPU
- type: cpu
metricType: Utilization
metadata:
value: "70"
# 트리거 1: RPS
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
query: sum(rate(http_requests_total{service="myapp"}[2m]))
threshold: "500"
# 트리거 2: 메모리
- type: memory
metricType: Utilization
metadata:
value: "80"
# 참고: 현재 KEDA는 OR 로직만 지원 (하나라도 임계값 초과 시 스케일)
# AND 로직이 필요한 경우 Prometheus 쿼리에서 조합2.7 ScaledJob (배치 처리)
일회성 작업을 위한 Job 스케일링입니다.
# scaledjob.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: batch-processor
namespace: production
spec:
jobTargetRef:
parallelism: 1
completions: 1
backoffLimit: 3
template:
spec:
containers:
- name: processor
image: batch-processor:v1
env:
- name: SQS_QUEUE_URL
value: https://sqs.ap-northeast-2.amazonaws.com/111122223333/batch-queue
resources:
requests:
cpu: 500m
memory: 512Mi
restartPolicy: Never
# 스케일링 설정
pollingInterval: 30
minReplicaCount: 0
maxReplicaCount: 100
# 성공한 Job 정리 (초)
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 5
# 스케일링 전략
scalingStrategy:
strategy: default # default, custom, accurate
# accurate: 메시지 수에 정확히 맞춤
# custom: 사용자 정의 메트릭 사용
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.ap-northeast-2.amazonaws.com/111122223333/batch-queue
queueLength: "1" # 메시지 1개당 Job 1개
awsRegion: ap-northeast-2
authenticationRef:
name: aws-credentials2.8 KEDA + HPA 상호작용
KEDA ScaledObject 생성
│
▼
KEDA가 HPA 자동 생성 (keda-hpa-{scaledobject-name})
│
▼
KEDA Metrics Server가 external.metrics.k8s.io 제공
│
▼
HPA가 KEDA 메트릭 사용하여 스케일링 결정
│
▼
Deployment replica 조정
주의사항:
- 동일 Deployment에 별도 HPA와 ScaledObject 동시 사용 금지
- ScaledObject가 HPA를 관리함
- 기존 HPA가 있다면 ScaledObject로 마이그레이션 필요3. VPA Pod Resize
VPA(Vertical Pod Autoscaler)는 Pod의 CPU/메모리 리소스 요청을 자동으로 조정합니다.
3.1 VPA 설치
# VPA Helm repo (비공식)
helm repo add cowboysysop https://cowboysysop.github.io/charts/
helm repo update
# 또는 공식 manifest로 설치
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.shHelm values:
# vpa-values.yaml
admissionController:
enabled: true
replicaCount: 2
resources:
requests:
cpu: 50m
memory: 128Mi
# MutatingWebhook 설정
mutatingWebhookConfiguration:
failurePolicy: Ignore # VPA 장애 시 Pod 생성 허용
recommender:
enabled: true
replicaCount: 1
resources:
requests:
cpu: 100m
memory: 256Mi
extraArgs:
- --storage=prometheus
- --prometheus-address=http://prometheus.monitoring:9090
- --history-length=48h
- --memory-aggregation-interval=24h
updater:
enabled: true
replicaCount: 1
resources:
requests:
cpu: 50m
memory: 128Mi
extraArgs:
- --min-replicas=2 # 최소 2 replicas 이상일 때만 업데이트3.2 UpdateMode 비교
| UpdateMode | 동작 | 사용 사례 |
|---|---|---|
| Off | 추천만 제공, 실제 변경 없음 | 분석 및 검토 단계 |
| Initial | Pod 생성 시에만 적용 | 기존 Pod 영향 최소화 |
| Recreate | Pod 재시작하여 적용 | 즉시 적용 필요 시 |
| Auto | Initial + Recreate 조합 | 완전 자동화 |
3.3 VPA CRD 예시
# vpa-recommendation-only.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: myapp-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
updatePolicy:
updateMode: "Off" # 추천만, 실제 변경 없음
resourcePolicy:
containerPolicies:
- containerName: myapp
# 리소스 경계 설정
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 4
memory: 8Gi
# 제어 대상 리소스
controlledResources:
- cpu
- memory
# 제어 모드
controlledValues: RequestsAndLimits
---
# vpa-auto.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: batch-worker-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: batch-worker
updatePolicy:
updateMode: "Auto"
# 최소 2개 replica 유지하면서 업데이트
minReplicas: 2
resourcePolicy:
containerPolicies:
- containerName: worker
minAllowed:
cpu: 250m
memory: 256Mi
maxAllowed:
cpu: 8
memory: 16Gi
controlledResources:
- cpu
- memory
---
# vpa-memory-only.yaml (HPA와 공존)
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: api
# 메모리만 VPA가 관리 (CPU는 HPA용)
controlledResources:
- memory
minAllowed:
memory: 256Mi
maxAllowed:
memory: 4Gi3.4 In-Place Pod Resize (KEP-1287)
Kubernetes 1.27+에서 Pod를 재시작하지 않고 리소스를 변경할 수 있습니다.
# deployment-resize-enabled.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v1
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 1000m
memory: 512Mi
# In-Place Resize 설정
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired # CPU 변경 시 재시작 불필요
- resourceName: memory
restartPolicy: RestartContainer # 메모리 변경 시 재시작 필요리사이즈 실행:
# 현재 리소스 확인
kubectl get pod myapp-xxx -n production -o jsonpath='{.spec.containers[0].resources}'
# kubectl patch로 리사이즈 (In-Place)
kubectl patch pod myapp-xxx -n production --subresource=resize -p '{
"spec": {
"containers": [{
"name": "myapp",
"resources": {
"requests": {"cpu": "200m"},
"limits": {"cpu": "2000m"}
}
}]
}
}'
# 리사이즈 상태 확인
kubectl get pod myapp-xxx -n production -o jsonpath='{.status.resize}'
# InProgress, Proposed, Infeasible, Deferred3.5 Goldilocks (최적 리소스 추천 대시보드)
# Goldilocks 설치
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm repo update
helm upgrade --install goldilocks fairwinds-stable/goldilocks \
-n goldilocks \
--create-namespace \
--set dashboard.enabled=true \
--set dashboard.service.type=LoadBalancer네임스페이스 활성화:
# 네임스페이스에 Goldilocks 레이블 추가
kubectl label namespace production goldilocks.fairwinds.com/enabled=true
# VPA가 자동으로 생성됨
kubectl get vpa -n production3.6 VPA + HPA 공존 전략
권장 패턴: CPU는 HPA, Memory는 VPA
# hpa-cpu.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
# vpa-memory.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: myapp-vpa
namespace: production
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: myapp
controlledResources:
- memory # 메모리만 VPA 관리
minAllowed:
memory: 256Mi
maxAllowed:
memory: 4Gi공존 시 주의사항:
┌───────────────────────────────────────────────────────────────┐
│ VPA + HPA 공존 권장 패턴 │
├───────────────────────────────────────────────────────────────┤
│ │
│ HPA VPA │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ CPU Utilization │ │ Memory Sizing │ │
│ │ Custom Metrics │ │ (controlledRes: │ │
│ │ External Metrics│ │ memory) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ │ Deployment │ │
│ └───────────┬───────────────┘ │
│ │ │
│ HPA: replicas 조정 │ VPA: resources.requests.memory 조정 │
│ ▼ │
│ ┌───────────────┐ │
│ │ Pods │ │
│ └───────────────┘ │
│ │
│ ⚠️ 주의: 동일 리소스(CPU)에 HPA와 VPA 동시 적용 금지 │
│ - HPA가 replica 조정 → VPA가 requests 변경 │
│ - → HPA 계산 기준 변경 → 무한 스케일링 루프 가능 │
│ │
└───────────────────────────────────────────────────────────────┘4. Custom Scheduler & Pod Deletion Cost
스케일 다운 시 어떤 Pod를 먼저 종료할지 제어할 수 있습니다.
상세 스케줄러 내용은 스케줄링 가이드를 참조하세요.
4.1 Pod Deletion Cost 개념
# Pod Annotation
metadata:
annotations:
controller.kubernetes.io/pod-deletion-cost: "100"동작 원리:
스케일 다운 시 Pod 삭제 우선순위:
1. controller.kubernetes.io/pod-deletion-cost 값이 낮은 Pod 먼저
2. 값이 같으면:
- Pending > Running
- Not Ready > Ready
- 최근 생성된 Pod > 오래된 Pod
- 더 많은 container restart > 적은 restart범위:
- 최소값: -2147483648 (먼저 삭제)
- 최대값: 2147483647 (마지막에 삭제)
- 기본값: 0
4.2 사용 사례
사례 1: Spot 노드 Pod 우선 삭제
# deployment-spot.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 10
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
# Spot 노드에 스케줄된 Pod는 낮은 cost
controller.kubernetes.io/pod-deletion-cost: "-100"
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: eks.amazonaws.com/capacityType
operator: In
values:
- SPOT
containers:
- name: myapp
image: myapp:v1
---
# deployment-ondemand.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-ondemand
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: myapp
tier: stable
template:
metadata:
labels:
app: myapp
tier: stable
annotations:
# On-Demand 노드에 스케줄된 Pod는 높은 cost
controller.kubernetes.io/pod-deletion-cost: "100"
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: eks.amazonaws.com/capacityType
operator: In
values:
- ON_DEMAND
containers:
- name: myapp
image: myapp:v1사례 2: 데이터 처리 Pod 보호
# statefulset-data-processor.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: data-processor
namespace: production
spec:
replicas: 5
selector:
matchLabels:
app: data-processor
template:
metadata:
labels:
app: data-processor
annotations:
# 데이터 처리 중인 Pod 보호
controller.kubernetes.io/pod-deletion-cost: "1000"
spec:
containers:
- name: processor
image: data-processor:v1
# 데이터 처리 완료 후 annotation 업데이트하는 로직 필요사례 3: 배치 작업 완료 후 우선 삭제
# batch-job-controller.yaml (컨셉)
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-worker
namespace: production
spec:
replicas: 10
template:
metadata:
annotations:
# 초기에는 높은 cost (작업 중)
controller.kubernetes.io/pod-deletion-cost: "500"
spec:
containers:
- name: worker
image: batch-worker:v1
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 작업 완료 시 deletion cost 낮추기
# (실제로는 외부 컨트롤러 필요)
echo "Batch job completed, reducing deletion cost"4.3 동적 Deletion Cost 관리 컨트롤러
# deletion-cost-controller.py
from kubernetes import client, config, watch
import json
config.load_incluster_config()
v1 = client.CoreV1Api()
# Spot 노드의 Pod에 낮은 deletion cost 부여
def update_deletion_cost_for_spot():
pods = v1.list_pod_for_all_namespaces(
label_selector="app=myapp"
)
for pod in pods.items:
node_name = pod.spec.node_name
if not node_name:
continue
node = v1.read_node(node_name)
capacity_type = node.metadata.labels.get(
'eks.amazonaws.com/capacityType', 'ON_DEMAND'
)
current_cost = pod.metadata.annotations.get(
'controller.kubernetes.io/pod-deletion-cost', '0'
)
if capacity_type == 'SPOT':
new_cost = '-100'
else:
new_cost = '100'
if current_cost != new_cost:
patch = {
'metadata': {
'annotations': {
'controller.kubernetes.io/pod-deletion-cost': new_cost
}
}
}
v1.patch_namespaced_pod(
name=pod.metadata.name,
namespace=pod.metadata.namespace,
body=patch
)
print(f"Updated {pod.metadata.name}: {current_cost} -> {new_cost}")
# 배치 작업 상태에 따른 deletion cost 조정
def watch_batch_status():
w = watch.Watch()
for event in w.stream(v1.list_namespaced_pod, namespace='production',
label_selector='app=batch-worker'):
pod = event['object']
event_type = event['type']
if event_type in ['ADDED', 'MODIFIED']:
# Pod 상태 확인
phase = pod.status.phase
conditions = pod.status.conditions or []
# Ready 상태이고 특정 annotation이 있으면 작업 완료로 판단
is_ready = any(
c.type == 'Ready' and c.status == 'True'
for c in conditions
)
job_completed = pod.metadata.annotations.get(
'batch.example.com/completed', 'false'
) == 'true'
if job_completed:
new_cost = '-500' # 완료된 작업은 먼저 삭제
elif is_ready:
new_cost = '500' # 진행 중인 작업은 보호
else:
new_cost = '0' # 준비 안된 Pod는 기본
current_cost = pod.metadata.annotations.get(
'controller.kubernetes.io/pod-deletion-cost', '0'
)
if current_cost != new_cost:
patch = {
'metadata': {
'annotations': {
'controller.kubernetes.io/pod-deletion-cost': new_cost
}
}
}
v1.patch_namespaced_pod(
name=pod.metadata.name,
namespace=pod.metadata.namespace,
body=patch
)
if __name__ == '__main__':
import threading
t1 = threading.Thread(target=update_deletion_cost_for_spot, daemon=True)
t2 = threading.Thread(target=watch_batch_status, daemon=True)
t1.start()
t2.start()
t1.join()
t2.join()5. Spot 노드 활용 전략
Spot 인스턴스를 활용하여 비용을 절감하면서 안정성을 유지하는 전략입니다.
5.1 Auto Mode NodePool with Spot
# nodepool-spot.yaml (EKS Auto Mode)
apiVersion: eks.amazonaws.com/v1
kind: NodePool
metadata:
name: general-spot
spec:
template:
spec:
nodeClassRef:
group: eks.amazonaws.com
kind: NodeClass
name: default
# Spot 인스턴스 설정
capacityType: Spot
# 인스턴스 타입 다양화 (가용성 향상)
instanceTypes:
- m6i.large
- m6i.xlarge
- m5.large
- m5.xlarge
- c6i.large
- c6i.xlarge
- r6i.large
- r6i.xlarge
# Taints
taints:
- key: eks.amazonaws.com/capacityType
value: SPOT
effect: NoSchedule
# Labels
labels:
capacity-type: spot
workload-type: stateless
# 스케일링 제한
limits:
cpu: 1000
memory: 2000Gi
# Disruption 설정
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 30s
---
# nodepool-ondemand.yaml
apiVersion: eks.amazonaws.com/v1
kind: NodePool
metadata:
name: general-ondemand
spec:
template:
spec:
nodeClassRef:
group: eks.amazonaws.com
kind: NodeClass
name: default
capacityType: OnDemand
instanceTypes:
- m6i.large
- m6i.xlarge
- m6i.2xlarge
labels:
capacity-type: on-demand
workload-type: stateful
limits:
cpu: 200
memory: 400Gi
disruption:
consolidationPolicy: WhenEmpty
consolidateAfter: 1h5.2 NodePool 분리 전략
┌────────────────────────────────────────────────────────────────────┐
│ NodePool 분리 전략 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ general-spot │ │ general-ondemand │ │
│ │ ──────────────── │ │ ──────────────── │ │
│ │ - Stateless 워크로드 │ │ - Stateful 워크로드 │ │
│ │ - Web servers │ │ - Databases │ │
│ │ - API servers │ │ - Message queues │ │
│ │ - Batch workers │ │ - Critical services │ │
│ │ │ │ │ │
│ │ Taint: │ │ No Taint │ │
│ │ capacityType=SPOT │ │ │ │
│ │ │ │ │ │
│ │ 비용: ~70% 절감 │ │ 비용: 기본 │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ 워크로드 배치: │
│ - Stateless + 내결함성 → Spot │
│ - Stateful + 중요도 높음 → On-Demand │
│ - 혼합 가능 (TopologySpread로 분산) │
│ │
└────────────────────────────────────────────────────────────────────┘5.3 Spot Interruption 처리
Node Termination Handler:
# aws-node-termination-handler.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: aws-node-termination-handler
namespace: kube-system
spec:
selector:
matchLabels:
app: aws-node-termination-handler
template:
metadata:
labels:
app: aws-node-termination-handler
spec:
nodeSelector:
eks.amazonaws.com/capacityType: SPOT
serviceAccountName: aws-node-termination-handler
hostNetwork: true
containers:
- name: handler
image: public.ecr.aws/aws-ec2/aws-node-termination-handler:v1.22.0
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: ENABLE_SPOT_INTERRUPTION_DRAINING
value: "true"
- name: ENABLE_SCHEDULED_EVENT_DRAINING
value: "true"
- name: ENABLE_REBALANCE_DRAINING
value: "true"
- name: DELETE_LOCAL_DATA
value: "true"
- name: GRACE_PERIOD
value: "120"
- name: WEBHOOK_URL
value: "https://hooks.slack.com/services/xxx"
- name: WEBHOOK_HEADERS
value: '{"Content-Type":"application/json"}'
- name: WEBHOOK_TEMPLATE
value: |
{
"text": "Spot Interruption: Node {{.NodeName}} is being terminated",
"attachments": [{
"color": "warning",
"fields": [
{"title": "Instance ID", "value": "{{.InstanceID}}", "short": true},
{"title": "Node", "value": "{{.NodeName}}", "short": true},
{"title": "Event Type", "value": "{{.EventType}}", "short": true}
]
}]
}
resources:
requests:
cpu: 50m
memory: 64Mi5.4 PDB + Pod Deletion Cost 조합
# pdb-spot-safe.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myapp-pdb
namespace: production
spec:
# 최소 가용 Pod 수
minAvailable: 3
# 또는 최대 비가용 비율
# maxUnavailable: 25%
selector:
matchLabels:
app: myapp
---
# deployment-spot-optimized.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 10
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
annotations:
# Spot 노드에서 실행되면 낮은 deletion cost
controller.kubernetes.io/pod-deletion-cost: "-100"
spec:
# Spot 노드 Toleration
tolerations:
- key: eks.amazonaws.com/capacityType
operator: Equal
value: SPOT
effect: NoSchedule
# Spot 노드 우선 선호
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: eks.amazonaws.com/capacityType
operator: In
values:
- SPOT
- weight: 20
preference:
matchExpressions:
- key: eks.amazonaws.com/capacityType
operator: In
values:
- ON_DEMAND
containers:
- name: myapp
image: myapp:v1
resources:
requests:
cpu: 200m
memory: 256Mi
# Graceful shutdown
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- sleep 15 && /app/graceful-shutdown.sh
terminationGracePeriodSeconds: 1205.5 TopologySpreadConstraints로 분산
# deployment-spread.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 12
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
topologySpreadConstraints:
# Spot/On-Demand 간 분산
- maxSkew: 2
topologyKey: eks.amazonaws.com/capacityType
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: myapp
# 가용영역 간 분산
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: myapp
# 노드 간 분산
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: myapp
tolerations:
- key: eks.amazonaws.com/capacityType
operator: Equal
value: SPOT
effect: NoSchedule
containers:
- name: myapp
image: myapp:v1분산 결과 예시:
가용영역 A 가용영역 B 가용영역 C
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Spot Node 1 │ │ Spot Node 3 │ │ Spot Node 5 │
│ Pod 1, Pod 2 │ │ Pod 5, Pod 6 │ │ Pod 9, Pod 10 │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ Spot Node 2 │ │ Spot Node 4 │ │ On-Demand Node 3│
│ Pod 3, Pod 4 │ │ Pod 7, Pod 8 │ │ Pod 11, Pod 12 │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ On-Demand Node 1│ │ On-Demand Node 2│ │ │
│ (백업) │ │ (백업) │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Spot 비율: 10/12 = 83%
On-Demand 비율: 2/12 = 17% (안전 마진)5.6 Graceful Shutdown 구현
# deployment-graceful.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
namespace: production
spec:
replicas: 10
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
# 종료 유예 시간 (Spot은 2분 사전 통지)
terminationGracePeriodSeconds: 120
containers:
- name: myapp
image: myapp:v1
ports:
- containerPort: 8080
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
echo "Received termination signal"
# 1. 새 요청 수락 중지 (LB에서 제외)
touch /tmp/unhealthy
# 2. 진행 중인 요청 완료 대기
sleep 15
# 3. 연결 정리
/app/graceful-shutdown.sh
# 4. 종료
echo "Graceful shutdown completed"
# Health checks
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 1
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
# Pod 우선순위 (Spot에서 중요)
priorityClassName: high-prioritygraceful-shutdown.sh 예시:
#!/bin/bash
# graceful-shutdown.sh
echo "Starting graceful shutdown..."
# 1. 새 연결 거부 (nginx의 경우)
# nginx -s quit
# 2. 데이터베이스 연결 정리
if [ -n "$DB_HOST" ]; then
echo "Closing database connections..."
# 연결 풀 정리 로직
fi
# 3. 캐시 플러시
if [ -n "$REDIS_HOST" ]; then
echo "Flushing cache..."
# Redis 연결 정리
fi
# 4. 메시지 큐 연결 정리
if [ -n "$SQS_QUEUE_URL" ]; then
echo "Draining message queue connections..."
# 진행 중인 메시지 처리 완료 대기
fi
# 5. 메트릭 전송 (최종 상태)
curl -s -X POST http://prometheus-pushgateway:9091/metrics/job/myapp/instance/${HOSTNAME} \
--data-binary @- << EOF
# TYPE shutdown_timestamp gauge
shutdown_timestamp $(date +%s)
EOF
echo "Graceful shutdown completed"
exit 05.7 비용 분석
Spot 절감 추정:
On-Demand 기준 비용 (월간):
- m6i.xlarge (4 vCPU, 16GB): $0.192/hr x 730hr = $140.16
- 10개 노드: $140.16 x 10 = $1,401.60
Spot 사용 시 (약 70% 할인):
- m6i.xlarge Spot: ~$0.058/hr x 730hr = $42.34
- 10개 노드: $42.34 x 10 = $423.40
월간 절감: $1,401.60 - $423.40 = $978.20 (70% 절감)
혼합 전략 (Spot 80%, On-Demand 20%):
- Spot 8개: $42.34 x 8 = $338.72
- On-Demand 2개: $140.16 x 2 = $280.32
- 합계: $619.04
- 절감: $1,401.60 - $619.04 = $782.56 (56% 절감)
권장: 80/20 혼합으로 안정성 확보하면서 50%+ 비용 절감5.8 Fallback 전략
# fallback-strategy.yaml
# 1. NodePool 우선순위 설정
apiVersion: eks.amazonaws.com/v1
kind: NodePool
metadata:
name: spot-priority
spec:
weight: 100 # 높은 우선순위
template:
spec:
capacityType: Spot
# ...
---
apiVersion: eks.amazonaws.com/v1
kind: NodePool
metadata:
name: ondemand-fallback
spec:
weight: 10 # 낮은 우선순위 (Spot 부족 시 사용)
template:
spec:
capacityType: OnDemand
# ...
---
# 2. Capacity Reservation (예약 용량)
# Terraform으로 관리
# resource "aws_ec2_capacity_reservation" "eks_fallback" {
# instance_type = "m6i.xlarge"
# instance_platform = "Linux/UNIX"
# availability_zone = "ap-northeast-2a"
# instance_count = 5
# instance_match_criteria = "targeted"
#
# tags = {
# Name = "eks-fallback-capacity"
# }
# }요약
스케일링 전략 선택 가이드
| 상황 | 권장 전략 |
|---|---|
| 웹 트래픽 기반 스케일링 | HPA + Prometheus Adapter (RPS 메트릭) |
| 큐 기반 워커 스케일링 | KEDA + SQS/Kafka 트리거 |
| 예측 가능한 트래픽 | KEDA Cron 트리거 + 메트릭 백업 |
| 리소스 최적화 | VPA (추천 모드) + Goldilocks |
| 비용 최적화 | Spot NodePool + Deletion Cost |
| 고가용성 요구 | TopologySpread + PDB + On-Demand 백업 |
핵심 포인트
- HPA Custom Metrics: Prometheus Adapter로 비즈니스 메트릭 기반 스케일링
- KEDA: 이벤트 드리븐 워크로드에 최적, 0으로 스케일 다운 지원
- VPA: HPA와 공존 시 리소스 분리 필수 (CPU vs Memory)
- Pod Deletion Cost: 스케일 다운 시 중요 Pod 보호
- Spot 활용: 70%+ 비용 절감 가능, 적절한 Fallback 필수
참고 자료
- Kubernetes HPA 공식 문서
- Prometheus Adapter
- KEDA 공식 문서
- VPA 공식 문서
- EKS Spot Best Practices
- Pod Deletion Cost
- KEDA 가이드
- 스케줄링 가이드
< 이전: GitOps 자동화 | 목차 | 다음: 운영 알림 구성 >