이야기박스

Kubernetes. 고급 스케줄링 본문

Computer & Data/Orchestration

Kubernetes. 고급 스케줄링

박스님 2022. 1. 12. 12:00
반응형

 

쿠버네티스의 가장 큰 강점은 사용자가 신경 쓰지 않아도 지정된 클러스터에 자동으로 리소스가 나누어지는 것에 있죠.

하지만 특정 워커 노드, 집단에 배포되면 안될때, 어떻게 하면 될까요?

 

쿠버네티스에서는 위 문제를 두 가지 관점에서 풀려고 하였습니다.

  • 노드 관점에서 받아들일 파드 정하기 - Node Taint, Pod Toleration 두 정책을 바탕으로 해결하고자 하였습니다.
  • 파드 관점에서 들어갈 노드 정하기 - Node Affinity를 통하여 파드의 배포를 관리자가 조율할 수 있도록 합니다.

 

# 노드 관점; Node Taint, Pod Toleration

## Taint ; Node 설정

Master 노드에서 테인트 확인 가능합니다.

$ kubectl describe node master.k8s
Name:         master.k8s
Role:
Labels:       beta.kubernetes.io/arch=amd64
              beta.kubernetes.io/os=linux
              kubernetes.io/hostname=master.k8s
              node-role.kubernetes.io/master=
Annotations:  node.alpha.kubernetes.io/ttl=0
              volumes.kubernetes.io/controller-managed-attach-detach=true
Taints:       node-role.kubernetes.io/master:NoSchedule
...

테인트의 구성 요소는 <key>=<value>:<effect> 형태로 정의되어 Node에 적용됩니다.

  • key : node-role.kubernetes.io/master
  • value : null
  • effect : NoSchedule

## Toleration ; Pod 설정

Node에 배포될 수 있는 티켓과 비슷한 개념이라 보면 됩니다.

Taint 처리가 되어 있는 Node에 Toleration이라는 티켓을 가지고 있으면, 그 Node에 Pod가 배포될 수 있는 거죠.

Toleration 정의는 Yaml 파일에서 Pod Spec에 정의하게 됩니다.

# Toleration Example
pods/pod-with-toleration.yaml 

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"

 

## 스케줄

노드의 테인트가 허용된 경우에만 포드가 노드에 스케줄 됩니다.

 

위 Master Node의 Taints 예제와 비교하여 아래 pod 정보를 봅시다.

$ kubectl describe po kube-proxy-80wqm -n kube-system
...
Tolerations:    node-role.kubernetes.io/master=:NoSchedule
                node.alpha.kubernetes.io/notReady=:Exists:NoExecute
                node.alpha.kubernetes.io/unreachable=:Exists:NoExecute
...

위에서 정의했던 "node-role.kubernetes.io/master=:NoSchedule" Taints와 일치하는 것을 확인할 수 있습니다.

Note.
Taints에서는 등호(=) 표시가 없는 것은 value가 null일 때, Taint와 Toleration이 표현하는 방법이 다르기 때문

 

## Taint Effect

Taint에 적용 가능한 Effect는 3가지가 있습니다.

 

NoSchedule

  • 노드가 테인트를 허용하지 않는 경우 포드가 노드에 스케줄 되지 않음.
  • 즉 Toleration이 일치하는 경우에만 배포.

PreferNoSchedule

  • NoSchedule의 소프트 버전.
  • 스케줄러가 노드에 스케줄을 안 하려고 하지만, 다른 곳에서 스케줄 할 수 없는 경우엔 스케줄을 진행.

NoExecute

  • 기존에 돌고 있던 포드에도 영향을 미침.
  • NoExecute 테인트를 톨러레이션 하지 않는 모든 포드가 제거.

 

## Custom Taint / Toleration

Taint 추가

# <node-type>=<production>:<NoSchedule>
$ kubectl taint node node1.k8s node-type=production:NoSchedule
node "node1.k8s" tainted

Toleration 추가

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: prod
spec:
  replicas: 5
  template:
    spec:
      ...
      tolerations:
      - key: node-type
        operator: Equal
        value: production
        effect: NoSchedule
Note. tolerations.operator
Exists라는 항목이 존재, 이는 일치하는 key의 모든 Toleration을 허용한다는 의미

