[Linux] 디스크 트러블슈팅

2026. 5. 22. 22:25·Linux
SMALL
시나리오

새벽 2시, 모니터링 알림이 울립니다. "디스크 사용량 95% 초과." SSH로 접속해 df -h를 확인하니 용량이 꽉 찼습니다. rm으로 30GB짜리 로그 파일을 삭제했는데 df -h가 여전히 100%. du -sh /*로 뒤져봐도 범인을 찾기 어렵습니다. Nginx 프로세스가 삭제된 파일의 핸들을 잡고 있어서 블록이 해제되지 않은 것이었습니다.

lsof | grep deleted — 이 한 줄로 '고스트 파일'을 찾을 수 있습니다.

디스크 트러블슈팅 심화

이번 챕터에서 배울 것
1df와 du 조합으로 디스크 사용량을 계층적으로 분석할 수 있다
2inode 고갈 원인을 df -i로 진단하고 소형 파일 폭증을 해결할 수 있다
3lsof | grep deleted로 고스트 파일을 탐지하고 블록 공간을 회수할 수 있다
4하드링크·심볼릭 링크 구조를 이해하고 파티션 경계를 넘는 스토리지 확장에 활용할 수 있다
5fsck와 smartctl로 파일시스템 손상과 디스크 하드웨어 이상을 진단할 수 있다
실습 환경 준비
디스크 사용량 및 inode 현황 확인
df -h && df -i
열려 있는 삭제 파일(고스트 파일) 탐색
sudo lsof | grep deleted
lsof 패키지 설치 (미설치 시)
sudo apt-get install -y lsof  # 또는 sudo yum install -y lsof
fsck 실행 전 주의사항
fsck는 반드시 언마운트된 파일시스템에서 실행해야 데이터 손상을 방지할 수 있다
개념
디스크 장애의 세 가지 유형
디스크 장애 세 가지 유형 — 물리 결함·파일시스템 손상·용량 고갈

"No space left on device" 에러는 겉으로 보면 단순해 보이지만, 실제 원인은 세 가지로 나뉩니다. 원인을 잘못 진단하면 시간만 낭비하고 장애가 지속됩니다.

디스크 Full 장애 진단 흐름 — df·du·lsof 3단계, 고스트 파일 동작 원리

유형 1: 데이터 블록 고갈 (가장 흔한 경우)

디스크의 실제 데이터 저장 공간(블록)이 모두 차는 경우입니다. df -h로 확인하면 사용률이 100%에 가깝게 나타납니다.

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   49G  512M  99% /

원인: 로그 파일 폭증, 대용량 덤프 파일, 코어 덤프, 도커 이미지 누적 등

유형 2: inode 고갈 (숨어있는 함정)

디스크 용량은 50% 이상 남아있는데 파일을 생성하거나 저장하면 "No space left on device" 에러가 발생합니다. inode(메타데이터 슬롯)가 모두 소진된 경우입니다.

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   23G   27G  46% /          ← 용량 여유 있어 보임

# 하지만 파일 생성 시도:
$ touch /tmp/testfile
touch: cannot touch '/tmp/testfile': No space left on device

원인: 수백만 개의 소형 파일(세션 파일, 캐시, 큐 메시지 등)

유형 3: 고스트 파일 점유 (가장 찾기 어려운 경우)

rm으로 파일을 삭제했지만 df -h의 사용량이 줄지 않습니다. 실행 중인 프로세스가 삭제된 파일의 파일 디스크립터를 계속 열고 있어 커널이 실제로 블록을 해제하지 못하는 상태입니다.

$ rm /var/log/app/huge-app.log    # 삭제했는데
$ df -h /                          # 공간이 그대로!
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   49G  512M  99% /

원인: 로그 파일을 열고 계속 쓰는 데몬이 재시작 없이 운영 중

— — —

블록(Block)과 inode의 관계

이 세 가지 장애 유형을 제대로 이해하려면 파일시스템의 두 가지 핵심 자원을 구별해야 합니다.

자원 역할 저장 내용 고갈 진단 명령
블록 (Block) 실제 데이터 저장 파일 내용(바이트) df -h
inode (아이노드) 메타데이터 저장 파일 이름, 권한, 소유자, 타임스탬프, 블록 포인터 df -i

파일 하나를 만들면 반드시 블록 하나 이상과 inode 딱 하나가 소비됩니다. 파일 내용이 1바이트라도 inode는 동일하게 소비됩니다. 수백만 개의 빈 파일(0바이트)을 만들면 블록은 거의 안 쓰이지만 inode는 빠르게 고갈됩니다.

파일시스템 포맷 시 inode 개수가 미리 결정됩니다 (ext4 기준)
기본값: 디스크 16KB마다 inode 1개 할당

50GB 디스크 → 약 3,200,000개 inode 슬롯
→ 빈 파일 320만 개를 만들면 inode 고갈!

— — —

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

bash# 실습 디렉토리 준비
mkdir -p /tmp/linux/part3/exam_3 && cd /tmp/linux/part3/exam_3

# 디스크 진단 실습용 대용량 파일 생성
dd if=/dev/urandom of=/tmp/linux/part3/exam_3/large_file.bin bs=1M count=200 2>/dev/null
for i in $(seq 1 50); do
  echo "$(date): log entry $i - application error occurred" >> /tmp/linux/part3/exam_3/app.log
done
echo "실습 파일 준비 완료: $(du -sh /tmp/linux/part3/exam_3/)"

이제 실습을 진행합니다.

디스크 사용량 심층 분석: du로 원인 찾기

장애가 발생하면 가장 먼저 해야 할 것은 "어디서 공간을 잡아먹고 있는가"를 파악하는 것입니다. df가 현황을 보여준다면 du는 원인을 찾아줍니다.

루트부터 계층적으로 내려가며 범인 찾기

bash# 루트(/) 하위 디렉토리별 용량을 큰 순서대로 상위 5개 출력
$ sudo du -sh /* 2>/dev/null | sort -hr | head -n 5
18G     /var
12G     /home
8.2G    /opt
3.1G    /usr
1.4G    /tmp

/var가 18GB로 1위입니다. 계속 파고 내려갑니다.

bash# /var 하위 디렉토리 분석
$ sudo du -sh /var/* 2>/dev/null | sort -hr | head -n 5
16G     /var/log
1.2G    /var/lib
512M    /var/cache
128M    /var/spool
64M     /var/tmp
bash# /var/log 하위 분석
$ sudo du -sh /var/log/* 2>/dev/null | sort -hr | head -n 5
14G     /var/log/app
1.1G    /var/log/nginx
512M    /var/log/mysql
256M    /var/log/syslog
128M    /var/log/auth.log

범인은 /var/log/app입니다. 14GB를 점유하고 있습니다.

유용한 du 옵션 정리

루트부터 계층을 타고 내려가는 방식 외에도, 특정 마운트 포인트에 걸친 용량을 제외하거나 숨김 파일까지 포함하는 옵션이 자주 쓰입니다.

bash# --max-depth로 깊이 제한 (--max-depth=1은 직접 자식만)
$ sudo du -h --max-depth=1 /var/log 2>/dev/null | sort -hr

# 특정 파일시스템만 분석 (-x: 다른 마운트 포인트 제외)
$ sudo du -sh -x /* 2>/dev/null | sort -hr | head -n 10

# 숨김 파일(.으로 시작) 포함해서 홈 디렉토리 분석
$ du -sh ~/.* ~/* 2>/dev/null | sort -hr | head -n 10

2>/dev/null 이 중요한 이유: du는 권한 없는 디렉토리를 만나면 "Permission denied" 에러를 stderr로 출력합니다. /proc, /sys 등의 가상 파일시스템에 접근할 때도 에러가 쏟아집니다. 2>/dev/null으로 에러 출력을 버려야 결과가 깔끔합니다.

— — —

inode 고갈 확인 및 원인 디렉토리 탐색

1단계: df -i로 inode 상태 확인

bash$ df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
devtmpfs        491346    412  490934    1% /dev
tmpfs           493548      1  493547    1% /dev/shm
/dev/sda1      3276800 3276799       1  100% /        ← inode 고갈!
/dev/sdb1       655360   1204  654156    1% /data

/dev/sda1의 IUse%가 100%입니다. inode가 딱 1개 남았습니다. 데이터 블록 여유가 있어도 파일 생성이 불가능합니다.

2단계: inode를 많이 소비하는 디렉토리 찾기

inode를 가장 많이 쓰는 디렉토리는 파일 수가 가장 많은 디렉토리입니다. find로 파일 수를 세어 범인을 찾습니다.

bash# 각 최상위 디렉토리의 파일 수 세기 (시간이 걸릴 수 있음)
$ sudo find / -xdev -printf '%h\n' 2>/dev/null | sort | uniq -c | sort -rn | head -n 20
2847291 /var/lib/php/sessions
 183420 /var/cache/nginx/fastcgi
  92847 /tmp/cache
  45123 /var/spool/postfix/deferred
  12847 /home/deploy/.npm/_cacache
   8234 /var/log/app

/var/lib/php/sessions에 파일이 280만 개 이상 있습니다! PHP 세션 파일이 정리되지 않고 누적된 전형적인 패턴입니다.

더 직관적인 방법: 디렉토리별 파일 수 세기

bash# 특정 디렉토리 내 파일 총 수
$ find /var/lib/php/sessions -type f 2>/dev/null | wc -l
2847291

# 하위 디렉토리별로 파일 수 출력
$ for dir in /var/lib/php/sessions/*/; do
    echo "$(find "$dir" -type f 2>/dev/null | wc -l) $dir"
  done | sort -rn | head -n 10

