Manual Scheduling


apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 8080
  nodeName:

Pod 를 생성하면 위 처럼 nodeName 이 빈 칸인 상태가 K8s memory 에 저장된다. kube-scheduler 는 생성된 Pod 중 scheduling 대상이 될 Pod 들을 nodeName 필드가 빈 칸인 것을 통해 찾아낸다. 이후 scheduling algorithm 으로 Pod 를 배치할 적절한 Node 를 찾아낸 후 Pod 를 배치한 뒤 nodeName 에 Node 의 이름을 추가한다.

apiVersion: v1
kind: Binding
metadata:
  name: nginx
target:
  apiVersion: v1
  kind: Node
  name: node02
curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind":"Binding", ...}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/

만약 kube-scheduler 가 실행 중이지 않다면 Pod 를 생성해도 Pending 상태로 Pod 가 작동하지 않는다. 때문에 수동으로 Pod 를 scheduling 하고 싶은 경우 직접 nodeName 을 지정해준 상태로 Pod 를 생성하거나, Pod 가 이미 생성된 경우 위 처럼 Binding 을 만들고 curl 요청을 통해 동적으로 Pod 를 배치할 수도 있다.

Labels and Selectors


 apiVersion: v1
 kind: Pod
 metadata:
   name: simple-webapp
   labels:
     app: App1
     function: Front-end
 spec:
   containers:
   - name: simple-webapp
     image: simple-webapp
     ports:
     - containerPort: 8080

Label 은 여러 K8s Object 중 관련된 Object 들만 찾아내기 위해 사용자가 지정한 일종의 별명과 같다. 위와 같은 Pod 를 생성하였을 경우,

kubectl get pods --selector app=App1

Selector 옵션을 통해 Label 값이 일치하는 Pod 만 찾아낼 수 있다.

 apiVersion: apps/v1
 kind: ReplicaSet
 metadata:
   name: simple-webapp
   labels:
     app: App1
     function: Front-end
 spec:
   replicas: 3
   selector:
     matchLabels:
       app: App1
   template:
     metadata:
       labels:
         app: App1
         function: Front-end
     spec:
       containers:
       - name: simple-webapp
         image: simple-webapp

위와 같은 ReplicaSet 을 생성할 때 metadata.labels 는 해당 ReplicaSet 에 대한 Label 이고, spec.selector.matchLabels 는 ReplicaSet 이 제어할 Pod 을 특정하기 위한 Label 이고, spec.template.metadata.labels 가 ReplicaSet 에 의해 생성될 Pod 의 Label 이다.

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: App1
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376 

Service 도 마찬가지로 spec.selector 를 통해 연결할 Pod 를 특정할 수 있다.

kubectl label nodes node01 color=blue

위 명령어를 통해 Label 을 달아줄 수 있다.

Annotations


apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: simple-webapp
  labels:
    app: App1
    function: Front-end
  annotations:
    buildversion: 1.34
spec:
  replicas: 3
  selector:
    matchLabels:
      app: App1
template:
  metadata:
    labels:
      app: App1
      function: Front-end
  spec:
    containers:
    - name: simple-webapp
      image: simple-webapp

Label 은 Selector 를 통해 Object 를 특정하기 위해 쓰이는 반면, Annotation 은 다른 일반적인 정보를 기록하기 위해 쓰인다.

Taints and Tolerations


kubectl taint nodes {node-name} {key}={value}:{taint-effect}
# Taint 설정
kubectl taint nodes node1 app=blue:NoSchedule
# Taint 제거
kubectl taint nodes node1 app=blue:NoSchedule-

Taint 는 Pod 가 특정 Node 에 배치되는 것을 방지하기 위해 사용할 수 있는 설정이다. 위와 같은 명령어를 통해 특정 Node 를 Taint 시킬 수 있다. taint-effect 는 총 3가지로 아래와 같다.

  • NoSchedule: Tolerant 하지 않은 Pod 는 schedule 되지 않는다. 이미 배치된 Intolerant 한 Pod 는 그대로 실행된다.
  • PreferNoSchedule: 웬만하면 schedule 되지 않지만 보장되진 않는다.
  • NoExecute: Tolerant 하지 않은 Pod 가 제거된다.
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  containers:
  - name: nginx-container
    image: nginx
  tolerations:
  - key: "app"
    operator: "Equal"
    value: "blue"
    effect: "NoSchedule"

