- NodeSelector ← 오늘 볼 내용
- Affinity and Anti-affinity ← 오늘 볼 내용
- Pod Overhead
- 파드 스케줄링 준비성(Readiness)
- 파드 토폴로지 분배 제약 조건
- Taints and Tolerations
- cordon and drain
https://kubernetes.io/ko/docs/concepts/scheduling-eviction/assign-pod-node/
1. NodeSelector
노드셀렉터에 대해서는 Label(2) 포스트에서 이미 다루었다.
복습 차원에서 다시 한번 확인해보자.
파드에 레이블을 할당할 수 있듯이 노드에도 레이블을 할당할 수 있다.
이 후 디플로이먼트를 통해서 파드를 생성할 때 템플릿 내에 노드셀렉터를 통해 특정 레이블을 가진 노드에 파드를 할당하도록 운영할 수 있다.
파드를 스케줄링할 때 원하는 노드에 스케줄링하도록 설계하는 가장 기초적인 방법이다.
실습
먼저 노드에 레이블을 추가한다.
kubectl label node node1 name=node1
kubectl label node node2 name=node2
디플로이먼트를 배포하여 노드셀렉터를 통해 파드가 모두 node1에 생성되도록 한다.
# nodeselector-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: select-node
spec:
replicas: 2
selector:
matchLabels:
name: web
matchExpressions:
- {key: rel, operator: In, values: [stable]}
template:
metadata:
labels:
name: web
rel: stable
spec:
containers:
- name: nginx-container
image: nginx:1.25
nodeSelector:
name: node1
정상적으로 동작하는 것을 확인한 후 배포한 디플로이먼트와 노드에 할당된 레이블을 제거한다.
kubectl delete -f nodeselector-deploy.yaml
kubectl label node node1 name-
kubectl label node node2 name-
NodeName
노드셀렉터가 아닌 좀 더 직접적으로 노드를 명시하는 방법이 있다.
아래 yaml 코드는 무조건 node2 에 스케줄링한다.
apiVersion: v1
kind: Pod
metadata:
name: nodenametest
spec:
containers:
- name: nginx
image: nginx:1.25
nodeName: node2
직접적으로 노드의 이름을 기재하여 원하는 노드에 스케줄되도록 만들 수도 있다.
허나 nodeName 을 사용해서 노드를 선택할 때의 몇 가지 제한은 다음과 같다.
- 만약 명명된 노드가 없으면, 파드가 실행되지 않고 따라서 자동으로 삭제될 수 있다.
- 만약 명명된 노드에 파드를 수용할 수 있는 리소스가 없는 경우 파드가 실패하고, 그 이유는 다음과 같이 표시된다. 예: OutOfmemory 또는 OutOfcpu.
- 클라우드 환경의 노드 이름은 항상 예측 가능하거나 안정적인 것은 아니다.
2. Affinity and Anti-affinity
nodeSelector 는 파드를 특정 레이블이 있는 노드로 제한하는 가장 간단한 방법이다.
어피니티/안티-어피니티 기능은 표현할 수 있는 제약 종류를 크게 확장한다.
NodeSelector 로 특정 노드에 파드를 생성하려고 할 때 어떤 노드든 매칭되는 레이블을 가지고 있지 않으면 파드는 절대 생성되지 않는다.
하지만 Affinity 는 다양한 표현방법으로 좀 더 유연하게 노드를 지정하여 파드를 생성한다.
어피니티 기능은 다음의 두 가지 종류로 구성된다.
- 노드 어피니티 기능은 nodeSelector 필드와 비슷하지만 더 표현적이고 소프트(soft) 규칙을 지정할 수 있게 해 준다.
- 파드 간 어피니티/안티-어피니티 는 다른 파드의 레이블을 이용하여 해당 파드를 제한할 수 있게 해 준다.
노드 어피니티
- requiredDuringSchedulingIgnoredDuringExecution: 규칙이 만족되지 않으면 스케줄러가 파드를 스케줄링할 수 없다. 이 기능은 nodeSelector와 유사하지만, 좀 더 표현적인 문법을 제공한다.
- preferredDuringSchedulingIgnoredDuringExecution: 스케줄러는 조건을 만족하는 노드를 찾으려고 노력한다. 해당되는 노드가 없더라도, 스케줄러는 여전히 파드를 스케줄링한다.
참고
앞의 두 유형에서, IgnoredDuringExecution 는 쿠버네티스가 파드를 스케줄링한 뒤에 노드 레이블이 변경되어도 파드는 계속 해당 노드에서 실행됨을 의미한다.
실습
현재 내가 운영중인 환경은 node1과 node2 두대로 운영중이다.
노드의 개수가 다르다면 아래 실습에서 본인이 추가로 레이블을 부여해주고 yaml코드를 수정하여 실습하면 된다.
노드의 개수가 많으면 더 이해가 쉽고 습득이 잘되리라 생각한다.
Node Affinity
직접 실습해보자.
먼저 노드에 각각 레이블을 지정한다.
kubectl label node node1 disk=ssd gpu=true cpu=intel name=node1
kubectl label node node2 disk=hdd gpu=false cpu=amd
이번엔 파드 템플릿을 구성한다.
아래 yaml코드로 파드를 실행하면 과연 어떤 노드로 파드가 생성될 지 생각해보자.
# pod-with-node-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
- hdd
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: gpu
operator: In
values:
- "true"
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
노드 어피니티를 통해 조건을 disk는 ssd 나 hdd 둘 중 하나만이라도 존재하면 할당되지만 gpu 가 true 인지 false 인지에 따라 배치되는 위치가 달라진다.
위 코드에서는 true 인 노드에 배치되는 것을 좀 더 선호하기에 결과적으로 node1 에 배치된 것이다.
preferred 는 선호하는 정도를 의미하지만 required 는 preferred 보다 우선되는 조건이기에 required 에서 조건이 매칭되지 않으면 preferred 는 무시된다.
위 레이블을 조금씩 변경하여 파드의 배치가 어떻게 변하는지 확인해보면 이해가 빠를 것이다.
operator 필드를 사용하여 쿠버네티스가 규칙을 해석할 때 사용할 논리 연산자를 지정할 수 있다.
In, NotIn, Exists, DoesNotExist, Gt 및 Lt 연산자를 사용할 수 있다.
NotIn 및 DoesNotExist 연산자를 사용하여 노드 안티-어피니티 규칙을 정의할 수 있다.
또는, 특정 노드에서 파드를 쫓아내는 노드 테인트(taint)를 설정할 수 있다.
Taint에 대해서는 다음에 알아보도록 하자.
가중치(weight)는 각 preferredDuringSchedulingIgnoredDuringExecution 어피니티 타입 인스턴스에 대해 1-100 범위의 weight를 명시할 수 있다.
파드는 preferred로 지정된 모든 조건을 각 노드와 매칭시켜 가중치의 합계를 기준으로 우선순위를 매긴다.
하지만 우선순위는 우선순위일 뿐 반드시 가중치가 높은 노드에 파드가 배치된다는 것을 의미하는 건 아니다.
계속해서 이번에는 다음과 같은 템플릿이 있다고 가정해보자.
required 는 조건이 맞다면 무조건 해당 노드로 배치가 되니 알기 쉽다.
preferred 의 조건을 보고 어떤 노드에 파드가 배치될 지 생각해보자.
# pod-with-affinity-anti-affinity-1.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-affinity-anti-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
- hdd
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 20
preference:
matchExpressions:
- key: cpu
operator: In
values:
- amd
- weight: 50
preference:
matchExpressions:
- key: gpu
operator: In
values:
- "true"
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
나의 경우는 아주 정직하게 가중치가 50으로 가장 높은 node1 에 배치되었다.
node2 에 배치되었다고 해도 잘못된 것은 아니다.
가중치에 의해서 선호도를 계산하여 우선순위만 매겨주는 것뿐이다.
사실 파드 하나만 생성하는 것으로는 항상 node1 에 생성된다(직접 해봄).
파드를 여러개 한번에 생성한다면 이야기가 달라진다.
위 yaml 파일에서 조금만 수정하여 파드를 10개 생성하는 ReplicaSet 을 구성하여 실행한 결과다.
파드의 배치 상태가 비율에 따라 node1 에 좀더 치우친 것을 볼 수 있고 일부 파드는 node2 로 배치된 것을 볼 수 있다.
위 코드를 파헤쳐보면 다음과 같다.
- node1 = 50
- disk=ssd로 required의 조건을 만족
- gpu=true로 preferred의 조건을 만족하여 가중치 + 50
- node2 = 20
- disk=hdd로 required의 조건을 만족
- cpu=amd로 preferred의 조건을 만족하여 가중치 + 20
파드가 위 결과에 따라 노드에 배치가 된다면 5:2 비율로 배치가 될 것이다.
가중치를 통해 파드를 스케줄링할 때 더 선호하는 노드를 결정할 수 있으며 더 유연한 파드 배치가 가능하다.
단, required를 만족하는 노드가 없다면 파드는 스케줄링되지 않고 pending 상태에 머무른다.
Node Anti-affinity
다음 yaml 코드를 실행시켜보자.
아래는 조금 전에 설명한 노드 안티 어피니티를 구성할 때 사용하는 DoesNotExist 오퍼레이터를 사용하여 스케줄링 시 배제할 노드를 지정하는 코드다.
# pod-with-affinity-anti-affinity-2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: name
operator: DoesNotExist
containers:
- name: myapp-container
image: nginx:1.25
name이라는 레이블은 node1만 가지고 있다.
하지만 yaml코드에서 name이라는 레이블을 가지고 있지 않은 노드에 파드를 스케줄링하도록 설계했기에 모든 파드는 node2에 스케줄링된다.
이와 같이 피하고 싶은 노드에 파드가 스케줄링되지 못하게 하는 Anti-affinity 가 있다.
파드 어피니티
파드간 어피니티와 안티-어피니티를 사용하여, 노드 레이블 대신, 각 노드에 이미 실행 중인 다른 파드 의 레이블을 기반으로 파드가 스케줄링될 노드를 제한할 수 있다.
파드간 어피니티와 안티-어피니티 규칙은 "X가 규칙 Y를 충족하는 하나 이상의 파드를 이미 실행중인 경우 이 파드는 X에서 실행해야 한다(또는 안티-어피니티의 경우에는 "실행하면 안 된다")"의 형태이며, 여기서 X는 노드, 랙, 클라우드 제공자 존 또는 리전 등이며 Y는 쿠버네티스가 충족할 규칙이다.
이러한 규칙(Y)은 레이블 셀렉터 형태로 작성하며, 연관된 네임스페이스 목록을 선택적으로 명시할 수도 있다. 쿠버네티스에서 파드는 네임스페이스에 속하는(namespaced) 오브젝트이므로, 파드 레이블도 암묵적으로 특정 네임스페이스에 속하게 된다. 파드 레이블에 대한 모든 레이블 셀렉터는 쿠버네티스가 해당 레이블을 어떤 네임스페이스에서 탐색할지를 명시해야 한다.
노드 어피니티와 마찬가지로 파드 어피니티 및 안티-어피니티에는 다음의 2 종류가 있다(노드 어피니티와 똑같음).
- requiredDuringSchedulingIgnoredDuringExecution
- preferredDuringSchedulingIgnoredDuringExecution
실습
Pod Affinity
우선 노드 레이블을 제외한 앞서 진행했던 모든 리소스를 정리한다.
kubectl delete all --all
node1에 security=S1이라는 레이블을 가진 파드하나를 생성한다.
# pod-affinity-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: testpod
labels:
security: S1
spec:
containers:
- image: nginx:1.25
name: nginx-container
ports:
- containerPort: 80
protocol: TCP
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values:
- ssd
아래 yaml을 통해 파드 어피니티가 정상적으로 동작하는지 테스트한다.
결과가 어떻게 될지 예측해보자.
# pod-with-pod-affinity.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: registry.k8s.io/pause:2.0
위 yaml코드를 해석하자면 다음과 같다.
정의된 파드 어피니티로 인해 파드는 security=S1 이라는 레이블을 가진 파드가 있는 노드 node1 에 함께 배치한다.
파드 안티 어피니티로 인해 security=S2 라는 레이블을 가진 파드가 있는 노드에는 가급적 파드를 스케줄링하지 않는다.
또한 파드 어피니티에서 topologyKey: kubernetes.io/hostname 은 이후에 스케줄링되는 파드들 중 조건과 일치하는 레이블을 가진 파드들은 모두 같은 호스트 네임을 가지는 노드에 스케줄링한다.
즉, security=S1 이라는 레이블을 가진 파드가 스케줄링되려고 하면 무조건 kubernetes.io/hostname=node1이라는 레이블을 가진 노드 node1 에 스케줄링한다.
파드 안티 어피니티에서 topologyKey: kubernetes.io/hostname 은 이후에 스케줄링되는 파드들 중 조건과 일치하는 레이블을 가진 파드들은 가급적 서로 다른 호스트 네임을 가진 노드에 스케줄링한다.
즉, security=S2 이라는 레이블을 가진 파드가 스케줄링되려고 하면 같은 레이블을 가진 파드와 동일한 노드에 스케줄링하지 않으려는 규칙으로 인해 각 노드에 중첩되지 않게 파드를 스케줄링한다.
topologyKey: kubernetes.io/hostname 라는 필드는 규칙이 실행된 동안 지속적으로 이후에 스케줄링되는 파드들의 레이블을 검토하여 hostname 을 기준으로 파드를 스케줄링하려고 한다.
Pod Anti-Affinity
다시 한번 노드 레이블을 제외한 앞서 진행했던 모든 리소스를 정리한다.
kubectl delete all --all
아래 yaml 파일은 파드 안티 어피니티에 대한 코드다.
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: registry.k8s.io/pause:2.0
이번에는 아래를 실행한다.
kubectl run testpod --image=nginx:1.25 --labels="security=S1"
나의 경우는 워커노드가 2대 밖에 없기 때문에 위 명령을 통해 생성한 파드가 다른 노드에 스케줄링된 것이 와닿지 않을 수 있지만 워커노드가 여러 대인 경우 쉽게 이해할 수 있을 것이다.
다음과 같이 파드 어피니티를 통해 이미 스케줄링된 파드의 레이블에 따라 파드를 어디에 스케줄링할지 결정하는 메커니즘을 정의할 수 있다.
다음 포스트에서 파드 어피니티에 대해 상세한 동작과정을 알아볼 것이다.
'Kubernetes' 카테고리의 다른 글
Kubernetes 기초 - Scheduling(2) (0) | 2024.01.04 |
---|---|
Pod Affinity - topologyKey 상세 동작 (0) | 2024.01.03 |
Kubernetes 기초 - 로깅과 모니터링(2) (0) | 2024.01.02 |
Kubernetes 기초 - 로깅과 모니터링(1) (0) | 2024.01.02 |
멀티 마스터 쿠버네티스 클러스터 구성 방법 (0) | 2023.12.31 |