[Daily morning study] Kubernetes 고급 스케줄링 - Taints, Tolerations, Node Affinity

#daily morning study

Image


기본 스케줄링의 한계

쿠버네티스 기본 스케줄러는 요청한 리소스(CPU, 메모리)와 노드의 가용 리소스를 기준으로 Pod를 배치한다. 하지만 실제 운영 환경에서는 이것만으로는 부족한 경우가 많다.

  • GPU 노드에는 ML 워크로드만 올리고 싶다
  • 특정 팀의 Pod는 특정 노드 그룹에만 스케줄링되어야 한다
  • 프로덕션 워크로드와 개발 워크로드를 물리적으로 분리하고 싶다
  • 고가용성을 위해 Pod 레플리카를 여러 존(AZ)에 분산시키고 싶다

이를 위해 쿠버네티스는 Taints/Tolerations와 Node Affinity/Anti-Affinity, Pod Affinity/Anti-Affinity 메커니즘을 제공한다.


Taints와 Tolerations

개념

Taint는 노드에 “오점”을 표시하는 것이다. Taint가 있는 노드에는 해당 Taint를 “참을 수 있는(Tolerate)” Pod만 스케줄링된다.

노드에 Taint 추가:
  kubectl taint nodes node1 key=value:effect

Pod에 Toleration 추가:
  Pod가 해당 Taint를 무시(tolerate)할 수 있음을 선언

Taint Effect의 종류

Effect동작
NoScheduleToleration 없는 Pod는 이 노드에 스케줄링되지 않음
PreferNoSchedule가능하면 스케줄링하지 않음, 다른 노드가 없으면 허용
NoExecute스케줄링 금지 + 이미 실행 중인 Pod도 축출(Evict)

예시

# 노드에 Taint 추가 (GPU 전용 노드)
kubectl taint nodes gpu-node-1 gpu=true:NoSchedule

# Pod에 Toleration 추가
apiVersion: v1
kind: Pod
metadata:
  name: ml-training-pod
spec:
  tolerations:
  - key: "gpu"
    operator: "Equal"
    value: "true"
    effect: "NoSchedule"
  containers:
  - name: trainer
    image: ml-trainer:latest
    resources:
      limits:
        nvidia.com/gpu: 1

operator에는 Equal(key=value 일치)과 Exists(key만 일치)가 있다.

NoExecute와 tolerationSeconds

NoExecute Taint는 이미 실행 중인 Pod도 내쫓는다. tolerationSeconds를 설정하면 일정 시간 후에 축출된다.

tolerations:
- key: "node.kubernetes.io/not-ready"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 300  # 노드가 NotReady가 되어도 5분간 유지

쿠버네티스는 기본적으로 모든 Pod에 node.kubernetes.io/not-ready:NoExecute와 node.kubernetes.io/unreachable:NoExecute에 대한 Toleration을 자동으로 추가하며, tolerationSeconds: 300이 기본값이다.

시스템 컴포넌트의 Taint

마스터 노드(Control Plane)에는 기본적으로 아래 Taint가 설정되어 있다.

node-role.kubernetes.io/control-plane:NoSchedule

그래서 일반 Pod는 마스터 노드에 스케줄링되지 않는다. kube-system의 DaemonSet들은 이 Taint에 대한 Toleration을 가지고 있어 마스터에도 실행된다.


Node Affinity

nodeSelector vs Node Affinity

nodeSelector는 단순한 key=value 매칭만 지원한다. Node Affinity는 더 표현력 있는 조건을 지원한다.

# nodeSelector - 단순
spec:
  nodeSelector:
    disktype: ssd

# nodeAffinity - 표현력 풍부
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
            - nvme

두 가지 모드

requiredDuringSchedulingIgnoredDuringExecution

  • 스케줄링 시 반드시 만족해야 함 (Hard constraint)
  • 조건을 만족하는 노드가 없으면 Pending 상태
  • 실행 중에 노드 레이블이 바뀌어도 Pod를 축출하지 않음 (Ignored)

preferredDuringSchedulingIgnoredDuringExecution

  • 선호하는 조건 (Soft constraint)
  • 조건을 만족하는 노드를 우선 선택하되, 없으면 다른 노드에도 배치
  • weight 값(1~100)으로 선호도 강도 설정
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - ap-northeast-2a
      - weight: 20
        preference:
          matchExpressions:
          - key: node-type
            operator: In
            values:
            - high-memory

지원하는 Operator

