LabHub
배우기 러닝패스 코스

Storage and Mounts

Three Layers: Device, Filesystem, Mount

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

블록 장치는 바이트의 배열이고, 파일시스템은 그 배열 위에 얹은 자료구조이며, 마운트는 그 자료구조를 디렉터리 트리에 붙이는 행위다. 이 셋은 완전히 별개다.

Layer map: 블록 장치 · 파일시스템 · 마운트 · inode 가 먼저 떨어졌다

왜 이게 필요했나

"디스크가 꽉 찼어요" 라는 한 문장에 최소 네 가지 다른 문제가 섞여 있다.

  1. 블록이 정말 다 찼다 (df -h 가 100%)
  2. inode 가 먼저 떨어졌다 (df -i 가 100%, 블록은 여유)
  3. 지운 파일을 누가 열고 있어 공간이 반환되지 않는다 (dfdu 불일치)
  4. 애초에 그 경로가 예상한 파일시스템이 아니다 (마운트가 안 됐다)

넷은 원인도 대응도 전혀 다르다. 그런데 증상은 똑같이 No space left on device 다. 세 층을 구분해야 이 넷이 갈린다.

저장소의 세 층과 각 층에서 오는 증상. 맨 아래 블록 장치는 바이트 배열이고 여기서 오는 것은 블록이 정말 다 찬 경우다. 가운데 파일시스템은 슈퍼블록과 inode 테이블이며 inode 개수는 포맷할 때 고정되므로 블록이 남아도 inode 가 먼저 떨어질 수 있다. 맨 위 마운트에서는 지운 파일을 누가 열고 있거나 애초에 그 경로가 예상한 파일시스템이 아닌 경우가 나온다

어떻게 동작하나

층 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-sizedu 의 값이 다른 것으로 확인할 수 있다.

층 2 — 파일시스템

mkfs 가 만드는 것은 슈퍼블록 + inode 테이블 + 블록 그룹이다. dumpe2fs -h 로 슈퍼블록을 읽으면 이런 값들이 나온다.

항목 의미 왜 중요한가
Block size 블록 하나의 크기(보통 4096) 작은 파일이 많으면 내부 단편화
Inode count 포맷 시점에 고정된 inode 개수 ext4 는 나중에 못 늘린다
Reserved block count root 전용 예약 블록 기본 5%, 대용량 데이터 디스크에서는 낭비
Filesystem UUID 이 파일시스템의 고유 ID fstab 에서 장치명 대신 쓴다

inode 는 포맷할 때 개수가 정해지고 나중에 늘릴 수 없다. 그래서 세션 파일이나 메일 큐가 작은 파일 수백만 개를 만들면 블록은 남아도 inode 가 먼저 떨어진다. 용량 알람은 df -hdf -i 둘 다 걸어야 하는 이유다.

예약 블록도 실무 포인트다. 기본 5% 는 루트 파일시스템에서는 합리적이지만(root 가 로그를 쓸 여유를 남긴다) 8TB 데이터 디스크에서는 400GB 를 놀리는 셈이다. tune2fs -m 1 로 낮춘다.

층 3 — 마운트

마운트는 파일시스템을 디렉터리 트리의 한 지점에 붙인다. 여기서 두 가지를 구분해야 한다.

디스크를 붙이고 나서 반드시 하는 것들

새 디스크를 붙여 마운트까지 하는 절차는 짧지만, 빠뜨리면 재부팅에서 드러나는 항목이 있다.

이름이 아니라 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 로 작성한다.