Pod 에 Tolerantion 을 부여하고 싶은 경우, spec.tolerations 에 Taint 와 같은 설정을 지정해주면 된다. kube-scheduler 는 기본적으로 Taint 와 Toleration 을 고려하며 Pod 를 배치하는데, 만약 Pod definition 에 spec.nodeName 필드가 명시되어 있다면 NoSchedule 이어도 scheduling 을 건너뛰고 Pod 가 배치된다. NoExecute 의 경우라면 Pod 가 배치되더라도 제거될 수 있다.

kubectl describe node kubemaster | grep Taint

Master Node 도 사실은 Taint 되어 있어서 Core Component 외 Object 가 배치되지 않는 것이다.

Node Selectors


apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  containers:
  - name: data-processor
    image: data-processor
  nodeSelector:
    size: Large

상대적으로 많은 하드웨어 리소스를 요구하는 컨테이너를 적절한 Node 에서 실행하기 위해서 spec.nodeSelector 설정을 활용할 수 있다. Node Selector 역시 label 을 활용해 대상이 될 Node 를 특정할 수 있다.

kubectl label nodes {node-name} {label-key}={label-value}
kubectl label nodes node-1 size=Large

위와 같은 커맨드로 특정 Node 에 label 을 달아주고 Pod definition file 에서 해당 label 을 가진 Node 를 지정해준 뒤 생성하면 원하는 Node 에 Pod 를 실행할 수 있다.

다만 spec.nodeSelector 설정은 단순히 label 만 확인하기 때문에 더 복잡한 조건으로 Node 를 설정하고 싶을 때 Node Affinity 를 활용할 수 있다.

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
            - Medium

위와 같이 Node Affinity 를 사용하면 size In Large, size NotIn Small, size Exists 등 더 복잡한 조건문을 통해 Pod 가 배치될 Node 를 지정할 수 있다. Pod 가 배치될 적절한 Node 를 찾기 위해 requiredDuringSchedulingIgnoredDuringExecution 과 같은 설정값을 줄 수 있는데,

  • Available
    • requiredDuringSchedulingIgnoredDuringExecution
    • preferredDuringSchedulingIgnoredDuringExecution
  • Planned
    • requiredDuringSchedulingRequiredDuringExecution
    • preferredDuringSchedulingRequiredDuringExecution 현재 2가지가 제공되고 추후 2가지가 더 추가될 예정이다. 해당 설정은 이름 그대로 작동하는데, DuringScheduling 은 Pod 가 Schedule 되는 시점에 Node 에 알맞는 label 이 있는지 확인하는 것이고 DuringExecution 은 Node 가 Pod 를 호스팅하는 중 label 이 변경되었을 시 처리를 지정하는 것이다. 예를 들어, preferredDuringSchedulingIgnoredDuringExecution 은 웬만하면 Node label 이 설정된 곳에 Pod 를 배치하고 싶다는 것이고, 배치된 이후 Node label 이 변경되더라도 그대로 호스팅해달라는 의미다.

Taints and Tolerations vs Node Affinity


Taints and Tolerations 기능과 Node Affinity 를 적절히 조합하여 사용하면 원하는 Pod 를 원하는 Node 에 배치시키는 것을 보장할 수 있다. 위 그림처럼 Node 를 Taint 시켜 원하는 Pod 외의 Pod 가 배치되는 것을 막고, Node Affinity 를 통해 Pod 가 특정 Node 에 배치되게끔 설정할 수 있다.

Resource Requirements and Limits


apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Pod 가 Node 에 배치되면 Node 의 리소스를 사용하게 되는데 위처럼 리소스 사용량을 지정해줄 수 있다. Request 와 Limit 은 말 그대로 Pod 가 필요한 최소 용량의 리소스와 최대로 점유할 수 있는 리소스를 의미한다. CPU 의 경우 Limit 을 절대 초과하지 않지만 메모리의 경우 어느정도 초과분을 사용할 수 있게 한다. 다만 너무 오랜 기간 Limit 을 초과하면 OOM 이 발생하며 Pod 가 제거될 수 있다.

LimitRange

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-resource-constraint
spec:
  limits:
  - default:        # 기본 Limit
      cpu: 500m
    defaultRequest: # 기본 Request
      cpu: 500m
    max:            # 최대 Limit
      cpu: "1"
    min:            # 최소 Request
      cpu: 100m
    type: Container

K8s Cluster 에 배치될 모든 Pod 에 대한 기본값을 설정해주고 싶은 경우 LimitRange Object 를 활용할 수 있다. LimitRange 는 namespace 내에서 적용되며 메모리도 동일하게 적용 가능하다. LimitRange Object 가 생성된 이후 적용되기 때문에 중간에 수정한다 하더라도 기존에 생성되어 있는 Pod 엔 적용되지 않고 새로 생성하는 Pod 에만 적용된다. 범위를 설정해주는 것이기 때문에 requests.cpu: 700m 짜리 Pod 를 생성하려고 하면 범위 외 요청이기에 Pod 생성이 제한된다.

ResourceQuota

apiVersion: v1
kind: ResourceQuota
metadata:
  name: my-resource-quota
spec:
  hard:
    requests.cpu: 4
    requests.memory: 4Gi
    limits.cpu: 10
    limits.memory: 10Gi

namespace 마다 리소스 사용량을 제한하고 싶을 경우 ResourceQuota 를 활용하여 namespace 전체에 리소스 요청 및 제한을 설정할 수 있다.

DaemonSets


apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: monitoring-daemon
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: monitoring-agent
  template:
    metadata:
      labels:
        app: monitoring-agent
    spec:
      containers:
      - name: monitoring-agent
        image: monitoring-agent

DaemonSet 은 모든 Node 에 1개의 Pod 를 배치하고 싶을 때 사용할 수 있는 Object 이다. 예를 들어 Datadog-Agent 와 같은 모니터링 에이전트 Pod 를 DaemonSet 을 통해 각 노드에 1개씩 배치할 수 있다. kube-proxy 역시 DaemonSet 으로 배포되어 있는 컴포넌트다.

K8s 는 DaemonSet 을 모든 Node 에 배포하기 위해 NodeAffinity 와 Default Scheduler 를 사용한다.

kubectl create -f daemon-set-definition.yaml
kubectl get daemonsets
kubectl describe daemonsets monitoring-daemon

위와 같은 커맨드로 DaemonSet 을 생성하고 조회할 수 있다.

Static Pods


Static Pod 은 kube-api-server 나 kube-scheduler 가 아닌 kubelet 이 생성하고 관리하는 Pod 다. kubelet 은 Node 의 /etc/kubernetes/manifests 디렉터리에 정의된 yaml 파일을 주기적으로 확인하고 해당 Node 에서 실행되도록 보장한다. kubeadm 이 Master Node 를 구성할 때 Static Pod 형식으로 controller-manager, kube-api-server, etcd 등을 생성한다. 때문에 kubectl get pods -n kube-system 으로 Pod 를 조회했을 때 Master Node 의 컴포넌트들이 Pod 로 실행되고 있는 것을 확인할 수 있는 것이다.

Priority Classes


root@controlplane ~ k get priorityclasses
NAME                      VALUE        GLOBAL-DEFAULT   AGE   PREEMPTIONPOLICY
system-cluster-critical   2000000000   false            24m   PreemptLowerPriority
system-node-critical      2000001000   false            24m   PreemptLowerPriority
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
description: "This priority class should be used for XYZ service pods only."
globalDefault: false
preemptionPolicy: PreemptLowerPriority
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 8080
    priorityClassName: high-priority

Pod 의 우선순위를 정해주는 방법이다. k8s components 와 같이 System 에 중요한 Pod 일 경우 2,000,000,000 ~ 1,000,000,000 범위를 할당하고 그 외 Pod 는 1,000,000,000 ~ -2,147,483,648 범위 내에서 우선순위를 할당할 수 있다. default Priority Value 는 0 이다.

