0. Contents
1. Overview
배치 제어 방법 (간단 → 복잡 순서)
| 방법 | 설명 |
|---|---|
| nodeSelector | 특정 라벨 가진 노드에만 배치 |
| Node Affinity | 필수/선호 조건으로 노드 선택 |
| Pod Affinity/Antiaffinity | 다른 Pod 위치 기준으로 배치 |
| Taints/Tolerations | 노드가 Pod를 거부/허용 |
2. Why Automated Placement?
문제
마이크로서비스 시스템
- 수십 ~ 수백 개의 격리된 프로세스
- 컨테이너 간 의존성, 노드 의존성 (SSD, GPU 등), 리소스 요구사항
- 이 모든 것이 시간에 따라 변함
배치가 미치는 영향
- 배치가 분산 시스템의 가용성, 성능, 용량에 직접 영향을 미친다
- 잘못 배치한 경우
- 한 노드 장애 시 서비스 전체가 다운된다
- 리소스 경쟁으로 성능이 저하된다
스케줄러가 하는 일
- API 서버에서 새 Pod 정의 가져옴 → 모든 노드와 정책 고려
- 1단계: Predicates 필터링 → 자격 없는 노드 탈락
- 2단계: Priorities 점수 매기기 → 남은 노드 순위 결정
- 3단계: 노드 할당 → 최고 점수 노드에 Pod 배치
스케줄러가 동작하는 세 가지 상황
- 초기 배포 시
- 스케일 업 시 (새 Pod 추가)
- 노드 장애 시 (다른 노드로 이동)
3. Available Node Resources
노드 가용 용량 공식 (Example 6-1)
Allocatable = Node Capacity - Kube-Reserved - System-Reserved
- Kube-Reserved: Kubelet, 컨테이너 런타임
- System-Reserved: OS 데몬 (sshd, udev 등)
예약하지 않을 때의 결과
- Pod들이 노드 전체 용량 다 사용 → OS, Kubelet도 자원 부족
- → 노드 전체 불안정 (리소스 고갈) 💥
Kubernetes 외부 컨테이너 문제
- Docker로 직접 실행한 컨테이너 → Kubernetes가 모름, 용량 계산에 반영 안 됨
- 스케줄러: “이 노드 자원 많네” → Pod 배치
- 실제로는: 외부 컨테이너가 자원 다 씀 💥
해결책: 플레이스홀더 Pod
# 아무것도 안 하지만 리소스만 선언
containers:
- name: placeholder
image: pause
resources:
requests:
cpu: "2" # 외부 컨테이너가 쓰는 만큼
memory: "4Gi" # 예약해둠
플레이스홀더 Pod의 효과
- 스케줄러가 이 Pod를 보고 해당 자원이 사용 중임을 인식한다
4. Container Resource Demands
리소스 요구사항 선언
- 스케줄러가 올바르게 배치하려면 컨테이너가 리소스 요구사항을 선언해야 한다
containers:
- name: my-app
resources:
requests: # 스케줄러가 배치 기준으로 사용
cpu: "500m" # CPU 0.5개 보장
memory: "128Mi" # 메모리 128MB 보장
limits: # 이 이상 사용 불가
cpu: "1000m"
memory: "256Mi"
문제: 선언 누락
- 선언 없으면 스케줄러가 올바른 판단을 내리지 못한다
- 피크 시간에 Pod들이 자원을 놓고 경쟁한다
requests 기준으로 계산
- 실제 사용량이 아닌 선언값으로 예약
requests: cpu: "2"→ 실제 0.5 사용해도 2개 예약된 것으로 봄
5. Placement Policies
기본 정책
- 스케줄러는 기본 Predicate와 Priority 정책을 가지고 있다
- 대부분의 경우 기본값으로 충분하다
Predicates (필터링)
필터링 규칙
- 자격 없는 노드를 걸러내는 규칙
- 통과 못하면 탈락
| Predicate | 설명 |
|---|---|
| PodFitsResources | 자원 충분한가? |
| PodFitsHostPorts | 포트 사용 가능한가? |
| NoDiskConflict | 디스크 충돌 없는가? |
| NoVolumeZoneConflict | 볼륨 존 충돌 없는가? |
| MatchNodeSelector | 노드 라벨 맞는가? |
| HostName | 특정 호스트명인가? |
Priorities (우선순위)
점수 규칙
- 후보 노드에 점수를 매기는 규칙
- weight가 높을수록 중요
| Priority | weight | 설명 |
|---|---|---|
| LeastRequestedPriority | 2 | 자원 적게 쓰는 노드 선호 |
| BalancedResourceAllocation | 1 | CPU/메모리 균형 노드 선호 |
| ServiceSpreadingPriority | 2 | 같은 서비스 Pod 분산 선호 |
| EqualPriority | 1 | 모든 노드 동등 |
전체 흐름
커스텀 스케줄러
# 특정 스케줄러 지정
spec:
schedulerName: my-scheduler # 기본값: default-scheduler
여러 스케줄러 동시 운영
- 여러 스케줄러를 동시에 운영할 수 있다
- GPU 전용, ML 워크로드 전용 등 특수한 배치 요구사항에 활용한다
권한: 관리자 전용
- 스케줄러 정책 변경과 커스텀 스케줄러 생성은 관리자만 가능하다
6. Node Selector
가장 단순한 노드 배치 방법
- 특정 라벨을 가진 노드에만 Pod를 배치한다
Example 6-3
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
nodeSelector:
disktype: ssd # 이 라벨 있는 노드에만 배치
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
| 노드 | 라벨 | 결과 |
|---|---|---|
| 노드1 | disktype=ssd | 배치 가능 ✅ |
| 노드2 | disktype=hdd | 탈락 ❌ |
| 노드3 | disktype=ssd | 배치 가능 ✅ |
노드에 라벨 추가
kubectl label node 노드이름 disktype=ssd
kubectl get nodes --show-labels
기본 라벨 활용
자동으로 붙는 기본 라벨
- 모든 노드에 자동으로 붙어있는 기본 라벨도 사용할 수 있다
| 라벨 | 설명 | 예시 |
|---|---|---|
kubernetes.io/hostname | 노드 이름 | node1 |
kubernetes.io/os | 운영체제 | linux, windows |
kubernetes.io/arch | CPU 아키텍처 | amd64, arm64 |
node.kubernetes.io/instance-type | 인스턴스 타입 | m5.large |
# 특정 노드에 직접 배치
nodeSelector:
kubernetes.io/hostname: node1
# Linux 노드에만
nodeSelector:
kubernetes.io/os: linux
nodeSelector 한계
한계: 단순 라벨 매칭
- 단순한 라벨 매칭만 가능하다
- “SSD면 좋겠어(선호)” 같은 표현이 불가능하다
- 무조건 AND 조건이다
대안: Node Affinity
- 복잡한 조건이 필요하면 Node Affinity를 사용한다
7. Node Affinity
nodeSelector의 일반화
- 필수/선호 조건을 구분한다
- 다양한 연산자를 사용할 수 있다
두 가지 규칙 타입
requiredDuringScheduling: 스케줄링 시점 필수
- 스케줄링 시점에 필수라는 뜻이다
- 새롭게 스케줄링되는 Pod에 대해 Node Affinity 조건이 반드시 지켜져야 한다
- 조건에 맞는 노드가 없으면 배치 자체가 되지 않는다
preferredDuringScheduling: 가급적 선호
- 필수값만 줄 수 있는 건 아니다
- “필수는 아니지만 가급적이면” 해당 노드로 스케줄링되도록 지정할 수 있다
- preferred로 지정하면 조건에 맞는 노드가 자원 부족 등의 사정으로 Pod를 받아줄 수 없을 때
- 조건에 맞지 않는 다른 노드로도 스케줄링된다
IgnoredDuringExecution: 실행 중 무시
- 실행 중이라면 무시하라는 뜻이다
- 이미 실행 중인 Pod는 해당 노드가 더 이상 규칙을 충족하지 않는 상태가 되어도 굳이 내려서 다시 옮기지 않는다
- 반대 개념으로
RequiredDuringExecution이 있다- 실행 중에도 조건 위반 시 재배치
- 이 글 작성 시점 기준 정식 릴리스되지 않았다
필드 구조
requiredDuringSchedulingIgnoredDuringExecution의 하위 필드
nodeSelectorTerms[]: 노드 셀렉터 설정을 묶는다matchExpressions[]: 그 안에서 실제 조건을 정의한다
matchExpressions[]의 하위 필드: key, operator, values[]
key: 노드의 레이블 키 중 하나operator:key가 만족해야 할 조건values[]:operator에 따라 비교할 값 목록
연산자 종류
| operator | 설명 |
|---|---|
| In | values[]에 설정한 값 중 레이블 값과 일치하는 것이 하나라도 있는지 확인 |
| NotIn | values[]에 있는 값 모두와 일치하지 않는지 확인 |
| Exists | key 필드 값이 레이블에 존재하는지만 확인 (values[] 불필요) |
| DoesNotExist | 노드 레이블에 key 필드 값이 없는지만 확인 (values[] 불필요) |
| Gt | values[]에 설정된 값(숫자)이 노드 레이블 값보다 큰지 확인 — values[]는 값 1개만 허용 |
| Lt | values[]에 설정된 값(숫자)이 노드 레이블 값보다 작은지 확인 — values[]는 값 1개만 허용 |
Example 6-4
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
affinity:
nodeAffinity:
# 필수: 코어 수 3개 초과 노드에만 배치
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions: # 라벨로 매칭
- key: numberCores
operator: Gt
values: [ "3" ]
# 선호: 가능하면 master 노드 피하기
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchFields: # 노드 필드(jsonpath)로 매칭
- key: metadata.name
operator: NotIn
values: [ "master" ] # In, NotIn만 가능, 값은 1개만
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
예시: SSD 노드 지정
- 아래처럼 조건을 쓰면, 결국
disktype=ssd라벨을 가진 노드에 스케줄링하라는 의미다
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values: [ "ssd" ]
matchExpressions vs matchFields
| matchExpressions | matchFields | |
|---|---|---|
| 기준 | 노드 라벨 | 노드 필드 (jsonpath) |
| 사용 가능 연산자 | 모두 | In, NotIn만 |
| 값 개수 | 여러 개 | 1개만 |
nodeSelector vs Node Affinity 비교
| nodeSelector | Node Affinity | |
|---|---|---|
| 필수/선호 구분 | ✗ 필수만 | ✓ 둘 다 |
| 연산자 | = 만 | In, NotIn, Gt, Lt 등 |
| 복잡한 조건 | ✗ | ✓ |
| 사용 난이도 | 쉬움 | 복잡함 |
8. Pod Affinity and Antiaffinity
기준: 노드 특성 vs Pod 위치 관계
- Node Affinity는 노드 특성 기준이다
- Pod 간의 위치 관계를 기준으로 배치하려면 Pod Affinity/Antiaffinity를 사용한다
개념
- Pod Affinity (같이 있고 싶어): 자주 통신하는 Pod끼리 같은 노드에 → 네트워크 지연 감소
- Pod Antiaffinity (떨어져 있고 싶어): 같은 서비스 Pod들을 여러 노드에 분산 → 고가용성
topologyKey
규칙 적용 레벨
- 어떤 레벨에서 규칙을 적용할지 결정한다
| topologyKey | 레벨 |
|---|---|
kubernetes.io/hostname | 노드 단위 |
topology.kubernetes.io/zone | 가용 영역 단위 |
topology.kubernetes.io/region | 리전 단위 |
rack | 랙 단위 (커스텀) |
labelSelector
Node Affinity와의 차이
- Node Affinity는
key/operator/values로 노드의 레이블을 찾는다 - Pod Affinity/Antiaffinity는
labelSelector로 기준이 되는 Pod의 레이블을 찾는다
매칭 방식
labelSelector.matchLabels에 지정한 레이블과 일치하는 Pod가 있는 노드를 찾는다- 그 노드의
topologyKey값을 기준으로 같은(Affinity) 또는 다른(Antiaffinity) 위치에 배치한다
예시 흐름
labelSelector.matchLabels: confidential=high인 Pod를 찾는다- → 그 Pod가 있는 노드의 topologyKey(
security-zone) 값 확인 - → 같은 값을 가진 노드에 배치 (Affinity)
Example 6-5
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
affinity:
# 필수: confidential=high Pod와 같은 security-zone에 배치
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
confidential: high # 이 라벨 가진 Pod 근처에
topologyKey: security-zone
# confidential=high Pod가 있는 노드의 security-zone 값과
# 같은 security-zone 라벨을 가진 노드에 배치
# 선호: confidential=none Pod와 다른 노드에 배치
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
confidential: none # 이 Pod랑 다른 노드에
topologyKey: kubernetes.io/hostname
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
시나리오: 기밀 데이터 처리 앱
- 보안 구역 안에 배치하고, 비기밀 Pod와 격리한다
- 필수 (podAffinity):
confidential=highPod와 같은security-zone✅ - 선호 (podAntiAffinity):
confidential=nonePod 없는 노드 우선 (weight: 100→ 강한 선호)
Step-by-Step: Pod Affinity/Antiaffinity 스케줄링 과정
9. Taints and Tolerations
노드의 거부 메커니즘
- Node Affinity는 Pod가 노드를 선택하는 것이다
- Taints/Tolerations는 반대로 노드가 Pod를 거부하는 메커니즘이다
관점 차이
- Node Affinity → Pod 관점: “나 이런 노드에 가고 싶어”
- Taints/Tolerations → 노드 관점: “나 아무 Pod나 못 받아”
opt-in vs opt-out
Taint Effect 종류
| Effect | 설명 |
|---|---|
| NoSchedule | Toleration 없으면 배치 안 함 (실행 중인 Pod 유지) |
| PreferNoSchedule | 가능하면 배치 안 함 (어쩔 수 없으면 가능) |
| NoExecute | 배치 안 함 + 실행 중인 Pod도 쫓아냄 |
Example 6-6 (Tainted Node)
apiVersion: v1
kind: Node
metadata:
name: master
spec:
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
# Toleration 없는 Pod 배치 불가
# 마스터 노드를 일반 Pod로부터 보호
# 명령어로 추가
kubectl taint nodes master node-role.kubernetes.io/master="true":NoSchedule
Example 6-7 (Pod with Toleration)
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists # value 상관없이 key만 있으면 매칭
effect: NoSchedule # 이 effect에만 적용
# 비워두면 모든 effect에 적용
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
Taint와 Toleration 매칭
| 노드 Taint | Pod Toleration | 결과 | |
|---|---|---|---|
| key | node-role.kubernetes.io/master | node-role.kubernetes.io/master | 일치 ✅ |
| operator | Exists | key만 있으면 됨 | |
| effect | NoSchedule | NoSchedule | 일치 ✅ |
매칭 성공 → 마스터 노드에 배치 가능 ✅
Step-by-Step: Taint/Toleration 매칭 과정
operator 종류
| operator | 설명 |
|---|---|
| Exists | key만 존재하면 매칭, value 불필요 |
| Equal | key와 value 모두 일치해야 매칭 |
Taint value 처리
- Taint의
value는 선택 필드다 value가 있으면- Pod의 톨러레이션이
operator: Equal을 쓸 때key뿐 아니라value까지 정확히 일치해야 통과한다
- Pod의 톨러레이션이
value를 생략하면 빈 문자열("")로 취급된다Equal을 쓸 경우 Pod 쪽value도 비어있어야 매칭된다
operator: Exists를 쓰면 애초에value를 보지 않는다- 이 값의 유무는 무관하다
실제 사용 사례
마스터 노드 보호
- 마스터 노드에 NoSchedule taint
- → 시스템 Pod만 toleration으로 접근
GPU 노드 전용화
- GPU 노드에
gpu=true:NoScheduletaint - → GPU 필요한 Pod만 toleration 추가
- → 일반 Pod는 GPU 노드 사용 불가
10. Stranded Resources and Descheduler
스케줄러의 한계 — 배치는 스냅샷 결정
관여 범위: 할당 순간까지
- 스케줄러는 Pod를 노드에 할당하는 순간까지만 관여한다
- 배치가 끝나면 역할이 끝난다
- Pod가 삭제되고 재생성되지 않는 한 배치를 바꾸지 않는다
스케줄러 = 스냅샷 기반 결정
- 새 Pod 배치 요청 → “지금 이 순간” 클러스터 상태만 확인
- 최적 노드 선택 → 배치 완료
- 이후 클러스터가 어떻게 변하든 관여 안 함
문제 상황
1. 리소스 단편화 (Stranded Resources, Figure 6-2)
| 리소스 | 사용률 | 상태 |
|---|---|---|
| CPU | 90% | 거의 소진 — 새 Pod 배치 불가 |
| Memory | 20% (4GB 여유) | 남아있지만 CPU가 없어 활용 불가 |
상황: 고립된 메모리
- 메모리는 남았지만 CPU가 없어 새 Pod가 못 들어온다
- 자원이 고립(Stranded)되는 상황이다
원인: 실제 사용량과 어긋난 요구사항
- 컨테이너 리소스 요구사항이 실제 사용량과 어긋나게 잡힌다
- CPU를 넉넉하게 요청하고 메모리는 적게 요청하면
- CPU가 먼저 소진된다
- 메모리만 계속 비어있는 상태로 고립된다
2. 클러스터 확장 시 불균형
- 처음: 노드 3개, Pod 균등 배치
- 나중: 노드 5개로 확장 → 새 노드 2개는 텅 빔
- 기존 Pod는 재배치되지 않아 불균형 유지
3. 노드 라벨 변경
- 노드A:
region=us-east라벨 → Pod 배치됨 - 라벨 변경:
region=us-west - 이미 배치된 Pod는 그대로 실행 (재배치 안 됨)
Descheduler
옵트인 재배치 기능
- 스케줄러는 신규 배치만 담당한다
- Descheduler는 이미 배치된 Pod를 재배치해 클러스터 리소스 활용률을 개선한다
- 선택적(옵트인) 기능이다
- 상시 실행되지 않는다
- 클러스터 관리자가 필요한 시점에 Job으로 실행한다
| 스케줄러 | Descheduler | |
|---|---|---|
| 대상 | 신규 Pod만 처리 | 이미 배치된 Pod 재배치 |
| 실행 | 컨트롤러로 상시 실행 | 필요할 때 Job으로 실행 |
동작 흐름
- 관리자가 Descheduler Job 실행
- 현재 클러스터 상태 분석
- 정책에 따라 퇴거 대상 Pod 선택 (예: RemoveDuplicates)
- 대상 Pod 삭제 (퇴거)
- 스케줄러가 다시 최적 노드에 재배치
- 클러스터 리소스 재조정 완료
Descheduler 정책 4가지 (쉽게 풀어보기)
1. RemoveDuplicates — “한 노드에 같은 Pod가 몰려있으면 정리”
평소: Deployment replicas=3
- 노드A: Pod 1개 / 노드B: Pod 1개 / 노드C: Pod 1개 (정상)
노드A 장애 발생
- → 컨트롤러가 “3개가 안 되네” 하고 노드B, C에 Pod를 추가로 만듦
- → 노드B: Pod 2개 / 노드C: Pod 2개 (총 4개, 원래는 3개여야 함)
노드A가 복구됨
- → 이제 Pod가 5개나 떠있음 (초과)
- → RemoveDuplicates가 중복분을 지워서 다시 3개로 맞춤
정리 대상: 한 노드에 몰린 중복 Pod
- 같은 서비스의 Pod가 한 노드에 여러 개 몰려도 이 정책이 정리해준다
2. LowNodeUtilization — “널널한 노드로 옮겨서 균등하게”
노드 세 그룹
- 노드를 세 그룹으로 나눠서 생각하면 쉽다
균등화 과정
- 꽉 찬 노드에서 Pod를 몇 개 내쫓는다
- 스케줄러가 그 Pod를 텅 빈 노드에 다시 배치해준다
- 결과적으로 노드들의 사용률이 고르게 맞춰진다
3. RemovePodsViolatingInterPodAntiAffinity / RemovePodsViolatingNodeAffinity — “규칙이 바뀌었는데 아직 안 지켜진 Pod 정리”
검사 시점: 최초 배치
- Affinity 규칙은 Pod가 처음 배치될 때만 검사한다
- 문제는 배치가 끝난 뒤에 규칙이 새로 추가되거나 노드 라벨이 바뀌는 경우다
위반 Pod 정리 흐름
- Pod가 배치될 당시엔 규칙 없었음 → 정상 배치
- 나중에 Affinity 규칙 추가 / 노드 라벨 변경
- → 이 Pod는 이제 규칙을 위반한 상태 (근데 스케줄러는 신경 안 씀)
- → Descheduler가 이 Pod를 퇴거
- → 스케줄러가 규칙에 맞는 노드로 다시 배치
절대 건드리지 않는 Pod들
정리 중에도 제외되는 Pod
- 핵심 시스템 Pod —
critical-pod로 표시된 것들 - 주인 없는 Pod — ReplicaSet/Deployment/Job 같은 컨트롤러가 관리하지 않는 Pod
- 퇴거해도 다시 살려줄 사람이 없다
- DaemonSet Pod — 노드마다 1개씩 있어야 하는 Pod
- 퇴거해봤자 그 자리에 다시 생긴다
- 로컬 스토리지 쓰는 Pod — 옮기면 그 안의 데이터가 사라진다
- PDB(PodDisruptionBudget) 위반이 되는 Pod
- “이 서비스는 최소 N개는 살아있어야 해"라는 규칙을 어기게 되면 건드리지 않는다
- Descheduler 자기 자신
- 스스로를 critical-pod로 표시해서 자기 자신은 안전하게 지킨다
퇴거 순서 — 덜 중요한 것부터
기준: QoS 등급
- 정리가 필요할 때, 아무 Pod나 무작위로 퇴거시키지 않는다
- QoS 등급이 낮은 것부터 순서대로 뺀다
| 퇴거 순서 | QoS | 설명 |
|---|---|---|
| 1번 | Best-Effort | 자원 요청을 아예 안 한 Pod (제일 가벼운 취급) |
| 2번 | Burstable | 최소 자원만 보장받는 Pod |
| 3번 | Guaranteed | 요청=제한으로 확실히 보장받는 Pod (제일 마지막까지 보호) |
보호 우선순위
- 중요한 서비스일수록(Guaranteed) 웬만하면 건드리지 않는다
- 자원 보장이 약한 Pod부터 먼저 정리 대상이 된다
해결 방향
해결책 1: 리소스 요구사항 세분화
- requests를 실제 사용량에 맞게 더 작게 설정
- → 더 많은 Pod가 빈틈에 들어갈 수 있음
해결책 2: Descheduler로 재배치
- 노드 단편화 해소, Pod 재조정
- → 리소스 활용률 개선
스케줄러 과도한 제한 주의
제약 중첩의 위험
- nodeSelector, Node/Pod Affinity, Taints를 한꺼번에 많이 걸면 배치 가능한 노드가 아예 없어질 수 있다
모든 제약을 동시에 건 경우
- nodeSelector + Node Affinity + Pod Affinity + Taints 전부 적용
- → 배치 가능한 노드가 없음 💥
- → Pod는 Pending 상태로 남고 리소스는 낭비됨
실무적인 선택
- 스케줄러는 배치 시점의 스냅샷만 보고 결정하며 이후 변화에 대응하지 않는다
- 최소한의 제약만 걸고 스케줄러를 신뢰한다
- 리소스 단편화가 누적되면 Descheduler로 정리한다
11. 배치 방법 종합 비교
| 방법 | 특징 |
|---|---|
| nodeSelector | 단순 라벨 매칭, 필수만 |
| Node Affinity | 필수/선호, 다양한 연산자 |
| Pod Affinity | 다른 Pod와 같이 배치 |
| Pod Antiaffinity | 다른 Pod와 떨어져 배치 |
| Taints/Tolerations | 노드가 Pod 거부, 허가증으로 접근 |
선택 가이드
| 상황 | 방법 |
|---|---|
| SSD, GPU 같은 특수 하드웨어 필요 | nodeSelector 또는 Node Affinity |
| 자주 통신하는 Pod끼리 가까이 | Pod Affinity |
| 고가용성 위해 Pod 분산 | Pod Antiaffinity |
| 마스터 노드 보호, 전용 노드 | Taints/Tolerations |
| 대부분의 경우 | 스케줄러에게 맡기는 게 최선 |
기본: 스케줄러에게 맡기기
- 스케줄러에게 맡기는 것이 기본이다
- 배치 로직을 과도하게 제어하면 오히려 스케줄러의 최적화를 방해한다
12. Discussion
목표: 최소한의 개입
- 배치는 최소한으로 개입하는 게 목표인 영역이다
- Chapter 2(Predictable Demands)의 가이드대로 컨테이너의 리소스 요구사항을 제대로 선언해둔다
- 스케줄러가 알아서 가장 적합한 노드에 Pod를 배치해준다
- 그것만으로 부족할 때, 원하는 배포 토폴로지로 스케줄러를 유도하는 방법은 여러 가지가 있다
- 아래는 단순한 것부터 복잡한 것 순으로 정리한 목록이다
- 이 목록은 쿠버네티스 릴리스마다 계속 바뀐다
단순 → 복잡, 7단계 스펙트럼
1. nodeName — Pod를 노드에 하드와이어링
- Pod를 특정 노드에 직접 못박는 가장 단순한 방법이다
- 원래 이 필드는 스케줄러가 정책에 따라 채워야 한다
- 수동으로 지정하면 Pod가 스케줄링될 수 있는 범위가 극도로 좁아진다
- 쿠버네티스 이전 시대처럼 “이 앱은 이 노드에서 돌린다"고 못박던 방식으로 되돌아가는 셈이다
- 그래서 지양해야 한다
2. nodeSelector — key-value 라벨 매칭
- Pod와 노드에 같은 key-value 라벨이 있어야 배치가 허용된다
- Pod와 노드에 의미 있는 라벨을 붙여두는 건 (다른 이유로도) 어차피 해야 할 일이다
- 그래서 nodeSelector는 스케줄러를 제어하는 가장 무난하고 받아들일 만한 수단이다
3. 기본 스케줄러 정책 변경
- 기본 스케줄러는 클러스터 안에서 새 Pod를 노드에 배치하는 역할을 웬만큼 잘 해낸다
- 하지만 필요하다면 이 스케줄러의 필터링/우선순위 정책 목록, 순서, 가중치(weight)를 바꿀 수도 있다
4. Pod Affinity/Antiaffinity
- Pod가 다른 Pod에 대한 의존성을 표현하는 규칙이다
- 지연시간 요구사항, 고가용성, 보안 제약 등을 이유로 쓴다
5. Node Affinity
- Pod가 노드에 대한 의존성을 표현하는 규칙이다
- 노드의 하드웨어, 위치 등을 고려할 때 쓴다
6. Taints and Tolerations
- 주도권이 Pod가 아니라 노드에 있다
- 어떤 Pod를 받고 안 받을지 노드가 결정한다
- 특정 그룹의 Pod를 위해 노드를 전용화하거나, 런타임에 Pod를 퇴거시키는 데 유용하다
- 또 하나의 장점: 새 라벨을 가진 노드를 클러스터에 추가할 때
- 기존의 모든 Pod에 라벨을 새로 붙일 필요 없이
- 그 새 노드에 배치되어야 할 Pod에만 톨러레이션을 추가하면 된다
7. 커스텀 스케줄러
- 위 방법들로도 충분하지 않거나 배치 요구사항이 복잡하다면 스케줄러를 직접 작성할 수도 있다
- 커스텀 스케줄러는 기본 스케줄러 대신, 혹은 기본 스케줄러와 나란히 실행될 수 있다
- 절충안: “스케줄러 익스텐더(scheduler extender)”
- 기본 스케줄러가 배치를 결정하는 마지막 단계에서 이 익스텐더를 호출하는 방식이다
- 스케줄러 전체를 구현할 필요 없이 노드를 필터링/우선순위화하는 HTTP API만 제공하면 된다
직접 스케줄러를 두는 장점
- 하드웨어 비용, 네트워크 지연, 리소스 활용도처럼 쿠버네티스 클러스터 바깥의 요소까지 배치 결정에 반영할 수 있다
- 기본 스케줄러와 나란히 여러 커스텀 스케줄러를 동시에 운영할 수 있다
- Pod마다 어떤 스케줄러를 쓸지 지정할 수도 있다
- 각 스케줄러가 서로 다른 Pod 그룹에 특화된 정책 집합을 가질 수 있다
핵심 요약
방법 선택의 어려움
- 이렇게 Pod 배치를 제어하는 방법은 다양하다
- 그중 알맞은 방법을 고르거나 여러 방법을 조합하는 일은 까다로울 수 있다
이 챕터의 핵심 세 가지
- 컨테이너 리소스 프로파일을 제대로 산정하고 선언하라
- Pod와 노드에 적절히 라벨을 붙여라
- 쿠버네티스 스케줄러에는 최소한으로만 개입하라
다음 챕터: Self Awareness
- 애플리케이션이 자기 자신에 대한 메타데이터(Pod 이름, 네임스페이스, 리소스 제한 등)를 다운워드 API로 조회하는 방법을 다룬다
