50. Scheduling 개요

Kubernetes 스케줄링이란?

“새로 만들어진 Pod를 어느 Node에 배치할지 결정하는 과정”

기본적으로 kube-scheduler가 자동으로 처리. 하지만 실무에서는 세밀한 제어가 필요한 경우가 많음:

상황사용할 도구
”DB Pod는 SSD 노드에만”NodeSelector / NodeAffinity
”관리자 노드에는 일반 Pod 못 들어오게”Taints / Tolerations
”모든 노드에 로그 수집기 1개씩”DaemonSet
”컨트롤 플레인 컴포넌트 배포”Static Pod
”중요한 Pod가 우선 스케줄”Priority Class
”자체 스케줄링 로직”Custom Scheduler

스케줄링은 “어디에” 만 결정. 실제 생성은 해당 노드의 kubelet이 담당.


51. Manual Scheduling

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 8080
  nodeName: # 보통은 비어있고 scheduler가 알아서 채워넣음
  • binding object

[!] The nodeName must be set during pod creation. Once the pod is running, Kubernetes does not permit modifications to the nodeName field.

52. lab

# 포드 설정 강제 덮어 쓰기
kubectl replace --force -f nginx.yaml

54. Labels and Selectors

공식문서: Labels and Selectors

# 모든 리소스 조회
kubectl get all
 
# 헤더 없이 리소스 조회
kubectl get all --no-headers
 
# 검색 시 라벨 필터 + 출력 라인 세기
kubectl get pods --selector=env=~~~ --no-headers | wc -l 

57. Taints and Tolerations

  • taint: 임의의 pod가 할당되는 것을 방지하기 위해 node에 설정
  • tolerations: taint가 있는 node에 할당 가능하도록 pod에 toleration을 설정

Taint Effect 3종

Effect동작
NoScheduletoleration 없으면 스케줄 불가 (기존 Pod 유지)
PreferNoScheduletoleration 없으면 가능한 피함 (강제 X)
NoExecutetoleration 없으면 기존 Pod까지 추방

Taint imperative 치트 (시험 필수)

# ── 추가 ──────────────────────────────────────────────────
kubectl taint nodes node1 key=value:NoSchedule
kubectl taint nodes node1 dedicated=db:NoSchedule
kubectl taint nodes node1 critical=true:NoExecute
 
# ── 제거 (마지막에 '-' 붙임) ───────────────────────────────
kubectl taint nodes node1 key=value:NoSchedule-
kubectl taint nodes node1 key:NoSchedule-        # value 없어도 OK
kubectl taint nodes node1 key-                    # 모든 effect 제거
 
# ── 모든 노드에 일괄 적용 ──────────────────────────────────
kubectl taint nodes --all key=value:NoSchedule
 
# ── 특정 라벨의 노드에만 적용 ──────────────────────────────
kubectl taint nodes -l tier=prod key=value:NoSchedule
 
# ── master/control-plane taint 확인 ────────────────────────
# kubeadm으로 설치한 마스터는 자동으로 NoSchedule taint 부여됨
kubectl describe node master | grep Taints
# Taints: node-role.kubernetes.io/control-plane:NoSchedule
 
# ── master에 Pod 스케줄 허용 (단일 노드 테스트) ───────────
kubectl taint nodes master node-role.kubernetes.io/control-plane:NoSchedule-
  • tolerations 값은 쌍따옴표로 작성
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  containers:
    - name: nginx-container
      image: nginx
  tolerations:
    - key: "app"
      operator: "Equal"
      value: "blue"
      effect: "NoSchedule"
    # operator: "Exists" 사용 시 value 생략 가능
    # - key: "app"
    #   operator: "Exists"
    #   effect: "NoSchedule"

Toleration 확인

# 노드의 taint 확인
kubectl describe node node1 | grep -i taint
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.taints}{"\n"}{end}'
 
# Pod의 toleration 확인
kubectl describe pod mypod | grep -i toleration -A5
kubectl get pod mypod -o yaml | grep -A5 tolerations

60. NodeSelector

  • 부하가 큰 pod를 스펙이 낮은 node에 할당해버리는 경우 node가 다운되기 때문에 NodeSelctor를 이용해 이런일이 없도록 함

