数年運用している FreeBSD を zfs raid1 にしたい。
元はSSD1台。追加したSSDは同じ型番。

# camcontrol devlist
<Samsung SSD 850 PRO 512GB EXM02B6Q>  at scbus6 target 0 lun 0 (pass5,ada4)   # new ssd
<Samsung SSD 850 PRO 512GB EXM02B6Q>  at scbus11 target 0 lun 0 (pass10,ada9) # old ssd

他の環境では何度も成功している gpart backup | gpart restore が通らない。

# gpart backup /dev/ada9 | gpart restore -F /dev/ada4
gpart: start '34': Invalid argument

どうやら、元は first:34 先は first:40 のようだ。これでは上手くいく筈がない。

# gpart backup /dev/ada9
GPT 128
1   freebsd-boot         34        128
2    freebsd-zfs        162 1000215021

# gpart list ada9
Geom name: ada9
modified: false
state: OK
fwheads: 16
fwsectors: 63
last: 1000215182
first: 34
entries: 128
scheme: GPT
Providers:
1. Name: ada9p1
   Mediasize: 65536 (64K)
   Sectorsize: 512
   Stripesize: 4096
   Stripeoffset: 1024
   Mode: r0w0e0
   efimedia: HD(1,GPT,83e479d0-f41b-11e4-92db-0025229f7519,0x22,0x80)
   rawuuid: 83e479d0-f41b-11e4-92db-0025229f7519
   rawtype: 83bd6b9d-7f41-11dc-be0b-001560b84f0f
   label: (null)
   length: 65536
   offset: 17408
   type: freebsd-boot
   index: 1
   end: 161
   start: 34
2. Name: ada9p2
   Mediasize: 512110090752 (477G)
   Sectorsize: 512
   Stripesize: 4096
   Stripeoffset: 1024
   Mode: r1w1e2
   efimedia: HD(2,GPT,868c6c71-f41b-11e4-92db-0025229f7519,0xa2,0x3b9e11ed)
   rawuuid: 868c6c71-f41b-11e4-92db-0025229f7519
   rawtype: 516e7cba-6ecf-11d6-8ff8-00022d09712b
   label: (null)
   length: 512110090752
   offset: 82944
   type: freebsd-zfs
   index: 2
   end: 1000215182
   start: 162
Consumers:
1. Name: ada9
   Mediasize: 512110190592 (477G)
   Sectorsize: 512
   Stripesize: 4096
   Stripeoffset: 0
   Mode: r1w1e3

# gpart list ada4
gpart: Class 'PART' does not have an instance named 'ada4'.

# gpart create -s GPT ada4
ada4 created

# gpart list ada4
Geom name: ada4
modified: false
state: OK
fwheads: 16
fwsectors: 63
last: 1000215175
first: 40
entries: 128
scheme: GPT
Consumers:
1. Name: ada4
   Mediasize: 512110190592 (477G)
   Sectorsize: 512
   Stripesize: 4096
   Stripeoffset: 0
   Mode: r0w0e0

FreeBSD 12 では start:34 は作れないのだろうか。

# freebsd-version -ku
12.0-RELEASE-p10
12.0-RELEASE-p11

このコミットが影響していて、実質 34 -> 40 がデフォルトになったように思える。
https://github.com/freebsd/freebsd/commit/eff424dd1339357d9cf9921b472de2138aa48d31
変更は出来ないように思える。

それでは既存の freebsd-zfs に足すミラーは作れるのだろうか。

どうやら試行錯誤してみると、既存の pool より小さくても attach できることが分かった。

# gpart backup /dev/ada9
GPT 128
1   freebsd-boot         34        128
2    freebsd-zfs        162 1000215021  <- これより小さくても attach 出来た

# gpart backup /dev/ada4
GPT 128
1    freebsd-zfs        162 998500000   <- attach 可

# gpart backup /dev/ada4
GPT 128
1    freebsd-zfs        162 998000000   <- attach 不可

# zpool attach zroot /dev/oldp2 /dev/newp1
cannot attach /dev/newp1 to /dev/oldp2: device is too small

なんとかなりそうだ。
だが、この境界はどうやって決まっているのだろうか。