세션 파일의 생김새 확인

bash$ ls -la /var/lib/php/sessions/ | head -n 10
total 45892
drwxrwx--- 2 root www-data      4096 Mar 26 02:31 .
drwxr-xr-x 4 root root          4096 Jan 15 09:00 ..
-rw------- 1 www-data www-data   247 Mar 19 08:41 sess_00a1b2c3d4e5f6a7
-rw------- 1 www-data www-data   189 Mar 20 14:22 sess_00b8c9d0e1f2a3b4
-rw------- 1 www-data www-data   312 Mar 18 03:15 sess_00c4d5e6f7a8b9c0
...

파일 하나의 크기는 수백 바이트에 불과하지만, 280만 개가 모이면 inode를 전부 소진합니다.

— — —

트러블슈팅
-bash: /bin/rm: Argument list too long

상황 — inode를 소진한 수백만 개의 세션 파일을 삭제하려고 합니다:

bash$ rm /var/lib/php/sessions/sess_*

원인 — 셸은 sess_* 글로브 패턴을 먼저 확장한 뒤 rm에 파일 목록을 인수로 전달합니다. 커널의 ARG_MAX 제한(약 2MB)을 수백만 개의 파일 경로가 초과하면 이 오류가 납니다.

진단 — ARG_MAX 한계와 현재 파일 수를 확인합니다:

bashgetconf ARG_MAX
# 2097152   # 약 2MB

ls /var/lib/php/sessions/ | wc -l
# 2847291   # 수백만 개 파일 존재
bash# ARG_MAX 확인
$ getconf ARG_MAX
2097152   # 약 2MB

