infra
Platform

모듈 맵

[Linux] Read-only 파일시스템 에러 진단과 디스크 IO 성능 병목 잡기

0 / 37 완료

펼치기
0 / 37 완료0%

리눅스 서버 운영 · 17 / 37

[Linux] Read-only 파일시스템 에러 진단과 디스크 IO 성능 병목 잡기

inode 고갈, 고스트 파일, 심볼릭 링크 — 디스크 Full 장애 완전 정복

🚨INCIDENT ALERT
HIGH

새벽 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: 고스트 파일 점유 (가장 찾기 어려운 경우)


디스크 I/O 성능 병목의 3대 진단 무기

용량은 충분하지만 웹 서버의 응답 속도가 비정상적으로 느려지고 Load Average가 폭증한다면, 이는 십중팔구 디스크 I/O 병목입니다.

  • iostat: 디스크의 읽기/쓰기 대기 시간 and 대역폭을 분석합니다.
    로컬 터미널
    $ sudo iostat -x 1 5
    
    출력 결과물에서 %util(디스크 대역폭 사용률)이 90% 이상을 유지하거나, await(I/O 요청이 디스크에서 처리되기까지 평균 대기 시간, ms 단위)이 15ms를 넘는다면 즉시 성능 병목으로 진단해야 합니다.
  • iotop: 디스크 쓰기를 폭발시키는 범인 프로세스를 색출합니다.
    로컬 터미널
    $ sudo iotop -o
    
    -o 옵션은 실제로 현재 디스크 I/O를 발생시키고 있는 프로세스만 필터링하여, 백그라운드에서 좀비처럼 대량 로그를 밀어 넣는 데몬을 즉각 검거하게 해 줍니다.
  • sar -d: 시계열 분석을 수행해 과거의 디스크 병목 골든타임 라인을 소급 분석합니다.

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 고갈!

💡개념

'디스크 꽉 참'을 좁혀가는 순서 — df에서 원인 분류까지 4단계

같은 "No space left on device"라도 원인은 큰 파일·열린 삭제 파일·inode 고갈로 갈립니다. 그래서 무작정 큰 파일부터 지우면 열린-삭제-파일이나 inode 문제를 놓쳐 알람이 곧 다시 울립니다. 아래 4단계는 "무엇이 바닥났나 → 어디가 → 왜 → 조치"의 순서로 원인을 계층으로 좁혀, 지우기 전에 진단이 먼저 오게 합니다.

