파일시스템 Resizing에 대해

클라우드 VM의 디스크를 200GB로 늘렸는데 df -h는 여전히 100GB를 가리킨다. 반대로 LVM 볼륨을 줄였더니 파일시스템이 아예 마운트되지 않는다. 둘 다 흔한 사고이고 원인은 같다. 우리가 “디스크 크기”라고 뭉뚱그려 부르는 값은 실제로는 여러 층에 따로 기록돼 있고, 층마다 크기를 바꾸는 도구와 제약이 전부 다르다. 이 글에서는 그 층 구조와 순서 규칙을 정리한 뒤 ext4·XFS·Btrfs·F2FS, 그리고 LVM·파티션 층의 확장·축소를 loop 장치 위에서 직접 돌려 확인한다.

리사이즈는 여러 층에서 따로 일어난다

아래에서 위로 블록 장치 → 파티션 → (선택) 볼륨 관리자 → 파일시스템 순으로 쌓이고, 각 층은 자기 바로 아래 층의 크기만 본다. 아래 층을 키워도 위 층이 그대로면 df는 변하지 않는다.

크기를 바꾸는 도구커널에 반영하는 법
블록 장치하이퍼바이저/클라우드 콘솔, truncate(이미지 파일)losetup -c, echo 1 > /sys/block/sdX/device/rescan
파티션 테이블growpart, parted resizepart, sfdiskpartprobe, partx -u
볼륨 관리자(LVM)lvextend, lvreduce자동
파일시스템resize2fs, xfs_growfs, btrfs filesystem resize, resize.f2fs자동

순서 규칙은 한 문장으로 요약된다. 확장은 아래층부터 위로, 축소는 위층부터 아래로. 파일시스템이 자기 밑의 장치보다 커지는 순간이 한 번도 없어야 한다는 뜻이다. 이 순서를 어기면 뒤에서 볼 bad geometry 사고가 난다.

실습 환경

Ubuntu 24.04(커널 6.8.0-138), e2fsprogs 1.47.0, xfsprogs 6.6.0, btrfs-progs 6.6.3, f2fs-tools 1.16.0에서 스파스 이미지 파일을 loop 장치로 붙여 실험했다. 실제 디스크와 달리 크기를 자유롭게 바꿀 수 있어 리사이즈 실습에 적합하다.

truncate -s 2G disk.img
losetup -f --show disk.img          # /dev/loop4
mkfs.ext4 -q -L demo /dev/loop4
mkdir -p /mnt/demo && mount /dev/loop4 /mnt/demo
dd if=/dev/urandom of=/mnt/demo/data.bin bs=1M count=700 status=none
df -h /mnt/demo
Filesystem      Size  Used Avail Use% Mounted on
/dev/loop4      2.0G  701M  1.2G  39% /mnt/demo

ext4 — 온라인 확장, 오프라인 축소

확장

truncate -s 4G disk.img             # 백업 파일만 키운다
lsblk -b -o NAME,SIZE /dev/loop4
losetup -c /dev/loop4               # 커널에 용량 변경을 통보
lsblk -b -o NAME,SIZE /dev/loop4
resize2fs /dev/loop4                # 마운트된 채로 확장
df -h /mnt/demo
NAME        SIZE
loop4 2147483648

NAME        SIZE
loop4 4294967296

resize2fs 1.47.0 (5-Feb-2023)
Filesystem at /dev/loop4 is mounted on /mnt/demo; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 1
The filesystem on /dev/loop4 is now 1048576 (4k) blocks long.

Filesystem      Size  Used Avail Use% Mounted on
/dev/loop4      3.9G  701M  3.0G  19% /mnt/demo

truncate 직후 lsblk가 여전히 2147483648을 보여주는 것이 핵심이다. 블록 장치 층에 크기 변경이 반영되기 전에는 resize2fs가 늘릴 공간을 찾지 못한다. 실제 디스크라면 losetup -c 대신 SCSI 재스캔이나 partprobe가 그 자리에 온다.

축소

