CKA 대비 kubernetes 스터디 - 2. Scheduler
Manual Scheduling
만약 k8s에서 해주는 scheduling 대신 직접 스케줄링을 진행하고 싶다면?
- 모든 pod에는 선택옵션으로 nodeName 필드를 추가할 수 있다.
- 보통은 사용자가 입력하는 게 아니고 k8s manifest를 적용하는 과정에서 자동으로 부여되는 값.
- Scheduler는 pod의 nodeName 필드를 탐색하면서, 해당 필드가 정의되지 않은 pod를 찾아낸다. 이 Pod들이 scheduling 대상이 됨.
- 선별된 pod에 scheduling 알고리즘을 돌려서, 해당 pod가 실행될 nodeName을 결정한다.
pod에 스케줄링이 되지 않으면, pod는 pending 상태를 유지한다.
스케줄링 알고리즘을 사용하지 않는 상태에서 가장 쉬운 pod 스케줄 방법은 nodeName 필드를 직접 설정하는 것.
이미 생성된 pod에 스케줄링을 적용하고 싶다면,
- pod-bind-definition.yaml 처럼 pod과 node를 연결할 수 있는 오브젝트를 정의한 뒤
- binding API에 post Request를 보낸다. 이 때, yaml파일은 json 형식으로 변환해서 post Request의 body에 담아야 한다.
Labels & Selectors
- labels : 특정한 것들을 그룹핑하기 위한 표준 (보통 criteria 기준)
- selectors : label 기준으로 선택하기 위한 입력값.
생성 방법
적용 방법
kubectl get pods --selector app=App1
-> k8s 내부적으로도 labels & selectors를 활용한다.
ReplicaSet의 경우
- spec.template.metadata.labels 필드 - replicaset이 생성하고 관리하는 pod에 붙은 label
- metadata.labels 필드 - replicaset 객체 자체에 붙는 label. 외부에서 replicaset을 찾아야 할 때 사용하는 필드.
- selector.matchLabels 필드 - replicaset 객체가 관리해야 하는 pod를 select하기 위한 필드.
- replicaSet을 생성할 때 pod의 label과 replicaset의 selector가 일치해야 정상적으로 객체가 생성됨.
Annotations
정보 기록을 위해 사용하는 optional 필드.
Taints & Tolerations
scheduling 과정에서 restriction을 추가하는 기능.
기본적으로는 세 개의 노드에, 네 개의 pod 중 어떤 것이든 할당될 수 있다.
- 만약 node 1에 Taint=blue 라고 설정을 해두면, unwanted pod은 해당 노드에 할당될 수 없다.
- node 1에 할당되어야 하는 pod에게는 toleration을 설정한다. 예컨대 pod D에 toleration을 설정하면 된다.
Taints는 Node에 설정하는 것이고, Tolerant는 Pod에 설정하는 것이다.
- pod가 특정 Node에는 접근하지 못하도록 조건을 걸어두는 개념임.
taint를 노드에 설정했을 때, taint 조건에 부합하지 않는 pod를 어떻게 할 것인지 설정하는 세 가지 옵션이 있다.
- NoSchedule : 절대 해당 노드에 pod를 추가하지 않는다.
- preferNoSchedule : 스케줄링에서 가급적이면 제외하지만, 100% 제외를 보장하지는 않는다.
- NoExecute : 조건에 부합하지 않는 pod은 전부 받지 않음 + 기존에 노드에서 돌고 있던 pod에도 조건을 적용해서, taint에 안 맞는 pod는 전부 노드에서 제외한다.
pod의 경우 kubectl taint nodes node1 app=myapp:NoSchedule
는 아래의 yaml 파일과 동일하다.
- yaml파일에서 이 필드를 적용하려면 반드시 Double Quote를 사용해야 한다.
- effect 항목에 정의한 값에 따라 신규 생성된 / 기존에 만들어져 있던 pod의 스케줄링이 결정된다.
... tolerations: - key: "app" operator: "Equal" value: "myapp" effect: "NoSchedule"
예시: NoExecute
Pod D에 NoExecute 옵션을 붙여서 toleration을 설정하면,
기존에 노드에서 돌고 있던 pod C는 toleration 조건을 만족하지 못할 경우 killed된다.
선술했듯, taint와 toleration 자체의 목적은 "pod가 특정 Node에는 접근하지 못하도록 조건을 걸어두는 개념" 이다.
- 따라서 node에 taint를 설정하고 pod에 toleration을 설정했다고 해서,
반드시 toleration을 설정한 pod이 taint 걸려 있는 노드에 매핑되는 것은 아니다. - node에 'toleration이 있는 pod만 수용하도록' 조건을 걸어둔 개념이기 때문.
- 만약 특정 노드와 특정 pod를 매핑하고 싶다면 NodeAffinity 개념을 사용해야 한다.
마스터 노드의 경우, 클러스터에서 처음 생성될 때 이미 "어느 pod도 master node에서는 실행되지 않도록" taint를 걸어두었다.
- 물론 마스터 노드에 toleration을 가진 pod를 생성할 수는 있지만, best practice는 아니다.
Node Selectors
- pod 3개 중 큰 pod은 node2나 3에서는 실행될 수 없는 스펙이다.
- 디폴트 스케줄링 알고리즘은 랜덤. 따라서 랜덤한 방식으로 적용하는 건 효율적이지 않다.
pod 자체에 limitation을 적용해서, 스펙을 만족하는 노드에만 해당 pod를 할당하도록 결정할 수 있다.
)
pod의 nodeSelector에서 label을 사용해 특정 node를 정의하면 된다.
- nodeSelector의 key-value는 node에서 정의해야 하며, pod를 생성하기 전에 node에 세팅되어 있어야 한다.
한계점
- key-value 조합만으로는 할 수 없는, 복잡한 조건이 필요한 경우 적용이 어려움
Node Affinity
두 개의 yaml파일은 정확히 같은 동작을 수행하지만, node affinity를 사용하면 보다 디테일하게 pod에 제약을 걸 수 있다.
- operator In : value에 해당하는 값이 label에 포함되기만 하면 된다. 예시의 경우 key라는 label값에 Large라는 문자열이 있기만 하면 됨.
- or 형태로 label 체크를 하고 싶다면 values를 리스트 형태로 넘겨주면 된다.
...
- key: size
operator: In
values:
- Large
- Medium
- NotIn : 없는지 체크하는 것 == Exists
자세한 내용은 docs에 소개되어 있다.
위와 같은 옵션은 pod가 생성되는 시점에 적용되고, node에 할당될 때 조건으로 동작함.
그런데 만약
- pod 할당을 하는 시점에서 '조건을 만족하는 node'가 없다면?
- 또는 어느 시점에 node 라벨이 갑자기 바뀌어서, 더 이상 조건절에 부합하지 않는다면?
Node Affinity의 type으로 위와 같은 상황을 대비하도록 설정할 수 있다.
- pod의 lifeCycle 시점에 적용되는 로직.
- 현재 Available 타입으로는 두 가지 옵션이 있고, Planned 타입의 옵션은 하나.
- DuringScheduling : pod가 처음 생성될 때
- required: 해당 node는 조건을 반드시 만족해야 함. 만족하는 노드가 없다면 pod는 스케줄 대상이 되지 않음.
- preferred : 어쨌든 pod 자체가 뜨는 게 우선이므로, 만족하는 노드가 없어도 일단 pod를 노드에 스케줄링한다.
- DuringExecution : pod는 돌고 있는 상황에서 node 정보에 변경이 있을 경우. label 값이 변경된다던가
- Ignored : 일단 스케줄된 pod는 새로운 조건을 무시한다.
- Required : 조건을 만족하지 않게 되면 evicted. pod를 중지하고 다른 노드에 스케줄링한다.
Taints / Toleration, NodeAffinity
Taint and Toleration
- taint로 노드에 제약조건을 걸고, 조건에 맞지 않는 pod가 스케줄링되지 않도록 한다.
- toleration으로 pod에 조건을 걸어서, taint가 걸려 있는 pod에도 스케줄링이 가능하도록 한다.
단, 스케줄링 자체는 랜덤이므로, taint와 toleration이 완벽한 매칭 스케줄링을 보장하는 건 아니다.
- 위 예시처럼, red pod가 할당될 수 있는 노드는 두 개다.
- red node
- taint를 걸어두지 않은 일반 노드
Node affinity
- 노드에 label을 생성하고 pod에 조건을 걸어서, 특정 조건을 만족하거나 만족하지 않는 노드에만 스케줄링되도록 조정한다.
- 단, 아무런 조건을 걸지 않은 pod의 경우 label 여부에 관계없이 스케줄링될 수 있다.
- 예시의 경우 red node에 아무런 조건 없는 일반 pod가 할당될 수 있음.
따라서 두 개를 혼용해 사용해야 node - pod를 완벽히 통제할 수 있는 형태로 스케줄링이 가능하다.
Resource Limitation / Limits
노드 세 개가 있는 k8s 클러스터를 가정하자.
- cpu 2, 1 memory, disk space가 있다
- pod는 생성될 때, 필요로 하는 리소스만큼의 여유분을 보장받은 채 노드에 할당된다.
- kubelet의 스케줄러는 리소스 여유분이 있는 노드에 pod을 할당한다.
어느 노드에도 pod을 정상적으로 할당할 수 없을 경우... pod는 노드에서 실행되지 못하고 pending 상태를 유지한다.
Pod의 리소스 request에서 디폴트 설정은 0.5 CPU, 256MB.
- cpu 0.1 = 100m (단위 m은 '밀리'). 설정 가능한 최솟값은 1m.
default limit request 값을 사용하도록 설정하려면 아래와 같이 LimitRange 객체를 k8s에 생성하고 등록해야 한다.
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
defaultRequest:
memory: 256Mi
type: Container
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-limit-range
spec:
limits:
- default:
cpu: 1
defaultRequest:
cpu: 0.5
type: Container
Memory의 경우 Mi와 M 값의 차이를 알아야 함. 서로 다른 단위다.
- docker의 경우 container가 사용할 수 있는 리소스 제한을 두지 않는다.
- 반면 k8s pod의 경우, 사용할 수 있는 리소스 제한의 디폴트 값이 정해져 있다.
- cpu : 1 vCpu
- Mem : 512MB
- 리소스 관련 설정은 pod 내부에 정의한 컨테이너별로 설정할 수 있다. request 요청값이나 limit값을 전부 개별로 설정 가능함.
만약 리소스 사용량이 Limit을 초과할 경우?
- cpu limit 초과 시: 쓰로틀링 가동해서 cpu 사용량이 limit을 넘지 않도록 통제
- memory limit 초과 시: 사용랑 초과가 지속될 경우 OOMKilled. pod을 제거해 버린다.
DaemonSets
- ReplicaSet과 유사함: deploy multiple instances of pod.
- 차이점: 단 하나의 copy of your pod만 클러스터에서 동작한다.
- 노드가 추가될 때마다 자동으로 DaemonSet pod 한 개가 실행되며, 노드가 삭제되면 자동으로 pod도 사라진다.
DaemonSet: 최소한 한 개의 pod가 모든 노드에서 실행되도록 보장하는 형태의 k8s Object
로그 모니터링을 위한 pod같은 경우가 가장 정확한 usecase.
- kube-proxy가 DaemonSet의 예시 중 하나다. 모든 노드에서 반드시 실행되며, pod 간 통신을 담당하고 있음.
- 네트워킹을 담당하는 solution 중 하나인 Weave-net도 비슷한 예시.
생성 방법은 ReplicaSet과 동일하다. Kind만 DaemonSet으로 설정하면 됨.
DaemonSet의 동작 원리?
- 모든 노드에 반드시 하나의 pod는 동작하도록 스케줄링?
- k8s 1.12 이전까지는 각 pod당 nodeName을 지정하고, 해당 노드에 스케줄링되는 식.
- 1.12 이상부터는 NodeAffinity + default scheduler로 처리하고 있다.
Static Pod
Worker node를 관리하는 kubelet이 할 수 있는 작업은 Pod 생성과 관리.
- 보통 kubelet은 master node의 kube-api로부터 값을 입력받아 pod을 생성해왔다.
- 만약 master node가 없고, Kube-api도 없는 상황이라면?
- 개별 컨테이너(호스트)의 특정 경로, 예컨대 /etc/kubernetes/manifest 경로에
- pod을 정의한 yaml파일을 생성하면, kubelet이 해당 정보를 토대로 pod를 직접 생성하고 관리할 수 있다.
- 이 pod는 kubelet의 관리 하에서만 만들어진다. kube-api나 다른 worker node와는 관계 없음.
- 단, pod만 생성 가능하다. replicaset이나 deployment 같은 오브젝트는 생성 불가능.
- 얘네는 control plane을 거쳐서 만들어져야 하는 k8s 오브젝트이기 때문.
static pod를 생성할 수 있도록 하는 호스트 경로는 kubelet service를 실행할 때 옵션값으로 정의할 수 있다.
--config 옵션으로 yaml파일을 옵션으로 줄 수도 있다.
yaml파일 형태는
staticPodPath: /etc/kubernetes/manifest
kubelet에서 직접 생성한 static pod는 docker ps
로 확인할 수 있다.
- kubelet 외에는 아무런 객체가 없기 때문에 kubectl utils를 사용할 수 없음. (kubectl은 kube-apiserver를 사용한다.)
물론, master node가 있는 상태에서도 static pod를 생성할 수 있다.
- 이 경우 static pod의 존재 자체는 kubectl에서 조회할 수 있다.
- 단, 조회가 되는 pod은 kubectl에서는 read only mirror다. 따라서 수정, 삭제 등은 불가능하다.
- 삭제하려면 kubelet의 manifest 폴더에서 직접 yaml파일을 지워야 한다.
확인해보면 실제로 pod가 할당된 노드가 정확히 특정되어 있는 걸 볼 수 있다. 위 예시의 경우 node 1.
Static Pod을 사용하는 이유?
- 노드control plane에 독립적으로 동작하면서, pod를 kubelet이 yaml파일을 토대로 직접 관리한다.
- 따라서 k8s master node에서 controller-manager, apiserver, etcd 등 컴포넌트를 구성하기 위한 yaml파일을 static pod으로 사용한다.
- manifests 경로에서 직접 파일을 삭제하지 않는 이상, 해당 컴포넌트는 master node에 pod형태로 반드시 존재한다.
DaemonSet과 Static Pod 비교하기
- 공통점: 둘 다 scheduler 없이 노드에 설치된다.
- 차이점
- static pod는 노드 내의 control plane 컴포넌트 설치에 주로 이용된다. kubelet이 직접 설치한다.
- DaemonSet은 monitoring / logging agent 설치 목적으로 사용한다. kube-apiserver를 통해서 설치할 수 있다. (DaemonSet Controller로 설치됨.)
static pod를 생성하기 위한 definition file을 찾고, 생성하는 방법을 알아둬야 한다.
- view / edit this options... 두 가지 방법 다 알아둬야 함.
multiple Schedulers
스케줄링 알고리즘은 기본적으로는 랜덤하게 + 조건(taint / toleration, nodeAffinity 등) 기반으로 노드에 pod 할당.
- 필요하다면 customized scheduler를 생성하고 배포해서 사용할 수 있다.
- 특정 애플리케이션 (pod)에는 특정 scheduler를 사용해서 노드에 배포하도록 pod 생성단계에서 설정할 수 있다.
scheduler 바이너리 파일을 가져와서, scheduler-name 필드만 변경한 채 k8s에 배포해보는 예시.
- scheduler name 필드가 pod 생성 시 스케줄러를 선택하는 key값이 된다.
kubeAdm으로 배포하는 방법
- command에 --scheduler-name 필드가 스케줄러 이름을 지정하는 필드.
- --leader-elect: 여러 개의 스케줄러가 master node에 있다고 할 때, 어떤 스케줄러가 실행될 것인지 결정하는 필드 (스케줄러는 여러 개 배포할 수 있지만, 마스터 노드에서는 한 개만 실행 가능하다.)
- 마스터 노드가 하나만 있는 상황에서 스케줄러가 여러 개라면, 하나를 뺀 나머지는 false 옵션을 줘야 한다.
- HA를 위해 multiple master node 를 세팅할 경우 --lock-object-name=<스케줄러 이름> 을 붙여줘야 한다.
- 이 옵션은 마스터 노드가 스케줄러를 선택할 때, 여러 개의 스케줄러를 구분하기 위한 목적으로 사용함.
정상적으로 배포가 완료되었다면 scheduler 두 개가 kube-system Namespace에 떠 있는 걸 확인할 수 있다.
pod 생성 시 schedulerName 필드에 scheduler pod 이름을 입력하면, 해당 스케줄러를 사용해서 배포할 수 있다.
- 만약 scheduler에 오류가 있을 경우 배포 요청한 pod는 pending 상태가 됨.
scheduler 이벤트 / 로그 확인하기.
Reference