Affinity를 사용하여 파드를 스케줄링할 노드를 찾을 때의 제약조건을 정의할 수 있다. Affinity를 사용하면 이전 시간에 언급한 nodeSelector보다 더 풍부한 방식으로 파드를 실행시키는 노드의 조건을 설정할 수 있다.
Affinity에는 크게 NodeAffinity와 Pod간 Affinity가 있다. 이번 글에서는 Node Affinity에 대해 알아보자.
NodeAffinity
Node Affinity
는 node label를 기반으로 파드를 스케줄링할 수 있는 노드를 제한할 수 있다. Affinity는 파드의 Spec이기 때문에 Affinity를 사용하면 파드가 노드를 선택하는 개념으로 볼 수 있다. Scheduler에서는 NodeAffinity 플러그인에서 NodeAffinity에 대한 필터링 및 스코어링 기능을 구현한다. Affinity에는 두 가지 유형이 있다.
requiredDuringSchedulingIgnoredDuringExecution
: 스케줄러는 규칙이 충족되지 않으면 파드를 스케줄링하지 않는다.preferredDuringSchedulingIgnoredDuringExecution
: 스케줄러는 해당 규칙이 충족되는 노드를 찾으려고 한다. 만약 일치하지 않는 노드가 없더라도 스케줄러는 파드를 스케줄링한다.
requiredDuringSchedulingIgnoredDuringExecution
requiredDuringSchedulingIgnoredDuringExecution
유형은 해당 규칙에 충족되는 노드가 있다면 파드를 해당 노드 중 하나로 스케줄링하고, 충족되는 노드가 없다면 파드를 스케줄링하지 않는 유형이다. 이 유형은 Filtering 에서 사용된다. 이 유형을 사용하면 nodeSelector와 거의 비슷한 방식으로 작동하기는 하지만 필터링 연산이 조금 더 풍부하다.
requiredDuringSchedulingIgnoredDuringExecution으로 이전 시간에서 nodeSelector로 파드를 스케줄링 한 것과 같이 nfs=true
인 노드로만 스케줄링 하도록 구성한다면 다음과 같다.
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-test
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nfs
operator: In
values:
- 'true'
containers:
- name: nginx
image: nginx
volumeMounts:
- name: contents
mountPath: /contents
volumes:
- name: contents
persistentVolumeClaim:
claimName: nginx-nfs-pvc
파드를 생성한 후 조회해보면 nfs=true
인 노드로 스케줄링 된 것을 확인할 수 있다.
Operator
NodeSelector와 NodeAffinity의 가장 큰 차이점은 NodeAffinity는 operator를 지원한다는 점이다. NodeAffinity에서 지원하는 operator는 총 4가지로, In
, NotIn
, Exists
, DoesNotExist
이 있다.
In
: 노드의 레이블key
에 대한 값이values
에서 지정한 값들 중 하나일 경우 해당 노드 선택NotIn
: 노드의 레이블key
에 대한 값이values
에서 지정한 값들 중 하나일 경우 해당 노드 제외Exists
: 노드의 레이블key
에 대한 값이 존재하는 경우 해당 노드 선택, 해당 operator를 사용하는 경우values
는 필요 없다.DoesNotExists
: 노드의 레이블key
에 대한 값이 존재하는 경우 해당 노드 제외, 해당 operator를 사용하는 경우values
는 필요 없다.
따지고보면 NodeSelector는 NodeAffinity에서의 values
가 하나 뿐인In
연산자와 같다. 대신 NodeAffinity는 In
외의 연산자와 여러개의 values 값을 지정하여 노드를 선택할 수 있어서 NodeSelector보다는 더 풍부한 방식으로 노드를 선택할 수 있다.
간단한 예시로, 파드를 nfs 레이블이 없는 노드에만 배치하고 싶을 때는 다음과 같이 지정하면 된다.
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-test2
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nfs
operator: DoesNotExist
containers:
- name: nginx
image: nginx
파드를 생성한 후 조회해보면 nfs
레이블이 없는 노드로 스케줄링 된 것을 확인할 수 있다.
Troubleshooting: 리소스가 부족한 경우
만약 nfs=true
인 노드에 파드에게 할당할 리소스가 부족하다면 어떻게 될까? 파드 스케줄링에서 필터링 단계 중, NodeResourcesFit
이 있는데 이 단계에서는 파드에 정의된 resources.requests
만큼의 리소스를 할당할 수 없는 노드는 파드 스케줄링 대상에서 제외시킨다.
실습을 위해 리소스를 충분히 많이 잡아먹는 파드를 하나 생성하고 나서 node-affinity-test
에 resources.requests
를 할당한 후 생성해보자.
apiVersion: v1
kind: Pod
metadata:
name: resource-gangster
spec:
nodeSelector:
nfs: "true"
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: '800m'
memory: 2Gi
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-test
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nfs
operator: In
values:
- 'true'
containers:
- name: nginx
image: nginx
volumeMounts:
- name: contents
mountPath: /contents
resources:
requests:
cpu: 0.5
memory: 512Mi
volumes:
- name: contents
persistentVolumeClaim:
claimName: nginx-nfs-pvc
파드를 생성해보면 Pending 상태로 머물러있는데, 이벤트를 조회해보면 다음과 같다.
대충 해석하자면, 노드 두 대 중 한 대는 Affinity
에서 걸리고, 나머지 한 대는 Affinity
에 의해 선택되었지만 파드에게 할당할 CPU와 메모리가 부족하기 때문에 파드를 실행시킬 노드가 없어서 파드 스케줄링에 실패했다는 것이다. 필터링 단계에서 모든 노드가 선택을 못받았기 때문에 파드는 Pending 상태로 머물게 된다.
이런 에러가 발생한다면 nodeAffinity를 지우거나, 어피니티를 만족시키는 노드의 리소스를 증설하거나, 어피니티를 만족시키는 노드를 추가하거나, 어피니티를 만족시키는 노드에서 필요없는 파드를 축출시켜 가용 리소스를 확보해야 한다.
preferredDuringSchedulingIgnoredDuringExecution
preferredDuringSchedulingIgnoredDuringExecution
유형은 해당 규칙에 충족되는 노드가 있다면, 규칙에 충족되는 노드에게 가중치를 부여하는 유형이다. requiredDuringSchedulingIgnoredDuringExecution
과 가장 큰 차이점은 preferredDuringSchedulingIgnoredDuringExecution
은 규칙에 충족되는 노드가 없어도 파드를 다른 노드로 스케줄링 할 수 있다는 것이다. 그래서 이 유형은 Scoring 에서 사용된다.
preferredDuringSchedulingIgnoredDuringExecution
유형에서는 1부터 100까지의 weight
를 부여할 수 있다. 스케줄러는 파드의 다른 모든 스케줄링 요구사항을 충족하는 노드를 찾으면 스케줄러는 노드가 충족하는 모든 기본 설정 규칙을 반복하고 해당 표현식에 대한 가중치 값을 합계에 더한다. 최종 합계는 노드의 다른 우선순위 함수의 점수에 더해진다. 스케줄러가 파드에 대한 스케줄링 결정을 내릴 때 총 점수가 가장 높은 노드가 우선순위를 가진다.
예시
CPU 연산을 자주하는 애플리케이션이 있고, 노드 중 CPU 성능이 좋은 노드가 있다고 가정하자. 그러면 해당 애플리케이션은 CPU 성능이 좋은 노드로 스케줄링되면 성능이 유리해진다.
해당 애플리케이션을 preferredDuringSchedulingIgnoredDuringExecution
으로 스케줄링 해보도록 하자.
일단 먼저, CPU 성능이 좋은 노드에 cpu=fast
라는 레이블을 추가하자.
$ kubectl labels no k8s-wn1 cpu=fast
그 다음 preferredDuringSchedulingIgnoredDuringExecution
을 사용하여 NodeAffinity를 구성하여 파드를 생성해보자.
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-test3
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
preference:
matchExpressions:
- key: cpu
operator: In
values:
- fast
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: '0.5'
memory: '512Mi'
파드를 조회해보면 cpu=fast
인 노드로 스케줄링 된 것을 확인할 수 있다.
cpu=fast 노드에 리소스가 없다면?
만약 cpu=fast
레이블이 있는 노드에 리소스가 없다면 어떻게 될까? 이전에 만들어둔 resource-gangster
파드를 생성한 후 node-affinity-test3
을 생성해보자.
apiVersion: v1
kind: Pod
metadata:
name: resource-gangster
spec:
nodeSelector:
cpu: "fast"
containers:
- name: nginx
image: nginx
resources:
requests:
cpu: '800m'
memory: 2Gi
이번에는 파드가 cpu=fast
가 아닌 노드로 스케줄링 되었다.
앞서 말했지만 preferredDuringSchedulingIgnoredDuringExecution
은 어피니티의 조건을 만족시키는 노드에게 우선순위를 매기는 것일 뿐, 조건을 만족시키지 않는 노드를 배제하는 것이 아니기 때문에 어피니티의 조건을 만족시키는 노드에서 다른 이유로 스케줄링을 할 수 없는 경우에도 조건을 만족시키지 않는 노드로 스케줄링을 할 수 있다.
resource-gangster
파드가 없는 경우에는 두 노드 모두 가용 리소스가 충분하기 때문에 필터링에서 모두 통과하고, preferredDuringSchedulingIgnoredDuringExecution
에 의해 k8s-wn1
노드가 가중치를 더 많이 받아 파드 스케줄링에 우선순위가 높아져 최종적으로 파드가 k8s-wn1
노드에 배치가 된다.
하지만 resource-gangster
파드가 k8s-wn1
노드에 스케줄링되어 k8s-wn1
노드에 가용 리소스가 없다면, 필터링 단계에서 k8s-wn1
노드가 제외되지만 k8s-wn2
노드는 제외되지 않고, NodeAffinity에 의해 가중치를 받는 노드가 없기 때문에 가중치가 없어도 k8s-wn2
노드로 파드가 스케줄링된다.
'DevOps > Kubernetes' 카테고리의 다른 글
Kubernetes Scheduling 소개 및 NodeSelector (1) | 2024.05.01 |
---|---|
[1] 쿠버네티스 확장 - kubectl plugin (0) | 2023.10.29 |
Helm Chart Repository 만들기 (2) - Harbor OCI registry (0) | 2023.10.12 |
Helm Chart Repository 만들기 (1) - Github (0) | 2023.10.09 |
Helm Chart 유효성 검증과 문서화 (0) | 2023.10.03 |
댓글