해결 — find는 파일을 하나씩 처리하므로 ARG_MAX 제한이 적용되지 않습니다:

방법 1: find -delete

find는 파일을 하나씩 처리하므로 ARG_MAX 제한이 적용되지 않습니다.

bash# 30일 이상 된 세션 파일 삭제 (안전 — find가 직접 삭제)
$ find /var/lib/php/sessions -type f -mtime +30 -delete

# 진행 상황을 보고 싶다면 -print와 함께 (삭제 전 dry-run)
$ find /var/lib/php/sessions -type f -mtime +30 -print | head -n 5
/var/lib/php/sessions/sess_00a1b2c3d4e5f6a7
/var/lib/php/sessions/sess_00b8c9d0e1f2a3b4
...

# 확인 후 실제 삭제
$ find /var/lib/php/sessions -type f -mtime +30 -delete

방법 2: find + xargs

bash# find로 파일 목록을 만들고 xargs로 배치 처리
$ find /var/lib/php/sessions -type f -mtime +30 -print0 | xargs -0 rm -f

# -print0과 -0: 파일명에 공백이 있어도 안전하게 처리

방법 3: rsync 트릭 (수백만 개 파일 초고속 삭제)

수백만 개 파일을 find -delete로 지우면 시간이 매우 오래 걸립니다. 빈 디렉토리와 rsync --delete를 조합하면 훨씬 빠릅니다.

bash# 빈 임시 디렉토리 생성
$ mkdir /tmp/empty_dir

# rsync로 세션 디렉토리를 비움 (매우 빠름)
$ rsync -a --delete /tmp/empty_dir/ /var/lib/php/sessions/

# 임시 디렉토리 정리
$ rmdir /tmp/empty_dir

삭제 후 inode 상태를 다시 확인합니다:

bash$ df -i /
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/sda1      3276800  429508 2847292   14% /   ← 정상 복구!

— — —

개념
inode 구조 심층 이해: 하드링크와 참조 카운트
inode 구조와 하드링크 — 디렉토리 엔트리·inode·데이터 블록 관계

rm으로 파일을 지웠는데 왜 공간이 안 돌아오는지, 하드링크로 연결된 파일 하나를 지워도 왜 원본이 살아있는지 — 이 두 현상은 inode의 참조 카운트를 이해하면 같은 원리로 설명됩니다. 실제 파일 이름은 파일시스템의 디렉토리 엔트리에 있고, 파일의 실체는 inode 번호로 참조됩니다. rm은 이름(디렉토리 엔트리)을 지우는 것이지 inode를 지우는 게 아닙니다. 그래서 프로세스가 inode를 아직 열고 있으면 이름이 없어도 데이터는 그대로 남습니다.

inode는 단순한 메타데이터 저장소가 아닙니다. 파일시스템의 핵심 자료구조로, 하드링크의 동작 원리와 고스트 파일 현상 모두 inode의 참조 카운트(reference count)로 설명됩니다.

inode가 저장하는 정보

inode #1823749
├── 파일 타입: 일반 파일 (-), 디렉토리 (d), 심볼릭 링크 (l)
├── 권한: 0644 (rw-r--r--)
├── 소유자: UID=1000, GID=1000
├── 크기: 4096 bytes
├── 타임스탬프:
│   ├── atime: 2026-03-26 02:15:00 (마지막 접근)
│   ├── mtime: 2026-03-25 18:30:00 (마지막 수정)
│   └── ctime: 2026-03-25 18:30:00 (마지막 상태 변경)
├── 링크 카운트(nlink): 2
└── 데이터 블록 포인터: [블록 4521, 블록 4522, ...]

inode에 파일 이름은 없습니다. 파일 이름은 디렉토리 엔트리(dentry)에 저장되고, dentry가 inode 번호를 가리킵니다.

링크 카운트(nlink)의 의미

링크 카운트는 "이 inode를 가리키는 디렉토리 엔트리가 몇 개인가"를 나타냅니다.

bash# inode 번호와 링크 카운트 확인
$ stat myfile.txt
  File: myfile.txt
  Size: 1024        Blocks: 8          IO Block: 4096   regular file
Device: fd01h/64769d Inode: 1823749     Links: 1         ← 링크 카운트 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/  ubuntu)  Gid: ( 1000/  ubuntu)

# 하드링크 생성
$ ln myfile.txt myfile_hard.txt

$ stat myfile.txt
  ...
  Inode: 1823749     Links: 2           ← 같은 inode, 링크 카운트 2

rm 명령은 실제로 파일을 즉시 지우지 않습니다. 링크 카운트를 1 감소시킵니다. 링크 카운트가 0이 되고, 해당 inode를 열고 있는 프로세스도 없을 때 비로소 블록이 해제됩니다. 이것이 고스트 파일 현상의 근본 원리입니다.

하드링크 vs 심볼릭 링크 핵심 차이

항목 하드링크 (Hard Link) 심볼릭 링크 (Symbolic Link)
inode 원본과 동일한 inode 공유 독립적인 새 inode 생성
크로스 파티션 불가 (같은 파일시스템만) 가능 (다른 파티션, NFS 등)
원본 삭제 시 데이터 유지 (링크 카운트만 감소) Broken link (빨간 링크) 발생
디렉토리에 적용 불가 (루트만 예외) 가능
ls -l 표시 -rw-r--r-- 2 (숫자가 링크 수) lrwxrwxrwx + -> 원본경로
inode 소비 추가 없음 1개 추가 소비
bash# 실제 확인: 같은 inode 번호 공유
$ ls -li myfile.txt myfile_hard.txt
1823749 -rw-r--r-- 2 ubuntu ubuntu 1024 Mar 26 02:15 myfile.txt
1823749 -rw-r--r-- 2 ubuntu ubuntu 1024 Mar 26 02:15 myfile_hard.txt
# ↑ inode 번호 동일!

