Menu

문서정보

목차

리눅스 native zfs 모듈

ZFS는 이른바 파일 시스템의 끝판왕이라고 부르는 녀석이다. 궁극의 파일 시스템이라고 선전하는데, 말뿐이 아닌 궁극의 파일 시스템이라고 할 만하다. 기능을 간단하게 살펴보고 넘어가자.

ZFS의 라이센스와 리눅스 커널 지원

ZFS는 오라클에서 개발한 파일 시스템으로 2006년 솔라리스 10에 처음 적용됐다. CDDL이라는 오픈소스 라이센스 정책을 따르고 있어서 리눅스에 적용하는게 별 문제가 없는 것 같지만 GPL과 충돌하는 요소가 있어서, 리눅스 커널에 직접 포함되진 못하고 있다. 대신 커널 모듈 형식으로 개발하고 있다. 2015년 현재 ZFS 리눅스 커널 모듈의 버전은 0.6.4다. 1.0이 아니지만 관련 커뮤니티에서는 사실상 안정화 버전으로 보고 있는 상태다. 코드자체가 오픈소스로 풀린 마당이니, 충분한 안정성을 보장하고 있다고 볼 수 있겠다. 실제 우분투 리눅스의 경우 ZFS Stable Release for Ubuntu 라면서 패키지를 배포하고 있다. 또한 ZFS 파일 시스템을 우분투의 표준 파일 시스템으로 사용 할 계획을 가지고 있음을 밝힌 기사도 있다. 리눅스 ZFS의 역사를 살펴보자. (2015년)현재 우분투와 젠투 리눅스는 ZFS를 잘 지원하고 있다. 루트 파일 시스템을 지원하고 있으며 곧 정식으로 지원하게 될 것으로 예상된다. 레퍼런스가 충분하지 않은 것 같지만, 사용하기엔 문제 없다 대충 이런 평가를 내리면 될 것 같다.

ZFS 관련 용어들

zpool

논리적 볼륨이다. ZFS는 물리적인 디스크를 논리적으로 재 구성한 vdev를 만든다. 이 vdev를 다시 묶은 것이 zpool이다. 물리적인 디스크들을 논리적으로 묶을 수 있기 때문에 소프트웨어만으로 RAID 시스템을 만들 수 있다. 리눅스에서 VLM으로 논리적인 디스크를 만들고 mdadm으로 소프트웨어 RAID를 구축하는 것과 매우 비슷하다.

vdev

zpool을 구성하는 요소다. Virtual Device(가상 장치)의 줄임말로 물리적인 디스크와 zpool 사이에 존재하는 추상화된 계층이다. 즉 ZFS는 물리적인 디스크 < vdev < zpool의 구성을 가지는데, LVM의 PV < VG < LV 의 관계와 비슷하다고 보면 되겠다. vdev 들은 몇 가지 구성 타입을 가지고 있다.

RAID 타입 vdev

Spare 타입 vdev

Cache 타입 vdev

Log 타입 vdev

Seperate intent LOG다. 동기쓰기를 비동기 쓰기처럼 동적하게 할 수 있다. 데이터쓰기에는 동기 쓰기비동기 쓰기가 있다.

데이터 쓰기를 요청하면, 운영체제는 데이터를 RAM의 캐시에 기록한다. 이 캐시에 기록된 내용이 실제 디스크까지 저장되는 것이 확인돼야 쓰기 요청이 완료된다. 완료까지 프로그램은 대기를 해야 하기 때문에 프로그램의 수행 속도가 느려지게 된다. 물론 "데이터가 안전하게 저장된다"는 동기화의 장점을 누릴 수 있다.

비동기는 RAM 캐시에 기록된걸 확인하면 쓰기 요청이 완료된다. 상대적으로 느린 디스크 쓰기 작업을 기다릴 필요가 없기 때문에, 소프트웨어가 빠르게 작동하는 장점이 있다. 반면 쓰기 명령 수행과 실제로 디스크에 데이터를 쓰는 시간이 일치하지 않기 때문에, 예상치 못한 상황에서 데이터가 유실될 수 있는 문제가 있다.

SLOG를 이용하면 비동기적인 방식으로 작동하면서, 데이터의 안정성까지 확보할 수 있다. 기본 개념은 단순하다.

 SLOG 개념
  1. 소프트웨어가 요청한 쓰게 데이터는 메모리와 SLOG에 모두 저장된다.
  2. SLOG에 쓰기가 끝나면, ZFS는 애플리케이션에 쓰기 완료 메시지를 보낸다.
  3. 디스크에 데이터가 저장된다.
