2019/10/28

btrfs 파일시스템 이해 및 활용


btrfs 파일시스템을 본격적으로 쓰기로 했기 때문에 까먹기 전에 사용법에 대해 다시 정리해 두는게 좋겠다. 예전 글에서 btrfs local snapshot으로 시스템 복원하는 방법에 대해 다루었는데, 몇 년 사이에 btrfs도 많이 개선됐기에, 복습도 되고 새로 btrfs 파일시스템을 접하는 이들에게도 도움이 될 것이다. 특히나 btrfs는 기존의 ext4 파일시스템과 많이 다르기 때문에 개념을 이해하지 않으면 제대로 활용하기 어렵다. 사실, btrfs wiki를 보면 대부분의 btrfs 사용법을 익힐 수 있지만, btrfs를 잘 모르는 사용자들은 적응하기가 쉽지 않다.

Copy-on-Write(CoW) 개념

c++ 고급 사용자라면 한 번쯤 접해 봤을 것이다. Qt Framework에서는 container 들을 포함한 여러 class 객체 들이 copy-on-write(또는 implicit sharing) 방식으로 동작한다. 공유 객체가 만들어질 때 공유 data를 생성하고 reference counter 를 1로 설정했다가 참조하는 객체가 늘어날 때마다 counter를 증가시키고 줄어들면 감소시킨다. 0이 되면 공유 data를 삭제하고 공유 객체는 소멸된다. 참조 객체들은 처음에는 공유 객체 주소 정보만 복사해서(shallow copy) 사용하다가 변경 사항이 생길 때 reference counter가 1보다 크면(여러 객체가 data를 공유하고 있으면) 공유 data를 모두 복사하고(deep copy) 나서 변경사항을 적용한다.

변경이 필요할 때(on-write) 공유 data를 복사하면(copy) 된다는 개념인데 btrfs도 이런 식으로 동작한다. 즉, 어떤 파일이 변경되지 않는 한 공유 data 참조 정보만 저장하면 된다. 파일이 변경될 때는 원본은 그대로 두고 빈 공간에 새 data를 만들어서 meta 정보로 새 data를 참조한다. 원본 공유 data를 참조하는 놈들이 없으면 원본을 지운다.

여기서 복사(copy)와 복제(clone) 개념을 구분할 필요가 생긴다. 복사는 원본 데이터와 똑같은 데이터를 갖는 새로운 놈을 만드는 것이다. 복제는 새로운 놈을 만들 때 처음엔 원본에 대한 참조 정보만 복사하고 있다가 변경이 필요할 때 원본 데이터를 복사하고 나서 변경시킨다. 이후로는 복제본도 복사본처럼 더이상 원본을 참조할 필요가 없다.

$ cp /etc/fstab ~/test.copy
$ cp --reflink=always ~/test.copy ~/test.clone

위의 첫째 cp는 copy를 수행한 것이고 두번째 cp는 clone을 수행한 것이다. test.clone은 test.copy를 참조하고 있으므로 파일 데이터 공간을 차지하지 않는다. 파일시스템 내부적으로 처리하기 때문에 사용자가 차이를 알기 어려운데, VM 이미지같이 큰 파일을 사용해 보면 복제가 당연히 빠르다고 느낄 수 있다. 하지만, 복제된 VM으로 최초 부팅시엔 VM 파일에 변경이 필요하고 원본 데이터를 복사해야하므로 VM이 느려질 수 밖에 없다. 한번 복사가 이루어진 후에는 VM 복사본이나 복제본이나 별 속도 차이가 없다. VM 파일이나 DBMS도 내부적으로 CoW 방식을 사용하는 놈들도 있다.

Copy-on-Wrtie의 기본 발상은,  대부분의 경우엔 원본을 참조하는 복제본만 있으면 되고, 변경이 필요할 때만 복사하면 된다는 것이다. 즉, 변경이 일어나지 않는 한 매우 빠르고 효율적이지만 변경시에는 느려지고  비효율적이다.

btrfs의 snapshot도 subvolume의 복제본이다. 특정 subvolume에 대한 snapshot을 만들면, 최초엔 snapshot이 subvolume을 참조만 하기 때문에 데이터 공간을 차지하지 않는다. 두 놈이 같은 참조 정보를 저장하고 있다가 subvolume 내의 파일이 변경되면 subvolume은 변경된 데이터를 빈 공간에 새로 저장하고, 그 파일에 대한 meta 정보를 갱신한다. snapshot은 계속 원본을 참조한다. snapshot에는 불필요한 정보가 남아 있는 것이므로 원본 subvolume이 많이 변경될수록 snapshot이 차지하는 공간이 점점 늘어나게 된다. subvolume 내의 어떤 파일을 삭제하면 snapshot으로 인해 원본 파일 데이터 전체를 보존해야 하므로 불필요한 공간이 크게 늘어난다. 뭐 snapshot의 존재 이유가 저장 공간을 희생해서 시스템 복구를 쉽게하자는 것이다.

만약, 이 snapshot을 다른 컴퓨터나 USB 외장 디스크에 backup 한다면 외부 파일시스템에는 참조 정보가 없기 때문에 최초 복사 시에는 모든 원본을 복사해야 한다. 최초의 backup subvolume이 만들어지면 이후로는 이를 참조하여 변경된 정보만으로 incremental backup을 할 수 있게 된다.

