誤家庭のLinuxで大規模ストレージを取り扱う

ストレージ規模がどんどん大きくなる当システム。次期再編成のためのメモである。 なお、話を一般化するために特殊な箇所は変更してあり、このまま適用できるわけではない。

前提

メインとなるホスト(Host Master A)と冗長ホスト(Host Master B)から60TBに及ぶ巨大なストレージを取り扱えるようにする。

バックアップとしてサーバー(Host Slave)を用いてデータの冗長化を行う。

基本的には無停止が可能なようにするが、HAのような完全無停止を要求するわけではない。

機材

安価に拡張可能なストレージ群を構築するには、NetGear ReadyNAS RN316が良いようだ。 これにNetGear ReadyNAS EDA500を追加することで10万円以内で筐体を用意することができる。ディスクとあわせると、20台60TBで約40万円が必要だ(2017年6月現在)。

バックアップは実際には現在はサーバーによる内蔵とAoEの組み合わせ、とするが、将来的には同様の方法をとるのが望ましいだろう。

構成

NetGearで用意したディスクを、NetGearの機能でRAID5化し(つまり有効ディスク数は18となる)、これをiSCSIで提供する。

iSCSIディスクはHost Master Anyがイニシエータとなり、これをLUKSデバイスとして暗号化/復号化し、その上にbtrfsを構築して利用する。

2台ひと組と考えることで、-m1 -d1オプションを使って無停止性を確保することができる。 だが、実際は一般の利用ではディスク故障を除いたデータが損失するファイルシステム事故は割と少ないので、-m1 -d0あるいは-m1 -dsingle構成にしたほうが良いと考えられる。 -m1 -d1とした場合、ディスク30台を使用して使える容量は9台分となる。

バックアップも基本的には同様に構築する。異なるグループのiSCSIを用意し、btrfsボリュームを作成する。

構築

接続

iSCSIによる通信でほぼネットワークはあふれることになるため、ネットワークは分離することにする。

上流側は主にはクライアントユースになる、と考えれば、上流側をUSBアダプタとすることでホストの切り替えを容易にすることが考えられる。

Host Master AをHost Masterと考えた場合、Host Master A及びHost Slaveが2つのNICを持っている状態 となる。 このとき、両者が所属するネットワーク(サブネット1)はホストネットワークであり、下流側はストレージのみを接続する、という形態にすることでiSCSIの通信を混在することを避けられる。

Host Master Bを使用する場合はHost Master AからUSB NICを取り外し、Host Master Aに接続されていたUSB NICにHost Master Bにささっている(サブネット1の)ケーブルを接続し、Host Master B内蔵NICにはHost Master Aに接続されていたサブネット2のケーブルを接続すれば良い。

NASの用意

NASのディスクを各NAS上でRAID5で構成する。

2台ひと組にしておけば、btrfsは-d1であってもディスクを使い切ってくれる。 -dsingleにする場合は、NAS内のディスクは容量を揃える必要があるが、NASの合計容量を揃える必要はなくなる。

全ディスクを連結してRAID5を作ってしまうと、ディスク容量の制約が厳しくなるので、この方法のほうが良いだろう。 性能面でも、暗号化とRAIDの組み合わせはかなり重いので、速度を考えればハードウェアRAIDのほうが望ましい。

RAID5を構成したディスク上にLUNを作る。 グループはMasterとSlaveで異なるものにしたほうが良いが、Master NASはサブネット2、Slave NASはサブネット3に置かれるため、実際に混在することはない。

ホストのセットアップ

iSCSIイニシエータとして利用するため、iSCSIイニシエータutilのインストールと、iscsiサービスの起動が必要になる。

利用方法については、archwiki参照のこと。

全てのiSCSIターゲットにログインした上、これをluksFormatする。 ディスクは増えていくことになるので、キーファイルを使ったほうがスクリプトにする際に良いだろう。

# cryptsetup luksFormat /dev/sdb /path/to/keyfile

そしてオープンする

# cryptsetup luksOpen --keyfile-/path/to/keyfile /dev/sdb Crypt1
# cryptsetup luksOpen --keyfile-/path/to/keyfile /dev/sdc Crypt2

btrfsファイルシステムの作成

# mkfs.btrfs -L MasterBtrfs -m1 -dsingle /dev/mapper/Crypt[12]

追加で、サブボリュームの作成や、fstabの編集などを行っておく。

iSCSIターゲットの探索とログイン、ディスクの復号化とマウントの一連の流れはスクリプトにしておいたほうが良いだろう。

利用

まずNASを起動し、次にホストを起動する。そして、スクリプトによってマウントする。 セットアップが終われば非常にシンプルである(ただし、スクリプトにしていないと打つコマンドの量は多い。それはそれでセキュアだが)

スクリプトをsystemdユニットにしておくのは良い考えだが、コケる可能性の高いユニットなので、enableにはしないほうが良いだろう。

NFSとファイル配置

NFS v4を提供する。Manjaro Linuxでの手順である。

まず、btrfsのマウントポイントは/mnt/masterであるとする。 しかし、実際には特定ユーザーのデータが配置されるのであり、~foo/.masterfsがマウントポイントである。

そこで、これを反映できるようにするため、スクリプトの先頭で次のようにしておく

mount --bind /mnt/master /mnt/master
mount --make-rshared /mnt/master
mount --bind /mnt/master ~foo/.masterfs

これにより、ふたつのマウントポイントを意識せずに共有することができるようになる。

さらに、ここではNFSのマウントポイントも別に用意する。

mount --bind /mnt/master /srv/nfs4/masterfs
mount --make-slave /srv/nfs4/masterfs

ここまではスクリプトに含めると良いだろう。

NFSパッケージのインストール。

# pacman -S nfs-utils

/etc/exportsの設定

/srv/nfs4/masterfs 192.168.1.0/24(rw,no_subtree_check,nohide)

v2, v3の無効化(/etc/conf.d/nfs-server.conf)

NFSD_OPTS="-N 2 -N 3"

起動。マウントを手動で行っているため、enableは非推奨。

# systemctl start nfs-server

他ホスト(サブネット1のホスト)からマウントするためには、次のようにする。

# mount -t nfs -o vers=4 masterhost1:/masterfs /mnt/masterfs

-t nfs4とする必要がある場合、サーバー側パスにエクスポートルートを含める必要がある場合があるという。

CIFS

Arch系ディストリビューションにおけるSambaのセットアップ手順はArchwikiが参考になるだろう。

Linuxを中心に使っているユーザーにとっては、常時マウントするわけではないから、ファイルマネージャでのusershareシェアで十分かもしれない。

ただし、AndroidユーザーにとってはCIFSで構築したほうがメディアファイルの再生も容易になる。

DLNA

メディアファイルを広く再生する方法として有効なDLNA。

DLNA対応のプレイヤーがあり、それによって大画面で見られるといった場合には役立つだろう。

主にHost Masterを使い、Host Masterにログインすることが前提であるならばRygelで良いと思う。 そうでない場合は、ReadyMedia(minidlnaパッケージ)を利用することが推奨されているようだ。

Archのminidlnaはminidlnaユーザー, minidlnaグループで動作する。 そのため、ライブラリはこのユーザーでアクセス可能でなくてはいけない。

また、面倒を避けるためには、/etc/minidlna.confinotifynoにしておくのが良いだろう。

あるいはユーザーで実行したい場合は

$ install -Dm644 /etc/minidlna.conf ~/.config/minidlna/minidlna.conf
$ vim ~/.config/minidlna/minidlna.conf
$ minidlnad -f ~/.config/minidlna/minidlna.conf -P ~/.config/minidlna/minidlna.pid

のようにする。

DAAP

iTunesを使用しているユーザーにとっては軽快に動作する音楽ストリーミング方法。 forked-daapdが推奨されているが、AURにしか存在しない。

