ZFSのバックアップをオブジェクトストレージに保存する

# ZFSのバックアップの基本


ZFSはスナップショットを取得し、バックアップをストリームで書き出すことができます。
吐き出されたストリームはZFSに書き戻すことができるため、バックアップや引っ越しに利用できます。

書き出しには `zfs send` コマンドを利用し、書き戻しには `zfs recv` コマンドを利用します。


```bash
# スナップショットの作成
sudo zfs snapshot dataset01/data@20211223
# スナップショットの書き出し
sudo zfs send dataset01/data@20211223 > data@20211223.bin
# スナップショットの書き戻し
sudo zfs recv dataset02/data < data@20211223.bin
```

:::note warn
zfs recv する先にすでにファイルシステムが存在する場合にはエラーとなります。
-F オプションを利用すると強制的に書き戻しが実行されます。
:::


また、ストリームで吐き出す時には、スナップショットとスナップショットの差分を出力することもできるため、容量を節約したバックアップもできます。

```bash
sudo zfs snapshot dataset01/data@20211223-2
# スナップショットの書き出し (20211223 から 20211223-2 への差分)
sudo zfs send -i dataset01/data@20211223 dataset01/data@20211223-2 > data@20211223-2.bin
# スナップショットの書き戻し
sudo zfs recv dataset02/data < data@20211223.bin
sudo zfs recv dataset01/data < data@20211223-2.bin
```

:::note warn
適切でない増分ストリームを zfs recv するとエラーになります。
:::


ZFSの基本的な使い方は、わたしが大昔に書いた [How to ZFS](https://chibiegg.gitbooks.io/how-to-zfs/content/) がそこそこわかりやすいと思うので、よかったら参照してください。

https://chibiegg.gitbooks.io/how-to-zfs/content/


# オブジェクトストレージへのバックアップ

`rclone` コマンドには `rclone rcat` コマンドがあり、標準入力から受けたデータをオブジェクトストレージに保存することができます。
(オブジェクトストレージに限らず、 `rclone` が対応するストレージ種別が利用できます)

ZFSがスナップショットを標準出力へ出力できるということは、オブジェクトストレージに保存することができるということです。

たとえば、次のようなコマンドを実行することで、lz4で圧縮しながらオブジェクトストレージへ保存することができます。

```bash
  zfs send dataset01/data@20211223 | lz4 | \
  rclone rcat --s3-chunk-size 1000000 --s3-upload-concurrency 5 --retries 10 -P  objstorage:bucketname/dataset01/data@20211223.lz4
```

スナップショットのストリームが大きい場合、 `--s3-chunk-size` を大きめに設定することが重要です。
オブジェクトストレージへは、分割アップロード (マルチパートアップロード) が行われており、チャンクサイズが小さすぎる場合分割数が制限を超えてしまうためです。

S3の場合、チャンクサイズは 5MB ~ 5GB、チャンク数は最大10,000個になるように調整する必要があります。

# ストリームの分割保存

オブジェクトストレージにおいて、一つのオブジェクトにはサイズの制限があります。
S3では5TBまでです。
つまり、5TB以上のファイルシステムのスナップショットはオブジェクトストレージに保存できません。

標準入力から受けたストリームを規定の容量毎に分割し、 `rclone rcat` コマンドを個別に実行して保存するためのコマンド `splitexec` を書きました。

https://github.com/chibiegg/splitexec

このコマンドを利用することで、次のように分割しながらオブジェクトストレージに保存することができます。

```
  zfs send dataset01/data@20211223 | lz4 | \
  splitexec -s 107374182400 rclone rcat --s3-chunk-size 1000000 --s3-upload-concurrency 5 --retries 10 -P objstorage:bucketname/dataset01/data@20211223.lz4.%05d
```
このコマンド例は 100GB毎に分割し、 `rclone rclone` コマンドを実行して標準入力で流し込みます。
こうすることで、5TBを超えるストリームも保存できます。

あんまり一つのオブジェクトが大きすぎるとダウンロードする時に困るため、そこそこのサイズで分割しておくのが良いだろうと思っています。

# 暗号化など

わたしはさらに `openssl` コマンドで暗号化したり、送信中の容量を `pv` コマンドで見たりするため、次のようなスクリプトを書いて利用しています。

```bash:zfssend.sh
#!/bin/bash -xe

OPENSSL_ENC_OPTIONS="enc -e -aes-256-cbc -salt -pass file:password.txt -pbkdf2"
ENC_SUFFIX="enc_aes256_pbkdf2"

FILESYSTEM="$1"
SNAPSHOT1="$2"
SNAPSHOT2="$3"

RCLONE_STORAGE="objstorage"
BUCKET="bucketname"

function usage () {
  echo "usage: $0 FILESYSTEM SNAPSHOT1 [SNAPSHOT2]"
}


if [ -z "${FILESYSTEM}" ]; then
  usage
  exit 1
fi

if [ -z "${SNAPSHOT1}" ]; then
  usage
  exit 1
fi


FSNAME=`basename ${FILESYSTEM}`

if [ -z "${SNAPSHOT2}" ]; then
  zfs send -R ${FILESYSTEM}@${SNAPSHOT1} | lz4 | \
  openssl ${OPENSSL_ENC_OPTIONS} | pv -c | \
  ~/splitexec -s 107374182400 rclone rcat --s3-chunk-size 1000000 --s3-upload-concurrency 5 --retries 10 -P ${RCLONE_STORAGE}:${BUCKET}/${FILESYSTEM}@${SNAPSHOT1}/${FSNAME}@${SNAPSHOT1}.lz4.${ENC_SUFFIX}.%05d
else
  zfs send -RI ${FILESYSTEM}@${SNAPSHOT1} ${FILESYSTEM}@${SNAPSHOT2} | lz4 | \
  openssl ${OPENSSL_ENC_OPTIONS} | pv -c | \
  rclone rcat -v ${RCLONE_STORAGE}:${BUCKET}/${FILESYSTEM}@${SNAPSHOT1}-${SNAPSHOT2}.lz4.${ENC_SUFFIX}
fi
```

```bash:全送信
sudo zfs snapshot dataset01/data@20210727
sudo ./zfssend.sh dataset01/data 20210727
```

```bash:差分送信
sudo zfs snapshot dataset01/data@20211224
sudo ./zfssend.sh dataset01/data 20210727 20211224
```

差分はあまり大きくしないことを前提に、スナップショット全体を送信するときは100GBで分割し、差分を送信するときは分割せずに送信する手段をとっています。

# さくらのクラウド オブジェクトストレージ

さくらインターネットのさくらのクラウドでもS3互換のオブジェクトストレージを提供しています。


https://manual.sakura.ad.jp/cloud/objectstorage/about.html


 `~/.config/rclone/rclone.conf` に以下のように設定を記述することで `rclone` が利用できます。

```
[objstorage]
type = s3
env_auth = false
access_key_id = "アクセスキー"
secret_access_key = "シークレットキー"
region = ""
endpoint = https://s3.isk01.sakurastorage.jp
location_constraint = ""
```

ぜひご利用ください。