새벽 2시, 모니터링 알림이 울립니다. "디스크 사용량 95% 초과." SSH로 접속해 df -h를 확인하니 용량이 꽉 찼습니다. rm으로 30GB짜리 로그 파일을 삭제했는데 df -h가 여전히 100%. du -sh /*로 뒤져봐도 범인을 찾기 어렵습니다. Nginx 프로세스가 삭제된 파일의 핸들을 잡고 있어서 블록이 해제되지 않은 것이었습니다.
lsof | grep deleted — 이 한 줄로 '고스트 파일'을 찾을 수 있습니다.
디스크 트러블슈팅 심화
df -h && df -i
sudo lsof | grep deleted
sudo apt-get install -y lsof # 또는 sudo yum install -y lsof
"No space left on device" 에러는 겉으로 보면 단순해 보이지만, 실제 원인은 세 가지로 나뉩니다. 원인을 잘못 진단하면 시간만 낭비하고 장애가 지속됩니다.
유형 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/)"
이제 실습을 진행합니다.
장애가 발생하면 가장 먼저 해야 할 것은 "어디서 공간을 잡아먹고 있는가"를 파악하는 것입니다. 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으로 에러 출력을 버려야 결과가 깔끔합니다.
— — —
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를 전부 소진합니다.
— — —
상황 — 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% / ← 정상 복구!
— — —
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 번호
— — —
상황 — 파일을 삭제했는데 공간이 돌아오지 않습니다:
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
— — —
상황 — 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는 단순히 파일을 찾는 것을 넘어 조건을 조합하고 일괄 처리까지 수행하는 강력한 도구입니다. 디스크 트러블슈팅에서 가장 빈번하게 사용되는 옵션을 실무 예제와 함께 익힙니다.
시간 조건: -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 장애를 받았을 때 단계별로 진행하는 진단 절차입니다. 순서대로 실행하면 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 장애의 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
— — —
장애가 발생한 후 대응하는 것보다 임계치에 도달하기 전에 알림을 받는 것이 훨씬 낫습니다. 실무에서 사용하는 디스크 모니터링 자동화 패턴을 소개합니다.
기본 모니터링 스크립트
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 동작 원리를 다룹니다.
'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 |