TEXT
"No space left on device" / 디스크 알람
   │
   ① df -h        → 어느 파일시스템이 꽉 찼나 특정 (Use% 100%인 마운트)
   │      동시에 df -i 로 inode도 본다 (용량 남는데 실패면 inode 고갈)
   │
   ② du 하향 탐색  → 그 마운트 안에서 큰 디렉터리를 위→아래로 좁힘
   │      du -sh /var/*  →  /var/log/*  →  ...  (범인 디렉터리까지)
   │
   ③ 원인 분류    ┬ 큰 실체 파일(로그·덤프)      → 정리 / 로테이션
   │              ├ df ≫ du (지웠는데 안 줄음)    → 열린 삭제 파일 (lsof +L1)
   │              └ 용량 남는데 생성 실패         → inode 고갈 (df -i)
   │
   ④ 조치         → 로그 정리 · 프로세스 재시작(fd 해제) · 소파일 대량 삭제
   ▼
[df -h · df -i 로 회복 확인]

각 단계가 하는 일과, 여기서 갈리는 판단:

단계하는 일여기서 갈림
① df로 특정df -h로 꽉 찬 마운트를 고르고 df -i로 inode도 함께 확인. 어느 파일시스템인지부터 못 박음df는 100%인데 어디가 큰지 모름 → ②로. df는 여유인데 생성 실패 → inode 의심, df -i
② du 하향 탐색특정한 마운트에서 du -sh로 큰 가지를 따라 위에서 아래로 좁힘 (/var/*/var/log/*)du 합이 df 사용량보다 한참 작으면 = 파일이 "숨어 있음" → ③의 열린 삭제 파일 의심
③ 원인 분류세 갈래로 나눔: 큰 실체 파일 · dfdu(삭제됐지만 열린 파일) · inode 고갈분류를 건너뛰고 지우면 헛수고. 열린 파일은 rm해도 안 줄고, inode 고갈은 용량 지워도 그대로
④ 조치실체 파일=정리·로테이트 · 열린 파일=프로세스 재시작 또는 /proc/PID/fd/N truncate · inode=소파일 삭제조치 후 df -h·df -i 둘 다 회복됐는지 확인. 한쪽만 보면 재발

핵심은 "지웠는데 왜 안 줄지"를 겉으로만 보지 않고 계층으로 나누는 것입니다. df는 파일시스템이 "블록을 얼마나 썼다고 여기는가"를, du는 "지금 이름이 붙어 있는 파일들의 합"을 셉니다 — 그래서 프로세스가 잡고 있는 삭제된 파일은 df엔 잡히고 du엔 안 잡혀 둘이 벌어집니다(lsof +L1로 확인, 프로세스 재시작이나 fd truncate로 회수). 반대로 용량은 남는데 파일이 안 만들어지면 블록이 아니라 inode 슬롯이 바닥난 것이라 df -i로만 보입니다. 그래서 진단은 늘 df -h+df -i를 함께 열고(무엇이 바닥났나) → du로 좁히고(어디가) → 세 원인 중 무엇인지 분류한 뒤(왜) 조치합니다. 이 순서를 건너뛰면 큰 파일만 지우다 열린-삭제-파일이나 inode 고갈을 놓쳐 알람이 곧 다시 울립니다.


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

로컬 터미널
# 실습 디렉토리 준비
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는 원인을 찾아줍니다.

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

로컬 터미널
# 루트(/) 하위 디렉토리별 용량을 큰 순서대로 상위 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위입니다. 계속 파고 내려갑니다.

로컬 터미널
# /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
로컬 터미널
# /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 옵션 정리

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

로컬 터미널
# --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으로 에러 출력을 버려야 결과가 깔끔합니다.

🔍실행 후 확인할 것
  • du -sh /var/log/* | sort -rh | head -20 결과에서 가장 큰 디렉토리가 상단에 표시된다
  • 2>/dev/null 없이 실행하면 Permission denied 메시지가 섞여 결과가 지저분해진다
  • --max-depth=1 옵션으로 직접 하위 디렉토리만 보여 분석 범위를 제한할 수 있다
  • sort -hr 결과가 사람이 읽기 좋은 단위(G, M, K)로 내림차순 정렬되어 나온다

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

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

로컬 터미널
$ 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로 파일 수를 세어 범인을 찾습니다.

로컬 터미널
# 각 최상위 디렉토리의 파일 수 세기 (시간이 걸릴 수 있음)
$ 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 세션 파일이 정리되지 않고 누적된 전형적인 패턴입니다.

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

로컬 터미널
# 특정 디렉토리 내 파일 총 수
$ 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

세션 파일의 생김새 확인

로컬 터미널
$ 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를 소진한 수백만 개의 세션 파일을 삭제하려고 합니다:

로컬 터미널
$ rm /var/lib/php/sessions/sess_*

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

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

로컬 터미널
getconf ARG_MAX
# 2097152   # 약 2MB

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

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

방법 1: find -delete

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

로컬 터미널
# 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

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

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

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

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

로컬 터미널
# 빈 임시 디렉토리 생성
$ mkdir /tmp/empty_dir

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

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

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

로컬 터미널
$ 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를 가리키는 디렉토리 엔트리가 몇 개인가"를 나타냅니다.

로컬 터미널
# 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개 추가 소비
로컬 터미널
# 실제 확인: 같은 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 번호

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

로컬 터미널
sudo rm /var/log/app/application.log
df -h /
# /dev/sda1  50G  49G  500M  99% /   ← 여전히 99%!

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

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

로컬 터미널
sudo lsof | grep deleted | awk '{print $7, $1, $2, $NF}' | sort -rn | head -5
OUTPUT
15032385536 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를 닫으면 공간이 즉시 반환됩니다:

서버 터미널
sudo systemctl restart app

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

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

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

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

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

로컬 터미널
# 기본 문법: 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 파티션에 여유가 있는 경우:

로컬 터미널
# 현재 상태 확인
$ 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: 버전 관리와 빠른 롤백

로컬 터미널
# 버전별 디렉토리 구조
$ 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: 링크가 디렉토리여도 내부로 들어가지 않음

심볼릭 링크 확인 명령어

로컬 터미널
# 링크가 가리키는 최종 실제 경로 확인
$ 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가 경고를 냅니다:

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

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

진단 — Broken Link를 찾습니다:

로컬 터미널
# 방법 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

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

로컬 터미널
# 방법 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 패턴에서는 사이트를 비활성화할 때 원본 파일 대신 링크만 제거하는 것이 올바른 방법입니다:

로컬 터미널
# 사이트 활성화: 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 부호 방향이 직관과 반대로 느껴지는 경우가 많아서 처음 쓸 때 헷갈립니다.

로컬 터미널
# 수정된 지 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)
-ctimeinode 상태가 변경된 시각 (change time, 권한/소유자 변경 포함)
+NN일 초과 (N일보다 오래됨)
-NN일 미만 (N일보다 최근)

크기 조건: -size

로컬 터미널
# 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

로컬 터미널
# 일반 파일만
$ 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로 결과에 명령 실행

로컬 터미널
# 찾은 각 파일에 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 {} \;

복잡한 조건 조합

로컬 터미널
# /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

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

로컬 터미널
#!/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')] 파일 정리 완료"
로컬 터미널
# 실행 권한 부여 후 실행
$ 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분)

로컬 터미널
# 블록 사용량 확인
$ 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분)

로컬 터미널
# 최상위 디렉토리별 용량
$ 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분)

로컬 터미널
# 삭제됐지만 여전히 점유 중인 파일
$ 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분)

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

Step 5: 즉각 조치

주의: 아래 명령은 데이터를 삭제하거나 서비스를 재시작합니다. 운영 서버에서는 먼저 find ... -print/docker system df -v로 대상을 검토하고, 백업·보존 정책과 담당자 승인을 확인한 뒤 격리된 경로에서 실행하세요. 특히 docker system prune -af --volumes는 사용하지 않는 이미지·컨테이너·볼륨을 영구 삭제합니다.

로컬 터미널
# 케이스 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 df -v

조치 후 확인

로컬 터미널
$ df -h && df -i
$ echo "현재 시각: $(date)"

진단 결과 요약 원라이너

로컬 터미널
# 현황을 한 번에 출력하는 진단 스크립트
$ 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 상태로 점유 중"

심화 — 용량 문제가 아닌데 쓰기가 막힐 때

💡개념

심화: 커널은 디스크 오류를 만나면 파일시스템을 읽기 전용으로 잠근다

지금까지의 장애는 모두 "공간(블록·inode)이 없어서" 쓰기가 막히는 경우였습니다. 하지만 용량도 inode도 멀쩡한데 갑자기 모든 쓰기가 실패하는, 다른 결의 장애가 있습니다. 그 열쇠는 커널이 디스크 오류를 만났을 때 어떻게 반응하도록 설계됐는지에 있습니다.

  • ext4의 기본 정책은 errors=remount-ro 입니다. 커널이 파일시스템 메타데이터를 읽거나 쓰다가 I/O 오류나 구조적 손상(inode 불일치, 저널 문제 등)을 감지하면, 손상을 더 번지게 하느니 즉시 파일시스템 전체를 읽기 전용으로 다시 마운트합니다. 그래서 물리적으로는 '섹터 하나의 문제'가 논리적으로는 '이 마운트의 모든 쓰기 실패'로 확대됩니다.
  • 이것은 버그가 아니라 안전장치입니다. 손상된 파일시스템에 계속 쓰면 복구 불가능한 데이터 파손으로 이어질 수 있어, 커널은 "멈추는 쪽"을 택합니다. errors= 옵션은 세 가지입니다 — continue(경고만 하고 진행, 위험), remount-ro(기본, 읽기 전용으로 잠금), panic(즉시 커널 패닉, HA 페일오버 유도용).
  • 증상은 용량 장애와 완전히 다릅니다. df·df -i 모두 여유가 있는데 touch·로그 쓰기가 전부 Read-only file system으로 실패합니다. lsof deleted도, du 드릴다운도 아무것도 잡지 못합니다 — 애초에 공간 문제가 아니기 때문입니다.
  • 진실은 dmesg에 있습니다. 커널 링 버퍼에 EXT4-fs error, I/O error, remounting filesystem read-only 같은 줄이 남습니다. 여기에 smartctl -aReallocated_Sector_Ct·Current_Pending_Sector 값을 더하면, 이게 일시적 논리 손상인지 진행 중인 하드웨어 고장인지 판단할 수 있습니다.
로컬 터미널
# 지금 루트가 읽기 전용으로 잠겼는지
mount | grep ' / '            # ...(ro,relatime)... 이면 읽기 전용

# 왜 잠겼는지 — 커널 로그가 유일한 증거
sudo dmesg -T | grep -iE 'ext4-fs error|i/o error|read-only'

핵심은 대응 순서입니다. 급하다고 mount -o remount,rw로 되돌리면 근본 원인을 덮은 채 잠시 뒤 다시 잠깁니다. 증거 확보(dmesg·smartctl) → 하드웨어냐 일시적 손상이냐 판단 → 언마운트 후 fsck → 필요하면 디스크 교체, 이 순서를 지켜야 같은 장애를 되풀이하지 않습니다.

상황: 서비스 로그가 갑자기 Read-only file system 에러로 도배되고, 애플리케이션이 임시 파일조차 만들지 못합니다. 그런데 df -hdf -i도 60%대로 멀쩡하고, lsof | grep deleted에도 잡히는 게 없습니다. 공간 장애의 어떤 진단도 들어맞지 않습니다.

원인: 디스크의 섹터 하나가 읽기·쓰기 오류를 내자 커널이 ext4 메타데이터 손상 위험을 감지하고, 기본 정책 errors=remount-ro에 따라 파일시스템을 통째로 읽기 전용으로 다시 마운트했습니다. 그 순간부터 모든 쓰기가 거부됩니다 — 공간이 아니라 파일시스템 상태(read-only)가 원인입니다.

진단: mount | grep ' / ' 출력에 ro가 보이면 확정입니다. 이유는 sudo dmesg -T | grep -iE 'ext4-fs error|i/o error|read-only'에 남은 remounting filesystem read-only 줄에서 확인합니다. 이어 sudo smartctl -a /dev/sdaReallocated_Sector_Ct·Current_Pending_Sector가 오르고 있는지 봐서 하드웨어 고장인지, 아니면 일시적 논리 손상인지 가릅니다.

해결: 절대 remount,rw로 급히 덮지 않습니다. (1) 가능하면 그 노드를 서비스에서 빼고 데이터를 즉시 백업합니다. (2) 파일시스템을 언마운트(루트라면 복구 모드로 부팅)한 뒤 fsck로 점검·복구합니다. (3) SMART가 물리 손상을 가리키면 복구가 성공해도 디스크를 교체합니다 — 재할당 섹터가 늘고 있는 디스크는 곧 다시 오류를 냅니다. 하드웨어가 멀쩡하고 일시적 손상이었다면 fsck 후 정상 마운트로 복귀합니다.


💼
실무 맥락현업 디스크 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으로 직접 정리:

로컬 터미널
# /etc/cron.d/php-session-cleanup
0 * * * * www-data find /var/lib/php/sessions -mtime +1 -delete

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

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

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

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

로컬 터미널
# 미사용 이미지, 컨테이너, 볼륨 한번에 정리
$ 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.* 파일이 산재

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

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

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

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

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

5위: 임시 파일 누적

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

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

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

로컬 터미널
# 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 사용률 알림 설정하기
현업 패턴

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

기본 모니터링 스크립트

로컬 터미널
#!/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
로컬 터미널
# 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% 미만입니다."

주간 디스크 현황 리포트

로컬 터미널
#!/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"
로컬 터미널
# 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 -delete30일 이상 된 파일 삭제
find /path -type f -size +100M -delete100MB 이상 파일 삭제
find /path -xtype l -deleteBroken 심볼릭 링크 삭제
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 lBroken 심볼릭 링크만 찾기

관련 모듈로 더 깊이:

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

지식 확인

퀴즈 — 10문제

Q1

운영 서버에서 `rm`으로 10GB 파일을 삭제했는데 `df -h`에서 용량이 회수되지 않았습니다. 가장 가능성 높은 원인은?

Q2

디스크 용량은 50% 이상 남아 있는데 `touch newfile` 실행 시 'No space left on device' 오류가 발생한다. 가장 가능성 높은 원인은?

Q3

`lsof | grep deleted` 명령어로 '고스트 파일'을 찾는 이유는?

Q4

`fsck`를 실행하기 전에 반드시 선행해야 하는 작업은?

Q5

`smartctl -a /dev/sda` 출력에서 'Reallocated_Sector_Ct' 값이 지속적으로 증가하고 있다면 어떤 조치가 필요한가?

Q6

한 파일에 ln(하드링크)으로 이름을 하나 더 만든 뒤, 원래 이름을 rm 해도 데이터가 사라지지 않는 이유는?

Q7

[심화] 디스크는 60%로 여유가 있는데 어느 순간부터 모든 쓰기가 'Read-only file system'으로 실패합니다. ext4 기본 설정에서 이런 일이 벌어지는 이유는?

Q8

[심화] 서비스 로그가 갑자기 Read-only file system 에러로 도배됐고 dmesg에 EXT4-fs error와 remounting filesystem read-only가 보입니다. df는 정상입니다. 올바른 대응 순서는?

Q9

[1급] 프로세스가 로그 파일을 연 채로 그 파일이 rm 되어 공간이 회수되지 않습니다. 프로세스를 재시작할 수 없을 때, 이름이 지워졌지만 열려 있는 파일만 골라 확인하고 공간을 회수하는 방법으로 옳은 것은?

Q10

[1급] 디스크 쿼터(quota)의 soft limit, hard limit, grace period의 관계로 옳은 것은?

0 / 10 답변

🧪 실습으로 확인하기

새 서버 인수인계 — 처음 30분

초급

낯선 Linux 서버를 인수받았을 때 OS, 서비스, 로그를 빠르게 파악하는 루틴을 직접 수행한다.

30📋 3단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요