Subvolume vs. folder(directory)

btrfs wiki에 따르면, subvolume은 자신의 독립적인 파일/디렉터리 계층(hierarchy)을 갖는 파일시스템의 일부이고, 파일 extents를 공유할 수 있다. snapshot은 최초에 원본 subvolume의 내용을 갖고 있는 subvolume이다.  btrfs 파일시스템은 block-level의 LVM 논리 volume과는 달리 file-extent 기반이다......

SysadminGuide에서, btrfs subvolume은 독립적으로 mount 할 수 있는 POSIX filetree이지만 block device는 아니다.  대부분의 다른 POSIX 파일시스템은 마운트할 수 있는 root가 단 1개인데 비해, btrfs는 top level subvolume을 포함한 각 subvolume이 독립적으로 마운트 할 수 있는 root를 갖는다. btrfs subvolume은 POSIX file namespace라 생각할 수 있다. LVM 논리 volume과 ZFS subvolume과도 다르다......

폴더가 파일들을 담을 수 있는 컨테이너 정도로 이해하는 사람들에게 subvolume이 뭔지 설명하기란 힘들다. 사용자 관점에서 subvolume과 폴더는 사용법도 비슷하고, @를 subvolume 명 앞에 사용하지 않으면  ls 명령으로 아예 구분할 수도 없다. 폴더와 subvolume 모두 하위에 자식 폴더나 자식 subvolume을 만들 수 있고 다른 폴더에 마운트도 할 수 있다.

흠, subvolume은 CoW를 사용하는 특별한 폴더라고 이해하는 게 편할 듯하다. 하지만, CoW 개념도 사실 일반 사용자들이 이해하기 어렵다. 자신을 복제할 수 있는 특별한 폴더 ???

여기서, 특별한 폴더라고 하는 이유는 subvolume을 복사해야 하는 상황에서는 subvolume이 폴더로 바뀌기 때문이다. 가령, subvolume을 cp -R 로 복사하면 복사본은 폴더로 바뀐다. 또한, mv를 사용할 때도 특정 subvolume 내의 자식 subvolume들은 부모 subvolume 내에서는 이동이 되지만, 부모 subvolume 밖의 폴더나 subvolume으로 mv하면 폴더 복사본을 만들고 나서(파일 참조 불가) 자신을 삭제한다. 또한, 부모 subvolume의 snapshot 생성 시에도 자식 subvolume은 snapshot에 빈 폴더로 남게 된다(이 경우도 파일 참조 불가 문제인듯. 다만, snapshot에는 복사본을 사용하는게 의미 없기 때문에 아예 빈 폴더로 두는 듯하다).

subvolume을 폴더 대신 사용 하는 이유는,
  • 특정 subvolume의 backup 및 복구: snapshot을 만들거나, snapshot을 send/receive로 incremental backup 후 문제 발생시 복구
  • 반대로, 특정 subvolume을 backup에서 제외:
    • 가령, ~/.cache 폴더를 subvolume으로 바꾸면 @home의 snapshot 생성시 제외됨
  • 독립적인 애플리케이션 관리: 예를 들어 /var/www 또는 /var/lib/postgresql 등
  • 특별한 속성의 파일들을 분리하거나 mount 시 특별한 속성 부여:
    • chattr +c (compress: 파일 압축) 또는 chattr +C(nodatacow: 파일 생성시 CoW 비적용) 
    • 가령, VM(가상 머신) 파일 저장소나 DB 파일 들은 CoW를 사용하지 않도록 설정
    • 단, mount로 특별한 속성을 부여하려면 반드시 subvolume이 별개의 btrfs device(또는 파티션)에 있어야 함(향후 btrfs 버전에서 개선 예정???)
    • 즉, 동일 btrfs 파일시스템 내의 subvolume은 mount 옵션을 개별적으로 설정해도 첫번째 mount한 subvolume의 옵션만 적용됨(/etc/fstab 사용시에도 동일함)
btrfs 기본 사용법

우분투에서 예전의 btrfs-tools 패키지는 btrfs-progs 패키지로 바뀌었다. btrfs 파일 시스템을 사용하면 자동으로 설치되지만, ext4 등 다른 파일시스템에서 btrfs 파일시스템의 파일들을 사용하려면 따로 설치해야 한다. 이 놈이 있어야 btrfs partition 생성, format, mount 등을 비롯한 btrfs 파일시스템에 대한 모든 것을 처리할 수 있다.

$ sudo apt install btrfs-progs

일부 btrfs 명령을 일반 사용자가 사용할 수는 있지만 매우 제한적이고 대부분은 root 권한으로 사용해야 한다. 또, btrfs 명령들은 중복되지 않는 한 줄여서 사용할 수 있다.

하나 이상의 전체 btrfs 파일시스템(또는 디바이스)을 보고 싶다면 아래 명령을 사용한다(fi = filesystem). 이를 제외한 나머지 명령들은 btrfs 파일시스템 1개에 대한 명령임에 유의해야 한다.

$ sudo btrfs fi show

특정 btrfs 파일시스템의 subvolume 목록을 조회하려면 다음 명령과 같이 맨 끝에 마운트 위치를 알려 주어야 한다.

$ sudo btrfs subvolume list /

