Kubernetes

Pod Scheduling에 대해서 알아보자 - 1 [NodeSelector, affinity&antiaffinity]

chaenii 2022. 3. 3. 15:41

쿠버네티스에서는 파드를 어떤 노드에 실행할 것인지에 관한 다양한 옵션이 있다.

이 옵션을 조합해서 사용자가 원하는 구조대로 클러스터 안에 파드들을 배치할 수 있다.

 

1. NodeSelector

파드가 클러스터 안 어떤  노드에서 실행될지를 키-값 쌍으로 결정한다.

노드셀렉터는 노드의 레이블에 설정된 값으로 노드를 선택한다.

 

노드에 label 생성하기

$ kubectl label nodes <node> <key>=<value>

 

kube-worker 노드에 disktype=ssd 라벨을 만든다.

$ kubectl label nodes kube-worker disktype=ssd
$ kubectl get nodes -L disktype

NAME           STATUS   ROLES                  AGE   VERSION   DISKTYPE
kube-master    Ready    control-plane,master   24d   v1.22.0   
kube-worker    Ready    <none>                 24d   v1.23.3   ssd
kube-worker2   Ready    <none>                 26m   v1.23.4

 

파드에 노드셀렉터를 설정하는 예

.spec.nodeselector.disktype 필드 값으로 hdd 설정했다.

노드의 레이블에서설정한 disktype=ssd와 다른 설정을 해서 파드를 실행해볼 것이다.

apiVersion: v1
kind: Pod
metadata:
  name: kubernetes-nodeselector-pod
spec:
  containers:
  - name: tensorflow
    image: tensorflow/tensorflow:nightly-jupyter
    ports:
    - containerPort: 8080
      protocol: TCP
  nodeSelector:
    disktype: hdd

 

파드 상태 확인

파드 상태가 pending 상태에 머물러있는데, 이는 .spec.nodeSelector.disktype 필드 값 hdd는 노드 레이블의 설정인 ssd와 달라서 실행할 노드가 없기 때문이다.

$ kubectl create -f nodeselector.yaml

$ kubectl get po kubernetes-nodeselector-pod
NAME                          READY   STATUS    RESTARTS   AGE
kubernetes-nodeselector-pod   0/1     Pending   0          2m9s
$ kubectl describe po kubernetes-nodeselector-pod
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  73s   default-scheduler  0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.

 

nodeSelector ssd로 변경하기

apiVersion: v1
kind: Pod
metadata:
  name: kubernetes-nodeselector-pod
spec:
  containers:
  - name: tensorflow
    image: tensorflow/tensorflow:nightly-jupyter
    ports:
    - containerPort: 8080
      protocol: TCP
  nodeSelector:
    disktype: ssd

 

pod 배포하기

pod가 정상적으로 배포되는 것을 확인할 수 있다.

disktype=ssd 라벨을 가진 node인 kube-worker에 배포되었다.

$ kubectl create -f nodeselector.yaml

NAME                          READY   STATUS    RESTARTS   AGE   IP             NODE          NOMINATED NODE   READINESS GATES
kubernetes-nodeselector-pod   1/1     Running   0          64s   10.244.1.111   kube-worker   <none>           <none>

 

2. Affinity & antiAffinity

 

2-1. Node Affinity

노드 어피니티는 노드셀렉터와 비슷하게 노드의 레이블을 기반으로 파드를 스케줄링한다.

 

노드 어피니티에는 두가지 필드가 있다.

- requiredDuringSchedulingIgnoredDuringExection: '스케줄링하는 동안 꼭 필요한' 조건

- preferredDuringSchedulingIgnoredDuringExection: '스케줄링하는 동안 만족하면 좋은' 조건 즉, 반드시 만족해야하는 것은 아니다.

 

두 필드는 실행중에 해당 노드의 조건이 변경되더라도 이미 실행 중인 파드는 그대로 실행된다.

 

노드에 라벨 생성하기

$ kubectl label nodes {kube-worker,kube-worker2} gpu="true"
$ kubectl label node kube-worker2 disktype=hdd

$ kubectl get nodes -L gpu,disktype
NAME           STATUS   ROLES                  AGE    VERSION   GPU    DISKTYPE
kube-master    Ready    control-plane,master   24d    v1.22.0          
kube-worker    Ready    <none>                 24d    v1.23.3   true   ssd
kube-worker2   Ready    <none>                 3h8m   v1.23.4   true   hdd

 

파드에 노드어피니티를 설정하는 예

apiVersion: v1
kind: Pod
metadata:
  name: tensorflow-nodeaffinity
spec:
  containers:
  - name: tensorflow-container
    image: tensorflow/tensorflow:nightly-jupyter
    ports:
    - containerPort: 8888
      protocol: TCP
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - {key: disktype, operator: Exists}
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 10
        preference:
          matchExpressions:
          - {key: gpu, operator: In, values: ["true"]}
          - {key: disktype, operator: In, values: ["ssd"]}

- 조건 1 : distype 라벨은 반드시 있어야 한다.

- 조건 2: gpu=true인 노드는 가산점 10점을 준다.

- 조건 3: disktype=ssd인 노드는 가산점 10점을 준다.

 

✅ 결과 

kube-worker : 조건 충족, 20점

kube-worker2 : 조건 충족, 10점

파드는 kube-worker에 배포될 것이다.

 

pod 배포하기

pod가 정상적으로 배포되는 것을 확인할 수 있다.

$ kubectl get po tensorflow-nodeaffinity -o wide
NAME                      READY   STATUS    RESTARTS   AGE    IP             NODE          NOMINATED NODE   READINESS GATES
tensorflow-nodeaffinity   1/1     Running   0          115s   10.244.1.113   kube-worker   <none>           <none>

 

