Published on

No space left on device인데 용량 남을 때 - inode 0% 해결

Authors

서버에서 No space left on device가 떴는데 df -h로 보면 디스크 용량이 넉넉한 경우가 있습니다. 이때 대부분의 범인은 inode 고갈입니다. 파일 시스템은 “바이트 용량”뿐 아니라 “파일 개수(=inode)”도 한정되어 있고, 작은 파일이 폭증하면 바이트는 남아도 새 파일을 만들 수 없어 동일한 에러가 발생합니다.

이 글은 다음을 목표로 합니다.

  • 용량은 남는데 왜 에러가 나는지 원리 이해
  • inode 0%(또는 IUse 100%) 상황을 빠르게 진단
  • 로그/캐시/임시파일/컨테이너 레이어 등 흔한 원인별 정리
  • 즉시 복구(응급조치) + 재발 방지(운영 설계)

> 운영 환경에서 이런 문제는 대개 “파일이 너무 많아져서” 발생합니다. EKS/컨테이너 환경에서도 노드의 /var/lib/containerd, /var/log, emptyDir 등에 작은 파일이 쌓이며 동일하게 터질 수 있습니다. 관련 트러블슈팅 관점은 EKS에서 503 Service Unavailable 원인 10분 진단처럼 “증상→원인 후보→검증” 흐름을 적용하면 빠르게 좁힐 수 있습니다.

1) 핵심 원리: 디스크 용량 vs inode

  • 디스크 용량(Blocks): 파일 내용이 차지하는 바이트
  • inode: 파일 메타데이터(권한/소유자/타임스탬프/블록 포인터 등)

파일 하나당 inode가 1개 필요합니다. 예를 들어 1KB짜리 파일 1,000만 개는 용량은 10GB 수준일 수 있지만 inode는 1,000만 개를 소모합니다. inode가 바닥나면 커널은 새 파일 생성/링크/임시파일 생성 등을 실패시키며 ENOSPC(No space left on device)를 반환합니다.

