Ubuntu 16.04のZFSで/homeを冗長化

目次

Ubuntu 16.04 から ZFS がサポートされたので、/homeをミラーリングした ZFS 上に作成して冗長化してみました。

これまで使っていたディスクが壊れてしまい、新しく Ubuntu 16.04 をインストールすることになりました。そこでせっかくなので、このリリースからサポートされた ZFS を使ってムフフなファイルたちを無くさないように大切な/homeを冗長化することにしました(注1)。

ここでは、ZFS をインストールして使用する方法を簡単にまとめます。ZFS ならばバックアップの作成も簡単ですが、ここでは触れません。

システム構成

CPU Pentium G620
RAM 16GiB
Disk 1TB x 2
OS Ubuntu 16.04 Server
メモリ
最低 4GB。できれば 8GB で、多ければ多いほど良い。もっとも 4GB 未満でも動かないということもないようです。今回のマシンは、KVM ホストもしているので 16GB にしています。 ZFS のメモリというと ECC 付きメモリ or not という話は避けられません。ECC 付きのメモリでないとファイルが知らない間に壊れているかもという恐ろしい話があります。一方で、それは他のファイルシステムでも同じで、ZFS だから壊れやすいとはことでもないという話もあります。私は個人的な用途なので気にせず ECC 無しのメモリを使用しています。
ディスク
ミラーリングの場合は、ディスクが二台必要です。三台でミラーリングして、冗長度を上げることも可能です。また RAIDZ の場合は最低 3 台、RAIDZ2 の場合は最低 4 台のドライブが必要です(注2)。 ZFS を構成するドライブ(パーティション)のサイズは、Btrfs と異なり全て同一である必要があります。容量の小さなドライブが混じっていると、全体がその最低サイズに合わせられます(注3)。またディスクを入れ替える場合も、同じかそれ以上のサイズが必要です。
OS
Ubuntu 16.04 Server(64bit)。ZFS を使用できるのは、64bit 版のみです。もっとも現在の Ubuntu では Desktop と Serve 共に 64bit 版が標準なので、それほど気にする必要はありません。しかしアップグレードしてきた場合は 32bit 版を使っているかも知れないので、その場合は注意が必要です。

システムをインストール

grubブートローダにはちょっとした問題(注4)があるので、ZFS はデータ用のファイルシステムとして使用することをオススメします。/(root)の復旧は/etcとインストール済みのパッケージ一覧があれば/(root)の復旧は簡単ですので、システム全体を ZFS にして/(root)自体も冗長化する利点はそれほど大きくありません。

そこで今回は/(root)には冗長性を持たせないで、/homeだけに RAID1 の冗長性を持たせることにしました。その代わり復旧に大切な/etcとパッケージ一覧、crontabcronにより適期的に ZFS 上にコピーしておきます。もちろん ZFS の snapshot を作成して過去に遡れるようにして。

まずsdaに小さなパーティション(8GiB で十分)を作成して、そこに通常通りシステムをインストールします。耐久性はそれほど要求されないので、USB フラッシュメモリにシステムをインストールしても良いかも知れません。

ZFS をインストール

ZFS に必要なカーネルモジュール zfs.ko はカーネルに同梱されているので、必要なユーティリティをインストールするだけで完了です。このモジュールがコンパイル済みでカーネルに含まれているので、これまでの DKMS による zfs.ko のコンパイルを待つ面倒臭さがなくなりました。

$ sudo apt install zfsutils-linux

ユーティリティのインストールが完了したら、ZFS に必要なカーネルモジュールがロードされファイルシステムが使えるようになっているか一応確認しておきます。

$ lsmod |grep zfs
zfs                  2813952  3
zunicode              331776  1 zfs
zcommon                57344  1 zfs
znvpair                90112  2 zfs,zcommon
spl                   102400  3 zfs,zcommon,znvpair
zavl                   16384  1 zfs

$ cat /proc/filesystems |grep zfs
nodev	zfs

ミラーリング(RAID1)プールの作成

まずインストールしてあるディスクを確認します。

今後ディスクにアクセするときは、sdaなどの名前ではなく/dev/disk/by-idに表示される名前を使用します(注5)。この名前は、インターフェイス(ata)とディスクのモデル名とシリアル番号の一部から作成されます。そのため今後ディスクを交換する時に、交換が必要なディスクを識別しやすくなります。

