06. Week7 파드 스케줄링 - chuirang/DevOps GitHub Wiki

Week 7 파드 스케줄링

목차

  1. 파드 스케줄링에 대한 이해

  2. Allocatable 에 대한 이해

  3. 노드 셀렉터, 노드 어피니티

  4. 파드 어피니티, 파드 안티어피니티

  5. 테인트와 톨러레이션


1. 파드 스케줄링에 대한 이해

1.1 파드 스케줄링 프로세스

스케줄러의 파드 스케줄링 과정을 바탕으로 쿠버네티스의 동작 과정을 살펴본다.

  • 나머지 컴포넌트는 api server 를 통해 관련 오브젝트를 관리하고 api server 만 etcd 와 직접 통신을 한다
  • 모든 컴포넌트는 consumer이자 producer 이다

https://user-images.githubusercontent.com/20136723/131639812-cb711a9d-fc7c-403b-975f-3bdf9c448eb4.png

https://harshanarayana.dev/2020/06/writing-a-custom-kubernets-scheduler/

  1. A pod is created and it’s desired state is stored into the etcd with spec.nodeName field set to empty indicating that the pod is required to be scheduled on a node
  2. Scheduler magically (details of this, for another time) figures out that there is a new pod that is spec.nodeName set to empty
  3. Scheduler figures out the node available in the cluster that best fists the scheduling requirements of the pod based on desired state
  4. Once this decision making is done, it informs API Server that a given pod has to be bound to a specific node identified in step #3
  5. API server persists this information into etcd as part of the desired state spec
  6. kublet running on the minion node that the pod was bound to, notices that it has to create a new pod and gets to work. With the help of CRI such as docker or rkt or such, the containers required to fulfil the pod’s desired state is create and the pod’s state transition completes

watch endpoint 살짝 살펴보기

$ kubectl proxy --port=<unused_port> &
$ curl -s http://127.0.0.1:8001/api/v1/watch/namespaces/default/pods   # default 네임스페이스의 pods 리스트를 watch 한다

## 여러 api를 확인해보기
$ curl -s http://127.0.0.1:8001/api   #  to get the API version.
$ curl -s http://127.0.0.1:8001/api/v1/namespaces   #  to get all the namespaces in the K8s cluster.
$ curl -s http://127.0.0.1:8001:8081/api/v1/pods   #  to get all the pods in the K8s cluster (pods' IPs, names, where does it exist, etc...).
$ curl -s http://127.0.0.1:8001/api/v1/services   #  to get all the services in the K8s cluster (ports exposed by K8s).
$ curl -s http://127.0.0.1:8001/api/v1/namespaces/<namespace>/pods   #  to get the pods that exists in that nemespace
$ curl -s http://127.0.0.1:8001/apis/extensions/v1beta1/namespaces/<namespace>/deployments   #  to get all the pods and containers' info (port #, pod name, # of conainers per pod, containers' specs)

1.2 스케줄링 알고리즘

https://user-images.githubusercontent.com/20136723/131639875-62c8adc7-1394-463b-929d-b69b992a74ef.png

https://www.slideshare.net/harryzhang735/kubernetes-beyond-a-black-box-part-1

대부분의 경우 파드가 노드에 할당되는 작업은 스케줄러에 맡기는 게 좋다. 다만 특정 요건을 위해 파드를 특정 조건의 노드나 상황에서 할당할 필요도 있다.

이번 과정에서는 스케줄러의 노드 선정 과정에서 사용자의 의도로 파드 스케줄링에 영향을 미치는 몇가지 방법을 다루도록 한다.

그 전에 주요한 주제 중 하나인 각 노드의 할당 가능한 용량의 정의를 먼저 살펴보자.

2. Allocatable Resource 에 대한 이해

앞서 특정 노드에 파드가 할당되기 위해서는 파드가 요청한 자원(request)의 총합보다 할당 가능한 노드의 용량(Allocatable)이 더 커야한다는 것을 이해했다.

https://user-images.githubusercontent.com/20136723/131639912-887f76e5-ce9d-433e-b08a-ff3b8258e82c.png

출처: [도서] Kubernetes in action

노드의 Allocatable (파드에 할당 가능한 용량)을 구하는 계산식은 아래와 같다.

Allocatable = node capacity - kube-reserved - system-reserved

https://user-images.githubusercontent.com/20136723/131639949-16baf560-a4b1-4093-a016-c3dc55d0dc00.png

https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/

  • Allocatable : 파드에 할당 가능한 용량

  • Node Capacity: 노드에 가용한 용량

  • kube-reserved: kubelet, container runtime 과 같은 쿠버네티스 데몬에 예약된 용량

  • system-reserved: OS 시스템 데몬이 안정적으로 사용되기 위해 예약된 용량