만약, 여러 개의 btrfs 파일시스템을 사용하고 있다면(예를 들어, /dev/sda3가 별개의 btrfs 파티션이고 /opt에 마운트돼 있다면),

$ sudo btrfs sub l /opt

와 같이 해야만 /dev/sda3의 subvolume 목록을 알 수 있다. 즉, 한번에 여러 btrfs 파일시스템 전체의 subvolume 목록을 조회할 수는 없고 각각의 btrfs 파일시스템에 대해 1개씩 조회해야 한다.

디스크 공간 관리

$ sudo btrfs fi usage /

이 명령으로 확인한 Free 공간은 df -h 명령으로 확인한 available 공간과 일치한다(Unallocated 용량이 합산됨). 하지만 CoW 파일시스템 특성상 Free (estimated) 공간이 실제 사용할 수 있는 공간에 더 가까울 수 있다. Unallocated 용량은 btrfs 파일시스템으로 파티션을 format 하더라도 전체 파티션을 모두 할당해서 사용하지는 않고 있다가 저장 공간이 부족하면 자동으로 할당한다. 또한, btrfs 파티션은 online으로(시스템 운영 중에) resize할 수 있는데 Unallocated 용량이 10GiB라면 바람직하진 않지만 아래와 같이 파티션 크기를 최대한 줄일 수 있다. gparted 앱을 사용해도 online resize가 된다.

$ sudo btrfs fi resize -10G /
$ sudo btrfs fi usage /

다시 최대 크기로 파티션을 늘리려면,

$ sudo btrfs fi resize max /

이외에도 아래의 명령들을 사용할 수 있다.

$ sudo btrfs fi df -H /

$ sudo mount /dev/sda2 /mnt
$ sudo btrfs fi du -s --iec /mnt/@home

defragment -r 명령은 하위 폴더를 포함해서(recursively) defragment를 수행하는데 subvolume과 mount point는 건너 띄도록 바뀐듯 하다. snapshot이나 reflink 파일에 적용하면 복제본 파일들이 복사본으로 바뀌어서 디스크 용량이 늘어나는 문제가 생긴다. 성능 감소가 대단하지 않기 때문에 defragment를 굳이 사용할 필요없다는 이들도 있다.

$ sudo btrfs fi defagment -r /no-cow/VM

subvolume 관리

subvolume의 생성(create), 복제(snapshot), 조회(list/show), 삭제(delete)를 위해 아래와 같이 btrfs subvolume 명령을 사용한다. 참고로, subvolume을 cp -R 명령으로 복사하면 앞서 설명했듯이 폴더로 바뀐다. 즉, btrfs 파일시스템이 아닌 다른 파일시스템을 어떤 폴더에 mount 해서 subvolume을 복사하려면 일반 폴더처럼 cp -R 명령을 사용하면 된다.

$ cd ~/
$ btrfs subvolume create @test
$ cp /etc/fstab @test/
$ ls @test/
$ btrfs sub snap @test @test-snapshot
$ sudo btrfs sub l /
$ sudo btrfs sub show @test
$ sudo btrfs sub sh @test-snapshot
$ sudo btrfs sub delete -c @test
$ mv @test-snapshot @test
$ sudo btrfs sub l /
$ sudo btrfs sub del -c @test

위에서 del -c 옵션은 subvolume을 지우고 즉시 commit을 수행하라는 것이다.

다른 btrfs 파일시스템으로 subvolume을 복제하려면 나중에 설명하겠지만 btrfs send/receive를 사용해야 한다. 가령, /dev/sda2가 root btrfs 파티션이고, /dev/sda3가 별개의 btrfs 파티션으로 /opt에 마운트 되어 있다면 아래와 같이 snapshot을 생성할 수는 없다.

$ cd /opt
$ sudo btrfs sub snap /mnt/@home ./@home-snapshot

USB 외장 디스크의 btrfs 파티션을 마운트해서 사용하는 경우를 생각해 볼 수 있다.

btrfs Incremental 백업 및 복구

하나의 btrfs 파일시스템 내에서는 snapshot 만으로도 local backup이 되고, snapshot은 원본 subvolume이 많이 변경되지 않는 한 디스크 용량도 많이 차지하지는 않는다. 백업해야 할 사용자 데이터가 많다면 외장 디스크나 다른 컴퓨터의 디스크에 백업해야 하는데 btrfs incremental 백업은 btrfs 파일시스템 간에만 가능하다. 즉, 백업 받는 쪽의 디스크도 btrfs 파일시스템이어야 한다.

여기서는 btrfs root 시스템이 /dev/sda2에 설치되어 있을 때 외장 디스크의 btrfs 파일시스템이 /dev/sdb3 파티션이라고 가정하고, @home subvolume의 snapshot을 백업했다가 복구하는 예를 들었다. 외장 디스크가 아니고 원격 컴퓨터의 btrfs 파일시스템을 사용하려면 ssh를 사용하면 된다.

1. 원본 subvolume 마운트

$ sudo mount /dev/sda2 /mnt
$ ls -CF /mnt
@/  @home/

2. 외장 디스크 btrfs 파티션 마운트

$ sudo mkdir /back
$ sudo mount /dev/sdb3 /back
$ sudo mkdir /back/backups

3. 최초 snapshot 생성 및 백업

$ sudo btrfs sub snap -r @home @home20190101
$ sudo sync