Label imperative 치트 (노드/파드 공통)

# ── 추가 ──────────────────────────────────────────────────
kubectl label nodes node1 size=Large
kubectl label nodes node1 tier=prod env=production    # 여러 개
kubectl label pods mypod app=web
 
# ── 제거 (key에 '-' 붙임) ──────────────────────────────────
kubectl label nodes node1 size-
kubectl label pod mypod app-
 
# ── 덮어쓰기 (기존 라벨이 있으면 --overwrite 필수) ────────
kubectl label nodes node1 size=XLarge --overwrite
kubectl label pod mypod app=webapp --overwrite
 
# ── 모든 노드/파드에 일괄 적용 ──────────────────────────────
kubectl label nodes --all zone=us-east
kubectl label pods --all env=test
 
# ── 특정 라벨 가진 것들에만 적용 ──────────────────────────
kubectl label nodes -l tier=prod critical=true
 
# ── 라벨 확인 ──────────────────────────────────────────────
kubectl get nodes --show-labels
kubectl get nodes -L size,tier         # 특정 라벨만 컬럼으로
kubectl get pods -l app=web            # 라벨로 필터
kubectl get pods -l 'env in (dev,stg)' # 고급 selector

NodeSelector 예시

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

61. NodeAffinity

NodeAffinity란?

“NodeSelector의 상위 호환 — 복잡한 조건과 선호도를 표현”

NodeSelector의 한계:

  • “size=Large 이거나 size=Medium” 같은 OR 조건 표현 불가
  • “있으면 좋지만 없어도 OK” 같은 선호 표현 불가
  • “size가 Small만 아니면 OK” 같은 부정 조건 불가

NodeAffinity는 이런 복잡한 조건을 2가지 강제 수준으로 지원:

타입의미
requiredDuringSchedulingIgnoredDuringExecution필수 — 조건 만족 노드 없으면 Pending
preferredDuringSchedulingIgnoredDuringExecution선호 — 있으면 좋지만 없어도 다른 노드에 배치

IgnoredDuringExecution의 의미: Pod가 이미 실행 중이면 노드 라벨이 바뀌어도 쫓아내지 않음 (스케줄 때만 검사).

  • NodeSelector 보다 복잡한 조건이 필요할 때 사용
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

65. Taints and Tolerations vs Node Affinity

  • Taints/Tolerations만 사용: 특정 pod가 toleration이 있어도 다른 노드에 갈 수 있음 (보장 X)
  • Node Affinity만 사용: 원하는 pod를 특정 노드에 배치 가능하지만, 다른 pod가 해당 노드에 들어오는 것을 막지 못함
  • 둘 다 사용해야 특정 pod만 특정 노드에 exclusive하게 배치 가능

핵심

Taints/Tolerations → 노드가 원하지 않는 pod를 밀어냄
Node Affinity → pod가 원하는 노드를 끌어당김
둘을 조합하면 전용 노드(dedicated node) 구성 가능

66. Resources amd Requirements

  • Requets: 최소 요구량
  • Limits: 제한

리소스 관련 imperative 치트

# ── 기존 Deployment의 리소스 변경 ─────────────────────────
kubectl set resources deploy/web --requests=cpu=100m,memory=256Mi
kubectl set resources deploy/web --limits=cpu=500m,memory=512Mi
kubectl set resources deploy/web --requests=cpu=100m --limits=cpu=500m
 
# ── ResourceQuota 생성 (imperative 가능!) ──────────────────
kubectl create quota my-quota \
  --hard=pods=10,cpu=4,memory=8Gi,requests.cpu=2,limits.cpu=4 \
  -n dev
 
# YAML 뽑기
kubectl create quota my-quota --hard=pods=10,cpu=4,memory=8Gi $do > quota.yaml
 
# ── 조회 ──────────────────────────────────────────────────
kubectl get resourcequota -n dev
kubectl describe resourcequota my-quota -n dev
# → Used / Hard 비교 표시

LimitRange (YAML 전용)

LimitRange는 imperative 생성 명령이 없음. 템플릿 복사해서 수정.

apiVersion: v1
kind: LimitRange
metadata:
  name: cpu-resource-constraint