Allocatable 직접 확인해보기

$ kubectl describe no <node-name>
Name:               u2004-fourth
Roles:              <none>
...
Capacity:
  cpu:                8
  ephemeral-storage:  103230212Ki
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             16343432Ki
  pods:               110
Allocatable:
  cpu:                8
  ephemeral-storage:  95136963222
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             16241032Ki
  pods:               110
...

16343432 - 16241032 = 102400 / 1024 = 100 MB (OS 를 위해 100MB는 미리 예약해 둔다)

쿠버네티스 노드의 CPU와 Memory는 단순히 파드를 실행하기 위해서 사용되지 않기 때문에 (예를 들어 OS 자체, 파드가 아닌 데몬, 모니터링 프로세스 등 이 사용하는 컴퓨팅 자원도 고려해야함) kube-reserved 나, system-reserved를 정의해야 합니다. 이는 kubelet 옵션으로 정의가 가능하다.

https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/#kube-reserved

퍼블릭 클라우드에서 제공하는 Managed Kuberentes 서비스에서는 노드 사이즈별로 내부적으로 적정한 예약된 리소스를 관리하고 있다.

https://learnk8s.io/allocatable-resources

(번역) https://brunch.co.kr/@jehovah/29

3. 노드 셀렉터, 노드 어피니티

3.1 노드 셀렉터

셀렉터(Selector) 는 레이블(key-value 맵 형태)이 설정된 대상을 선택하는 방식을 말한다. (보통 특정 서비스 오브젝트가 관련된 파드를 선택할 때 파드에 정의된 레이블을 바탕으로 선택한다)

노드 셀렉터는 노드에 설정된 레이블을 파드에서 직접 지정하는 방식이다.

노드 셀렉터 사용 예시

  • gpu를 사용할 수 있는 노드와 일반 노드가 구분된 경우
  • 관리 목적의 파드를 system 노드에만 실행하고자 하는 경우 (사용자 워커 노드와 분리하는 목적)
  • ingress controller 등 특정 서비스를 전용으로 실행하는 노드가 있는 경우

노드 셀렉터는 상대적으로 강력한 스케줄링 방법으로 반드시 해당 레이블이 있는 노드에만 배치된다.

사용 예시와 같이 gpu가 없는 노드에 파드가 실행되면 안되는 경우, 특정 파드를 사용자 워커 노드와 분리하여 실행하는 경우 등에 사용된다.

노드 셀렉터 실습

현재 아래와 같이 노드들에 레이블이 지정되어 있으므로, 파드에 nodeSelector를 사용하여 특정 파드를 특정 노드에 실행 시킬 수 있습니다.

$ kubectl get no --show-labels
NAME           STATUS   ROLES                  AGE   VERSION   LABELS
u2004-fourth   Ready    <none>                 46d   v1.20.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,esxi=hostC,kubernetes.io/arch=amd64,kubernetes.io/hostname=u2004-fourth,kubernetes.io/os=linux
u2004-master   Ready    control-plane,master   63d   v1.20.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=u2004-master,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node-role.kubernetes.io/master=
u2004-second   Ready    <none>                 63d   v1.20.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,esxi=hostA,kubernetes.io/arch=amd64,kubernetes.io/hostname=u2004-second,kubernetes.io/os=linux,node-type=system
u2004-third    Ready    <none>                 63d   v1.20.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,esxi=hostB,kubernetes.io/arch=amd64,kubernetes.io/hostname=u2004-third,kubernetes.io/os=linux

전반적인 실습 과정은 아래를 참고하였습니다.

https://kubernetes.io/ko/docs/concepts/scheduling-eviction/assign-pod-node/#%EB%85%B8%EB%93%9C-%EC%85%80%EB%A0%89%ED%84%B0-nodeselector

1단계: 노드에 레이블 붙이기
$ kubectl get nodes # 노드의 이름을 가져온다
$ kubectl label nodes <노드 이름> <레이블 키>=<레이블 값>
$ kubectl label nodes u2004-third disktype=ssd
$ kubectl get nodes --show-labels # 레이블 확인
2단계: 파드 생성
pod-nodeselector.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

그런 다음에 kubectl apply -f pod-nodeselector.yaml 을 실행하면, 레이블이 붙여진 노드에 파드가 스케줄된다.

3.2 노드 어피니티

노드 어피니티는 노드 셀렉터 접근 방식을 보다 일반화 한 것으로 강제성 규칙이 추가되었으며, 레이블을 연산자(In, NotIn, Exists, DoesNotexist, Gt, Lt) 를 통해 표현 가능한 제약 조건을 확장한다.