# perl -nle 'print if /Get the minimum/../^}/' sys/cddl/contrib/opensolaris/uts/common/fs/zfs/vdev.c 
* Get the minimum allocatable size. We define the allocatable size as
* the vdev's asize rounded to the nearest metaslab. This allows us to
* replace or attach devices which don't have the same physical size but
* can still satisfy the same number of allocations.
*/
uint64_t
vdev_get_min_asize(vdev_t *vd)
{
        vdev_t *pvd = vd->vdev_parent;

        /*
        * If our parent is NULL (inactive spare or cache) or is the root,
        * just return our own asize.
        */
        if (pvd == NULL)
                return (vd->vdev_asize);

        /*
        * The top-level vdev just returns the allocatable size rounded
        * to the nearest metaslab.
        */
        if (vd == vd->vdev_top)
                return (P2ALIGN(vd->vdev_asize, 1ULL << vd->vdev_ms_shift));

        /*
        * The allocatable space for a raidz vdev is N * sizeof(smallest child),
        * so each child must provide at least 1/Nth of its asize.
        */
        if (pvd->vdev_ops == &vdev_raidz_ops)
                return ((pvd->vdev_min_asize + pvd->vdev_children - 1) /
                    pvd->vdev_children);

        return (pvd->vdev_min_asize);
}

模すと

# zdb -C zroot | ack 'asize|shift'
                metaslab_shift: 32
                ashift: 9
                asize: 512105119744

return (P2ALIGN(vd->vdev_asize, 1ULL << vd->vdev_ms_shift));
                    512105119744            32

# perl -E 'say 1<<32'
4294967296
# perl -E 'say 512105119744/4294967296'
119.233764648438
# perl -E 'say 4294967296*119'
511101108224
# perl -E 'say 4294967296*119/512'
998244352

998244352 は上の 998000000 〜 998500000 の間にある。

もう少しコードを読んでみると。

# sys/cddl/contrib/opensolaris/uts/common/fs/zfs/sys/vdev_impl.h
typedef struct vdev_label {
        char		vl_pad1[VDEV_PAD_SIZE];			/*  8K */
        char		vl_pad2[VDEV_PAD_SIZE];			/*  8K */
        vdev_phys_t	vl_vdev_phys;				/* 112K	*/
        char		vl_uberblock[VDEV_UBERBLOCK_RING];	/* 128K	*/
} vdev_label_t;							/* 256K total */

/*
* vdev_dirty() flags
*/
#define	VDD_METASLAB	0x01
#define	VDD_DTL		0x02

/* Offset of embedded boot loader region on each label */
#define	VDEV_BOOT_OFFSET	(2 * sizeof (vdev_label_t))
/*
* Size of embedded boot loader region on each label.
* The total size of the first two labels plus the boot area is 4MB.
*/
#define	VDEV_BOOT_SIZE		(7ULL << 19)			/* 3.5M */

/*
* Size of label regions at the start and end of each leaf device.
*/
#define	VDEV_LABEL_START_SIZE	(2 * sizeof (vdev_label_t) + VDEV_BOOT_SIZE)
#define	VDEV_LABEL_END_SIZE	(2 * sizeof (vdev_label_t))

と VDEV_LABEL_START_SIZE + VDEV_LABEL_END_SIZE あたりから

# perl -E 'say 998244352 + ((256*1024*2)+(7<<19))/512 + (256*1024*2)/512'
998253568

境界は 998253568 だろうか。合っているようだ。

# gpart backup /dev/ada4
GPT 128
1    freebsd-zfs        162 998253568   <- attach 可

# gpart backup /dev/ada4
GPT 128
1    freebsd-zfs        162 998253567   <- attach 不可

# gpart backup と境界の差
# perl -E 'say 1000215021 - 998253568'
1961453 
# perl -E 'say 1961453*512/1024/1024'
957.74072265625 # gpart list の size より 約957メガ 小さくても attach 可能


結論
1 上のコミット後は gpart create -s GPT で first:34 のものは作れず first:40 になる。
2 zfs attach zmirror oldp newp する場合、newp は oldp の asize より小さくできる場合がある。

オフィシャル: http://pefs.io
ports: https://www.freshports.org/sysutils/pefs-kmod/
コンパクトなチュートリアルあり: https://wiki.freebsd.org/PEFS
スライド: http://pefs.io/static/pefs-devsummit-2011-oct.pdf

2011とあるのでどうだろうと思ったのですが FreeBSD 12.0-RELEASE で普通に使えました。
普通のチュートリアルは上を参照ください。下はもう一つのチュートリアルです。
下で private ディレクトリまで準備できます。

# pkg install pefs-kmod
# kldload pefs
# mkdir private.enc private
# pefs mount private.enc private

