No space left on device다. 단순히 디스크가 꽉 찬 경우도 있지만, df -h로 확인하면 용량이 남아있는데도 이 에러가 뜨는 경우가 있다. Rocky Linux 9 환경에서 4가지 원인을 직접 재현하고 해결한다.$ touch /tmp/newfile touch: cannot touch '/tmp/newfile': No space left on device $ echo "data" > /var/log/app.log bash: /var/log/app.log: No space left on device
파일을 생성하거나 쓸 수 없는 상태다. 원인은 크게 4가지로 나뉜다.
| # | 원인 | 핵심 진단 명령어 | 특징 |
|---|---|---|---|
| 1 | 진짜 디스크 용량 부족 | df -h | Use% 100% |
| 2 | inode 고갈 | df -i | 용량은 남아있는데 에러 |
| 3 | 삭제한 파일이 용량을 잡고 있음 | lsof +L1 | rm 후에도 용량 안 늘어남 |
| 4 | /var/log 로그 폭증 | du -sh /var/log/* | 로그가 GB 단위로 쌓임 |
가장 단순한 경우다. 디스크 파티션이 100% 찬 상태다. 로그 파일이 무한정 쌓이거나, 대용량 파일이 생성됐거나, 백업 파일을 정리하지 않았을 때 발생한다.
$ df -h Filesystem Size Used Avail Use% Mounted on /dev/sda1 20G 20G 0 100% / tmpfs 3.9G 0 3.9G 0% /dev/shm
Use% 100% — 디스크가 꽉 찼다. 이제 어디서 용량을 먹고 있는지 찾아야 한다.
$ du -sh /* 2>/dev/null | sort -rh | head -10 8.5G /var 4.2G /usr 3.1G /home 2.8G /opt $ du -sh /var/* | sort -rh | head -5 7.9G /var/log 312M /var/cache 180M /var/lib
/var/log가 7.9G나 차지하고 있다. 더 파고들어보자.
$ find / -xdev -type f -size +50M -exec ls -lh {} \; 2>/dev/null -rw-r--r-- 1 root root 3.2G /var/log/messages -rw-r--r-- 1 root root 2.1G /var/log/secure -rw-r--r-- 1 root root 1.8G /var/log/app/error.log
rm이 아니라 truncate로 비워야 한다. 프로세스가 파일을 열고 있으면 삭제해도 용량이 회수되지 않는다 (원인 3에서 자세히 설명).$ truncate -s 0 /var/log/messages $ truncate -s 0 /var/log/secure $ df -h / Filesystem Size Used Avail Use% Mounted on /dev/sda1 20G 12G 6.7G 64% /
ncdu를 설치하면 인터랙티브하게 용량을 분석할 수 있다. dnf install -y ncdu && ncdu / — 방향키로 디렉토리를 탐색하면서 어디가 큰지 직관적으로 파악 가능하다.이것이 가장 헷갈리는 케이스다. df -h로 확인하면 용량은 충분한데 파일을 만들 수 없다. 많은 사람들이 이 상황에서 "디스크 남아있는데 왜?"하고 멘붕에 빠진다.
파일 시스템은 파일 하나당 inode 하나를 사용한다. inode는 파일의 메타데이터(권한, 소유자, 위치 등)를 저장하는 자료구조인데, 파일 시스템 생성 시 개수가 고정된다. 파일 크기가 0바이트여도 inode 하나를 소비한다. 그래서 작은 파일이 수십만 개 쌓이면 용량은 남아있어도 inode가 바닥날 수 있다.
핵심은 df -h와 df -i를 둘 다 확인하는 것이다.
$ df -h / Filesystem Size Used Avail Use% Mounted on /dev/sda1 20G 8.0G 11G 43% / # ← 용량 여유 충분! $ df -i / Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 1310720 1310720 0 100% / # ← inode 완전 소진!
df -h: Use% 43% — 용량 여유 충분. df -i: IUse% 100% — inode 완전 소진. 용량은 남았지만 inode가 0이라 새 파일을 만들 수 없는 것이다.
$ find / -xdev -type d -exec sh -c 'echo "$(find "$1" -maxdepth 1 -type f | wc -l) $1"' _ {} \; 2>/dev/null | sort -rn | head -10 524288 /var/spool/postfix/maildrop 412000 /tmp/sess 89320 /var/lib/php/sessions
/var/spool/postfix/maildrop에 52만 개의 파일이 쌓여있었다. 미발송 메일 큐가 원인이었다.
$ find /var/spool/postfix/maildrop -type f -delete $ df -i / Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 1310720 501320 809400 38% /
/var/spool/postfix/maildrop), PHP 세션 파일(/var/lib/php/sessions), 임시 파일(/tmp), 캐시 파일이 소규모로 대량 생성되는 경우.큰 로그 파일을 rm으로 삭제했는데, df -h로 확인하면 용량이 그대로인 경우다. 이건 많은 사람들이 겪는 함정이다.
이유는 간단하다. 파일을 삭제해도 프로세스가 해당 파일을 열고 있으면 실제로는 디스크에서 제거되지 않는다. 파일은 (deleted) 상태로 남아서 용량을 계속 차지한다. 프로세스가 파일 핸들을 놓을 때까지(= 프로세스가 종료되거나 재시작될 때까지) 디스크 공간은 회수되지 않는다.
$ lsof +L1 2>/dev/null | head -10 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME tail 450 root 3r REG 0,127 52428800 0 2890 /var/log/biglog (deleted) java 1280 app 12w REG 8,1 2147483648 0 3921 /var/log/app.log (deleted)
(deleted)가 보이면 파일은 삭제되었지만 프로세스가 아직 잡고 있는 상태다. SIZE/OFF 열을 보면 해당 파일이 차지하는 용량을 알 수 있다.
가장 깔끔한 방법이다. 프로세스를 재시작하면 파일 핸들이 해제되면서 용량이 즉시 회수된다.
$ kill 450 $ systemctl restart app-service $ df -h / Filesystem Size Used Avail Use% Mounted on /dev/sda1 20G 12G 6.7G 64% /
프로덕션에서 프로세스를 재시작할 수 없는 상황이라면, /proc을 통해 열린 파일 디스크립터를 직접 비울 수 있다.
# PID 1280이 잡고 있는 fd 12를 비우기 $ : > /proc/1280/fd/12 $ df -h / # 용량 즉시 확보됨
rm 대신 truncate -s 0으로 비워라. 파일을 삭제하지 않고 내용만 비우기 때문에 프로세스가 열고 있어도 안전하다. rm은 파일 자체를 삭제하므로 프로세스가 잡고 있으면 용량이 회수되지 않는다.애플리케이션이 에러 로그를 쏟아내거나, logrotate가 제대로 동작하지 않으면 /var/log가 기하급수적으로 커진다. 특히 애플리케이션이 무한 루프에 빠져서 에러를 반복 출력하는 경우, 몇 시간 만에 디스크가 꽉 차는 일이 실제로 발생한다.
$ du -sh /var/log/ 7.9G /var/log/ $ du -sh /var/log/* | sort -rh | head -5 3.2G /var/log/messages 2.1G /var/log/secure 1.8G /var/log/app/error.log 512M /var/log/journal 180M /var/log/audit
$ find /var/log -type f -size +50M -exec ls -lh {} \; -rw-r--r-- 1 root root 3.2G /var/log/messages -rw-r--r-- 1 root root 2.1G /var/log/secure -rw-r--r-- 1 root root 1.8G /var/log/app/error.log
# rm이 아닌 truncate 사용! $ truncate -s 0 /var/log/messages $ truncate -s 0 /var/log/secure # journal 로그 정리 (100MB만 유지) $ journalctl --vacuum-size=100M Vacuuming done, freed 420.0M of archived journals. $ du -sh /var/log/ 1.2G /var/log/
즉시 조치만으로는 또 쌓인다. logrotate를 설정해서 로그가 일정 크기 이상 커지면 자동으로 회전(rotate)시켜야 한다. copytruncate 옵션이 핵심인데, 로그 파일을 복사한 뒤 원본을 비우는 방식이라 프로세스 재시작 없이 로테이션이 가능하다.
/var/log/app/*.log {
daily # 매일 로테이션
rotate 7 # 7일치만 보관
compress # gz 압축
missingok # 파일 없어도 에러 안 냄
notifempty # 빈 파일은 스킵
maxsize 100M # 100MB 넘으면 일자 관계없이 로테이션
copytruncate # 복사 후 원본 비우기 (프로세스 재시작 불필요)
}
logrotate -d /etc/logrotate.d/app으로 dry run을 돌려서 설정이 제대로 적용되는지 미리 확인할 수 있다.No space left on device를 만났을 때, 이 순서대로 확인하면 원인을 빠르게 찾을 수 있다.
# 디스크 용량 확인 $ df -h # inode 확인 $ df -i # 디렉토리별 용량 (상위 10개) $ du -sh /* 2>/dev/null | sort -rh | head -10 # 큰 파일 찾기 (100MB 이상) $ find / -xdev -type f -size +100M -exec ls -lh {} \; # 삭제됐지만 열려있는 파일 $ lsof +L1 # 로그 비우기 (안전) $ truncate -s 0 /var/log/messages # journal 정리 $ journalctl --vacuum-size=100M # 로그 파일 실시간 증가 감시 $ watch -n 1 'du -sh /var/log/*'
| 항목 | 버전 |
|---|---|
| OS | Rocky Linux 9.3 (Blue Onyx) |
| Filesystem | XFS / ext4 |
df -i로 inode 사용률을 확인하라. IUse%가 100%면 inode가 고갈된 것이다. 파일 크기와 관계없이 파일 하나당 inode 하나를 소비하기 때문에, 작은 파일이 대량으로 쌓이면 용량은 남아있어도 새 파일을 만들 수 없다.rm은 파일 자체를 삭제한다. 하지만 프로세스가 파일을 열고 있으면 디스크 공간이 회수되지 않는다. truncate -s 0은 파일을 삭제하지 않고 내용만 비운다. 파일 핸들이 유지되기 때문에 프로세스가 열고 있어도 안전하고, 용량도 즉시 확보된다. 로그 파일은 항상 truncate를 쓰는 게 맞다.(deleted) 표시가 붙어 있으면 파일은 디렉토리에서 사라졌지만 디스크 공간을 계속 차지하고 있다는 뜻이다.maxpct 설정이 낮으면(기본 25%) inode 할당 가능 공간이 제한된다. xfs_growfs -m으로 비율을 늘릴 수 있다. ext4는 파일 시스템 생성 시 inode 수가 고정되므로 포맷 전에 mkfs.ext4 -N으로 충분히 설정해야 한다.node_filesystem_avail_bytes 메트릭으로 남은 용량을, node_filesystem_files_free로 남은 inode를 모니터링할 수 있다. Grafana에서 80% 이상일 때 알림을 설정하면 디스크가 꽉 차기 전에 대응할 수 있다.df -h와 df -i를 둘 다 확인해야 한다 — 용량이 남아있어도 inode가 바닥나면 같은 에러가 뜬다rm이 아니라 truncate -s 0으로 비워야 한다 — 프로세스가 열고 있으면 rm으로는 용량이 회수되지 않는다lsof +L1로 삭제됐지만 용량을 잡고 있는 파일을 찾고, 프로세스 재시작 또는 /proc truncate로 해결한다copytruncate 옵션으로 프로세스 재시작 없이 로테이션 가능