tolerations:
   - operator: "Exists"

 

## 활용

노드는 하나 이상의 Taints를 가질 수 있지만 파드는 하나의 Toleration만 가질 수 있습니다.

Toleration을 사용해 포드의 스케줄 시간을 조정할 수 도 있죠.

$ kubectl get po prod-350605-1ph5h -o yaml
...
  tolerations:
  - effect: NoExecute
    key: node.alpha.kubernetes.io/notReady
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.alpha.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
Question. Deployment와 같은 다른 리소스에서 사용하는 Pod 시간 관련 요소들과 함께는 어떻게 동작할까?
(ex/ deployment의 지연 배포)

 

# 파드 관점; Node Affinity

노드에 라벨을 우선 정의합니다. 

$ kubectl describe node gke-kubia-default-pool-db274c5a-mjnf
Name:     gke-kubia-default-pool-db274c5a-mjnf
Role:
Labels:   beta.kubernetes.io/arch=amd64
          beta.kubernetes.io/fluentd-ds-ready=true
          beta.kubernetes.io/instance-type=f1-micro
          beta.kubernetes.io/os=linux
          cloud.google.com/gke-nodepool=default-pool
          failure-domain.beta.kubernetes.io/region=europe-west1 --
          failure-domain.beta.kubernetes.io/zone=europe-west1-d --
          kubernetes.io/hostname=gke-kubia-default-pool-db274c5a-mjnf --

위 노드의 친화성과 관련 있는 라벨은 다음과 같습니다. 

  • failure-domain.beta.kubernetes.io/region=europe-west1 : 노드가 있는 지리적인 영역 지정
  • failure-domain.beta.kubernetes.io/zone=europe-west1-d : 노드가 있는 사용 가능한 영역 지정
  • kubernetes.io/hostname=gke-kubia-default-pool-db274c5a-mjnf : 노드의 호스트 이름

하지만 이외에도 사용자가 커스텀하게 라벨을 추가할 수 있습니다.

 

## Node Affinity Rule

apiVersion: v1
kind: Pod
metadata:
  name: kubia-gpu
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu
            operator: In
            values:
            - "true"

nodeAffinity

  • requiredDuringSchedulingIgnoredDuringExecution
    • 노드가 포드를 노드에 스케줄 하기 위해 가지고 있어야 하는 라벨
  • preferredDuringSchedulingIgnoredDuringExecution
    • 필드에 정의된 규칙이 노드에서 이미 실행 중인 포드에는 영향 주지 않음

nodeAffinity는 새로운 포드 스케줄링에만 영향을 주고 기존에 스케줄링된 포드에는 영향을 주지 않습니다.

그렇기 때문에 사용자가 직관적으로 이해하기 좋게 IgnoreDuringExecution이 접미어로 붙어 있는 거죠.

 

 

nodeSelectorTerms

matchExpressions 필드는 라벨이 일치하도록 표현식을 정의하는 데 사용됩니다.

위 예제의 yaml 파일은 다음과 같은 프로세스로 스케줄이 진행되게 되죠.

 

 

 

## 스케줄링 우선순위 지정

preferredDuringSchedulingIgnoredDuringExecution 필드를 이용한 예제를 살펴보겠습니다.

관리자는 'weight'이라는 필드에 가중치를 두어 우선순위를 정할 수 있습니다.

 

특정 Node에 라벨 부여

$ kubectl label node node1.k8s availability-zone=zone1
node "node1.k8s" labeled
$ kubectl label node node1.k8s share-type=dedicated
node "node1.k8s" labeled
$ kubectl label node node2.k8s availability-zone=zone2
node "node2.k8s" labeled
$ kubectl label node node2.k8s share-type=shared
node "node2.k8s" labeled
$ kubectl get node -L availability-zone -L share-type
NAME         STATUS    AGE       VERSION   AVAILABILITY-ZONE   SHARE-TYPE
master.k8s   Ready     4d        v1.6.4    <none>              <none>
node1.k8s    Ready     4d        v1.6.4    zone1               dedicated
node2.k8s    Ready     4d        v1.6.4    zone2               shared

 