inode 고갈이 특히 잘 나는 패턴

  • 애플리케이션이 요청당 임시파일을 생성하고 삭제를 누락
  • 로그가 파일 단위로 쪼개져 쌓임(예: request-id별 파일)
  • 스풀 디렉터리(/var/spool/*), 큐, 세션 파일이 무한 증가
  • 컨테이너 런타임 저장소(/var/lib/docker, /var/lib/containerd)에 레이어/스냅샷/로그가 누적
  • CI/CD 워크스페이스에 작은 아티팩트가 폭증

2) 1분 진단: 진짜 inode가 문제인지 확인

2.1 df -hdf -i를 함께 본다

# 용량(바이트) 기준
sudo df -h

# inode(파일 개수) 기준
sudo df -i

df -i 출력에서 특정 마운트의 IUse%가 100%라면 inode 고갈입니다.

2.2 에러가 발생한 경로가 어느 파일시스템인지 확인

# 문제가 난 디렉터리가 어느 마운트에 속하는지
df -h /문제/경로
df -i /문제/경로

실무에서 흔한 함정은 /tmp가 별도 파티션이거나, 컨테이너 환경에서 /var/lib/...가 별도 디스크(EBS)로 마운트되어 있다는 점입니다. “전체 디스크는 여유”인데 “특정 마운트만 inode 100%”일 수 있습니다.

2.3 inode를 잡아먹는 디렉터리 찾기(파일 개수 기준)

용량이 아니라 파일 개수가 핵심이므로 du보다 “파일 수 카운트”가 유효합니다.

# 최상위에서 디렉터리별 파일 개수(대략적)
# -x: 다른 파일시스템으로 넘어가지 않게(중요)
# 주의: 파일이 매우 많으면 시간이 걸릴 수 있음
sudo bash -c 'for d in /*; do echo -n "$d "; find "$d" -xdev -type f 2>/dev/null | wc -l; done' | sort -k2 -n

특정 경로가 의심되면 더 좁혀봅니다.

sudo find /var/log -xdev -type f | wc -l
sudo find /var/lib -xdev -type f | wc -l
sudo find /tmp -xdev -type f | wc -l

3) 응급 복구: “inode를 즉시 확보”하는 방법

3.1 가장 효과적인 1차 조치: 불필요한 작은 파일 삭제

대표적으로 다음을 먼저 의심합니다.

  • 오래된 로그(압축/로테이션 미적용)
  • 임시 디렉터리(/tmp, /var/tmp)에 쌓인 찌꺼기
  • 애플리케이션 캐시/세션

예시(로그 7일 초과 삭제):

# /var/log 하위에서 7일 초과 파일 삭제
sudo find /var/log -xdev -type f -mtime +7 -print -delete

예시(tmp 3일 초과 삭제):

sudo find /tmp -xdev -type f -mtime +3 -print -delete
sudo find /var/tmp -xdev -type f -mtime +3 -print -delete

> 주의: 서비스가 사용하는 경로를 무턱대고 지우면 장애가 커집니다. 먼저 -print로 목록을 확인하고, 애플리케이션/런타임 특성을 파악한 뒤 삭제하세요.

3.2 “삭제했는데도” inode가 안 돌아온다? 열린 파일 핸들 문제

리눅스는 프로세스가 파일을 열고 있는 상태라면, 파일을 rm으로 지워도 디스크(또는 inode/블록)가 즉시 반환되지 않을 수 있습니다(정확히는 디렉터리 엔트리는 제거되지만, 참조가 남아 공간이 해제되지 않음).

다음으로 “삭제됐지만 열린 상태인 파일”을 찾습니다.

# (deleted)로 표시되는 열린 파일 확인
sudo lsof +L1

# 특정 마운트에서만 보고 싶다면
sudo lsof +L1 /var

해결은 보통 해당 프로세스를 재시작하거나, 로그 파일을 사용하는 데몬의 경우 **logrotate 신호(예: nginx -s reopen)**를 보내는 방식입니다.

3.3 컨테이너 노드에서 자주 터지는 위치

  • Docker: /var/lib/docker
  • containerd: /var/lib/containerd
  • kubelet: /var/lib/kubelet
  • 로그: /var/log/containers, /var/log/pods

컨테이너 런타임 정리 예시:

# Docker 사용 시: 안 쓰는 이미지/컨테이너/네트워크 정리
sudo docker system prune -af

# containerd(crictl) 사용 시: 미사용 이미지 정리(환경에 따라 다름)
sudo crictl images
sudo crictl rmi --prune

노드가 EKS라면, 장애가 난 노드만 드레이닝 후 재부팅/교체하는 운영도 흔합니다. 이런 유형의 “노드 리소스 고갈”은 네트워크 자원 고갈(IP)과도 결이 비슷한데, 진단 접근은 EKS VPC CNI IP 누수로 Pod IP 고갈 해결하기처럼 “고갈 지표 확인→고갈 원인 추적→재발 방지”로 가져가면 좋습니다.

4) 근본 원인 찾기: 어떤 파일이 inode를 잡아먹었나

4.1 “파일이 많은 디렉터리” Top N 찾기

깊이 2~3 정도로만 빠르게 훑는 방식이 현실적입니다.

# /var 아래에서 depth 3까지 디렉터리별 파일 개수 집계(시간 주의)
sudo find /var -xdev -type f -printf '%h\n' 2>/dev/null \
  | awk -F/ 'NF{print "/"$2"/"$3"/"$4}' \
  | sort | uniq -c | sort -nr | head -50

출력 상위에 뜨는 디렉터리를 기준으로 애플리케이션/에이전트(로그 수집기, 모니터링, 배치 작업) 설정을 확인합니다.

4.2 자주 보는 원인별 체크리스트

  • logrotate 미설정/실패: /etc/logrotate.d/*, journalctl --disk-usage 확인
  • 애플리케이션 임시파일: 업로드/압축/리포트 생성 작업의 실패 시 cleanup 누락
  • 메일 스풀: /var/spool/postfix, /var/spool/mail
  • 패키지/캐시: /var/cache/* (단, inode 폭증은 보통 캐시의 “파일 수”가 많을 때)
  • 코어덤프: /var/lib/systemd/coredump (용량도 같이 먹는 경우가 많음)

5) 재발 방지: 운영 설계(가장 중요)

5.1 로그 정책: 파일 개수 폭증을 막는다

  • “요청당 파일” 같은 설계를 피하고, 가능하면 단일 파일 + 로테이션
  • 로테이션은 “용량”뿐 아니라 “파일 개수” 관점에서 rotate 값을 관리

logrotate 예시:

/var/log/myapp/*.log {
  daily
  rotate 14
  compress
  missingok
  notifempty
  copytruncate
}

5.2 systemd-journald 사용 시 상한 설정

# 현재 사용량
journalctl --disk-usage

# 설정 파일 편집
sudo sed -i 's/^#\?SystemMaxUse=.*/SystemMaxUse=1G/' /etc/systemd/journald.conf
sudo systemctl restart systemd-journald

5.3 파일 시스템 선택/생성 시 inode 고려(ext4)

ext4는 파일시스템 생성 시 inode가 결정됩니다(일반적으로 충분하지만, 작은 파일이 비정상적으로 많은 워크로드면 부족할 수 있음).

  • “작은 파일 수천만 개”가 예상되면 별도 파티션을 두고 inode 밀도를 높여 생성 고려
  • 이미 만든 ext4의 inode 수는 온라인에서 쉽게 늘릴 수 없습니다(재생성/마이그레이션이 현실적)

새 파티션 생성 시(예시):

# inode 비율(-i) 조정: 바이트당 inode 하나를 얼마나 촘촘히 만들지
# 값이 작을수록 inode가 더 많이 생성됨(메타데이터 오버헤드 증가)
sudo mkfs.ext4 -i 16384 /dev/nvme1n1

5.4 모니터링: inode도 알람에 넣어야 한다

용량 알람만 걸어두면 inode 고갈은 예고 없이 터집니다.

  • 노드/서버별 df -iIUse%를 수집
  • 70/85/95% 3단계 알람 추천

간단한 크론 기반 체크 예시:

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

THRESHOLD=85

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

6) 자주 묻는 질문(실무 포인트)

