0. Contents

1. Overview

Pod 배치 문제수십 ~ 수백 개의 Pod어느 노드에 배치할지사람이 직접 결정?→ 불가능Kubernetes 스케줄러가 자동으로 결정노드 가용 용량 확인컨테이너 리소스 요구사항 확인배치 정책 적용
사람 대신 스케줄러가 용량 · 요구사항 · 정책을 보고 노드를 고른다

배치 제어 방법 (간단 → 복잡 순서)

방법설명
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를 보고 해당 자원이 사용 중임을 인식한다
Node Capacity — 노드 전체 용량Kube-ReservedKubelet · 런타임System-Reservedsshd · udev 등플레이스홀더 Pod외부 컨테이너 몫일반 Pod 몫requests 로 나눠 준다예약 — Pod 에 주지 않는다Allocatable — 스케줄러가 나눠 주는 용량예약을 안 하면Pod 가 노드 용량을 다 쓴다OS · Kubelet 도 자원 부족 → 노드 불안정Kubernetes 밖 컨테이너가 있으면스케줄러는 그 자원을 모른다pause Pod 에 requests 만 선언해 자리 맡김
스케줄러가 보는 건 노드 전체가 아니라 Allocatable — 밖에서 쓰는 몫은 플레이스홀더 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가 높을수록 중요
Priorityweight설명
LeastRequestedPriority2자원 적게 쓰는 노드 선호
BalancedResourceAllocation1CPU/메모리 균형 노드 선호
ServiceSpreadingPriority2같은 서비스 Pod 분산 선호
EqualPriority1모든 노드 동등

전체 흐름

① Predicates 필터링자격 없는 노드 탈락ABC통과 3개탈락 7개PodFitsResourcesPodFitsHostPortsMatchNodeSelector자원 · 포트 · 라벨 등을 본다② Priorities 점수남은 후보 순위 매기기노드A90노드B70노드C50LeastRequested · w 2ServiceSpreading · w 2BalancedResource · w 1weight 클수록 크게 반영③ 노드 할당최고 점수 노드에 배치노드A · 90점새 Pod여기서 실행점수 1등 한 곳만 고른다스케줄러가 도는 때초기 배포스케일 업노드 장애
거르고(Predicates) → 점수 매기고(Priorities) → 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
노드라벨결과
노드1disktype=ssd배치 가능 ✅
노드2disktype=hdd탈락 ❌
노드3disktype=ssd배치 가능 ✅

노드에 라벨 추가

kubectl label node 노드이름 disktype=ssd
kubectl get nodes --show-labels

기본 라벨 활용

자동으로 붙는 기본 라벨

  • 모든 노드에 자동으로 붙어있는 기본 라벨도 사용할 수 있다
라벨설명예시
kubernetes.io/hostname노드 이름node1
kubernetes.io/os운영체제linux, windows
kubernetes.io/archCPU 아키텍처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이 있다
    • 실행 중에도 조건 위반 시 재배치
    • 이 글 작성 시점 기준 정식 릴리스되지 않았다
required — 필수requiredDuringSchedulingIgnoredDuringExecution맞는 노드 있음numberCores > 3 등그 노드에만 배치조건 통과한 노드만맞는 노드 없음모든 노드 탈락배치 안 됨Pending 으로 대기preferred — 선호preferredDuringSchedulingIgnoredDuringExecution맞는 노드 여유 있음조건 충족그 노드에 배치weight 만큼 점수 가산맞는 노드 자원 부족받아줄 수 없음다른 노드로 배치조건 안 맞아도 된다IgnoredDuringExecution — 실행 중에는 다시 보지 않는다Pod 실행 중배치 때는 조건 충족노드 라벨 바뀜이제 조건을 어긴다Pod 그대로내려서 옮기지 않는다실행 중에도 다시 맞추는 RequiredDuringExecution 은 글 작성 시점에 정식 릴리스 전
required 는 못 맞추면 배치 안 함, preferred 는 못 맞춰도 배치 — 둘 다 배치 뒤에는 다시 보지 않는다

