Kubernetes in Action : Chapter16. 고급 스케쥴링

    들어가기 전

    이 글은 쿠버네티스 인 액션 16장을 공부하며 작성한 글입니다.


    16.1 Taint와 Toleration을 사용해 특정 노드에서 파드 실행 제한

    Taint는 오염을 의미하고, Toleration은 용인이라는 의미다. 노드에 Taint(오염)를 이용해 특정 표시를 해두었을 때, Pod가 이것은 Tolerate(용인)하는 경우라면 그 파드는 노드에 스케쥴링이 가능하다는 의미다. Taint와 Toleration은 각각 이런 역할을 하는 녀석들이다. Taint + Toleration은 파드를 특정 노드에 스케쥴링 하도록 하기 보다는 특정 노드에 파드가 배포되지 않도록 동작한다. 

    • Taint : 노드에 추가할 수 있는 스케쥴링 관련 정보다. 노드에 추가된 모든 Taint를 만족하는 Pod만 스케쥴링 될 수 있도록 함. 
    • Pod : Pod에는 Toleration을 추가할 수 있음. Pod에 추가된 모든 Toleration이 Taint를 만족시키면 Tain가 있는 노드에 파드를 스케쥴링 할 수 있음. 
    • Toleration은 스케쥴을 '허용'하지만, 노드에 스케쥴링 되도록 '보장'하지는 않는다.

    이 절에서는 Taint와 Toleration을 이용해 특정 노드에서 파드 실행을 제한하는 방법을 살펴본다. 공식문서는 이곳(https://kubernetes.io/ko/docs/concepts/scheduling-eviction/taint-and-toleration/)에 있다. 


    16.1.1 Taint와 Toleration 소개

    가장 쉽게 찾아볼 수 있는 Taint의 예시는 마스터 노드다. 아래 명령어를 이용해서 마스터 노드의 Taint를 살펴보자. 

    $ kubectl describe node ubuntu-master | grep -i taints
    >>> 
    Taints:             node-role.kubernetes.io/control-plane:NoSchedule

    마스터 노드에는 node-role.kubernetes.io/control-plane:NoSchedule 이라는 Taint가 달려있음을 알 수 있다. Taint는 Key / Value / Effect로 구성되어있다. 구성은 다음과 같고, 구성을 바탕으로 Taint를 살펴보면 Key / Effect로 나눠 볼 수 있음을 알 수 있다. 참고로 Value는 Null이 될 수 있으며 Value를 사용하면 Toleration을 사용하는 방법이 조금 바뀐다. 

    # value를 사용하는 경우
    key=value:effect
    
    # value를 사용하지 않는 경우. Value는 Null이 될 수 있음. 
    key:effect
    
    # Taints 분석
    key: node-role.kubernetes.io/control-plane
    effect : NoSchedule

    Taint / Toleration은 다음 작용을 한다

    • Taint가 있는 노드에는 모든 Taint를 만족하는 Toleration을 가진 파드만 스케쥴링됨. 
      • 3개의 Taint를 가진 노드라면, 파드는 3개의 Toleration이 필요함. 
    • Toleration을 가진 파드는 Taint가 하나도 존재하는 노드에 스케쥴링 가능함. 


    어떤 파드가 Node-role.kubernetes.io/Control-plane Toleration을 가질까?

    기본적으로 쿠버네티스는 마스터 노드에 어떠한 파드도 배포하지 않으려고 한다. 그렇지만 실제로 마스터 노드에는 몇몇 파드들이 배포되어있다. 마스터 노드에 배포된 파드는 주로 kube-proxy같은 시스템 파드다. 


    파드의 Toleration 표시하기

    파드의 Toleration은 매니페스트 파일에 정의된다. 따라서 다음 명령어를 사용하면 Toleration을 살펴볼 수 있다. 

    $ kubectl get pod calico-node-72dls -o yaml -n kube-system | grep -i toleration -A 10
    >>>
      tolerations:
      - effect: NoSchedule
        operator: Exists
      - key: CriticalAddonsOnly
        operator: Exists
      - effect: NoExecute
        operator: Exists
      - effect: NoExecute
        key: node.kubernetes.io/not-ready
        operator: Exists
      - effect: NoExecute

    kube-proxy라는 DaemonSet 파드의 Toleration을 살펴보면 위와 같다. 그런데 한 가지 이상한 점은 Taint는 반드시 Key / Effect가 필요하다고 했는데, Key가 없는 Toleration도 존재한다는 것이다. 이것은 공식 문서의 아래 내용에서 알 수 있다.

    Note:
    There are two special cases:
    - An empty key with operator Exists matches all keys, values and effects which means this will tolerate everything.
    (Exists Operator이면서 Key가 없는 것은 모든 Key, Value와 매칭됨)
    - An empty effect matches all effects with key key1.
    (Effect가 비어있으면 모든 Effect에 대응됨)

    KubeProxy는 다음 특성을 가진다. 

    • 어떤 Key / Value를 가진 Noschdule Effect 노드에 배포 가능함. 
    • CriticalAddonsOnly Key가 있는 노드에 어떤 Effect를 가져도 배포 가능함. 등등

    Taint 효과(Effect)이해하기

    Taint는 Key / Value / Effect로 표현할 수 있다고 했다. Taint의 Effect는 세 가지가 존재한다. 

    이름 효과
    NoSchedule Toleration을 가진 파드만 스케쥴링 가능함. 기존에 배포된 파드에는 영향 안 줌.
    PreferNoSchedule Toleration을 가지지 않은 파드는 어쩔 수 없을 때만 스케쥴링 함. (배포할 노드가 없는 경우). 기존에 배포된 파드에는 영향 안 줌.
    NoExecute Toleration을 가진 파드만 스케쥴링 가능함. 기존에 배포된 파드도 Toleration이 없으면 제거됨. 

    16.1.2 노드에 사용자 정의 Taint 추가하기

    노드에 Taint를 추가할 수 있다. Taint를 추가하는 명령어는 다음과 같다. 추가한 Taint를 제거하고자 한다면, Taint 명령어 마지막에 -를 붙여서 빼주면 된다. 

    # 추가하는 방법
    $ kubectl taint node <node_name> <key>=<value>:<effect>
    
    # 삭제하는 방법
    $ kubectl taint node <node_name> <key>=<value>:<effect>-
    
    # Key + Value
    $ kubectl taint node ubuntu-master key1=value1:NoSchedule
    
    # Key
    $ kubectl taint node ubuntu-master key1:NoSchedule

    16.1.3 파드에 Toleration 추가하기

    파드 스펙에 tolerations 영역에 파드가 가지는 Toleration을 추가할 수 있다.  

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name : c16-3
    spec:
      replicas: 5
      selector:
        matchLabels:
          app: c16-3
      template:
        metadata:
          labels:
            app: c16-3
        spec:
          containers:
            - name: busybox
              image: busybox
              command: ["sleep", "99999"]
          # Toleration 추가
          tolerations:
            - key: k1
              value: v1
              effect: NoSchedule
              operator: Equal

    Toleration을 추가하고 안 하고에 따라서 배포된 노드는 아래처럼 차이가 난다.

    # Toleration 없이 Deployment 배포 → Worker2에만 배포됨
    $ kubectl get pod -o wide -w
    NAME                    READY   STATUS    RESTARTS        AGE    IP            NODE             NOMINATED NODE   READINESS GATES
    c14-13-pod              1/1     Running   3 (2d13h ago)   6d5h   50.0.91.252   ubuntu-worker1   <none>           <none>
    c16-3-5994c57d6-4tjvk   1/1     Running   0               24s    50.0.86.47    ubuntu-worker2   <none>           <none>
    c16-3-5994c57d6-pnrvz   1/1     Running   0               24s    50.0.86.59    ubuntu-worker2   <none>           <none>
    c16-3-5994c57d6-vkz6n   1/1     Running   0               24s    50.0.86.15    ubuntu-worker2   <none>           <none>
    c16-3-5994c57d6-zqlzd   1/1     Running   0               24s    50.0.86.44    ubuntu-worker2   <none>           <none>
    c16-3-5994c57d6-zvw77   1/1     Running   0               24s    50.0.86.26    ubuntu-worker2   <none>           <none>
    
    # Toleration와 함께 Deployment 배포 → 골고루 배포됨.
    $ kubectl get pod -o wide 
    NAME                     READY   STATUS    RESTARTS        AGE    IP            NODE             NOMINATED NODE   READINESS GATES
    c14-13-pod               1/1     Running   3 (2d14h ago)   6d5h   50.0.91.252   ubuntu-worker1   <none>           <none>
    c16-3-76ffdf9588-875dr   1/1     Running   0               10s    50.0.91.195   ubuntu-worker1   <none>           <none>
    c16-3-76ffdf9588-jnkmq   1/1     Running   0               10s    50.0.91.221   ubuntu-worker1   <none>           <none>
    c16-3-76ffdf9588-kj8zg   1/1     Running   0               10s    50.0.91.225   ubuntu-worker1   <none>           <none>
    c16-3-76ffdf9588-w8bs5   1/1     Running   0               10s    50.0.86.63    ubuntu-worker2   <none>           <none>
    c16-3-76ffdf9588-wh45l   1/1     Running   0               10s    50.0.86.34    ubuntu-worker2   <none>           <none>

    16.1.4 Taint와 Toleration 활용 방안 이해

    다른 방법도 더 있겠지만, 가장 기초적인 것은 다음 두 가지 방법이 있다. 

     

    스케쥴링에 Taint + Toleration 사용하기

    첫번째 예시는 특별한 하드웨어가 있는 노드의 스케쥴링이다. 예를 들어 GPU가 있는 노드가 있는데, GPU가 필요없는 파드를 배포할 필요는 없다. 이럴 때는 Taint에 GPU를 추가하고, GPU가 필요한 파드만 Toleration을 추가해서 해당 노드에 배포가 가능하도록 처리한다. 

    두번째는 특정 그룹의 어플리케이션을 특정 노드에만 배포하도록 만들 수 있다. 예를 들어 Taint group=group1:NoSchedule / group=group2:NoSchedule을 각각의 노드에 추가할 수 있다. 그리고 A 팀은 group1 Taint가 있는 노드에만 배포하도록 Toleration을 작성할 수 있다. 

    이런 것처럼 각 Effect를 이런 의도로 사용할 수 있다.

    • NoSchedule : 새 파드의 스케쥴링을 방지
    • PreferNoSchedule : 선호하지 않는 노드를 정의.
    • NoExecute : 노드에서 기존 파드를 제거 

     

    노드 실패 후 파드를 재스케쥴링하기까지의 시간 설정 (tolerationSeconds)

    NoExecute Effect는 이 Taint를 만족하지 못하는 파드를 노드에서 모두 제거한다. 그런데 이 때, 파드의 Toleration에 tolerationSeconds를 지정하느냐에 따라서 다르게 동작한다. 

    • NoExecute Toleration 가지지 못한 파드 : 노드에서 바로 Evict 됨.
    • NoExecute Toleration 있음 + tolerationSeconds 있음 : 노드에서 파드가 tolerationSeconds만큼 Bind된 후 제거됨. 
      • tolerationSeconds 시간이 다 되기 전에 노드에서 Taint를 제거하면 파드가 Evict 되지 않음. 
    • NoExecute Toleration 있음 + tolerationSeconds 없음 : 노드에서 Bound됨. 

    16.1.5 기타

    Operator 관점

    Operator에는 Exist와 Equal이 존재하는데, 둘의 사용방법은 다르다.

    • Operator가 Exist인 경우, value를 사용할 수 없음.
    • Operator가 Equal인 경우 value가 있어야함. 
    • Operator가 표시되지 않으면 기본값 'Equal'을 사용함.

    다중 Taint  / Toleration

    아래의 Taint를 가진 노드가 있을 때, 이 Toleration을 가진 파드가 있다고 가정해보자. 이 경우, 파드는 노드에 배포될 수 없다. 왜냐하면 세번째 Taint인 key2=value2:NoSchedule을 만족하는 Toleration이 없기 때문이다. 정리하면 노드가 가지고 있는 모든 Taint를 만족하는 Toleration이 있어야 파드가 스케쥴링 된다. 

    kubectl taint nodes node1 key1=value1:NoSchedule
    kubectl taint nodes node1 key1=value1:NoExecute
    kubectl taint nodes node1 key2=value2:NoSchedule
    
    
    tolerations:
    - key: "key1"
      operator: "Equal"
      value: "value1"
      effect: "NoSchedule"
    - key: "key1"
      operator: "Equal"
      value: "value1"
      effect: "NoExecute"

    16.2 Node Affinity를 사용해 파드를 특정 노드에 배포하기

     Node Affinity는 Node Selector에서 좀 더 복잡한 기능이 들어간 버전이다. 비교하면 다음과 같다.

    항목 내용
    Node Selector 파드가 특정 Label을 가진 Node에만 배포되도록 함.
    Node Affinity 파드가 선호하는 Label을 설정, 선호하는 라벨을 가진 노드에 우선 분배됨. 
    (우선 분배하고 안되면 다른 노드에 분배함)
    Taint & Toleration Taint를 기반으로 동작함. (라벨이 아님)

    Node Affinity와 관련된 공식 문서는 이곳(https://kubernetes.io/ko/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/)에서 확인 가능하다. 


    Node Affinity로 파드 배포하기

    Node Affinity는 라벨을 기반으로 동작한다. 동작하는 메커니즘은 아래와 같다. 

    1. 노드에 라벨을 붙인다.
    2. 파드의 Spec의 NodeAffinity쪽에 선호하는 라벨을 선언한다
    3. 쿠버네티스가 파드를 스케쥴링 할 때, NodeAffinity를 참고해서 파드를 스케쥴링한다. 

    따라서 이 실습을 하기 위해서는 노드에 라벨을 붙이고, 파드를 선언하면 된다.

    # 노드 레이블링
    # worker1 / worker2에 각각의 라벨을 붙임
    $ kubectl label node ubuntu-worker1 availability-zone=zone1
    $ kubectl label node ubuntu-worker2 share-type=dedicated
    
    
    # 선호도를 다르게 줌. 
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: c16-9
    spec:
      replicas: 10
      selector:
        matchLabels:
          app: c16-9
      template:
        spec:
          affinity:
            nodeAffinity:
              preferredDuringSchedulingIgnoredDuringExecution:
                - preference:
                    matchExpressions:
                      - key: availability-zone
                        operator: In
                        values:
                          - zone1
                  weight: 80
                - preference:
                    matchExpressions:
                      - key: share-type
                        operator: In
                        values:
                          - dedicated
                  weight: 20
          containers:
            - name: c16-9
              image: busybox
              command: ["sleep", "99999"]

    배포를 한 결과를 살펴보면 다음과 같다. 만약 availability-zone=zone1 라벨이 있는 Node에 Taint가 붙어 있는 경우라면, 이 Deployment는 Tolerance가 없어서 해당 Node에는 절대로 배포될 수 없다. 앞에서 실습을 하고 넘어온 경우라면 이 문제가 있을 수 있기 때문에 미리 노드에 있는 Taint를 제거하는 것을 추천한다.

    $ kubectl get pod -o wide -w
    NAME                     READY   STATUS    RESTARTS   AGE   IP            NODE             NOMINATED NODE   READINESS GATES
    c16-9-6557df5998-5r8x5   1/1     Running   0          45s   50.0.91.222   ubuntu-worker1   <none>           <none>
    c16-9-6557df5998-7mqqb   1/1     Running   0          45s   50.0.91.241   ubuntu-worker1   <none>           <none>
    c16-9-6557df5998-82d45   1/1     Running   0          45s   50.0.91.210   ubuntu-worker1   <none>           <none>
    c16-9-6557df5998-8lw78   1/1     Running   0          45s   50.0.91.253   ubuntu-worker1   <none>           <none>
    c16-9-6557df5998-9m47n   1/1     Running   0          45s   50.0.91.211   ubuntu-worker1   <none>           <none>
    c16-9-6557df5998-9pqtl   1/1     Running   0          45s   50.0.91.248   ubuntu-worker1   <none>           <none>
    c16-9-6557df5998-9pxcg   1/1     Running   0          45s   50.0.86.63    ubuntu-worker2   <none>           <none>
    c16-9-6557df5998-hv9rs   1/1     Running   0          45s   50.0.91.254   ubuntu-worker1   <none>           <none>
    c16-9-6557df5998-rwhkm   1/1     Running   0          45s   50.0.86.22    ubuntu-worker2   <none>           <none>
    c16-9-6557df5998-zf4pj   1/1     Running   0          45s   50.0.91.217   ubuntu-worker1   <none>           <none>

     


    Node Affinity와 관련된 것들

    NodeAffinity는 아래 두 가지 필드를 이용해서 적용할 수 있다.

    항목 내용
    requiredDuringScheduling
    IgnoredDuringExecution
    - 스케쥴링을 할 때만 동작함. 이미 실행중인 파드에는 영향 없음. 
    - NodeAffinity에 매칭되는 라벨을 가진 노드에만 배포됨. 없으면 Pending.
    preferredDuringScheduling
    IgnoredDuringExecution
    - 스케쥴링을 할 때만 동작함. 이미 실행중인 파드에는 영향 없음. 
    - NodeAffinity에 매칭되는 라벨의 선호도를 가지고, 우선적으로 배포됨.
    - 배포할 노드가 없으면 매칭되는 라벨이 없는 노드라도 배포됨. 

    RequiredDuringSchedulingIgnoredDuringExeuction으로 NodeAffinity를 사용했을 때, 매칭되는 라벨을 가진 노드가 하나도 없으면 파드는 Pending 상태로 대기한다. 

    # RequiredDuringScheduling → 맞는 Label이 없어서 Pending됨. 
    
    Events:
      Type     Reason            Age   From               Message
      ----     ------            ----  ----               -------
      Warning  FailedScheduling  31s   default-scheduler  0/4 nodes are available: ... 2 node(s) didn't match Pod's node affinity/selector. preemption: 0/4 nodes are available: 4 Preemption is not helpful for scheduling..

     

    PreferredDuringSchedulingIgnoredDuringExecution을 사용하는 경우는 각 라벨에 대한 선호도가 선택된다. 그리고 배포할만한 노드가 없으면, NodeAffinity에 대응되는 라벨이 없는 노드라도 배포가 된다. 선호도에 따른 라벨 우선순위는 다음과 같다. 

    - preference:
        matchExpressions:
          - key: availability-zone
            operator: In
            values:
              - zone1
      weight: 80
    - preference:
        matchExpressions:
          - key: share-type
            operator: In
            values:
              - dedicated
      weight: 20

    다음 선호도가 존재할 때, 라벨의 우선순위는 다음과 같다. 우선순위에 따라서 파드를 주로 배포한다. 

    우선순위 라벨
    1 (Availability-zone : zone1)  + (share-type : dedicated)
    2 Availability-zone : zone1
    3 share-type : dedicated
    4 아무 라벨
    • 선호도 
      • availability-zone : zone1 (80%)
      • share-type : dedicated (20%)

    Node Affinity에서 우선순위 100%를 했는데?? 

    Node Affinity에서 아래 매니페스트 파일처럼 우선순위 100%를 한 경우를 가정해보자. 

    preferredDuringSchedulingIgnoredDuringExecution:
      - preference:
          matchExpressions:
            - key: availability-zone
              operator: In
              values:
                - zone1
        weight: 100

    그런데 실제 배포를 했을 때, Availability-Zone = zone1 라벨이 있는 노드뿐만 아니라 다른 노드에도 배포될 수 있다. 예를 들어 아래처럼 말이다.

    # 2개의 워커 노드 존재
    # w1 노드에 availability-zone = zone1 라벨 있음. 
    # w2 노드에 라벨 없음.
    
    # 배포 결과
    NAME        NODE
    pod1        w1
    pod2        w1
    pod3        w1
    pod4        w1
    pod5        w2

    이것은 쿠버네티스 스케쥴러가 가진 기능 중 하나다. 쿠버네티스 스케쥴러는 NodeAffinity를 참고해서 스케쥴링 하는데, 다른 우선순위 지정 기능도 참고한다. 그 중 하나가 Selector-SpreadPriority 기능이다. 

    Selector-SpreadPriority는 동일한 ReplicaSet / Service에 속하는 파드가 하나의 노드에만 배포되지 않도록 하는 기능이다. 동일한 역할을 하는 파드가 하나의 노드에만 배포되어 있는 경우, 노드 Fail 발생 시 큰 문제가 발생할 수 있기 때문에 쿠버네티스는 Selector-SpreadPriority를 이용해서 분산시켜준다. 


    16.3 파드 어피니티 / 안티 어피니티를 이용해 파드 함께 배치하기 

    노드 어피니티는 파드 - 노드 간의 어피니티에만 영향을 미친다. 파드 간의 어피니티를 설정해야하는 경우에는 파드 어피니티 / 안티 어피니티를 사용해야한다. 


    16.3.1 파드 간 어피니티를 사용해 같은 노드에 파드 배포하기 

    백엔드 / 프론트 파드를 같은 노드에 배포하는 상황을 가정해보자. 먼저 아래 백엔드 파드를 배포한다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: c16-13-back
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: back-13
      template:
        metadata:
          name: c16-13-back
          labels:
            app: back-13
        spec:
          containers:
            - name: back
              image: busybox
              command: [ "sleep", "99999" ]

    이후 프론트엔드 파드를 배포한다. 프론트엔드 파드는 PodAffinity를 설정해서 파드끼리 같은 곳에 배포되도록 한다.

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: c16-13-front
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: front
      template:
        metadata:
          name: c16-13-front
          labels:
            app: front
        spec:
          containers:
            - name: front
              image: busybox
              command: ["sleep", "99999"]
          affinity:
            podAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                - topologyKey: kubernetes.io/hostname
                  labelSelector:
                    matchLabels:
                      app: back-13

    프론트엔드 파드를 파드 어피니티를 설정하면 된다. 이 때 다음과 같은 방식으로 배포된다.

    1. 스케쥴러는 labelSelector의 조건과 매칭되는 모든 파드를 찾는다. 여기서 app=back-13이라는 라벨을 가진 파드를 찾는다.
    2. 검색된 파드에서 같은 topologyKey를 가진 '노드'로 Frontend 파드를 배포한다. 

    그림으로 도식화해보면 다음과 같다. 

    App: Backend 라벨을 가진 Pod Affinity를 가지고, 노드와 관련된 Topology Key는 Fail.zone으로 한다고 가정해보자. 그러면 다음과 같이 동작한다.

    1. 스케쥴러는 App: Backend를 라벨로 가진 모든 파드를 찾아온다. 
    2. 위에서 필터링된 파드가 배포된 노드의 라벨을 살펴본다. 각 노드의 라벨 중 Fail.zone을 가지고 있는 노드에 파드를 배포한다. 

    예를 들어 필터링 된 파드 중 어떤 녀석들은 배포된 노드가 fail.zone이라는 라벨을 가지고 있지 않을 수 있다. 위의 그림에서는 총 6개의 필터링 된 파드가 있지만, 이중에서 TopologyKey를 Fail.zone으로 가지는 녀석은 2개의 파드 밖에 없고, 해당 파드가 있는 Node1 / Node3 중에 선택해서 배포한다. 

    반대로 topologyKey를 kubernetes.io/hostname으로 할 수도 있다. 이 경우에는 Node1 / Node2 / Node3 모두 이 라벨을 가지고 있기 때문에 스케쥴링 대상이 된다. 

    topologyKey는 배포되는 노드의 단위를 이야기한다. failzone으로 선택한 경우는 다음과 같이 배포된다.

    1. 필터링 한 파드는 Pod1 / Pod2만 남는다.
    2. 필터링 된 파드의 TopologyKey는 fail.zone이다. Fail.Zone은 z1이다. 
    3. 노드 중 Fail.Zone 라벨을 가진 것은 Node1 / Node3이 된다. 
    4. 스케쥴러는 파드 어피니티 룰을 적용해서 Node1/  Node3를 선택해서 배포한다. 

    아래에서 결과를 확인할 수 있다.

    • 노드 라벨 상태
      • ubuntu-worker1 / ubuntu-worker3 : test 라벨을 가지고 있음.
      • ubuntu-worker2 : test 라벨을 가지지 않음. 
    • front의 Pod Affinity
      • c16-13-back의 파드를 Pod Affinity로 설정함.
      • TopologyKey를 test로 설정함. 
      • Node 중 test 라벨을 가진 것은 worker1 / worker3 밖에 없음. → Front 파드는 worker1 / worker3에만 배포됨. 
    NAME                                 READY   STATUS    RESTARTS      AGE     IP            NODE             NOMINATED NODE   READINESS GATES
    c16-13-back-7544759d68-6hgv4         1/1     Running   0             2m28s   50.0.91.221   ubuntu-worker1   <none>           <none>
    c16-13-front-66f5576c7c-2rj7t        1/1     Running   0             15s     50.0.187.27   ubuntu-worker3   <none>           <none>
    c16-13-front-66f5576c7c-8stf6        1/1     Running   0             115s    50.0.91.223   ubuntu-worker1   <none>           <none>
    c16-13-front-66f5576c7c-97wfc        1/1     Running   0             15s     50.0.91.226   ubuntu-worker1   <none>           <none>
    c16-13-front-66f5576c7c-bz4rc        1/1     Running   0             115s    50.0.91.222   ubuntu-worker1   <none>           <none>
    c16-13-front-66f5576c7c-knq27        1/1     Running   0             15s     50.0.187.63   ubuntu-worker3   <none>           <none>
    c16-13-front-66f5576c7c-mjfk2        1/1     Running   0             15s     50.0.91.224   ubuntu-worker1   <none>           <none>
    c16-13-front-66f5576c7c-nh26l        1/1     Running   0             15s     50.0.91.225   ubuntu-worker1   <none>           <none>
    c16-13-front-66f5576c7c-r2tht        1/1     Running   0             15s     50.0.187.39   ubuntu-worker3   <none>           <none>
    c16-13-front-66f5576c7c-rb4hn        1/1     Running   0             115s    50.0.187.7    ubuntu-worker3   <none>           <none>
    c16-13-front-66f5576c7c-vmw9l        1/1     Running   0             15s     50.0.187.62   ubuntu-worker3   <none>           <none>

     


    스케쥴러가 파드 어피니티 규칙을 사용하는 방법 이해  (파드가 지워지면?)

    앞서서 배포할 때, 백엔드 파드를 먼저 배포한 후에 프론트 파드를 배포하는 작업을 했다. 이 때 Pod Affinity 규칙은 프론트 파드에만 존재하고 있었다. 그렇다면 이런 경우를 고려해보면 어떨까?

    백엔드 파드를 제거하면, 파드는 어떻게 스케쥴링 될까? 

    백엔드 파드가 다시 스케쥴링 될 때, 스케쥴러는 프론트 파드의 명세에만 있는 Pod Affinity 규칙을 지키면서 백엔드 파드를 다시 스케쥴링 한다. 예를 들어 프론트 파드가 백엔드 파드와 같은 노드에 배포되도록 Pod Affinity가 정해져있다면, 스케쥴러는 백엔드 파드가 재기동할 때 프론트 파드가 존재하는 곳에 백엔드 파드를 스케쥴링 한다. Pod Affinity의 규칙이 깨지지 않도록 스케쥴러가 스케쥴링 해준다. 


    16.3.2 동일한 Rack, 가용 영역, Region에 파드 배포

    Pod Affinity를 이용할 때, 각 Pod가 배포될 노드를 선택할 수 있다. 

    • Pod Affinity에 선택된 Pod와 같은 Rack에 있는 노드에 배포
    • Pod Affinity에 선택된 Pod와 같은 가용영역에 있는 노드에 배포
    • Pod Affinity에 선택된 Pod와 같은 Region에 있는 노드에 배포

    이런 느낌으로 배포될 노드를 선택할 수 있다. 이것은 앞서 설명했던 topologyKey를 이용해서 선택할 수 있으며, topologyKey는 작게는 호스트네임(같은 노드)부터 크게는 같은 Region(같은 지역에 속하는 노드)까지 범위를 줘서 선택할 수 있다. 

    TopologyKey는 다음과 같이 동작한다.

    1. 스케쥴러는 PodAffinity 구성을 확인하고 레이블 셀렉터와 일치하는 파드를 찾음.
    2. 찾은 파드가 실행중인 노드들 중 podAffinity에 지정된 topologyKey 필드와 일치하는 Key를 갖는 노드 Label을 찾음. (value는 상관없음) 
    3. 찾은 파드가 돌아가고 있는 노드의 topologyKey 라벨의 Value가 같은 노드를 선택해서 스케쥴링함. 

    16.3.3 필수 요구 사항 대신 Pod Affinity 선호도 표현하기 

    Pod Affinity는 Soft Affinity / Hard Affinity가 존재한다. 각각은 다음과 같이 사용 가능하다. 

    항목 구성 동작 방식
    Hard Affinity Required... 만족하는 녀석이 없으면, Pod는 스케쥴링 되지 않음.
    Soft Affinity Preffered... 1. 만족하는 녀석이 없으면, 다른 노드에 스케쥴링 됨. 
    2. 선호도를 지정해서, 각 조건별로 다르게 분배해서 스케쥴링 할 수 있음. 

    Preffered 옵션을 사용해서 배포하면 된다. 구성하는 방식은 NodeAffinity와 동일하다고 보면 된다. 


    16.3.4 Pod Anti Affinity를 사용해 파드들이 서로 떨어지게 스케쥴링 하기 

    서로 다른 노드에 배포되어야만 하는 파드들이 존재할 수도 있다. 이 때는 Anti Affinity를 이용해서 배포할 수 있다. Anti-Affinity도 Pod Affinity와 마찬가지로 파드를 라벨로 찾아낸 후에 TopologyKey를 바탕으로 배포하지 않을 노드를 선택한다. 

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: c16-18
    spec:
      replicas: 5
      template:
        metadata:
          name: c16-18
          labels:
            app: c16-18-frontend
        spec:
          affinity:
            podAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
                - topologyKey: kubernetes.io/hostname
                  labelSelector:
                    matchLabels:
                      app: c16-18-frontend
          containers:
            - name: c16-18-frontend
              image: busybox
              command: ["sleep", "99999"]

    다음과 같이 Anti Affnity를 설정해서 배포할 수 있다. 이것은 결과적으로 다음과 같이 동작한다. 

    • 여기서 생성되는 파드는 서로 다른 노드에 배포된다. 이유는 다음과 같다.
      • c16-18-frontend가 배포된 파드를 검색함. 이 곳에서 파드가 배포된 노드와 같은 곳에 배포되지 않도록 topologyKey를 kubernetes.io/hostname으로 지정함.

    파드 어피니티 사용 시 주의 사항

    공식 문서에서는 파드 / 안티 어피니티는 꽤 많은 컴퓨팅 리소스가 필요하기 때문에 많은 노드에서는 사용하지 않는 것을 추천한다고 한다. 아무래도 스케쥴러가 파드 어피니티 규칙을 사용하는 방법 이해  (파드가 지워지면?) 이 부분에서 설명했던 것 같은 알고리즘을 구현하기 위해 많은 컴퓨팅 파워를 사용하는 것이 아닌가 예상된다. 

    참고: 파드간 어피니티와 안티-어피니티에는 상당한 양의 프로세싱이 필요하기에 대규모 클러스터에서는 스케줄링 속도가 크게 느려질 수 있다. 수백 개의 노드를 넘어가는 클러스터에서 이를 사용하는 것은 추천하지 않는다.
    https://kubernetes.io/ko/docs/concepts/scheduling-eviction/assign-pod-node/

    16.4 요약

    • Taint - Toleration
      • Node에 Taint(얼룩)을 묻히면, 이것을 Tolerate 할 수 있는 Pod만 해당 Node에 배포될 수 있음. Taint / Toleration은 스케쥴링을 보장하는 것이 아니라 '스케쥴링이 가능'하다는 것을 의미함. 
      • Taint는 key=value:effect / key:effect 형식으로 구성됨. 
      • effect는 다음이 존재함.
        • NoSchedule : Toleration이 없는 파드는 스케쥴링 불가능. 이미 배포된 파드에는 영향 없음.
        • PrefferedNoSchedule : Toleration이 없는 파드는 어쩔 수 없을 때만 스케쥴링 가능. 이미 배포된 파드에는 영향 없음.
        • NoExecute : Toleration이 없는 파드는 스케쥴링 불가능. 이미 배포된 파드는 제거함. 
      • toleration + toleration Second Time
        • Effect가 NoExecute인 경우 다음과 같이 동작함.
        • toleration 없음 → 바로 제거됨.
        • toleration 존재 + tolerationSecondTime 존재 → 이 시간만큼만 파드가 생존한 후, 제거됨.
        • toleration 존재 + tolerationSecondTime 없음 → 파드는 계속 생존함. 
      • Operator 관점
        • Operator가 Exist인 경우, value를 사용할 수 없음. key만 판단함. 
        • Operator가 Equal인 경우 value가 있어야함. 
        • Operator가 표시되지 않으면 기본값 'Equal'을 사용함.
      • 다중 Taint를 가진 노드에 스케쥴링이 가능하려면, 모든 Taint를 Toleration 해야 스케쥴링 가능함.
    • Node Affinity
      • Soft / Hard Affinity가 존재함. (prefferedDuring.. / RequiredDuring...)
      • 스케쥴링 하는 시점에만 고려됨. 이미 배포된 파드에는 영향을 주지 않음.
      • 노드가 가진 라벨을 기반으로 배포할 곳을 선택함.
      • PrefferedDuring.. 을 선택하면 선호도를 선택할 수 있음.
        • A : 80% , B : 20%를 지정할 수 있음. 이 경우, A+B > A > B > 없음 순으로 우선순위가 생성되고 배포됨. 
        • A : 100%로 지정하더라도, 다른 노드에 일부 파드가 배포될 수 있음. 이 경우, 스케쥴러의 우선순위 함수 중 spread-Priority가 동작함. (단일 노드에 배포 시, 장애시 문제가 발생할 수 있음.)
    • Pod Affinity / Anti Affinity
      • Pod의 라벨 + Node의 라벨(topologyKey)을 기반으로 동작함. 
      • 스케쥴링 하는 시점에만 고려됨. 
      • Soft / Hard Affinity가 존재함. (prefferedDuring.. / RequiredDuring...)
      • 스케쥴링은 다음과 같이 동작함.
        • Affinity에 설정된 라벨을 가진 파드를 모두 찾음.
        • 해당 파드가 배포된 노드들 중에 topologyKey를 라벨로 가지는 노드를 찾음. topologyKey 라벨 중, 파드가 배포된 노드의 Value를 수집함. 
        • 노드들 중 수집한 Value를 라벨로 가지는 노드에 스케쥴링을 함. 
        • 예를 들어 Node1는 test: A, Node2는 test:B, Node3는 test:A를 가지고 있다고 가정. 그리고 선택된 파드가 test:A라는 topologyKey에 해당된다고 가정. 스케쥴러는 Node1, Node3에만 스케쥴링함.
      • topologyKey를 이용해서 배포될 노드의 범위를 선택할 수 있음. (kubernetes.io/hostname, fail.zone 등등)
      • A 파드가 배포되고, B 파드가 A 파드에 PodAffinity를 가지도록 설정되었을 때, A 파드가 제거되어서 재기동되면 스케쥴러는 B 파드가 가진 PodAffinity 규칙이 깨지지 않도록 배포함. (이 때, A파드는 PodAffinity 설정값을 가지지 않음. )

    댓글

    Designed by JB FACTORY