Published on

logrotate 했는데 디스크 100%? 열린 삭제파일 찾기

Authors

서버에서 logrotate 를 돌렸는데도 df 는 여전히 100%에 가깝고, 서비스는 느려지고, 새 로그도 못 쓰는 상황이 종종 발생합니다. 이때 많은 경우 “파일은 지웠는데 공간이 안 돌아오는” 리눅스 파일 시스템 동작 방식 때문에 문제가 지속됩니다.

핵심은 간단합니다.

  • 파일을 삭제해도(디렉터리 엔트리 제거) 해당 파일을 열고 있는 프로세스가 있으면 inode가 남아 디스크 블록이 유지됩니다.
  • 이 상태는 du 로는 잘 안 보이고, lsof/proc 로 찾아야 합니다.

이 글에서는 열린 삭제파일을 찾는 실전 커맨드, 즉시 회수하는 방법, 그리고 logrotate 설정에서 재발을 막는 포인트를 정리합니다.

증상: du 는 작은데 df 는 꽉 찼다

가장 흔한 시그널은 아래 조합입니다.

  • df -h/var 또는 / 가 90~100%
  • du -sh /var/log/* 는 생각보다 작음
  • journalctl 이나 애플리케이션이 No space left on device 를 뱉음

리눅스에서 “삭제”는 파일 내용이 즉시 사라지는 게 아니라, 해당 파일을 가리키는 이름(디렉터리 엔트리)만 제거하는 의미에 가깝습니다. 열려 있는 파일 디스크립터가 남아 있으면 커널은 블록을 해제하지 않습니다.

원인: logrotate가 지운 파일을 프로세스가 계속 쓰는 경우

logrotate 는 보통 다음 중 하나로 로그를 회전합니다.

  • rename 방식: app.logapp.log.1 로 이름 변경 후 새 파일 생성
  • copytruncate 방식: 내용을 복사해 백업 파일 만들고, 원본 파일을 truncate

rename 방식에서는 “기존 파일 핸들”을 잡고 있던 프로세스가 계속 그 파일에 쓰게 됩니다. 그런데 그 파일이 압축/삭제까지 되면, 프로세스 입장에서는 여전히 열린 파일에 쓰고 있지만 파일 경로는 사라져 “deleted” 상태가 됩니다.

즉,

  • 파일 시스템 관점: 경로는 삭제됨
  • 커널 관점: 열린 inode는 살아 있음
  • 디스크 관점: 블록이 계속 점유됨

1분 진단: 열린 삭제파일 찾기 (lsof)

가장 빠른 방법은 lsof(deleted) 를 찾는 것입니다.

sudo lsof +L1 | head
  • +L1 은 링크 카운트가 1보다 작은(대개 0, 즉 삭제된) 파일을 의미합니다.

좀 더 “로그 파일”에 집중하려면:

sudo lsof +L1 | grep -E "\(deleted\)|/var/log" | head -n 50

특정 마운트(예: /var)에서만 보고 싶다면:

sudo lsof +L1 /var

출력에서 중요한 컬럼은 다음입니다.

  • COMMAND / PID: 어떤 프로세스가 잡고 있는지
  • FD: 어떤 파일 디스크립터인지(예: 1w, 2w, 3u)
  • SIZE/OFF: 얼마나 커졌는지
  • NAME: 경로 끝에 (deleted) 표시

디스크를 실제로 먹는 놈만 추리기

SIZE/OFF 기준으로 큰 것만 보고 싶을 때는 정렬을 한 번 거칩니다.

sudo lsof +L1 | awk '{print $7, $2, $1, $9}' | sort -n | tail -n 20

환경에 따라 컬럼 위치가 달라질 수 있으니, 먼저 lsof +L1 원본을 보고 조정하세요.

lsof 가 없다면: /proc 로 직접 찾기

최소 설치 이미지나 컨테이너 환경에서는 lsof 가 없을 수 있습니다. 이때는 /proc 의 파일 디스크립터 심볼릭 링크를 훑으면 됩니다.

for pid in /proc/[0-9]*; do
  ls -l "$pid/fd" 2>/dev/null | grep "(deleted)" && echo "PID=${pid##*/}"
done

프로세스 이름까지 같이 보고 싶다면:

for pid in /proc/[0-9]*; do
  cmd=$(tr -d '\0' < "$pid/cmdline" 2>/dev/null | cut -c1-120)
  if ls -l "$pid/fd" 2>/dev/null | grep -q "(deleted)"; then
    echo "PID=${pid##*/} CMD=$cmd"
    ls -l "$pid/fd" 2>/dev/null | grep "(deleted)" | head
  fi
done

즉시 해결: 공간을 회수하는 3가지 방법

찾았으면 이제 “열린 파일 핸들”을 정리해야 디스크 블록이 반환됩니다.

1) 가장 안전: 프로세스 재시작(또는 로그 재오픈)

정석은 해당 프로세스가 로그 파일을 다시 열도록 만드는 것입니다.

  • systemd 서비스라면:
sudo systemctl restart your-service
  • nginx 같이 재오픈 시그널을 지원하면(재시작보다 안전):
sudo kill -HUP $(cat /run/nginx.pid)

애플리케이션/데몬마다 “로그 재오픈” 시그널이 다릅니다. 웹 서버나 일부 데몬은 HUP 로 재오픈을 지원합니다.

2) 긴급 처치: 열린 FD에 truncate 를 걸어 0으로 만들기

재시작이 당장 어렵고 “디스크부터 살려야” 한다면, 열린 파일 디스크립터가 가리키는 실제 파일에 대해 크기를 0으로 만들 수 있습니다.

