본문 바로가기

인프라 기술/Kubernetes

[K8s: 쿠버네티스 인 액션] 16강 - 고급 스케줄링

728x90

파드가 스케줄링되는 위치를 노드 셀렉터 설정 말고 영향을 줄 수 있음

 

 

 

16.1 테인트와 톨러레이션을 사용해 특정 노드에서  파드 실행 제한

 

> 테인트 / 톨러레이션은 어떤 파드가 특정 노드를 사용할 수 있는지를 제한하기 위해 사용

> 노드의 [테인트가 허용된(tolerate) 경우] 에만 파드가 해당 노드에 스케줄링 될 수 있음

> 노드 셀렉터와는 다르게, 테인트는 기존 파드를 수정하지 않고, 특정 노드에 테인트를 추가하는 것만으로도 해당 노드에 배포되지 않도록 할 수 있음

 

 

 

16.1.1 테인트와 톨러레이션 소개

 

<노드의 테인트 표시>

 

$ kubectl describe node master.k8s > 마스터 노드의 세부 정보 출력

Labels ...
Annotations ..
Taints    node-role.kubernetes.io/master:NoSchedule    # 마스터 노드에 하나의 테인트 확인 가능

 

> 테인트에는 키 (Key), 값 (Value), 효과 (Effect) 가 있고, <key>=<value>:<effect> 형태로 표시됨

> 즉 위에는 키는  ~~.io 까지, Value 는 NULL (없음) 이고, 효과는 NoSchedule 이다. 

> 파드가 이 테인트를 허용하지 않는한 마스터 노드에 스케줄링될 수 없도록 막는다. 이 테인트를 허용하는 것은 마스터 노드에서 실행될 필요가 있는 시스템 파드이다. 

 

 

<파드의 톨러레이션 표시 - 파드가 테인트를 허용하는 행위>

 

$ kubectl describe po kube-proxy-80wqm -n kube-system

...

Tolerations : noe-role.kube.io/master=:NoSchedule ( 그 외 notReady=:Exists:NoExecute , unreachable=:Exists:NoExecute 인 톨러레이션도 존재)

 

> 위 톨러레이션은 마스터 노드에 적용된 테인트와 일치하므로, 해당 pod는 본인이 마스터 노드에 스케줄링 되는 것을 허용한다

 

 

<테인트 이해하기>

 

> 위 NoSchedule, NoExecute 처럼, 테인트는 관련된 효과를 참조할 수 있다 (테인트 입장에서 보는게 맞는 듯, 파드 입장에선 그냥 해당 테인트 허용 여부만!) 

 

1. NoSchedule - 파드가 테인트를 허용하지 않는 경우, 파드가 노드에 스케줄링되지 않음 
2. PreferNoSchedule - 1번을 따르되, 다른 곳에 스케줄링 할 수 없으면 해당 노드에 스케줄링 가능
3. NoExecute - 이미 실행중인 파드에도 영향을 줌. NoExceut 테인트를 노드에 추가하면 해당 노드에서 이미 실행중이여도, NoExecute 테인트를 허용하지 않은 파드는 노드에서 제거됨 (1,2번은 아닌듯?)

 

 

 

 

16.1.2 노드에 사용자 정의 테인트 추가하기

 

 

> 프로덕션 노드에서 프로덕션이 아닌 파드가 실행되지 않도록 하는 Use Case ( 근데 [프로덕션 노드] 라는걸 따로 함? 노드도 레이블을 줄 수 있었나?)

> 위 Use Case 를 위해서는 프로덕션 노드에 테인트를 추가하면 됨. 

 

$ kubectl taint node node1.k8s  node-type=production:NoSchedule (Key=value:효과)

node "node1.k8s" tainted # 적용되었다는 출력

 

$ kubectl run test --image busybox --replicas 5 -- sleep 99999

deployment "test" created >> 5 개의 imagebox 를 사용한 파드 레플리카를 띄우는 디플로이먼트를 만듬

 

$ kubectl get po -o wide

만든 파드들을 모두 확인해서 어떤 NODE 에 배포되었는지 확인해보면, node1.k8s 에는 띄워지지 않았음 (node-type=production 톨러레이션이 없기 때문) 

 

