Affinity란?
Kubernetes 스케줄러는 기본적으로 리소스 여유가 있는 노드에 Pod을 배치한다. 하지만 실무에서는 “GPU 노드에만 ML Pod 배치”, “같은 서비스의 Pod을 다른 노드에 분산”, “캐시 서버와 같은 존에 배치” 같은 세밀한 제어가 필요하다. Affinity는 이런 스케줄링 선호도를 선언적으로 정의하는 메커니즘이다.
Affinity는 크게 Node Affinity(어떤 노드에 배치할 것인가)와 Pod Affinity/Anti-Affinity(다른 Pod과의 관계로 배치를 결정)로 나뉜다.
Node Affinity
nodeSelector의 상위 호환이다. 더 풍부한 연산자와 선호/필수 구분을 지원한다.
Required (필수 조건)
apiVersion: apps/v1
kind: Deployment
metadata:
name: ml-training
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
# GPU 노드에만 배치
- key: gpu-type
operator: In
values: ["nvidia-a100", "nvidia-v100"]
# 특정 존 제외
- key: topology.kubernetes.io/zone
operator: NotIn
values: ["ap-northeast-2c"]
containers:
- name: trainer
image: ml-trainer:latest
resources:
limits:
nvidia.com/gpu: 1
requiredDuringSchedulingIgnoredDuringExecution은 조건을 만족하는 노드가 없으면 Pod이 Pending 상태로 남는다. nodeSelectorTerms 내 여러 항목은 OR 관계, matchExpressions 내 항목은 AND 관계다.
Preferred (선호 조건)
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80 # 1~100, 높을수록 강한 선호
preference:
matchExpressions:
- key: node-type
operator: In
values: ["high-memory"]
- weight: 20
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["ap-northeast-2a"]
Preferred는 조건을 만족하지 못해도 다른 노드에 스케줄링된다. weight로 여러 선호 조건의 우선순위를 조절한다. 스케줄러는 각 노드에 대해 weight 합산 점수를 계산해 가장 높은 노드를 선택한다.
연산자 종류
| 연산자 | 의미 | 예시 |
|---|---|---|
In |
값 목록 중 하나와 일치 | gpu-type In [a100, v100] |
NotIn |
값 목록에 없음 | zone NotIn [2c] |
Exists |
키가 존재 (값 무관) | gpu Exists |
DoesNotExist |
키가 없음 | spot DoesNotExist |
Gt / Lt |
숫자 비교 | cpu-cores Gt 16 |
Pod Affinity: 함께 배치
특정 Pod이 실행 중인 노드/존에 함께 배치하고 싶을 때 사용한다. 캐시 서버와 API 서버를 같은 존에 두면 네트워크 지연을 줄일 수 있다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
template:
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["redis-cache"]
# 같은 존(zone)에 배치
topologyKey: topology.kubernetes.io/zone
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["redis-cache"]
# 가능하면 같은 노드까지
topologyKey: kubernetes.io/hostname
topologyKey가 핵심이다. 이 키의 라벨 값이 같은 노드를 “같은 토폴로지 도메인”으로 본다.
| topologyKey | 범위 |
|---|---|
kubernetes.io/hostname |
같은 노드 |
topology.kubernetes.io/zone |
같은 가용 영역 |
topology.kubernetes.io/region |
같은 리전 |
Pod Anti-Affinity: 분산 배치
고가용성의 핵심이다. 같은 서비스의 Pod을 다른 노드/존에 분산해 단일 장애점을 제거한다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 3
template:
metadata:
labels:
app: api-server
spec:
affinity:
podAntiAffinity:
# 필수: 같은 노드에 2개 이상 배치 금지
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["api-server"]
topologyKey: kubernetes.io/hostname
# 선호: 가능하면 다른 존에 분산
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["api-server"]
topologyKey: topology.kubernetes.io/zone
주의: required Anti-Affinity는 노드 수보다 replicas가 많으면 Pod이 Pending된다. replicas가 3인데 노드가 2대뿐이면 1개는 스케줄링 불가다. 이런 경우 preferred를 사용하자.
실전 조합 패턴
실무에서는 Node Affinity + Pod Anti-Affinity를 함께 사용하는 것이 일반적이다.
# 패턴: 웹 서버를 웹 전용 노드에 배치하되, 노드별로 분산
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role
operator: In
values: ["web"]
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["web-server"]
topologyKey: kubernetes.io/hostname
Affinity vs Topology Spread
Pod Anti-Affinity는 “같은 곳에 두지 마”라는 규칙이고, Topology Spread Constraints는 “균등하게 분산하라”는 규칙이다.
| 기능 | Anti-Affinity | Topology Spread |
|---|---|---|
| 목적 | 특정 Pod과 같은 곳에 배치 금지 | 도메인 간 균등 분산 |
| skew 제어 | 불가 (0 or 1만) | maxSkew로 세밀 제어 |
| 추천 | 절대 금지 조건 | 비율 기반 분산 |
디버깅
# Pod이 Pending인 이유 확인
kubectl describe pod <pod-name> | grep -A 10 Events
# 노드 라벨 확인
kubectl get nodes --show-labels
# 스케줄러가 고려하는 노드 점수 확인
kubectl get events --field-selector reason=FailedScheduling
# 특정 노드에 어떤 Pod이 있는지 확인
kubectl get pods -o wide --field-selector spec.nodeName=node-1
가장 흔한 실수는 topologyKey 오타, 라벨 불일치, required + 리소스 부족 조합이다. describe pod의 Events에서 원인을 정확히 알려준다.
정리
Affinity는 K8s 스케줄링의 핵심 도구다. Node Affinity로 워크로드를 적합한 하드웨어에 배치하고, Pod Anti-Affinity로 고가용성을 확보하며, Pod Affinity로 지연 시간에 민감한 서비스를 가까이 배치한다. required는 절대 조건, preferred는 최선 노력으로 구분하고, 노드 수와 replicas 관계를 항상 고려하자.