ZFSストレージバックエンドのNFSサーバを立てる

8 min read読了の目安(約7500字

※この記事はQiitaからの移行記事です

やりたいこと

  • RAID-Zで信頼性が高い冗長構成のストレージを組みたい
  • NFSでZFSストレージを共有したい
  • できるだけ楽したい

やったこと

よくやるファイアウォールとかの設定はやっている前提です.

環境

  • Ubuntu Server 18.04 LTS
  • OpenZFS: 0.7.5-1ubuntu16.8
    • 2020/6/25現在は0.7.5-1ubuntu16.9がaptでインストールできる最新のバージョンのようです
  • RAID-Z用HDD 4TB x3
    • よく「SMRが〜」とか言われていますがSMRです.2.5インチだとSMRしか無かったので.
  • ローカル: 172.16.3.0/24
  • サーバのローカルIP: 172.16.3.10

HDDは別にこだわらなくてもいいと思います.壊れても良いための冗長構成なので.
全部残さず使うのであれば,容量だけは揃えましょう.

(本当はRAID-Z x2でストライピングを組みたかったけど予算の関係で断念)

ZFSストレージのセットアップ

先にパーティションを切っておきます.全部使う場合はこの工程はやらなくてもOKです.
私はパーティションラベルをつけておきたかったのでやりました.
(ラベルをつけることによるメリットはArchWikiにわかりやすく書かれています.)

$ sudo parted /dev/sdb --script \
> 'mklabel gpt mkpart disk0 zfs 2048s 7814037134s quit'

# 残り2つも同様に設定

partedの使い方についてはこちらの解説がわかりやすいです.
後から気づいたのですが,パーティションの範囲は%でも指定できます.そのほうが楽です.

パーティションが切れたらZFSのストレージプールを作ります.
先にOpenZFSをインストールしておきましょう.

$ sudo apt install -y zfsutils-linux

今回はRAID-Zで組むので

$ sudo zpool create tank raidz \
> /dev/disk/by-partlabel/disk0 \
> /dev/disk/by-partlabel/disk1 \
> /dev/disk/by-partlabel/disk2

を実行したらストレージプールの出来上がりです.
生のディスクを使用する場合は

$ sudo zpool create tank raidz /dev/sdb /dev/sdc /dev/sdd

みたいな感じで実行してあげるとよしなにフォーマットしてストレージプールを作ってくれます.
他の場所で使っていたディスクをそのまま放り込んでも大丈夫です.
作れたら確認してみましょう.

$ sudo 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
	        disk0  ONLINE       0     0     0
	        disk1  ONLINE       0     0     0
	        disk2  ONLINE       0     0     0

errors: No known data errors

つぎに,ストレージプールを用途ごとにデータセットに切り分けていきます.

$ sudo zfs create -o normalization=formD -o compression=lz4 tank/nfs-dataset
$ sudo zfs create -o normalization=formD -o compression=gzip-9 tank/backup-dataset

-oでデータセットを作る際のプロパティを指定します.
ここで指定しているnormalization=formDはUnicodeの正規化,compressionは圧縮方式の指定です.
特に圧縮方式についてはどんな用途で使用するのかによって変えると効果的です.
私はNFS用途ではそこそこ書込速度が欲しいので標準的なlz4,バックアップ用途では圧縮効率の良さが欲しいのでgzip-9を指定しました.
各圧縮方式を詳しく比較してくださっている記事がこちらにあるので参考にすると良いと思います.

他にどのようなプロパティがあるかはsudo zfs get allで一覧を見ることができます.
また,OpenZFSのドキュメントには各プロパティの詳細が載っています.

作成されたデータセットは,デフォルトでは/tank/nfs-datasetのようにマウントされています.

NFSサーバを設定する

ファイアウォールをきちんと機能させたいのでNFSのバージョンはv4を使用します.
(v4だと使用するポートは2049/tcpに限定できます.)

まずNFSサーバ用のパッケージをインストールしておきます.

(server)$ sudo apt install -y nfs-kernel-server

インストールできたら,共有するディレクトリのルート(エクスポートルート)を設定していきます.
ZFSの場合,この設定には2通りの方法があります.

普通の方法

ZFS以外のストレージの場合と同じ方法です.

共有するディレクトリの設定を/etc/exportsに書いていきます.
なお,共有するディレクトリのパーミッションはあらかじめ適切な値に設定しておいてください.

/etc/exports
/tank/nfs-dataset 172.16.3.0/24(rw,sync,crossmnt,fsid=0,secure,no_subtree_check)

fsid=0を指定することで,NFSクライアントで設定するサーバのNFSエクスポートルートが/になります.
クライアント側ではsudo mount -t nfs 172.16.3.10:/ [マウントポイント]と打ち込んでやるだけでNFSストレージがマウントできるようになります.
他のオプションについては,用途によって適宜変えてください.(参考: man exports(5))

ユーザのアクセスを制限するために,NFSではエクスポートルートにあたるディレクトリを専用のディレクトリにバインドマウントして使いますが,ZFSで同じ手法をとると使用できるディスクのサイズがクライアント側から見えなくなってしまいます.
そのため,データセットのディレクトリを直接エクスポートルートに設定しています.

/etc/exportsを書いたら一度nfs-kernel-server.serviceに読み込ませる必要があります.

(server)$ sudo exportfs -a # /etc/exportsから共有するディレクトリを追加
(server)$ sudo systemctl restart nfs-kernel-server
(server)$ showmount -e localhost # NFS用にマウントされてるか確認
Export list for localhost:
/tank/nfs-dataset 172.16.3.0/24

exportfsコマンドは/etc/exportsのをチェックし,/var/lib/nfs/etabに載っていないものがあれば追加します.差分をチェックするわけではないので/etc/exportsからエクスポートルートを削除してもshowmountで確認すると残っています.
共有ディレクトリを削除したい場合はsudo exportfs -rを実行します.これは現在保持しているエクスポートルートのリストを削除するオプションです.すべて削除されるので,この後にsudo exportfs -aで追加する必要があります.
また,デフォルトのものも含めて,エクスポートルートに設定されているすべてのオプションを見たい場合は/var/lib/nfs/etabを参照すると良いです.

sharenfsプロパティを使う方法

ZFSのsharenfsプロパティを使うことで,NFSサーバで扱うZFSデータセットの設定が簡単にできます.
こちらを参考にすると良いです.

まず,/etc/exportsにダミーのエクスポートルートを設定をします.
これは,エクスポートルートが何も設定されていない状態ではnfs-kernel-server.serviceが起動しないためです.

/etc/exports
/mnt localhost(ro)

そのうえでnfs-kernel-server.serviceを起動します.

$ sudo systemctl start nfs-kernel-server.service

サービスが起動できたら,エクスポートしたいデータセットにsharenfsプロパティを設定します.

$ sudo zfs set sharenfs=on tank/nfs-dataset
$ sudo zfs get sharenfs tank/nfs-dataset
NAME              PROPERTY  VALUE     SOURCE
tank/nfs-dataset  sharenfs  on        local
$ showmount -e localhost
/tank/nfs-dataset *
/mnt              localhost.localdomain

さらにエクスポートのオプションを追加することもできます.

$ sudo zfs set \
> sharenfs="rw=@172.16.3.0/24,sync,crossmnt,fsid=0,secure,no_subtree_check,root_squash" \
> tank/nfs-dataset
$ sudo zfs get sharenfs tank/nfs-dataset
NAME              PROPERTY  VALUE                                                                       SOURCE
tank/nfs-dataset  sharenfs  rw=@172.16.3.0/24,sync,crossmnt,fsid=0,secure,no_subtree_check,root_squash  local
$ showmount -e localhost
/tank/nfs-dataset 172.16.3.0/24
/mnt              localhost.localdomain

指定できるオプションについてはドキュメントに書いてあるとおり,/etc/exportsで設定できるものについては大丈夫です.
ちなみに本家にはあるshareプロパティはOpenZFSには存在しません.sharenfssharesmbのみです.

sharenfsプロパティを設定すると自動的にNFSでエクスポートされるようになります.
エクスポートのon/offを明示的に切り替えたい場合はzfs share|unshareを使います.

Ubuntuの場合,以上の設定は次回からサーバ起動時に自動的に読み込まれます.
サービスやcronで設定する必要はありません.

NFSクライアントでマウント

サーバ側のセットアップができたのでクライアント側でマウントしてみます.

(client)$ sudo mount -t nfs -v 172.16.3.10:/ /mnt/nfs

ちなみに私のクライアント環境 (Ubuntu 18.04 LTS, nfs-common: 1:1.3.4) では,サーバ側のエクスポートルートをフルパス/tank/nfs-datasetにするとNFSのバージョンがv3になってしまい,ファイアウォールでブロックされてしまいました.
/mnt/nfsはクライアント側のマウントポイントです.適当なディレクトリを設定してください.
マウントがうまくいったら起動時に自動でマウントしてくれるよう,クライアントの/etc/fstabに書いておきましょう.

/etc/fstab
172.16.3.10:/ /mnt/nfs nfs noauto,x-systemd.automount,x-systemd.device-timeout=10,timeo=14,x-systemd.idle-timeout=1min 0 0

いい感じに設定してあげることでNFSストレージをアドホックにマウントできるようになります.ArchWikiを参考にしてください.

最後に

今回はKerberos認証とかは使用していないので,このままグローバルに公開するのは危険です.
え?LANだったら大丈夫だとでも思ってるんですか?
(私の場合はnextcloudなりで適当なプライベートクラウドストレージを立てようと考えているのでNFSでユーザ認証をするつもりはありません.)
インターネット経由でも使いたい場合は,別途ユーザ認証と通信の暗号化ができる環境を構築してください.


参考: