Published on

리눅스 디스크 100%? inode 고갈 진단·복구 실전

Authors

서버에서 df -h는 여유가 있는데도 배포/로그/임시파일 생성이 실패하며 No space left on device가 발생하는 경우가 있습니다. 이때 많은 팀이 “디스크 100%”로 오해하고 용량 증설부터 시도하지만, 실제 원인은 inode 고갈인 경우가 흔합니다. inode는 “파일 하나당 1개씩 소비되는 메타데이터 슬롯”이라서, 작은 파일이 수백만 개 쌓이면 데이터 용량과 무관하게 파일 생성이 막힙니다.

이 글은 운영 환경에서 바로 적용할 수 있도록 진단 → 원인 파일 찾기 → 안전한 정리 → 복구 확인 → 재발 방지 순서로 정리합니다.

1) 증상: 디스크는 남았는데 파일이 안 만들어진다

대표적인 증상은 아래와 같습니다.

  • touch: cannot touch 'x': No space left on device
  • 로그가 더 이상 기록되지 않음(애플리케이션/웹서버)
  • 컨테이너/파드에서 임시 파일 생성 실패
  • df -h 상 사용률이 낮거나 충분한데도 장애 발생

이때는 “용량”이 아니라 inode를 확인해야 합니다.

2) 10초 진단: df -i로 inode 사용률 확인

df -i는 파일시스템별 inode 사용량을 보여줍니다.

# inode 사용률 확인
sudo df -i

# 예시 출력(가상의 값)
# Filesystem      Inodes   IUsed   IFree IUse% Mounted on
# /dev/nvme0n1p1  6553600 6553500     100  100% /
  • IUse%가 100%이면 inode 고갈입니다.
  • 특정 마운트(/, /var, /tmp, /var/lib/docker 등)만 100%일 수도 있습니다.

df -h와 df -i가 엇갈릴 때의 해석

  • df -h 여유 + df -i 100%: 작은 파일 폭증
  • df -h 100% + df -i 여유: 큰 파일/로그/덤프
  • 둘 다 100%: 둘 다 문제(대개 로그 폭증 + 작은 파일 폭증이 같이 발생)

3) inode를 많이 먹는 범인 디렉터리 찾기

inode는 “파일 개수”에 비례합니다. 즉, 용량이 아니라 파일 수를 세야 합니다.

3-1) 빠른 스캔: 상위 디렉터리별 파일 개수

아래는 루트 기준 1레벨에서 파일 개수(대략)를 세는 방법입니다.