Operator의미
In값이 목록에 포함됨
NotIn값이 목록에 포함되지 않음
Exists키가 존재함
DoesNotExist키가 존재하지 않음
Gt값이 지정된 값보다 큼
Lt값이 지정된 값보다 작음

Pod Affinity / Anti-Affinity

Node Affinity가 노드의 레이블을 기준으로 하는 반면, Pod Affinity는 이미 실행 중인 다른 Pod를 기준으로 스케줄링을 결정한다.

Pod Affinity

같은 노드(또는 존, 리전)에 특정 Pod와 함께 배치하고 싶을 때 사용한다.

spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - cache
        topologyKey: kubernetes.io/hostname  # 같은 노드에 배치

사용 사례: 웹 서버 Pod를 캐시(Redis) Pod와 같은 노드에 배치하여 네트워크 지연 최소화.

Pod Anti-Affinity

특정 Pod와 같은 위치에 배치되지 않도록 한다. 고가용성 구성에 필수적이다.

spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - web-frontend
        topologyKey: topology.kubernetes.io/zone  # 다른 AZ에 배치

사용 사례: 웹 프론트엔드 레플리카 3개를 서로 다른 AZ에 분산하여 단일 장애점 제거.

topologyKey

topologyKey는 “어떤 단위”로 같은 위치/다른 위치를 판단할지 결정한다.

topologyKey범위
kubernetes.io/hostname같은 노드
topology.kubernetes.io/zone같은 가용 영역(AZ)
topology.kubernetes.io/region같은 리전

실전 패턴: 세 가지 메커니즘 조합

패턴 1: 특정 팀 전용 노드 풀

# 팀 노드에 Taint 추가
kubectl taint nodes team-a-node-1 team=a:NoSchedule

# 팀 노드에 레이블 추가
kubectl label nodes team-a-node-1 team=a
# 팀 A의 Pod
spec:
  tolerations:
  - key: "team"
    operator: "Equal"
    value: "a"
    effect: "NoSchedule"
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: team
            operator: In
            values:
            - a

Taint만 있으면 toleration을 가진 다른 팀의 Pod도 들어올 수 있다. nodeAffinity까지 함께 사용해야 완전히 격리된다.

패턴 2: 고가용성 Deployment

레플리카 3개를 3개의 AZ에 분산시키는 구성:

spec:
  replicas: 3
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: my-web
            topologyKey: topology.kubernetes.io/zone

이 설정으로 같은 AZ에 2개 이상의 레플리카가 배치되지 않는다. 단, AZ가 2개뿐이라면 레플리카 3개 중 하나가 Pending 상태가 된다.

패턴 3: Topology Spread Constraints (권장)

쿠버네티스 1.18+에서는 topologySpreadConstraints를 사용하면 더 유연하게 분산 배치를 설정할 수 있다.

spec:
  topologySpreadConstraints:
  - maxSkew: 1           # 존 간 최대 Pod 수 차이
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule  # 조건 불만족 시 스케줄링 금지
    labelSelector:
      matchLabels:
        app: my-web

maxSkew: 1이면 AZ 간 Pod 수 차이가 최대 1개로 유지된다. Pod Anti-Affinity보다 더 정밀한 분산 제어가 가능하다.


디버깅

Pod가 Pending 상태일 때 스케줄링 실패 이유를 확인하는 방법:

# Pod 이벤트 확인
kubectl describe pod <pod-name>

# 주요 이유들
# "0/5 nodes are available: 5 node(s) had taint {...}, that the pod didn't tolerate"
# "0/5 nodes are available: 5 node(s) didn't match pod affinity rules"
# "0/5 nodes are available: 5 node(s) didn't match node selector"

kubectl get nodes --show-labels로 노드 레이블을, kubectl describe node <node> 하단의 Taints 섹션으로 Taint를 확인할 수 있다.


정리

메커니즘기준용도
Taints/Tolerations노드에 오점 표시노드 격리, 특수 노드 보호
Node Affinity노드의 레이블특정 노드 그룹 선호/필수
Pod Affinity실행 중인 Pod의 레이블함께 배치 (코로케이션)
Pod Anti-Affinity실행 중인 Pod의 레이블분리 배치 (고가용성)
Topology SpreadPod 분산 정도균등 분산

Taint/Toleration은 “이 노드는 특별해, 허가된 Pod만 들어와”의 관점이고, Affinity는 “이 Pod는 어디 가고 싶어”의 관점이다. 두 메커니즘은 서로 독립적으로 동작하므로 완전한 격리가 필요하다면 둘 다 사용해야 한다.