>> 살짝 헷갈리는 부분은, node-type=production 이란 톨러레이션 명시가 중요한건지, :NoSchedule 까지의 톨러레이션 명시가 중요한건지? 톨러레이션은 그냥 테인트와 완벽히 일치하는 text 값이여야 하는게 중요한건지??

 

 

 

16.1.3 파드에 톨러레이션 추가

 

> production 노드에 배포하고 싶은 디플로이먼트를 만든다고 해보자.

 

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: prod
spec:
  replicas: 5
  template:
    spec:
      ...
      tolerations:
      - key: node-type    # 이 톨러레이션은 프로덕션 노드의 테인트를 허용하는 명세이다
        Operator: Equal   # = 
        value: production
        effect: NoSchedule

 

> 이렇게 하고 위에처럼 $ kubectl get po -o wide 를 해보면 node1, node2 모두 배포됨을 알 수 있다

> 따라서 프로덕션이 아닌 노드에는 다음 정도 되는 수준의 테인트를 추가하면 위 디플로이먼트는 node1 에만 배포할 것이다

## node-type=non-production:NoSchedule

 

 

 

16.1.4 테인트와 톨러레이션의 활용 방안 이해 

 

> 테인트 톨러레이션 명세 조건

1. 노드와 파드는 각각 하나 이상의 테인트와 톨러레이션을 가질 수 있다
2. 테인트는 key, effect 가 필수이지만, value 는 아니다
3. 톨러레이션은 Equal 연산자를 활용하여 특정한 값을 허용하거나, Exists 연산자를 활용하여 특정 테인트 키에 여러 값을 허용할 수 있다. (Value 부분인데, 난 effect 적인 부분도 궁금) 

 

 

<스케줄링에 테인트와 톨러레이션 활용하기> 

 

> 클러스터를 여러 파티션으로 분할해 개발 팀이 원하는 노드에만 파드를 스케줄링하게 만들 수 있음 (개발 인프라?)

> 하드웨어적인 부분도 노드 스케줄링 가능 (이거 초반에 다른 방안이 나왔었는데 노드 셀렉터? 이거였나?)

 

 

<노드 실패 후 파드를 재스케줄링하기까지의 시간 설정>

 

> 파드를 실행중인 노드가 준비되지 않거나 도달할 수 없는 경우, 톨러레이션을 이용해 다른 노드로 리스케줄링 전 대기까지 설정할 수 있다. 

> $ kubectl get po prod-350605-1ph5h -o yml 를 통해 출력 결과를 살펴보자

 

...
tolerations:
- effects: NoExecute
  key: node.alpha.kubernetes.io/notReady  # 노드가 준비되지 않은 상태에서 파드는 재스케줄링 전 300초 대기
  operator: Exists
  tolerationSeconds: 300 
- effects: NoExecute
  key: node.alpha.kubernetes.io/unreachable  # 도달할 수 없는 노드도 파드는 재스케줄링 전 300초 대기
  operator: Exists
  tolerationSeconds: 300

 

> 별도로 톨러레이션을 적용하지 않아도, 이 두 톨러레이션은 기본적으로 파드에 추가된다. 

 

 

 

 

16.2 노드 어피니티를 사용해 파드를 특정 노드로 유인하기

 

 

>  K8s 는 특정 노드 집합에만 파드를 스케줄링 하도록 지시할 수 있다

 

 

< 노드 어피니티와 노드 셀렉터 비교>

 

> 노드 셀렉터는 파드의 대상이 되려면 해당 필드에 지정된 모든 레이블을 포함시켜야 함 (레거시 느낌임)

> 노드 어피니티는 필수 요구사항 / 선호도를 지정하는 방식으로, K8s 에게 어떤 노드들은 특정한 파드를 선호한다는 것을 알려주면, K8s 는 해당 노드 집단에서 하나를 선택해서 파드를 스케줄링하려고 시도한다. (만약 불가능하면 다른 노드 선택, 선호도면!) 

 

 

< 디폴트 노드 레이블 검사> 

 