축소는 세 단계의 관문을 통과해야 한다. 마운트 상태, fsck 여부, 최소 크기를 차례로 확인하는 셈이다.

resize2fs /dev/loop4 2G             # 마운트된 상태에서 축소 시도
umount /mnt/demo
resize2fs /dev/loop4 2G             # fsck 없이 축소 시도
e2fsck -f -y /dev/loop4
resize2fs /dev/loop4 2G             # 정상 축소
resize2fs -P /dev/loop4             # 더 줄일 수 있는 하한
resize2fs /dev/loop4 1G             # 하한 아래로 축소 시도
resize2fs: On-line shrinking not supported

resize2fs: Please run 'e2fsck -f /dev/loop4' first.

demo: 12/262144 files (0.0% non-contiguous), 214095/1048576 blocks
Resizing the filesystem on /dev/loop4 to 524288 (4k) blocks.
The filesystem on /dev/loop4 is now 524288 (4k) blocks long.

Estimated minimum size of the filesystem: 370276

resize2fs: New size smaller than minimum (370276)

실제로 쓰이는 블록은 214095개(836MiB)인데 -P가 내놓은 하한은 370276블록(1.41GiB)이다. 이 값은 여유를 크게 잡은 추정치라 “사용량만큼은 줄어들겠지”라는 계산으로 파티션 크기를 미리 정해두면 안 된다. resize2fs -M은 바로 이 추정치까지 줄인다.

언마운트 상태에서의 확장도 문제없이 동작한다. ext4에서 마운트를 요구하는 쪽은 축소가 아니라 축소 금지 조건뿐이다.

XFS — 확장 전용, 축소는 사실상 불가

XFS는 정반대다. xfs_growfs는 마운트된 파일시스템만 다루고, 인자도 블록 장치가 아니라 마운트 포인트를 받는다.

umount /mnt/xfs
truncate -s 4G xfs.img && losetup -c /dev/loop5
xfs_growfs /dev/loop5               # 언마운트 상태에서 시도
mount /dev/loop5 /mnt/xfs
xfs_growfs /mnt/xfs
df -h /mnt/xfs
xfs_growfs: /dev/loop5 is not a mounted XFS filesystem

data blocks changed from 524288 to 1048576

Filesystem      Size  Used Avail Use% Mounted on
/dev/loop5      4.0G  610M  3.4G  16% /mnt/xfs

축소는 커널 5.13 이후 실험적으로 들어와 있지만, 줄일 수 있는 것은 마지막 할당 그룹(AG) 안의 미사용 공간뿐이다. AG 하나를 통째로 넘기는 크기를 요구하면 그대로 거절당한다.

xfs_info /mnt/xfs | head -1         # agcount=8, agsize=131072 blks
xfs_growfs -D 262144 /mnt/xfs       # 1048576 -> 262144 (AG 6개를 날리는 요청)
xfs_growfs -D 1040000 /mnt/xfs      # 마지막 AG 안에서 8576블록만 축소
meta-data=/dev/loop5             isize=512    agcount=8, agsize=131072 blks

[EXPERIMENTAL] try to shrink unused space 262144, old size is 1048576
xfs_growfs: XFS_IOC_FSGROWFSDATA xfsctl failed: Invalid argument

[EXPERIMENTAL] try to shrink unused space 1040000, old size is 1048576
data blocks changed from 1048576 to 1040000

의미 있는 크기를 줄이려면 백업 후 mkfs.xfs로 다시 만드는 것 외에 방법이 없다. XFS로 포맷할 파티션은 처음부터 크기를 보수적으로 잡는 편이 낫다.

Btrfs — 마운트한 채로 양방향

Btrfs는 확장과 축소 모두 온라인으로 되는 대신, 반드시 마운트 포인트를 인자로 받는다. 블록 장치를 넘기면 거부한다.