Q1. df -h는 여유인데 왜 ENOSPC가 동일하게 뜨나요?

애플리케이션 입장에선 “새 파일 생성 실패”가 곧 ENOSPC입니다. 커널이 실패 이유를 inode 고갈/블록 고갈로 구분해주긴 하지만, 많은 라이브러리/툴은 이를 뭉뚱그려 No space left on device로 표시합니다.

Q2. inode 100%면 바로 서비스가 죽나요?

새 파일 생성이 필요한 순간부터 문제가 터집니다.

  • 로그 기록 실패 → 프로세스가 종료/크래시
  • 소켓/임시파일 생성 실패
  • DB/캐시가 임시 파일을 못 만들어 장애

장애 양상은 다양하지만 “갑자기 여러 컴포넌트가 연쇄적으로 이상”해지는 패턴이 흔합니다.

Q3. 삭제했는데도 회복이 안 되면?

  1. 다른 마운트가 문제인지 재확인(df -i /경로)
  2. (deleted) 열린 파일 확인(lsof +L1) 후 프로세스 재시작

이 두 가지가 대부분입니다.

마무리: 체크 순서(요약)

  1. df -h와 함께 반드시 df -i 확인
  2. 문제가 난 경로의 마운트 지점을 특정(df -i /path)
  3. 파일 개수 폭증 디렉터리를 찾고(find ... | wc -l), 안전하게 정리
  4. 삭제 후에도 회복이 없으면 lsof +L1로 열린 삭제 파일 점검
  5. logrotate/journald 상한 + inode 모니터링으로 재발 방지

inode 고갈은 “용량 관리”만 해서는 막기 어렵고, 워크로드 특성(작은 파일 폭증)을 이해해 설계/정책/모니터링을 함께 가져가야 재발을 끊을 수 있습니다.