- Published on
리눅스 디스크 100%인데 용량이 안 줄 때 - deleted-but-open(lsof)
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 df -h는 100%를 가리키는데, 분명히 로그/덤프 파일을 rm로 지웠고 du -sh로 봐도 큰 파일이 안 보이는 상황이 있습니다. 이때 많은 경우 원인은 deleted-but-open 파일입니다. 즉, 파일은 디렉터리 엔트리에서 삭제되었지만(그래서 du에 잘 안 잡힘), 어떤 프로세스가 그 파일을 열린 파일 디스크립터(FD) 로 계속 잡고 있어 커널이 블록을 회수하지 못하는 상태입니다.
이 글에서는 (1) 왜 이런 일이 생기는지, (2) lsof로 어떻게 정확히 찾아내는지, (3) 서비스 중단 없이/최소화하며 공간을 회수하는 방법, (4) 컨테이너·Kubernetes 환경에서의 변형 케이스까지 실전 중심으로 정리합니다.
1) 왜 rm 했는데도 디스크가 안 비나?
리눅스 파일 삭제는 “데이터 블록을 즉시 지우는” 동작이 아니라, 디렉터리에서 파일 이름(링크)을 제거하는 동작에 가깝습니다.
- 프로세스가 파일을 열면 커널은 inode와 데이터 블록을 참조하는 FD를 유지합니다.
rm은 파일 이름을 지우고 링크 카운트를 감소시킵니다.- 링크 카운트가 0이더라도 열려 있는 FD가 남아 있으면 커널은 그 inode/블록을 유지합니다.
- 그 프로세스가 종료되거나 FD를 닫을 때 비로소 블록이 회수되어
df사용량이 줄어듭니다.
그래서 다음과 같은 전형적인 증상이 나타납니다.
df -h: 사용량이 높고 줄지 않음du -sh /: 큰 사용량이 안 보이거나df와 차이가 큼- 로그 로테이션 직후, 애플리케이션이 오래된 로그 파일 핸들을 계속 잡고 있음
2) 먼저 증상 확인: df vs du 불일치
deleted-but-open을 의심하기 전에, 기본적으로 df와 du의 관점 차이를 확인해두면 디버깅이 빨라집니다.
# 파일시스템 관점(할당된 블록)
df -hT
# 디렉터리 트리 관점(보이는 파일 합)
du -xh / | sort -h | tail -n 30
df: 파일시스템이 “할당한 블록” 기준du: 현재 경로에서 “보이는 파일” 기준
rm로 삭제된 파일은 디렉터리에서 보이지 않으므로 du에 안 잡히지만, 블록은 여전히 할당 상태일 수 있어 df에는 남습니다.
3) 핵심 진단: lsof로 deleted-but-open 찾기
가장 확실한 방법은 lsof로 삭제되었지만 열려 있는 파일을 찾는 것입니다.
3.1 lsof로 (deleted) 파일 필터링
# (deleted) 표시가 있는 열린 파일 찾기
sudo lsof +L1 | grep -i deleted
+L1은 링크 카운트가 1 미만(즉 0)인 파일을 보여줍니다.- 출력에
deleted가 붙으면 거의 확정입니다.
자주 쓰는 형태는 다음입니다.
# 특정 마운트/경로에 대해서만 보고 싶을 때
sudo lsof +L1 /var | grep -i deleted
# 용량 큰 것부터 보고 싶을 때(간단 파이프)
sudo lsof +L1 | awk 'BEGIN{OFS="\t"} {print $7,$2,$1,$9}' | sort -n | tail -n 20
lsof의 SIZE/OFF(바이트) 컬럼이 큰데, 파일 경로가 ... (deleted)로 나오면 그 프로세스가 공간을 붙잡고 있습니다.
3.2 /proc로도 확인 가능
lsof가 없거나 최소 환경일 때는 /proc/<pid>/fd로 확인할 수 있습니다.
PID=1234
ls -l /proc/$PID/fd | grep deleted
심볼릭 링크가 ... (deleted)로 보이면 동일한 문제입니다.
4) 공간 회수 방법: “프로세스가 FD를 닫게” 만들기
해결은 단순합니다. 해당 프로세스가 그 파일 FD를 닫도록 해야 합니다. 방법은 상황에 따라 3가지로 나뉩니다.
4.1 가장 안전: 서비스 재시작(또는 로그 재오픈)
대부분의 케이스는 로그 파일입니다. 애플리케이션/데몬을 재시작하면 FD가 닫히면서 공간이 회수됩니다.
# systemd 서비스라면
sudo systemctl restart nginx
sudo systemctl restart your-app
# 재시작 후 확인
sudo lsof +L1 | grep -i deleted
df -h
재시작이 자주 필요하거나 재시작 루프가 엮여 있다면 systemd StartLimitHit 같은 이슈가 함께 발생할 수 있습니다. 그 경우는 systemd 재시작 루프(StartLimitHit) 해결법도 같이 참고하면 좋습니다.
4.2 무중단에 가깝게: 로그 재오픈 시그널(HUP/USR1)
일부 데몬은 재시작 없이 로그 파일을 다시 열도록 시그널을 지원합니다.
- nginx:
USR1(로그 reopen) - 많은 데몬:
HUP(설정 reload + 로그 reopen)
# nginx 예시: 마스터 PID에 USR1
sudo kill -USR1 $(cat /run/nginx.pid)
# 또는 HUP
sudo kill -HUP <pid>
이 방식은 서비스 중단을 최소화하면서 deleted FD를 정리할 수 있다는 장점이 있습니다.
4.3 최후의 수단: 열린 FD에 직접 truncate(주의)
프로세스를 재시작할 수 없고, 당장 디스크가 꽉 차 장애가 나는 상황이면 열린 FD를 0으로 truncate 해서 공간만 회수하는 방법이 있습니다.
lsof로 문제 FD의 PID를 찾고/proc/<pid>/fd/<fd>에 대해: >또는truncate수행
# 예: PID 1234가 FD 5로 큰 deleted 파일을 잡고 있을 때
sudo ls -l /proc/1234/fd/5
# 파일 내용을 0으로 만들어 블록 회수
sudo truncate -s 0 /proc/1234/fd/5
# 또는
sudo sh -c ': > /proc/1234/fd/5'
# 확인
sudo lsof -p 1234 | grep deleted
df -h
주의할 점:
- 이건 “파일을 닫는” 게 아니라 “내용을 비우는” 것입니다.
- 애플리케이션이 해당 FD에 계속 쓰면 다시 커질 수 있습니다.
- 데이터 파일(로그가 아닌 DB 파일 등)에 하면 치명적입니다.
따라서 로그/임시 파일임이 확실하고, 장애 회피 목적일 때만 제한적으로 사용하세요.
5) 자주 발생하는 원인 패턴
5.1 logrotate와 애플리케이션의 궁합 문제
logrotate가 파일을 rename/remove 했는데 애플리케이션이 새 로그 파일로 재오픈하지 않으면, 프로세스는 계속 “삭제된 옛 파일”에 씁니다.
대응:
- logrotate 설정에
postrotate에서kill -HUP또는 데몬별 reopen 시그널을 넣기 - 애플리케이션 로그 라이브러리 설정에서 파일 핸들 재오픈 옵션 확인
5.2 Java/Node/Python 앱의 stdout/stderr 리다이렉션
운영 중 nohup.out 같은 파일로 stdout이 연결되어 있고, 파일을 지워도 프로세스가 계속 쓰는 케이스가 있습니다.
- 해결: 프로세스 재시작 또는 출력 재구성(systemd journald 사용 등)
5.3 컨테이너/Kubernetes에서의 변형
컨테이너에서도 동일하게 발생합니다. 특히 다음 상황에서 자주 보입니다.
- 노드 디스크가 가득 차서 Pod가 이상 동작
- 컨테이너 내부에서 로그 파일을 직접 관리하며 rotate
진단 포인트:
- 문제는 보통 노드 파일시스템(예: /var/lib/docker, /var/lib/containerd, /var/log) 에서 발생
- 컨테이너 PID를 호스트에서 찾아
lsof로 확인해야 할 수 있음
Kubernetes에서 리소스 고갈/장애는 연쇄적으로 Pod 종료 지연, OOM, 스케줄 실패로 번질 수 있습니다. 비슷한 운영 디버깅 맥락으로는 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전도 함께 보면 좋습니다.
6) 실전 트러블슈팅 절차(체크리스트)
장애 상황에서 순서대로 진행하면 안전합니다.
6.1 1단계: 디스크/파티션 확인
df -hT
mount | column -t
어느 마운트가 찼는지 확정합니다. 루트(/)인지, /var인지, 별도 데이터 볼륨인지에 따라 범위가 달라집니다.
6.2 2단계: du로 “보이는” 대용량 제거 여지 확인
sudo du -xh /var | sort -h | tail -n 50
여기서도 안 나오는데 df가 높으면 deleted-but-open 가능성이 큽니다.
6.3 3단계: lsof로 deleted-but-open 확정
sudo lsof +L1 | grep -i deleted
- PID/COMMAND 확인
- SIZE/OFF가 큰 항목 우선
6.4 4단계: 영향도 낮은 방식부터 적용
- 로그 reopen 시그널
- 서비스 restart
- (긴급 시)
/proc/<pid>/fd/<fd>truncate
적용 후:
sudo lsof +L1 | grep -i deleted
df -h
7) 예방: “삭제 대신 truncate”와 로깅 아키텍처 정리
운영에서 재발을 줄이려면 다음을 권장합니다.
- 로그 파일을 애플리케이션이 직접 들고 쓰는 구조라면, rotate 후 reopen이 확실히 되도록 구성
- 가능하면 systemd 환경에서는 파일 로깅보다 journald로 수집하고, 외부로는 fluent-bit/vector 등으로 전달
- 임시 파일/덤프 파일은 생성 위치를 별도 볼륨로 분리(예:
/var/tmp전용 파티션) - 디스크 100%는 연쇄 장애를 부르므로 알람을 “90%/95%/98%” 단계로 분리
8) 요약
rm했는데df가 안 줄면 deleted-but-open을 의심합니다.sudo lsof +L1 | grep deleted가 가장 빠른 확정 방법입니다.- 해결은 “파일을 지우는 것”이 아니라 프로세스가 FD를 닫게 하는 것(재시작/로그 재오픈)입니다.
- 긴급 시
/proc/<pid>/fd/<fd>에 truncate로 공간만 회수할 수 있지만, 로그 등 안전한 대상에서만 제한적으로 사용해야 합니다.
운영 환경에서 이 문제는 한 번 겪으면 다음부터는 5분 안에 해결할 수 있는 유형입니다. 다만 DB 데이터 파일, 트랜잭션 로그 같은 민감한 파일에 대해 성급히 truncate하지 않도록, lsof 출력의 경로/프로세스를 반드시 확인하고 단계적으로 조치하세요.