btrfs filesystem resize -1G /mnt/btr     # 상대 크기 지정
btrfs filesystem resize max /mnt/btr     # 장치 끝까지 확장
btrfs filesystem resize 300M /mnt/btr    # 데이터보다 작게 축소 시도
btrfs filesystem resize -1G /dev/loop6   # 장치를 인자로 넘기면
Resize device id 1 (/dev/loop6) from 4.00GiB to 3.00GiB

Resize device id 1 (/dev/loop6) from 3.00GiB to max

ERROR: unable to resize '/mnt/btr': No space left on device

ERROR: not a directory: /dev/loop6
ERROR: resize works on mounted filesystems and accepts only
directories as argument.

장치가 여러 개인 Btrfs라면 resize 1:-1G처럼 devid:크기 형식으로 어느 장치를 줄일지 지정해야 한다. devid는 btrfs filesystem show로 확인한다.

F2FS — 오프라인 전용

F2FS는 resize.f2fs로만 크기를 바꾸며, 확장·축소 모두 언마운트가 필요하다. 축소는 기본적으로 거부되고 -s(safe resize) 플래그를 붙여야 진행된다.

resize.f2fs /dev/loop9              # 마운트된 상태에서 확장 시도
umount /mnt/f2fs
resize.f2fs /dev/loop9              # 장치 끝까지 확장
resize.f2fs -t 2097152 /dev/loop9   # 512B 섹터 단위 = 1GiB로 축소 시도
resize.f2fs -s -t 2097152 /dev/loop9
Info: Mounted device!
	Error: Not available on mounted device!

[update_superblock: 765] Info: Done to update superblock
Done: 0.536388 secs

[ASSERT] (f2fs_resize: 757)  --> Nothing to resize, now only supports resizing with safe resize flag

Done: 0.115062 secs

-t가 받는 값은 블록이 아니라 512바이트 섹터 수다. 다른 도구들이 2G 같은 표기를 받아주는 것과 달라서 자릿수를 틀리기 쉽다.

LVM — lvextend -r / lvreduce -r

LVM이 끼면 층이 하나 더 늘지만, lvextend/lvreduce-r(--resizefs) 옵션이 파일시스템 리사이즈까지 알아서 순서대로 호출해준다.

lvextend -r -L +2G /dev/demovg/demolv
  Size of logical volume demovg/demolv changed from 2.00 GiB (512 extents) to 4.00 GiB (1024 extents).
  Logical volume demovg/demolv successfully resized.
resize2fs 1.47.0 (5-Feb-2023)
Filesystem at /dev/mapper/demovg-demolv is mounted on /mnt/lv; on-line resizing required
The filesystem on /dev/mapper/demovg-demolv is now 1048576 (4k) blocks long.

-r를 빼먹고 축소하면 어떻게 되는지가 이 글에서 가장 중요한 대목이다. LVM은 경고만 하고 그대로 진행한다.

lvreduce -f -L 1G /dev/demovg/demolv     # -r 없이 LV만 축소 (2G ext4 위에서)
umount /mnt/lv
blockdev --getsz /dev/demovg/demolv
e2fsck -f -n /dev/demovg/demolv
mount /dev/demovg/demolv /mnt/lv
dmesg | tail -1
  WARNING: Reducing active and open logical volume to 1.00 GiB.
  THIS MAY DESTROY YOUR DATA (filesystem etc.)
  Size of logical volume demovg/demolv changed from 2.00 GiB (512 extents) to 1.00 GiB (256 extents).

2097152

e2fsck: Attempt to read block from filesystem resulted in short read while checking journal
/dev/demovg/demolv: ********** WARNING: Filesystem still has errors **********

mount: /mnt/lv: wrong fs type, bad option, bad superblock on /dev/mapper/demovg-demolv, ...

EXT4-fs (dm-0): bad geometry: block count 524288 exceeds size of device (262144 blocks)

커널이 남긴 bad geometry 한 줄이 상태를 정확히 말해준다. 슈퍼블록은 524288블록(2GiB)이라고 적혀 있는데 장치는 262144블록(1GiB)뿐이라 마운트 자체가 막힌다. 이 경우 LV를 원래 크기로 되돌린 뒤(lvextend) e2fsck -f로 복구하고, 그다음에 lvreduce -r로 다시 시도해야 한다.