예를 들어 PID가 1234 이고, lsof 에서 FD가 5w 라면 /proc/1234/fd/5 가 대상입니다.

sudo truncate -s 0 /proc/1234/fd/5

또는 셸 리다이렉션으로도 가능합니다.

sudo sh -c 'echo -n > /proc/1234/fd/5'

주의점:

  • 이 방법은 “로그 내용이 사라집니다”. 디스크 회수 목적의 응급처치로만 쓰세요.
  • 잘못된 FD를 건드리면 예상치 못한 부작용이 있을 수 있으니, 반드시 lsof -p 1234 로 재확인 후 진행하세요.

3) 프로세스 종료(최후의 수단)

서비스 영향이 있더라도 디스크가 꽉 차서 더 큰 장애가 나기 직전이라면 프로세스를 종료해 inode를 해제할 수도 있습니다.

sudo kill 1234

정상 종료가 안 되면:

sudo kill -9 1234

다만 kill -9 는 정리 루틴을 건너뛰므로 가능한 피하세요.

logrotate 설정에서 재발 방지 포인트

문제를 해결했으면, 다음 회전에서 같은 일이 반복되지 않게 해야 합니다.

1) postrotate 에서 재오픈 신호/재시작을 보장

대표적으로 nginx는 postrotate 에서 HUP 을 보내는 패턴을 씁니다.

/var/log/nginx/*.log {
  daily
  rotate 14
  compress
  delaycompress
  missingok
  notifempty
  sharedscripts
  postrotate
    [ -s /run/nginx.pid ] && kill -HUP $(cat /run/nginx.pid)
  endscript
}
  • sharedscripts 는 여러 파일이 매칭돼도 postrotate 를 한 번만 실행합니다.
  • delaycompress 는 방금 회전한 파일을 즉시 압축하지 않고 다음 회전에서 압축합니다. 일부 프로세스/툴링과 충돌을 줄이는 데 도움이 됩니다.

2) copytruncate 는 만능이 아니다

copytruncate 는 프로세스 재시작 없이도 “같은 파일 경로”를 유지하게 만들어 편해 보이지만, 단점이 있습니다.

  • 복사와 truncate 사이에 로그가 유실될 수 있음
  • 큰 파일이면 I/O 부담이 큼

가능하면 애플리케이션이 로그 재오픈을 지원하도록 구성하고, postrotate 로 처리하는 게 더 안정적입니다.

3) systemd/journald 사용 시 별도 확인

애플리케이션이 파일 로그가 아니라 journald로만 남긴다면 logrotate 와 무관할 수 있습니다. 디스크가 journald로 찬 경우는 다음을 확인하세요.

journalctl --disk-usage

용량 제한은 예를 들어 이렇게 설정합니다.

sudo mkdir -p /etc/systemd/journald.conf.d
cat <<'EOF' | sudo tee /etc/systemd/journald.conf.d/size.conf
[Journal]
SystemMaxUse=1G
SystemKeepFree=500M
EOF
sudo systemctl restart systemd-journald

실전 트러블슈팅 플로우(체크리스트)

운영 중에는 “원인 찾기” 자체가 시간을 잡아먹습니다. 아래 순서로 보면 빠르게 수렴합니다.

  1. df -h 로 꽉 찬 마운트 확인
  2. du -sh /var/log/* 로 대략적인 로그 사용량 확인
  3. sudo lsof +L1(deleted) 파일 확인
  4. SIZE/OFF 를 잡고 있는 PID를 식별
  5. 가능하면 “재오픈 신호” 또는 서비스 재시작으로 해결
  6. 긴급하면 /proc/PID/fd/FDtruncate 로 공간 회수
  7. logrotate postrotate 와 애플리케이션 로깅 정책 개선

컨테이너 환경에서도 동일한 문제가 발생할 수 있는데, 특히 노드 디스크가 꽉 차면 파드가 연쇄적으로 죽거나 스케줄링이 실패합니다. 장애 상황에서 원인을 빠르게 좁히는 접근은 다른 운영 이슈에서도 동일하게 중요합니다. 예를 들어 쿠버네티스에서 재시작 루프를 빠르게 진단하는 방법은 K8s CrashLoopBackOff 원인 10분내 찾는 법 도 함께 참고하면 좋습니다.

자주 하는 실수와 주의사항

rm 했는데 왜 용량이 안 줄죠?

rm 은 경로를 지우는 것이고, 열린 파일 디스크립터가 남아 있으면 공간은 유지됩니다. “열린 삭제파일”이 대표 케이스입니다.

du 로는 왜 안 보이나요?

du 는 디렉터리 트리를 따라가며 “경로가 존재하는 파일”만 합산합니다. 경로가 사라진 (deleted) 파일은 합산 대상에서 빠집니다.

이미 압축된 로그도 같은 문제를 만들 수 있나요?

압축 파일 자체가 아니라, 압축/삭제되기 전 원본 로그를 프로세스가 계속 쓰는 상황이 문제입니다. 회전 직후에도 프로세스가 새 파일로 전환하지 않으면 계속 누적됩니다.

마무리

logrotate 이후에도 디스크가 100%로 남는 문제는 대부분 “삭제된 파일을 프로세스가 계속 열고 있는” 전형적인 리눅스 동작에서 시작합니다. lsof +L1 또는 /proc 로 열린 삭제파일을 찾아 PID를 특정하고, 재시작/재오픈 또는 긴급 truncate 로 공간을 회수하면 빠르게 정상화할 수 있습니다.

다음 회전에서 재발하지 않도록 logrotatepostrotate 에 재오픈 신호를 넣고, 애플리케이션 로깅 전략(파일 로그 vs journald)까지 함께 점검하는 것을 권장합니다.