필드 구조

requiredDuringSchedulingIgnoredDuringExecution의 하위 필드

  • nodeSelectorTerms[]: 노드 셀렉터 설정을 묶는다
  • matchExpressions[]: 그 안에서 실제 조건을 정의한다

matchExpressions[]의 하위 필드: key, operator, values[]

  • key: 노드의 레이블 키 중 하나
  • operator: key가 만족해야 할 조건
  • values[]: operator에 따라 비교할 값 목록

연산자 종류

operator설명
Invalues[]에 설정한 값 중 레이블 값과 일치하는 것이 하나라도 있는지 확인
NotInvalues[]에 있는 값 모두와 일치하지 않는지 확인
Existskey 필드 값이 레이블에 존재하는지만 확인 (values[] 불필요)
DoesNotExist노드 레이블에 key 필드 값이 없는지만 확인 (values[] 불필요)
Gtvalues[]에 설정된 값(숫자)이 노드 레이블 값보다 큰지 확인 — values[]는 값 1개만 허용
Ltvalues[]에 설정된 값(숫자)이 노드 레이블 값보다 작은지 확인 — 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

matchExpressionsmatchFields
기준노드 라벨노드 필드 (jsonpath)
사용 가능 연산자모두In, NotIn만
값 개수여러 개1개만

nodeSelector vs Node Affinity 비교

nodeSelectorNode 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=high Pod와 같은 security-zone ✅
  • 선호 (podAntiAffinity): confidential=none Pod 없는 노드 우선 (weight: 100 → 강한 선호)

Step-by-Step: Pod Affinity/Antiaffinity 스케줄링 과정

confidential=high confidential=none 새 Pod 후보/선정 노드

9. Taints and Tolerations

노드의 거부 메커니즘

  • Node Affinity는 Pod가 노드를 선택하는 것이다
  • Taints/Tolerations는 반대로 노드가 Pod를 거부하는 메커니즘이다

관점 차이

  • Node Affinity → Pod 관점: “나 이런 노드에 가고 싶어”
  • Taints/Tolerations → 노드 관점: “나 아무 Pod나 못 받아”

opt-in vs opt-out

Affinity (opt-out)기본: 모든 노드 사용 가능노드노드노드노드특정 노드만 선택 → 나머지 제외노드노드노드노드Taint/Toleration (opt-in)기본: 이 노드 사용 불가PodPodPodPodToleration 있는 Pod만 허용PodTolerationPodPodPod
Affinity 는 다 열어 두고 골라 빼고, Taint 는 다 막아 두고 골라 들인다

Taint Effect 종류

