df는 디스크가 꽉 찼다는데 du로 더해보면 용량이 안 맞습니다
Ubuntu 22.04, 톰캣에 올린 스프링부트 앱 서버입니다. 지난번에 로그 관련으로 몇 번 글 올렸던 그 서버인데, 이번엔 루트 파티션이 거의 꽉 찼다고 알람이 떴습니다. 서비스가 로그를 많이 남기는 편이라 로그 때문이겠거니 하고 들어가 봤습니다.
df로 보면 루트가 거의 100%입니다.
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p1 50G 47G 1.2G 98% /그런데 du로 루트 전체를 더해보면 20G대밖에 안 나옵니다. 20G가 넘게 차이가 납니다.
$ sudo du -xsh /
23G /
$ sudo du -xh --max-depth=1 / | sort -rh | head
12G /var
5.1G /usr
3.2G /opt
.../var/log 아래를 뒤져봐도 df가 말하는 47G만큼 큰 파일은 안 보입니다. logrotate는 돌고 있어서 오래된 로그는 .1, .2.gz로 정리돼 있고, 실제로 큰 로그 파일을 지워본 적도 있는데 df 수치는 그대로입니다.
reboot하면 공간이 돌아온다는 얘기는 봤는데, 운영 서버라 함부로 재부팅은 못 합니다. 지금 당장 원인을 특정하고 싶습니다. df와 du가 이렇게 크게 어긋나는 건 뭘 봐야 하고, 재부팅 없이 공간을 회수하려면 어디부터 확인해야 하는지 궁금합니다.
df와 du가 20G 넘게 어긋나고, 로그를 지워도 df가 안 줄고, 재부팅하면 돌아온다는 증상 세 개가 겹치면 거의 확정적으로 삭제됐지만 아직 열려 있는 파일(deleted but still open) 때문입니다. 지난번에 로그 많이 남긴다고 하셨으니, 스프링부트/톰캣 JVM이 로그 파일을 연 채로 쓰고 있는데 그 파일이 rm되거나 logrotate 과정에서 unlink된 경우로 보입니다.
원인을 정확히 짚으면 이렇습니다. df는 파일시스템이 커널 레벨에서 실제로 할당한 블록을 읽고, du는 디렉터리 트리를 돌면서 디렉터리 엔트리가 있는 파일만 더합니다. 리눅스는 파일을 rm해도 그 파일을 열고 있는 프로세스가 있으면 디렉터리 엔트리(링크)만 지우고 inode와 블록은 프로세스가 fd를 닫을 때까지 그대로 유지합니다. 그래서 디렉터리에서는 사라져 du에는 안 잡히지만(링크 수 0), 블록은 계속 점유돼 df에는 그대로 남습니다. 재부팅하면 공간이 돌아오는 이유도 이거라서, 프로세스가 죽으면서 fd가 닫히고 블록이 회수되는 겁니다. 큰 로그를 지워도 df가 꿈쩍 안 한 것도 같은 이유입니다.
재부팅 없이 지금 특정하는 순서입니다. lsof의 +L1은 링크 수가 1 미만(즉 0)인 열린 파일만 보여줍니다. 공식 설명대로 unlink된(삭제됐는데 열린) 파일만 골라냅니다.
# 링크 수 0인 열린 파일(삭제됐는데 점유 중) 전체
sudo lsof +L1
# 루트 파일시스템으로 한정해서 보기 (+a 로 필터 AND)
sudo lsof -a +L1 /출력에서 NLINK가 0이고 끝에 (deleted)로 표시된 줄을 보면 됩니다. SIZE 컬럼이 수십 G짜리로 크게 잡힌 놈이 범인입니다. PID와 COMMAND(java일 가능성이 큽니다), 그리고 FD 번호를 확인하세요. lsof가 없거나 설치가 꺼려지면 /proc로도 됩니다.
# 각 프로세스가 연 fd 중 (deleted) 표시된 것 찾기
sudo ls -l /proc/*/fd/ 2>/dev/null | grep '(deleted)'범인을 찾았으면 공간 회수 방법은 두 가지입니다. 제일 안전한 건 해당 프로세스를 정상 재기동하거나, 로깅 프레임워크가 지원하면 로그 파일을 다시 열게(reopen) 만드는 겁니다. 운영 서버라 재기동이 부담되면, rm은 이미 된 상태이므로 열려 있는 fd 자체를 truncate해서 블록만 즉시 비울 수 있습니다. 파일을 다시 지우는 게 아니라, 아직 열린 그 fd의 내용을 0으로 비우는 방식입니다.
# PID와 FD 번호를 위에서 확인한 값으로 바꿔서
sudo truncate -s 0 "/proc//fd/"
# 또는
sudo sh -c ': > /proc//fd/'이렇게 하면 프로세스는 같은 fd에 계속 쓰지만 점유 블록이 바로 풀려서 df가 즉시 내려갑니다. 다만 그 로그 파일을 아예 쓰지 않게 되는 건 아니라서, 프로세스가 append 모드가 아니라 오프셋 유지 모드로 쓰고 있으면 다음 쓰기 때 sparse hole이 생길 수 있습니다. 근본 해결은 logrotate 설정을 copytruncate로 바꾸거나, 로테이트 후 앱에 재오픈 시그널(logrotate의 postrotate에서 reload)을 보내 fd가 unlink된 파일을 붙들고 있지 않게 하는 겁니다.
참고로 df/du 차이가 큰데 lsof +L1에서 아무것도 안 나오면 그때는 다른 원인(마운트포인트 위에 다른 파일시스템이 겹쳐 마운트돼 가려진 디렉터리, 또는 예약 블록)을 봐야 하는데, 지금 증상(재부팅 시 회수, 로그 대량 생성)으로는 삭제된 열린 파일이 맞을 가능성이 큽니다.
확인에 참고한 공식 자료입니다. lsof FAQ (readthedocs), lsof(8) man page.
로그인 후 답글을 남길 수 있습니다.