동기쓰기이지만 마치 비동기처럼 작동한다. 당연한 이야기지만 SLOG vdev가 디스크보다 충분히 빨라야 지만 성능 효과를 볼 수 있다. 달리 말해서 이미 SSD를 데이터 저장 매체로 사용하고 있다면 SLOG를 운용해봐야 별 효과를 누릴 수 없다는 이야기. 이미 SSD를 데이터 저장 매체로 사용하고 있다면, zpool에 그냥 붙여도 상관 없다.

 zpool에 SLOG를 저장

Dataset

일반적인 파일시스템에 해당하는 부분이다. 애플리케이션이 실제 사용하는 공간이다. 논리적인 디스크위에 구축되기 때문에, 물리적인 특성과 상관없이 관리 할 수 있다. 데이터셋 안에 새로운 데이터셋을 만들 수 있고(예컨데, 파티션 안에 다른 파티션을 만드는게 가능하다.), 용량도 마음대로 조절 할 수 있다.

scrub

Silent corruption을 방지 한다. 100% 안전한 매체는 없다. 디스크는 그냥 가만히 둬도 여러가지 이유로 비트가 변경될 수 있다. 이렇게 변질된 데이터는 바로 검출 할 수 없기 때문에 위험할 수 있다. ZFS의 scrub 명령을 이용하면, 디스크의 메타데이터와 체크섬을 검사해서 변질된 데이터를 찾아서 정정한다. 모든 파일 시스템을 검사하기 때문에 많은 시간이 걸린다.

Data corruption에 대한 내용은 wikipedia 문서를 참고하자.

native zfs 모듈 설치

설치 환경

설치 환경은 다음과 같다.

native zfs 설치

Native ZFS for Linux team 사이트에서 패키지를 다운로드 할 수 있다. /etc/apt/sources.list에 PPA를 추가한다.
# cat /etc/apt/sources.list
.....
deb http://ppa.launchpad.net/zfs-native/stable/ubuntu vivid main
저장소 정보를 업데이트 하자.
# apt-get update
.....
Get:87 http://us.archive.ubuntu.com vivid-backports/restricted Translation-en [14 B]               
Get:88 http://us.archive.ubuntu.com vivid-backports/universe Translation-en [3,760 B]              
Fetched 31.6 MB in 3min 7s (169 kB/s)                                                              
Reading package lists... Done
W: Size of file /var/lib/apt/lists/partial/apt.dockerproject.org_repo_dists_ubuntu-vivid_Release.gpg is not what the server reported 819 442
W: GPG error: http://ppa.launchpad.net vivid InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY 1196BA81F6B0FC61
에러가 떨어진다. PPA를 인증하기 위한 퍼블릭키가 없단다. 설치하고 다시 업데이트 하자.
# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1196BA81F6B0FC61
커널 모듈은 리눅스 커널 버전에 맞게 컴파일 해야 하는데, 이를 위해서 리눅스 헤더 파일이 필요하다.
# apt-get install linux-headers-$(uname -r)
이제 zfs 패키지를 설치한다.
# apt-get install ubuntu-zfs
lsmod로 확인 해보자.
# lsmod | grep zfs
zfs                  2793472  0 
zunicode              331776  1 zfs
zcommon                57344  1 zfs
znvpair                90112  2 zfs,zcommon
spl                    94208  3 zfs,zcommon,znvpair
zavl                   16384  1 zfs

리눅스 ZFS 관리

zpool 생성

테스트를 위해서 리눅스 VM에 SATA disk를 4개 추가했다. fdisk로 확인한 디스크 정보는 다음과 같다.
# fdisk -l
......
Disk /dev/sdb: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sdc: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sdd: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sde: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
.....
sbd, sdc, sdd, sde 4개의 디스크가 보인다.

이제 zpool을 만들자. zpool을 만들 때, zpool의 raid 타입을 선택해야 한다.
  1. Striped Vdev : RAID0과 같다. 패리티 없이 stripe만 한다. 이 방식은 공간을 효율적으로 사용할 수 있으며, (패리티 연산이 없으니)빠르다는 장점이 있지만 디스크에 문제가 생겼을 경우 데이터를 복구할 방법이 없다는 단점이 있다. 데이터는 zpool을 구성하는 디스크에 분산 저장되기 때문에, 디스크 하나에 생긴 문제가 전체 데이터에 치명적인 영향을 미칠 수 있다.
  2. Striped Mirror : RAID10과 비슷하다. 미러링된 디스크에 데이터를 스트리핑 한다.
  3. RAIDZ : RAID5와 비슷하다. 비용과 안정성 사이에 적절한 균현을 맞춘 타입이라고 볼 수 있다. 가장 널리 사용하는 타입이기도 하다.
  4. RAIDZ2 : RAID6와 비슷하다. 더블 패리티를 사용해서 안정성을 높이고 있다. 퍼블릭 클라우드 정도에서 필요로 하는 안정성을 갖추길 원한다면 선택을 고려해봄 직하다. zpool은 최소 4개의 디스크로 구성이 되며, 동시에 3개의 디스크에 문제가 생기지 않는 한은 복구가 가능하다.
  5. RAIDZ3 : 트리플 패리티를 사용한다. 디스크 3개에 문제가 생겨도 복구 할 수 있다.