여기서 주의할 점은, btrfs send/recive 명령을 사용하기 위해 snapshot 생성시 read-only(-r) 옵션을 사용해야 한다. 그리고, 이 -r 옵션이 recursively라고 착각하는 이들도 더러 있는데, ZFS와는 달리 btrfs snapshot은 원본 subvolume에 자식 subvolume이 있으면 빈 폴더로 바꿔서 snapshot이 생성된다. 앞서 설명했듯이 btrfs에서 자식 subvolume을 만드는 이유는 ~/.cache 폴더와 같이 불필요한 특정 폴더를 subvolume으로 대체함으로써 snapshot이나 백업에서 제외하기 위해서이다.

아래와 같이 writable snapshot을 생성했을 경우 btrfs property 명령으로 read-only snapshot으로 만들 수도 있다.

$ sudo btrfs sub snap @home @home20190101
$ sudo btrfs property set -ts /mnt/@home20190101 ro true
$ sudo sync

위에서 true대신 false 옵션을 사용하면 read-only snapshot을 다시 writable snapshot으로 설정한다.

$ sudo btrfs send /mnt/@home20190101 | sudo btrfs receive /back/backups

위의 명령과 같이 최초 백업시에는 외장 디스크에 참조할 데이터가 없기 때문에 btrfs send시 아무 옵션이 없다. 외장디스크의 /backups 폴더에 @home20190101 subvolume이 생성된다.

4. Increametal 백업

이제 다음날 @home 파일들이 변경돼서 백업을 해야 한다면, 아래와 같이 새로운 snapshot을 만들어서 incremental backup을 할 수 있게 된다.

$ sudo btrfs sub snap -r @home @home20190102
$ sudo sync
$ ls -CF /mnt
@/  @home/  @home20190101/  @home20190102/

$ sudo btrfs send -p /mnt/@home20190101 /mnt/@home20190102 | sudo btrfs receive /back/backups

위의 -p <parent subvolume> 옵션은 incremental 백업시 참조할 부모 subvolume이다. -p 옵션을 사용하면  receive(백업) 쪽에서 부모 subvolume의 snapshot을 만들고 나서 변경된 부분을 백업 @home20190102 subvolume에 반영한다. 대부분의 경우에 -p 옵션만으로 충분하다.

참고로 -c <clone source subvolume>을 여러 개 사용할 수도 있는데 원본 subvolume을 여러개 참조해서 백업 subvolume을 생성하라는 의미이다. -c 옵션은 snapshot을 생성하지 않고 빈 백업 subvolume을 먼저 만들고 나서 백업 쪽에 clone source subvolume에 참조할 데이터가 있을 경우 그 들을 참조해서 백업 subvolume이 만들어지도록 한다.

$ ls -CF /back/backups
@home20190101/  @home20190102/

5. 외장 디스크의 백업 snapshot에서 root btrfs 파일시스템의 @home 복구

최신 데이터 백업이 됐기 때문에 아래와 같이 불필요한 snapshot들을 제거해도 된다.

$ sudo btrfs sub del -c /mnt/@home20190101
$ sudo btrfs sub del -c /mnt/@home20190102
$ sudo btrfs sub del -c /back/backups/@home20190101

$ ls -CF /mnt
@/  @home/

$ ls -CF /back/backups
@home20190102/

이제는 복구할 것이므로 백업 쪽의 snapshot을 send 한다. @home을 대체할 것이므로 writable snapshot으로 만들어야 한다.

$ sudo btrfs send /back/backups/@home20190102 | sudo btrfs receive /mnt
$ sudo btrfs property set -ts /mnt/@home20190102 ro false

$ ls -CF /mnt
@/  @home/  @home20190102/

이제는 local snapshot을 복구하는 것과 똑같이 하면 된다.

$ sudo mv @home @home-problem
$ sudo mv @home20190102 @home
$ sudo reboot

안전한 Checksum 검사 및 복구

btrfs scrub은 디스크의 모든 data를 읽어서 checksum을 다시 계산해서 기존 값과 비교함으로써 meta data를 포함한 data(file 단위는 아님)가 깨졌는지 알려주고 가능한 경우 복구한다. Background 프로세스로 돌기 때문에 상태를 확인하려면 아래와 같이 하면 된다. RAID를 구성한 경우에는 최소한 한달에 한번 주기적으로 scrub을 돌리는 게 좋단다.

$ sudo btrfs scrub start /
$ sudo btrfs scrub status

아예 root btrfs 파일시스템을 마운트 할 수 없을 경우에는 우분투 설치 iso로 부팅해서,

$ sudo btrfs scrub /dev/sda2

scrub으로 복구가 안될 경우엔 아래 명령으로 B-tree를 복구해 본다. 

$ sudo mount -o usebackuproot /dev/sda2 /mnt

위의 과정은 모두 안전한 복구 방법인데, 디스크 문제로 파일이 깨졌을 때 사용할 수 있는 방법은 아니다. 이것으로 복구가 안되면 안전하지 않은 btrfs check/restore/rescue 등의 명령을 사용해야 한단다.

2019/10/25

btrfs 파일시스템에 우분투 19.10 clean 설치


우분투 19.10이 지난 주말에 출시 됐는데 native root ZFS 설치가 가능한 점이 가장 눈에 띈다. 우분투 설치시에 NVIDIA 드라이버를 설치할 수 있는 점도 좋은 점이다. 우분투 19.10의 새로운 점들은 Release Note를 참고하는 게 좋고, omg! Ubuntu!의 포스트에도 정리가 잘 되어 있다.