> 노드에는 많은 레이블이 있지만, 노드 어피니티에 있어서 다음과 같은 디폴트 레이블들이 중요함

1. failure-domain.beta.kubernetes.io/region - 노드가 위치한 지리적 리전을 지정
2. failure-domain.beta.kubernetes.io/zone - 노드가 있는 가용 영역을 지정
3. kubernetes.io/hostname - 노드의 호스트 이름

 

> 3장에서 노드에 사용자 레이블 추가, 파드의 노드 셀렉터로 이런 레이블들을 사용했었대...

 

 

 

16.2.1 하드 노드 어피니티 규칙 지정

 

# Node Selector 를 사용한 모습
apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  nodeSelector:  # gpu=true 라는 레이블이 있는 노드에만 스케줄링됨
    gpu: "true"

 

> gpu 같은 하드웨어가 있는 노드에만 파드가 배포되도록 nodeSelector 가 사용된 모습

 

# Node Affinity 를 사용한 모습
apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu-naff
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
      - matchExpressions:
        - key: gpu
          operator: In
          values:
          - "true"

 

ㅅㅂ 해석 진짜

> requiredDuringScheduling .. 이 필드 아래에 정의된 규칙은 파드가 노드로 스케줄링되고자 할 때 가져야 하는 레이블 지정

> ..IgnoredDuringExecution 이 필드 아래에 정의된 규칙은 노드에서 이미 실행중인 파드에는 영향을 미치지 않는다

> 즉, 이 속성을 적용하는 시점부터 nodeSelecting 을 지정하며, 기존 파드들에는 영향을 주지 않는다는 뜻

 

 

<nodeSelectorTerm 이해>

 

> nodeSelectorTerms 와 matchExpressions 필드가 사용됨을 알 수 있음 

> 걍 보이는 그대로 이해하면 됨 operator 가 In 이므로 values 를 여러개 지정할 수 있는 거인듯 ( "pending" 과 같은 value 에도 지정 가능)

 

 

 

16.2.2 파드의 스케줄링 시점에 노드 우선순위 지정

 

 

> 노드 어피니티의 가장 큰 장점은, 스케줄러가 선호할 노드를 지정할 수 있다는 것 (preferredDuringScheduling... 으로 가능)

> Use Case : 특정 머신과, 관계사 용으로 예약된 머신에 파드가 스케줄링되기를 원하지만, 공간이 충분치 않거나 특별한 이유가 있다면 다른 머신에 띄워져도 상관 없는 경우

 

 

<노드 레이블링>

 

>  노드에 적절한 레이블링이 되어야 함. 가용 영역을 지정하는 레이블, 전용/공유 Type 을 표시하는 레이블을 두어보자.

> $ kubectl label node node1.k8s av-zone=z1 과, share-type=dedicated 등을 활용하여 레이블링을 한 결과,

> node1 은 Availability Zone 이 zone1, Share-Type 이 dedicated 으로 레이블링 되었으며, node2 는 각각 zone2, shared 로 레이블링 됨. 

 

<선호하는 노드 어피니티 규칙 지정>

 

> zone1 과 dedicated 노드를 선호한다는 Deployment 를 생성해보자. (어피니티를 사용) 

 

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: pref
spec:
  template:
    ...
    spec:
      affinity:
        nodeAffinity:
        preferredDuringSchedulingIgnoredDuringExecution: # 선호도를 명시 (필수가 아님)
        - weight: 80  # 비중 80 을 주는 선호도 
          preference:
            matchExpressions:
            - key: availability-zone  # 자신의 파드는 av-zone 이 zone1 인 곳에 스케줄링 되는 것을 선호함
              operator: In
              values:
              - zone1
        - weight: 20
          preference:
            matchExpressions:  # 그 다음으로 선호하는 것. (Order By 느낌)
            - key: shared-type
              operator: In
              values:
              - dedicated

 

 

< 노드 선호도 작동 방법 이해하기 >

 

> 위 디플로이먼트의 파드가 스케줄링될 때, 노드는 네 개의 그룹으로 분할된다 (AVZone, Shared-Type 2*2) 