spec:
  limits:
  - default: # this section defines default limits
      cpu: 500m
    defaultRequest: # this section defines default requests
      cpu: 500m
    max: #l imit
      cpu: "1"
    min: # request
      cpu: 100m
    type: Container

ResourceQuota 예시

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
spec:
  hard:
    requests.cpu: "1"
    requests.memory: "1Gi"
    limits.cpu: "2"
    limits.memory: "2Gi"
    requests.nvidia.com/gpu: 4
    pods: "10"
    persistentvolumeclaims: "5"
    services.loadbalancers: "2"
  • limit range가 적용된 후 새로 생성된 파드들에만 적용됨
  • ResourceQuota가 설정된 namespace에서는 Pod가 반드시 resources 필드를 가져야 함

67. DaemonSets

공식문서: DaemonSet

  • 모든 노드에 정확히 하나의 pod 복제본을 실행
  • 노드 추가 → 자동으로 pod 배포 / 노드 제거 → pod 자동 삭제

대표 사용 사례:

  • 모니터링 에이전트, 로그 수집기 (Fluentd, Filebeat 등)
  • kube-proxy (모든 워커 노드에 필요)
  • 네트워킹 솔루션 (weave-net, calico 등)

DaemonSet은 kubectl create imperative 명령이 없음 → YAML 필수

DaemonSet YAML 빠르게 만들기 (시험 치트)

# Deployment YAML을 뽑아서 kind만 DaemonSet으로 변경하는 패턴
kubectl create deploy monitoring --image=fluentd --dry-run=client -o yaml > ds.yaml
 
# 수정할 것:
# 1. kind: Deployment  →  kind: DaemonSet
# 2. spec.replicas 줄 삭제
# 3. spec.strategy 줄 삭제 (있으면)
# 4. metadata.creationTimestamp / status: {} 등 불필요 필드 제거
 
kubectl apply -f ds.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: monitoring-daemon
spec:
  selector:
    matchLabels:
      app: monitoring-agent
  template:
    metadata:
      labels:
        app: monitoring-agent
    spec:
      containers:
        - name: monitoring-agent
          image: monitoring-agent
kubectl get daemonsets
kubectl get ds                                # 약어
kubectl describe daemonset monitoring-daemon
kubectl get ds -A                             # 모든 namespace
 
# 특정 노드에만 배포 (nodeSelector 활용)
# spec.template.spec.nodeSelector 추가
  • v1.12 이전: nodeName 속성으로 직접 스케줄링
  • v1.12 이후: default scheduler + NodeAffinity 활용

68. Static Pods

공식문서: Create static Pods

  • kubelet이 kube-apiserver 없이 독립적으로 관리하는 pod
  • 지정된 디렉토리(/etc/kubernetes/manifests)에 YAML 파일을 두면 kubelet이 자동 생성
  • 파일 변경 → pod 재생성, 파일 삭제 → pod 삭제
  • Pod만 생성 가능 (ReplicaSet, Deployment 등 상위 리소스 불가)

설정 방법 1: kubelet 서비스 파일에 직접 지정

--pod-manifest-path=/etc/kubernetes/manifests

설정 방법 2: config 파일 사용

# kubelet config (kubeconfig.yaml)
staticPodPath: /etc/kubernetes/manifests

클러스터 내 동작:

  • kubelet이 static pod 생성 시 kube-apiserver에 mirror object (읽기 전용) 생성
  • kubectl get pods로 확인 가능하지만 API로 수정/삭제 불가
  • pod 이름에 노드명이 suffix로 붙음 (예: static-web-node01)

kubeadm 클러스터에서 컨트롤 플레인 컴포넌트가 Static Pod으로 실행됨

Static Pod 실습 (시험 빈출)

# ── 1. staticPodPath 찾기 ─────────────────────────────────
# kubelet config 경로 확인
ps -ef | grep kubelet | grep -o 'config=[^ ]*'
# 예: config=/var/lib/kubelet/config.yaml
 
grep -i staticPodPath /var/lib/kubelet/config.yaml
# staticPodPath: /etc/kubernetes/manifests
 