제일 무난한 RAIDZ로 만들었다. zpool의 이름은 volumes다.
# zpool create -f volumes raidz /dev/sdb /dev/sdc /dev/sdd /dev/sde
이제 4개의 디스크로 구성된 논리적인 볼륨이 만들어졌다. zpool의 상태를 확인해 보자.
# zpool status 
  pool: volumes
 state: ONLINE
  scan: none requested
config:

	NAME        STATE     READ WRITE CKSUM
	volumes     ONLINE       0     0     0
	  raidz1-0  ONLINE       0     0     0
	    sdb     ONLINE       0     0     0
	    sdc     ONLINE       0     0     0
	    sdd     ONLINE       0     0     0
	    sde     ONLINE       0     0     0

파일 시스템 생성

volumes에 파일 시스템을 만들어보자.
# zfs create volumes/yundream
# zfs create volumes/red3018

# zfs list
NAME               USED  AVAIL  REFER  MOUNTPOINT
volumes            135K  23.0G  25.4K  /volumes
volumes/red3018   25.4K  23.0G  25.4K  /volumes/red3018
volumes/yundream  25.4K  23.0G  25.4K  /volumes/yundream

압축

압축을 설정해보자. 테스트를 위해서 lz4gzip 방식을 선택했다.
# zfs set compression=lz4 volumes
# zfs get compression=gzip2 volumes/yundream 
NAME              PROPERTY     VALUE     SOURCE
volumes           compression  lz4       local
volumes/red3018   compression  lz4       inherited from volumes
volumes/yundream  compression  gzip      inherited from volumes

압축 테스트를 위해서 linux-3.18.22.tar를 복사해 보기로 했다. 파일의 크기는 약 600메가다.
# ls -al linux-3.18.22.tar
-rw-r--r-- 1 root root 580802560 Oct  3 21:02 linux-3.18.22.tar

volumes/yundream과 volumes/red3018로 복사했다.
# time cp linux-3.18.22.tar /volumes/red3018/

real	0m3.209s
user	0m0.000s
sys	0m0.232s
# time cp linux-3.18.22.tar /volumes/yundream

real	0m15.922s
user	0m0.000s
sys	0m0.272s
거의 5배의 시간 차이가 난다. lz4가 고속이긴 고속인가 보다. 압축률은 어떨까.
# zfs get compressratio 
NAME              PROPERTY       VALUE  SOURCE
volumes           compressratio  3.42x  -
volumes/red3018   compressratio  2.76x  -
volumes/yundream  compressratio  4.51x  -
확실히 gzip이 60% 정도 압축륙이 더 높긴 하지만, 걸리는 시간을 생각한다면 일반적인 상황에서는 lz4가 나은 선택이다. 읽기/쓰기 속도 보다 압축률이 더 중요한 서비스라면 gzip을 사용해 볼만 하다.

쿼터 설정

파일시스템 별로 사용용량을 제한 할 수 있다.
# zfs create volumes/red3018
# zfs list
NAME                 USED  AVAIL  REFER  MOUNTPOINT
volumes              414K  23.0G  25.4K  /volumes
volumes/red3018     25.4K  23.0G  25.4K  /volumes/red3018
volumes/yundream01  26.9K  10.0G  26.9K  /volumes/yundream01
red3018이라는 파일 시스템을 만들었다. 쿼터제한 없이 전체 디스크를 소비할 수 있게 돼있다. 쿼터를 5G로 제한했다.
# zfs set quota=5G volumes/red3018
# zfs list
NAME                 USED  AVAIL  REFER  MOUNTPOINT
volumes              419K  23.0G  25.4K  /volumes
volumes/red3018     25.4K  5.00G  25.4K  /volumes/red3018
volumes/yundream01  26.9K  10.0G  26.9K  /volumes/yundream01
이렇게 파일 시스템 가동중에 간단히 쿼터를 변경 할 수 있다. 리눅스의 LVM을 이용해서 삽질해 본 경험이 있다면, 이게 얼마나 편한지 느낌이 올 거다.

파일 시스템을 만들 때 쿼터를 설정할 수도 있다.
# zfs create -o quota=5G volumes/test02
# zfs list
NAME                 USED  AVAIL  REFER  MOUNTPOINT
volumes              447K  23.0G  25.4K  /volumes
volumes/red3018     25.4K  5.00G  25.4K  /volumes/red3018
volumes/test02      25.4K  5.00G  25.4K  /volumes/test02
volumes/yundream01  26.9K  10.0G  26.9K  /volumes/yundream01

