Pod Scheduling에 대해서 알아보자 - 1 [NodeSelector, affinity&antiaffinity]
쿠버네티스에서는 파드를 어떤 노드에 실행할 것인지에 관한 다양한 옵션이 있다.
이 옵션을 조합해서 사용자가 원하는 구조대로 클러스터 안에 파드들을 배치할 수 있다.
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