> 1 순위 : zone1, ded // 2순위 :zone1, shared ...

 

 

< 노드가 두 개인 클러스터에 파드 배포하기 >

 

> 아까 node1 : zone1, dedicated 이였고, node2 : zone2, shared 였다. 

> 따라서 이 디플로이먼트로 배포하면 모두 node1 에 배포되어야 함을 예상할 수 있지만, 그렇게 되지 않는다

> 5개 중 하나는 node2 에 배포된다. 그 이유는 노드 어피니티 우선순위 지정 기능만으로 결정되는게 아니기 때문. (node2 로 된 이유는 ~ 때문이다는 불가능한듯, 그냥 여러 요인이 있다고 설명함)

 

> 가령, Selector-SpreadPriority 라는 기능이 있다

> 이 기능은 동일한 파드 레플리카 셋 / 서비스에 속하는 파드들이 배포될 때 최대한 여러 노드에 분산시켜 전체 서비스 중단 방지를 지원한다. 

> 저자가 20개 했는데, node 2 로는 2개만 분산되었다고 함. (만약 노드 어피니티 지정이 없었다면, 고르게 10개씩 분산되었을 것) 

 

 

 

 

16.3 파드 어피니티와 안티-어피니티를 이용해 파드 배치하기

 

 

> 때때로는 파드간의 어피니티를 지정할 필요가 있음 (지금까지는 파드와 / 노드간)

> Use Case : 프런트 엔드 파드와 백엔드 파드를 서로 가깝고 동일한 노드에 배치되도록 함 (가깝게 유지?)

 

 

 

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

 

> busybox 란 이미지의 백엔드 파드에 레이블 app=backend 를 추가해서 디플로이먼트를 띄웠다

> 프론트 엔드 파드는 다음과 같이 정의한다

 

apiVersion: extension/v1beta1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 5
  template:
    ...
    spec:
      affinity:
        podAffinity:  # podAffinity 규칙
          requiredDuringSchedulingIgnoredDuringExecution:  # required 설정으로, 필수 요구 사항임을 명세
          - topologyKey: kubernetes.io/hostname   # 이건 뭔지 모르겠는데
            labelSelector:  # 이 디플로이먼트 파드는 셀렉터와 일치하는 노드에 배포되어야 함
              matchLabels:
                app: backend
        ...

 

 

 

 

> 위 프런트엔드 파드의 디플로이먼트는 app=backend 레이블이 있는 파드와 동일한 노드 (topologyKey 로 지정된 설정) 에 배포되도록하는 필수 요구 사항을 가진다 (topologyKey: hostname 의 뜻이 "동일한 hostname 에 스케줄링하라" 라는 뜻인듯) 

 

> 이거 외에 파드 어피니티의 다른 설정들도 같이 보여줬으면 더 이해가 잘될 듯. 하나의 Use Case 만 가지고 전체 개념을 설명하니까 좀 그럼..

 

> $ kubectl get po -o wide 를 통해서 상태를 보자

> 백엔드 파드가 node2 에 배포되어 있으니, 위 디플로이먼트를 실행해보면 다 node2 에 5개의 replica 들이 띄워지게 된다

 

 

< 스케줄러가 파드 어피니티 규칙을 사용하는 방법 이해 > 

 

> 이후 백엔드 파드를 삭제하고 다시 띄워도 스케줄러는 이 파드를 node2 에만 배포되는 흥미로운 속성이 있음 (프런트엔드 파드에만 규칙이 있지만, 이 규칙을 깨지 않기 위함) 

> 스케줄러의 로그를 확인해보면, 새 백엔드 파드를 배포할 때 InterPodAffinityPriority 를 위해 node2 가 더 높은 점수가 로깅된걸 확인할 수 있음 

 

 

 

16.3.2 동일한 랙, 가용 영역 또는 리전에 파드 배포 (이런건 클라우드 인프라에서 얘기) 

 

> 위처럼 완벽히 동일한 노드는 아니더라도, 같은 가용 영역에 실행하는 것 정도는 원하는건 흔한 상황

