개요
클러스터에 한 번 배치된 Pod는 Node의 상황에 따라 다시 배치되지 않는다.
이 말은 곧 Pod/Node를 선택하는 스케줄링 전략은 신중해야 한다는 뜻이다.
목표
- Pod가 원하는 적절한 Node에 배치될 수 있도록 하는 조건을 이해한다.
- 다양한 Pod 배치 전략을 통해 Schedule Process와 Affinity에 대해 알아본다.
여정
Scheduling Process
Pod가 배치될 Node를 선택하는 데는 아래와 같은 과정을 거친다.
- 스케줄이 가능한 Node 중에서
- Pod의 필터링 조건을 통과하고
- 우선순위 정렬 시 가장 높은 Node 선택
위 조건을 한번 더 차근차근 살펴보자
스케줄이 가능한 Node
- Node는 기본적으로 할당 가능한 Pod의 수가 정해져 있다.
- 일반적으로 110개(On-premise, GCP)이며, AWS는 55개이다.
- 여기서 kube-system 과 같이 중요한 Pod, Daemon 들을 제하면 그 수는 더 감소한다.
- Node는 할당 가능한 Pod 수를 초과하게 되면 Pod를 순차적으로 종료한다.
- QoS(Quality of Service)에 따라서 가장 종료해도 될만한 Pod부터 순차적으로 축출(Eviction)된다.
Pod의 필터링 조건을 통과
Pod는 자신이 배치될 Node를 선택할 수 있다.
선택할 수 있는 조건은 여러가지가 있는데 대표적으로 아래와 같다.
- 자원 요구 사항(CPU, Memory 등)
- Node가 충족해야 하는 조건(NodeAffinity)
- Pod 간의 관계 조건(PodAffinity & PodAntiAffinity)
- Node가 기피 하는 조건(Taint & Toleration)
자원 요구 사항은 이전 글(자원 요구 사항 정의)에서 설명했으니
Node Affinity부터 차근차근 알아보자
Node가 충족해야 하는 조건(NodeAffinity)
Affinity는 Pod를 특정 Label이 있는 Node로 제한할 수 있는 정책을 말한다.반대로, AntiAffinity는 특정 Label이 있는 Node를 선택하지 않도록 하는 정책을 말한다.
그 중에서 NodeSelector는 가장 간단하게 Node를 선택할 수 있는 방법이다.
# Node의 Label 속성 중에 disktype을 지정하는 예시
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
참고로, NodeAntiAffinity는 존재하지 않는다.
다만, operator 중 DoesNotExist , NotIn 등으로 동일한 효과를 만들 수 있다.
- requiredDuringSchedulingIgnoredDuringExecution
- 규칙이 만족되지 않으면 스케줄러가 파드를 스케줄링할 수 없다.
- 이 기능은 nodeSelector와 유사하지만, 좀 더 표현적인 문법을 제공한다.
- preferredDuringSchedulingIgnoredDuringExecution
- 스케줄러는 조건을 만족하는 노드를 찾으려고 노력한다.
- 해당되는 노드가 없더라도, 스케줄러는 여전히 파드를 스케줄링한다.
- 가중치(weight)는 1~100 범위를 명시할 수 있고, 합계가 가장 높은 Node를 먼저 선택한다.
# Node의 Label 속성 중 zone이 (antarctica-east1, antarctica-west1)에 속하고
# 그 Node 중에서 disktype이 ssd인 node를 우선하여 선택한다.
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
Pod 간의 관계 조건(PodAffinity & PodAntiAffinity)
- Pod 간의 Label을 기준으로 Node에 스케줄링할 조건을 지정하는 방법
- Affinity와 결합하여 기존에 이미 배치된 Pod를 기준으로 다른 Pod가 특정 Node 배치되도록 스케줄링될 Node를 제한할 수 있다.
- 이 때는 Node들마다 존재하는 일관된 Label이 존재해야 한다.
- Pod가 같은 Node에 배치하는 예제(가까운 곳에서 통신하려는 경우)
- preferredDuringSchedulingIgnoredDuringExecution 이용
- security=S1 라벨을 가진 Pod들과 같은 존(topology.kubernetes.io/zone)에 위치하도록 필수로 스케줄링
- security=S2 라벨을 가진 Pod들과 다른 존에 위치하도록 선호 스케줄링(S1에 되도록 가깝게 스케줄링)
# [security=S1 레이블을 가진 Pod]를 배치중인 Node 중에서
# [해당 Node와 동일한 zone에 있는 Node]에 Pod를 스케줄링한다.(preferred)
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
- 같은 Node에 여러 Pod가 존재하지 못하도록 하는 경우
- requiredDuringSchedulingIgnoredDuringExecution 이용
- app=web-server 레이블을 가진 Pod들 간에는 Node를 반드시 다르게 스케줄링
- 동시에 app=cache 레이블을 가진 Pod들과 같은 호스트 노드에 스케줄링
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
selector:
matchLabels:
app: web-server
replicas: 3
template:
metadata:
labels:
app: web-server
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-server
topologyKey: "kubernetes.io/hostname"
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: "kubernetes.io/hostname"
containers:
- name: web-app
image: nginx:1.16-alpine
Node가 기피하는 조건 (Taint & Toleration)
Affinity는 Pod가 Node를 선택할 수 있도록 한다.
반면, Taint는 Node가 Pod를 선택할 수 있도록 하는 정책이다.
Taint라는 개념이 낯설 수도 있지만, Cluster 생성 초기부터 이미 적용되어 있다.
바로, Master Node(Control-Plane)에 기본적으로 적용된 Taint인 node-role.kubernetes.io/control-plane:NoSchedule 이다.
Taint를 사용하는 예시를 조금 더 자세하게 알아보자.
Taint는 key=value 형태로 Node에 적용하는 옵션으로 아래와 같이 부여할 수 있다.
# Taint 부여
kubectl taint nodes node1 key1=value1:NoSchedule
# Taint 제거
kubectl taint nodes node1 key1=value1:NoSchedule-
Taint에는 주로 사용되는 옵션이 3가지가 있다.
- NoSchedule : 무시되지 않은 경우, 해당 노드에 파드를 스케줄하지 않는다.
- PreferNoSchedule : 무시되지 않은 경우, NoSchedule 가 없어도 해당 노드에 파드를 스케줄하지 않으려고 시도한다.
- NoExecute : 무시되지 않은 경우, 파드가 노드에서 축출되고(노드에서 이미 실행 중인 경우), 노드에서 스케줄되지 않는다(아직 실행되지 않은 경우).
이 외에도 Node Controller에서는 Node 상태에 따라 자동적으로 아래 Taint를 설정한다.
- node.kubernetes.io/not-ready: 노드가 준비되지 않았다. 이는 NodeCondition Ready 가 "False"로 됨에 해당한다.
- node.kubernetes.io/unreachable: 노드가 노드 컨트롤러에서 도달할 수 없다. 이는 NodeCondition Ready 가 "Unknown"로 됨에 해당한다.
- node.kubernetes.io/memory-pressure: 노드에 메모리 할당 압박이 있다.
- node.kubernetes.io/disk-pressure: 노드에 디스크 할당 압박이 있다.
- node.kubernetes.io/pid-pressure: 노드에 PID 할당 압박이 있다.
- node.kubernetes.io/network-unavailable: 노드의 네트워크를 사용할 수 없다.
- node.kubernetes.io/unschedulable: 노드를 스케줄할 수 없다.
- node.cloudprovider.kubernetes.io/uninitialized: "외부" 클라우드 공급자로 kubelet을 시작하면, 이 테인트가 노드에서 사용 불가능으로 표시되도록 설정된다. 클라우드-컨트롤러-관리자의 컨트롤러가 이 노드를 초기화하면, kubelet이 이 테인트를 제거한다.
Toleration은 이러한 Taint를 통과하여 Pod를 배치할 수 있는 방법이다.
Toleration도 Pod에 적용하는 옵션으로 역시 key와 value를 활용한다.
약간의 차이점이라고 하면 operator 동작을 존재하는지(exist), 같은지(equal) 등으로 설정할 수 있다. (기본 값은 equal이다.)
추가적으로 tolerationSeconds 옵션을 통해 지정된 시간만큼 Node에 존재하도록 허용할 수도 있다. 이 시간이 지나면 축출되며 기본 값은 300초이다.
# example-key에 NoSchedule이 있는 경우에도 배치할 수 있도록 Toleration 설정
# 300초가 지나면 허용하지 않으므로 축출됨
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: "example-key"
operator: "Exists"
effect: "NoSchedule"
tolerationSeconds: 300
이러한 Toleration에는 특별한 사용 예시가 2가지 있다.
# 만약, effect 값이 없다면 해당 key가 존재하기만 해도 통과시킨다.
tolerations:
- key: "example-key"
operator: "Exists"
# 만약, key 값이 없고, exist 동작을 가진 Toleration이라면,
# 해당 하는 모든 effect를 통과한다.
tolerations:
- operator: "Exists"
effect: "NoSchedule"
이렇게 Taint와 Toleration을 이용하면 Affnity 외에도 더 자세한 Node의 상황을 묘사할 수 있다.
마무리
지금까지 Pod가 Node에 배치될 수 있도록 하는 스케줄링 전략에 대해 알아보았다.
- 전략적으로 사용해야 하는 GPU와 같은 자원을 소모하거나,
- 고가용성을 위해 균일하게 배치하거나,
- Cache 서버를 특정 Pod와 동일 Node에 배치하는 등 다양한 전략이 가능하다.
스케줄링 전략이 중요한 이유는 한 번 배치된 Pod는 다시 배치되지 않기 때문이다.
한편, 이미 불균형하게 배포된 Pod를 적절하게 재배치하기 위해서 Descheduler라는 프로젝트도 등장하였다.
Kubernetes SIGs 공식 프로젝트로 자원 활용도나, Pod 수 균형 등 다양한 조건을 기준으로
재배치할 수 있는 좋은 프로젝트이다. (한번 찾아보는 것을 권장한다!)
참고자료
테인트(Taints)와 톨러레이션(Tolerations) | Kubernetes
'DevOps' 카테고리의 다른 글
[다.인.서] Dive into Service Mesh | Cloud Native와 Service Mesh (0) | 2024.05.12 |
---|---|
[쿠.짤] 쿠버네티스 짤팁 | 컨테이너는 결합할 수 있다(Init, Sidecar, Ambassador) (0) | 2024.01.23 |
[쿠.짤] 쿠버네티스 짤팁 | 정상 상태와 수명 주기를 이해하자! (Probe와 SIGKILL) (0) | 2024.01.06 |
[쿠.짤] 쿠버네티스 짤팁 | KinD로 Local Kubernetes 구축하기 (0) | 2024.01.03 |
[쿠.짤] 쿠버네티스 짤팁 | 자원 요구사항을 정의하자! (QoS, Priority Class, Resource Quota, LimitRange) (0) | 2023.12.22 |