리눅스 파일시스템과 디렉토리 구조
배포 파이프라인이 "No space left on device"로 멈췄습니다. 그런데 df -h 를 보면 디스크는 60%밖에 안 찼습니다. 용량은 남는데 왜 파일을 못 만들까요?
df -i 를 쳐보니 inode 사용률이 100%였습니다. 작은 캐시 파일 수백만 개가 inode를 모두 소진한 것이었습니다. 용량(블록)과 inode는 별개의 자원이라는 걸 모르면 이 장애 앞에서 한참을 헤맵니다.
파일시스템은 "용량이 얼마 남았나"보다 훨씬 깊은 구조를 가집니다. FHS 디렉토리 배치, inode와 링크, /etc/fstab 마운트까지 — 장애가 났을 때 어디를 봐야 하는지를 이 모듈에서 손으로 확인합니다.
리눅스 시스템에서 "모든 것은 파일이다(Everything is a file)"라는 철학은 단순한 슬로건이 아닙니다. 디바이스 드라이버, 프로세스 정보, 네트워크 소켓까지 파일 인터페이스로 접근합니다. 파일시스템 구조를 이해하면 장애 대응 속도가 획기적으로 빨라집니다.
리눅스마스터 1급 시험의 첫 번째 핵심 도메인입니다. FHS 디렉토리 구조, inode 개념, 링크 차이는 매회 출제됩니다. 이론을 이해하고 직접 실습하여 디스크 공간 문제를 스스로 진단하는 능력을 기릅니다.
- 1FHS(Filesystem Hierarchy Standard) — /bin, /sbin, /usr, /etc, /var, /proc, /sys 등 주요 디렉토리 역할
- 2inode 구조 — 파일 메타데이터 저장 방식, 파일명이 inode에 없는 이유, 링크 카운트
- 3하드링크와 심볼릭링크 — ln / ln -s 명령어, 동작 차이, 원본 삭제 후 접근 가능 여부
- 4df / du / stat / find 명령어 — 디스크 사용량 분석과 대용량 파일 탐색
- 5/etc/fstab 6개 필드 구조 — dump/pass 필드 의미, UUID 사용 이유
이 챕터는 별도 소프트웨어 설치 없이 기본 리눅스 명령어만으로 진행합니다. /tmp 공간이 최소 100MB 이상 남아 있는지 확인하세요. 사용자 계정으로 진행하며 일부 명령어는 sudo가 필요합니다.
Ubuntu 22.04 LTS도 동일하게 실습 가능
df -hTls -la /mkdir -p /tmp/fs-lab && echo '준비 완료'FHS 표준 디렉토리 구조
Filesystem Hierarchy Standard란?
FHS(Filesystem Hierarchy Standard)는 리눅스/유닉스 시스템에서 디렉토리와 파일의 위치를 표준화한 규격입니다. 배포판마다 경로가 달라지면 스크립트와 소프트웨어의 이식성이 깨집니다. FHS 덕분에 CentOS에서 작성한 관리 스크립트가 Ubuntu에서도 동일하게 동작합니다.
/
├── bin → 모든 사용자용 필수 명령어 바이너리 (ls, cp, mv, cat, bash...)
├── sbin → 시스템 관리자용 필수 바이너리 (fdisk, fsck, ip, ifconfig...)
├── usr/
│ ├── bin → 추가 사용자 명령어 (gcc, git, vim, python3...)
│ ├── sbin → 추가 시스템 관리 명령어 (useradd, groupadd...)
│ ├── lib → /usr/bin, /usr/sbin 공유 라이브러리
│ └── local/ → 관리자가 직접 빌드한 소프트웨어 (make install 기본 경로)
│ ├── bin/
│ └── lib/
├── etc → 시스템 전역 설정 파일 (/etc/passwd, /etc/nginx/nginx.conf...)
├── var/
│ ├── log → 로그 파일 (/var/log/messages, /var/log/secure...)
│ ├── spool → 메일, 프린터 큐 (/var/spool/mail...)
│ ├── lib → 서비스 상태 데이터 (/var/lib/mysql...)
│ └── run → 런타임 PID 파일 (/var/run/nginx.pid...)
├── tmp → 임시 파일 (재부팅 시 삭제, 누구나 쓰기 가능, sticky bit 설정)
├── home → 일반 사용자 홈 디렉토리 (/home/alice, /home/bob)
├── root → root 사용자 홈 디렉토리 (루트 파티션 / 가 아님!)
├── opt → 벤더 제공 독립 패키지 (/opt/oracle, /opt/splunk, /opt/jdk-21)
├── mnt → 수동 임시 마운트 포인트 (관리자가 직접 마운트)
├── media → 자동 마운트 이동식 미디어 (USB, CD-ROM)
├── dev → 디바이스 파일 (/dev/sda, /dev/null, /dev/random, /dev/tty...)
├── proc → 프로세스/커널 정보 가상 파일시스템 (메모리에만 존재, 디스크 0)
├── sys → 커널 서브시스템/하드웨어 정보 가상 파일시스템 (sysfs)
├── boot → 부트로더, 커널 이미지 (vmlinuz-x.y.z, initramfs...)
├── lib → /bin, /sbin 실행에 필요한 공유 라이브러리 (.so 파일)
└── srv → 서비스 데이터 (FTP 루트, HTTP 루트 등)
핵심 디렉토리 역할 비교
| 디렉토리 | 특성 | 대표 파일/용도 |
|---|---|---|
/bin | 싱글유저 모드에서도 필요한 필수 명령어 | ls, cp, bash, cat |
/sbin | 시스템 부팅/복구에 필요한 관리 명령어 | fdisk, mkfs, fsck |
/usr/bin | 멀티유저 환경 일반 명령어 | gcc, vim, git, python3 |
/usr/local/bin | 관리자가 직접 컴파일/설치한 소프트웨어 | 커스텀 빌드 프로그램 |
/opt | 독립 패키지 전체 (자체 bin/lib 포함) | /opt/jdk-21/bin/java |
/etc | 정적 설정 파일 (실행 파일 없음) | passwd, fstab, sshd_config |
/var | 가변 데이터 (로그, 캐시, 데이터베이스) | /var/log/messages |
/tmp | 임시 파일, 재부팅 시 삭제 보장 | 빌드 임시 파일, 업로드 버퍼 |
/proc | 가상 파일시스템, 디스크 미사용 | /proc/cpuinfo, /proc/[PID]/ |
/sys | 가상 파일시스템, 커널 오브젝트 노출 | /sys/class/net/eth0/ |
/proc와 /sys 가상 파일시스템
# /proc는 디스크 공간을 전혀 사용하지 않음
df -h /proc
# Filesystem Size Used Avail Use% Mounted on
# proc 0 0 0 - /proc
# 커널 정보 실시간 조회
cat /proc/cpuinfo | grep "model name" | head -1
# model name : Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz
cat /proc/meminfo | head -3
# MemTotal: 8169748 kB
# MemFree: 2034000 kB
# MemAvailable: 5832000 kB
cat /proc/version
# Linux version 5.14.0 (gcc 11.4.1) #1 SMP PREEMPT
# /proc/sys는 커널 파라미터 읽기/쓰기 가능
cat /proc/sys/net/ipv4/ip_forward
# 0
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward # 런타임 변경
# 또는: sudo sysctl -w net.ipv4.ip_forward=1
# /proc/[PID] - 개별 프로세스 정보
ls /proc/1/
# cmdline cwd environ exe fd maps mem mounts net status
cat /proc/1/cmdline | tr '\0' ' '
# /usr/lib/systemd/systemd --switched-root --system
/bin과 /usr/bin: 현대 리눅스의 변화
# 전통적 FHS: /bin은 싱글유저 모드 전용, /usr/bin은 멀티유저
# 현대 배포판(CentOS 7+, Ubuntu 20.04+): usrmerge로 통합
ls -la /bin
# lrwxrwxrwx 1 root root 7 /bin -> usr/bin ← 심볼릭링크!
# 시험에서는 전통적 FHS 기준으로 답안 선택
# /bin: 부팅 필수 명령어
# /usr/bin: 일반 사용자 명령어
# /usr/local/bin: 직접 설치한 소프트웨어
inode 구조와 파일 저장 메커니즘
inode란 무엇인가?
파일시스템은 크게 두 영역으로 구성됩니다: inode 영역(메타데이터)과 데이터 블록 영역(실제 내용)입니다.
파일시스템 전체 구조
┌──────────────────────────────────────────────────────┐
│ 슈퍼블록: 파일시스템 전체 메타데이터 (블록 크기, 총 inode 수...) │
├──────────────────────────────────────────────────────┤
│ inode 비트맵: 어떤 inode 번호가 사용 중인지 추적 │
├──────────────────────────────────────────────────────┤
│ 데이터 비트맵: 어떤 데이터 블록이 사용 중인지 추적 │
├──────────────────────────────────────────────────────┤
│ inode 테이블: inode 구조체 배열 │
│ [inode#1234][inode#1235][inode#1236]... │
├──────────────────────────────────────────────────────┤
│ 데이터 블록: 실제 파일 내용 │
└──────────────────────────────────────────────────────┘
inode 구조 상세
inode #1234
┌──────────────────────────────────┐
│ 파일 타입 + 접근 권한 (mode) │ -rw-r--r-- = 0644
│ 링크 카운트 (link count) │ 하드링크 수
│ 소유자 UID │
│ 소유 그룹 GID │
│ 파일 크기 (bytes) │
│ atime: 마지막 접근(read) 시간 │
│ mtime: 마지막 내용 수정 시간 │
│ ctime: 마지막 메타데이터 변경 시간 │ chmod, chown, rename 포함
│ 직접 블록 포인터 × 12 │
│ 단일 간접 블록 포인터 │
│ 이중 간접 블록 포인터 │
│ 삼중 간접 블록 포인터 │
└──────────────────────────────────┘
※ 파일명은 inode에 없음 → 디렉토리 엔트리에 저장
inode에 저장되는 정보 vs 저장되지 않는 정보
| inode에 저장됨 | inode에 저장되지 않음 |
|---|---|
| 파일 타입 (일반/디렉토리/링크/소켓...) | 파일명 |
| 접근 권한 (chmod 값) | 파일 내용 (데이터 블록에 저장) |
| 소유자 UID / 그룹 GID | 파일의 경로 (전체 경로) |
| 파일 크기 (bytes) | |
| 하드링크 카운트 | |
| atime / mtime / ctime | |
| 데이터 블록 포인터 |
핵심 포인트: 파일명은 디렉토리 파일(엔트리)에 저장됩니다. 디렉토리는 내부적으로 "파일명 → inode 번호" 매핑 테이블입니다.
디렉토리 내부 구조
/tmp/fs-lab/ 디렉토리 파일의 실제 내용
┌─────────────────┬───────────┐
│ 파일명 │ inode 번호 │
├─────────────────┼───────────┤
│ . (현재) │ 262145 │
│ .. (부모) │ 131073 │
│ original.txt │ 262146 │
│ hardlink.txt │ 262146 │ ← 동일한 inode! (하드링크)
│ symlink.txt │ 262147 │ ← 별도 inode (심볼릭링크)
└─────────────────┴───────────┘
inode 번호와 상세 정보 확인
# -i 옵션으로 inode 번호 표시
ls -li /etc/passwd
# 131073 -rw-r--r-- 1 root root 2847 Mar 15 09:00 /etc/passwd
# └─inode번호 └링크수
# stat 명령으로 inode 전체 정보 확인
stat /etc/passwd
# File: /etc/passwd
# Size: 2847 Blocks: 8 IO Block: 4096 regular file
# Device: fd00h Inode: 131073 Links: 1
# Access: (0644/-rw-r--r--) Uid: (0/root) Gid: (0/root)
# Access: 2026-03-28 09:00:00.000000000 +0900 ← atime
# Modify: 2026-03-15 09:00:00.000000000 +0900 ← mtime (내용 변경)
# Change: 2026-03-15 09:00:00.000000000 +0900 ← ctime (메타데이터 변경)
# file 명령으로 실제 파일 타입 확인 (확장자와 무관)
file /bin/ls /etc/passwd /dev/sda /usr/lib/libc.so.6
# /bin/ls: ELF 64-bit LSB pie executable, x86-64
# /etc/passwd: ASCII text
# /dev/sda: block special (8/0)
# /usr/lib/libc.so.6: ELF 64-bit LSB shared object
하드링크(Hard Link)
하드링크는 기존 inode를 가리키는 새로운 디렉토리 엔트리입니다. 원본과 동일한 inode 번호를 공유합니다.
# 하드링크 생성 (ln 명령, -s 옵션 없음)
echo "중요한 데이터" > original.txt
ln original.txt hardlink.txt
# inode 번호가 동일한지 확인
ls -li original.txt hardlink.txt
# 268451 -rw-r--r-- 2 user user 15 Mar 28 10:00 hardlink.txt
# 268451 -rw-r--r-- 2 user user 15 Mar 28 10:00 original.txt
# └─동일한 inode! └─링크 카운트 = 2로 증가
# 원본 삭제 → 링크 카운트 1 감소, 데이터 블록은 유지
rm original.txt
cat hardlink.txt
# 중요한 데이터 ← 여전히 읽기 가능!
ls -li hardlink.txt
# 268451 -rw-r--r-- 1 user user 15 Mar 28 10:00 hardlink.txt
# └─ 링크 카운트 = 1로 감소 (0이 되면 실제 삭제)
하드링크 제약:
- 다른 파일시스템(파티션) 간에는 생성 불가
- 디렉토리에는 생성 불가 (순환 참조 방지)
심볼릭링크(Symbolic Link / Soft Link)
심볼릭링크는 대상 경로 문자열을 저장하는 독립적인 파일입니다. 별도의 inode를 가집니다.
# 심볼릭링크 생성 (-s 옵션 필수)
echo "대상 파일" > target.txt
ln -s target.txt symlink.txt # 상대경로 심볼릭링크
ln -s /etc/hostname /tmp/hostname_link # 절대경로 심볼릭링크
# inode 번호가 다름을 확인
ls -li target.txt symlink.txt
# 268451 -rw-r--r-- 1 user user 10 Mar 28 10:00 target.txt
# 268452 lrwxrwxrwx 1 user user 10 Mar 28 10:00 symlink.txt -> target.txt
# └─다른 inode! └─ 'l' = 링크 타입
# 원본 삭제 → 심볼릭링크가 깨짐 (Dangling Link)
rm target.txt
cat symlink.txt
# cat: symlink.txt: No such file or directory ← 접근 불가!
# 깨진 심볼릭링크 찾기
find /tmp -type l ! -follow 2>/dev/null
# /tmp/symlink.txt ← 빨간색으로 표시됨
하드링크 vs 심볼릭링크 핵심 비교
| 항목 | 하드링크 | 심볼릭링크 |
|---|---|---|
| inode 공유 | 원본과 동일한 inode | 별도 inode 할당 |
| 원본 삭제 후 | 데이터 유지 (링크 카운트 기반) | 깨진 링크 (Dangling Link) |
| 파티션 간 생성 | 불가 (동일 파일시스템만) | 가능 |
| 디렉토리 대상 | 불가 | 가능 |
| ls -l 표시 | 일반 파일과 동일 (-) | l 타입, -> 경로 표시 |
| 링크 카운트 변화 | 증가 (원본 삭제 시 감소) | 변화 없음 |
| 실무 사용 예 | 로그 백업, 공간 절약 | /usr/bin/python -> python3.11, nginx sites-enabled |
/etc/fstab 구조와 마운트 관리
/etc/fstab 파일 형식
/etc/fstab은 시스템 부팅 시 자동으로 마운트할 파일시스템을 정의합니다. 6개 필드를 공백(탭/스페이스)으로 구분합니다.
# /etc/fstab 예시
# <장치> <마운트포인트> <타입> <옵션> <dump> <pass>
UUID=a1b2c3d4-e5f6-... / xfs defaults 0 1
UUID=c9d0e1f2-a3b4-... /boot xfs defaults 0 2
UUID=e5f6a7b8-c9d0-... /home ext4 defaults,noexec 1 2
UUID=b3c4d5e6-a7b8-... swap swap defaults 0 0
tmpfs /tmp tmpfs defaults,nosuid,size=1g 0 0
//192.168.1.100/share /mnt/nas cifs credentials=/etc/samba/creds,uid=1000 0 0
6개 필드 상세 설명
| 필드 번호 | 이름 | 설명 |
|---|---|---|
| 1번째 | 장치 | UUID=..., LABEL=..., /dev/sda1, NFS/CIFS 주소 |
| 2번째 | 마운트 포인트 | 마운트될 디렉토리 경로 (swap은 예외로 "swap" 사용) |
| 3번째 | 파일시스템 타입 | ext4, xfs, btrfs, tmpfs, nfs, cifs, swap, vfat 등 |
| 4번째 | 마운트 옵션 | defaults, ro, noexec, nosuid, nodev, user, bind 등 |
| 5번째 | dump | 0: dump 백업 비대상, 1: dump 백업 대상 |
| 6번째 | pass | 0: fsck 안 함, 1: 루트 파일시스템(최우선), 2: 기타 |
시험 필수 암기: 5번째(dump) = 백업 여부, 6번째(pass) = fsck 점검 순서
주요 마운트 옵션
# defaults = rw,suid,dev,exec,auto,nouser,async 의 조합
# 보안 강화 옵션 (조합 사용 권장)
noexec # 실행 파일 실행 금지 (/tmp, /home 적용 권장)
nosuid # setuid/setgid 비트 무시 (권한 상승 방지)
nodev # 디바이스 파일 인식 금지
# 마운트 동작
ro # 읽기 전용 (read-only)
rw # 읽기/쓰기 (기본값)
auto # 부팅 시 자동 마운트 (기본 포함)
noauto # 수동 마운트만 허용 (mount -a에서 제외)
user # 일반 사용자도 마운트 가능
# fstab 변경 후 적용
sudo mount -a # fstab에 정의된 미마운트 항목 전체 마운트
sudo mount -o remount / # 루트 파일시스템 옵션 변경 적용
UUID 사용 이유
# 장치명(/dev/sdb)은 디스크 추가/제거 시 변경될 수 있음
# UUID는 포맷 시 부여된 고유 값으로 영구적
# UUID 확인 방법
blkid
# /dev/sda1: UUID="a1b2c3d4-e5f6-7890-abcd-ef1234567890" TYPE="xfs"
# /dev/sda2: UUID="c9d0e1f2-a3b4-5678-cdef-012345678901" TYPE="xfs"
blkid /dev/sda1 # 특정 장치만 확인
lsblk -o NAME,UUID,TYPE # 트리 형태로 확인
목표
inode 번호, 링크 카운트, 파일 타입을 직접 확인하여 하드링크와 심볼릭링크의 동작을 체감합니다.
실습 환경 준비
mkdir -p /tmp/linux-master/part1/filesystem && cd /tmp/linux-master/part1/filesystem
# 실습용 디렉토리 구조 생성
mkdir -p {etc,var/log,var/lib,home/testuser,opt/myapp,tmp}
cat > etc/app.conf << 'EOF'
[server]
host = 0.0.0.0
port = 8080
log_dir = /var/log/myapp
[database]
host = localhost
port = 5432
name = mydb
EOF
for i in 1 2 3; do
echo "$(date): Log entry $i" >> var/log/app.log
done
echo "export APP_HOME=/opt/myapp" >> etc/environment
ls -la
mkdir -p /tmp/fs-lab && cd /tmp/fs-lab
echo "리눅스 inode 실습 파일" > original.txt
1단계: ls -lai로 inode 정보 확인
ls -lai /tmp/fs-lab/
# total 12
# 262145 drwxr-xr-x 2 user user 4096 Mar 28 10:00 . ← 현재 디렉토리 inode
# 131073 drwxrwxrwt 18 root root 4096 Mar 28 10:00 .. ← 부모(/tmp) inode
# 262146 -rw-r--r-- 1 user user 22 Mar 28 10:00 original.txt
# ↑inode번호 ↑링크수
출력 필드: inode번호 | 권한 | 링크수 | 소유자 | 그룹 | 크기 | 날짜 | 파일명
2단계: 하드링크 생성 후 inode 변화 관찰
ln original.txt hardlink.txt
ls -lai
# 262146 -rw-r--r-- 2 user user 22 Mar 28 10:00 hardlink.txt
# 262146 -rw-r--r-- 2 user user 22 Mar 28 10:00 original.txt
# └─ 동일한 inode 번호! └─ 링크 카운트 2로 증가
# stat으로 링크 카운트 명시적 확인
stat original.txt | grep Links
# Links: 2
3단계: 심볼릭링크 생성 후 inode 비교
ln -s original.txt symlink.txt
ls -lai
# 262146 -rw-r--r-- 2 user user 22 Mar 28 10:00 hardlink.txt ← inode 262146
# 262146 -rw-r--r-- 2 user user 22 Mar 28 10:00 original.txt ← inode 262146 (동일)
# 262147 lrwxrwxrwx 1 user user 12 Mar 28 10:00 symlink.txt -> original.txt ← 다른 inode!
# ↑다른 번호 ↑l=link타입
4단계: 원본 삭제 후 각 링크 동작 확인
rm original.txt
# 하드링크는 여전히 읽기 가능
cat hardlink.txt
# 리눅스 inode 실습 파일 ← 정상 출력
ls -li hardlink.txt
# 262146 -rw-r--r-- 1 user user 22 Mar 28 10:00 hardlink.txt
# └─ 링크 카운트가 1로 감소 (0이 되면 데이터 블록 해제)
# 심볼릭링크는 깨짐 (Dangling Link)
cat symlink.txt
# cat: symlink.txt: No such file or directory
ls -la symlink.txt
# lrwxrwxrwx 1 user user 12 Mar 28 10:00 symlink.txt -> original.txt
# (터미널에서 빨간색으로 표시됨)
5단계: find로 깨진 심볼릭링크 탐색
find /tmp/fs-lab -type l ! -follow
# /tmp/fs-lab/symlink.txt ← 깨진 심볼릭링크 목록 출력
목표
df와 du 명령어를 활용하여 파일시스템 사용량을 분석하고 대용량 파일 또는 디렉토리를 찾습니다.
1단계: df로 파일시스템 전체 현황 파악
# -h: human-readable 단위, -T: 파일시스템 타입 표시
df -hT
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/mapper/rl-root xfs 47G 12G 35G 26% /
# /dev/sda1 xfs 1014M 261M 754M 26% /boot
# /dev/mapper/rl-home xfs 24G 200M 24G 1% /home
# tmpfs tmpfs 1.9G 12K 1.9G 1% /run
# proc proc 0 0 0 - /proc ← 0bytes!
# inode 사용량 확인 (이것도 반드시 체크!)
df -i
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/mapper/rl 6291456 45231 6246225 1% /
# (IUse%가 100%면 inode 고갈 → "No space left on device" 오류 발생)
2단계: du로 디렉토리별 용량 분석
# 최상위 디렉토리별 용량 (1레벨 요약)
sudo du -sh /*
# 0 /dev ← 디바이스 파일, 실제 크기 없음
# 0 /proc ← 가상 파일시스템
# 5.3M /etc
# 89M /var
# 227M /usr
# 특정 디렉토리 하위 분석 (정렬 포함)
du -sh /var/* | sort -rh | head -10
# 50M /var/log
# 12M /var/cache
# 1.1M /var/lib
# /var/log 내부 드릴다운
du -sh /var/log/* | sort -rh | head -5
# 28M /var/log/journal
# 15M /var/log/audit
# 4.5M /var/log/messages
3단계: find로 대용량 파일 검색
# 100MB 이상 파일 검색
find / -xdev -type f -size +100M -exec ls -lh {} \; 2>/dev/null
# -rwxr-xr-x 1 root root 150M /usr/lib/jvm/java-17/lib/server/libjvm.so
# -rw-r--r-- 1 root root 2.1G /var/lib/mysql/ibdata1
# 오늘 새로 생긴 대용량 파일
find /var -newer /var/log/wtmp -size +50M -type f 2>/dev/null
# 특정 사용자의 파일 총 용량
find /home/alice -type f -exec du -ch {} + | tail -1
# 2.3G total
4단계: stat으로 파일 상세 메타데이터 확인
stat /etc/hostname
# File: /etc/hostname
# Size: 10 Blocks: 8 IO Block: 4096 regular file
# Device: fd00h Inode: 262148 Links: 1
# Access: (0644/-rw-r--r--) Uid: (0/root) Gid: (0/root)
# Access: 2026-03-28 09:00:00 ← 마지막 읽기
# Modify: 2026-03-20 14:23:11 ← 마지막 내용 변경
# Change: 2026-03-20 14:23:11 ← 마지막 메타데이터 변경 (chmod 포함)
목표
/etc/fstab 각 필드를 정확히 해석하고, mount/umount 명령으로 수동 마운트를 실습합니다.
1단계: 현재 fstab 내용 분석
cat /etc/fstab
# UUID=a1b2c3d4... / xfs defaults 0 1
# UUID=c9d0e1f2... /boot xfs defaults 0 2
# UUID=e5f6a7b8... swap swap defaults 0 0
각 줄을 직접 해석해 보세요:
- 1번째 필드: 장치 (UUID로 지정, blkid로 확인 가능)
- 2번째 필드: 마운트 포인트 (swap은 "swap")
- 3번째 필드: 파일시스템 타입
- 4번째 필드: 옵션 (defaults의 구성 요소는?)
- 5번째 필드: dump 백업 (0=비대상, 1=대상)
- 6번째 필드: fsck 순서 (0=안함, 1=루트, 2=기타)
2단계: tmpfs 임시 마운트 실습
# /mnt/tmptest에 100MB 제한 tmpfs 마운트
sudo mkdir -p /mnt/tmptest
sudo mount -t tmpfs -o size=100m tmpfs /mnt/tmptest
# 마운트 확인
df -hT /mnt/tmptest
# Filesystem Type Size Used Avail Use% Mounted on
# tmpfs tmpfs 100M 0 100M 0% /mnt/tmptest
mount | grep tmptest
# tmpfs on /mnt/tmptest type tmpfs (rw,relatime,size=102400k)
# 파일 생성 테스트
echo "tmpfs 테스트" > /mnt/tmptest/test.txt
cat /mnt/tmptest/test.txt
# tmpfs 테스트
# 언마운트 (재부팅하지 않고 해제)
sudo umount /mnt/tmptest
ls /mnt/tmptest
# (비어 있음 - tmpfs 내용 사라짐)
3단계: 다양한 방법으로 마운트 정보 확인
# 방법 1: mount 명령 (현재 마운트 목록)
mount | grep -v "cgroup\|proc\|sys\|dev" | column -t
# 방법 2: /proc/mounts (커널이 직접 관리하는 마운트 정보)
cat /proc/mounts | grep -v "^none" | head -10
# 방법 3: findmnt (트리 형태로 가장 보기 좋음)
findmnt
# TARGET SOURCE FSTYPE OPTIONS
# / /dev/mapper/rl xfs rw,relatime
# ├─/sys sysfs sysfs rw,nosuid,nodev
# ├─/proc proc proc rw,nosuid,nodev
# └─/home /dev/mapper/rl-h xfs rw,relatime
# UUID 조회 (fstab 작성 시 필요)
sudo blkid | grep -v swap
# /dev/sda1: UUID="a1b2c3d4-..." TYPE="xfs"
- ls -lai 결과에서 하드링크로 만든 두 파일의 inode 번호가 동일한가?
- df -h 와 df -i 의 결과가 다를 수 있음을 확인했는가? (용량과 inode는 별개 자원)
- du -sh /* 로 가장 큰 디렉토리를 한눈에 찾을 수 있는가?
- fstab에 잘못된 UUID를 넣으면 부팅이 막힐 수 있으니, mount -a 로 검증 후 재부팅하는 습관이 있는가?
문제 상황
웹 서버 로그 기록 도중 갑자기 쓰기 오류가 발생했습니다.
$ echo "test" > /var/log/app/new.log
bash: /var/log/app/new.log: No space left on device
$ df -h /var/log
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/rl-root 47G 23G 24G 49% /
# 24GB가 남아 있는데 왜 오류가?!
원인 진단: inode 고갈
디스크 블록 공간은 남았지만 inode가 모두 소진된 상황입니다. 소용량 파일이 수백만 개 생성될 때 발생합니다.
# inode 사용량 확인 — 핵심 진단 명령어!
df -i /
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/mapper/rl 6291456 6291456 0 100% /
# └──── 0개 남음! 이것이 원인
# 어디에 파일이 집중됐는지 찾기
find / -xdev -type d | while read d; do
count=$(find "$d" -maxdepth 1 -type f 2>/dev/null | wc -l)
echo "$count $d"
done | sort -rn | head -10
# 891234 /var/spool/clientmqueue
# 423156 /tmp/php_sessions
원인 파악
ls /var/spool/clientmqueue | wc -l
# 891234 ← sendmail 오류 메일 큐 누적
find /tmp -name "sess_*" | wc -l
# 423156 ← PHP 세션 파일 미정리
해결 방법
# 1. 불필요한 소용량 파일 대량 삭제
find /var/spool/clientmqueue -type f -delete
find /tmp -name "sess_*" -mtime +1 -delete
# 2. 정리 후 inode 사용률 확인
df -i /
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/mapper/rl 6291456 145231 6146225 3% /
예방 방법
# PHP 세션 자동 정리 (cron)
# /etc/cron.d/php-session-cleanup
# 0 * * * * root find /var/lib/php/session -type f -mtime +1 -delete
# sendmail 큐 자동 정리 (systemd timer 또는 logrotate)
# 파일시스템 생성 시 inode 수 조정 (소용량 파일 많은 파티션)
mkfs.ext4 -i 4096 /dev/sdb1 # 기본(16384) 대비 4배 많은 inode
문제 상황
logrotate가 실행되어 /var/log/app.log가 rotate됐는데, df 결과가 전혀 변하지 않습니다.
$ ls -lh /var/log/app.log*
-rw-r--r-- 1 app app 0 Mar 28 03:00 /var/log/app.log ← 새 빈 파일
-rw-r--r-- 1 app app 8.2G Mar 27 23:59 /var/log/app.log.1 ← rotate된 파일
$ df -h /
Filesystem Size Used Avail Use%
/dev/sda1 50G 48G 2.0G 96% ← 8.2GB가 회복되지 않았다!
원인: 프로세스가 삭제된 파일의 fd(file descriptor)를 계속 보유
파일이 rename/delete되어도 프로세스가 파일을 열어두고 있으면 inode 참조 카운트가 0이 되지 않아 실제 디스크 블록이 해제되지 않습니다.
# 삭제됐지만 프로세스가 열어두고 있는 파일 찾기
lsof | grep deleted
# java 12345 app 3w REG 253,0 8825052160 131074 /var/log/app.log (deleted)
# └─ PID 12345 프로세스가 삭제(renamed)된 파일을 fd 3으로 계속 쓰고 있음
# 특정 파일시스템 기준으로 확인
lsof +L1 /var/log
해결 방법
# 방법 1: 애플리케이션에 SIGHUP 전송 → 로그 파일 재오픈 (권장)
kill -HUP $(cat /var/run/myapp.pid)
# systemd 서비스라면 reload
systemctl reload myapp
# 방법 2: 프로세스 재시작
systemctl restart myapp
# 방법 3: 실행 중인 채로 파일 내용만 비우기 (임시 조치, 데이터 유실)
> /proc/12345/fd/3 # 해당 fd를 직접 truncate
logrotate 설정에 postrotate 추가 (근본 해결)
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 14
compress
missingok
notifempty
postrotate
kill -HUP $(cat /var/run/myapp.pid 2>/dev/null) 2>/dev/null || true
endscript
}
# 또는 copytruncate 옵션: 복사 후 원본을 0바이트로 잘라내므로 재오픈 불필요
# (단, 복사~truncate 사이 짧은 순간 데이터 유실 가능)
실제 업무 시나리오
모니터링 알람: "운영 서버 / 파티션 사용률 85% 초과". 어떤 순서로 분석하고 조치할까요?
표준 진단 절차 (5분 이내)
# 1단계: 전체 파일시스템 현황 파악 (30초)
df -hT # 블록 사용량 확인
df -i # inode 사용량도 반드시 확인
# 2단계: 주요 디렉토리별 용량 비교 (1분)
sudo du -sh /var/log /var/lib /home /opt /tmp 2>/dev/null
# 예시 출력:
# 32G /var/log ← 로그가 주범
# 8.5G /var/lib
# 2.1G /home
# 3단계: 주범 디렉토리 내부 드릴다운 (2분)
sudo du -sh /var/log/* | sort -rh | head -10
# 28G /var/log/nginx/access.log ← nginx 접근 로그 이상 증가
# 4단계: 해당 파일 상세 확인
ls -lh /var/log/nginx/access.log
stat /var/log/nginx/access.log
# 5단계: 프로세스가 잡고 있는지 확인
lsof /var/log/nginx/access.log
# 6단계: 조치
logrotate -f /etc/logrotate.d/nginx # 강제 rotate + SIGHUP
현업에서 자주 쓰는 one-liner
# 시스템 전체에서 가장 큰 파일 Top 20
find / -xdev -type f -exec du -Sh {} + 2>/dev/null | sort -rh | head -20
# 삭제됐지만 열린 fd로 낭비되는 공간 총합
lsof +L1 | awk 'NR>1 {sum += $8} END {printf "%.1f GB\n", sum/1024/1024/1024}'
# 30일 이상 접근 안 된 로그 파일 목록
find /var/log -type f -atime +30 -name "*.log" -exec ls -lh {} \;
증상별 진단 명령어 요약표
| 증상 | 진단 명령어 | 예상 원인 |
|---|---|---|
| df 꽉 참, du 합계 불일치 | lsof | grep deleted | 열린 fd로 인한 미해제 블록 |
| df 여유 있음, 쓰기 오류 | df -i | inode 고갈 |
| 대용량 파일 위치 모름 | du -sh /* | sort -rh | 디렉토리별 드릴다운 |
| 파일 수정 시각 확인 | stat <파일> | atime/mtime/ctime 확인 |
| 디스크 블록 낭비 의심 | lsof +L1 | 프로세스가 삭제 파일 점유 |