> 동일한 가용 영역에 배포하려면, topologyKey: failure-domain.beta.kubernetes.io/zone 으로 설정하면 됨

> 동일한 리전에 배포하려면, topologyKey: failure-domain.beta.kubernetes.io/region 으로 설정하면 됨

 

 

<topologyKey 에 대한 이해>

 

> 뭔가 [랙] 이란 용어를 지금까지 당연히 써왔던 용어처럼 아무 설명 없이 사용해서 당황스럽긴 함

(랙(Rack)은 PC나 서버, 통신장비, 각종 계측기 등 일정 시스템을 구성하는 장비들을 보관하고 시스템 구성에 필요한 환경을 만들어주는 제품이라고 함)

> 만약 내가 rack 이라는 레이블을 추가함 (노드 레이블인듯?). 그리고 노드들에 rack: rack1, rack2 이런 레이블들을 함. 그러고 프론트엔드 파드에 topologyKey: rack 이라고 한다면, 위에서 봤던 것처럼 app=backend 인 파드와 동일한 rack 인 노드에 배포하라는 뜻

> 이렇게 자체적인 상황을 제어해야 하는 경우도 topologyKey 를 자체적으로 설정해서 사용할 수 있다는 점을 언급

 

 

 

16.3.3 필수 요구 사항 대신 파드 어피니티 선호도 표현 (required 가 아닌 preferred)

 

 

> 위 예제에 이어서, 최대한 프런트 / 백 파드를 동일한 노드에 스케줄링하고 싶지만, 여의치 않으면 다른 곳에 해도 괜찮다는 선호도를 지정할 수 있다

 

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 5
  template:
    ...
    spec:
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:  # 선호도
          - weight: 80   # 이전 예제 처럼 가중치가 있음
            podAffinityTerm:
              topologyKey: kubernetes.io/hostname   # 동일하게 topology 를 부여하여 계산을 함
              labelSelector:
                matchLabels:
                  app: backend

 

 

> app=backend 의 파드가 운영되고 있는 [hostname] 의 노드에 스케줄링 되는 것을 선호하게 하는 podAffinity 를 명세함

> 위에서 한 노드 어피니티와 마찬가지로, 5개 중 하나는 다른 노드에 배포하게 된다 (위에서 말한 것과 동일한 이유) 

 

 

 

16.3.4 파드 안티-어피니티를 사용해 파드들이 서로 떨어지게 스케줄링하기

 

 

> 붙이는게 아니라 떨어지게 하고 싶을 수도 있음 ( anti-affinity ) 

> 위 설정에서 podAffinity --> podAntiAffinity 로 바꾼다면 (required 로 한다면), app=backend 의 파드가 운영되고 있는 [hostname] 의 노드에는 스케줄링 하지 않게 된다 (거의 완벽히 동일한 사용법임)

 

 

< 같은 디플로이먼트의 파드를 분산시키기 위해 안티 - 어피니티 사용 > 

 

> 동일한 서비스 중인 파드들을 반드시 다른 노드들로 분산시키길 원할 수 있음 (재앙 대비, 리전별 백업 대비 등) 

> 다음과 같이 명세하면, 각 파드들은 서로 다른 노드 (레이블이 없는 노드들만 선택)에 스케줄링된다

 

kind: Deployment
...
spec:
  replicas: 5
  template:
    metadata:
      labels:
        app: frontend   # 배포되는 파드들은 app=frontend 로 레이블링 되어 있다
    spec:
      affinity:
      podAntiAffinity:   # 안티 속성
        requiredDuringSchedulingIgnoredDuringExecution:
        - topologyKey: kubernetes.io/hostname
          labelSelector:
            matchlabels:
              app: frontend    # 이 레이블링이 되어 있는 파드와 같은 hostname 은 Anti 가 Required

 

 

> 현재 두 개의 노드가 있는데, 이 deployment 를 배포하고 파드들의 상태를 확인한다면 , 두 개의 파드만 각 노드에 띄워지고, 세 개는 생성은 되지만 Pending 상태인 것을 확인할 수 있다. 

> 안티 역시 필수가 아닌 선호도로 preferred 속성을 사용할 수 있다

728x90