zfsのスナップショットはzfs send, zfs recvで、pool間をまたいでコピーができる。 もちろん、同一ホストでもネットワーク越しでも同じ。

また、send, recvで送受するデータの実体はストリームなので、sendとrecvをパイプラインで繋ぐ。
パイプラインで繋ぐということは、ストリームを操作、制御するコマンドをsend, recvの間に挟んでも構わないということになる。

こういった特性を活かして、ネットワーク越しにzfsデータを送受するときには、間にバッファツールを挟む。

バッファツールはその名の通りデータをバッファするツールで、今回のケースで言えば、受け側ホストか経路ネットワークの問題か、要するにデータを受け取れないときにはデータをためておき、受け取れるときには限界までデータを流し込む。
こうすることでzfs send recvの転送速度向上を期待できる。

なお、「ネットワーク越し」という場合、その「ネットワーク」がプライベート網かインターネットなどの公衆網のどちらなのか、で要件が大きく変わる。
それについては後述の「データの流れとコマンド実行方法」を参照のこと。

バッファツールの定番mbuffer

バッファツールには定番mbufferを使う。

pkgでインストール(FreeBSDの方)

送受それぞれのホストでインストールしましょうね。

$ sudo pkg install mbuffer
Password:
Updating FreeBSD repository catalogue...
Fetching meta.txz: 100%    944 B   0.9kB/s    00:01
Fetching packagesite.txz: 100%    6 MiB   2.2MB/s    00:03
Processing entries: 100%
FreeBSD repository update completed. 31778 packages processed.
All repositories are up to date.
The following 2 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
        mbuffer: 20190127
        mhash: 0.9.9.9_5

Number of packages to be installed: 2

177 KiB to be downloaded.

Proceed with this action? [y/N]: y
[1/2] Fetching mbuffer-20190127.txz: 100%   47 KiB  48.1kB/s    00:01
[2/2] Fetching mhash-0.9.9.9_5.txz: 100%  130 KiB 132.8kB/s    00:01
Checking integrity... done (0 conflicting)
[1/2] Installing mhash-0.9.9.9_5...
[1/2] Extracting mhash-0.9.9.9_5: 100%
[2/2] Installing mbuffer-20190127...
[2/2] Extracting mbuffer-20190127: 100%
Message from mhash-0.9.9.9_5:

===>   NOTICE:

The mhash port currently does not have a maintainer. As a result, it is
more likely to have unresolved issues, not be up-to-date, or even be removed in
the future. To volunteer to maintain this port, please create an issue at:

https://bugs.freebsd.org/bugzilla

More information about port maintainership is available at:

https://www.freebsd.org/doc/en/articles/contributing/ports-contributing.html#maintain-port

FreeBSDの悲哀を感じるログです(メンテナがいない)。

mbufferのオプション

よく使うのだけ

Options説明
-i <filename>標準入力の代わりにファイルを指定。
-o <filename>標準出力の代わりにファイルを指定。
-I <port>標準入力の代わりにポートを指定。
-O <host:port>標準出力の代わりにポートを指定。
-s <size>データを扱う単位。バイト指定だがB,K,M,Gなどの単位指定可
-m <size>バッファサイズ。バイト指定だがB,K,M,Gなどの単位指定可
-4強制的にIPv4にする。IPv4の場合は必須。かつ前の方で指定すること
-6強制的にIPv6にする
-v <level>verboseレベル。0~6で指定

-sには、zfsのrecordsizeを指定するとよい。デフォルトでは128k。zfs get recordsizeで分かる。
バッファサイズはお好きに。メモリを使います。1Gくらいかな。

データの流れとコマンド実行方法

さて、バッファツールを使うとしてデータはどう流すべきか。 データの流れとコマンド実行方法について、要件の観点で示す。

データを流すのはどういう経路か

ひとまずは以下が一番素直な構成である。

zfs send --> mbuffer --(network)--> mbuffer --> zfs recv

受け側の方でmbufferとzfs recvを実行して待ち受け状態にしておき、そのあとに送り側でzfs sendとmbufferを実行する、というもの。

しかし上記の構成は、プライベート網だけに限るべき。

というのも、mbufferはデータストリームをそのままネットワークにリダイレクトしているだけなので、データは丸見えだからである。
また、この構成の場合、受け側は特定のポートで口を開けて待っているだけで、受け取るデータが適正なものであるかについては一切、関知しない。それどころか認証すらしない。
来るもの拒まずのガバガバであるからして、非セキュアな網では使ってはいけない。

経路が信用できないときは、sshを経由すること。認証もあるし、データも暗号化してくれるからね。

この場合のデータの流れは以下の通り。

zfs send --> mbuffer --> ssh --> (network) --> sshd --> mbuffer --> zfs recv

