- Published on
logrotate 후 디스크 100%? 열린 삭제파일 찾기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 logrotate를 돌린 직후 디스크 사용률이 갑자기 100%가 되는 경우가 있습니다. 그런데 이상하게도 du -sh /var/log/*로는 큰 파일이 안 보이거나, 지운 로그가 분명한데 df -h는 계속 꽉 찬 상태로 남습니다.
이 현상의 핵심은 “삭제됐지만 아직 열려 있는 파일(open deleted file)” 입니다. 리눅스 파일 시스템은 파일을 삭제해도, 그 파일을 열고 있는 프로세스가 파일 디스크립터(fd) 를 닫기 전까지 실제 블록을 해제하지 않습니다. 그래서 디스크는 계속 찬 것처럼 보이고, du는 경로 기반으로 합산하기 때문에(이미 디렉터리 엔트리가 사라짐) 원인을 못 찾습니다.
이 글에서는 (1) 증상 확인, (2) 열린 삭제 파일 찾기, (3) 즉시 공간 회수, (4) logrotate 설정으로 재발 방지까지 실전 절차로 정리합니다.
1) 증상 패턴: df는 100%인데 du는 멀쩡하다
대표적인 재현 시나리오는 다음과 같습니다.
- 애플리케이션이
/var/log/app.log에 계속 쓰고 있음 logrotate가app.log를app.log.1로 rename하고 새 파일 생성- 그런데 애플리케이션이 로그 파일을 재오픈(reopen) 하지 않아서
- 프로세스는 여전히 이전 inode(이제는 삭제되거나 이름이 바뀐 파일) 에 계속 write
- 결과적으로 디스크는 계속 차고, 경로에서 사라진 파일이라
du로는 안 잡힘
먼저 차이를 확인합니다.
df -h /
du -sh /var/log 2>/dev/null
df는 파일시스템 전체 사용량(할당된 블록)du는 디렉터리 트리에 “현재 이름이 존재하는 파일”의 합
따라서 df만 높고 du가 낮으면 열린 삭제 파일을 의심할 확률이 큽니다.
2) 열린 삭제 파일을 가장 빠르게 찾는 방법: lsof +L1
가장 실전적인 1줄은 아래입니다.
sudo lsof +L1
+L1은 link count가 1 미만(= 0)인 파일, 즉 삭제된 파일을 의미- 출력에
(deleted)가 붙는 항목이 핵심
용량이 큰 것부터 보고 싶다면(환경에 따라 정렬이 필요):
sudo lsof +L1 | awk '{print $7, $9, $2, $1}' | sort -n | tail -n 20
- 7번째 컬럼은 SIZE/OFF(배포판/버전에 따라 다를 수 있음)
- 로그 파일 경로가
(deleted)로 보이거나,/var/log/... (deleted)형태가 나오면 거의 확정입니다.
systemd-journald도 자주 범인이다
특히 /var/log/journal 용량이 크거나, journald가 파일을 잡고 있는 경우도 있습니다.
sudo lsof +L1 | grep -E 'journald|/var/log/journal'
3) /proc로 직접 확인하기: fd가 가리키는 (deleted)
lsof가 없거나, 컨테이너/최소 OS에서 설치가 어려울 때는 /proc로 확인할 수 있습니다.
프로세스별로 열린 파일 디스크립터를 보려면:
PID=1234
ls -l /proc/$PID/fd | grep deleted
예시 출력:
... -> /var/log/app.log (deleted)
이 상태에서 해당 fd가 계속 커지고 있다면, 애플리케이션이 삭제된 파일에 계속 쓰는 중입니다.
4) 디스크 공간을 즉시 회수하는 3가지 방법
원칙은 간단합니다. 해당 fd를 닫게 만들면 공간이 회수됩니다. 방법은 상황에 따라 3가지가 많습니다.
방법 A) 프로세스 재시작(가장 안전하고 권장)
애플리케이션이 로그를 재오픈하도록 재시작하거나 reload 합니다.
sudo systemctl restart myapp
# 또는
sudo systemctl reload myapp
- 재시작이 어렵다면 graceful reload 지원 여부를 확인하세요.
- nginx/apache 같은 서버는 reload만으로도 로그 재오픈이 됩니다.
nginx 예:
sudo nginx -s reopen
# 또는
sudo systemctl kill -s USR1 nginx
방법 B) 열린 삭제 파일을 0바이트로 truncate(긴급 처방)
서비스를 내릴 수 없고, 당장 디스크를 비워야 한다면 해당 fd를 truncate 해서 공간을 회수할 수 있습니다.
(deleted)파일을 잡고 있는 PID와 FD를 찾습니다.
sudo lsof +L1 | grep '(deleted)'
- 예를 들어 PID=1234, FD=5w 라면
/proc/1234/fd/5를 truncate:
: > /proc/1234/fd/5
# 또는
truncate -s 0 /proc/1234/fd/5
- 파일 이름은 이미 삭제되어도, fd는 살아있으므로 해당 경로에 쓰기 가능
- 단, 애플리케이션이 계속 쓰면 다시 커집니다. 근본 해결은 재오픈/재시작입니다.
방법 C) journald 공간 회수
journald가 원인이라면 보존 정책을 조정하고 vacuum을 실행합니다.
sudo journalctl --disk-usage
sudo journalctl --vacuum-size=1G
# 또는 기간 기준
sudo journalctl --vacuum-time=7d
그리고 /etc/systemd/journald.conf에서 상한을 두는 게 좋습니다.
# /etc/systemd/journald.conf
SystemMaxUse=1G
RuntimeMaxUse=200M
적용:
sudo systemctl restart systemd-journald
5) 왜 logrotate가 이런 문제를 만들까: rename vs copytruncate
대부분의 logrotate는 기본적으로 rotate 시 파일을 rename 하고 새 파일을 만듭니다. 하지만 애플리케이션이 로그 파일을 계속 열어둔 채로(파일 핸들을 유지한 채로) 쓰면, rename된 “옛 파일”에 계속 쓰게 됩니다.
이때 운영에서 흔한 선택지는 두 가지입니다.
- 애플리케이션이 로그 재오픈(signal/reload) 을 지원한다면: rename 방식 유지 + postrotate에서 reopen
- 애플리케이션이 재오픈을 못 한다면:
copytruncate사용(부작용 이해 필요)
권장 1) postrotate에서 재오픈 시그널 보내기
예: nginx는 USR1로 로그 파일을 재오픈합니다.
/var/log/nginx/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
sharedscripts
postrotate
/bin/kill -USR1 $(cat /run/nginx.pid 2>/dev/null) 2>/dev/null || true
endscript
}
애플리케이션이 systemd라면 더 단순하게:
/var/log/myapp/*.log {
daily
rotate 7
compress
missingok
notifempty
sharedscripts
postrotate
/bin/systemctl kill -s HUP myapp.service 2>/dev/null || true
endscript
}
- HUP/USR1 등은 앱마다 다릅니다(문서 확인)
sharedscripts는 여러 파일이 매칭돼도 postrotate를 1번만 실행
권장 2) copytruncate는 최후의 수단
copytruncate는 “기존 파일을 복사해서 rotated 파일로 저장한 뒤, 원본 파일을 0으로 truncate” 합니다. 애플리케이션은 같은 파일 핸들을 계속 쓰므로 열린 삭제 문제는 줄어듭니다.
/var/log/legacy-app.log {
daily
rotate 7
compress
missingok
notifempty
copytruncate
}
다만 다음 단점이 있습니다.
- 복사 시점과 truncate 사이에 로그가 일부 유실/중복될 수 있음
- 대용량 로그는 복사 비용이 큼(IO spike)
가능하면 “재오픈 지원 + postrotate”가 더 정석입니다.
6) 실전 점검 체크리스트(10분 내 복구 플로우)
- 디스크 100% 확인
df -h
du로는 안 보이면 열린 삭제 파일 의심
du -sh /var/log 2>/dev/null
- 열린 삭제 파일 찾기
sudo lsof +L1
- 범인 프로세스 확인 후 조치
- 가능:
systemctl restart/reload - 불가:
: > /proc/<pid>/fd/<fd>로 긴급 회수
- 재발 방지: logrotate에 postrotate 추가 또는 copytruncate 검토
운영 중에는 장애가 네트워크/인프라 문제처럼 보이기도 합니다. 예를 들어 디스크 100%는 애플리케이션 타임아웃/502/504로 번질 수 있어, 트러블슈팅 시 인프라/네트워크와 함께 원인을 분리해야 합니다. 비슷한 “겉보기 증상과 실제 원인이 다른” 장애 진단 흐름은 EKS Pod→RDS 504 타임아웃 - SG·NACL·NAT 10분 진단나, 인그레스 502에서 로그가 비는 케이스를 다룬 EKS Ingress 502인데 Pod 로그가 비면? ALB/NLB 헬스체크부터도 함께 참고하면 좋습니다.
7) 보너스: 컨테이너/Kubernetes 환경에서의 주의점
컨테이너에서는 로그가 파일이 아니라 stdout/stderr로 흘러가고, 노드에서 journald/container runtime이 보관하는 경우가 많습니다. 하지만 다음 패턴은 여전히 발생합니다.
- 컨테이너 내부에서 파일 로그를 직접 쌓음(
/app/log/*.log) - 사이드카나 에이전트가 파일을 tail 하며 open 유지
- rotate 후 tailer가 fd를 잡고 있어 (deleted) 누수
이때도 원리는 동일합니다.
- 노드에서
lsof +L1로 (deleted) 확인 - 해당 PID(에이전트/프로세스) 재시작
- rotate 전략을 tailer 친화적으로 변경(재오픈 시그널, copytruncate 등)
마무리
logrotate 이후 디스크가 100%로 붙고 du로 원인이 안 보이면, 거의 항상 열린 삭제 파일이 원인입니다. 해결은 “누가 그 파일을 잡고 있나”를 찾아 fd를 닫게 만드는 것이고, 예방은 “rotate 이후 로그 재오픈”을 보장하는 설정입니다.
운영에서 가장 중요한 포인트는 두 가지입니다.
- 복구:
lsof +L1로 즉시 범인을 찾고 재시작/트렁케이트로 공간 회수 - 재발 방지: postrotate로 reopen 신호를 보내거나, 불가피하면 copytruncate 적용
이 두 가지만 체계화해두면, 새벽에 df 100% 경보가 떠도 10분 내로 안정화할 수 있습니다.