# ── 2. Static Pod 생성 (특정 노드에서) ────────────────────
# 해당 노드에 SSH 접속 후
sudo tee /etc/kubernetes/manifests/static-web.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: static-web
spec:
  containers:
  - name: web
    image: nginx
    ports:
    - containerPort: 80
EOF
 
# kubelet이 자동 감지하여 Pod 생성 (보통 20초 이내)
# 마스터에서 확인
kubectl get pods -A | grep static-web
# → static-web-node01   (노드명 suffix)
 
# ── 3. Static Pod 삭제 ────────────────────────────────────
# ❌ kubectl delete pod 는 안됨 (kubelet이 재생성)
# ✅ 파일 삭제로 제거
sudo rm /etc/kubernetes/manifests/static-web.yaml
 
# ── 4. imperative로 Static Pod YAML 생성 ──────────────────
# 일반 Pod처럼 dry-run으로 뽑아서 manifests/에 저장
kubectl run static-web --image=nginx --restart=Never --dry-run=client -o yaml \
  > /etc/kubernetes/manifests/static-web.yaml

Static Pod 식별 방법

# Mirror Pod의 owner가 Node면 Static Pod
kubectl get pod kube-apiserver-master -n kube-system -o yaml | grep -A3 ownerReferences
# kind: Node
 
# 이름에 노드명이 붙어있음
kubectl get pods -n kube-system | grep -E "kube-apiserver|etcd|kube-controller|kube-scheduler"
# → 모두 -master 같은 suffix 있음
구분Static PodsDaemonSets
생성 주체kubelet이 직접 관리DaemonSet controller (via API server)
Control Plane 필요XO
용도컨트롤 플레인 컴포넌트 배포모든 노드에 에이전트 배포
Scheduler 관여XX

75. Priority Classes

PriorityClass란?

“Pod들 중에 누가 더 중요한지 숫자로 표시 — 자원 부족 시 덜 중요한 Pod를 내쫓음”

왜 필요한가?

  • 클러스터 자원이 부족할 때 중요한 Pod(결제, 모니터링) 가 먼저 자리를 차지해야 함
  • 낮은 우선순위 Pod가 자원을 선점하고 있으면 Preemption(선점) 으로 공간 확보

동작 방식:

  1. 각 Pod에 숫자 부여 (value: 1000000)
  2. 자원 부족 시 스케줄러가 “이 Pod는 중요한데 자리가 없네” 감지
  3. 우선순위 낮은 Pod를 evict(제거) 하고 새 Pod 배치
  • Pod에 우선순위(숫자)를 부여하여 스케줄링 순서를 제어
  • 값이 높을수록 먼저 스케줄링
  • 사용자 범위: -2B ~ +1B / 시스템 예약: ~2B
kubectl get priorityclass
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000000
description: "미션 크리티컬 pod용"

Pod에 적용:

spec:
  priorityClassName: high-priority

Preemption (선점):

  • 기본 정책: PreemptLowerPriority → 낮은 우선순위 pod를 evict하고 자원 확보
  • preemptionPolicy: Never → 선점 없이 대기
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority-no-preempt
value: 1000000000
preemptionPolicy: Never
  • globalDefault: true 로 설정하면 priorityClassName 미지정 pod의 기본값이 됨 (하나만 가능)
  • 미지정 시 기본 우선순위는 0

78. Multiple Schedulers

  • 기본 스케줄러 외에 커스텀 스케줄러를 추가 배포 가능
  • 각 스케줄러는 고유한 이름 필요 (기본: default-scheduler)

스케줄러 설정 파일:

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: my-scheduler

Pod에서 커스텀 스케줄러 지정:

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

배포 방법:

  1. 별도 서비스로 실행 (kube-scheduler 바이너리 + 별도 config)
  2. Static Pod으로 배포
  3. Deployment로 배포 (ServiceAccount + RBAC + ConfigMap 필요)

확인:

# 어떤 스케줄러가 pod를 배치했는지 확인
kubectl get events -o wide
 
# 스케줄러 로그 확인
kubectl logs my-custom-scheduler -n kube-system
  • 잘못 구성된 경우 pod가 Pending 상태 유지

80. Configuring Scheduler Profiles