유저와 그룹에 대한 쿼터 설정도 가능하다. yundream 유저에 대해서 /volumes/yundream01에 2G의 쿼터를 설정했다.
# zfs set userquota@yundream=2G volumes/yundream01 
# zfs get userquota@yundream volumes -r
NAME                PROPERTY            VALUE               SOURCE
volumes             userquota@yundream  none                local
volumes/red3018     userquota@yundream  none                local
volumes/test02      userquota@yundream  none                local
volumes/yundream01  userquota@yundream  2G                  local

developer 그룹에 5G의 쿼터를 설정했다.
# zfs set groupquota@developer=5G volumes/test02
# zfs get groupquota@developer volumes/test02
NAME            PROPERTY              VALUE                 SOURCE
volumes/test02  groupquota@developer  5G                    local

파일 시스템별로 그룹과 유저에 할당된 쿼터와 사용량을 확인 할 수 있다.
# zfs groupspace volumes/test02
TYPE         NAME       USED  QUOTA
POSIX Group  developer     0     5G
POSIX Group  root        512   none

# zfs userspace volumes/yundream01 
TYPE        NAME       USED  QUOTA
POSIX User  root      6.00K   none
POSIX User  yundream      0     2G

spare

spare 지원도 테스트 해봤다. 3개의 디스크를 raidz로 묶고, 디스크 하나를 spare로 묶었다.
# zpool create tank raidz /dev/sdb /dev/sdc /dev/sdd spare /dev/sde
# zpool status
  pool: tank
 state: ONLINE
 scan: none requested
config:

        NAME        STATE     READ WRITE CKSUM
        tank        ONLINE       0     0     0
          raidz1-0  ONLINE       0     0     0
            sdb     ONLINE       0     0     0
            sdc     ONLINE       0     0     0
            sdd     ONLINE       0     0     0
        spares
          sde       AVAIL   
이제 raidz1 pool의 디스크 중 하나가 FAILURE 되면 spares의 디스크가 FAILURE 디스크를 대신해서 작동할 거다. 역시 물리적 환경을 구성해야 제대로 테스트할 수 있을 것 같다.

Share

파일 시스템에 대한 공유 기능도 포함돼 있다. 관리자는 파일 시스템에 대한 SMB, iSCSI, NFS 공유를 관리 할 수 있다.

NFS

NFS를 지원하기 위해서 커널 모듈을 설치해야 한다.

volumes/test02를 NFS 공유 설정 했다. 192.168.56.1 호스트에서 읽기/쓰기로 volumes/test02 파일 시스템을 마운트 하도록 허용 했다. 192.168.56.0/24 와 같은 CID 형식으로 표기 할 수도 있다.
# zfs get sharenfs
zfs get sharenfs
NAME             PROPERTY  VALUE                      SOURCE
volumes          sharenfs  off                        local
volumes/red3018  sharenfs  off                        inherited from volumes
volumes/test02   sharenfs  rw=@192.168.56.1,insecure  local
하나 이상의 호스트에 대한 접근 설정도 가능하다.
# zfs set sharenfs=rw=@192.168.56.1:@192.168.56.2:ro=@172.12.0.0/8,insecure volumes/test02
192.168.56.1에서 nfs 파일 시스템을 마운트 해보자. mount가 nfs를 지원하도록 하기 위해서 nfs-common 패키지를 설치한 후 마운트 했다.
# apt-get install nfs-common
# moun 192.168.56.50:/volumes/test02 /mnt/myvolume

iSCSI

iSCSI는 블럭 스토리지를 공유하기 위한 도구다. 원격에 있는 하드디스크를 로컬 PC에 붙이는 거라고 보면 된다.

()현재 Linux ZFS는 iscsi를 지원하지 않는 것 으로 보인다. 메뉴얼 상에는 shareiscsi가 보이는데, 공유하려고 하면 잘못된 property라는 에러가 뜬다. 그래서 tgtadm을 이용해서 iscsi를 구성하기로 했다. iSCSI에 대한 자세한 내용은 iSCSI를 참고하자.

먼저 테스트를 위한 볼륨을 만들었다. iSCSI는 파일 시스템이 아닌 디스크를 공유하는 툴이므로 Disk 타입의 볼륨을 만들어야 한다.
# zfs create -V 5G volumes/docker
zfs list를 해보자.
# zfs list
NAME              USED  AVAIL  REFER  MOUNTPOINT
volumes          5.16G  17.9G  25.4K  /volumes
volumes/docker   5.16G  23.0G  12.0K  -
volumes/red3018  25.4K  5.00G  25.4K  /volumes/red3018
volumes/test02   25.4K  5.00G  25.4K  /volumes/test02
디스크 타입이기 때문에 마운트 포인트가 없다. 대신 zvol 디바이스가 만들어지는데 /dev/zvol 밑에서 찾을 수 있다.
# ls -al /dev/zvol/volumes/docker 
lrwxrwxrwx 1 root root 9 Oct 17 12:35 /dev/zvol/volumes/docker -> ../../zd0
# file /dev/zd0
/dev/zd0: block special (230/0)