Deployment 생성

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: pref
spec:
  template:
    ...
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 80
            preference:
              matchExpressions:
              - key: availability-zone
                operator: In
                values:
                - zone1
          - weight: 20
            preference:
              matchExpressions:
              - key: share-type
                operator: In
                values:
                - dedicated
      ...

위에서 적용된 선호 옵션은 다음과 같습니다.

  1. availability-zone=zone1
  2. share-type=dedicated

첫 번째 옵션을 두 번째 옵션보다 4배 중요하게 설정하여 배포되도록 합니다.

 

노드의 선호도 동작 방식

포드의 친화성 규칙에 설정된 가중치에 따라 우선순위가 매겨지게 되고 아래와 같이 배포가 진행됩니다.

 

두 개의 노드에 배포

$ kubectl get po -o wide
NAME                READY   STATUS    RESTARTS  AGE   IP          NODE
pref-607515-1rnwv   1/1     Running   0         4m    10.47.0.1   node2.k8s
pref-607515-27wp0   1/1     Running   0         4m    10.44.0.8   node1.k8s
pref-607515-5xd0z   1/1     Running   0         4m    10.44.0.5   node1.k8s
pref-607515-jx9wt   1/1     Running   0         4m    10.44.0.4   node1.k8s
pref-607515-mlgqm   1/1     Running   0         4m    10.44.0.6   node1.k8s
Question. 한대만 node2에 배포됨. 왜?
스케줄러는 다른 우선순위 기능을 사용해 포드가 스케줄 될 위치를 결정한다.
ReplicaSet 또는 Service에 속한 포드가 노드 장애로 인해 한번에 죽지 않기 위함

 

# 노드 친화성을 통한 포드 위치 결정

특정 포드들은 가까워야 애플리케이션 성능이 향상되는 경우가 있습니다. ( 예를 들어 웹 프론트+백엔드 )

이들은 노드 친화성을 이용하여 구성이 가능합니다.

 

## 인터포드 친화성을 이용한 배포

1. Backend deploy with label

$ kubectl run backend -l app=backend --image busybox -- sleep 999999
deployment "backend" created

 'app=backend' 라는 라벨을 추가하여 배포합니다.

 

2.  Frontend deployment

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 5
  template:
    ...
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - topologyKey: kubernetes.io/hostname
            labelSelector:
              matchLabels:
                app: backend
      ...

'app=backend' 라벨이 있는 파드와 디플로이먼트가 동일한 노드(topologyKey)에 배포되어야 한다는 엄격한 룰을 적용합니다.

 

위와 같은 방식으로 특정 서비스들을 가깝게 배치할 수 있습니다.

 

## 동일한 랙, 가용 영역 / 지리적 영역에 배포

위에서 사용되었던 친화성 관련 라벨을 사용할 수 있습니다.

동일 가용 영역 failure-domain.beta.kubernetes.io/zone
같은 지리적 위치 failure-domain.beta.kubernetes.io/region

 

topologyKey

노드에 rack 라벨을 지정하여 사용.

 

## Anti Affinity

위에서는 노드 친화성을 바탕으로 파드를 배치하는 것을 보았다면, 이번에는 반 친화성으로 파드를 서로 멀리 스케줄링하는 것을 해보려고 합니다. 이는 podAntiAffinity 이용하여 구현할 수 있습니다.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 5
  template:
    metadata:
      labels:
        app: frontend
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - topologyKey: kubernetes.io/hostname
            labelSelector:
              matchLabels:
                app: frontend
      containers: ...

 

 

# 후기

대충 정보 나열만 해둔 글이 근 3년간 방치가 되고 있었네요. 드디어 글을 다듬어 다시 정리하게 되었습니다.

앞으로 이런 밀린 글들을 하나씩 정리해봐야겠습니다. ㅎㅎ

 

쿠버네티스 클러스터에 서비스가 많아질수록, 노드가 커질수록 위 고민을 점점 많이 하게 되더라고요. 특히 요새 오픈소스로 나와있는 Helm Chart 들을 살펴보면 대부분 affinity, tolerations 등 설정을 지정할 수 있는 부분이 많이 보입니다. 잘 알아두어야 이들을 활용할 수 있을 것 같아요.

반응형