node selector와 node affinity 둘 다 pod를 특정 node로 지정하여 스케줄링하는 방법에 대한 것들이다. 먼저 node selector에 대해서 알아보도록 하자.
다음의 pod들과 node들이 있다고 하자. 이들은 서로 자원량이 다르기 때문에 크기에 비레하여 자원량이 다르다고 생각하면된다.
즉, pod는 크면 클수록 더 많은 자원을 요구하고, node는 크면 클수록 더 많은 이용 가능한 자원을 갖고 있는 것이다.
--pod1--- -pod2-
| | | |
| | ------
| |
---------
----Node1---- --Node2--
| | | |
| | | |
| | ---------
| |
-------------
즉 pod1은 pod2보다 더 많은 자원량을 요구하고, node1은 node2보다 더 많은 가용 자원을 가지고 있다. 우리가 생각하기에는 pod1
이 node1
로 스케줄링되고 node2
에 pod2
가 스케줄링되는 것이 베스트로 보이지만, 실상은 그렇지 않을 수 있다.
----Node1---- --Node2--
| | | pod1 |
| pod2 | | |
| | ---------
| |
-------------
막상 node1
에 자원 요구량이 적은 pod2
가 배치되어 node1
의 자원량이 너무 많이 낭비되고, node2
에는 자원 요구량이 많은 pod1
이 배치되어 자원이 너무 부족해질 수 있다.
이러한 문제를 해결하기 위해서 pod를 특정 node에 배치되도록 지정할 수 있는데, 이전처럼 직접 nodeName
으로 특정 node를 딱 지정하는 것이 아니라, label
을 사용하여 node
그룹을 지정할 수 있다.
이것이 nodeSelector
이다. 가령 pod에서 nodeSelector
로 size: Large
로 쓰게된다면 size=Large
label을 가진 node
만이 pod를 스케줄링 할 수 있게 된다.
위의 예시를 들어보면 다음과 같다.
------pod1------- -pod2-
| nodeSelector: | | |
| size: Large | | |
| | ------
| |
-----------------
----Node1---- --Node2--
| size=Large| | |
| | | |
| | ---------
| |
-------------
이러한 상태라면 pod1
은 size: Large
label을 가진 Node1
에 스케줄링된다.
이렇게 nodeSelector
를 사용하여 특정 label을 지정한 node
에 배치될 수 있도록 만들 수 있다.
그럼 어떻게 pod에 nodeSelector
를 지정할 수 있는가? 그건 pod를 생성하는 file에서 설정할 수 있다.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: data-processor
image: data-processor
nodeSelector:
size: Large
이제 data-processor
pod는 nodeSelector
를 통해서 size: Large
label을 가진 node에 스케줄링되는 것이다.
그럼 어떻게 node에 label을 넣을 수 있는가?? node뿐만 아니라, 모든 kubernetes object에 label을 부여하는 방식은 다음과 같다.
kubectl label <object-type> <name> <key>=<value>
가령, node
중 node1
node에 size=Large
라는 label을 추가하고 싶다면 다음과 같다.
kubectl label nodes node-1 size=Large
node selector에는 몇 가지 한계가 있는데, 가장 큰 한계는 복잡한 node설정을 가질 때, 단순한 label지정 배치 이외에는 여러 기능이 없다는 것이다.
가령, node의 가용 자원량을 label로 표기하여 size: Large
, size: Medium
, size: Small
이 있다고 하자. pod는 Small
빼고는 모두 스케줄링이 가능하지만 nodeSelector
로 설정할 때는 Large
나 Medium
둘 중 하나의 label을 선택해야한다.
이러한 문제를 바로 affinity
가 해결해주는 것이다.
node affinity를 사용하는 가장 큰 이유는 pod를 특정 node들에 스케줄링하도록 하기위함이다. 이는 node selector와 같은 목적이지만 node affinity를 사용하면 좀 더 복잡한 방식의 node들을 타겟으로 설정할 수 있다.
이전의 node selector를 쓴 yaml파일을 보도록 하자.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: data-processor
image: data-processor
nodeSelector:
size: Large
이 파일과 동일한 기능을 하는 node affinity가 바로 다음과 같다.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
spec:
containers:
- name: data-processor
image: data-processor
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In
values:
- Large
두 yaml파일은 같은 기능을 하는 pod를 만든다. 즉, 둘 다 size=Large
label을 가진 node에 해당하는 pod를 스케줄링하라는 것이다.
node affinity에 대해서 좀 더 자세히 알아보도록 하자. 가장 중요한 부분은 key
, operator
, values
이다. 특히 operator: In
이라는 것은 label
의 key인 size
에 해당하는 value로 values
리스트 중 하나만 맞아도 스케줄 후보 node에 들어간다는 것이다. 사실 나머지 부분들은 affinity
에서 반복되는 부분이므로, 그냥 이렇게 쓰는 거구나라고 알고만 있으면 된다.
만약 해당 pod를 Large
나 Medium
node에 스케줄링하고 싶다면 다음과 같이 쓸 수 있는 것이다.
...
spec:
containers:
- name: data-processor
image: data-processor
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In
values:
- Large
- Medium
operator
에는 In
말고도 NotIn
도 있어서 Small
node말고 Large
, Medium
node만 가능하게 해주고 싶다면 다음과 같이 쓸 수 있다.
...
spec:
containers:
- name: data-processor
image: data-processor
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: NotIn
values:
- Small
또 다른 operator
로는 Exists
가 있는데, 이는 key
가 있는 node에만 해당 pod를 스케줄링하겠다는 것이다. 즉, values
는 뭐든 간에 중요하지 않고 key
가 있냐없냐라는 것이다.
...
spec:
containers:
- name: data-processor
image: data-processor
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: Exists
다음의 경우 key
인 size
가 label로 있는 지 없는 지만 검사하여 해당 pod를 스케줄링할 node를 선택한다. values
를 써도되지만 어차피 비교가 없으므로 쓰지 않아도 된다.
이외에도 다양한 operator
가 있으니 문서를 참조하여 보도록 하자.
그런데 만약 affinity의 key-value
label, operator
조건에 node들이 모두 맞지 않으면, 해당 pod는 아무런 스케줄링도 받지못해 pending
되는 것인가??
또한, node affinity에 따라 pod가 특정 node에서 실행되고 있는데, node의 label이 바뀌어 버리면 실행되고 있던 pod는 어떻게되는가??
이러한 모든 상황에 대한 대처는, spec.affinity.nodeAffinity
아래의 아주 긴 field에 따라 다르다. 이 부분이 바로 node affinity type
인 것이다.
node affinity type은 두 가지 부분으로 나눌 수 있는데, 하나는 스케줄링 될 때(DuringScheduling)
과 하나는 pod가 node에서 실행되고 있을 때(DuringExecution)
이다. 이 두 가지 부분에 대한 해답은 각 부분의 앞에 적혀있다.
Available
requiredDuringSchedulingIgnoredDuringExecution
: 필수적으로 node affinity에 맞게 node를 선택해 pod를 스케줄링하라는 것이다. 따라서, node affinity에 맞는 node가 없다면 pod는 스케줄링되지 않는다. 단, pod가 특정 node에서 실행되고 있을 때, label이 바뀌어 pod의 node affinity를 어겨도, 무시하고 pod는 계속 해당 node에서 실행된다.
preferredDuringSchedulingIgnoredDuringExecution
: node affinity에 맞게 node를 선택해 pod를 스케줄링하는 것을 선호하지만, node affinity에 맞는 node가 없다면 pod는 affinity에 맞지 않은 node라도 스케줄링된다. 단, pod가 특정 node에서 실행되고 있을 때, label이 바뀌어 pod의 node affinity를 어겨도, 무시하고 pod는 계속 해당 node에서 실행된다.
Planned
requiredDuringSchedulingRequiredDuringExecution
: 필수적으로 node affinity에 맞게 node를 선택해 pod를 스케줄링하라는 것이다. 따라서, node affinity에 맞는 node가 없다면 pod는 스케줄링되지 않는다. 또한, pod가 특정 node에서 실행되고 있을 때, label이 바뀌어 pod의 node affinity를 어기면 실행되고 있던 pod는 evicted
된다. 이후 node에서 빠져나와 terminated
된다.표를 그리면 다음과 같다.
DuringScheduling | DuringExecution | |
---|---|---|
Type 1 | Required | Ignored |
Type 2 | Preferred | Ignored |
Type 3 | Required | Required |
Type 3
를 기준으로 위의 예제를 빌려보면 pod1
의 affinity
로 size: Large
를 가지고 있고, Node1
이 label로 size=Large
을 가지고 있으면 Node1
애 pod1
을 스케줄링 할 수 있다. 만약, Node1
이 size=Large
label이 없었다면 pod1
은 스케줄링되지 못하고 pending
된다.
Node1
에 pod1
이 스케줄링되어 실행되고 있다고 하자. 만약 Node1
의 label 중 size=Label
가 사라지면 Node1
에서 실행 중이던 pod1
은 evicted
상태가 되고, Node1
에서 빠져나와 종료된다.ㅈ