Node-pressure Eviction
- 공식 문서 - Node-pressure Eviction
- kubelet이 노드의 memory·disk·inode 등을 모니터링하다가, 특정 소비 수준에 도달하면 Pod을 선제적으로 fail시켜 리소스를 회수하고 starvation을 막는 동작
- evict 시 — kubelet이 선택된 Pod의 phase를
Failed로 바꾸고 종료
- API-initiated eviction과 다름 — 별개의 메커니즘
- 중요 — kubelet은 PodDisruptionBudget([[disruptions]])과
terminationGracePeriodSeconds를 존중하지 않음
- soft threshold →
eviction-max-pod-grace-period 적용
- hard threshold →
0s(즉시 종료)
Self healing 동작
- end-user Pod을 죽이기 전에 먼저 노드 레벨 리소스 회수(세션 2) 시도 — 예: disk 부족 시 미사용 컨테이너 이미지 삭제
- Deployment·StatefulSet 등 워크로드 객체가 관리하는 Pod이면 → control plane(
kube-controller-manager)이 evict된 Pod을 대체할 새 Pod 생성
static Pod의 self healing
- 리소스 압박 노드의 static Pod([[pod]])도 evict될 수 있음 → kubelet이 대체본 생성 시도 (static Pod은 “이 노드에서 돌리겠다”는 의도이므로)
- 대체 시 static Pod의 priority를 고려 — priority가 낮은데 클러스터에 더 높은 priority Pod이 있고 노드가 압박 상태면 자리 확보를 못 할 수 있음. 그래도 kubelet은 모든 static Pod 실행을 계속 시도
Eviction signals
- 특정 시점의 리소스 현재 상태값 — kubelet이 이를 threshold(노드에 남아 있어야 할 최소량)와 비교해 evict 결정
- 각 signal은 퍼센트(
%) 또는 절대값 지원 (퍼센트는 해당 signal의 전체 capacity 기준)
| Signal |
의미 |
Linux 전용 |
memory.available |
가용 메모리 = capacity − workingSet |
|
nodefs.available |
nodefs 가용 디스크 |
|
nodefs.inodesFree |
nodefs 가용 inode |
• |
imagefs.available |
imagefs 가용 디스크 |
|
imagefs.inodesFree |
imagefs 가용 inode |
• |
containerfs.available |
containerfs 가용 디스크 |
|
containerfs.inodesFree |
containerfs 가용 inode |
• |
pid.available |
가용 PID = maxpid − curproc |
• |
- 참고 — 일부 kubelet GC 플래그(
--maximum-dead-containers 등)는 eviction 도입으로 deprecated
Memory signal 계산
- Linux —
memory.available는 free -m이 아니라 cgroupfs에서 도출 (컨테이너 안에선 free -m이 동작 안 하고, node allocatable과도 맞물리기 때문)
inactive_file 제외 — inactive LRU의 file-backed 메모리는 압박 시 회수 가능하므로 workingSet(사용량)에서 빼 가용으로 간주
- Windows — 글로벌 메모리 commit 수준에서 도출 (
CommitLimit − CommitTotal). page-file 크기가 바뀌면 CommitLimit도 변함
Filesystem signal — nodefs / imagefs / containerfs
- kubelet이 인식하는 3가지 파일시스템 식별자
| 식별자 |
저장 대상 |
nodefs |
노드 메인 FS — 로컬 볼륨, memory 아닌 emptyDir, 로그, ephemeral storage 등 (예: /var/lib/kubelet) |
imagefs |
(선택) 컨테이너 이미지(read-only 레이어). containerfs가 없으면 writable 레이어도 여기 저장 |
containerfs |
(선택) 컨테이너 writable 레이어. 이걸 쓰면 imagefs는 이미지 전용으로 분리됨 |
- 식별자 ≠ 별도 마운트 포인트 — 흔한 구성에선 2~3개가 같은 파일시스템을 가리킴
- 3가지 일반 레이아웃
- 단일 rootfs —
nodefs·imagefs·containerfs가 모두 같은 FS
- split disk —
imagefs·containerfs가 같은 전용 디스크 (이미지+writable), nodefs는 별도
- split image —
containerfs·nodefs가 같은 FS, 이미지만 별도 imagefs
containerfs 분리는 feature gate KubeletSeparateDiskGC 필요 (현재 CRI-O v1.29+만 지원)
- kubelet은 런타임에서 이 FS들을 자동 탐색하며, 그 외 로컬 FS는 무시. 이미지·컨테이너용 다중 FS는 미지원
Eviction thresholds
- 형식 —
[eviction-signal][operator][quantity] (예: memory.available<1Gi)
quantity는 절대값 또는 퍼센트 — 예: 10GiB 노드에서 1GiB 미만 시 evict라면 memory.available<1Gi 또는 <10% (둘 다는 불가)
- soft와 hard 두 종류
Soft eviction threshold
- threshold + 관리자가 지정한 grace period 쌍 — grace period를 초과해야 evict (grace period 미지정 시 kubelet이 시작 시 에러)
- 관련 플래그
eviction-soft — threshold 집합 (예: memory.available<1.5Gi)
eviction-soft-grace-period — 각 threshold가 얼마나 유지돼야 evict인지 (예: memory.available=1m30s)
eviction-max-pod-grace-period — evict 시 Pod 종료에 허용할 최대 grace period(초)
- soft threshold 충족 시 —
eviction-max-pod-grace-period와 soft grace period 중 작은 값 사용. max를 안 주면 즉시 kill(graceful 없음)
Hard eviction threshold
- grace period 없음 — 충족 즉시 graceful 없이 Pod을 kill해 리소스 회수
- 플래그
eviction-hard. 기본값 ↓
| Signal |
기본 threshold |
memory.available |
<100Mi (Linux) / <500Mi (Windows) |
nodefs.available |
<10% |
imagefs.available |
<15% |
nodefs.inodesFree |
<5% (Linux) |
imagefs.inodesFree |
<5% (Linux) |
- 기본값은 아무 파라미터도 안 바꿨을 때만 적용 — 하나라도 바꾸면 나머지는 기본값을 상속하지 않고 0이 됨. 커스텀 시엔 모든 threshold를 명시해야 함 (또는
MergeDefaultEvictionSettings: true로 두면 나머지가 기본값 상속)
containerfs 기본값 — nodefs와 같은 FS면 nodefs 값을, imagefs와 같은 FS면 imagefs 값을 따름. containerfs threshold 커스텀은 미지원(시도 시 경고 후 무시)
Eviction 모니터링 주기
- kubelet이 threshold를 평가하는 주기 =
housekeeping-interval (기본 10s)
Node conditions
- threshold(hard·soft) 충족 시 — kubelet은 grace period와 무관하게 노드가 압박 상태임을 node condition으로 보고
- eviction signal → node condition 매핑
| Node Condition |
대응 signal |
의미 |
MemoryPressure |
memory.available |
가용 메모리가 threshold 도달 |
DiskPressure |
nodefs·imagefs·containerfs의 .available/.inodesFree |
루트/이미지/컨테이너 FS의 가용 디스크·inode가 threshold 도달 |
PIDPressure |
pid.available |
(Linux) 가용 PID가 threshold 미만 |
- control plane은 이 node condition을 다시 taint로 매핑 (
node.kubernetes.io/memory-pressure 등)
- node condition 갱신 주기 =
--node-status-update-frequency (기본 10s) — threshold 평가 주기인 housekeeping-interval과는 별개
Node condition oscillation
- soft threshold 근처에서 노드가 grace period를 못 채우고 threshold 위아래로 진동하면 → node condition이
true↔false를 반복 → 잘못된 evict 결정 유발
eviction-pressure-transition-period로 방지 — condition 상태를 바꾸기 전 kubelet이 대기해야 하는 최소 시간 (기본 5m)
노드 레벨 리소스 회수 (Reclaiming node level resources)
DiskPressure 보고 시 — end-user Pod을 evict하기 전에 노드 레벨 리소스부터 회수 (세션 1 Self healing의 구체 동작). 노드의 파일시스템 구성(위 nodefs/imagefs/containerfs)에 따라 동작이 다름
| 파일시스템 구성 |
trigger FS → kubelet 회수 동작 |
nodefs만 (imagefs·containerfs 없음) |
nodefs: ① 죽은 Pod·컨테이너 GC → ② 미사용 이미지 삭제 (순서대로) |
imagefs 분리 |
nodefs: 죽은 Pod·컨테이너 GC / imagefs: 모든 미사용 이미지 삭제 |
imagefs+containerfs 분리 |
containerfs: 죽은 Pod·컨테이너 GC / imagefs: 모든 미사용 이미지 삭제 |
- 패턴 — 죽은 Pod·컨테이너 GC는 쓰기 레이어·로그가 쌓이는 FS(
nodefs 또는 containerfs)가 압박일 때, 이미지 삭제는 imagefs가 압박일 때
Pod 선택 기준 (Pod selection for kubelet eviction)
- 노드 레벨 회수로도 signal이 threshold 아래로 안 내려가면 → kubelet이 end-user Pod evict 시작
- evict 순서 결정 파라미터 (순서대로) — ① 사용량이 request 초과 여부 → ② Pod Priority → ③ request 대비 사용량(초과 정도)
- 결과 랭킹
| 순위 |
대상 |
정렬 기준 |
| 먼저 evict |
사용량 > request인 BestEffort·Burstable |
Priority → request 초과량 |
| 나중 evict |
Guaranteed + 사용량 < request인 Burstable |
Priority |
- kubelet은 QoS 클래스([[pod-qos]])로 순서를 정하지 않음 — 다만 QoS로 evict 순서를 대략 예측 가능. QoS는 EphemeralStorage request엔 적용 안 되므로
DiskPressure 상황엔 이 예측이 안 맞음
- Guaranteed Pod은 다른 Pod의 소비 때문엔 절대 evict 안 됨. 단 예외 — 시스템 데몬(kubelet·journald 등)이
system-reserved·kube-reserved보다 많이 쓰고, 노드에 request 미만으로 쓰는 Guaranteed·Burstable Pod만 남았다면 → 노드 안정성 위해 그 중 Priority 낮은 것 먼저 evict
- static Pod을 압박 시 evict에서 보호하려면 → Pod의
priority 필드를 직접 설정 (static Pod은 priorityClassName 미지원)
- inode·PID starvation으로 evict할 땐 → inode·PID엔 request가 없으므로 상대 Priority만으로 순서 결정
파일시스템 구성별 정렬 기준
- disk 사용량으로 정렬할 때, 어떤 FS가 trigger인지에 따라 집계 대상이 다름
| 구성 |
trigger FS → Pod 정렬 기준(사용량 집계) |
nodefs=imagefs 동일 FS |
nodefs: 총 디스크 (로컬 볼륨 + 로그 + 전 컨테이너 쓰기 레이어) |
nodefs·imagefs 분리 |
nodefs: 로컬 볼륨 + 로그 / imagefs: 전 컨테이너 쓰기 레이어 |
imagefs·containerfs 분리 |
containerfs: 로컬 볼륨 + 로그 + 쓰기 레이어 / imagefs: 이미지별 storage 사용량 순위 |
Minimum eviction reclaim
- 문제 — Pod 하나 evict해도 회수량이 적으면 kubelet이 threshold를 반복 재충족 → 연쇄 evict(thrashing)
eviction-minimum-reclaim(플래그 또는 kubelet config) — 리소스별 최소 회수량 지정. signal이 threshold에 닿은 뒤에도 이 양만큼 추가 회수할 때까지 계속 회수
evictionHard:
nodefs.available: "1Gi"
imagefs.available: "100Gi"
evictionMinimumReclaim:
nodefs.available: "500Mi" # 1Gi 회복 후 500Mi 더 → 1.5Gi까지 회수
imagefs.available: "2Gi" # 102Gi까지 회수. 단 회수 가능량 < 2Gi면 아무것도 안 함
- 기본값 — 모든 리소스에 대해
0
containerfs.available은 커스텀 최소 회수량 설정 불가 — nodefs/imagefs 구성에 따라 자동 설정 (세션 1의 containerfs threshold 커스텀 미지원과 같은 맥락)
Node out of memory behavior
- kubelet이 메모리를 회수하기 전에 노드에 OOM 이벤트 발생 시 → 커널의
oom_killer가 대응 (kubelet의 evict와 별개 경로)
- kubelet은 Pod의 QoS에 따라 컨테이너마다
oom_score_adj 설정
| QoS |
oom_score_adj |
Guaranteed |
-997 |
Burstable |
min(max(2, 1000 − 1000×memoryRequest/machineMemory), 999) — request 비율 클수록 낮음(=덜 죽음) |
BestEffort |
1000 |
system-node-critical priority Pod의 컨테이너도 -997 부여 (Guaranteed급 보호)
- OOM 시 —
oom_killer가 컨테이너의 메모리 사용 비율로 oom_score를 구하고 거기에 oom_score_adj를 더해 effective oom_score 산출 → 가장 높은 컨테이너부터 kill
- 결과 — 낮은 QoS이면서 request 대비 메모리를 많이 쓰는 컨테이너가 먼저 죽음
- evict와 차이 — OOM kill된 컨테이너는
restartPolicy에 따라 재시작 가능 (Pod evict는 Pod 전체 종료 후 보통 다른 노드에 재생성)
Good practices
Schedulable resources and eviction policies
- 원칙 — eviction policy를 쓸 땐, 스케줄되자마자 메모리 압박을 유발해 곧바로 evict를 트리거하는 Pod이 안 잡히도록
--system-reserved를 충분히 확보해야 함
- 예시 — 노드 10GiB / 시스템 데몬용 10% 예약 / 95% 사용 시 evict 목표
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi
--system-reserved=1.5Gi = 시스템 몫 1GiB(10%) + eviction threshold 500Mi — 이렇게 잡아야 allocatable이 의도대로 계산됨
- threshold 도달 조건 — Pod이 request 초과 사용 또는 시스템이 1GiB 넘게 사용 →
memory.available가 500Mi 아래로 떨어짐
DaemonSets and node-pressure eviction
- Pod priority가 evict 결정의 핵심 요소
- DaemonSet Pod을 evict에서 보호하려면 → pod spec에 충분히 높은
priorityClassName 지정
- 반대로 자원 여유가 있을 때만 그 DaemonSet Pod이 돌게 하려면 → 낮은 priority(또는 기본값) 사용
Known issues
kubelet may not observe memory pressure right away
- kubelet은 기본적으로 cAdvisor를 주기적으로 폴링해 메모리 통계 수집 → 그 사이 사용량이 급증하면
MemoryPressure를 제때 못 잡아 OOM killer가 먼저 발동될 수 있음
--kernel-memcg-notification — memcg 알림 API를 켜 threshold 교차 시 즉시 통지 받게 함
- 극단적 활용률이 목표가 아니라면 —
--kube-reserved·--system-reserved로 시스템 메모리를 미리 떼어두는 게 현실적 우회책
active_file memory is not considered as available memory
- Linux 커널은 active LRU의 file-backed 메모리를
active_file로 추적. kubelet은 이를 회수 불가로 간주 → workingSet(사용 중)에 포함해 가용에서 제외 (회수 가능한 inactive_file을 가용으로 보는 위 Memory signal 계산과 대비)
- 문제 — block-backed 로컬 스토리지(ephemeral 포함)를 많이 쓰는 워크로드는 커널 파일·블록 캐시가
active_file로 쌓임 → kubelet이 높은 메모리 사용으로 오인 → 노드를 memory pressure로 taint → 불필요한 Pod evict (kubernetes#43916)
- 우회책 — I/O 집약 컨테이너는 memory request = limit으로 설정 (적정 limit 값은 추정·측정 필요)