デバイス、ファイルシステム、マウントの三層
한국어 원문으로 표시합니다.
한 줄 요약
블록 장치는 바이트의 배열이고, 파일시스템은 그 배열 위에 얹은 자료구조이며, 마운트는 그 자료구조를 디렉터리 트리에 붙이는 행위다. 이 셋은 완전히 별개다.
왜 이게 필요했나
"디스크가 꽉 찼어요" 라는 한 문장에 최소 네 가지 다른 문제가 섞여 있다.
- 블록이 정말 다 찼다 (
df -h가 100%) - inode 가 먼저 떨어졌다 (
df -i가 100%, 블록은 여유) - 지운 파일을 누가 열고 있어 공간이 반환되지 않는다 (
df와du불일치) - 애초에 그 경로가 예상한 파일시스템이 아니다 (마운트가 안 됐다)
넷은 원인도 대응도 전혀 다르다. 그런데 증상은 똑같이 No space left on device 다. 세 층을 구분해야 이 넷이 갈린다.
어떻게 동작하나
층 1 — 블록 장치
/dev/sda, /dev/nvme0n1p1, 루프 장치 /dev/loop0 같은 것들이다. 커널에게는 그냥 읽고 쓸 수 있는 바이트 배열이다.
여기서 중요한 사실 하나. 파일시스템은 블록 장치가 아니라 평범한 파일 위에도 만들 수 있다. mkfs.ext4 는 대상이 장치인지 파일인지 신경 쓰지 않는다. 그저 그 대상의 처음부터 정해진 구조를 써 넣을 뿐이다. 이 성질 덕분에 특권 없이도 파일시스템 내부를 학습할 수 있다.
truncate -s 256M /root/disk/lab.img # 희소 파일 — 실제 점유는 0
mkfs.ext4 -F -L LABHUB /root/disk/lab.img
blkid /root/disk/lab.img
dumpe2fs -h /root/disk/lab.img
debugfs -R 'ls -l /' /root/disk/lab.img
truncate 로 만든 파일은 희소 파일(sparse) 이다. 논리 크기는 256MB 인데 실제 디스크 점유는 거의 0 이다. du --apparent-size 와 du 의 값이 다른 것으로 확인할 수 있다.
층 2 — 파일시스템
mkfs 가 만드는 것은 슈퍼블록 + inode 테이블 + 블록 그룹이다. dumpe2fs -h 로 슈퍼블록을 읽으면 이런 값들이 나온다.
| 항목 | 의미 | 왜 중요한가 |
|---|---|---|
| Block size | 블록 하나의 크기(보통 4096) | 작은 파일이 많으면 내부 단편화 |
| Inode count | 포맷 시점에 고정된 inode 개수 | ext4 는 나중에 못 늘린다 |
| Reserved block count | root 전용 예약 블록 | 기본 5%, 대용량 데이터 디스크에서는 낭비 |
| Filesystem UUID | 이 파일시스템의 고유 ID | fstab 에서 장치명 대신 쓴다 |
inode 는 포맷할 때 개수가 정해지고 나중에 늘릴 수 없다. 그래서 세션 파일이나 메일 큐가 작은 파일 수백만 개를 만들면 블록은 남아도 inode 가 먼저 떨어진다. 용량 알람은 df -h 와 df -i 둘 다 걸어야 하는 이유다.
예약 블록도 실무 포인트다. 기본 5% 는 루트 파일시스템에서는 합리적이지만(root 가 로그를 쓸 여유를 남긴다) 8TB 데이터 디스크에서는 400GB 를 놀리는 셈이다. tune2fs -m 1 로 낮춘다.
층 3 — 마운트
마운트는 파일시스템을 디렉터리 트리의 한 지점에 붙인다. 여기서 두 가지를 구분해야 한다.
- 장치명(
/dev/sdb1)은 불안정하다. 디스크를 추가하거나 부팅 순서가 바뀌면 sdb 가 sdc 가 된다. 그래서 fstab 에는 UUID 나 LABEL 을 쓴다. - 마운트 지점 디렉터리에 원래 있던 파일은 가려질 뿐 사라지지 않는다. 마운트를 풀면 다시 보인다. "데이터가 사라졌다" 는 신고가 사실은 "새 파일시스템이 그 위에 덮였다" 인 경우가 있다.
디스크를 붙이고 나서 반드시 하는 것들
새 디스크를 붙여 마운트까지 하는 절차는 짧지만, 빠뜨리면 재부팅에서 드러나는 항목이 있다.
이름이 아니라 UUID 로 적는다. /dev/sdb 는 장치 순서에 따라 바뀐다.
디스크를 하나 더 붙이거나 순서가 달라지면 /etc/fstab 의 그 줄이 다른 디스크를
가리키게 되고, 최악의 경우 부팅이 멈춘다.
blkid /dev/sdb1
# UUID="..." TYPE="ext4"
fstab 에 nofail 을 넣을지 정한다. 없으면 그 디스크가 안 붙었을 때 부팅이
비상 셸로 떨어진다. 데이터 디스크라면 nofail 을 넣어 시스템은 뜨게 하고
경보로 알리는 편이 낫고, 그 디스크가 없으면 서비스가 의미 없는 경우라면
빼는 것이 맞다.
고치기 전에 마운트가 되는지 시험한다. 재부팅해서 확인하는 것은 가장 비싼 방법이다.
mount -a # fstab 대로 전부 붙여 본다. 오류가 나면 여기서 난다
findmnt --verify # 문법과 대상까지 검사한다
정렬과 예약 블록을 본다. ext4 는 기본으로 5%를 root 에 예약하는데, 1TB 데이터 디스크에서는 50GB 를 놀리는 셈이다. 데이터 전용이면 줄여도 된다.
tune2fs -m 1 /dev/sdb1
늘리는 것은 두 단계다. 클라우드에서 볼륨을 키워도 파일 시스템은 그대로다.
파티션을 늘리고(growpart), 그다음 파일 시스템을 늘린다(resize2fs 또는
xfs_growfs). 마운트한 채로 늘릴 수 있지만 줄이는 것은 대개 못 한다 —
XFS 는 축소를 아예 지원하지 않는다. 그래서 처음에 크게 잡는 것보다 작게
잡고 늘리는 쪽이 안전하다.
LVM 을 쓸지 지금 정한다. 나중에 바꾸려면 데이터를 옮겨야 한다. 디스크를 더할 계획이 있거나 스냅샷이 필요하면 처음부터 LVM 위에 만든다.
현장에서 만나는 모습
마운트 실패로 루트가 차는 사고. /var/lib/postgresql 에 별도 디스크를 마운트하도록 설계했는데 fstab 항목이 틀려 마운트가 안 됐다. 데이터베이스는 아무 불평 없이 루트 파일시스템에 데이터를 쓰기 시작한다. 며칠 뒤 루트가 100% 가 되고 시스템 전체가 멈춘다. 그래서 마운트 지점 디렉터리에 .not-mounted 같은 표식 파일을 두고 애플리케이션이 그걸 확인하게 하는 팀도 있다.
다음 실습에서 할 것
파일 위에 진짜 ext4 를 만들어 UUID 를 뽑고, 슈퍼블록을 읽고, tune2fs 로 예약 비율을 바꾸고, debugfs 로 안을 들여다본다. 마지막에는 그 이미지를 마운트하는 fstab 줄을 UUID 로 작성한다.