iSCSI 타겟을 만들고 확인했다.
# tgtadm --lld iscsi --op new --mode target --tid 1 -T iqn.joinc.com.example:storage.disk1
# tgtadm --lld iscsi --op show --mode target
Target 1: iqn.joinc.com.example:storage.disk1
    System information:
        Driver: iscsi
        State: ready
    I_T nexus information:
    LUN information:
        LUN: 0
            Type: controller
            SCSI ID: IET     00010000
            SCSI SN: beaf10
            Size: 0 MB, Block size: 1
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store type: null
            Backing store path: None
            Backing store flags: 
    Account information:
    ACL information:
LUN에 zvol을 맵핑한다.
# tgtadm --lld iscsi --op new --mode logicalunit --tid 1 --lun 1 -b /dev/zvol/volumes/docker 
# tgtadm --lld iscsi --op show --mode target
Target 1: iqn.joinc.com.example:storage.disk1
    System information:
        Driver: iscsi
        State: ready
    I_T nexus information:
    LUN information:
        LUN: 0
            Type: controller
            SCSI ID: IET     00010000
            SCSI SN: beaf10
            Size: 0 MB, Block size: 1
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store type: null
            Backing store path: None
            Backing store flags: 
        LUN: 1
            Type: disk
            SCSI ID: IET     00010001
            SCSI SN: beaf11
            Size: 5369 MB, Block size: 512
            Online: Yes
            Removable media: No
            Prevent removal: No
            Readonly: No
            SWP: No
            Thin-provisioning: No
            Backing store type: rdwr
            Backing store path: /dev/zvol/volumes/docker
            Backing store flags: 
    Account information:
    ACL information:
LUN 1에 연결된 디스크 정보를 확인 할 수 있다.

이제 클라이언트에서 iSCSI를 마운트해서 사용하면 된다. 타겟을 찾아서 붙이면 된다.
# iscsiadm --mode discovery --type sendtargets --portal 192.168.56.50
192.168.56.50:3260,1 iqn.joinc.com.example:storage.disk1
# iscsiadm --mode node --targetname iqn.joinc.com.example:storage.disk1 --portal 192.168.56.50 --login
Logging in to [iface: default, target: iqn.joinc.com.example:storage.disk1, portal: 192.168.56.50,3260] (multiple)
Login to [iface: default, target: iqn.joinc.com.example:storage.disk1, portal: 192.168.56.50,3260] successful.
fdisk 로 확인해보자.
# fdisk -l 
.....
Disk /dev/sdd: 5 GiB, 5368709120 bytes, 10485760 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
이제 파티셔닝해서 사용하면 된다.

SnapShot 관리

COW

스냅샷은 파일 시스템과 볼륨에 대한 읽기 전용의 복사본이다. 일정한 공간을 확보해서, 스냅샷 정보를 저장한다. 스냅샷에는 실제 파일(혹은 블록) 정보가 저장되는 대신에 위치 정보만 링크형식으로 저장하기 때문에 적은 공간으로 백업 시스템을 구축 할 수 있다. 이게 가능한 이유는 zfs가 cow를 사용하고 있기 때문이다. 스냅샷을 이해하려면 먼저 cow(copy on write)의 원리를 알고 있어야 한다.

현재 파일 시스템에 4개의 데이터가 있다고 가정해 보자. 스냅샷을 뜨게 될경우 파일을 복사하는 대신에 파일의 위치와 시간 정보만을 스냅샷 공간에 저장하고 이들에 대한 링크만 관리한다. 작업이 계속 진행되서 2 개의 데이터가 추가됐다.

앗.. 그런데 실수로 데이터를 잘못 추가 했다. 이 경우 스냅샷에 있는 정보를 재구성하면, 스냅샷을 떳던 시간으로 데이터를 복구 할 수 있다. 이렇게 주기적으로 스냅샷을 만들어 두면, 원하는 시간대로 데이터를 복구 할 수 있다.

하지만 헛점이 보인다. 위의 시나리오는 데이터 수정이 없이 추가만 있을 때를 가정하고 있다. 현실적으로 데이터에 대한 수정은 반드시 발생을 한다. 이 경우 기존 스냅샷의 "수정된"데이터를 가리키게 되므로 스냅샷이 깨지게 된다. "이미 만들어진 스냅샷이 가리키는 정보는 변경이 되면 안된다." COW는 기존 데이터를 다른 위치로 복사해서 원본을 안전하게 유지하고, 새로운 데이터를 다른 블럭에 쓰는 방식으로 문제를 해결하고 있다.

  1. 유저가 데이터 작업을 하고 있다.
  2. 그런데, 스냅샷 2가 가리키는 위치의 데이터를 수정하게 됐다.
  3. 스냅샷이 가리키는 데이터가 수정되면 안되므로, 다른 블럭에 데이터를 저장(copy)하고 스냅샷 2가 가리키는 정보를 수정한다.
  4. 수정된 데이터는 새로운 블럭에 쓴다(write).