강세성 규칙은 required 나 preferred 로 지정된다. required 는 필수이고 이는 노드 셀렉터와 유사하게 동작한다. 반면 preferred는 반드시 충족되어야 하는 것은 아니나 일치하는 노드의 가중치를 증가시켜 노드가 선택되도록 유도한다.

pod-with-node-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-node-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: numberCores
            operator: Gt
            values: [ "3" ]
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: node-type
            operator: In
            values:
            - [ "system" ]
  containers:
  - name: with-node-affinity
    image: k8s.gcr.io/pause:2.0

4. 파드 어피니티, 파드 안티어피니티

노드 셀렉터와 노드 어피니티는 레이블이나 특정 속성을 바탕으로 파드가 실행될 노드를 지정하는 방식입니다.

이와 달리 파드 어피니티와 파드 안티어피니티는 파드간의 관계(상대적인 위치나 의존 관계)를 정의하는 개념으로 파드들이 서로 같이 혹은 분산되게 실행되도록 규칙을 정의하는 것입니다.

파드 어피니티/안티어피니티의 사용 예시

  • 고가용성을 위해 특정 파드 집합을 서로 상이한 환경에 배치
    • 특정 레플리카 셋의 파드들을 서로 다른 랙/존에 배치
  • 지연 시간 개선을 위해 파드를 밀첩한 환경에 배치
    • 동일한 서비스를 위한 was / db 를 밀첩한 환경에 비치
    • 서로 상호 호출이 많은 서비스를 밀첩한 환경에 배치
    • 서로 데이터를 주고 받는 서비스를 밀첩한 환경에 배치

파드 어피니티는 노드 단위로만 제한되지 않고, 인프라스트럭처의 토폴로지 레벨에서 규칙을 표현할 수 있습니다. 즉 랙, 존, 리전과 같이 토폴로지를 정의하였다면 topologyKey에 정의된 기준을 바탕으로 서로 밀첩하거나 분산되는 규칙의 기준을 줄 수 있습니다.

아래와 같이 특정 리전의 3개의 Availability Zone에 걸쳐서 생성된 3개의 노드가 있다고 가정하면, topologyKey: failure-domain.beta.kubernetes.io/zone 을 통해 특정 레플리카 셋의 구성된 파드들을 서로 다른 노드에 배치시킬 수 있다.

https://user-images.githubusercontent.com/20136723/131640000-286af6f3-6ef3-4c0f-8d9a-8aa052d31dae.png

https://www.verygoodsecurity.com/blog/posts/kubernetes-multi-az-deployments-using-pod-anti-affinity

5. 테인트와 톨러레이션

노드 어피니티는 특정 노드 셋을 (preference 나 hard requirement 규칙으로) 선택하도록 유도하는 파드의 속성이다. 테인트 는 그 반대로, 노드가 파드 셋을 제외시킬 수 있다. 테인트는 노드의 속성으로 테인트(taint) 사전적으로 오염된 것이라고 보면 좋다. 파드는 테인트된 노드에 할당되지 않는다. 이를 톨러레이션(toleration) 하는 파드만 할당이 가능하다.

즉 아래와 같이 노드에 테인트를 정의할 수 있다.

$ kubectl taint nodes node1 example-key=value1:NoSchedule

이를 허용하려는 파드는 아래와 같은 톨러레이션이 필요하다.

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"

테인트는 노드의 속성으로 파드가 실행되지 않도록 한다. 테인트가 사용되는 상황은 어느 경우일까?

쿠버네티스 내부적으로 노드 컨트롤러는 노드가 특정 조건(파드가 배치되면 안되는 상황)이 되었을 때 노드에 테인트를 설정한다. 즉 아래와 같은 built-in 테인트를 가진다.

  • node.kubernetes.io/not-ready: Node is not ready. This corresponds to the NodeCondition Ready being "False".
  • node.kubernetes.io/unreachable: Node is unreachable from the node controller. This corresponds to the NodeCondition Ready being "Unknown".
  • node.kubernetes.io/memory-pressure: Node has memory pressure.
  • node.kubernetes.io/disk-pressure: Node has disk pressure.
  • node.kubernetes.io/pid-pressure: Node has PID pressure.
  • node.kubernetes.io/network-unavailable: Node's network is unavailable.
  • node.kubernetes.io/unschedulable: Node is unschedulable.
  • node.cloudprovider.kubernetes.io/uninitialized: When the kubelet is started with "external" cloud provider, this taint is set on a node to mark it as unusable. After a controller from the cloud-controller-manager initializes this node, the kubelet removes this taint.

https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/

⚠️ **GitHub.com Fallback** ⚠️