まだ鍵を準備していませんが、private 以下に書いていくと、private.enc 以下に暗号化された実体が記録されていきます。

Key chains というのを作っていくのですが スライド ページ10DAG があります。
模して次を作っていくことにします。

+-----------------------+
| T  (9e39b8c21dea1d93) |
+-----------------------+
            |
            v
+-----------------------+  +-----------------------+  
| S1 (87ee2f60bd8eaf5a) |  | S2 (84551ece0649113e) |
+-----------------------+  +-----------------------+
            |                          |
            v                          v
+--------------------------------------------------+
| C  (5c1894bd1c4f6366)                            |
+--------------------------------------------------+

箱の中は パスフレーズ(パスフレーズ文字列のハッシュ値) とします。
Tの子はS1で、S1の子はCです。

まだ鍵を準備していませんので、書けません。

# echo str > private/file_C
zsh: read-only file system: private/file_C

pefs addchain で鍵を登録するのですが、このコマンドは親と子のペアを登録するのが標準です。
最初の鍵(子のいない鍵)を登録するため -Z オプションをします。
さらにマウント元のディレクトリ private.enc を対象とするために -f を指定します。

# pefs addchain -f -Z private.enc
Enter parent key passphrase: C
Reenter parent key passphrase: C
# find -s ./ -type f
./private/.pefs.db
./private.enc/.pefs.db
# pefs showchains private
Enter passphrase: C
Key chain:
        1    5c1894bd1c4f6366 aes128-xts
# echo str > private/file        _C
zsh: read-only file system: private/file_C

パスフレーズのからのハッシュ値が名前となります。
この時点ではマウント直後と同様に書けません。
使用する鍵を指定します。

# pefs addkey -c private
Enter passphrase: C
# echo str > private/file_C
# find -s ./ -type f
./private/.pefs.db
./private/file_C
./private.enc/.+2+jSBPun89VBA4xC5mgqMcwe8pGHPmt
./private.enc/.pefs.db

書けました。
ファイル名と実体が暗号化されています。(暗号化には tweak を用いるとあり、同じパスフレーズであっても同じになると限りません)
使用する鍵を除いてみましょう。

# pefs flushkeys private
# find -s ./ -type f
./private/.+2+jSBPun89VBA4xC5mgqMcwe8pGHPmt
./private/.pefs.db
./private.enc/.+2+jSBPun89VBA4xC5mgqMcwe8pGHPmt
./private.enc/.pefs.db

file_C が確認できなくなりました。
ここまでで通常の使用が開始できると思います。

次は上の Key chains を構成してみましょう。

# pefs addkey -c private
Enter passphrase: C
# pefs addchain private
Enter parent key passphrase: S1
Reenter parent key passphrase: S1
Enter chained key passphrase: C
Reenter chained key passphrase: C
# pefs addchain private
Enter parent key passphrase: S2
Reenter parent key passphrase: S2
Enter chained key passphrase: C
Reenter chained key passphrase: C
# pefs addchain private
Enter parent key passphrase: T
Reenter parent key passphrase: T
Enter chained key passphrase: S1
Reenter chained key passphrase: S1

pefs showchains では入力したパスフレーズの鍵とその子が表示されます。

# pefs showchains private
Enter passphrase: T
Key chain:
        1    9e39b8c21dea1d93 aes128-xts
        2    87ee2f60bd8eaf5a aes128-xts
        3    5c1894bd1c4f6366 aes128-xts
# pefs showchains private
Enter passphrase: S1
Key chain:
        1    87ee2f60bd8eaf5a aes128-xts
        2    5c1894bd1c4f6366 aes128-xts

# pefs showchains private
Enter passphrase: S2
Key chain:
        1    84551ece0649113e aes128-xts
        2    5c1894bd1c4f6366 aes128-xts

複数の鍵が使用できると、どのように振る舞うのでしょうか。
まずは暗号化に用いられた鍵を確認します。

# pefs getkey private/file_C
Key(private/file_C): 5c1894bd1c4f6366 aes128-xts

次は S1 を使ってみましょう。

# pefs showkeys private
Keys:
        0    5c1894bd1c4f6366 aes128-xts
# pefs flushkeys private
# pefs showkeys private
No keys specified
# pefs addkey private
Enter passphrase: S1
# pefs showkeys private
Keys:
        0    87ee2f60bd8eaf5a aes128-xts
        1    5c1894bd1c4f6366 aes128-xts

指定したパスフレーズの鍵とその子が全て使えるようになります。
暗号化してみます。