스냅샷 데이터를 수정하는 문제는 깔끔하게 해결 했다. 굉장히 좋은 솔류션 같지만 문제가 없는 건 아니다. 마법의 은탄환 같은 건 없다.
  1. 하지만 스냅샷 데이터에 대한 수정이 있을 때 마다, 추가적인 복사가 발생한다. 데이터베이스처럼 많은 수의 작은 파일을 수정하는 작업에는 좋지 않다.
  2. 스냅샷은 저장되는 데이터와 동일한 볼륨에 있어야 한다. 디스크 자체에 생기는 문제에는 대응 할 수 없다. 디스크 실패에 대한 레이드등의 구성은 어차피 해야 한다.

스냅샷 관리

아래와 같이 만들 수 있다.
# zfs snapshot volumes/test02@snapshotName

테스트를 해보자. 1-1.txt 1-2.txt 파일 두개를 만든다음 스냅샷을 떴다. 스냅샷 이름은 현재 시간으로 정했다.
# cd /volumes/test01
# touch 1-1.txt 1-2.txt 
# zfs snapshot volumes/test01@201510171423 

2-1.txt 2-2.txt 파일을 만든 다음 스냅샷을 떳다.
# touch 2-1.txt 2-2.txt
# zfs snapshot volumes/test02@201510171430

현재 두 개의 스냅샷이 있다.
# zfs list -t snapshot -r volumes/test02
NAME                          USED  AVAIL  REFER  MOUNTPOINT
volumes/test02@201510171423  13.5K      -  25.4K  -
volumes/test02@201510171430      0      -  25.4K  -

rollback을 이용해서 특정 스냅샷 시점으로 복원 할 수 있다. test02@201510171423 시점으로 복원해 보자.
# zfs rollback -r volumes/test02@201510171423
복원 됐는지 확인해 보자.
# ls
1-1.txt  1-2.txt
-r 옵션을 이용 할 경우 롤백한 시점 이후에 만들어지는 모든 스냅샷들은 삭제되기 때문에 사용에 주의해야 한다.

Storage Node 복구

나는 AWS 상에서 운영하려 한다. 어떤 이유로 zfs 인스턴스의 운영체제에 문제가 생기면, 다른 인스턴스를 재 빨리 띄워서 volume을 붙여야 한다. 대략 아래의 과정을 따를 것이다.
  1. AWS API를 이용해서 문제가 생긴 인스턴스로 부터 EBS 볼륨을 Detach 한다.
  2. 새로운 인스턴스를 띄운다. EBS 볼륨을 Attach 한다.
테스트는 Virtualbox로 진행했다. 4개의 볼륨을 원래 인스턴스에서 detach 한 다음, 동일한 버전의 리눅스와 zfs 모듈을 설치한 다음 볼륨을 attach해서 실행했다. zfs 모듈이 올라가는 즉시 zpool을 구성하는 걸 확인 할 수 있었다.

이상 테스트 결과로 볼 때 AWS 상에서 Atcive & Standby 방식의 고가용성 구성도 쉽게 가능 할 것 같다. 문제라면 인스턴스로 부터 EBS 볼륨을 detach / attach 하는데 시간이 꽤 걸린다는 점이다. 대략 10초 정도는 걸리는 느낌이다.

Volume Attach

운영 중에 디스크 용량이 찰 것 같으면, 디스크 공간을 늘려줘야 한다. RaidZ로 구성했다면, 새로운 Raidz pool을 만들어서 추가해줘야 한다. 역시 virtualbox로 테스트를 하기로 했다. 4개의 디스크를 추가 해서 테스트를 진행. 현재의 디스크 상태는 아래와 같다.
# fdisk -l

Disk /dev/sda: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x0232a3fd

Device     Boot  Start      End  Sectors  Size Id Type
/dev/sda1  *      2048   499711   497664  243M 83 Linux
/dev/sda2       501758 16775167 16273410  7.8G  5 Extended
/dev/sda5       501760 16775167 16273408  7.8G 8e Linux LVM

Disk /dev/sdb: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: A6914B49-3A77-A542-A7A7-913498D3ABA1

Device        Start      End  Sectors Size Type
/dev/sdb1      2048 16758783 16756736   8G Solaris /usr & Apple ZFS
/dev/sdb9  16758784 16775167    16384   8M Solaris reserved 1

