privileged
제한 없음.
Pod을 어느 노드에 놓을 것인가
flowchart LR
W["watch<br/>spec.nodeName 이 비어 있는 Pod"] --> F["Filtering<br/>놓을 수 없는 노드를 거른다"]
F --> S["Scoring<br/>남은 노드에 점수를 매긴다"]
S --> B["Binding<br/>spec.nodeName 을 채운다"]
B --> KL["kubelet 이 가져가 실행"]
F -->|"남은 노드가 0"| P["Pending<br/>Events 에 이유가 찍힌다"]
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class F,S key
class P bad
class W,B,KL mute
spec.nodeName이 비어 있는 Pod을 발견한다
Filtering — 놓을 수 없는 노드를 전부 거른다
Scoring — 남은 노드에 점수를 매긴다
최고점 노드로 binding — spec.nodeName을 채운다
Filtering에서 걸러지는 이유들
Pending의 원인은 대부분 이 목록 안에 있다.
그리고 describe pod의 Events가 어느 항목에서 걸렸는지 문장으로 알려준다.
방향이 반대인 두 축이 핵심이다.
flowchart LR
POD["Pod"] -->|"nodeSelector · nodeAffinity<br/>내가 갈 노드를 고른다"| NODE["노드"]
NODE -->|"taint<br/>내가 받을 Pod을 거부한다"| POD
POD2["Pod"] -->|"podAffinity · antiAffinity<br/>다른 Pod 기준으로"| POD3["다른 Pod"]
classDef pod fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef node fill:#fef3c7,stroke:#d97706,color:#78350f
class POD,POD2,POD3 pod
class NODE node
| 도구 | 관점 | 성격 |
|---|---|---|
nodeName |
Pod → 특정 노드 | 스케줄러를 건너뛴다 |
nodeSelector |
Pod → 노드 라벨 | 단순, 강제 |
| nodeAffinity | Pod → 노드 라벨 | 표현력 있음, 강제/선호 선택 가능 |
| podAffinity / podAntiAffinity | Pod → 다른 Pod | 함께 / 떨어뜨려 놓기 |
| taints / tolerations | 노드 → Pod | 노드가 밀어낸다 |
| topologySpreadConstraints | Pod → 분산 | 균등 분포 |
affinity는 Pod이 노드를 고르는 것, taint는 노드가 Pod을 거부하는 것. 둘은 함께 쓰여야 완성된다.
spec: nodeName: node01kubectl label node node01 disktype=ssdkubectl get nodes --show-labelskubectl label node node01 disktype- # 삭제spec: nodeSelector: disktype: ssd모든 노드에 자동으로 붙는 라벨도 있다 —
kubernetes.io/hostname, kubernetes.io/os,
topology.kubernetes.io/zone, node-role.kubernetes.io/control-plane.
spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disktype operator: In values: ["ssd", "nvme"]Filtering 단계에서 작동한다. 조건에 맞는 노드가 하나도 없으면 Pod은 Pending이다.
spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 50 preference: matchExpressions: - key: topology.kubernetes.io/zone operator: In values: ["ap-northeast-2a"]Scoring 단계에서 작동한다. weight(1~100)로 점수만 준다.
조건에 맞는 노드가 없어도 다른 노드에 배치된다.
flowchart LR
R["requiredDuringScheduling…"] --> FIL["Filtering 에서 작동<br/>못 맞추면 Pending ❌"]
P["preferredDuringScheduling…"] --> SCO["Scoring 에서 작동<br/>weight 만큼 점수 ✅"]
I["…IgnoredDuringExecution"] --> EX["이미 뜬 Pod 은<br/>조건이 깨져도 안 쫓아낸다"]
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class FIL bad
class SCO ok
class EX warn
class R,P,I mute
requiredDuringScheduling + IgnoredDuringExecution
= “배치할 때는 반드시, 실행 중에는 무시”
| 연산자 | 의미 |
|---|---|
In |
값이 목록 안에 있다 |
NotIn |
목록에 없다 (= 안티 어피니티) |
Exists |
키가 있기만 하면 된다 (values 없음) |
DoesNotExist |
키가 없어야 한다 |
Gt / Lt |
숫자 비교 (nodeAffinity 전용) |
AND / OR 구조
flowchart TB
NST["nodeSelectorTerms"]
NST -->|OR| T1["term 1"]
NST -->|OR| T2["term 2"]
T1 -->|AND| E1["matchExpression a"]
T1 -->|AND| E2["matchExpression b"]
T2 -->|AND| E3["matchExpression c"]
classDef or fill:#fef3c7,stroke:#d97706,color:#78350f
classDef and fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
class T1,T2 or
class E1,E2,E3 and
nodeSelectorTerms 의 항목들끼리는 ORmatchExpressions 끼리는 ANDspec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: app: web topologyKey: kubernetes.io/hostname # ← 무엇을 "같은 곳"으로 볼 것인가topologyKey가 무엇을 “같은 곳”으로 볼지 정한다. 이게 핵심이다.
flowchart TB
subgraph Z1["zone ap-northeast-2a"]
subgraph N1["node01"]
W1["web"]
end
subgraph N2["node02"]
W2["web"]
end
end
subgraph Z2["zone ap-northeast-2c"]
subgraph N3["node03"]
W3["web"]
end
end
K1["topologyKey<br/>kubernetes.io/hostname"] -.->|"노드마다 하나씩"| N1
K2["topologyKey<br/>topology.kubernetes.io/zone"] -.->|"zone 마다 하나씩"| Z1
classDef key fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
classDef pod fill:#dcfce7,stroke:#16a34a,color:#14532d
class K1,K2 key
class W1,W2,W3 pod
kubernetes.io/hostname → 노드 단위로 (한 노드에 하나씩)topology.kubernetes.io/zone → 가용영역 단위로podAffinity = 조건에 맞는 Pod 곁에, podAntiAffinity = 떨어뜨려podAffinity는 계산 비용이 크다. 대규모 클러스터에서 스케줄링이 느려지는 원인이 되기도 한다.
kubectl taint node node01 key=value:NoSchedulekubectl taint node node01 key=value:NoSchedule- # 제거 (끝에 하이픈)kubectl taint node node01 gpu=true:NoSchedulekubectl describe node node01 | grep -A3 Taintseffect 셋의 차이는 “새 Pod”과 “이미 있는 Pod”을 각각 어떻게 하느냐다.
flowchart LR
T{"taint effect"}
T -->|NoSchedule| A1["새 Pod: 배치 안 함 ❌"]
T -->|NoSchedule| A2["기존 Pod: 그대로 둔다 ✅"]
T -->|PreferNoSchedule| B1["새 Pod: 되도록 피함<br/>자리 없으면 배치 ⚠️"]
T -->|PreferNoSchedule| B2["기존 Pod: 그대로 둔다 ✅"]
T -->|NoExecute| C1["새 Pod: 배치 안 함 ❌"]
T -->|NoExecute| C2["기존 Pod: 쫓아낸다 ❌"]
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
class A1,C1,C2 bad
class A2,B2 ok
class B1 warn
| effect | 의미 |
|---|---|
NoSchedule |
새 Pod을 배치하지 않는다. 이미 있는 것은 그대로 |
PreferNoSchedule |
되도록 피한다. 자리가 없으면 배치한다 |
NoExecute |
배치도 안 하고, 이미 있는 Pod도 쫓아낸다 |
**taint는 “노드에 붙이는 조건”**이고, **toleration은 “Pod이 그 조건을 견딜 수 있다는 선언”**이다. toleration이 있다고 그 노드에 가는 것은 아니다 — 갈 수 있게 될 뿐이다.
spec: tolerations: - key: "gpu" operator: "Equal" # Equal | Exists value: "true" effect: "NoSchedule"
- key: "node.kubernetes.io/not-ready" operator: "Exists" effect: "NoExecute" tolerationSeconds: 300 # 300초까지는 버틴다
- operator: "Exists" # key 생략 = 모든 taint를 견딘다operator: Exists 면 value를 쓰지 않는다effect를 생략하면 모든 effect에 대해 적용된다key까지 생략하고 Exists만 두면 전부 무시 — DaemonSet에서 쓰는 패턴flowchart LR
Q1{"노드에 taint 가 있나"}
Q1 -->|없다| R1["아무 Pod 이나 올 수 있다"]
Q1 -->|있다| Q2{"Pod 에 맞는 toleration 이 있나"}
Q2 -->|없다| R2["이 노드에는 못 온다 ❌"]
Q2 -->|있다| R3["올 수 있다<br/>단 온다는 보장은 없다 ⚠️"]
R3 --> Q3{"유도하고 싶은가"}
Q3 -->|"그렇다"| R4["nodeSelector · nodeAffinity 를<br/>같이 써야 그 노드로 간다 ✅"]
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
classDef warn fill:#fef3c7,stroke:#d97706,color:#78350f
classDef mute fill:#f1f5f9,stroke:#94a3b8,color:#334155
class R1,R4 ok
class R2 bad
class R3 warn
class Q1,Q2,Q3 mute
| taint | 언제 |
|---|---|
node.kubernetes.io/not-ready |
노드가 Ready가 아닐 때 (NoExecute) |
node.kubernetes.io/unreachable |
노드와 통신이 안 될 때 (NoExecute) |
node.kubernetes.io/memory-pressure |
메모리 부족 |
node.kubernetes.io/disk-pressure |
디스크 부족 |
node.kubernetes.io/unschedulable |
cordon 했을 때 |
node-role.kubernetes.io/control-plane |
컨트롤 플레인 노드 (NoSchedule) |
노드가 죽어도 Pod이 5분 동안 안 옮겨가는 이유가 여기 있다.
sequenceDiagram
participant N as 노드
participant CM as node 컨트롤러
participant P as Pod
N--xCM: 하트비트 중단
CM->>N: not-ready / unreachable taint 부착 · NoExecute
Note over P: 모든 Pod 에 자동 toleration<br/>tolerationSeconds 300
P->>P: 300초 버틴다
P--xN: 300초 후 축출
Note over P: 상위 컨트롤러가 다른 노드에 새로 만든다
not-ready / unreachable에 대한 5분(300초) toleration이 자동으로 붙는다stateDiagram-v2
[*] --> Ready: 정상
Ready --> SchedulingDisabled: kubectl cordon
SchedulingDisabled --> Ready: kubectl uncordon
Ready --> Drained: kubectl drain
Drained --> Ready: kubectl uncordon
note right of SchedulingDisabled
새 Pod 만 막는다.
기존 Pod 은 그대로 돈다.
end note
note right of Drained
cordon + 기존 Pod 축출.
PDB 를 존중해 기다린다.
end note
kubectl cordon node01 # 새 Pod 배치 금지 (기존은 유지)kubectl uncordon node01 # 해제
kubectl drain node01 \ --ignore-daemonsets \ # DaemonSet Pod은 어차피 못 옮기니 무시 --delete-emptydir-data \ # emptyDir을 쓰는 Pod도 지운다 --force # 컨트롤러 없는 Pod(단독 Pod)도 지운다cordon = 노드에 unschedulable 표시 (taint가 붙는다)drain = cordon + 기존 Pod을 전부 축출(evict)spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule # 또는 ScheduleAnyway labelSelector: matchLabels: app: webmaxSkew는 도메인 간 개수 차이의 허용치다.
flowchart LR
subgraph OK["maxSkew 1 · 허용 ✅"]
A1["zone A<br/>Pod 3개"]
A2["zone B<br/>Pod 2개"]
A3["차이 1"]
end
subgraph NG["maxSkew 1 · 위반 ❌"]
B1["zone A<br/>Pod 3개"]
B2["zone B<br/>Pod 1개"]
B3["차이 2 → DoNotSchedule 이면 Pending"]
end
classDef ok fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef bad fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
class A1,A2,A3 ok
class B1,B2,B3 bad
maxSkew: 1 이면 zone A에 3개일 때 zone B는 최소 2개여야 한다whenUnsatisfiable: DoNotSchedule = 강제, ScheduleAnyway = 선호podAntiAffinity보다 표현이 정확하고 계산이 싸다. “노드/존에 고르게 퍼뜨려라”가 목적이라면 이쪽이 정답이다.
apiVersion: scheduling.k8s.io/v1kind: PriorityClassmetadata: name: high-priorityvalue: 1000000globalDefault: falsepreemptionPolicy: PreemptLowerPriority # 또는 Neverdescription: "중요한 워크로드용"spec: priorityClassName: high-prioritysequenceDiagram
participant H as 높은 우선순위 Pod
participant S as scheduler
participant N as 노드 · 자리 없음
participant L as 낮은 우선순위 Pod
H->>S: 스케줄 요청
S->>N: Filtering — 맞는 노드가 없다
S->>L: 축출 (preempt)
Note over L: describe 에 Preempted by … 기록
S->>H: 확보된 자리에 binding
L->>S: 다시 스케줄링 시도 (다른 노드일 수 있다)
preemptionPolicy: Never — 우선순위는 높지만 남을 쫓아내지는 않는다system-cluster-critical, system-node-critical축출된 Pod은 describe에 Preempted by ... 로 기록된다.
spec: schedulerName: my-schedulerdefault-schedulerkubectl get pods -n kube-system -l component=kube-schedulerkubectl get events | grep -i schedul커리큘럼의 “Pod admission” 항목. 네임스페이스 라벨로 켠다.
kubectl label ns dev \ pod-security.kubernetes.io/enforce=baseline \ pod-security.kubernetes.io/enforce-version=latest \ pod-security.kubernetes.io/warn=restricted레벨(얼마나 조이나) × 모드(어기면 어떻게 하나)의 조합이다.
privileged
제한 없음.
baseline
알려진 권한 상승을 막는다.
hostNetwork, privileged 등 금지.
restricted
강하게 제한.
runAsNonRoot, capabilities drop ALL 등 요구.
| 모드 | 동작 |
|---|---|
enforce |
거부한다 |
audit |
감사 로그에 남긴다 |
warn |
사용자에게 경고를 보여준다 |
PodSecurityPolicy(PSP)는 v1.25에서 제거되었다. 옛 자료에 나오면 무시할 것.
kubectl get pods -o wide # Pending인 것 확인kubectl describe pod web # ★ Events 를 읽는다kubectl get events --sort-by=.lastTimestampkubectl describe node node01 # Taints / Allocated resourceskubectl get nodes # SchedulingDisabled 표시 확인Events 메시지로 원인이 거의 확정된다.
flowchart LR
E{"describe pod → Events"}
E -->|"Insufficient cpu / memory"| R1["request 여유 부족<br/>describe node 의 Allocated resources"]
E -->|"had untolerated taint"| R2["toleration 없음<br/>describe node 의 Taints"]
E -->|"didn't match node affinity/selector"| R3["라벨 불일치<br/>get nodes --show-labels"]
E -->|"were unschedulable"| R4["cordon 되어 있다<br/>kubectl uncordon"]
E -->|"unbound immediate PersistentVolumeClaims"| R5["PVC 가 Bound 안 됨 · 13장"]
E -->|"메시지가 없다"| R6["스케줄러가 죽었거나<br/>schedulerName 오타"]
classDef norm fill:#fef3c7,stroke:#d97706,color:#78350f
classDef alarm fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
class R1,R2,R3,R4,R5 norm
class R6 alarm
| 메시지 | 원인 |
|---|---|
Insufficient cpu / Insufficient memory |
request 여유 부족 |
node(s) had untolerated taint {...} |
toleration 없음 |
node(s) didn't match Pod's node affinity/selector |
라벨 불일치 |
node(s) were unschedulable |
cordon 되어 있다 |
pod has unbound immediate PersistentVolumeClaims |
PVC가 Bound 안 됨 (13장) |
| (메시지 없음) | 스케줄러가 죽었거나 schedulerName 오타 |
nodeName 채우기)required…IgnoredDuringExecution = “배치할 때만 강제, 실행 중엔 무시”drain은 --ignore-daemonsets 필수, PDB를 존중해 기다린다describe pod의 Events 한 줄로 끝나는 경우가 대부분