주녁, DevNote
article thumbnail

개요

클러스터에 한 번 배치된 Pod는 Node의 상황에 따라 다시 배치되지 않는다.

이 말은 곧 Pod/Node를 선택하는 스케줄링 전략은 신중해야 한다는 뜻이다.

목표

  • Pod가 원하는 적절한 Node에 배치될 수 있도록 하는 조건을 이해한다.
  • 다양한 Pod 배치 전략을 통해 Schedule Process와 Affinity에 대해 알아본다.

여정

Scheduling Process

Pod가 배치될 Node를 선택하는 데는 아래와 같은 과정을 거친다.

  1. 스케줄이 가능한 Node 중에서
  2. Pod의 필터링 조건을 통과하고
  3. 우선순위 정렬 시 가장 높은 Node 선택

위 조건을 한번 더 차근차근 살펴보자

 

스케줄이 가능한 Node

  • Node는 기본적으로 할당 가능한 Pod의 수가 정해져 있다.
    • 일반적으로 110개(On-premise, GCP)이며, AWS는 55개이다.
    • 여기서 kube-system 과 같이 중요한 Pod, Daemon 들을 제하면 그 수는 더 감소한다.
  • Node는 할당 가능한 Pod 수를 초과하게 되면 Pod를 순차적으로 종료한다.
    • QoS(Quality of Service)에 따라서 가장 종료해도 될만한 Pod부터 순차적으로 축출(Eviction)된다.

Pod의 필터링 조건을 통과

Pod는 자신이 배치될 Node를 선택할 수 있다.

선택할 수 있는 조건은 여러가지가 있는데 대표적으로 아래와 같다.

  • 자원 요구 사항(CPU, Memory 등)
  • Node가 충족해야 하는 조건(NodeAffinity)
  • Pod 간의 관계 조건(PodAffinity & PodAntiAffinity)
  • Node가 기피 하는 조건(Taint & Toleration)

자원 요구 사항은 이전 글(자원 요구 사항 정의)에서 설명했으니 

Node Affinity부터 차근차근 알아보자


Node가 충족해야 하는 조건(NodeAffinity)

Affinity는 Pod를 특정 Label이 있는 Node로 제한할 수 있는 정책을 말한다.반대로, AntiAffinity는 특정 Label이 있는 Node를 선택하지 않도록 하는 정책을 말한다.

그 중에서 NodeSelector가장 간단하게 Node를 선택할 수 있는 방법이다.

# Node의 Label 속성 중에 disktype을 지정하는 예시
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
containers:
  - name: with-node-affinity
    image: registry.k8s.io/pause:2.0

 

참고로, NodeAntiAffinity는 존재하지 않는다.

다만, operator 중 DoesNotExist , NotIn 등으로 동일한 효과를 만들 수 있다.

  • requiredDuringSchedulingIgnoredDuringExecution
    • 규칙이 만족되지 않으면 스케줄러가 파드를 스케줄링할 수 없다.
    • 이 기능은 nodeSelector와 유사하지만, 좀 더 표현적인 문법을 제공한다.
  • preferredDuringSchedulingIgnoredDuringExecution
    • 스케줄러는 조건을 만족하는 노드를 찾으려고 노력한다.
    • 해당되는 노드가 없더라도, 스케줄러는 여전히 파드를 스케줄링한다.
    • 가중치(weight)는 1~100 범위를 명시할 수 있고, 합계가 가장 높은 Node를 먼저 선택한다.
# Node의 Label 속성 중 zone이 (antarctica-east1, antarctica-west1)에 속하고
# 그 Node 중에서 disktype이 ssd인 node를 우선하여 선택한다.
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - antarctica-east1
            - antarctica-west1
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
containers:
  - name: with-node-affinity
    image: registry.k8s.io/pause:2.0

Pod 간의 관계 조건(PodAffinity & PodAntiAffinity)

  • Pod 간의 Label을 기준으로 Node에 스케줄링할 조건을 지정하는 방법
  • Affinity와 결합하여 기존에 이미 배치된 Pod를 기준으로 다른 Pod가 특정 Node 배치되도록 스케줄링될 Node를 제한할 수 있다.
  • 이 때는 Node들마다 존재하는 일관된 Label이 존재해야 한다.

 

  • Pod가 같은 Node에 배치하는 예제(가까운 곳에서 통신하려는 경우)
    • preferredDuringSchedulingIgnoredDuringExecution 이용
    • security=S1 라벨을 가진 Pod들과 같은 존(topology.kubernetes.io/zone)에 위치하도록 필수로 스케줄링
    • security=S2 라벨을 가진 Pod들과 다른 존에 위치하도록 선호 스케줄링(S1에 되도록 가깝게 스케줄링)
# [security=S1 레이블을 가진 Pod]를 배치중인 Node 중에서
# [해당 Node와 동일한 zone에 있는 Node]에 Pod를 스케줄링한다.(preferred)
apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
containers:
  - name: with-node-affinity
    image: registry.k8s.io/pause:2.0
  • 같은 Node에 여러 Pod가 존재하지 못하도록 하는 경우
    • requiredDuringSchedulingIgnoredDuringExecution 이용
    • app=web-server 레이블을 가진 Pod들 간에는 Node를 반드시 다르게 스케줄링
    • 동시에 app=cache 레이블을 가진 Pod들과 같은 호스트 노드에 스케줄링
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-server
  replicas: 3
  template:
    metadata:
      labels:
        app: web-server
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-server
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - cache
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

