IL-0071 Onkar Raghunath Shelke · 2026-09-17 (게시일)kubernetesobservability
글쓴이가 운영하던 클러스터에서 워커 노드 한 대의 inode 가 바닥날 추세라는 알림이 왔다. 디스크 사용률은 83%, inode 사용률은 67% 였다. 알림이 장애로 이어지기 전에 문제를 잡았다. 글쓴이는 지금 그 클러스터에 접근할 수 없어서, 일부 수치는 장애 기록에서 가져왔고 나머지는 계산으로 되짚었다고 밝혔다.
kubelet 은 이미지가 노드를 채우는 것을 이미지 가비지 컬렉션으로 막는다. 이미지 파일 시스템 사용률이 imageGCHighThresholdPercent 의 기본값인 85 를 넘으면, kubelet 은 쓰지 않는 이미지를 오래된 것부터 지워 imageGCLowThresholdPercent 의 기본값인 80 까지 내린다. 두 값 모두 바이트 기준이고 파일 수는 보지 않는다. 리눅스의 기본 하드 eviction 설정에는 nodefs.inodesFree 와 imagefs.inodesFree 가 모두 5% 로 들어 있다. 그러나 이는 노드를 지키려고 파드를 축출하는 마지막 단계다. 바이트에는 85% 에서 도는 이미지 가비지 컬렉션과 하드 eviction 두 장치가 있지만, inode 에는 하드 eviction 하나뿐이다. inode 가 모자랄 때 처음 일어나는 일이 가장 큰 영향을 주는 일이다.
ext4 는 파일 시스템을 만들 때 inode_ratio 로 inode 수를 정하고 그 뒤로 바꾸지 않는다. 기본 /etc/mke2fs.conf 값은 16384 다. 비어 있지 않은 파일은 아무리 작아도 4 KiB 블록 하나를 쓰므로, 작은 파일만 있으면 디스크의 25% 를 쓴 시점에 inode 가 바닥난다. 이 노드는 inode 가 모두 약 1,630만 개로, 계산상 8192 정도의 비율로 포맷된 것으로 보였다. 이 비율이면 작은 파일만 있을 때 디스크의 50% 를 쓴 시점에 inode 가 바닥난다.
문제는 클러스터가 아니라 이미지였다. kubelet, containerd, 노드 이미지는 모두 기본값으로 돌고 있었다. 이미지는 단일 스테이지 빌드여서 소스, 빌드 결과물, 개발 의존성을 포함한 node_modules 가 모두 들어갔다. containerd 의 콘텐츠 저장소는 압축된 레이어를 다이제스트로 중복 제거하지만, overlayfs 스냅샷터는 레이어마다 따로 풀고 서로 공유하지 않는다. .dockerignore 에서 node_modules 를 빼지 않았거나 CI 가 레이어 캐시 없이 매번 npm install 을 다시 돌리면, 내용이 같아도 다이제스트가 달라져 새 스냅샷이 생긴다. 이 스냅샷은 이미지가 지워질 때까지 남고, kubelet 은 바이트 기준 85% 에 닿아야 이미지를 지운다. 사람들이 주로 보는 디스크 사용률이 기준선을 넘지 않았기 때문에 아무도 알아채지 못했다.
워커 노드 한 대에서 NodeFilesystemFilesFillingUp 알림이 울려 담당자가 호출됐다. inode 가 바닥날 추세라는 알림이었다.
담당자는 누구나 그렇듯 디스크부터 봤다. 디스크는 83% 사용 중으로 103G 를 쓰고 22G 가 남아 있었다. inode 는 67% 사용 중으로 11M 을 쓰고 5.3M 이 남아 있었다. 더 찬 쪽은 디스크였지만 알림은 inode 쪽에서 왔다. NodeFilesystemFilesFillingUp 은 predict_linear 로 만든 추세 알림이라, 현재 수준이 아니라 늘어나는 기울기를 보고 울린다.
du --inodes -xS /var | sort -rh | head -n 20 으로 파일이 많은 디렉터리를 찾았다. 맨 위는 로그도 파드 볼륨도 아니었다. containerd 의 스냅샷 저장소인 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/ 였다. 여러 스냅샷에 app/node_modules/@mui/icons-material 이라는 같은 경로가 들어 있었고, 스냅샷마다 파일이 21,553개였다. 이미지 하나에 든 파일은 모두 4만 개가 넘었다.
증상 노드 디스크 가득 참
node_exporter mixin 에 들어 있는 NodeFilesystemFilesFillingUp 알림이 inode 사용률 67% 에서 울렸다. predict_linear 로 inode 가 바닥날 시점을 예측하는 추세 알림이다.
–
kubelet 의 백그라운드 정리는 바이트를 기준으로 돈다. 용량보다 파일 수가 먼저 차는 워크로드에는 미리 손쓸 장치가 없다. kubelet 은 inode 가 5% 남았을 때 파드 축출로 처음 개입한다. 파드 축출은 노드를 지키는 마지막 수단일 뿐 해결책이 아니다.
ext4 의 inode 수는 포맷할 때 정해진다. 작은 파일이 많으면 디스크가 넉넉해도 inode 가 먼저 바닥난다.
추세 알림은 증가가 잠시 멈추면 조용해진다. kubelet 의 95% eviction 지점보다 한참 앞에서 울리는 고정 기준 알림을 함께 둔다.
node_modules 뿐 아니라 Python virtualenv, 벤더링한 PHP 패키지, Ruby gem 처럼 작은 파일이 많은 의존성 트리를 담은 이미지는 모두 같은 문제를 일으킨다.
IL-0040 비운영 쿠버네티스 클러스터에 Helm 차트를 한꺼번에 설치하자 작은 gp2 루트 볼륨의 IOPS 가 바닥나, 워커 노드 세 대에서 kubelet 이나 Docker 가 내려가 세 대 모두 NotReady 가 됐다Saltside · 2017같은 원인 · 공통 노드 루트 디스크, kubelet, 컨테이너 런타임
같은 원인 · 공통 kubelet
IL-0169 FR-PAR 의 Scaleway Kubernetes API 가 메모리 부족으로 쓰러져 노드 인증이 막히고 Kapsule 노드가 NotReady 가 됐다.Scaleway · 2024같은 원인 · 공통 kubelet
IL-0176 시계열 급증으로 호스트 메모리가 바닥나자 oom_score_adj 999 를 받은 Prometheus 컨테이너가 커널 OOM Killer 에게 먼저 죽어 CrashLoopBackOff 에 빠졌다.LINE · 2020같은 원인 · 공통 kubelet
IL-0002 빌더 파드가 노드 한 대에 몰리자 kubelet 이 기본 API QPS 한도에 막혀 파드 상태를 늦게 갱신했고, CI 빌드가 AWS IAM 자격 증명을 받지 못해 실패했다Zalando · 2019같은 원인 · 공통 kubelet
https://incidentlib-mcp.benchlet.com/mcpAPI 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.