$ ls /dev/disk/by-id | grep ^ata
ata-ST31000524AS_9VPCZPQV
ata-ST31000524AS_9VPCZPQV-part1
ata-TOSHIBA_DT01ACA100_36D1KYUNS

ディスクを確認したら、それぞれに同じ容量のパーティションを作成します。

$ sudo cfdisk /dev/disk/by-id/ata-ST31000524AS_9VPCZPQV
$ sudo cfdisk /dev/disk/by-id/ata-TOSHIBA_DT01ACA100_36D1KYUNS

$ ls /dev/disk/by-id | grep ^ata
ata-ST31000524AS_9VPCZPQV
ata-ST31000524AS_9VPCZPQV-part1
ata-TOSHIBA_DT01ACA100_36D1KYUNS
ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1

なぜかブートドライブに作成したパーティションが見えないのでudevadmin triggerコマンドを試してみます。ダメなときは再起動します。

$ ls /dev/disk/by-id/ | grep ^ata
ata-ST31000524AS_9VPCZPQV
ata-ST31000524AS_9VPCZPQV-part1
ata-ST31000524AS_9VPCZPQV-part2
ata-TOSHIBA_DT01ACA100_36D1KYUNS
ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1

パーティションを作成せずにディスク丸ごと ZFS にすることも可能で、この方がパフォーマンスが良いとのことです。しかし同じ 1TB として売られているディスクが、メーカーや製品が違ってもセクター数が同じという自信がありません。そこで私はいつも最大サイズより少し小さいパーティションを作成して使っています。

また最近のディスクはセクターサイズが 4KiB なので、パーティションの区切りを 4KiB のセクターサイズに合わせます。もっとも最近のfdiskなどは、そのことを考慮して配置してくれるので特に気にする必要はありません。

ディスクにパーティションを作成したら、プールを作成します。

$ sudo zpool create -o ashift=12 -o autoexpand=on \
  tank mirror \
  ata-ST31000524AS_9VPCZPQV-part2 \
  ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1

$ sudo zpool status
  pool: tank
 state: ONLINE
  scan: none requested
config:

	NAME                                        STATE     READ WRITE CKSUM
	tank                                        ONLINE       0     0     0
	  mirror-0                                  ONLINE       0     0     0
	    ata-ST31000524AS_9VPCZPQV-part2         ONLINE       0     0     0
	    ata-TOSHIBA_DT01ACA100_36D1KYUNS-part1  ONLINE       0     0     0

errors: No known data errors

-o ashift=12は、セクターサイズの指定です。 この指定によりセクターサイズを 2^12B(4KiB)だと指定しています。通常は指定する必要はありませんが、512B/sec のディスクを使用していると将来 4KiB/sec のディスクしか手に入らなくなった時に困ったことになります。

-o autoexpand=onは、将来ディスクを交換した時に可能ならプールのサイズを自動的に拡張する指定です。 このオプションがなくても特に困ることはありません。必要ならば、ディスクの交換後にプールのサイズをマニュアルで拡張することが可能です。ただし拡張できるのは、いずれの場合も冗長構成のディスク全てが現在のプールサイズよりも大きくなった場合に限られます。

tankは、プールの名前です。 /(root)直下のディレクトリ名(bin など)でなければ自由に選べますが、tankpoolが慣習として使われます。

また-O compression=lz4を一緒に指定してファイルシステム全体を圧縮することも可能です。後で見るように、ファイルシステムを作る時に個別に圧縮の有無と方式を指定することも可能です。

ファイルシステムを作成

プールを作成すると自動的に/にプール全体がマウントされますが、目的に応じてファイルシステムを切り出します。今回は、/home用に切り出します。

$ sudo zfs create -o atime=on -o relatime=on \
  -o compression=lz4 tank/home
$ sudo zfs list
NAME        USED  AVAIL  REFER  MOUNTPOINT
tank        372K   868G    96K  /tank
tank/home    96K   868G    96K  /tank/home