파티션 층 — growpart

클라우드에서 디스크를 늘렸을 때 가장 자주 빠뜨리는 층이다. 디스크는 커졌지만 파티션 끝은 예전 자리에 그대로 있어 파일시스템이 늘어날 자리가 없다.

truncate -s 4G part.img && losetup -c /dev/loop8
lsblk -o NAME,SIZE /dev/loop8
growpart /dev/loop8 1               # 마운트된 채로 파티션 확장
lsblk -o NAME,SIZE /dev/loop8
resize2fs /dev/loop8p1
df -h /mnt/part
NAME      SIZE
loop8       4G
└─loop8p1   1G

CHANGED: partition=1 start=2048 old: size=2097152 end=2099199 new: size=8386527 end=8388574

NAME      SIZE
loop8       4G
└─loop8p1   4G

Filesystem      Size  Used Avail Use% Mounted on
/dev/loop8p1    4.0G  201M  3.6G   6% /mnt/part

growpart는 파티션 번호를 디스크와 공백으로 분리해 받는다(growpart /dev/sda 1). GPT라면 디스크 끝으로 옮겨야 하는 백업 헤더 갱신까지 함께 처리하므로 parted로 직접 손대는 것보다 안전하다.

파일시스템별 요약

파일시스템확장축소도구걸리는 조건
ext4온라인 · 오프라인오프라인만resize2fs축소 전 e2fsck -f 필수, -P 하한 이상
XFS온라인만(마운트 필수)사실상 불가xfs_growfs축소는 마지막 AG 여유분만, 실험적
Btrfs온라인만온라인만btrfs filesystem resize인자는 마운트 포인트, 다중 장치는 devid 지정
F2FS오프라인만오프라인만resize.f2fs축소는 -s 필요, 크기는 512B 섹터 단위

주의사항

  • 축소는 되돌릴 수 없다고 보고 반드시 먼저 백업한다. 확장은 실패해도 원상태가 유지되지만, 축소는 중간에 끊기면 파일시스템이 그대로 깨진다.
  • resize2fs -P/-M이 내놓는 하한은 실제 사용량보다 훨씬 클 수 있다(위 실측: 사용 836MiB, 하한 1.41GiB). 목표 크기를 이 값으로 미리 가정하지 말고 실제로 조회해서 정한다.
  • LUKS를 쓰면 층이 하나 더 붙는다. 확장은 cryptsetup resize를 파일시스템 확장 전에, 축소는 파일시스템 축소 후에 실행한다.
  • 루트 파일시스템은 언마운트할 수 없으므로 ext4 축소에는 라이브 USB나 initramfs 셸이 필요하다. 확장은 온라인으로 되니 루트를 XFS로 잡아두면 축소 요구가 생겼을 때 선택지가 없어진다는 점도 같이 고려한다.
  • 파티션 테이블을 바꾼 뒤에는 partprobe 또는 partx -u로 커널에 알린다. 파티션이 사용 중이면 갱신이 거부될 수 있고, 그때는 재부팅이 가장 확실하다.
  • 클라우드 블록 스토리지는 대부분 확장만 지원하고 축소는 지원하지 않는다. 볼륨을 새로 만들어 데이터를 옮기는 것이 유일한 축소 방법인 경우가 많다.

마무리

리사이즈 사고는 대부분 “어느 층까지 반영됐는지”를 확인하지 않아서 생긴다. lsblk로 블록 장치와 파티션 크기를, dfdumpe2fs -h/xfs_info로 파일시스템 크기를 각각 따로 확인하는 습관을 들이면 문제 지점이 바로 드러난다. 확장은 아래에서 위로, 축소는 위에서 아래로 — 이 순서만 지키면 남는 것은 파일시스템별 제약을 아는 일뿐이고, 그 제약은 위 요약 표에 정리한 대로다.

참고

답글 남기기