Disk /dev/sdc: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 41AF10F3-BE03-C140-AB2E-DE8EA8DD9453

Device        Start      End  Sectors Size Type
/dev/sdc1      2048 16758783 16756736   8G Solaris /usr & Apple ZFS
/dev/sdc9  16758784 16775167    16384   8M Solaris reserved 1

Disk /dev/sdd: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 22A76EA8-D4CC-564E-931D-C11C5D306FF1

Device        Start      End  Sectors Size Type
/dev/sdd1      2048 16758783 16756736   8G Solaris /usr & Apple ZFS
/dev/sdd9  16758784 16775167    16384   8M Solaris reserved 1

Disk /dev/sde: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 3B6323BA-B087-E844-AB3F-C1221899539A

Device        Start      End  Sectors Size Type
/dev/sde1      2048 16758783 16756736   8G Solaris /usr & Apple ZFS
/dev/sde9  16758784 16775167    16384   8M Solaris reserved 1

Disk /dev/sdf: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sdg: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sdh: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/sdi: 8 GiB, 8589934592 bytes, 16777216 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/mapper/ubuntu--vg-root: 6.7 GiB, 7226785792 bytes, 14114816 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/mapper/ubuntu--vg-swap_1: 1020 MiB, 1069547520 bytes, 2088960 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/zd0: 3 GiB, 3221225472 bytes, 6291456 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 8192 bytes
I/O size (minimum/optimal): 8192 bytes / 8192 bytes
/dev/sdf ~ /dev/sdi 4개의 디스크가 보인다. zfs add 명령을 이용해서 볼륨을 추가했다.
# zpool add volumes raidz1 /dev/sdf /dev/sdg /dev/sdh /dev/sdi
# zpool status
  pool: volumes
 state: ONLINE
  scan: none requested
config:

	NAME                                       STATE     READ WRITE CKSUM
	volumes                                    ONLINE       0     0     0
	  raidz1-0                                 ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VBff36b243-479041f1  ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VB4ab3827f-4c5044ad  ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VBb6246c2c-0b7a4919  ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VB42a0c1e1-145956f4  ONLINE       0     0     0
	  raidz1-1                                 ONLINE       0     0     0
	    sdf                                    ONLINE       0     0     0
	    sdg                                    ONLINE       0     0     0
	    sdh                                    ONLINE       0     0     0
	    sdi                                    ONLINE       0     0     0
볼륨에 파일시스템이 추가되면, 자동으로 데이터들이 분배된다.

zpool migration

운영체제의 업데이트, 복구 등의 이유로 스토리지 풀을 마이그레이션 해야 하는 경우가 있다. 우선 마이그레이션 할 볼륨의 정보를 확인해 보자.
# zpool status
  pool: volumes
 state: ONLINE
  scan: resilvered 512 in 0h0m with 0 errors on Sun Nov  8 06:11:58 2015
config:

	NAME                                       STATE     READ WRITE CKSUM
	volumes                                    ONLINE       0     0     0
	  raidz1-0                                 ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VBff36b243-479041f1  ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VB4ab3827f-4c5044ad  ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VBb6246c2c-0b7a4919  ONLINE       0     0     0
	    ata-VBOX_HARDDISK_VB42a0c1e1-145956f4  ONLINE       0     0     0
4개의 디바이스로 이루어진 volumes를 확인 할 수 있다. 먼저 export 명령을 이용해서 운영체제로 부터, 모든 디바이스를 disconnect 한다.
# zfs export volumes

리눅스에서 export를 하면 디바이스를 umount 하는 작업을 한다. 따라서 export 전에 디스크의 모든 작업을 완료해야 한다. 그렇지 않으면 아래와 같은 (umount하면서 많이 봤음직한)에러 메시지를 볼 수 있다.
# zpool export volumes
umount: /volumes: target is busy
        (In some cases useful info about processes that
         use the device is found by lsof(8) or fuser(1).)
cannot unmount '/volumes': umount failed
export를 성공하고 나면 더 이상 디바이스 풀을 볼 수 없다.
# zpool export volumes
# zpool status
no pools available
이제 목적지 운영체제에 디바이스를 붙이고 import를 해보자.
# zpool import
   pool: volumes
     id: 18357592395927659775
  state: ONLINE
 action: The pool can be imported using its name or numeric identifier.
 config:

	volumes                                    ONLINE
	  raidz1-0                                 ONLINE
	    ata-VBOX_HARDDISK_VBff36b243-479041f1  ONLINE
	    ata-VBOX_HARDDISK_VB4ab3827f-4c5044ad  ONLINE
	    ata-VBOX_HARDDISK_VBb6246c2c-0b7a4919  ONLINE
	    ata-VBOX_HARDDISK_VB42a0c1e1-145956f4  ONLINE