# pefs getkey private/file_S1
Key(private/file_S1): 87ee2f60bd8eaf5a aes128-xts

file_C と file_S1 では用いられた鍵が異なるようです。
では使える鍵が無いときはどうなるのでしょうか。

# pefs flushkeys private
# pefs showkeys private
No keys specified
# find -s ./ -type f
./private/.+2+jSBPun89VBA4xC5mgqMcwe8pGHPmt
./private/.pefs.db
./private/.uwLFhItsCXV2+kA004wv15VtCBXlY2wp
./private.enc/.+2+jSBPun89VBA4xC5mgqMcwe8pGHPmt
./private.enc/.pefs.db
./private.enc/.uwLFhItsCXV2+kA004wv15VtCBXlY2wp
# pefs addkey private
Enter passphrase: C
# find -s ./ -type f
./private/.pefs.db
./private/.uwLFhItsCXV2+kA004wv15VtCBXlY2wp
./private/file_C
./private.enc/.+2+jSBPun89VBA4xC5mgqMcwe8pGHPmt
./private.enc/.pefs.db
./private.enc/.uwLFhItsCXV2+kA004wv15VtCBXlY2wp
# pefs addkey private
Enter passphrase: S1
pefs: cannot add key: File exists <- このメッセージは子の C を登録しようとしたものに対してだと思われます。S1は登録されています。
# pefs showkeys private
Keys:
        0    5c1894bd1c4f6366 aes128-xts
        1    87ee2f60bd8eaf5a aes128-xts
# find -s ./ -type f
./private/.pefs.db
./private/file_C
./private/file_S1
./private.enc/.+2+jSBPun89VBA4xC5mgqMcwe8pGHPmt
./private.enc/.pefs.db
./private.enc/.uwLFhItsCXV2+kA004wv15VtCBXlY2wp

使える鍵に応じて部分的に復号化されているのが確認できます。

他、補足です。

* pefs setkey <directory> とすることで、そのディレクトリ以下で用いる鍵を指定できます。
* リネームでは用いる鍵は引き継がます。
* 存在するファイル名に対して新たな内容を書いても、用いる鍵は引き継がれます。
* 存在しないファイル名に対して書いた場合は、getkey の 0 が用いられます。
* pam_pefs(8) があります。
* ファイルレイアウトの階層や更新日時やパーミッションまで暗号化するのは意図していません。(geliなどとは異なります)
* この文章で 鍵を登録 とか書いてますが、鍵の実体が .pefs.db に書かれる訳ではありません。既知のパスフレーズかの確認や親子関係の保持に用いられるようです。
* 所謂パスワード変更を行うには、新たな親キーの登録と、そのキーを使った暗号化を意図した新たなinodeに書き写していくような処理が必要になるでしょう。
* Key chains として、最も下の子が複数存在することも可能です。(ここでは C 一つですが) 構造を表示するコマンドは無いようです。

munin-asyncd 75秒の壁

今さらながら munin-asyncd を使ってみたんだけど、どうも munin-node からの値取得が75秒以上かかる場合に問題が出るようだ。。。

# ack --color -H 0.75 -C /usr/local/share/munin/munin-asyncd
/usr/local/share/munin/munin-asyncd-157-	# start the next run close to the end of a munin-node update operation
/usr/local/share/munin/munin-asyncd-158-	# (i.e. try to avoid overlapping activities)
/usr/local/share/munin/munin-asyncd:159:	my $when_next = int((int($when / $minrate) + 0.75) * $minrate);
/usr/local/share/munin/munin-asyncd-160-	while ($when_next <= $when) {
/usr/local/share/munin/munin-asyncd-161-		$when_next = $when_next + $minrate;  

以下推測。
munin-asyncd は5分刻みの75秒前に munin-node に値を取得しにいって
list してシーケンシャルに fetch $servece していって、最初の time で値を保存する。
75秒後に munin-cron の munin-update からリクエストが来て、前回の spoolfetch 中の timestamp で spoolfetch $time する。
前回の spoolfetch のタイミングで munin-asyncd が動作中だと、それまでに終わっている値は送られるのだけど、まだ終わっていない分は送られず、次回の spoolfetch にも含まれず、この分が欠落する。

munin-ayncd --fork などを使うと解決するのだけれど load average スパイクが起こるかも。

github には複数 Issue が寄せられています。次期メジャーバージョンアップでは改善するかもです。

perl adv
perl adv