Published on

리눅스 디스크 100%인데 용량이 안 보일 때 해결

Authors

서버 운영 중 가장 당황스러운 순간 중 하나가 df -h는 디스크 100%를 가리키는데, 정작 du -sh /*로 훑어보면 “그만한 용량이 어디에도 없다”는 상황입니다. 이 현상은 리눅스의 파일 삭제/링크(inode) 동작, 파일시스템 예약 블록, 컨테이너 오버레이, 마운트 포인트 착시 등이 겹치며 자주 발생합니다.

이 글에서는 (1) df vs du의 관점 차이를 먼저 정리하고, 이어서 **lsof로 ‘삭제됐지만 열려있는 파일’**을 찾는 방법, inode 고갈 진단, 마운트/오버레이 착시까지 포함해 실전 트러블슈팅 루틴을 제공합니다.

> 운영 환경에서 디스크 이슈는 장애 전파가 빠릅니다. 관련해서 “타임아웃/데드라인 전파”처럼 연쇄 장애를 줄이는 설계 관점은 gRPC 타임아웃 지옥 탈출 - 데드라인 전파 설계도 참고할 만합니다.

1) 먼저 확인: df와 du가 왜 다르게 보이나

  • df: 파일시스템 전체 관점(블록 단위)에서 사용량을 봅니다. 삭제된 파일이라도 어떤 프로세스가 파일 디스크립터로 잡고 있으면 블록은 계속 점유합니다.
  • du: 디렉터리 트리 기준으로 파일을 따라가며 합산합니다. 이미 unlink(삭제)되어 디렉터리 엔트리에서 사라진 파일은 du로는 보이지 않습니다.

즉, df=100%인데 du가 작게 나오면 1순위 의심은:

  1. 삭제된 파일을 프로세스가 계속 열고 있음 (가장 흔함)
  2. inode 고갈 (파일 수가 너무 많음)
  3. 다른 마운트 포인트/오버레이로 가려짐 (컨테이너/바인드 마운트)
  4. reserved blocks(예약 블록), 스냅샷, CoW 등 파일시스템 특성

이제부터는 “가장 빨리 원인을 좁히는 순서”로 진행합니다.

2) 5분 진단 루틴(명령어 체크리스트)

아래 순서대로 실행하면 대부분의 케이스를 빠르게 잡습니다.

2.1 어떤 파티션이 100%인지 확정

df -hT
# inode도 함께
df -ih
  • Use% 100%인 대상이 /, /var, /home, 특정 데이터 볼륨인지 확인
  • df -ih에서 IUse% 100%면 용량이 아니라 inode 문제일 가능성이 큽니다(뒤에서 다룸).

2.2 du는 “같은 파일시스템만” 보도록 제한

du가 다른 마운트까지 따라가면 합산이 왜곡됩니다. 대상 파일시스템 내부만 보려면 -x를 사용하세요.

# 루트 파티션 기준(다른 마운트는 제외)
sudo du -xhd1 / 2>/dev/null | sort -h

# /var가 별도 파티션이면 /var에서 수행
sudo du -xhd1 /var 2>/dev/null | sort -h

여기서도 큰 범인이 안 보이면 다음 단계로.

3) 가장 흔한 원인: 삭제됐지만 열려있는 파일(lsof)

리눅스에서 파일을 rm하면 디렉터리 엔트리만 제거(unlink)됩니다. 하지만 어떤 프로세스가 그 파일을 열고 있으면 실제 데이터 블록은 마지막 핸들이 닫힐 때까지 유지됩니다.

이 경우 du는 파일을 못 찾지만 df는 블록 사용량을 계속 포함합니다.

3.1 lsof로 (deleted) 파일 찾기

sudo lsof +L1
# 또는
sudo lsof | grep -E "\(deleted\)"
  • +L1은 링크 수(link count)가 1 미만인(즉, 디렉터리에서 사라진) 열린 파일을 보여줍니다.
  • 출력에서 SIZE/OFF가 큰 항목이 범인일 확률이 높습니다.

예시로 이런 형태를 보게 됩니다:

  • 프로세스: java, python, nginx, dockerd, containerd, rsyslogd
  • 파일: /var/log/app.log (deleted)

3.2 해결: 프로세스를 재시작하거나 FD를 닫기

가장 안전한 해결은 해당 프로세스 재시작입니다.

# systemd 서비스라면
sudo systemctl restart <service>

# 프로세스만 확인
ps -fp <PID>

서비스 재시작이 어려운 경우(예: 대형 JVM), 임시로 파일 디스크립터를 직접 비우는 방식도 있지만 운영 리스크가 큽니다. 그래도 불가피하다면(원리 이해 후) 다음처럼 해당 FD에 대해 truncate를 수행할 수 있습니다.

# PID가 1234이고, 열린 파일 디스크립터가 5라면
sudo sh -c ': > /proc/1234/fd/5'
  • 이 방법은 “열려있는 파일”의 내용을 0으로 만들어 블록을 반환시킬 수 있습니다.
  • 단, 애플리케이션이 로그/데이터 파일로 사용 중이면 예상치 못한 동작(로그 손실, 데이터 손상)을 유발할 수 있으니 최후의 수단으로만 고려하세요.

3.3 로그 로테이션 설정 점검(logrotate)

이 문제가 반복된다면 대개 로그 로테이션이 잘못되어 있습니다.

  • 애플리케이션이 copytruncate가 필요한 방식인데 rename만 하고 프로세스가 파일 핸들을 계속 잡는 경우
  • 로테이트 후 postrotate에서 kill -HUP/reload를 안 해서 파일 핸들이 갱신되지 않는 경우

/etc/logrotate.d/*를 점검하고, 서비스별 권장 로테이션 방식을 따르세요.

4) inode 고갈: “용량은 남았는데 디스크가 가득 찼다”

df -h는 여유가 있어 보이는데도 파일 생성이 실패하거나(“No space left on device”), 반대로 df가 100%로 보이는데 du가 애매한 경우도 있습니다. 이때 df -ih로 inode를 봐야 합니다.

4.1 inode 사용량 확인

df -ih

IUse%가 100%면 파일을 더 만들 수 없습니다. 작은 파일이 수백만 개 쌓이면 이런 일이 생깁니다.

4.2 inode를 많이 쓰는 디렉터리 찾기

용량이 아니라 “파일 개수”를 세어야 합니다.

# 상위 1레벨 디렉터리별 파일 수(대략)
for d in /var/*; do
  echo -n "$d: "
  sudo find "$d" -xdev -type f 2>/dev/null | wc -l
done | sort -n -t: -k2

또는 특정 경로에서 상위 디렉터리별 inode 소비를 빠르게 보고 싶다면:

sudo find /var -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -n | tail

4.3 흔한 원인과 조치

  • /tmp, /var/tmp에 임시 파일 폭증
  • /var/lib/docker 또는 컨테이너 런타임 레이어/캐시(작은 파일 다량)
  • 앱의 캐시/세션 디렉터리(파일 기반)
  • CI 작업 디렉터리(빌드 산출물 누적)

조치는 “삭제”가 기본이지만, inode가 자주 고갈되면:

  • 캐시를 파일이 아닌 DB/오브젝트 스토리지로 전환
  • 주기적 정리(cron/systemd timer)
  • 파일시스템 재구성 시 inode 밀도 고려(ext4 mkfs 옵션)

5) 마운트/오버레이 착시: du가 못 보는 공간

5.1 다른 파일시스템이 덮어쓴 경우

예를 들어 /var/log 아래에 별도 마운트가 올라가면, 원래 /var/log에 있던 데이터가 “가려져” 보이지 않을 수 있습니다.

mount | column -t
findmnt -T /var/log
  • 마운트 전 원래 경로에 쌓인 데이터는 마운트를 내리기 전까지 접근이 어렵습니다.
  • du/에서 돌릴 때 -x를 안 주면 다른 파일시스템까지 합산되어 혼란이 커집니다.

5.2 컨테이너 환경(overlay2)에서의 함정

도커/컨테이너d는 보통 /var/lib/docker 또는 /var/lib/containerd 아래에 overlay 스토리지를 둡니다. 이미지 레이어, 로그, 빌드 캐시가 쌓이면 df가 먼저 터집니다.

# Docker 사용 시
sudo docker system df
sudo docker system prune -af --volumes

# containerd(환경별 상이)
sudo du -sh /var/lib/containerd 2>/dev/null

운영 클러스터에서 무작정 prune는 위험합니다. 노드 역할/워크로드 특성에 맞춰 정리 정책을 설계해야 합니다. (쿠버네티스 관련 장애 진단 글로는 AKS ImagePullBackOff - ACR 권한·토큰 만료 진단처럼 “원인-증상 분리” 접근이 도움이 됩니다.)

6) 파일시스템 예약 블록(reserved blocks)로 100%처럼 보이는 경우

ext 계열 파일시스템은 기본적으로 root용으로 일부 블록을 예약합니다(보통 5%). 작은 볼륨에서는 이게 체감상 크게 보일 수 있습니다.

# ext4 예시: 예약 블록 확인
sudo tune2fs -l /dev/sdXN | grep -E 'Reserved block count|Block count|Reserved block percentage'

예약 비율을 줄일 수도 있지만(예: 데이터 볼륨), 시스템 파티션에서는 무리하게 줄이면 장애 시 복구 여지가 줄어듭니다.

# 예약 비율을 1%로(데이터 볼륨에서만 신중히)
sudo tune2fs -m 1 /dev/sdXN

7) 그래도 안 잡히면: “df와 du의 차이”를 수치로 대조

아래는 한 파일시스템에서 df 사용량과 du 합계를 비교해 갭이 큰지 확인하는 패턴입니다.

# 대상 마운트가 /var라고 가정
DF_USED=$(df -B1 /var | awk 'NR==2{print $3}')
DU_USED=$(sudo du -sxB1 /var 2>/dev/null | awk '{print $1}')
echo "df used: $DF_USED"
echo "du used: $DU_USED"
echo "gap  : $((DF_USED - DU_USED)) bytes"
  • gap이 크면: (deleted) 열린 파일, 스냅샷/CoW, 메타데이터 오버헤드 등을 더 의심
  • gap이 작으면: 단순히 du를 잘못 돌렸거나(다른 마운트 포함/제외), 특정 디렉터리 권한 때문에 누락되었을 가능성

권한 누락을 줄이려면 sudo로 실행하고, 에러를 숨기지 말고 확인하세요.

sudo du -xhd1 /var | sort -h

8) 운영에서의 재발 방지 체크리스트

  • 로그 로테이션이 “프로세스 핸들 갱신”까지 포함하는지 점검(postrotate, reload/HUP)
  • /var/log, /tmp, 앱 캐시 디렉터리에 상한(보관일/용량)을 정책화
  • 컨테이너 노드는 이미지/빌드 캐시 정리 전략 수립(무작정 prune 금지)
  • inode 모니터링 추가: node_filesystem_files_free, node_filesystem_files (node-exporter)
  • 디스크 90% 경보 시 즉시 lsof +L1 자동 수집(사후 분석에 결정적)

9) 정리: 원인별 “가장 빠른 한 방”

  • df=100%, du로 큰 파일이 안 보임 → sudo lsof +L1로 (deleted) 열린 파일 찾기 → 서비스 재시작
  • “No space left on device”인데 용량은 남음 → df -ih로 inode 확인 → 파일 수 폭증 디렉터리 정리
  • 컨테이너 노드 디스크 급증 → /var/lib/docker|containerd 점검, 이미지/로그/볼륨 정책화
  • 작은 볼륨에서 100%처럼 보임 → ext 예약 블록 확인(tune2fs)

이 루틴만 몸에 익혀도 “디스크 100%인데 용량이 안 보이는” 대부분의 케이스를 10분 안에 해결할 수 있습니다.