스케줄링 단계:

  1. Scheduling Queue - 우선순위 정렬 (PrioritySort 플러그인)
  2. Filter - 자원 부족 노드 제거 (NodeResourcesFit, NodeName, NodeUnschedulable 등)
  3. Score - 남은 노드 점수 매김 (NodeResourcesFit, ImageLocality 등)
  4. Bind - 최종 노드에 pod 바인딩 (DefaultBinder)

Extension Points: 각 단계에 플러그인을 추가/제거할 수 있음

다중 프로필 (v1.18+): 단일 바이너리에서 여러 스케줄러 프로필 실행 가능

apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
  - schedulerName: my-scheduler-2
    plugins:
      score:
        disabled:
          - name: TaintToleration
        enabled:
          - name: MyCustomPluginA
          - name: MyCustomPluginB
  - schedulerName: my-scheduler-3
    plugins:
      preScore:
        disabled:
          - name: '*'
      score:
        disabled:
          - name: '*'
  • 별도 바이너리 대신 하나의 프로세스에서 여러 프로필을 실행하면 race condition 방지

82 Admission Controllers

Admission Controller란?

“API Server가 요청을 ETCD에 저장하기 직전에, ‘이 요청을 허락할까? 뭔가 수정할까?’ 검토하는 관문”

왜 필요한가?

  • RBAC은 “누가 뭘 할 수 있나” 까지는 제어하지만 요청 내용 자체는 검사 안함
  • 예: RBAC으로 “Pod 생성 권한”을 줬다면 → 어떤 이미지를 쓰든, 루트 권한이든 다 통과

Admission Controller가 메꾸는 부분:

  • 특정 이미지 레지스트리에서 온 이미지만 허용
  • 루트 사용자(UID 0) Pod 생성 금지
  • 리소스 limits 없으면 자동으로 기본값 삽입
  • Secret/ConfigMap이 빠진 Pod 생성 거부
1. kubectl → API Server 요청
2. Authentication      (누구?)
3. Authorization       (할 수 있나?)
4. Admission Controllers (내용은 OK?)
5. Resource 생성 (ETCD 저장)
  • RBAC만으로는 세밀한 제어가 불가능한 경우 Admission Controller 사용
    • 예: 특정 이미지 레지스트리만 허용, 루트 권한 실행 금지, 특정 capability 제한 등

종류:

  • AlwaysPullImages: 항상 이미지를 pull
  • DefaultStorageClass: PVC에 StorageClass 미지정 시 기본값 자동 부여
  • EventRateLimit: API 서버 요청 속도 제한
  • NamespaceExists / NamespaceAutoProvision: 네임스페이스 존재 여부 검증 / 자동 생성

활성화된 Admission Controller 확인:

kube-apiserver -h | grep enable-admission-plugins
 
# kubeadm 환경
kubectl exec -n kube-system kube-apiserver-controlplane -- kube-apiserver -h | grep enable-admission-plugins

활성화/비활성화:

--enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
--disable-admission-plugins=DefaultStorageClass

85. Validating and Mutating Admission Controllers

두 가지 유형:

  • Mutating: 요청 객체를 변경할 수 있음 (예: DefaultStorageClass가 StorageClass 추가)
  • Validating: 요청을 검증하고 허용/거부 (예: NamespaceExists가 네임스페이스 존재 확인)

Mutating이 먼저 실행되고, 이후 Validating이 실행됨

외부 Admission Controller — Webhook:

  • MutatingAdmissionWebhook / ValidatingAdmissionWebhook
  • 외부 서버에 admission review 요청을 보내고 응답에 따라 허용/거부
  • 클러스터 내 Deployment 또는 외부 서비스로 구현 가능

Webhook 서버 구성 흐름:

  1. webhook 로직이 있는 서버 개발 (보통 /validate 또는 /mutate 엔드포인트)
  2. 클러스터에 Deployment + Service로 배포
  3. ValidatingWebhookConfiguration 또는 MutatingWebhookConfiguration 리소스 생성
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: "pod-policy.example.com"
webhooks:
  - name: "pod-policy.example.com"
    clientConfig:
      service:
        namespace: webhook-namespace
        name: webhook-service
      caBundle: <CA_BUNDLE>
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE"]
        resources: ["pods"]
    admissionReviewVersions: ["v1"]
    sideEffects: None