k8s cluster 에 리소스가 부족한 경우 preemptionPolicy default 설정인 PreemptLowerPriority 로 인해 낮은 priority 를 가진 Pod 이 제거되고 그 자리를 차지하게 된다. 이를 방지하기 위해 never 옵션을 줄 수도 있다.

Multiple Schedulers


apiVersion: v1
kind: Pod
metadata:
  name: my-custom-scheduler
  namespace: kube-system
spec:
  containers:
  - command:
	- kube-scheduler
	- --address=127.0.0.1
	- --kubeconfig=/etc/kubernetes/scheduler.conf
	- --config=/etc/kubernetes/my-scheduler-config.yaml
    image: k8s.gcr.io/kube-scheduler-amd64:v1.11.3
    name: kube-scheduler
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: my-scheduler
leaderElection:
  leaderElect: true
  resourceNamespace: kube-system
  resourceName: lock-object-my-scheduler

K8s 는 Default Scheduler 외에도 여러개의 Scheduler 를 사용할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  schedulerName: my-custom-scheduler

특정 Scheduler 를 사용하여 Pod 를 배포하고 싶을 경우 spec.schedulerName 에 Scheduler 이름을 지정해주면 된다.

kubectl get events

위 명령어를 통해 어떤 Scheduler 가 Pod 를 배포했는지 확인도 가능하다.

Configuring Scheduler Profiles


apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: my-scheduler-1
  plugins:
    score:
      disabled:
      - name: TaintToleration
      enabled:
      - name: MyCustomPluginA
      - name: MyCustomPluginB
- schedulerName: my-scheduler-2
  plugins:
    preScore:
      disabled:
      - name: '*'
    score:
      disabled:
      - name: '*'
- schedulerName: my-scheduler-3

Scheduler 는 Scheduling Queue Filtering Scoring Binding 순으로 Pod 가 배포될 적절한 Node 를 선정해낸다.

  • Scheduling Queue: Pod definition file 에 지정된 우선순위를 기준으로 생성할 Pod 를 정렬한다.
  • Filtering: NodeName, NodeUnschedulable, TaintToleration, NodeAffinity 등을 기준으로 Pod 가 배치될 수 있는 Node 를 골라낸다.
  • Scoring: 필터링된 Node 들 중 Pod 가 배치된 후 남은 리소스 등 여러가지 사항들을 고려하여 Node 를 선택한다.
  • Binding: 선택된 Node 에 Pod 를 배치한다.

Admission Controllers


kubectl 로 kube-api-server 에 명령어를 보내면 Authentication 과 Authorization 을 거쳐 명령어를 실행하게 된다. Authorization 에서 RBAC 을 통해 Role 을 기반으로 K8s Object 를 조작하는 명령을 제한할 수 있는데 더 복잡한 제어가 필요한 경우 Admission Controller 를 통해 제어할 수 있다.

kubectl exec -it kube-apiserver-controlplane -n kube-system -- kube-apiserver -h | grep enable-admission-plugins

위 명령어를 실행해보면 어떤 플러그인들이 활성화되어 있는지 확인할 수 있다.

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
	- kube-apiserver
	- --authorization-mode=Node,RBAC
	- --advertise-address=172.17.0.107
	- --allow-privileged=true
	- --enable-bootstrap-token-auth=true
	- --enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
    image: k8s.gcr.io/kube-apiserver-amd64:v1.11.3
    name: kube-apiserver

특정 플러그인을 활성화하고 싶을 경우 enable-admission-plugins 옵션을 통해 활성화 할 수 있다.

Validating and Mutating Admission Controllers


Admission Controller 들은 크게 Mutating Controller 와 Validating Controller 로 나뉜다. 예를 들어 어떤 Object 에 지정한 Namespace 가 존재하지 않을 때 NamespaceAutoProvision 이 다른 Namespace 를 지정하고 NamespaceExists 가 Namespace 존재 여부를 확인한다.

References