라벨 지우기

$ kubectl label node {kube-worker,kube-worker2} gpu-
$ kubectl label node {kube-worker,kube-worker2} disktype-

 

2-2. Pod Affinity

 

파드의 언피니티와 안티 어피니티는 디플로이먼트나 스테이트풀세트로 파드를 배포했을 때 개별 파드 사이의 관계를 정의하는 용도로 사용한다.

 

예를들어, 컨테이너로 서비스를 운영하다보면, 서비스 A와 서비스 B의 파드 사이에 자주 통신할 때가 있다. 어피니티는 이런 상황에서 서비스 A와 서비스 B의 파드들을 같은 노드에 속하게 만들어 효율을 높인다.

 

안티어피니티는 CPU나 네트워크 같은 하드웨어 자원을 많이 사용하는 앱 컨테이너가 있을 때 여러 노드로 파드를 분산하는 역할을 한다. 안티 어피니티를 설정하지 않으면 디플로이먼트로 배포한 파드가 노드 하나에서만 실행되어 자원을 많이 소모할 수 있다.

 

파드 어피니티에는 두가지 필드가 있다.

- requiredDuringSchedulingIgnoredDuringExection: '스케줄링하는 동안 꼭 필요한' 조건

- preferredDuringSchedulingIgnoredDuringExection: '스케줄링하는 동안 만족하면 좋은' 조건 즉, 반드시 만족해야하는 것은 아니다.

 

topologyKey

- 노드 label을 이용해 pod의 affinity와 antiaffinity를 설정할 수 있는 또 하나의 기준

- 쿠버네티스는 pod를 스케줄링할 때 먼저 Pod의 label을 기준으로 대상 노드를 찾고, 이후 topologykey 필드를 확인해 해당 노드가 원하는 노드인지 확인


📘 Affinity 

 

파드 배포하기

busybox 이미지에 app=backend라는 라벨을 가지고 있는 backend라는 파드를 생성했다.

kube-worker2에 배포된 것을 확인했다.

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

$ kubectl get po backend -o wide
NAME      READY   STATUS    RESTARTS   AGE   IP           NODE           NOMINATED NODE   READINESS GATES
backend   1/1     Running   0          12s   10.244.3.2   kube-worker2   <none>           <none>

 

affinity를 정의해 backend 파드가 배포된 노드에 새로운 파드를 배포해보자.

podAffinty를 설정함으로써 app=backend 라벨을 가지고 있는 backend 파드가 배포된 kube-worker2에 배포를 성공했다.

 

affinity를 정의하는 예제

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 5
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: backend
            topologyKey: kubernetes.io/hostname
      containers:
      - name: busybox-container
        image: busybox
        args:
        - sleep
        - "999999"

 

$ kubectl get po -L app -o wide
NAME                                     READY   STATUS    RESTARTS   AGE    IP             NODE           NOMINATED NODE   READINESS GATES   APP
frontend-b55f67d87-7ttqx                 1/1     Running   0          36s    10.244.3.7     kube-worker2   <none>           <none>            frontend
frontend-b55f67d87-mctq6                 1/1     Running   0          36s    10.244.3.5     kube-worker2   <none>           <none>            frontend
frontend-b55f67d87-x7zp4                 1/1     Running   0          36s    10.244.3.6     kube-worker2   <none>           <none>            frontend
frontend-b55f67d87-xqd2h                 1/1     Running   0          36s    10.244.3.4     kube-worker2   <none>           <none>            frontend
frontend-b55f67d87-zlxql                 1/1     Running   0          36s    10.244.3.3     kube-worker2   <none>           <none>            frontend

📘 AntiAffinity

이번엔 반대로 Antiaffinity를 정의해  backend 파드가 배포된 노드를 피해 새로운 파드를 배포해보자.

.spec.affinity.podAntiAffinity에 정의함으로써 app=backend 라벨을 가지고 있는 backend 파드가 배포된 kube-worker2를 배제하고 배포를 성공했다. 

 

현재 worker node는 총 2대로 kube-worker2를 제외하면 kube-worker 밖에 남지 않는다.

따라서 해당 파드는 kube-worker에 배포될 것이다.

$ kubectl get nodes -A
NAME           STATUS     ROLES                  AGE     VERSION
kube-master    Ready      control-plane,master   24d     v1.22.0
kube-worker    Ready      <none>                 24d     v1.23.3
kube-worker2   Ready      <none>                 3h53m   v1.23.4

 

antiaffinity를 정의하는 예제

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 5
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: backend
            topologyKey: kubernetes.io/hostname
      containers:
      - name: busybox-container
        image: busybox
        args:
        - sleep
        - "999999"

 

$ kubectl get po -L app -o wide
NAME                                     READY   STATUS    RESTARTS   AGE    IP             NODE           NOMINATED NODE   READINESS GATES   APP
frontend-85b445d679-bqvn7                1/1     Running   0          87s    10.244.1.118   kube-worker    <none>           <none>            frontend
frontend-85b445d679-dsbpd                1/1     Running   0          87s    10.244.1.117   kube-worker    <none>           <none>            frontend
frontend-85b445d679-hm69l                1/1     Running   0          87s    10.244.1.115   kube-worker    <none>           <none>            frontend
frontend-85b445d679-tcbnj                1/1     Running   0          87s    10.244.1.114   kube-worker    <none>           <none>            frontend
frontend-85b445d679-wxl9g                1/1     Running   0          87s    10.244.1.116   kube-worker    <none>           <none>            frontend

 

 

반응형