# 심볼릭 링크는 다른 inode
$ ln -s myfile.txt myfile_sym.txt
$ ls -li myfile.txt myfile_sym.txt
1823749 -rw-r--r-- 1 ubuntu ubuntu 1024 Mar 26 02:15 myfile.txt
1823751 lrwxrwxrwx 1 ubuntu ubuntu   10 Mar 26 02:20 myfile_sym.txt -> myfile.txt
# ↑ 다른 inode 번호

— — —

트러블슈팅
rm으로 14GB 로그 파일을 삭제했는데 df -h 용량이 전혀 줄지 않음 (deleted but open)

상황 — 파일을 삭제했는데 공간이 돌아오지 않습니다:

bashsudo rm /var/log/app/application.log
df -h /
# /dev/sda1  50G  49G  500M  99% /   ← 여전히 99%!

원인 — rm은 디렉토리 엔트리(이름)를 제거할 뿐입니다. Java 프로세스가 파일 디스크립터를 열고 계속 로그를 쓰고 있으면, 커널은 모든 fd가 닫힐 때까지 블록을 해제하지 않습니다. 디렉토리에서 보이지 않지만 공간을 점유하는 "고스트 파일" 상태가 됩니다.

진단 — lsof | grep deleted로 범인을 찾습니다:

bashsudo lsof | grep deleted | awk '{print $7, $1, $2, $NF}' | sort -rn | head -5
output15032385536 java 1847 /var/log/app/application.log (deleted)
524288000 java 1847 /var/log/app/error.log (deleted)
104857600 nginx 2031 /var/log/nginx/access.log (deleted)

해결 — 서비스 재시작으로 fd를 닫으면 공간이 즉시 반환됩니다:

bashsudo systemctl restart app

df -h /
# /dev/sda1  50G  35G  15G  70% /   ← 14GB 반환!

재시작할 수 없는 긴급 상황이라면 /proc를 통해 직접 truncate합니다:

bash# PID 1847, fd 17번 — 파일 내용 비워 즉시 공간 회수
sudo truncate -s 0 /proc/1847/fd/17

— — —

심볼릭 링크 생성과 실무 활용

기본 심볼릭 링크 생성: ln -s

bash# 기본 문법: ln -s <원본 경로> <링크 경로>
$ ln -s /data/uploads /var/www/html/uploads

# 확인
$ ls -la /var/www/html/
total 16
drwxr-xr-x 3 www-data www-data 4096 Mar 26 03:00 .
drwxr-xr-x 5 root     root     4096 Jan 01 00:00 ..
-rw-r--r-- 1 www-data www-data  512 Mar 26 02:55 index.html
lrwxrwxrwx 1 www-data www-data   14 Mar 26 03:00 uploads -> /data/uploads

l로 시작하는 권한(lrwxrwxrwx)과 -> 화살표가 심볼릭 링크의 표식입니다.

실무 패턴 1: 꽉 찬 파티션 우회 저장

웹 서버의 루트 파티션(/)이 꽉 찼지만, 별도 마운트된 /data 파티션에 여유가 있는 경우:

bash# 현재 상태 확인
$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        30G   29G  500M  98% /          ← 거의 꽉 참
/dev/sdb1       500G   50G  450G  10% /data       ← 여유 충분

# 1. 업로드 디렉토리를 /data로 이동
$ sudo mv /var/www/html/uploads /data/uploads

# 2. 원래 위치에 심볼릭 링크 생성
$ sudo ln -s /data/uploads /var/www/html/uploads

# 3. 웹 서버 입장에서는 아무것도 변하지 않음
$ ls -la /var/www/html/uploads/
# /data/uploads의 내용이 그대로 보임

애플리케이션 코드 변경 없이 파티션 경계를 투명하게 넘어갑니다.

실무 패턴 2: 버전 관리와 빠른 롤백

bash# 버전별 디렉토리 구조
$ ls -la /opt/app/
drwxr-xr-x 3 app app 4096 Mar 25 10:00 releases/
drwxr-xr-x 5 app app 4096 Mar 26 02:00 releases/v2.3.1/
drwxr-xr-x 5 app app 4096 Mar 26 03:00 releases/v2.3.2/   ← 신버전
lrwxrwxrwx 1 app app   22 Mar 26 03:05 current -> releases/v2.3.2/

# 롤백: 링크만 변경하면 됨 (다운타임 없음)
$ ln -sfn /opt/app/releases/v2.3.1 /opt/app/current
# -f: 기존 링크 덮어쓰기, -n: 링크가 디렉토리여도 내부로 들어가지 않음

심볼릭 링크 확인 명령어

bash# 링크가 가리키는 최종 실제 경로 확인
$ readlink -f /var/www/html/uploads
/data/uploads

# 링크 체인 단계별 출력
$ readlink /var/www/html/uploads
/data/uploads

# 심볼릭 링크만 찾기
$ find /var/www -type l -ls 2>/dev/null

— — —

트러블슈팅
ls -la에서 빨간색으로 표시되는 Broken Symbolic Link — 원본 파일 없음

상황 — Nginx sites-enabled 디렉토리에서 빨간색 링크가 보이고 nginx -t가 경고를 냅니다:

outputlrwxrwxrwx 1 root root   38 Mar 20 14:00 myapp -> /etc/nginx/sites-available/myapp
# ↑ 빨간색으로 표시됨 (원본 파일이 삭제됨)