-o atime=on -o relatime=onは、ファイルのアクセスタイムの更新方式を指定します。 Linux はファイルにいつアクセスしたかという情報を記録しており、ファイルにアクセスするたびにファイルの情報を書き換えています。しかしこの情報が使われることはほとんどありません。そこで-o atime=offとしてアクセスタイムの更新を止めてしまうと、ファイルアクセスのパフォーマンスが上がります。しかし一部このアクセス情報を使用するプログラムもあるので、だいたい問題ない程度に更新の頻度を減らすようにします。

-o compression=lz4は、ファイルシステムを圧縮する方法を指定します。 ただ将来さらに優れた圧縮方法が導入されたら自動的にそれを使うように-o compression=onとしておいたほうが良かったかも知れません。zfs setコマンドで後から変更できます。

ファイルシステムを作成したら/homeの内容を移動させて置き換えます。

$ sudo rsync -av /home/ /tank/home
$ cd /
$ sudo rm -rf /home/*
$ sudo zfs set mountpoint=/home tank/home

ZFS はファイルシステムをマウントする時に/etc/fstabを見ないので、/etc/fstabを書き換える必要はありません。

swap on ZFS

スワップをわざわざ ZFS 上に取る必要はないのですが、お試し的にスワップ領域を ZFS 上に取ってみました。

スワップ領域を作成

スワップ領域を ZFS 上に作成します(注6)。

$ sudo zfs create -V 16G \
  -b $(getconf PAGESIZE) tank/swap
$ sudo zfs set com.sun:auto-snapshot=false tank/swap
$ sudo zfs set sync=always tank/swap

ブロックサイズをページサイズと同じくすることで空きメモリが少ない状況で大きなブロックの書き換えを防ぎます。

sync=alwaysとすることで、キャッシュをすぐに空にしてメモリの使用量を抑えます。

スワップを有効にする

スワップボリュームを作成したら、後は通常のスワップ領域を追加するときと同じです。

まず/etc/fstabを編集して、次のエントリーを追加します。

# swap
/dev/zvol/tank/swap none	swap	default	0	0

次に実際にスワップ領域をフォーマットしてシステムに追加します。

$ sudo mkswap -f /dev/zvol/tank/swap

$ sudo swapon --all --verbose
swapon /dev/zd32
swapon: /dev/zd32: found swap signature: version 1d, page-size 4, 同じ byte order
swapon: /dev/zd32: pagesize=4096, swapsize=17179869184, devsize=17179869184

fstabを設定してあるので、再起動しても自動的にスワップ領域が有効になります。

参照と脚注

  1. Linuxで使用できるディスクの冗長化には、歴史のあるmdや既に安定期に入りつつ有るBtrfsを使用できます。なのにZFSをなぜ選択したか? 単純にmdやBtrfsよりZFSのコマンドが私にとって分かりやすかったからです。
  2. RAIDZやRAIDZ2に必要なドライブの数は、次の式が成り立つ必要があります。ここで128KiBは、ZFSのデフォルトレコードサイズです。 128KiB % (ドライブ数 - パリティ数) == 0
    %は、剰余を求める演算記号 よって、RAIDZはパリティ1なので3,5,9台、RAIDZ2はパリティ2なので4,6,10台となります。
  3. Btrfsは、容量の異なるディスク(パーティション)を組み合わせても、だけ有効に使ってZFSよりも容量を稼げることがあります。またファイルシステムに空きが有る場合は、現在よりも容量の小さいドライブに置き換えることも可能です。
  4. 少し手間はかかりますが、/(root)自体をZFSにすることも可能です。
    ただしgrubブートローダに問題があり、/(root)の入ったプールを作成する時にいくつかのZFS機能を無効にする必要があります。最初の時だけ気にするなら良いのですが、zpool statusコマンドを実行するとzpool upgradeしてくださいというメッセージが親切にも表示されます。そこでzpool upgradeしてしまうと、システムが起動できなくなってしまいます。
    もちろん開発中のgrubではこの制限が取り除かれています。それでもZFSに新しい機能が導入されるたびにブートできなくなる不安がつきまといます。
  5. ZFSのプールを作成する時に/dev/sdaなどの名前を使用した場合は、zpool importコマンドでインポートし直せば/dev/disk/by-idでの名前に変更可能です。ZFSパッケージ登録記念:UBUNTU 15.10にZFS ON LINUX
  6. HOWTO use a zvol as a swap device
    スワップボリューム – archlinux
    Using a zvol for a swap device – FAQ zfsonlinux