Fuwari Banner
지니제스트Tech Archive
IT & 테크6분 소요

리눅스 디스크 100% 해결

ggeniezst

리눅스 서버에서 rm으로 대용량 로그를 삭제했음에도 df 사용량이 100%로 유지되는 원인(Unlinked Open File)을 파악하고, lsof 명령어로 프로세스 파일 핸들을 추적하여 무중단으로 디스크 공간을 복구하는 실전 가이드입니다.

Sponsored

리눅스 서버에서 No space left on device 오류를 만나 급하게 대용량 로그 파일을 rm -f로 삭제했는데, df -h를 확인해 보면 사용량이 여전히 100%에 머물러 있는 경우가 있습니다. 반대로 du -sh /*로 디렉터리별 용량을 측정해 보면 공간이 널널하게 남아 있어 혼란을 줍니다.

이는 파일의 디렉터리 엔트리 링크만 끊어졌을 뿐, 실행 중인 특정 프로세스가 해당 파일의 디스크립터(File Descriptor)를 계속 잡고 있어 커널이 실제 디스크 블록 할당을 해제하지 못하기 때문입니다.

프로덕션 서버를 재시작하지 않고 lsof 명령어로 물려 있는 삭제 파일 핸들을 찾아 안전하게 디스크 공간을 회수하는 방법을 정리합니다.


df와 du의 용량 계산 차이

리눅스 파일 시스템에서 dfdu 명령어가 디스크 사용량을 계산하는 방식은 근본적으로 다릅니다.

파일 삭제 메커니즘과 Unlinked Open Files

리눅스에서 파일은 파일명을 담은 디렉터리 엔트리와 메타데이터 및 실제 데이터 블록 위치를 가리키는 아이노드(inode)로 분리되어 있습니다. 사용자가 rm 명령어를 실행하면 링크 카운트(Link Count)가 1 줄어듭니다.

만약 Nginx, Java, Python 등 백그라운드 프로세스가 해당 파일을 열어둔 채 실행 중이라면, 링크 카운트가 0이 되어도 참조 카운트(Reference Count)가 0이 될 때까지 운영체제 커널은 데이터 블록을 반환하지 않습니다. 이를 Unlinked Open File 상태라고 부릅니다.

비교 항목 df (Disk Free) du (Disk Usage)
측정 대상 파일 시스템 슈퍼블록(Superblock) 파일 트리 및 디렉터리 엔트리
측정 방식 커널이 관리하는 전체/사용 블록 즉각 조회 실제 존재하는 파일들의 크기를 재귀 합산
열린 파일 반영 삭제되었으나 프로세스가 잡고 있는 용량 포함 파일명이 삭제된 파일은 접근 불가하여 누락
소요 시간 1초 미만 (메타데이터 즉시 조회) 파일 개수에 비례하여 디스크 I/O 소요

df는 커널이 할당한 모든 실제 물리 블록을 계산하므로 실제 파일 시스템 잔여량을 보여주며, du는 파일 트리 탐색 시점 기준 존재하는 파일만 합산하므로 삭제된 열린 파일의 용량을 감지하지 못합니다.


삭제된 파일 핸들(Unlinked Files) 추적

디스크를 점유하고 있는 원인 프로세스를 찾기 위해 lsof(List Open Files) 유틸리티를 사용합니다.

lsof 명령어로 삭제된 대용량 파일 탐색

터미널에서 root 권한으로 파일 시스템 상에서 지워졌지만 프로세스가 열고 있는 파일 목록을 추출합니다:

BASH
# 열려 있는 삭제 파일 중 용량이 큰 순서로 상위 10개 정렬
lsof -nP | grep '(deleted)' | sort -k7 -nr | head -n 10

출력 결과에서 7번째 컬럼(파일 크기, 바이트 단위)과 2번째 컬럼(PID), 1번째 컬럼(COMMAND), 마지막 컬럼(파일 경로)을 확인합니다:

TEXT
COMMAND   PID USER   FD   TYPE DEVICE   SIZE/OFF      NODE NAME
node    24102 root   14w   REG  252,1 42949672960   2884102 /var/log/app/error.log (deleted)
java    18231 app     4w   REG  252,1 18454937600   1920391 /opt/app/logs/catalina.out (deleted)

위 결과는 Node.js 프로세스(PID: 24102)가 이미 삭제된 약 40GB 크기의 /var/log/app/error.log 파일을 파일 디스크립터 14w(쓰기 모드 FD 14번)로 계속 잡고 있음을 보여줍니다.


서비스 무중단 디스크 용량 즉시 복구

원인 프로세스를 파악했다면 프로세스를 강제 종료(kill -9)하지 않고 실시간으로 디스크 공간을 되찾을 수 있습니다.

/proc 가상 파일 시스템의 FD 직접 Truncate

리눅스 커널의 /proc/<PID>/fd/<FD_NUM> 경로는 해당 프로세스가 열어둔 파일 디스크립터에 직접 접근할 수 있는 링크를 제공합니다. 여기에 빈 내용을 리다이렉션하면 파일 크기를 즉시 0바이트로 비울 수 있습니다:

BASH
# 파일 디스크립터를 0바이트로 즉시 초기화 (PID: 24102, FD: 14)
: > /proc/24102/fd/14

# 또는 truncate 유틸리티 사용
truncate -s 0 /proc/24102/fd/14

⚠️ 주의: /proc/<PID>/fd/<FD> 심볼릭 링크 자체를 rm으로 삭제해서는 안 됩니다. 반드시 내용만 비우는 방식을 사용해야 프로세스가 파일 입출력 오류(I/O Error)를 겪지 않고 디스크 블록만 즉각 반환됩니다.

복구 결과 확인

명령어 실행 직후 다시 df -h를 실행해 디스크 여유 공간이 반환되었는지 확인합니다:

BASH
df -hT /

기타 디스크 점유 원인 점검

삭제된 파일 핸들 외에도 실무 서버 환경에서 디스크를 급격하게 채우는 대표적인 요소들입니다.

systemd 저널 로그(journalctl) 정리

systemd-journald는 기본 설정 상태에서 수 기가바이트의 시스템 로그를 영구 보관할 수 있습니다:

BASH
# 1. 저널 로그 사용량 확인
journalctl --disk-usage

# 2. 최근 2일 이전의 오래된 저널 로그 삭제
journalctl --vacuum-time=2d

# 3. 최대 용량을 500MB로 제한하여 정리
journalctl --vacuum-size=500M

Docker 컨테이너 json-log 파일 비우기

Docker 컨테이너의 표준 출력 로그는 기본적으로 JSON 파일 형식으로 호스트의 /var/lib/docker/containers/<ID>/<ID>-json.log에 누적됩니다.

BASH
# 실행 중인 모든 Docker 컨테이너의 json-log를 0바이트로 초기화
truncate -s 0 /var/lib/docker/containers/*/*-json.log

Docker 로그 파일 역시 rm으로 지우면 프로세스가 파일 핸들을 쥐고 있어 디스크가 반환되지 않으므로 반드시 truncate를 사용합니다.


재발 방지를 위한 Logrotate 설정

동일한 디스크 고갈이 반복되지 않도록 대용량 로그를 생성하는 서비스는 logrotatecopytruncate 옵션을 적용하는 것이 안전합니다.

TEXT
/var/log/app/*.log {
    daily
    missingok
    rotate 7
    compress
    delaycompress
    notifempty
    copytruncate
}

copytruncate는 파일을 이동(mv)하거나 새로 생성하지 않고, 현재 열려 있는 로그 파일 내용을 복사한 뒤 원본 파일 내용을 0으로 잘라냅니다. 이를 통해 애플리케이션 재시작 없이도 안전하게 로그 파일 크기를 순환시킬 수 있습니다.

Sponsored