btrfs도 잘 활용하지 못하는 판에 캐노니컬은 btrfs 대신 ZFS를 밀어주고 있다. 궁금해서 VirtualBox에서 ZFS로 설치해 보기로 했다. 처음부터 삽질을 하게 만들어 불길한 예감이 들었는데 우분투 19.10을 본격적으로 설치하면서 꽤나 삽질을 해야했다.

VirtualBox(v6.0.14)에서 새로운 VM을 만들어 우분투 설치 iso로 부팅하고 나서 설치를 진행하면 VM이 먹통이 돼버린다. 우분투 19.04 host에서 top을 띄워 보면 VirtualBox cpu 점유율이 100%가 넘는다. 우분투 19.10을 설치하려면 최소 2GB 메모리가 필요하다는데 VM에 2GB를 할당해 놓았었다. 포기하려다 VM에서 gparted로 파티션을 수동분할 해서 설치를 진행하니까 설치가 잘 된다. 우연인가 싶어 먹통 만들기 테스트를 몇가지 했는데 우분투 설치 iso로 부팅이 되자 마자 gparted를 띄우고 View > Disk Info만 확인해도 host의 top에서 VirtualBox cpu 점유율이 떨어진다. VirtualBox나 우분투의 버그이겠지만 아무튼 쓸모 있는 해결책이다. 혹시나 해서 VM의 메모리를 4GB로 늘렸더니 cpu 점유율이 정상으로 돌아온다. 메모리가 넉넉하면 이 방법이 더 좋겠다.

흠, 아무튼 ZFS 설치는 잘 된다. 단, VM 생성시 시스템 설정에서 EFI를 사용하도록 해야 ZFS 설치가 가능하다. 이유는 우분투 ZFS가 gpt 멀티 파티션을 사용하기 때문인데 bios 환경에서는 설정이 복잡해서 아예 설치를 지원하지 않는다. 아직은 Experimental 딱지가 붙어 있으니 실제 시스템에 적용하려면 삽질 정신이 필수일 게다. 더구나, 전체 디스크를 지우고 파티션을 자동 할당하기 때문에 실제 시스템에는 사전 준비없이 ZFS로 설치하지 않는게 좋다. 캐노니컬이 ZFS에 꽤 심혈을 기울이는 모양이다. ZFS 관리를 위한 zsys를 열심히 만들고 있단다. ZFS가 나온지는 꽤 됐는데 서버 파일시스템으로써는 최고라는 수식어가 붙고 있다. 리눅스에서는 License 충돌 문제때문에 ZoL(ZFS on Linux) 프로젝트가 별개로 진행되었고 커널 모듈로만 사용할 수 있는 단점이 있다. ZFS를 배우려는 이들에게 우분투 19.10 VM이 큰 도움이 될 것이다. 기능적으로는 ZFS가 btrfs보다 우월해 보이는데 Desktop 사용자들에게는 btrfs도 꽤나 쓸만하다. SSD가 범용화되면서 애플의 apfs와 더불어 서버건 데스크탑이건 모바일이건 간에 바야흐로 copy-on-write 파일시스템이 대세가 되고 있다. 아무튼 우분투의 ZFS 지원이 사용자들에게 배움의 단맛과 삽질의 쓴맛을 동시에 안겨 줄 것이다.

하지만, 이 글 제목에서와 같이 나는 당분간 btrfs에 좀 더 적응하기로 했다. 몇년 내 리눅스에서 ZFS가 대세가 되면 갈아 타겠지... 지금 당장 ZFS로 우분투 root 파일시스템으로 갈아 타려면 아래의 제약 사항들을 극복해야 한다.
  • grub이 아직 ZFS를 완벽하게 지원하지 못한다.
  • ZFS 커널 모듈로 부팅해야 하므로 root ZFS 설치 절차가 복잡하고 문제 발생시 시스템 복구 등 관리 상의 복잡함이 배가된다.
  • ZFS를 제대로 사용하려면 dataset(btrfs의 subvolume) 설정을 미세하게 할 필요가 있는데 수작업으로 하기엔 좀 복잡하다.
  • ZFS는 btrfs 보다 기능이 많기 때문에 배워야 할 것도 많다.
copy-on-write 파일시스템의 장점은 time-machine 류의 snapshot과 변분(incremental/differential) backup이 가능하고 대형 파일 복제가 매우 빠르며, 파일시스템 문제 발생시 쉽게 복구할 수 있다는 점이다. 파일 압축과 암호화를 지원하는 점도 장점이다. 실제로 btrfs 파일시스템을 사용하고 있는 우분투 19.04에서 우분투 19.10으로 upgrade를 진행했는데 중간에 gdm이 새로 뜨더니 로그인이 안돼서 설치를 제대로 마무리 할 수 없게 됐었다. 다행히 설치 전에 snapshot을 떠 놓아서 다시 19.04로 쉽게 돌아 갈 수 있었다. 할 수 없이 우분투 19.10 clean install을 진행하기로 했고 지금은 snapshot 덕분에 grub에서 19.04로도 부팅할 수 있고 19.10으로도 부팅할 수 있다. 서버라면 RAID 구성도 가능하겠지만 데스크탑에서는 USB 외장하드에 백업이 되는 것 만으로도 매우 유용하다. 필요할 때만 백업해 주면 되는데 시간을 꽤나 절약할 수 있기 때문이다. 뭐, 단점이라면 ext4 파일시스템에 비해 약간의 성능 저하가 있을 수 있는데 피부로 느낄 만큼 크지는 않다. subvolume에 lzo 압축을 적용해서 사용하는데 느리다고 느껴 본 적은 없다. 대신 저장 공간은 크게 절약된다(단, 동영상이나 음악 파일과 같이 자체 압축된 파일들이 많지 않다면...).