# 루트 1레벨 디렉터리별 파일 개수(권한 오류는 무시)
for d in /*; do
  [ -d "$d" ] || continue
  c=$(sudo find "$d" -xdev -type f 2>/dev/null | wc -l)
  printf "%10s  %s\n" "$c" "$d"
done | sort -nr | head
  • -xdev로 다른 파일시스템(예: NFS, 별도 마운트)로 넘어가지 않게 제한합니다.
  • 파일 수가 비정상적으로 큰 디렉터리가 상위에 뜹니다(대개 /var, /tmp, /home, /var/lib/...).

3-2) 더 정확히: 특정 디렉터리 내부 Top N 찾기

범인 후보가 /var라면 다음처럼 좁혀갑니다.

# /var 아래 2레벨 디렉터리별 파일 개수
sudo find /var -xdev -mindepth 2 -maxdepth 2 -type f -printf '%h\n' \
  | sort | uniq -c | sort -nr | head -20

이 출력에서 상위에 뜨는 경로가 inode를 잡아먹는 핵심입니다.

3-3) 자주 나오는 범인 패턴

  • /var/log/* 로그 로테이션 실패, 디버그 로그 폭증
  • /tmp, /var/tmp 임시 파일 누수(크론/배치/SDK)
  • /var/lib/docker/overlay2 컨테이너 레이어/로그/캐시
  • /var/lib/containerd 컨테이너 런타임 캐시
  • 애플리케이션 캐시 디렉터리(예: 이미지 썸네일, 세션 파일)
  • 스풀/큐 디렉터리(메일 큐, 작업 큐)

컨테이너 환경에서 자주 겪는 다른 “고갈” 이슈로는 메모리 부족이 있습니다. 메모리 누수/리밋 문제는 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전도 함께 참고하면 장애 분류가 빨라집니다.

4) 안전한 복구: “지우기 전에” 영향도부터 확인

inode 고갈은 급하지만, 무작정 rm -rf는 2차 장애를 만들 수 있습니다. 아래 순서로 안전하게 진행합니다.

4-1) 무엇을 지울지 판단하는 체크리스트

  • 로그: 최근 N일만 남기고 정리 가능? (규정/감사/보관 정책 확인)
  • 캐시: 삭제해도 재생성 가능한가?
  • 임시파일: 프로세스가 사용 중인지 확인 필요
  • 큐/스풀: 삭제하면 데이터 유실 가능(업무 영향 큼)

4-2) 열린(삭제된) 파일이 공간/inode를 계속 점유하는 경우

파일을 삭제했는데도 inode가 회복되지 않으면, 프로세스가 파일을 열린 채로 잡고 있을 수 있습니다.

# 삭제된 파일을 열고 있는 프로세스 찾기
sudo lsof +L1
  • +L1은 링크 수가 0(삭제됨)인 파일을 의미합니다.
  • 이런 경우 프로세스 재시작(또는 로그 핸들 reopen)이 필요합니다.

5) 실전 정리 레시피 (상황별)

5-1) /tmp, /var/tmp 임시파일 정리

임시 디렉터리는 오래된 파일부터 제거하는 방식이 안전합니다.

# /tmp에서 3일 이상 지난 파일 삭제(디렉터리 포함)
sudo find /tmp -xdev -mindepth 1 -mtime +3 -print -delete

# /var/tmp에서 7일 이상 지난 파일 삭제
sudo find /var/tmp -xdev -mindepth 1 -mtime +7 -print -delete
  • -print를 먼저 넣어 어떤 파일이 지워지는지 로그로 남기면 추적에 도움이 됩니다.

5-2) 로그 폭증: logrotate 점검 + 즉시 정리

logrotate 동작 확인

# 설정 문법 체크 및 디버그
sudo logrotate -d /etc/logrotate.conf

# 강제 실행(실제 수행)
sudo logrotate -f /etc/logrotate.conf

오래된 로그 파일 정리(예: 14일 초과)

sudo find /var/log -xdev -type f \( -name "*.log" -o -name "*.gz" -o -name "*.1" \) \
  -mtime +14 -print -delete

주의:

  • 애플리케이션이 단일 파일에 계속 쓰는 구조면, 삭제 대신 truncate가 더 안전할 때가 있습니다.
# 파일을 지우지 않고 내용만 비움(파일 핸들은 유지)
sudo truncate -s 0 /var/log/myapp/debug.log

5-3) Docker/Containerd: 이미지·컨테이너 찌꺼기 정리

컨테이너 호스트에서 inode 고갈이 자주 나옵니다.

Docker

# 전체 사용량 확인
sudo docker system df

# 사용하지 않는 리소스 정리(주의: 중지 컨테이너/미사용 이미지 삭제)
sudo docker system prune -af

# 볼륨까지 정리(데이터 볼륨이 날아갈 수 있으니 매우 주의)
# sudo docker system prune -af --volumes

containerd(환경에 따라)

배포판/클러스터 구성에 따라 crictl을 사용합니다.

# 미사용 이미지 정리(환경에 따라 동작 상이)
sudo crictl images
sudo crictl rmi --prune

EKS/쿠버네티스에서 노드 리소스 고갈은 다양한 형태로 표출됩니다. 네트워크/인증 지연이 섞이면 원인 추적이 더 어려워지므로, 예를 들어 NAT/DNS/PrivateLink 병목은 EKS Pod STS AssumeRole 타임아웃 - NAT·PrivateLink·DNS처럼 별도로 분리 진단하는 습관이 도움이 됩니다.

6) 복구 확인: inode가 실제로 돌아왔는지 검증

정리 후에는 반드시 inode 회복을 확인합니다.

sudo df -i

# 특정 마운트만 보고 싶다면
sudo df -i / /var /tmp

또한 애플리케이션이 정상적으로 파일을 생성하는지(로그/업로드/세션/빌드 산출물 등) 기능 확인을 합니다.

7) 재발 방지: inode 고갈은 “설계/운영” 문제다

inode 고갈은 단발성 청소로 끝나지 않습니다. 원인을 제거하지 않으면 반복됩니다.

7-1) 로그/임시파일 정책을 코드와 설정으로 고정

  • logrotate 정책 명시(보관 기간, 압축, 크기 기준)
  • 애플리케이션 로그 레벨/샘플링(디버그 폭주 방지)
  • /tmp 사용 시 TTL/정리 작업(크론 또는 systemd-tmpfiles)

systemd-tmpfiles로 /tmp 자동 정리 예시

배포판에 따라 /usr/lib/tmpfiles.d/tmp.conf 또는 /etc/tmpfiles.d/*.conf로 관리합니다.

# 예: /etc/tmpfiles.d/mytmp.conf
# 10일 지난 /tmp/myapp 아래 파일/디렉터리 정리
# d: 디렉터리 생성(권한/소유), D: 디렉터리 및 내용 정리 정책
D /tmp/myapp 0755 myuser mygroup 10d

적용:

sudo systemd-tmpfiles --clean

7-2) 모니터링: 디스크(용량)와 inode를 분리해서 알림

많은 모니터링은 df -h 기반이라 inode 고갈을 놓칩니다.

  • 노드/서버별 inode 사용률(IUse%) 메트릭 수집
  • 70%/85%/95% 단계별 알림
  • /var, /tmp, 컨테이너 런타임 경로는 별도 임계치

간단한 크론 체크 스크립트 예시:

#!/usr/bin/env bash
set -euo pipefail

THRESHOLD=90

while read -r fs inodes iused ifree iuse mnt; do
  [[ "$fs" == "Filesystem" ]] && continue
  pct=${iuse%%%}
  if (( pct >= THRESHOLD )); then
    echo "[ALERT] inode usage ${pct}% on ${mnt} (${fs})"
  fi
done < <(df -iP)

7-3) 파일시스템 설계: inode가 넉넉한 포맷/마운트 고려

  • ext4는 생성 시 inode 수가 결정됩니다(기본 비율). “작은 파일이 매우 많은 워크로드”라면 포맷 옵션을 검토해야 합니다.
  • XFS는 inode가 동적으로 관리되어(ext4 대비) inode 고갈 이슈가 덜한 편이지만, 운영 표준/복구 절차와 함께 판단해야 합니다.
  • 컨테이너 워크로드는 /var/lib/docker 같은 경로를 별도 디스크로 분리하면 장애 격리가 쉬워집니다.

8) 장애 대응 체크리스트(요약)

  • df -hdf -i를 함께 확인했는가?
  • inode 100%인 마운트 지점이 어디인지 특정했는가?
  • find ... | wc -l로 파일 수가 비정상적인 디렉터리를 찾았는가?
  • lsof +L1로 삭제된 파일을 잡고 있는 프로세스가 있는지 확인했는가?
  • 로그/임시/캐시/컨테이너 찌꺼기 중 무엇이 원인인지 분류했는가?
  • 정리 후 df -i로 회복을 검증했는가?
  • logrotate/systemd-tmpfiles/모니터링으로 재발 방지를 걸었는가?

마무리

inode 고갈은 “디스크 100%”처럼 보이지만, 실제로는 파일 개수 한도에 부딪힌 상태입니다. df -i로 빠르게 판별하고, 파일 수 폭증 지점을 찾아 안전하게 정리한 뒤, 로그·임시파일 정책과 모니터링을 붙이면 재발을 크게 줄일 수 있습니다. 특히 컨테이너/마이크로서비스 환경에서는 캐시·레이어·로그가 분산되어 쌓이기 쉬우니, inode 알림을 기본으로 넣어두는 것이 운영 비용을 줄이는 가장 확실한 방법입니다.