원인 — 심볼릭 링크가 가리키는 원본이 삭제되거나 이동된 상태입니다. 링크 자체는 존재하지만 대상이 없습니다.

진단 — Broken Link를 찾습니다:

bash# 방법 1: -L 옵션 없이 find (심볼릭 링크 자체를 따라가지 않음)
$ find /etc/nginx/sites-enabled -type l 2>/dev/null

# 방법 2: ! -e 조건으로 링크가 가리키는 대상이 없는 것 찾기
$ find /etc/nginx/sites-enabled -xtype l 2>/dev/null
/etc/nginx/sites-enabled/myapp

# 방법 3: readlink로 대상 확인 후 존재 여부 검증
$ for link in /etc/nginx/sites-enabled/*; do
    if [ -L "$link" ] && [ ! -e "$link" ]; then
        echo "Broken: $link -> $(readlink $link)"
    fi
  done
Broken: /etc/nginx/sites-enabled/myapp -> /etc/nginx/sites-available/myapp

해결 — 링크를 제거하거나 원본 경로로 재생성합니다:

bash# 방법 1: Broken Link 제거
$ sudo rm /etc/nginx/sites-enabled/myapp

# 방법 2: 원본 파일이 이동된 경우 링크 재생성
$ sudo ln -sf /etc/nginx/sites-available/myapp-v2 /etc/nginx/sites-enabled/myapp

# 방법 3: 시스템 전체 Broken Link 일괄 제거 (주의해서 사용)
$ sudo find / -xtype l -delete 2>/dev/null

Nginx sites-enabled 패턴에서는 사이트를 비활성화할 때 원본 파일 대신 링크만 제거하는 것이 올바른 방법입니다:

bash# 사이트 활성화: sites-available → sites-enabled 링크
$ sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/

# 사이트 비활성화: 링크만 제거, 원본은 유지
$ sudo rm /etc/nginx/sites-enabled/myapp

# Nginx 설정 테스트 후 재로드
$ sudo nginx -t && sudo systemctl reload nginx

Broken Link는 Nginx, Apache 등 웹 서버의 설정 디렉토리에서 흔히 발생합니다. 사이트를 비활성화할 때 원본 설정 파일을 지우면 Broken Link가 남아 nginx -t 경고의 원인이 됩니다.

— — —

find 심화: 복잡한 조건 조합과 일괄 처리

find는 단순히 파일을 찾는 것을 넘어 조건을 조합하고 일괄 처리까지 수행하는 강력한 도구입니다. 디스크 트러블슈팅에서 가장 빈번하게 사용되는 옵션을 실무 예제와 함께 익힙니다.

시간 조건: -mtime, -atime, -ctime

오래된 로그나 백업 파일을 정리할 때 "30일 이상 된 것만 지워라"는 조건이 필요합니다. +N과 -N 부호 방향이 직관과 반대로 느껴지는 경우가 많아서 처음 쓸 때 헷갈립니다.

bash# 수정된 지 7일 이상 된 파일 (7일 초과)
$ find /var/log -type f -mtime +7

# 정확히 7일 전에 수정된 파일
$ find /var/log -type f -mtime 7

# 수정된 지 1시간 이내 파일 (분 단위: -mmin)
$ find /var/log -type f -mmin -60

# 수정된 지 30분~2시간 사이 파일
$ find /var/log -type f -mmin +30 -mmin -120
옵션 의미
-mtime 파일 내용이 수정된 시각 (modify time)
-atime 파일에 마지막으로 접근한 시각 (access time)
-ctime inode 상태가 변경된 시각 (change time, 권한/소유자 변경 포함)
+N N일 초과 (N일보다 오래됨)
-N N일 미만 (N일보다 최근)

크기 조건: -size

bash# 100MB 이상인 파일 찾기
$ find / -type f -size +100M 2>/dev/null

# 1GB 이상인 파일을 크기 순으로 정렬
$ find / -type f -size +1G -printf '%s %p\n' 2>/dev/null | sort -rn | head -n 10

# 0바이트 (빈) 파일 찾기 (inode만 소비)
$ find /tmp -type f -size 0 2>/dev/null
단위 의미 예시
c 바이트 -size +1024c
k 킬로바이트 (1024 bytes) -size +10k
M 메가바이트 -size +100M
G 기가바이트 -size +1G

타입 조건: -type

bash# 일반 파일만
$ find /tmp -type f

# 디렉토리만
$ find /var -type d -empty   # 비어있는 디렉토리

# 심볼릭 링크만
$ find /etc -type l

# Broken 심볼릭 링크만 (-xtype l: 링크가 가리키는 대상의 타입이 l이면 broken)
$ find /etc -xtype l 2>/dev/null

-exec로 결과에 명령 실행

bash# 찾은 각 파일에 ls -lh 실행 (파일별로 명령 실행, 속도 느림)
$ find /var/log -type f -size +100M -exec ls -lh {} \;

# {}+ 로 한번에 여러 파일 처리 (속도 빠름, xargs와 유사)
$ find /var/log -type f -size +100M -exec ls -lh {} +

# 30일 이상 된 .log 파일 압축 (gzip)
$ find /var/log/app -name "*.log" -mtime +30 -exec gzip {} \;

# 30일 이상 된 .gz 파일 삭제
$ find /var/log/app -name "*.log.gz" -mtime +30 -exec rm -f {} \;

복잡한 조건 조합

bash# /tmp에서 1일 이상 됐고 10MB 이상인 일반 파일 찾기
$ find /tmp -type f -mtime +1 -size +10M

# -o: OR 조건 (괄호 이스케이프 필요)
$ find /var/log \( -name "*.log" -o -name "*.log.gz" \) -mtime +30

# -not 또는 !: NOT 조건
$ find /home -type f -not -name "*.conf" -size +1M

# 에러 출력 숨기기 (권한 없는 디렉토리)
$ find / -type f -size +500M 2>/dev/null

실전: 오래된 로그와 백업 파일 일괄 삭제 스크립트

bash#!/bin/bash
# cleanup-old-files.sh — 오래된 로그 및 백업 파일 자동 정리

LOG_DIR="/var/log/app"
BACKUP_DIR="/backup"
DAYS_KEEP=30

echo "[$(date '+%Y-%m-%d %H:%M:%S')] 파일 정리 시작"

# 오래된 로그 파일 압축
echo ">>> ${DAYS_KEEP}일 이상 된 로그 파일 압축 중..."
find "$LOG_DIR" -type f -name "*.log" -mtime +${DAYS_KEEP} -exec gzip -9 {} \;

# 60일 이상 된 압축 로그 삭제
echo ">>> 60일 이상 된 압축 로그 삭제 중..."
find "$LOG_DIR" -type f -name "*.log.gz" -mtime +60 -delete

# 오래된 백업 파일 삭제
echo ">>> ${DAYS_KEEP}일 이상 된 백업 파일 삭제 중..."
find "$BACKUP_DIR" -type f \( -name "*.tar.gz" -o -name "*.sql.gz" \) \
    -mtime +${DAYS_KEEP} -delete

# 빈 디렉토리 정리
echo ">>> 빈 디렉토리 정리 중..."
find "$LOG_DIR" "$BACKUP_DIR" -type d -empty -mindepth 1 -delete 2>/dev/null

# 현재 디스크 사용량 출력
echo ">>> 정리 후 디스크 상태:"
df -h "$LOG_DIR"

echo "[$(date '+%Y-%m-%d %H:%M:%S')] 파일 정리 완료"
bash# 실행 권한 부여 후 실행
$ chmod +x /usr/local/sbin/cleanup-old-files.sh

# crontab으로 매일 새벽 3시에 자동 실행
$ sudo crontab -e
# 추가:
0 3 * * * /usr/local/sbin/cleanup-old-files.sh >> /var/log/cleanup.log 2>&1

— — —

종합 진단 플로우: 디스크 Full 장애 대응 절차

현장에서 디스크 Full 장애를 받았을 때 단계별로 진행하는 진단 절차입니다. 순서대로 실행하면 10분 이내에 원인을 파악하고 대응할 수 있습니다.

Step 1: 현황 파악 (1분)

bash# 블록 사용량 확인
$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   49G  512M  99% /

# inode 사용량 확인
$ df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
/dev/sda1      3276800 3276700     100  100% /

# 두 가지 모두 확인해 유형 판단

Step 2: 대용량 파일/디렉토리 탐색 (2분)

bash# 최상위 디렉토리별 용량
$ sudo du -sh /* 2>/dev/null | sort -hr | head -n 10

# 원인 디렉토리를 찾을 때까지 반복
$ sudo du -sh /var/* 2>/dev/null | sort -hr | head -n 10
$ sudo du -sh /var/log/* 2>/dev/null | sort -hr | head -n 10

Step 3: 고스트 파일 확인 (1분)

bash# 삭제됐지만 여전히 점유 중인 파일
$ sudo lsof | grep deleted | awk 'NR==1{print "SIZE CMD PID FD FILE"} {print $7, $1, $2, $4, $NF}' | \
  column -t | sort -rn | head -n 10

Step 4: inode 고갈 시 파일 수 많은 디렉토리 확인 (2분)

bash# 파일 수 기준 상위 디렉토리 (느릴 수 있음)
$ sudo find / -xdev -printf '%h\n' 2>/dev/null | \
  sort | uniq -c | sort -rn | head -n 10

Step 5: 즉각 조치

bash# 케이스 A: 대용량 오래된 로그
$ sudo find /var/log -name "*.log" -mtime +30 -delete

# 케이스 B: 세션/캐시 파일 대량 적재
$ sudo find /var/lib/php/sessions -type f -mtime +7 -delete

# 케이스 C: 고스트 파일 (프로세스 재시작)
$ sudo systemctl restart <서비스명>

# 케이스 D: 도커 사용 시 미사용 이미지/볼륨 정리
$ sudo docker system prune -af --volumes

조치 후 확인

bash$ df -h && df -i
$ echo "현재 시각: $(date)"

진단 결과 요약 원라이너

bash# 현황을 한 번에 출력하는 진단 스크립트
$ echo "=== 블록 사용량 ===" && df -h && \
  echo "" && echo "=== inode 사용량 ===" && df -i && \
  echo "" && echo "=== 상위 5개 디렉토리 ===" && \
  sudo du -sh /* 2>/dev/null | sort -hr | head -n 5 && \
  echo "" && echo "=== 고스트 파일 ===" && \
  sudo lsof 2>/dev/null | grep deleted | wc -l | xargs -I{} echo "{}개 파일이 deleted 상태로 점유 중"

— — —

실무 맥락
현업 디스크 Full 시나리오 Top 5 — 로그·세션·도커·코어덤프·고스트파일 원인별 대응법

실제 운영 환경에서 디스크 Full 장애의 80%는 아래 5가지 패턴에서 발생합니다. 미리 알아두면 장애 발생 즉시 원인을 짐작할 수 있습니다.

1위: 로그 파일 폭증

/var/log/app/application.log 가 수십 GB로 증가

원인: 애플리케이션 버그로 에러 루프, 또는 디버그 레벨 로그가 프로덕션에 켜진 경우

예방: Logrotate 설정으로 로그 자동 순환. /etc/logrotate.d/myapp에 설정 파일 작성

/var/log/app/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        systemctl reload myapp 2>/dev/null || true
    endscript
}

2위: PHP/Python 세션 파일 누적

/var/lib/php/sessions/sess_* 가 수백만 개
/tmp/session/ 디렉토리에 수십만 개

원인: 세션 GC(Garbage Collection)가 비활성화되어 있거나 GC 확률이 너무 낮게 설정됨

예방 (PHP): php.ini에서 session.gc_probability = 1, session.gc_divisor = 100 설정. 또는 cron으로 직접 정리:

bash# /etc/cron.d/php-session-cleanup
0 * * * * www-data find /var/lib/php/sessions -mtime +1 -delete

3위: 도커 이미지/볼륨 누적

/var/lib/docker/ 가 수십~수백 GB

원인: 배포 파이프라인에서 이미지를 계속 빌드하지만 이전 이미지를 정리하지 않음

예방: 정기적인 정리 + 자동화

bash# 미사용 이미지, 컨테이너, 볼륨 한번에 정리
$ docker system prune -af

# cron으로 주간 자동 정리 (이미지 태그 유지, 미사용만)
0 4 * * 0 root docker image prune -af >> /var/log/docker-cleanup.log 2>&1

4위: 코어 덤프 파일

/var/core/core.nginx.12847 가 수 GB
/tmp/core.* 파일이 산재

원인: 애플리케이션 크래시 시 생성되는 코어 덤프. 분석에 필요하지만 방치하면 디스크 잠식

예방: 코어 덤프 크기 제한 또는 저장 위치 관리

bash# 코어 덤프 크기 0으로 제한 (비활성화)
$ ulimit -c 0

# 또는 /etc/security/limits.conf에 영구 적용
* soft core 0

# 코어 덤프 위치 확인
$ cat /proc/sys/kernel/core_pattern

5위: 임시 파일 누적

/tmp/, /var/tmp/ 에 수 GB 임시 파일

원인: 배치 작업, 컴파일, 압축 해제 후 임시 파일을 정리하지 않음

예방: systemd-tmpfiles로 자동 관리 (현대 배포판에 기본 포함)

bash# systemd-tmpfiles 설정 확인
$ cat /usr/lib/tmpfiles.d/tmp.conf
# /tmp는 10일 이상 된 파일 자동 삭제
q /tmp 1777 root root 10d
# /var/tmp는 30일 이상 된 파일 자동 삭제
q /var/tmp 1777 root root 30d

# 수동으로 즉시 적용
$ sudo systemd-tmpfiles --clean

— — —

실무 맥락
디스크 모니터링 자동화 — cron 스크립트로 블록·inode 사용률 알림 설정하기

장애가 발생한 후 대응하는 것보다 임계치에 도달하기 전에 알림을 받는 것이 훨씬 낫습니다. 실무에서 사용하는 디스크 모니터링 자동화 패턴을 소개합니다.

기본 모니터링 스크립트

bash#!/bin/bash
# /usr/local/sbin/disk-monitor.sh

THRESHOLD=85          # 경고 임계치 (%)
INODE_THRESHOLD=80    # inode 경고 임계치 (%)
SLACK_WEBHOOK="https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
HOSTNAME=$(hostname -f)

send_alert() {
    local message="$1"
    local payload="{\"text\": \"$message\"}"

    # Slack 알림 (선택사항)
    if [ -n "$SLACK_WEBHOOK" ]; then
        curl -s -X POST -H 'Content-type: application/json' \
            --data "$payload" "$SLACK_WEBHOOK" > /dev/null 2>&1
    fi

    # syslog에 기록
    logger -t disk-monitor "$message"

    # 이메일 발송 (mailx 설치 필요)
    echo "$message" | mail -s "[ALERT] 디스크 경고: $HOSTNAME" admin@company.com 2>/dev/null
}

# 블록 사용량 점검
df -h --output=pcent,target | tail -n +2 | while read -r usage mount; do
    usage_num="${usage//%/}"
    if [ "$usage_num" -ge "$THRESHOLD" ]; then
        msg="[DISK ALERT] ${HOSTNAME}: ${mount} 블록 사용률 ${usage} (임계치: ${THRESHOLD}%)"
        send_alert "$msg"
        echo "$msg"
    fi
done

# inode 사용량 점검
df -i --output=ipcent,target | tail -n +2 | while read -r usage mount; do
    usage_num="${usage//%/}"
    if [ "$usage_num" -ge "$INODE_THRESHOLD" ]; then
        msg="[INODE ALERT] ${HOSTNAME}: ${mount} inode 사용률 ${usage} (임계치: ${INODE_THRESHOLD}%)"
        send_alert "$msg"
        echo "$msg"
    fi
done
bash# crontab 등록: 5분마다 점검
*/5 * * * * root /usr/local/sbin/disk-monitor.sh