forked-daapdをインストールし、libraryセクションのdirectoriesを設定、当該ディレクトリをdaapd実行ユーザー(デフォルトでdaapd:daapd)がアクセス可能な状態にして

# systemctl start forked-daapd

で動作する。

LinuxではAmarokあるいはRhythemboxで再生できる。 Androidスマートフォンでは、DAAP Media Playerなどがあるようだ。

2008年頃に流行したDAAPだが、既にだいぶ廃れている印象はある。

ストレージとの分離

ユーザーデータ(dotfileなど)にも容量の大きなものがあるため、ストレージに退避したいと考えるかもしれないが、割とそれはリスキーである。

dotfileの場合、ファイルシステムをまたぐと動作しないもの、シンボリックリンクをこえられないものがあったりする。 また、ログイン時においてはストレージから切り離された状態にある可能性もあり、またストレージがダウンする可能性もあると考えなくてはいけない。

つまり、ストレージが切り離されても動作する状態を保ったほうが良い、ということである。

また、作業中のファイルなどは、ストレージとは別にシステム側にもあったほうが良い。ストレージが切り離された時に作業不能になる度合いを軽減するためである。

さらに、xdgディレクトリはシンボリックリンクが切れているとログイン時に変更されてしまう。 そのため、データが大きい~/Pictures~/Videoなどは、単純なディレクトリとして、その下にシンボリックリンクを配置したほうが良い。

運用の一例としてだが、次のようなストレージ構成だとする

sda      Internal SSD      System
sda1     Internal SSD      System, /
sda2     Internal SSD      System, Swap
sdb      iSCSI 1GbE        Data, Btrfs
sdc      iSCSI 1GbE        Data, Btrfs

次のようなマウント結果となる

/dev/mapper/luks-xxxxxxxxxxxxxxxxx on / type ext4 (rw,relatime,data=ordered)
/home/foo/.storage on type btrfs (rw,noatime,compress=lzo,space_cache,subvolid=258,subvol=/master)

ストレージへ同期されるべきものとして以下のように構成する

WorkSpace
+ Documents
+ Dotfiles

製作中のメディアファイルはDocumentsではなく~/.storage/user/art/以下に配置し、Documentsは文書ファイルのみを配置することで軽量なディレクトリにすることができる。

次のようなスクリプトを作っておく

#!/bin/zsh

WS=$HOME/WorkSpace

update() {
  rsync -avH "$@"
}

sync() {
  rsync -avH --delete "$@"
}

gitup() {
  (
      cd "$1"
        git add .
        git commit -m "Synced at $(date +%y%m%d)"
        git push
    )
}

hgup() {
  (
      cd "$1"
        hg add .
        hg commit -m "Synced at $(date +%y%m%d)"
        hg push
    )
}