btrfs 파일시스템 파티션 조정

디스크 파티션을 조정해야 하므로 중요한 데이터를 별도의 디스크에 백업해 둬야 한다. UEFI 시스템에 btrfs 설치시 필요한 파티션은 EFI System(200MiB)과 btrfs(나머지 용량) 2개이다. 그 동안 우분투 전용 파티션만 4~5개 사용했었는데 이제 1개로 모두 합치게 되었다. EFI System 파티션은 모든 OS가 공용으로 사용하기 때문에 기존에 사용하고 있었다면 새로 추가할 필요는 없다. 리눅스 커널 5.0부터(우분투 19.04부터) btrfs 파일시스템 내의 Swap 파일을 Swap 파티션 대신 사용할 수 있다. 참고로 우분투 19.10의 ZFS는 EFI System, Swap, grub, boot pool, root pool 등 5개의 파티션을 사용하더라. 아마 boot와 root 파티션을 분리한 이유는 encryption이 가능하게 하려는 것인듯 하다. btrfs와 zfs는 비슷한 점이 많기 때문에 boot와 root 파티션을 나누는 게 좋을 수도 있지만 암호화를 사용하지 않으면 굳이 나눌 필요는 없어 보인다. ZFS의 Swap 파티션도 약간의 문제는 있지만 zvol dataset을 사용하면 굳이 필요하지는 않다. 그리 보면 ZFS엔 grub 파티션이 하나 더 있는 셈인데 아직 grub이 ZFS를 완벽하게 지원하지 못해서 분리한 듯하다(굳이 분리할 필요는 없어 보인다).

한마디로 btrfs든 zfs이든 ext4처럼 사용자 파티션 들을 더이상 구분할 필요가 없다. 가능한 파티션을 쪼개지 않는게 더 좋다. 이는 나중에 btrfs의 subvolume 또는 zfs의 dataset을 사용함으로써 구분할 수 있기 때문이다. 궁극적으로 데스크탑 사용자에겐 EFI 시스템 파티션을 제외하면 1개의 파티션으로 충분하다. subvolume 간에는 Quota를 따로 설정하지 않는 한 저장 공간을 공유하기 때문에 파티션을 여러 개 사용하면서 디스크 용량을 얼마씩 할당할지 더이상 고민할 필요가 없다.

참고로, 디스크 파티션을 분리해서 각각 btrfs 파일시스템으로 format 하면 각각의 파티션 수만큼 독립적인 btrfs 파일시스템을 만들게 되고, 각 파티션이 물리적인 Device 역할을 하게 된다. 대개는 여러 개의 디스크를 가지고 RAID를 구성하지만 분리된 btrfs 파티션들을 사용해서 RAID를 구성할 수도 있다. 주의할 점은 단일 btrfs 파일시스템 내에서 subvolume 들을 mount 할 때 다양한 옵션을 사용할 수 없다는 점이다. root subvolume의 mount 옵션을 따를 수 밖에 없다. 즉, root subvolume이 압축을 사용하고 있다면, 이후의 모든 subvolume은 mount 시 nocompress 옵션을 주더라도 압축을 사용하게 된다. 다만, 나중에 다루겠지만 우회적인 해결책은 있다. ZFS에서는 subvolume(dataset)이 파일시스템 단위가 되기 때문에 미세하고도 유연하게 subvolume 설정을 다르게 할 수 있다.

우분투 설치 iso로 부팅하여 설치시 참고 사항

우분투 installer(Ubiquity)를 이용하여 btrfs 파티션에 우분투 설치를 할 때 subvolume 압축을 사용할 수 있다. 압축을 사용하는 이유는 저장 공간을 줄이는 의미도 있지만 btrfs 파일시스템의 성능을 높이는 효과도 있다. 그냥 설치 후 나중에 /etc/fstab에서 subvolume 압축 옵션을 주고 mount 할 수도 있는데, btrfs에서는 기존의 파일들은 압축하지 않고 새로 생성된 파일들만 압축하기 때문에, 설치 전에 빈 subvolume을 압축 옵션을 주고 mount 해서 설치를 진행하는 것이다. 물론, 설치를 마치고 나서 defrag 명령에 압축 옵션을 사용할 수도 있기는 하지만, 사용 중인 파일들은 압축할 수 없기 때문에 바람직 하지는 않다.

Install Ubuntu 아이콘을 double click 하여 설치 진행시, [Installation type] 설정에서 [something else]를 선택하여 파티션 용도에 맞게 EFI 시스템 파티션을 [change]하고 btrfs 파티션은 /에 마운트 지정한다. 설정을 마친 후, [Where are you?] 설정(Time zone) 화면에서 멈추고, <Ctrl>+<Alt>+<t> 키 조합으로 터미널을 띄운다.