Effect설명
NoScheduleToleration 없으면 배치 안 함 (실행 중인 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 매칭

노드 TaintPod Toleration결과
keynode-role.kubernetes.io/masternode-role.kubernetes.io/master일치 ✅
operatorExistskey만 있으면 됨
effectNoScheduleNoSchedule일치 ✅

매칭 성공 → 마스터 노드에 배치 가능 ✅

Step-by-Step: Taint/Toleration 매칭 과정

Taint (master) Toleration 없는 Pod Toleration 있는 Pod

operator 종류

operator설명
Existskey만 존재하면 매칭, value 불필요
Equalkey와 value 모두 일치해야 매칭

Taint value 처리

  • Taint의 value는 선택 필드다
  • value가 있으면
    • Pod의 톨러레이션이 operator: Equal을 쓸 때 key뿐 아니라 value까지 정확히 일치해야 통과한다
  • value를 생략하면 빈 문자열("")로 취급된다
    • Equal을 쓸 경우 Pod 쪽 value도 비어있어야 매칭된다
  • operator: Exists를 쓰면 애초에 value를 보지 않는다
    • 이 값의 유무는 무관하다

실제 사용 사례

마스터 노드 보호

  • 마스터 노드에 NoSchedule taint
  • → 시스템 Pod만 toleration으로 접근

GPU 노드 전용화

  • GPU 노드에 gpu=true:NoSchedule taint
  • → GPU 필요한 Pod만 toleration 추가
  • → 일반 Pod는 GPU 노드 사용 불가

10. Stranded Resources and Descheduler

스케줄러의 한계 — 배치는 스냅샷 결정

관여 범위: 할당 순간까지

  • 스케줄러는 Pod를 노드에 할당하는 순간까지만 관여한다
  • 배치가 끝나면 역할이 끝난다
  • Pod가 삭제되고 재생성되지 않는 한 배치를 바꾸지 않는다

스케줄러 = 스냅샷 기반 결정

  • 새 Pod 배치 요청 → “지금 이 순간” 클러스터 상태만 확인
  • 최적 노드 선택 → 배치 완료
  • 이후 클러스터가 어떻게 변하든 관여 안 함

문제 상황

1. 리소스 단편화 (Stranded Resources, Figure 6-2)

리소스사용률상태
CPU90%거의 소진 — 새 Pod 배치 불가
Memory20% (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으로 실행

동작 흐름

  1. 관리자가 Descheduler Job 실행
  2. 현재 클러스터 상태 분석
  3. 정책에 따라 퇴거 대상 Pod 선택 (예: RemoveDuplicates)
  4. 대상 Pod 삭제 (퇴거)
  5. 스케줄러가 다시 최적 노드에 재배치
  6. 클러스터 리소스 재조정 완료
스케줄러 — 신규 Pod만, 상시 실행새 Pod 요청초기 배포 · 스케일 업 · 장애노드 선택지금 이 순간 상태만 본다노드에 배치이후 변화엔 관여 안 함시간이 지나며 생기는 어긋남 — 스케줄러는 다시 보지 않는다자원 고립CPU 90% · 메모리는 남음확장 후 불균형새 노드는 텅 빔노드 라벨 변경기존 Pod 는 그대로Descheduler — 이미 배치된 Pod 재배치, 옵트인Descheduler Job관리자가 필요할 때 실행퇴거 대상 선택RemoveDuplicates 등 정책Pod 퇴거QoS 낮은 것부터점선 화살표 — 퇴거된 Pod 를 컨트롤러가 다시 만들면 새 Pod 요청이 되어 스케줄러가 다시 배치한다
스케줄러는 배치 순간만 본다 — 쌓인 어긋남은 Descheduler가 퇴거로 풀고, 재배치는 다시 스케줄러 몫

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 — “널널한 노드로 옮겨서 균등하게”

노드 세 그룹

  • 노드를 세 그룹으로 나눠서 생각하면 쉽다
텅 빈 노드threshold 미만→ Pod를 받아도 되는 노드적당히 쓰는 노드→ 그냥 둔다꽉 찬 노드targetThreshold 초과→ 여기서 Pod를 퇴거시킨다thresholdtargetThreshold퇴거된 Pod 는 스케줄러가 텅 빈 노드에 다시 배치
꽉 찬 노드에서 빼고, 텅 빈 노드가 받는다 — 막대 높이는 예시

균등화 과정

  • 꽉 찬 노드에서 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 배치를 제어하는 방법은 다양하다
  • 그중 알맞은 방법을 고르거나 여러 방법을 조합하는 일은 까다로울 수 있다

이 챕터의 핵심 세 가지

  1. 컨테이너 리소스 프로파일을 제대로 산정하고 선언하라
  2. Pod와 노드에 적절히 라벨을 붙여라
  3. 쿠버네티스 스케줄러에는 최소한으로만 개입하라

다음 챕터: Self Awareness

  • 애플리케이션이 자기 자신에 대한 메타데이터(Pod 이름, 네임스페이스, 리소스 제한 등)를 다운워드 API로 조회하는 방법을 다룬다