EKS Pod 스케줄링 & 가용성 패턴
📅 작성일: 2026-02-12 | 수정일: 2026-02-14 | ⏱️ 읽는 시간: 약 54분
📌 기준 환경: EKS 1.30+, Karpenter v1.x, Kubernetes 1.30+
1. 개요
Kubernetes의 Pod 스케줄링은 서비스 가용성, 성능, 비용 효율성에 직접적인 영향을 미치는 핵심 메커니즘입니다. 올바른 스케줄링 전략을 적용하면 다음과 같은 이점을 얻을 수 있습니다:
- 고가용성: 장애 도메인 분리를 통한 서비스 중단 최소화
- 성능 최적화: 워크로드 특성에 맞는 노드 배치로 응답 시간 개선
- 리소스 효율: 노드 리소스의 균형 있는 활용으로 비용 절감
- 안정적 운영: 우선순위 기반 리소스 보장 및 Preemption 제어
본 문서는 Pod 스케줄링의 핵심 개념부터 고급 패턴까지 다루며, EKS 환경에서 실전 적용 가능한 YAML 예시와 의사결정 가이드를 제공합니다.
본 문서는 Pod 수준의 스케줄링 패턴에 초점을 맞춥니다. 클러스터 전체의 고가용성 아키텍처(Multi-AZ 전략, Topology Spread, Cell Architecture)는 EKS 고가용성 아키텍처 가이드를 참조하세요.
스케줄링이 중요한 이유
| 시나리오 | 잘못된 스케줄링 | 올바른 스케줄링 |
|---|---|---|
| 장애 격리 | 모든 replica가 같은 노드 → 노드 장애 시 전체 중단 | Anti-Affinity로 노드 분산 → 부분 장애만 발생 |
| 리소스 경합 | CPU 집약적 Pod들이 한 노드에 집중 → 성능 저하 | Node Affinity로 워크로드 분리 → 안정적 성능 |
| 비용 최적화 | GPU 필요 없는 Pod가 GPU 노드에 배치 → 비용 낭비 | Taints/Tolerations로 전용 노드 격리 → 비용 절감 |
| 업그레이드 안전성 | PDB 미설정 → 롤링 업데이트 중 서비스 중단 | PDB 설정 → 최소 가용 Pod 보장 |
| 긴급 대응 | 우선순위 미설정 → 중요 워크로드 Pending | PriorityClass 설정 → 중요 Pod 우선 스케줄링 |
2. Kubernetes 스케줄링 기본 원리
2.1 스케줄링 프로세 스
Kubernetes 스케줄러는 3단계 프로세스를 거쳐 Pod를 노드에 배치합니다:
1. Filtering (Predicates): 요구사항을 충족하지 못하는 노드를 제외
- 리소스 부족 (CPU, Memory)
- Taints/Tolerations 불일치
- Node Selector 조건 미충족
- Volume 토폴로지 제약 (EBS AZ-Pinning)
- Port 충돌
2. Scoring (Priorities): 남은 노드들에 점수를 매겨 최적의 노드 선택
- 리소스 밸런스 (균등 사용)
- Pod Affinity/Anti-Affinity 만족도
- 이미지 캐시 존재 여부
- Topology Spread 균등도
- 노드 Preference (PreferredDuringScheduling)
3. Binding: 최고 점수 노드에 Pod를 할당하고 Kubelet에 통보
Pod가 Pending 상태로 남아있다면, kubectl describe pod <pod-name>으로 Events 섹 션을 확인하세요. Insufficient cpu, No nodes available, Taint not tolerated 등의 메시지로 실패 원인을 파악할 수 있습니다.
2.2 스케줄링에 영향을 주는 요소
| 요소 | 타입 | 영향 단계 | 강제성 | 주요 사용 사례 |
|---|---|---|---|---|
| Node Selector | Pod | Filtering | Hard | 특정 노드 타입 지정 (GPU, ARM) |
| Node Affinity | Pod | Filtering/Scoring | Hard/Soft | 세밀한 노드 선택 조건 |
| Pod Affinity | Pod | Scoring | Hard/Soft | 관련 Pod를 가까이 배치 |
| Pod Anti-Affinity | Pod | Filtering/Scoring | Hard/Soft | Pod를 서로 멀리 배치 |
| Taints/Tolerations | Node + Pod | Filtering | Hard | 전용 노드 격리 |
| Topology Spread | Pod | Scoring | Hard/Soft | AZ/노드 간 균등 분산 |
| PriorityClass | Pod | Preemption | Hard | 우선순위 기반 리소스 선점 |
| Resource Requests | Pod | Filtering | Hard | 최소 리소스 보장 |
| PDB | Pod Group | Eviction | Hard | 최소 가용 Pod 보장 |
Hard vs Soft 제약:
- Hard (Required): 조건을 충족하지 못하면 스케줄링 실패 →
Pending상태 - Soft (Preferred): 조건을 선호하지만 충족하지 못해도 스케줄링 진행 → 차선책 허용
3. Node Affinity & Anti-Affinity
3.1 Node Selector (기본)
Node Selector는 가장 간단한 노드 선택 메커니즘으로, 레이블 기반 정확한 일치(exact match)만 지원합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: gpu-workload
spec:
replicas: 2
selector:
matchLabels:
app: ml-training
template:
metadata:
labels:
app: ml-training
spec:
nodeSelector:
node.kubernetes.io/instance-type: g5.2xlarge
workload-type: gpu
containers:
- name: trainer
image: ml/trainer:v2.0
resources:
requests:
nvidia.com/gpu: 1
제한사항: Node Selector는 AND 조건만 지원하며, OR, NOT, 비교 연산자 등을 사용할 수 없습니다. 복잡한 조건이 필요하면 Node Affinity를 사용하세요.
3.2 Node Affinity 상세
Node Affinity는 Node Selector의 확장 버전으로, 복잡한 논리 조건과 선호도(preference)를 표현할 수 있습니다.
Required vs Preferred
| 타입 | 동작 | 사용 시기 |
|---|---|---|
requiredDuringSchedulingIgnoredDuringExecution | 조건 충족 필수 (Hard) | 반드시 특정 노드에 배치해야 할 때 |
preferredDuringSchedulingIgnoredDuringExecution | 조건 선호 (Soft, 가중치 기반) | 선호하지만 대안 허용할 때 |
IgnoredDuringExecution은 Pod가 이미 실행 중일 때 노드 레이블이 변경되어도 Pod를 Evict하지 않는다는 의미입니다. 미래에 RequiredDuringExecution이 도입되면 실행 중에도 조건 불충족 시 재배치됩니다.
연산자 종류
| 연산자 | 설명 | 예시 |
|---|---|---|
In | 값이 목록에 포함됨 | values: ["t3.xlarge", "t3.2xlarge"] |
NotIn | 값이 목록에 포함되지 않음 | values: ["t2.micro", "t2.small"] |
Exists | 키가 존재함 (값 무관) | 레이블 존재 여부만 확인 |
DoesNotExist | 키가 존재하지 않음 | 특정 레이블이 없는 노드 선택 |
Gt | 값이 크다 (숫자) | values: ["100"] (CPU 코어 수 등) |
Lt | 값이 작다 (숫자) | values: ["10"] |
사용 사례별 YAML 예시
예시 1: GPU 노드에 ML 워크로드 배치 (Hard)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ml-training
spec:
replicas: 3
selector:
matchLabels:
app: ml-training
template:
metadata:
labels:
app: ml-training
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values:
- g5.xlarge
- g5.2xlarge
- g5.4xlarge
- key: karpenter.sh/capacity-type
operator: NotIn
values:
- spot # GPU 워크로드는 Spot 제외
containers:
- name: trainer
image: ml/trainer:v3.0
resources:
requests:
nvidia.com/gpu: 1
cpu: "4"
memory: 16Gi
예시 2: 인스턴스 패밀리 선호 (Soft, 가중치)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 6
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
affinity:
nodeAffinity:
# 필수: On-Demand 노드만 사용
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: karpenter.sh/capacity-type
operator: In
values:
- on-demand
# 선호: c7i > c6i > m6i 순서
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values:
- c7i.xlarge
- c7i.2xlarge
- weight: 80
preference:
matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values:
- c6i.xlarge
- c6i.2xlarge
- weight: 50
preference:
matchExpressions:
- key: node.kubernetes.io/instance-type
operator: In
values:
- m6i.xlarge
- m6i.2xlarge
containers:
- name: api
image: api-server:v2.5
resources:
requests:
cpu: "1"
memory: 2Gi
예시 3: 특정 AZ 지정 (데이터베이스 클라이언트)
apiVersion: apps/v1
kind: Deployment
metadata:
name: db-client
spec:
replicas: 4
selector:
matchLabels:
app: db-client
template:
metadata:
labels:
app: db-client
spec:
affinity:
nodeAffinity:
# RDS 인스턴스와 같은 AZ (us-east-1a)에 배치하여 Cross-AZ 비용 절감
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
containers:
- name: client
image: db-client:v1.2
env:
- name: DB_ENDPOINT
value: "mydb.us-east-1a.rds.amazonaws.com"
3.3 Node Anti-Affinity
Node Anti-Affinity는 명시적인 문법이 없지만, Node Affinity의 NotIn, DoesNotExist 연산자로 구현합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: avoid-spot
spec:
replicas: 3
selector:
matchLabels:
app: critical-service
template:
metadata:
labels:
app: critical-service
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
# Spot 노드 회피
- key: karpenter.sh/capacity-type
operator: NotIn
values:
- spot
# ARM 아키텍처 회피
- key: kubernetes.io/arch
operator: NotIn
values:
- arm64
containers:
- name: app
image: critical-service:v1.0
4. Pod Affinity & Anti-Affinity
Pod Affinity와 Anti-Affinity는 Pod 간의 관계를 기반으로 스케줄링 결정을 내립니다. 이를 통해 관련된 Pod들을 가까이 배치하거나(Affinity), 멀리 배치(Anti-Affinity)할 수 있습니다.
4.1 Pod Affinity
Pod Affinity는 특정 Pod가 있는 토폴로지 도메인(노드, AZ, 리전)에 다른 Pod를 함께 배치합니다.
주요 사용 사례:
- Cache Locality: 캐시 서버와 애플리케이션을 같은 노드에 배치하여 레이턴시 최소화
- Data Locality: 데이터 처리 워크로드를 데이터 소스와 가까이 배치
- Communication Intensive: 빈번하게 통신하는 마이크로서비스를 같은 AZ에 배치
apiVersion: apps/v1
kind: Deployment
metadata:
name: cache-client
spec:
replicas: 3
selector:
matchLabels:
app: cache-client
template:
metadata:
labels:
app: cache-client
spec:
affinity:
podAffinity:
# Hard: Redis Pod와 같은 노드에 배치 (초저지연 요구사항)
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: kubernetes.io/hostname
containers:
- name: client
image: cache-client:v1.0
topologyKey 설명:
| topologyKey | 범위 | 설명 |
|---|---|---|
kubernetes.io/hostname | 노드 | 같은 노드에 배치 (가장 강력한 co-location) |
topology.kubernetes.io/zone | AZ | 같은 AZ에 배치 |
topology.kubernetes.io/region | 리전 | 같은 리전에 배치 |
| 커스텀 레이블 | 사용자 정의 | 예: rack, datacenter |
Soft Affinity 예시 (선호, 대안 허용):
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
spec:
replicas: 6
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
spec:
affinity:
podAffinity:
# Soft: API 서버와 같은 AZ 선호 (Cross-AZ 비용 절감)
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- api-server
topologyKey: topology.kubernetes.io/zone
containers:
- name: frontend
image: web-frontend:v2.0
4.2 Pod Anti-Affinity
Pod Anti-Affinity는 특정 Pod가 있는 토폴로지 도메인에 다른 Pod를 배치하지 않도록 합니다. 고가용성 확보의 핵심 패턴입니다.
Hard Anti-Affinity (장애 도메인 격리)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 6
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
affinity:
podAntiAffinity:
# Hard: 각 노드에 최대 1개 replica만 배치 (노드 장애 격리)
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- api-server
topologyKey: kubernetes.io/hostname
containers:
- name: api
image: api-server:v3.0
resources:
requests:
cpu: "1"
memory: 2Gi
Hard Anti-Affinity를 kubernetes.io/hostname에 적용하면, replica 수가 노드 수보다 많을 때 일부 Pod가 Pending 상태로 남습니다. 예를 들어 노드 3개에 replica 5개를 배포하면 2개가 스케줄링되지 않습니다. 이 경우 Soft Anti-Affinity를 사용하세요.
Soft Anti-Affinity (권장 패턴)
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 10
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
affinity:
podAntiAffinity:
# Soft: 가능한 한 다른 노드에 분산 배치 (유연성 확보)
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- worker
topologyKey: kubernetes.io/hostname
containers:
- name: worker
image: worker:v2.1
resources:
requests:
cpu: "500m"
memory: 1Gi
Hard vs Soft 선택 기준
| 시나리오 | 권장 | 이유 |
|---|---|---|
| replica 수 ≤ 노드 수 | Hard | 각 노드에 정확히 1개씩 배치 가능 |
| replica 수 > 노드 수 | Soft | 일부 노드에 2개 이상 배치 허용 |
| 미션 크리티컬 서비스 | Hard (AZ 레벨) | 장애 도메인 완전 격리 |
| 일반 워크로드 | Soft | 스케줄링 유연성 확보 |
| 빠른 스케일링 필요 | Soft | Pending 상태 방지 |
4.3 Affinity/Anti-Affinity vs Topology Spread 비교
| 비교 항목 | Pod Anti-Affinity | Topology Spread Constraints |
|---|---|---|
| 목적 | Pod 간 분리 | Pod 균등 분산 |
| 세밀함 | Pod 단위 제어 | 도메인 간 균형 제어 |
| 복잡성 | 낮음 | 중간 |
| 유연성 | Hard/Soft 선택 | maxSkew로 허용 범위 제어 |
| 주요 사용 | 같은 앱 replica 분리 | 여러 앱의 전체 균형 |
| AZ 분산 | 가능 | 더 정교함 (minDomains) |
| 노드 분산 | 가능 | 더 정교함 (maxSkew) |
| 권장 조합 | Topology Spread (AZ) + Anti-Affinity (노드) |
Topology Spread Constraints는 Pod Anti-Affinity보다 더 정교한 분산 제어를 제공합니다. 자세한 내용과 YAML 예시는 EKS 고가용성 아키텍처 가이드를 참조하세요.
4.3.1 Topology Spread Constraints 실전 패턴
Topology Spread Constraints는 복잡한 분산 요구사항을 우아하게 해결합니다. 실제 프로덕션 환경에서 자주 사용되는 패턴을 YAML과 함께 소개합니다.
패턴 1: Multi-AZ 균등 분배 (기본)
가장 일반적인 패턴으로, 모든 replica를 AZ 간에 균등하게 분산시킵니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: multi-az-app
namespace: production
spec:
replicas: 9
selector:
matchLabels:
app: multi-az-app
template:
metadata:
labels:
app: multi-az-app
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: multi-az-app
containers:
- name: app
image: myapp:v1.0
resources:
requests:
cpu: 500m
memory: 512Mi
동작 방식:
maxSkew: 1: AZ 간 Pod 수 차이가 최대 1개까지 허용- 9개 replica → us-east-1a(3), us-east-1b(3), us-east-1c(3)
whenUnsatisfiable: DoNotSchedule: 조건 위반 시 Pod를 Pending 상태로 유지
사용 시나리오:
- 미션 크리티컬 서비스의 AZ 장애 대응
- 클라이언트 트래픽이 모든 AZ에서 균등하게 들어오는 경우
- 데이터센터 수준의 장애 격리가 필요한 경우
패턴 2: minDomains 활용 (최소 AZ 보장)
minDomains는 Pod가 반드시 분산되어야 하는 최소 도메인(AZ) 수를 보장합니다. AZ 축소 시나리오에서 Pod가 한 곳으로 밀리는 것을 방지합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: ha-critical-service
namespace: production
spec:
replicas: 6
selector:
matchLabels:
app: ha-critical-service
tier: critical
template:
metadata:
labels:
app: ha-critical-service
tier: critical
spec:
topologySpreadConstraints:
- maxSkew: 1
minDomains: 3 # 반드시 3개 AZ에 분산
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: ha-critical-service
containers:
- name: service
image: critical-service:v2.5
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
cpu: "2"
memory: 2Gi
동작 방식:
minDomains: 3: 최소 3개 AZ에 Pod 분산 보장- 6개 replica → 각 AZ에 최소 2 개씩 배치
- 특정 AZ가 리소스 부족이어도, 다른 AZ로만 몰리지 않음
사용 시나리오:
- 금융, 결제 시스템 등 초고가용성 요구 서비스
- SLA 99.99% 이상 보장 필요 시
- AZ 축소(Zonal Shift) 중에도 최소 가용성 유지
minDomains를 설정하면 해당 수만큼의 도메인이 존재하지 않거나 리소스가 부족할 경우, Pod가 Pending 상태로 남습니다. 클러스터에 실제로 사용 가능한 AZ 수를 확인 후 설정하세요.
패턴 3: Anti-Affinity + Topology Spread 조합
같은 노드에 replica를 2개 이상 배치하지 않으면서, 동시에 AZ 간 균등 분배를 보장하는 패턴입니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: combined-constraints-app
namespace: production
spec:
replicas: 12
selector:
matchLabels:
app: combined-app
template:
metadata:
labels:
app: combined-app
version: v3.0
spec:
# 1. Topology Spread: AZ 간 균등 분산 (Hard)
topologySpreadConstraints:
- maxSkew: 1
minDomains: 3
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: combined-app
# 2. Anti-Affinity: 노드 간 분산 (Hard)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- combined-app
topologyKey: kubernetes.io/hostname
containers:
- name: app
image: combined-app:v3.0
resources:
requests:
cpu: "2"
memory: 4Gi
동작 방식:
- Level 1 (AZ): 12개 replica → 각 AZ에 4개씩 균등 배치
- Level 2 (Node): 각 노드에 최대 1개 Pod만 배치
효과:
- 노드 장애 시 최대 1개 Pod만 영향
- AZ 장애 시 최대 4개 Pod만 영향
- 총 12개 중 8개(66.7%) 항상 가용
사용 시나리오:
- 단일 장애점(Single Point of Failure) 완전 제거
- 하드웨어 장애와 데이터센터 장애 모두 대응
- 고트래픽 API 서버, 결제 게이트웨이