Node가 기피하는 조건 (Taint & Toleration)

Affinity는 Pod가 Node를 선택할 수 있도록 한다.

반면, Taint는 Node가 Pod를 선택할 수 있도록 하는 정책이다.

 

Taint라는 개념이 낯설 수도 있지만, Cluster 생성 초기부터 이미 적용되어 있다.

바로, Master Node(Control-Plane)에 기본적으로 적용된 Taint인 node-role.kubernetes.io/control-plane:NoSchedule 이다.

 

Taint를 사용하는 예시를 조금 더 자세하게 알아보자.

Taint는 key=value 형태로 Node에 적용하는 옵션으로 아래와 같이 부여할 수 있다.

# Taint 부여
kubectl taint nodes node1 key1=value1:NoSchedule

# Taint 제거
kubectl taint nodes node1 key1=value1:NoSchedule-

 

Taint에는 주로 사용되는 옵션이 3가지가 있다.

  • NoSchedule : 무시되지 않은 경우, 해당 노드에 파드를 스케줄하지 않는다.
  • PreferNoSchedule : 무시되지 않은 경우, NoSchedule 가 없어도 해당 노드에 파드를 스케줄하지 않으려고 시도한다.
  • NoExecute : 무시되지 않은 경우, 파드가 노드에서 축출되고(노드에서 이미 실행 중인 경우), 노드에서 스케줄되지 않는다(아직 실행되지 않은 경우).

이 외에도 Node Controller에서는 Node 상태에 따라 자동적으로 아래 Taint를 설정한다.

  • node.kubernetes.io/not-ready: 노드가 준비되지 않았다. 이는 NodeCondition Ready 가 "False"로 됨에 해당한다.
  • node.kubernetes.io/unreachable: 노드가 노드 컨트롤러에서 도달할 수 없다. 이는 NodeCondition Ready 가 "Unknown"로 됨에 해당한다.
  • node.kubernetes.io/memory-pressure: 노드에 메모리 할당 압박이 있다.
  • node.kubernetes.io/disk-pressure: 노드에 디스크 할당 압박이 있다.
  • node.kubernetes.io/pid-pressure: 노드에 PID 할당 압박이 있다.
  • node.kubernetes.io/network-unavailable: 노드의 네트워크를 사용할 수 없다.
  • node.kubernetes.io/unschedulable: 노드를 스케줄할 수 없다.
  • node.cloudprovider.kubernetes.io/uninitialized: "외부" 클라우드 공급자로 kubelet을 시작하면, 이 테인트가 노드에서 사용 불가능으로 표시되도록 설정된다. 클라우드-컨트롤러-관리자의 컨트롤러가 이 노드를 초기화하면, kubelet이 이 테인트를 제거한다.

 

Toleration은 이러한 Taint를 통과하여 Pod를 배치할 수 있는 방법이다.

Toleration도 Pod에 적용하는 옵션으로 역시 key와 value를 활용한다.

약간의 차이점이라고 하면 operator 동작을 존재하는지(exist), 같은지(equal) 등으로 설정할 수 있다. (기본 값은 equal이다.)

추가적으로 tolerationSeconds 옵션을 통해 지정된 시간만큼 Node에 존재하도록 허용할 수도 있다. 이 시간이 지나면 축출되며 기본 값은 300초이다.

# example-key에 NoSchedule이 있는 경우에도 배치할 수 있도록 Toleration 설정
# 300초가 지나면 허용하지 않으므로 축출됨
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"
		tolerationSeconds: 300

 

이러한 Toleration에는 특별한 사용 예시가 2가지 있다.

# 만약, effect 값이 없다면 해당 key가 존재하기만 해도 통과시킨다.
tolerations:
- key: "example-key"
  operator: "Exists"
# 만약, key 값이 없고, exist 동작을 가진 Toleration이라면,
# 해당 하는 모든 effect를 통과한다.
tolerations:
- operator: "Exists"
  effect: "NoSchedule"

 

이렇게 Taint와 Toleration을 이용하면 Affnity 외에도 더 자세한 Node의 상황을 묘사할 수 있다.


마무리

 

지금까지 Pod가 Node에 배치될 수 있도록 하는 스케줄링 전략에 대해 알아보았다.

  • 전략적으로 사용해야 하는 GPU와 같은 자원을 소모하거나,
  • 고가용성을 위해 균일하게 배치하거나,
  • Cache 서버를 특정 Pod와 동일 Node에 배치하는 등 다양한 전략이 가능하다.

스케줄링 전략이 중요한 이유는 한 번 배치된 Pod는 다시 배치되지 않기 때문이다.

 

한편, 이미 불균형하게 배포된 Pod를 적절하게 재배치하기 위해서 Descheduler라는 프로젝트도 등장하였다.

Kubernetes SIGs 공식 프로젝트로 자원 활용도나, Pod 수 균형 등 다양한 조건을 기준으로

재배치할 수 있는 좋은 프로젝트이다. (한번 찾아보는 것을 권장한다!)


참고자료

노드에 파드 할당하기 | Kubernetes

테인트(Taints)와 톨러레이션(Tolerations) | Kubernetes

 

profile

주녁, DevNote

@junwork

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!