gitup $WS/Documents
for i in $WS/Dotfiles/*
do
  hgup $i
done
sync ~/Pictures ~/.storage/XDG/
sync ~/Videos ~/.storage/XDG/
sync ~/Mail ~/.storage/

slave系

これでmaster系は54TB(3TB * 20 disks)ものスペースを一台に集約し、そのファイルを他のホストでアクセスするようにすることができた。 NFS/CIFS/DLNA/DAAPでアクセスできるため、ファイルの取り扱いは非常に良いはずだ。

加えてRAID5によりディスク故障やディスク破壊にも対応する。

だが、これでも壊れる可能性はある。誤消去や誤った更新・上書き、NASの故障、Btrfsの修復不能なクラッシュにどう対応するのだろうか?

ここでRAIDとバックアップは違う、という基本に立ち戻る必要がある。 つまり、このように堅牢なシステムを作っても、あるいはBtrfsクラッシュに備えてRAID5 NASにBtrfs RAID1を組み合わせたとしても、バックアップは別途必要なのである。

基本的にバックアップはメインストレージと同等の設備が必要になる。 もしメインストレージをBtrfs RAID1としているのであれば、バックアップにおける無停止性はそれほど重要ではないため、その場合はメインストレージの半分で済むということになる。

つまりおおよそシンメトリカルな2つのストレージ群とホストが編成されることになる。 ただし、slave系のコントローラホストはサーバーであり、処理性能よりも省電力性や静音性が重要となるだろう。 とはいえ、あまりにも処理性能をおざなりにすると、SSHの処理でもたついて時間がかかることになる。

現状で当環境においてはProLiant MicroServerを使用しているが、どちらかといえばラップトップのほうが適性があるとされている。 標準でコンソールを備え、無停電電源装置を内蔵した状態になるからだ。しかも省電力で静音性も高い。 メインホストからみた速度向上のためにはメモリは多目であったほうがいいだろう。

構築、ストレージへの接続、Btrfsの作成は同様である。

バックアップはBtrfsのsend/receive機能を使うことができる。 差分バックアップも用意で、本当に変更されたものだけを転送するため、非常に効率が良い。

次のようなスクリプトを用意する。

#!/bin/zsh

notify-send "BtrSnapSync: Woke"

if ! btrfs subvolume snapshot -r /mnt/master /mnt/master/snapshots/snapshot-new
then
  print "BtrSnapSync: **CRITICAL** Failed to create new snapshot"
  exit 2
fi

sync

notify-send "BtrSnapSync: Snapshot."

if [[ -e /mnt/master/snapshots/snapshot-old ]]
then
  if btrfs send -v -p /mnt/master/snapshots/snapshot-old /mnt/master/snapshots/snapshot-new/
  then
    notify-send "Send Snapshot."
    btrfs subvolume delete /mnt/master/snapshots/snapshot-old
    mv /mnt/master/snapshots/snapshot-new /mnt/master/snapshots/snapshot-old
    notify-send "Deleted old Snapshot."
  else
    notify-send "Failed to send Snapshot"
    btrfs subvolume delete /mnt/master/snapshots/snapshot-new
  fi
else
  if btrfs send -v /mnt/master/snapshot-new
  then
    notify-send "Send Snapshot."
    mv /mnt/master/snapshots/snapshot-new /mnt/master/snapshots/snapshot-old
    notify-send "Deleted old Snapshot."
  else
    notify-send "Failed to send Snapshot"
    btrfs subvolume delete /mnt/master/snapshots/snapshot-new
  fi
fi

また、slave側は次のようなスクリプトを用意する。

#!/bin/sh
btrfs receive /MirrorRoot && mv /MirrorRoot/snapshot-new /MirrorRooot/snapshot`date +%y%m%d%H%M`

あとは次のようにすることで転送する。

$ btrfssend.zsh | ssh -i /root/.ssh/btrfssnapsync root@slave.local /usr/local/sbin/btrfsreceiver.sh

暗号化を行わず処理を軽減するには、まずslave側で次のように実行する(OpenBSD Netcatを使用している)

# nc -l 12358 | btrfsreceiver.sh

続いてmaster側。ここではbashを使っている。

$ btrfssend.zsh > /dev/tcp/slave.local/12358

暗号化の必要がないとしても、危険だと考えられるため、SSHの利用が推奨される。

btrfsのsnapshot/send/receiveの挙動を検証する

スナップショットと容量とリネーム(mv)

最初の状態。スナップショットはsharedとされている

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  96.00KiB       0.00B           -  ./test1
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
 192.00KiB       0.00B    96.00KiB  .

ファイルを追加。追加した分以外がsharedで、サイズが増えているのはsnapshotではなくワーキングボリューム。

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB    68.00KiB           -  ./test1/fileC
 164.00KiB    68.00KiB           -  ./test1
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
 260.00KiB    68.00KiB    96.00KiB  .

新規にスナップショットを取ると増えたファイルもsharedに入った。合計で3ファイル分の容量で無駄がない

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB       0.00B           -  ./test1/fileC
 164.00KiB       0.00B           -  ./test1
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
  40.00KiB       0.00B           -  ./snap2/fileA
  56.00KiB       0.00B           -  ./snap2/fileB
  68.00KiB       0.00B           -  ./snap2/fileC
 164.00KiB       0.00B           -  ./snap2
 424.00KiB       0.00B   164.00KiB  .

もう一度ファイルを増やす。

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB       0.00B           -  ./test1/fileC
  20.00KiB    20.00KiB           -  ./test1/fileD
 184.00KiB    20.00KiB           -  ./test1
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
  40.00KiB       0.00B           -  ./snap2/fileA
  56.00KiB       0.00B           -  ./snap2/fileB
  68.00KiB       0.00B           -  ./snap2/fileC
 164.00KiB       0.00B           -  ./snap2
 444.00KiB    20.00KiB   164.00KiB  .

そしてスナップショット。これは準備段階。

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB       0.00B           -  ./test1/fileC
  20.00KiB       0.00B           -  ./test1/fileD
 184.00KiB       0.00B           -  ./test1
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
  40.00KiB       0.00B           -  ./snap2/fileA
  56.00KiB       0.00B           -  ./snap2/fileB
  68.00KiB       0.00B           -  ./snap2/fileC
 164.00KiB       0.00B           -  ./snap2
  40.00KiB       0.00B           -  ./snap3/fileA
  56.00KiB       0.00B           -  ./snap3/fileB
  68.00KiB       0.00B           -  ./snap3/fileC
  20.00KiB       0.00B           -  ./snap3/fileD
 184.00KiB       0.00B           -  ./snap3
 628.00KiB       0.00B   184.00KiB  .

mvしてみると、ファイル名は異なっているが、sharedの量は増えていない。
mvは理解してくれるらしい。ファイル名ではなく実体の問題。

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB       0.00B           -  ./test1/fileC
  20.00KiB       0.00B           -  ./test1/fileE
 184.00KiB       0.00B           -  ./test1
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
  40.00KiB       0.00B           -  ./snap2/fileA
  56.00KiB       0.00B           -  ./snap2/fileB
  68.00KiB       0.00B           -  ./snap2/fileC
 164.00KiB       0.00B           -  ./snap2
  40.00KiB       0.00B           -  ./snap3/fileA
  56.00KiB       0.00B           -  ./snap3/fileB
  68.00KiB       0.00B           -  ./snap3/fileC
  20.00KiB       0.00B           -  ./snap3/fileD
 184.00KiB       0.00B           -  ./snap3
 628.00KiB       0.00B   184.00KiB  .

rsync fileE fileF; rm fileEとしてみた。
やはり、別ファイルとして扱われ、sharedの量が減っている。

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB       0.00B           -  ./test1/fileC
  20.00KiB    20.00KiB           -  ./test1/fileF
 184.00KiB    20.00KiB           -  ./test1
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
  40.00KiB       0.00B           -  ./snap2/fileA
  56.00KiB       0.00B           -  ./snap2/fileB
  68.00KiB       0.00B           -  ./snap2/fileC
 164.00KiB       0.00B           -  ./snap2
  40.00KiB       0.00B           -  ./snap3/fileA
  56.00KiB       0.00B           -  ./snap3/fileB
  68.00KiB       0.00B           -  ./snap3/fileC
  20.00KiB    20.00KiB           -  ./snap3/fileD
 184.00KiB    20.00KiB           -  ./snap3
 628.00KiB    40.00KiB   164.00KiB  .

ちょっと手直し。3つ目のスナップショットを取る前の状態に。

# btrfs subvol delete test1
# mv snap3 test1
# btrfs property set -ts test1 ro false

mvして、ファイルを追加した。

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
  40.00KiB       0.00B           -  ./snap2/fileA
  56.00KiB       0.00B           -  ./snap2/fileB
  68.00KiB       0.00B           -  ./snap2/fileC
 164.00KiB       0.00B           -  ./snap2
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB       0.00B           -  ./test1/fileC
  20.00KiB       0.00B           -  ./test1/fileE
 112.00KiB   112.00KiB           -  ./test1/fileF
 296.00KiB   112.00KiB           -  ./test1
  40.00KiB       0.00B           -  ./snap3/fileA
  56.00KiB       0.00B           -  ./snap3/fileB
  68.00KiB       0.00B           -  ./snap3/fileC
  20.00KiB       0.00B           -  ./snap3/fileD
 184.00KiB       0.00B           -  ./snap3
 740.00KiB   112.00KiB   184.00KiB  .

shareされていないのは追加されたfileFだけ。ここでスナップショットを取っておく。

     Total   Exclusive  Set shared  Filename
  40.00KiB       0.00B           -  ./snap1/fileA
  56.00KiB       0.00B           -  ./snap1/fileB
  96.00KiB       0.00B           -  ./snap1
  40.00KiB       0.00B           -  ./snap2/fileA
  56.00KiB       0.00B           -  ./snap2/fileB
  68.00KiB       0.00B           -  ./snap2/fileC
 164.00KiB       0.00B           -  ./snap2
  40.00KiB       0.00B           -  ./test1/fileA
  56.00KiB       0.00B           -  ./test1/fileB
  68.00KiB       0.00B           -  ./test1/fileC
  20.00KiB       0.00B           -  ./test1/fileE
 112.00KiB       0.00B           -  ./test1/fileF
 296.00KiB       0.00B           -  ./test1
  40.00KiB       0.00B           -  ./snap3/fileA
  56.00KiB       0.00B           -  ./snap3/fileB
  68.00KiB       0.00B           -  ./snap3/fileC
  20.00KiB       0.00B           -  ./snap3/fileD
 184.00KiB       0.00B           -  ./snap3
  40.00KiB       0.00B           -  ./sendsnap-latest/fileA
  56.00KiB       0.00B           -  ./sendsnap-latest/fileB
  68.00KiB       0.00B           -  ./sendsnap-latest/fileC
  20.00KiB       0.00B           -  ./sendsnap-latest/fileE
 112.00KiB       0.00B           -  ./sendsnap-latest/fileF
 296.00KiB       0.00B           -  ./sendsnap-latest
   1.01MiB       0.00B   296.00KiB  .

send/receive

さて、ここからsend/receiveを試してみる。まずはsnapshot-latestの単純な送信

# btrfs send sendsnap-latest > ~/send1
At subvol sendsnap-latest

# ls -l send1
-rw-r--r-- 1 root root 294193  8月 31 12:42 send1

使用量通りと考えていいだろう。
次にsnap3に基づいて作ってみる。容量は差分容量通りといった感じ。

# btrfs send -p snap3 sendsnap-latest > ~/send2
At subvol sendsnap-latest

# ls -l send*
-rw-r--r-- 1 root root 294193  8月 31 12:42 send1
-rw-r--r-- 1 root root 114925  8月 31 12:44 send2

それぞれの差分を作る。

# btrfs send snap1 > ~/sendsnap1
At subvol snap1
# btrfs send -p snap1 snap2 > ~/sendsnap1-2
At subvol snap2
# btrfs send -p snap2 snap3 > ~/sendsnap2-3
At subvol snap3

# ls -l send*
-rw-r--r-- 1 root root 294193  8月 31 12:42 send1
-rw-r--r-- 1 root root 114925  8月 31 12:44 send2
-rw-r--r-- 1 root root  94926  8月 31 12:45 sendsnap1
-rw-r--r-- 1 root root  66551  8月 31 12:45 sendsnap1-2
-rw-r--r-- 1 root root  18576  8月 31 12:45 sendsnap2-3

全サブボリュームを削除してみる。

# for i in *; do btrfs subvol delete $i; done
Delete subvolume (no-commit): '/mnt/1/sendsnap-latest'
Delete subvolume (no-commit): '/mnt/1/snap1'
Delete subvolume (no-commit): '/mnt/1/snap2'
Delete subvolume (no-commit): '/mnt/1/snap3'
Delete subvolume (no-commit): '/mnt/1/test1'

最新ボリュームのsend2をreceiveしてみる。

# btrfs receive . < ~/send2
At snapshot sendsnap-latest
ERROR: cannot find parent subvolume

元になるサブボリュームがないとだめらしい。
順に戻してみる。

# btrfs receive . < ~/sendsnap1
At subvol snap1
# btrfs receive . < ~/sendsnap1-2
At snapshot snap2
# btrfs receive . < ~/sendsnap2-3
At snapshot snap3
# btrfs receive . < ~/send2
At snapshot sendsnap-latest

順当に行える。では、名前が変わったらどうだろう?
sendsnap-latestを削除して、ベースになるsend3をリネームしてみる。

# btrfs subvol delete sendsnap-latest/
Delete subvolume (no-commit): '/mnt/1/sendsnap-latest'
# mv snap3 sendsnap-base
# btrfs receive . < ~/send2
At snapshot sendsnap-latest
# ls
sendsnap-base  sendsnap-latest  snap1  snap2

sendsnap-latestはsnap3というサブボリュームに基づいて作られていたが、snap3という名前のサブボリュームは、sendsnap-baseという名前に変更されてしまったため、もうない。
だが、一致する名前はなくても正しく書き戻すことができた。

ls -l sendsnap-latest/
合計 296
-rw------- 1 yek yek  40005  8月 31 12:11 fileA
-rw------- 1 yek yek  54006  8月 31 12:20 fileB
-rw------- 1 yek yek  66006  8月 31 12:22 fileC
-rw------- 1 yek yek  18066  8月 31 12:24 fileE
-rw------- 1 yek yek 114066  8月 31 12:35 fileF

ということは名前ではなく、IDなどで判断しているのだろう。元になったサブボリュームが存在していればOKだ。
では中間がなくなったらどうだろう?

元snap3、元sendsnap-baseだけを残してみる

# btrfs subvol delete sendsnap-latest/
Delete subvolume (no-commit): '/mnt/1/sendsnap-latest'
[mint 1]# btrfs subvol delete snap2
Delete subvolume (no-commit): '/mnt/1/snap2'
[mint 1]# btrfs subvol delete snap1
Delete subvolume (no-commit): '/mnt/1/snap1'
# btrfs fi du .
     Total   Exclusive  Set shared  Filename
  40.00KiB    40.00KiB           -  ./sendsnap-base/fileA
  56.00KiB    56.00KiB           -  ./sendsnap-base/fileB
  68.00KiB    68.00KiB           -  ./sendsnap-base/fileC
  20.00KiB    20.00KiB           -  ./sendsnap-base/fileD
 184.00KiB   184.00KiB           -  ./sendsnap-base
 184.00KiB   184.00KiB       0.00B  .

全くシェアはされていないが、シェアフラグを削除し、データは消していないようだ。
ハードリンクのような仕組みだろうか。

サブボリューム名とシンボリックリンク

では、名前においてsubvol名はシンボリックリンクでも良いのだろうか?
send2を戻してリンクしてみる。

# btrfs receive . < ~/send2
At snapshot sendsnap-latest
# ln -s sendsnap-latest/ current
# mount -o subvol=current /dev/mapper/hymaster_1 /mnt/3
# ls /mnt/3
fileA  fileB  fileC  fileE  fileF

こうなった。

マウントするサブボリュームの切り替えは、set-defualtでも良いが、シンボリックリンクを論理的な名前にすることも可能なようだ。

ディスク故障でbtrfsの機能を使い倒す

対応方法

ディスク容量が逼迫しているので何らかの対処が必要である。
とりあえず考えられる方法としては

  • ディスクを大容量のものに交換する
  • USBあるいはeSATA接続のディスクを追加する
  • NASを追加してディスクを追加する
  • AoEを使って他ホストのディスクを追加する
  • 内蔵ディスクを追加する

のいずれかだ。そもそも取り扱うデータ量が多いため、削減ということは考えられない。
もちろん、削減自体は行っているが、その精査にあてる時間のほうが重要で、無効な対策であると考えられる。それほど引き延ばすこともできない。

大容量への交換は効果的だが、現在3TBを使用しており、4TBの場合は33%の容量増となり、不足である。
6TB以上は高額すぎるため却下された。

USB/eSATAを最も有力な候補としていたが、台数搭載するためには金額的にも高く、またいずれの製品も信頼性に劣るようだ。

多数搭載のケースを使用するのであればReadyNASも有力な候補だった。ReadyNASはiSCSIに対応するが、「ReadyNASに搭載されているディスクを指定する」ことはできない。何台搭載しても、btrfs的に見れば1台にしかならないのだ。

そのため、btrfsで管理するためにはReadyNASを複数台追加する必要がある。ReadyNAS全体でbtrfsから見て1台のディスクであり、ReadyNASの台数=ディスク台数になる。
btrfsは不均等なRAID1をサポートするため、例えばReadyNASに12TBを搭載すれば12TB RAID1を構成することが可能だが、あまりうれしくはない状態になる。

AoEを使うのは追加費用のかからない方法だ(ディスクとホストはあるため)。
だが、問題はAoEを使う方法は常に

  • マウント前にターゲットホストを起動していなければならない
  • アンマウント前にターゲットホストを終了してはいけない
  • ネットワークを切断してはいけない

と結構厳しい条件になる。
ターゲットホストが消費電力が大きく、また冗長系システムであり、日常稼働しているものであることを考えるとこれは厳しい。

そこで内蔵ディスク追加を選択した。

Z400の3.5inchシャドウベイの数は2、SATAポート数は6である。
現在、iStarUSAの5.25inchベイ*2を3.5inch HDD*3に変換するリムーバブルラックを使用してシステムSSDを含む計5台を搭載している。
そのため、2台追加となると、マウント的にもSATAポート的にも足りない。

採用した方法はエアリアのSATA*2 PCI-e 1xカードによりポートを2増設し、DVDドライブを除去してiStarUSAの5.25inch*3をHDD*5に変換するリムーバブルラックを搭載するというもの。

かなり無茶だが、さらに無茶なのが、そもそもビデオカードがGeForce GTX750Tiに変更されているために、隣のPCI-e 4xが埋まってしまっており、逆側のPCI-e 4xは使用済みなので、PCI-e 16xに接続した、ということか。

ディスク追加手順

ディスクの追加は、ディスクを装着した状態で

# btrfs device add /dev/sdx /path/to/volume
# btrfs device add /dev/sdy /path/to/volume
# btrfs balance /path/to/volume

のようになる。
balanceの所要時間は果てしなく、うちのケースでは40時間ほどかかった。

そして、balanceがまもなく終わろうという頃に、もともとあったほうのディスクが壊れた。

故障ディスクの除去と交換

故障ディスクが正常にアクセスできない状態にあるとき、btrfsファイルシステムへのアクセスはシステム全体を止めてしまう。
この状態ではbtrfs device deleteもできない。

もしこれが可能な状態であればdeleteよりも新規ディスクも装着した状態でのreplaceのほうが早い。

故障ディスクはオフラインの状態でまず除去してしまい、その上で新規ディスクに交換する。
そして、新規ディスクに対して

# btrfs device add /dev/sdx /path/to/volume

したあと、

# btrfs device delete missing /path/to/volume

するとreplaceに近い挙動になる。

しかし、実際にやるとシステムが死んでしまった。ログにがっつりRIPが残っていた。

ファイルシステム新規作成

もはや修復は不可能なので、ファイルシステムを再作成してバックアップから書き戻す。

dm_crypt上に作成するのであれば、dmデバイスを用意したうえで

$ sudo mkfs.btrfs -L labelname -d raid1 -m raid1 /dev/mapper/crypt_dev_*

こうしてみると気づくのだが、btrfsのミラーリングはミラーレッグの指定ができない。
ジャーナリングの強化版というコンセプトなので当然なのかもしれないが、結構不安。本物のRAID1がほしい人はLVMを使うほうが良さそう。LVM+Btrfsであれば、2台ずつ容量を揃える必要があるが、容量の異なるディスクを混在できる。

バックアップのbtrfsファイルシステムからのリストア

btrfsにはsend/receive機能があり、簡単にバックアップができる。

btrfs sendは差分も可能でストリームで送られる。つまりファイルとして保存することもできる。
この場合復元はcatによって行うことになるだろう。差分だとどうなるのかはわからない。

recieveはsendによって出力されるストリームを元にファイルへと書き出す。

ネットワーク越しのバックアップ手段として行う場合は、権限的な難しさがある。send/receiveはroot権限が必要なためだ。
rsyncであればユーザー権限でのバックアップが可能だが、send/receiveを使う場合はそうはいかない。
受け渡しをssh経由で行おうとすると、rootでのsshアクセスを許していなかったりして結構なネックになる。
別に鍵を作れば良い話ではあるが。

sudoを用いたコマンドラインで行うのであれば、socatを使用する方法がある。receive側は

$ socat tcp-listen:30000 | sudo btrfs receive /path/to/volume

send側は

$ sudo btrfs subvolume snapshot -r /path/to/volume /path/to/snapshot
$ sudo btrfs send /path/to/snapshot | socat stdin tcp-connect:receiverhost:30000

経路安全性を問題にするのであれば、openssl-listenを使うか、ssh -Lを使うなどの方法がある。
インターフェイスを限定できないのが問題なら、rubyワンライナーを使う方法や、もうひとつプログラムを増やし、ソケットで読んで書き込む仕様として、ssh経由でソケットに出力するプログラムを起動するという方法もある。

例えば次のようなスクリプトである。

#!/usr/bin/zsh
zmodload zsh/net/socket

zsocket -l /tmp/btrfs-syncer
sock=$REPLY
while zsocket -a $sock
do
  (
    btrfs receive /path/to/volume <&$REPLY
  ) &
done

こちらはrootで実行する。対してsshで実行すべきは

#!/usr/bin/zsh
zmodload zsh/net/socket
zsocket /tmp/btrfs-syncer
cat >&$REPLY
exec {REPLY}>&-

あとは

$ sudo btrfs subvolume snapshot -r /path/to/volume /path/to/snapshot
$ sudo btrfs send /path/to/snapshot | ssh receivinghost.locadomain btrfs-syncer-client

注意点として、例えばsubvolというサブボリュームに書き戻したい場合、subvolというサブボリューム上にreceiveすると結構困ってしまう。
その場合はそのペアレントボリュームにreceiveした上で(snapshotの名前になってしまうので)mvするのが正しい。

なお、mvしてもreadonlyのままになるため、ロールバックするためには属性変更が必要。

$ sudo btrfs property set -ts subvol ro false

ro trueでreadonly。
IDによるマウントを使っているのであれば、set-default

追加のクリティカルヒット

しかし、またも/dev/sdgの大量エラーという結果になった。一見すると正常だが、ログを見ると1.77TiBの書き込みの間に71456ものblk_update_requestが発行されている。

2度続けてsdgだったため、ケーブル、ラック、ボードのいずれかが問題である可能性も考えられたため、ディスクを入れ替えて(/dev/sdfの位置にあるディスクと入れ替えた)試したところエラーはなく、ディスクの不良と断定。初期不良を申請し、翌日回答、翌々日到着となった(NTT-Xの対応は早い)。

ちなみに、NTT-Xにディスクの初期不良対応を申請したところ、

  • メーカーに確認→メーカー回答を得て応答(交換or返金)→交換の回答を得て発送→到着時に集荷として引き渡し

という手順だった。

交換ディスクは

# dd if=/dev/zero of=/dev/sdf bs=512M

として全域書き込みを行い、

# journalctl --no-pager | grep -F blk_ | tail

としてblk_update_requestが発生していないことを確認した。

そして前述の通り、新規ファイルシステム作成、マスターボリュームのマウント、socatと連動したsend/receive、リネーム、プロパティ変更と行い、エラーがないことを確認した。
なぜか全域でデータ量が5.30TiBまで減少していた。

しかしtopで、kworker/3:2及びksoftirqd/3が張り付いたままになっている。
kworkerはおそらくbtrfs絡みだろうし、ksoftirqdが固まっているということはディスク処理だろうから、しばらく待つことにする。特にkworkerはずっとS欄がR(Running)であり、明らかに忙しくしているので安全を考えて待つ。

結局kworderがしずまらなかったので、syncしてunmountし、それが問題なくunmountされたことを確認してからreboot。

エラーが何もないことを確認して、おそらくこれで完了。
とてもとてもとても大変だった。

サーバーをManjaro i3に

サーバー(ProLiant Microserver, Manjaro Linux LXQt)が、起動中にコケるようになっていたので、Manjaro i3で作りなおしてみた。

バックアップ作業

単純にsystemdユニット、そこから呼ばれるスクリプトと設定ファイルをバックアップした。
後にfstabもバックアップする必要があったことに気づく。

i3 Window Manager

i3 window managerは、タイル型ウィンドウマネージャだ。

タイル型ウィンドウマネージャというと、AwesomeやXMonadが有名だが、Manjaroで最も活発なのはi3だ。i3, XMonad, bspwmの比較を見ても、最も人気があるのはi3であるようだ。

Awesomeと比べると利点は多い。マルチディスプレイに対応しているというのが大きいのではないか。

bspwmもManjaroではCommunity buildのあるウィンドウマネージャだ。bspwmのほうが、ウィンドウに隙間があってスタイリッシュだが、i3のほうがとっつきやすい。ちなみに、Gnome3をi3で使っている人もいるようだ。なかなか変態的だ。けれど結構使いやすいのではないかという気もする。
ここまでカスタマイズすれば作業環境は快適だろう。

机でちゃんとした作業スペースがあるわけでもなく、膝の上の小さなキーボードの上でも扱いやすい、また複数のモニタープログラムを並べ、状況を把握したい場合にGUIを立ち上げてコンソールを叩くようなケースも考えられるのだし、サーバーにi3というのはベストな選択なのではないか。

i3やbspwmというとArchばかり出てくるが、それはArchであれば環境構築が楽なGUIとしての選択肢にもなりうるし、そもそもArchでもなければなかなかパッケージもないからだろう。

非常に機能的で使いやすいウィンドウマネージャだ。

Manjaro i3

Community buildではあるが、Manjaroにはi3 Editionが存在する。
ウィンドウマネージャの操作感こそ違うが、全体的な印象としては変わらない。Thusインストーラもそのままだ。インストール手順も、LUKSやbtrfsに関するソフトウェアも変わらない。

Manjaro Welcomeはいきなりフローティングウィンドウで表示される。
フローティングウィンドウはタイルとは完全に独立として表示され、マウスでフォーカスすることになる。ちなみに、クリックフォーカスではなく、オーバーフォーカスである。

ModはSUPER(Windows)に設定されている。Mod+Enterでターミナルエミュレータを開くことができる。基本的な操作方法は画面に表示されているし、mod+Shift+hでヘルプを表示できる。

なお、フリードライバで起動する必要があった。

アップデート

キーリングがおかしなことになってしまうので

# pacman-key --init
# pacman-key --populate archlinux manjaro
# pcaman-key --refresh
# pacman -Syuu

その間にavahi-daemonとsshdをenableしておく。mdns_minimalは既にエントリに記載されており、問題ない。

さらに、/etc/default/grub にデフォルトオプションとしてsystemd.unit=multiuser.targerを追加しておく。
グラフィカル起動する時は、Grubでオプションを編集して削る。

再起動したら、

# mhwd-kernel -i linux45

Linux 4.5はまだRC1だが、問題なく動作した。

設定

退避したファイルを戻すのだが、tarパイプでファイルとして保存したものを、tarパイプで戻そうとするとパーミッションを書き換えられてしまうので要注意。一度これでやり直しになった。

/etc/fstabを書いて完成だ。

i3は良いけれど

Synapticsによる設定をちゃんとしていれば結構快適だったりするし、Cinnamon/Plasma/XFceはタイリングもでき、便利なアプレットもあったりするので、実際に選択する機会は少ないかも。

btrfsを構成するディスクが壊れてからRMAで交換発送するまで

障害の発生と確認

2015-12-31朝、いつものようにディスクを復号化し、マウントするとエラーが表示された。
ここのところ好調だったため、念の為Scrubを開始した。

# btrfs scrub start /world

そして、恐怖を味わうこととなる。

scrub started at Thu Dec 31 11:32:28 2015, running for 03:14:49
total bytes scrubbed:3.03TiB with 7192102 errors
error details: read=7192102

エラーが出るのははじめてではないが、数が異常だ。そして、これらはそのほとんどがuncorrectableと提示される。RAID1で組んでいるため、通常は修正されるし、修正されなかったのは初めてだ。

ここで仕組みを説明しよう。

通常ファイルシステムは、ひとつのブロックデバイス(ディスクパーティションであることが普通)上に作成され、独立したファイルの管理となる。
しかし、btrfsは1台以上の任意のブロックデバイス上に作成することができる。
WindowsでいえばC:やD:といった単位は、通常はディスクの数以上(ディスクにパーティションがひとつなら1、2つなら2)だが、複数のブロックデバイス上に作成できるため、2台、3台で1つのドライブにすることができる。

実はこのようなことはWidnowsでもできたりする。ダイナミックディスク機能だが、扱いにくいのでほぼ使われることはない。
複数のブロックデバイスを結合するファイルシステムというのは、クラスタファイルシステムにとって普通のことであるといえる。GSFやOCFSなど色々とある。そして、ZFSもそうだ。
だが、普通はディスクは全て同じ容量でなければならないか、違う容量であった場合は最も小さいディスクに合わせられる。btrfsはこの制限がないばかりか、3台のディスクでディスク容量合計の1/2のミラーを行うようなことができる。ただし、ここでは詳しくは述べない。

btrfsは低速だが堅牢を志向している。ブロックが壊れた場合—アクセス中に電源断にあったとか、宇宙線で飛ばされたとか—に検出できるだけでなく、そもそも壊れないようにするし、壊れた場合でも複数のディスクに同一の内容を記録するRAID1(ミラーリング)機能を用いていれば別のミラーを参照することで訂正することすら可能だ。
また、運用しながらディスクを追加/除去することも可能。

この仕組みのため、エラーは起こらないように設計されているはずなのに少なく見積もっても他のファイルシステムの数百倍は壊れるファイルシステムでありながら、実際にRAID1構成でデータが壊れてしまうことはまず発生しない。

それでありながら修復しようとしたら700万を越えるエラーが発見され、しかも修正できないというのはただごとではない。

2時間を過ぎてシステム全体がフリーズした。完全に。
btrfsの操作は結構システムをフリーズさせることが多い。busyで止まって、そのまま息絶えてしまうのだ。

700万というのは異常なので、次にファイルシステムのチェックを行う。この時点からPCは使えない。

# btrfsck --repair --check-data-csum /dev/mapper/worlddisk01

そして、大量に出力される、デバイスエラー、blk_update。果てしなく出力され続ける。
こうなると、ディスクが怪しいように感じるが、しばらく出力されるエラーを眺めてみることにした。

ほとんどは/dev/sddだが、ちょいちょい/dev/sdcがまじる。

7時間後、ファイルシステムエラーなしでfsck終了。

それで直ったとは到底思えないが再度scrub。15分後、システム停止。

そこで、smartctlを用いてディスクをチェックする。結果、sdb, sdeはエラーにならないが(現在、マスター系btrfsは4台のクラスタである。なお、sdaはシステム用のSSD)、sdc, sddは通過しない。特にsddは瞬殺だ。

つまり、ディスクが2台壊れた。少なくともsddはもはや動かなくなるのも時間の問題だ。

2レッグミラーなので、1台壊れても同じデータはもう1台にある。しかし、2台壊れるとデータは失われる。btrfsは複雑なミラーリングを行うため、必ずしも対になるディスクはないので、2台壊れた時に損失する可能性は非常に高い。

4台のクラスタで2台が同時に故障する確立は、どれだけ多めに見積もっても百万分の一に満たない。それが大晦日など「特別な日で、かつ長期の休み」と重なる確率は天文学的な値になるはずだ。

データの保全

まだマウントはできるため、とりあえずsystemdユニットを修正してro(読み取り専用)でマウントするようにして、スレーブ系にrsyncで送る。

スレーブ系はbtrfsだが、マウント時に自動でスナップショットを取るようになっている。そのため、手動で消さない限りデータの履歴を取り出すことができる。
rsyncは差分でデータを送るが、既に既存のデータが破損している可能性がある。
この場合は、rsyncで差分が送られ、スレーブ側のデータは破壊される。ただし、スナップショットによって履歴が取り出せることで、正しいデータに修正することができる。

問題は前回のミラーリング以降に追加または更新されたデータだ。それが壊れていた場合はそれは失うしかない。

rsyncはエラーを起こすことなく終了した。もしbad blockにあるデータが差分に存在したらblk_update_requestを発行せざるをえないし、もしミラーされている両方のデータのcsumが合わない場合はbtrfsはRead errorになるはずなので、エラーなく終了したということは恐らく全データが無事転送されたはずだ。

データを失うことは避けられたか?

なお、私のシステムはRAID1ミラーリングにソフトミラーリングという構成で、2段冗長構成になっている。この台数でRAID1ミラーでデータが失われる可能性は極めて低い一方で、この冗長性のためにこの膨大なディスク容量に対して3倍のコストがかかる。
スレーブ系はやめてしまおうかと考えたこともあったが、もしそれを実行していたら、私は5TBに渡る全てのデータを損失していたことになる。

ディスクの除去

まずはsddをこのままにしておくと致命傷になる可能性があるため除去を試みるが、ファシルステム6TB(全体では12TBで、RAID1のため半分)に対して使用量は5TB。
当然ながら、4台から1台を除去するためには使用率は75%以下でなくてはいけない。しかし、5/6は0.833なので明らかにできない。

# btrfs device delete /dev/mapper/world03 /world

案の定、34時間後、No space left on device.ということで失敗した。
Bad blockが多いために余計に時間がかかった感じだ。

ちなみに、方法はもうひとつあった。
そもそもディスクを除去してしまい、degradedでマウントした上で

# btrfs device delete missing /world

する方法だ。壊れたディスクが1台ならこれでもよかったのだが、sdcも壊れていたため、sdcでは壊れているがsddでは生きているデータがある可能性も考えるとかなりためらわれた。scrubできないので、これを予め解消できない。

そして、replaceという方法を別にすれば手順は

  1. sddを除去
  2. 新ディスクを取り付ける
  3. balance(sdcがえらいことになるかも)
  4. sdcを除去
  5. 新ディスクを取り付ける
  6. balance

なので、軽く10日はかかる計算となる。

また、成功した場合でもsdcとsddが死んでいるため、開いていないから気づいていないだけで壊れているデータが紛れ込んでいる可能性があり、なかなか怖い。

ディスクの発注

34時間の間に発注した。

なんといっても大晦日だったので、2日のアキバの初売りに突撃するか(体調もよくないのに辛いだろう)、ネットで発注するかだった。

結局悩んだ上で、深夜3時にツクモネットショップで発注。今回はSeagateが2,4TBがセール、TOSHIBAが3,4,6,8TBがセールだったため、単価の安い3TBは元値が高いTOSHIBAしかなく、ネットショップの通常セールでSeagateの3TBのほうが税込みだとわずかに安かった。交通費を含めると確実にやすかったためネットショップ発注。

ただし、発送が4日以降だったため、結局到着したのは5日だった。

復旧

device deleteしている最中に発案したのが、もう現在のマスター系のファイルシステムは破棄して、新しいディスクで新規のbtrfsを構築してデータを逆向きに同期することで復元するというバックアップを用いた方法だった。

スレーブ系は全てのデータを持っているはずなので、問題はないはずだ。

device deleteに失敗したことを踏まえてこれを実行。rsync -aHvで36時間を要した。

sent 82,870,445 bytes  received 4,285,991,604,707 bytes  36,456,286.60 	bytes/sec

total size is 4,358,770,747,505  speedup is 1.02

仮想マシン関連のデータは破棄することとなるため、データサイズはある程度減っている。

この方法、ディスク数が増えてくると所要時間は逆転する。
今回の場合、1台あたりが3TBで、データ総量が4.3TB。
3TBのブロックを他のディスクに移すよりも4.3TBを転送するほうが早く、そのあとbalanceすることを考えれば圧倒的に早い。ただし、同時接続してdevice replaceする場合はまた話が変わってくる。

RMA

ディスクはWD Greenで保証期間内だ。ウェスタンデジタルのディスクは交換保証がついている。

RMAの手順と方法については、KLX/ITLOGに非常に詳しく書かれているので、ほぼこの通りで良い。

そして、ここに書かれている通り、名前を漢字で登録した結果文字化けてRMAを作りなおしてもらうということもやってしまった。ちなみに、私はサポートにメールフォームからメールした。

嬉しいことに交換理由にSMARTエラーという項目があり、話が早い。

ESDバッグや5cmものクッションなどハードルが高いが、比較的IT機器の購入が多く、梱包も保存している(この手のことを予測して)ため、なんとかなった。

また、発送先なのだが、東京にdrop pointが作られたため、東京港に送ればよくなった。費用的には小さく、送りやすく、それでいて配送品質も保たれるようになってハッピーだ。

RMAが結構やりやすいので、これで通ってくれたら、今後は保証期間の長さのためにWD REDを買ってもいいかもしれない。(故障しないというのは信用しない。特に運のない私の場合は)

結果

  • 障害時間は156時間
  • 手順や対応は極めて良く、迅速であった
  • データの損失は恐らくない
  • 資産的損失はRMAが通れば送料片道でしかないから大したことない
  • だが、年末年始が見事なまでに潰れた
  • ライフもかなり削った感じ

Firefoxによるbtrfsの破壊トラブル

btrfsが頻繁に壊れるトラブルに見舞われた。
デスクトップがフリーズし、再起動するもディスクへのアクセスで非常に長い時間がかかる。
くりかえし再現したが、いずれもFirefoxの、特定のプロファイルでYouTubeを見ている最中だった。

btrfs scrubすると、ある特定のポイントに集中的にエラーが発見される。
結局、他のブラウザや、他のプロファイルでは発生しないが、
「問題のあるプロファイルを.mozillaごとリネームしても発生するし、問題のないプロファイルを同じファイル名にリネームしても発生する」という状態だった。

究明はできなかったが、とりあえず報告まで。

暗号化ディスクをsystemdでがんばる

これまで単純にスクリプトで暗号化ディスクをマウントしていた。
systemdスクリプトにするのは簡単で、実際にSystemdで自動マウントしていた時期もある。

だが、今回は「ちゃんと」Systemdを使うことにした。

スクリプトはGitHubで公開している。

私の場合、btrfsのボリュームとして4つのデバイスを使い、その4つのデバイスはディスク全体をdm-crypt plainで暗号化したものだ。
つまり、おおまかにはcrpytsetupのあとmountする必要があり、かつcryptsetupは全ディスク分ループしなくてはいけない。

これ自体はスクリプトとして用意してあり、これまでそれを使っていた。
Systemd対応にするのも、単純にSystemd経由にするだけなら、以前のエントリの通り簡単なユニットで良い。

今回はスクリプトは、「後処理」に対応した。
もちろん、スクリプトに後処理を組み込むなら非常に簡単な話だ。
だが、それでは「後処理に失敗するとユニット全体が失敗」してしまう。
そこで、systemdのExecStartPostを使うことにした。
それに伴って、スクリプトは起動スクリプトらしくなったし、設定ファイルを使うようになったりしている。

elif [[ "$1" == post ]]
then
  ### StartPost
  if whence -f opendisk_after >&2
  then
	opendisk_after
  else
	exit 0
  fi

fi

これでだいぶ整った。これを呼ぶほうは

ExecStartPost=/usr/local/sbin/opencryptdisk.zsh post /etc/opencryptdisk/%I.conf

%Iについては後ほど説明。

しかしここでだいぶ躓いた。ExecStartの完了を待たずにExecStartPostしてしまうため、ここに処理が入っているとコケるのだ。

これは、Typeを省略しているとsimpleとして扱われる。これは、フォアグラウンドで走るデーモンのためのユニットタイプで、「実行と同時に起動完了・実行終了とみなす」というもの。
実際はスクリプトの実行が終わった時に起動完了・実行終了としてほしいため、

Type=oneshot

を追加した。

同じような理由でExecStopが入っているとこけていたため、スクリプトにはcloseも入っているが、ExecStopが外されている。
ExecStopでアンマウントするのであれば、

RemainAfterExit=yes

として、「スクリプト終了時に起動完了・実行は継続とみなす」にする必要がある。
ただ、マウントと暗号化デバイスは、多分終了時に自動的にうまくやってくれるため、必要ないと判断して外してある。

systemdユニットがname@.serviceの形になっていると、name@param.serviceとして起動することができ、@paramをユニット内で%Iとして使うことができる。
複数ボリュームのマウントを可能にするため、この機能を使用している。


Systemdについてだが、非常に高機能だが、一方で複雑でめんどくさい。

例えば、マウントもスクリプト内でやるのではなく、.mountユニットにして、そのAfterで復号化してから実行、という形もとれたが、明らかにmountを書くほうが早い。

やはりかなりの暗黒面だ。

Btrfsのバックアップがうまくいかない

sshの問題

「sshdだとダメなのに、sshd -d(デバッグモード)では通る!!」 と言っていた問題は、yum update -yしたあとで再起動したら解決した。 意味不明だ。

send/receiveはよく止まる

2.79TiBに達した時に、send側コンピュータが停止してしまう。 なお、スナップショットの削除を行って再びためすと、さらに短く失敗するようになる。

最初はオーバーヒートかと思ったのだが、2.79TBで止まる、ということが共通しているため、問題があるように疑われる。

これで代替手段というと、スクリプトを組むしかないか。 それなりに複雑なものになりそうだ。もっとも、btrfsが集中的なwriteそのものを受け付けないのなら話は別だが、3TB近く書けるのだから、まさか。

rsyncでなくsend/receiveを使うことでBtrfsに戻す

概要

btrfsがrsyncで死んでしまうので、現在はLVM+XFSを使用しているが、いい加減容量は限界近いのでbtrfsに戻す。

rsyncで詰んでしまう以上、rsyncではなく、send/receiveを用いたものとする。 btrfs wikiによれば、rsyncを用いるよりも遥かに高速なのだという。

手順

まずはアンマウント

umount /MirrorSlave

そして、LVMをインアクティブに

vgchange -a n

btrfsを作る

mkfs.btrfs -L MirrorBtr /dev/mapper/btr*

/etc/fstabを編集する

UUID=0123456-7890-acbd-ef01-234567890ab /MirrorRoot           btrfs   noauto,noatime,autodefrag,compress-force=lzo,space_cache,subvol=mirror 0 0

初期化スクリプトを編集する

#!/bin/zsh

typeset -i index=1

for i in /dev/disk/by-id/{ata-foo,ata-bar,ata-baz,ata-quux}
do
#  print $i
  cryptsetup --hash=sha512 --cipher=twofish-xts-plain --offset=0 --key-file=/home/foo/key --key-size=512 open --type=plain "$i" "btrdm_$(printf "%02d" $index)" || exit 1
(( index++ ))
done

mount /MirrorRoot
	
#mount -U 7ebc7d8d-35c6-4d98-8457-3323c242e9fe -o noatime,autodefrag,compress-force=lzo,space_cache,subvol=mirror /MirrorRoot

#pvscan
#vgscan
#lvscan
#vgchange -a y MirrorVG
#lvchange -a y MirrorVG/MirrorLV
#mount -U "0d09b605-a52b-48f4-8ad5-ed26456ab6cd" /MirrorRoot

クライアント側スクリプト。

#!/usr/bin/zsh

notify-send "BtrSnapSync: Woke"

if ! btrfs subvolume snapshot -r /home/foo/share /home/foo/share/snapshots/snapshot-new 
then
  print "BtrSnapSync: **CRITICAL** Failed to create new snapshot"
  exit 2
fi

sync

notify-send "BtrSnapSync: Snapshot."

if [[ -e /home/foo/share/snapshots/snapshot-old ]]
then
  if btrfs send -v -p /home/foo/share/snapshots/snapshot-old /home/foo/share/snapshots/snapshot-new/  | ssh -v -i /root/.ssh/btrsnapsync daisy.local /usr/local/sbin/btrrecv.sh
  then
    notify-send "Send Snapshot."
    btrfs subvolume delete /home/foo/share/snapshots/snapshot-old
    mv /home/foo/share/snapshots/snapshot-new /home/foo/share/snapshots/snapshot-old
    notify-send "Deleted old Snapshot."
  else
    notify-send "Failed to send Snapshot"
    btrfs subvolume delete /home/foo/share/snapshots/snapshot-new
  fi
else
  if btrfs send -v /home/foo/share/snapshots/snapshot-new  | ssh -i /root/.ssh/btrsnapsync daisy.local /usr/local/sbin/btrrecv.sh
  then
    notify-send "Send Snapshot."
    mv /home/foo/share/snapshots/snapshot-new /home/foo/share/snapshots/snapshot-old
    notify-send "Deleted old Snapshot."
  else
    notify-send "Failed to send Snapshot"
    btrfs subvolume delete /home/foo/share/snapshots/snapshot-new
  fi
fi

サーバー側スクリプト

#!/bin/sh
btrfs receive /MirrorRoot && mv /MirrorRoot/snapshot-new /MirrorRooot/snapshot`date +%y%m%d%H%M`

rootのSSH鍵を生成(適宜)

mkdir .ssh
ssh-keygen -f .ssh/snapshot
chmod 700 .ssh
chmod 600 .ssh/*

鍵を転送(適宜)

scp .ssh/snapshot.pub server:.ssh/authorized_keys
ssh server
chmod 600 .ssh/authorized_keys

鍵をコマンドに結びつける

vim .ssh/authorized_keys

sshd_configでforced-commands-only

vim /etc/ssh/sshd_config

挙動

btrfsのsnapshotやsend/receiveについて知識がなかったため、挙動をひとつひとつ確認することにした。

まず、snapshotはディレクトリに見える。 そのディレクトリにはスナップショットの完全なファイルがあるように見える。常時アクセス可能な凍結された状態が存在するわけだ。

そして、スナップショットは元となるボリュームにネストされたサブボリュームになる。 つまり、/fooにマウントされているサブボリュームのスナップショットは、/foo/snapshot1のようになるというわけだ。この外、例えば/barには置けない。

この/foo/snapshot1はmvは可能だが、rmはできない。rmするのであれば、btrfs subvolume deleteでなければならない。

このスナップショットをsendする時、-pオプションがなければそのsnapshotを構成する全体を送信する。あれば、指定されたサブボリューム(マウントされたディレクトリを指定)との差分を送信する。

内容は標準出力に吐かれ、-fオプションで保存することもできるが、それはリダイレクトで保存しても良いようだ。

receiveについても、およそ似たような挙動である。 標準出力から読んで再編成するか、もしくは-fオプションでファイルから編成する。 これは、sharのようなものではなく、編成されるのはスナップショットである。 つまり、snapshot1というsnapshotをreceiveした場合、snapshot1というsnapshotが作られる。

mvで名前が変わった状態で、-pオプション付きで送りつけたsnapshotがどうなるのかは、まだ試していない。

snapshotにロールバックする場合は、マウントポイントの付け替えと、サブボリュームのデリートでよさそうだ。set-defaultが必要か?

多くのスナップショットを維持することでディスク容量やI/Oにどのように影響するのかはわからない。 英語文書を読めばよいのかもしれないが、なかなか大変そうなので、そこには当面手を付ける予定はない。

思わぬつまづき

rootユーザーでRSA鍵でsshログインできないという問題に遭遇した。 一般ユーザーならばできるし、またサーバー側がsshd -dで起動した場合は通る。 デバッグモードなら通るため、メッセージの確認もできない。 かなり困った。

壊れたファイルシステムの保存・復元作業

先日の騒動、無事btrfsの修復ができたが、損失の可能性がある。 そこで、バックアップを利用して損失があった場合に備える。

基本的な考え方としては、損失がある可能性のあるファイルを上書きせずにバックアップし、その上でrsyncによりバックアップの更新を行う。

完全なsnapshotを取るのではなく、データに差分があるものについて行うこととした。 つまり、全データについて、実際に読んで、比較することになる。

まずは待避先となるサブボリュームを用意する。

# btrfs subvolume create escaped_snap

次に、さらに別のサブボリュームを用意し、そこに全データをコピーする。今回はディスクに余裕があるので、不安定なデスクトップの稼働時間を抑えることを優先した。

# btrfs subvolume create dest2
# umount /WorldMirror
# mount -o gzip,noatime,space_cache,subvol=dest2 /dev/mapper/btrvolume01 /WorldMirror

最初はrecursiveにファイルを探索して、cmpで比較し、差分があるものについてコピーしようとした。

#!/usr/bin/zsh
# $1 = olddir
# $2 = newdir
# $3 = snapshot base dir

setopt extended_glob
cd "$1"


for i in **/*(#qD.)
do
  print ">>$i"
  if cmp "$i" "$2/$i"
  then
    print "OK"
  else
    sha256sum "$2/**/$i" > /tmp/rec-dir-cmpsnap-sum
    sum=$(sha256sum "$i" | cut -d " " -f 1)
    
    if ! fgrep -q "$sum" /tmp/rec-dir-cmpsnap-sum
    then
      tar cfv - "$i" | (cd "$3"; tar xf -)
    fi

  fi
done

rm /tmp/rec-dir-cmpsnap-sum

だが、うまくいかなかった。理由はよくわからなかったが。

そこで、再帰的なチェックサム差分のためにrsyncのdry-runを使う。

rsync -cvna /mnt/3/ /Worldmirror/ > rsyncdiff.result

/mnt/3には元々の、上書きされていないバックアップがある。

dry-runなので、これによってコピーされようとしているファイルが出力される。 -cオプションでチェックサム比較しているので、「バイナリ的に違いがある、または新しいコピーには存在しないもの」がリストアップされる。

これで転送に関する余分なメッセージを削除すればファイルリストができる。 今回はファイルリストにmvした300GBを越えるファイルがあるので、それについてはリストから削除しておく。

そしてそのリストに基づいてスナップショットへとコピー

while read
do
tar cvf - "$REPLY" | (cd /mnt/2; tar xf -)
done < ~/rsyncdiff.result2

長い道のりだった