受け側のmbuffer, zfs recvは送り側からのsshによるリモート実行。 ssh リモートサーバ コマンド ってやるやつな。

定期的に実行するか

また別の観点で言えば、zfs send, recvを定期的に実行するかどうかである。

定期的にバックアップデータをリモートのサーバに送るということなら、経由する網がセキュアであってもsshを使ったほうがよい。

理由は、コマンドの実行が片方だけのホストで完結するからである。sshを使う場合、例えば送り側でzfs sendし、sshによるリモートコマンド実行で受け側のzfs recvもやってしまう。
つまりコマンド実行は送り側だけでよい。ということは、cronによる定期的な実行も楽にできる。

しかしsshを使わない場合だと、受け側でmbuffer, zfs recvを実行したあとに送り側でzfs send, mbufferを実行する必要がある。これを自動でやるのは非常に面倒だ。

したがって、定期的にバックアップをリモートへ送信するなら、やはりsshを使ったほうがよい。

じゃあsshでいいじゃないか

そうなんだけど、sshの場合はちょっと設定が面倒である。
この先を読んでほしい。

ともあれ、この記事ではプライベート網、一回限りのデータ送信として、sshを使わない方法を採る。

どのユーザで実行するか。

zfsの操作は基本、rootしかできないので、送受それぞれのサーバでrootで実行すること。

しかし、定期的にバックアップしたデータを流すのなら、さすがにrootにやらすのは危険だ。
くわえてパスワードなしで相手先ホストにログインできる設定をしておかないといけない。

そういう場合には以下のようにして特定ユーザにzfs操作を許可しておき、そのユーザを使う。
(ユーザhousekeeperに、vault/chamberへのcreate, recv, mount, sendを許可)

# zfs allow -u housekeeper create,receive,mount,send vault/chamber

もちろん鍵認証等、パスワードなしでコマンド実行できるようにしておく。

実行

繰り返すが受け側->送り側の順で実行すること。

受け側

受け側で以下を実行。

受け口の指定はポートだけでよい。

なお。-4オプションを与えて強制的にIPv4にする。そうしないとmbufferはIPv6で延々と待ち続ける。また、-4は最初に指定しないと読んでくれない(末尾に付けたら無視される)
怪しいなと思ったらsockstat -l4, sockstat -l6として確認すること。
(えらい時間を無駄にした)

# mbuffer -4 -s 128k -m 1G -I 8000 | zfs recv vaults/itunes

送り側

送り側で以下を実行。
こちらではIPアドレス、ポートを指定する。送り側はボロサーバなのでメモリを確保できず500MBにした。

# zfs send -R vault/itunes@monthly-2019-06-01_05.30.00--1y1d | mbuffer -O 192.168.1.7:8000 -s 128k -m 500m
mbuffer: warning: allocating more than half of available memory
in @ 28.0 MiB/s, out @ 28.0 MiB/s, 10.9 GiB total, buffer 100% full

送り側からデータが来ると受け側では以下の通り画面表示が変化する。

# mbuffer -4 -s 128k -m 1G -I 8000 | zfs recv vaults/itunes
in @ 28.4 MiB/s, out @ 28.4 MiB/s, 14.4 GiB total, buffer 100% fulll

【参考】ssh経由時のコマンド例

参考までに。ただし未検証。
直接sshに流し込むので、mbufferに-O, -Iなどの出力先、入力元を指定するオプションは不要。

# zfs send -R <スナップショット> | mbuffer -s 128k -m 1G | ssh <ユーザ>@<リモートホスト> 'mbuffer -s 128k -m 1G | zfs receive <展開先ディレクトリ>'

脱線

USB3.0のUSB-HDDケース(GW3.5AX2-SU3/REV2.0)に書き込んだところ、112 MiB/sなんてスピードが出て興奮したのだが、ものの数分もするとOSを巻き込んで固まるという事象が発生。
やむなくUSB2.0の口に繋いでみると今度は問題なく動作し安心するがスピードは28 MiB/s。これはダメかもわからんね。

結果

受け側

$ mbuffer -4 -s 128k -m 1G -I 8000 | zfs recv vaults/itunes
in @  0.0 kiB/s, out @  0.0 kiB/s, 1121 GiB total, buffer   0% fulll
summary: 1121 GiByte in 11h 09min 48.8sec - average of 28.6 MiB/s

送り側

$ zfs send -R vault/itunes@monthly-2019-06-01_05.30.00--1y1d | mbuffer -O 192.168.1.7:8000 -s 128k -m 500m
mbuffer: warning: allocating more than half of available memory
in @  0.0 kiB/s, out @ 28.6 MiB/s, 1121 GiB total, buffer   1% fulll
summary: 1121 GiByte in 11h 08min 49.5sec - average of 28.6 MiB/s