$ df -h
$ sudo btrfs subvolume list /target

명령으로 확인해 보면 btrfs 파티션(/dev/sda2로 가정)의 두 개의 subvolume 중에 @는 /target, @home은 /target/home, EFI 파티션은 /target/boot/efi에 마운트 되어 있음을 알 수 있다. 이 단계에서는 /target/swapfile과 /target/etc/fstab만 생성되어 있다.

먼저, btrfs용 swapfile은 root subvolume(@)에 snapshot을 사용할 수 있도록 하기위해 @swap subvolume에 다시 만들어야 한다. 그리고, @ subvolume을 포함한 모든 subvolume은 /etc/fstab에서 압축 옵션을 주고 부팅시 자동 mount 될 것이므로, swapfile이 압축되지 않도록 해야 하고 /target/etc/fstab 파일도 이에 맞게 아래와 같이 수정해야 한다.

$ sudo nano /target/etc/fstab

UUID=12345678-1234567890ab / btrfs defaults,noatime,compress=lzo,subvol=@ 0 1
UUID=1234-1234  /boot/efi vfat  umask=0077 0 1
UUID=12345678-1234567890ab /home btrfs defaults,noatime,compress=lzo,subvol=@home 0 1
UUID=12345678-1234567890ab /swap btrfs defaults,noatime,nodatacow,subvol=@swap 0 0
/swap/swapfile none  swap  sw 0 0

기존의 fstab 파일 내용과 다른 점은 @, @home 부분에 noatime,compress=lzo 부분이 추가됐고, @swap subvolume을 /swap에 mount하도록 한 줄이 추가됐다. 또, /swapfile 대신 /swap/swapfile을 사용한다. /swap mount 옵션에 compress 대신 nodatacow를 주긴했지만 /etc/fstab의 첫줄에 의해 mount 옵션이 동일하게 적용되기 때문에 옵션이 적용되지는 않는다. 다만, 실제로 압축을 사용하지 않을 것이므로 구분해 둘 필요는 있다. 이 단계에서 사용자 subvolume을 fstab에 더 추가할 수도 있겠지만 설치 후에 작업하는게 더 편해 보인다.

@swap/swapfile은 아래와 같이 만든다.

$ sudo -i
$ swapoff -a
$ rm /target/swapfile
$ mount /dev/sda2 /mnt
$ cd /mnt && ls
$ btrfs subvolume create @swap
$ chattr +C @swap
$ lsattr /mnt
$ cd @swap
$ touch swapfile
$ fallocate --length 1000MiB swapfile
$ chmod 600 swapfile
$ mkswap swapfile

$ swapon swapfile
$ cat /proc/swaps
$ swapoff -a
$ cd /
$ umount /mnt

@swap을 mount 할 swap 폴더를 아래와 같이 생성한다.

$ mkdir /target/swap
$ chattr +C /target/swap

참고로, btrfs 파일시스템에서 chattr +C 옵션은 파일이나 폴더/subvolume에 no-cow(no copy-on-write) 속성을 설정하는데,  subvolume 마운트시 nodatacow 옵션을 준 것과 같은 효과가 있고 압축을 사용하지 않는다. no-cow 속성이 설정되어 있더라도 subvolume의 snapshot 생성에는 아무 문제가 없다. no-cow 폴더 내의 파일이나 하위 폴더 들은 size가 0이거나 새로 생성될 때만 no-cow 속성이 계승된다. cp로 cow 파일을 no-cow 폴더에 복사하면 파일을 새로 만들기 때문에 no-cow 속성이 부여된다. no-cow 파일을 cow 폴더에 복사할 때도 cow 속성으로 바뀐다. mv로 기존의 파일을 옮길 때는 파일 속성을 그대로 유지한다.

이제 fstab과 같은 옵션으로 아래와 같이 remount 한다. lzo 압축 방식은 압축율은 좀 안좋지만 성능은 빠르다(ZFS의 lz4와는 다른 방식임). noatime 옵션은 파일시스템에서 access time을 사용하지 않도록 하는 것인데 성능을 높이기 위한 것이다.

$ sudo mount -o remount,defaults,noatime,compress=lzo /target
$ sudo mount -o remount,defaults,noatime,compress=lzo /target/home
$ grep btrfs /proc/mounts

이미 생성된 파일들은 몇개 안되지만 자동 압축되지 않았으므로 아래와 같이 수동으로 압축한다(/target/home은 비어 있음).

$ sudo btrfs fi defrag -r -clzo /target

다시 Ubiquity로 돌아와 [Where are you?] 설정을 마치고 우분투 설치를 진행하여 완료 후 디스크의 btrfs 파티션으로 재 부팅하면 우분투 clean 설치가 마무리된다.

참고로, ZFS는 이런 식으로 설치 할 수 없다. 파티셔닝부터 모든 게 자동화 되어 있으니까... 그리고, 우분투 19.10부터 설치시 3rd Party 비디오 드라이버와 WIFI 드라이버를 선택하면 자동으로 설치해 준다(설치 iso에 드라이버 탑재). Intel H/W PC에서 NVIDIA의 경우 부팅시 화면이 지글거리던 문제도 좀 나아졌다.

btrfs 사용자 subvolume 생성

btrfs 명령의 기본 사용법에 대해서는 이전 글을 참조하는 게 좋다. 이제 우분투 19.10으로 부팅해서 터미널에서 사용자 subvolume을 생성한다. @opt를 예로 든다.

$ sudo -i
$ mount /dev/sda2 /mnt
$ cd /mnt

$ btrfs sub create @opt
$ mkdir /opt
$ mount /dev/sda2 -o subvol=@opt /opt

위의 명령들을 사용해서 @opt subvolume 생성 후 /opt 폴더에 마운트해서 subvolume을 사용할 수 있게 된다. /etc/fstab에 등록해 주면 부팅시 자동으로 마운트된다.

VM 파일, DBMS storage 등에는 copy-on-write를 사용하면 성능 문제가 발생할 수 있으므로, Swap 파티션과 마찬가지로 no-cow 속성을 사용하는게 좋다. 아래와 같이 @nocow subvolume을 만들어 사용하면 된다. 어차피 압축을 사용하지 않기 때문에 동영상이나 음악 파일과 같이 자체 압축을 사용하는 파일들도 @nocow subvolume에 담아 두는게 좋아 보인다.

$ btrfs sub create @nocow
$ chattr +C @nocow
$ lsattr ./
$ mkdir /nocow
$ chattr +C /nocow
$ mount /dev/sda2 -o nodatacow,defaults,subvol=@nocow /nocow
$ grep btrfs /proc/mounts

앞서 설명했듯이 마운트 옵션을 다르게 주어도 root subvolume의 옵션을 따르기 때문에 의미는 없다. chattr +C가 이미 그 일을 해 주고 있다. 결국은 간단하게 모든 subvolume에 대해 아래와 같은 방식으로 마운트하면 된다.

$ mount /dev/sda2 -o subvol=@nocow /nocow

우분투 19.10 설치 후 발생한 문제들

우선, grub-efi 패키지가 제대로 설치되어 있지 않았다.

$ sudo apt install grub-efi --reinstall

그리고, grub에서 HDD의 iso 파일로 부팅이 안된다. 우분투 19.04에서는 잘 됐었기 때문에 EFI System 파티션의 /EFI/ubuntu 폴더를 19.04 버전으로 되돌려서 사용하고 있다. 이것이 가능했던 것은 우분투 19.04의 snapshot 덕분이다. 우분투 19.04 부팅 메뉴를 grub에 추가하되 linux와 initrd 부분에 @root-19.04 subvolume을 참조하도록 해야 한다.

linux   /@root-19.04/boot/vmlinuz-5.0.0-32-generic root=UUID=...... ro rootflags=subvol=@root-19.04
initrd  /@root-19.04/boot/initrd.img-5.0.0-32-generic

그리고, 부팅시 @root-19.04와 @home-19.04 subvolume이 mount 되어야 하므로 @root-19.04/etc/fstab 파일에서 @는 @root-19.04로, @home은 @home-19.04로 각각 수정해 주어야 한다. writable snapshot은 보통의 subvolume과 같이 파일을 수정할 수 있다. 이 경우에도 원본 subvolume은 안전하다.

이런 식으로 특정일의 @와 @home snapshot을 grub 메뉴에 등록해서 과거로 부팅할 수 있게 된다. 참고로, ZSF에서는 과거 특정 시점의 snapshot으로 rollback하면 그 과거 특정 시점 이후 생성한 snapshot 들은 삭제된다. 이에 반해 btrfs에서는 rollback이라는 것이 특정 시점의 snapshot을 사용하는 것이므로 중간 시점의 snapshot 들을 삭제할 필요가 없다. 즉, 과거에서 현재까지의 모든 snapshot 들의 상태로 들락날락 할수 있는 장점이 있다.

그 다음 문제는 VirtualBox 사이트에서 제공하는 6.0.14 버전이 설치가 안된다. 다행히 우분투 19.10 패키지에 6.0.14 버전이 탑재돼 있었다.

$ sudo apt install virtualbox virtualbox-ext-pack

설치시 modprobe 오류가 발생해서 모듈들이 로딩되지 않는 문제가 생기는데 수동으로 다시 올리면 되고, 재부팅해도 잘 된다. 또한, 이렇게 설치한 virtualbox는 guest additions iso 파일을 제대로 다운로드하지 못해서 별개로 다운로드 받아서 설치해야 했다.

btrfs 파일시스템에서 우분투 Clean Install시 사용자 설정 복원

Clean Install의 장점은 최신 버전의 앱들을 다시 설치함과 동시에 앱들이 구동되는 환경을 최신으로 바꿔 줄 수 있다는 점이다. 그러면 사용자가 과거에 설정했던 부분들을 어떻게 조화시킬 수 있을까? 이것이 가장 큰 문제이다.

현재 사용하고 있는 방법은 원하는 앱들을 모두 설치하고 나서 아래와 같이 하는 것이다.

$ sudo mount /dev/sda2 /mnt
$ cd /mnt
$ sudo btrfs sub snap @home-19.04 @home-new
$ cd /mnt/@home-new/aaa
$ rm -rf ./.*
$ cp -R /mnt/@home/aaa/.* .
$ cp /mnt/@home-19.04/aaa/.bashrc .
$ cp -R /mnt/@home-19.04/aaa/.config/VirtualBox .

$ sudo mv @home @home-19.10
$ sudo mv @home-new @home
$ sudo reboot