import 명령을 내리면 물리적인 디스크를 검사해서 스토리지 풀의 정보를 읽어온다. id가 18357592395927659775 인 스토리지 풀을 사용 할 수 있음을 알 수 있다. 이제 id 혹은 풀 이름으로 import 할 수 있다.
# zpool import volumes
마이그레이션 목적지 운영체제에 동일한 이름의 볼륨이 있을 경우 충돌이 나서 실패한다. 이 경우 다른 이름으로 import 할 수 있다.
# zpool import volumes volumes02
# zpool list
NAME        SIZE  ALLOC   FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
volumes02  63.5G  45.4M  63.5G         -     0%     0%  1.00x  ONLINE  -

zpool 백업 및 복구

기본적으로 스냅샷은 로컬디스크 기반으로 작동한다. 만약 파일시스템이나 물리적 디스크 자체에 문제가 생길 경우에는 스냅샷만으로는 복구 할 수 없다. 스냅샷은 빠른 롤백이나 복구를 위해서 사용하는 툴이지, 그 자체로 완전한 백업/복구 솔류션은 아니다. 완전한 백업을 위해서는 아래의 요구사항이 지켜져야 한다. 이중 스냅샷을 이용한 원격복제에 대해서 살펴보려한다.

ZFS send를 이용한 snapshot 전송

zfs send 명령을 이용하면 스냅샷의 스트림을 로컬 시스템의 다른 pool이나 혹은 원격에 있는 다른 시스템으로 복사 할 수 있다. 아래는 로컬의 다른 풀로 복사하는 예제다.

먼저 스냅샷을 만든다. 최초의 스냅샷이라고 가정한다. 즉 지금시간 까지의 모든 내용을 가지고 있다.
# zfs snapshot myvol@today

스냅샷을 로컬의 myvol02로 복제한다.
# zfs send snapshot myvol@today | zfs recv myvol02

원격으로 복사하는 방법은 크게 두 가지다. 하나는 나중에 복구하기 위해서 스냅샷만 복사해두는 방법이고, 또 다른 하나는 아예 복제를 만드는 방법이다. 스냅샷 복사는 간단하다. send snapshot 내용을 로컬에 복사하고 scp등을 이용해서 원격에 복사하기만 하면 된다.
# zfs send snapshot myvol@today > today.snap
# scp today.snap ubuntu@remoteserver:/home/ubuntu

아래는 원격에 복제 파일시스템을 만드는 방법이다.
# zfs send snapshot myvol@today | ssh remoteserver zfs recv myvol

증분 스냅샷 저장

매번 스냅샷 전체를 뜨는 것은 공간 낭비다. ZFS는 증분 스냅샷을 지원한다. 이전 스냅샷에서 변경된 부분만 저장되므로 공간을 아낄 수 있다. 아래와 같이 3개의 스냅샷을 가진 파일 시스템이 있다고 가정해보자.

		

기타 이야기

loop 디바이스를 이용한 zfs 테스트 환경 구축

zfs기반의 애플리케이션을 개발한다면, virtualbox에 구축하는 것보다는 로컬에 구축하는게 더 나을 수 있다. 어쨋든 virtualbox로 관리하는 건 여러모로 괴롭다. losetup을 이용하면 간단하게 블럭디바이스를 만들 수 있다.

5G까지 image 파일 4개를 만들었다. 하나 만들고 나머지 세개는 그냥 복사했다.
# qemu-img create -f raw zfs01.img 5G
# cp zfs01.img zfs02.img
...

losetup을 이용해서 loop 디바이스를 만들었다.
# losetup /dev/loop1 zfs01.img
# losetup /dev/loop2 zfs02.img
# losetup /dev/loop3 zfs03.img
# losetup /dev/loop4 zfs04.img

fidsk로 확인해 보자.
# fdisk -l
Disk /dev/loop1: 5 GiB, 5368709120 bytes, 10485760 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/loop2: 5 GiB, 5368709120 bytes, 10485760 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/loop3: 5 GiB, 5368709120 bytes, 10485760 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/loop4: 5 GiB, 5368709120 bytes, 10485760 sectors
Units: sectors of 1 * 512 = 512 bytes
....

이제 zpool을 만들어서 테스트하면 된다.
# zpool create -f volumes raidz /dev/loop1 /dev/loop2 /dev/loop3 /dev/loop4
# zpool status
pool: volumes
          state: ONLINE
          scan: none requested
          config:

          NAME        STATE     READ WRITE CKSUM
          volumes     ONLINE       0     0     0
            raidz1-0  ONLINE       0     0     0
              loop1   ONLINE       0     0     0
              loop2   ONLINE       0     0     0
              loop3   ONLINE       0     0     0
              loop4   ONLINE       0     0     0

참고 문헌

결론