Prometheus + Node Exporter를 사용하는 환경

Prometheus를 이미 사용 중이라면 Node Exporter가 디스크 메트릭을 자동으로 수집합니다. Grafana 알림 규칙을 추가합니다:

yaml# prometheus-rules.yml
groups:
  - name: disk_alerts
    rules:
      - alert: DiskSpaceLow
        expr: |
          (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|aufs"}
           / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|aufs"}) < 0.15
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "디스크 여유 공간 부족: {{ $labels.instance }}"
          description: "{{ $labels.mountpoint }}의 여유 공간이 15% 미만입니다. (현재: {{ $value | humanizePercentage }})"

      - alert: InodeLow
        expr: |
          (node_filesystem_files_free{fstype!~"tmpfs|overlay"}
           / node_filesystem_files{fstype!~"tmpfs|overlay"}) < 0.20
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "inode 부족 경고: {{ $labels.instance }}"
          description: "{{ $labels.mountpoint }}의 inode 여유가 20% 미만입니다."

주간 디스크 현황 리포트

bash#!/bin/bash
# /usr/local/sbin/weekly-disk-report.sh
# 매주 월요일 오전 9시에 실행하여 팀에게 현황 공유

REPORT_FILE="/tmp/disk-report-$(date +%Y%m%d).txt"

{
echo "======================================"
echo " 디스크 현황 주간 리포트"
echo " 서버: $(hostname -f)"
echo " 시각: $(date '+%Y년 %m월 %d일 %H:%M')"
echo "======================================"
echo ""
echo "--- 파티션별 블록 사용량 ---"
df -h --output=source,size,used,avail,pcent,target | column -t
echo ""
echo "--- 파티션별 inode 사용량 ---"
df -i --output=source,itotal,iused,ifree,ipcent,target | column -t
echo ""
echo "--- 상위 10개 대용량 디렉토리 ---"
sudo du -sh /* 2>/dev/null | sort -hr | head -n 10
echo ""
echo "--- 100MB 이상 파일 ---"
find / -type f -size +100M -printf '%s %p\n' 2>/dev/null | \
    sort -rn | head -n 20 | \
    awk '{printf "%.1fMB\t%s\n", $1/1024/1024, $2}'
echo ""
echo "--- deleted 상태 파일 점유 용량 ---"
sudo lsof 2>/dev/null | grep deleted | \
    awk '{sum += $7} END {printf "총 %.1fMB\n", sum/1024/1024}'
} > "$REPORT_FILE"

# 이메일 발송
mail -s "[주간] 디스크 현황 리포트: $(hostname -f)" \
    -a "Content-Type: text/plain; charset=UTF-8" \
    team@company.com < "$REPORT_FILE"

rm -f "$REPORT_FILE"
bash# crontab 등록
0 9 * * 1 root /usr/local/sbin/weekly-disk-report.sh

— — —

핵심 명령어 요약

이 챕터에서 다룬 명령어를 상황별로 정리합니다. 장애 현장에서 바로 참조할 수 있도록 북마크해 두세요.

진단 명령어

명령어 설명
df -h 파티션별 블록(용량) 사용률 확인
df -i 파티션별 inode 사용률 확인
du -sh /* | sort -hr | head -n 5 루트 하위 디렉토리 용량 상위 5개
du -sh --max-depth=1 /var/log 특정 디렉토리 1단계 깊이만 분석
lsof | grep deleted 삭제됐지만 점유 중인 파일 확인
find / -xdev -printf '%h\n' | sort | uniq -c | sort -rn 파일 수 많은 디렉토리 찾기

삭제 및 정리 명령어

명령어 설명
find /path -type f -mtime +30 -delete 30일 이상 된 파일 삭제
find /path -type f -size +100M -delete 100MB 이상 파일 삭제
find /path -xtype l -delete Broken 심볼릭 링크 삭제
truncate -s 0 /proc/PID/fd/N 고스트 파일 내용 즉시 비우기
rsync -a --delete /tmp/empty/ /path/ 대량 파일 빠른 삭제 (rsync 트릭)

심볼릭 링크 명령어

명령어 설명
ln -s /원본/경로 /링크/경로 심볼릭 링크 생성
ln -sfn /새원본 /기존링크 기존 링크 대상 변경
readlink -f /링크경로 링크의 실제 최종 경로 확인
find /path -type l 심볼릭 링크만 찾기
find /path -xtype l Broken 심볼릭 링크만 찾기

다음 모듈에서는 네트워킹 기초 — IP, 서브넷, 게이트웨이, DNS 동작 원리를 다룹니다.

반응형
LIST

'Linux' 카테고리의 다른 글

[Linux] DNS & 이름 해석 트러블슈팅  (0) 2026.05.22
[Linux] 네트워킹 기초 (Networking Basics)  (0) 2026.05.22
[Linux] LVM & 볼륨 관리  (0) 2026.05.22
[Linux] 디스크와 스토리지 관리  (0) 2026.05.22
[Linux] Bash 스크립팅 기초  (0) 2026.05.22
'Linux' 카테고리의 다른 글
  • [Linux] DNS & 이름 해석 트러블슈팅
  • [Linux] 네트워킹 기초 (Networking Basics)
  • [Linux] LVM & 볼륨 관리
  • [Linux] 디스크와 스토리지 관리
cumo
cumo
  • cumo
    이것저것
    cumo
    • 분류 전체보기 (147) N
      • 이것저것 (1)
      • 보안뉴스 (15)
      • Project (12)
      • wargame (1)
      • Cloud (25)
      • DevOps (21)
      • Linux (43)
      • 네트워크 (23)
      • AWS Developer BootCamp (1)
      • WEB&WAS (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 도구모음 사이트
    • 참고 기술 블로그
  • 공지사항

  • 인기 글

  • 태그

    Volume Group
    vi
    포트진단
    인프라
    스토리지확장
    데몬
    텍스트편집기
    Linux
    ubuntu
    vim
    서버편집
    리눅스
    눅스네트워킹
    논리볼륨
    터미널멀티플렉서
    bash입문
    부팅서비스
    디렉토리구조
    서버관리
    nano
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.3
cumo
[Linux] 디스크 트러블슈팅
상단으로

티스토리툴바