新品コンピュータを初期状態に戻せるようにバックアップ

これは何

新品コンピュータを購入したときに、完全に元の状態に復元できるようにするものである。

バックアップ手段は色々とあるのだが、もっとも確実かつ完全な手段が「ディスクの完全なクローンを得る」という方法だ。

その方法について紹介しよう。

なお、これは「やや複雑な手段をとっても効率的に、確実に元に戻せる手段を構築したい」という人向けであり、 そのような場合は諦めるという人や、考えたり努力することを回避したいという人は対象としていない。

新品ならでは

新品のコンピュータのディスクは、ファイルをコピーしているわけではない。 データを流し込んで複製しているのだ。

一般的には、先頭からWindowsシステム用の領域が書き込まれ、そして末尾にバックアップ用の領域があるのが一般的である。 バックアップ用の領域はパーティションが切られ、その中に格納されていることもあるし、パーティションレイアウトの末尾の後ろに配置される場合もある。

このため、データが書かれているのは先頭と末尾のみで、中間は全くデータがなく、0が書かれている。

128GBのディスクをフルクローンした場合は当然ながら128GBのファイルが出来上がる。 だがしかし、連続する0のデータは圧縮すると極めて小さくなる。そのため、このようなディスクイメージは圧縮することによって非常にコンパクトにできるのだ。

これが使用していたディスクであればうまくいかない。 データが記録されていない領域も0で埋められているわけではなく、ごちゃごちゃに記録された状態になっているため、フルディスクイメージを圧縮したときに極端に小さくなることはない。 そのため、完全性や確実性が低下してでもなるべくコンパクトな形式でバックアップするのが一般的なアプローチになる。(partimageやntfscloneなどを使うということだ)

書き戻しに関して

次のようにして取得したイメージは

$ dd if=/dev/sda | xz -z -c > /mnt/sda.img.xz

次のようにして書き戻すことができるのだが

$ xz -d -c /mnt/sda.img.xz > /dev/sda

この場合、128GBの「元のディスクに」書き戻すことを前提している。

サイズが違う場合どうなるかというと、ディスクが大きければパーティションがディスク全域ではなく途中で終了し、途中にリカバリー用の領域が置かれた状態になる。 ディスクが小さい場合はパーティションがディスクのサイズよりも大きく設定されてしまう。 この修正はちょっと大変だ(Windows上では難しいかもしれない)。

だが、実際のところリカバリー領域は書き戻しによって復元できているため、なくなってしまっても構わない、と考えられる。 そして、パーティションテーブルはパーティションの内容より前にある。 よって「先頭からデータのある分だけを復元すれば良い」ということになる。

このため、復元したデータは全部書く必要はなく

$ xz -d -c /mnt/sda.img.xz | dd of=/dev/sda bs=1024M count=10

のようにすれば良い(1024MB=1GiBを10回なので10GiB書き込むことになる)。 内容としてはどのみち残りは0なのであり、書いてもかかなくても同じだ。 ディスクサイズが異なる場合はパーティションサイズのみ変更すれば良い。

バックアップの仕方

Linuxで起動する…ところまでは省略しよう。 UnetBootinなどの使用は勧めないが。

まずはpartedでディスクを確認しよう

$ sudo parted -l

これによって各ディスク名(/dev/sdaなど)を得ることができる。 ここではバックアップ対象のディスクはsdaであるとしよう。 異なる場合は読み替えること。

リムーバブルディスクにバックアップ

モダンなLinuxシステムではリムーバブルディスクを接続すれば認識され、ファイルマネージャが起動する。

ファイルマネージャ上でリムーバブルディスクを開き、そこで右クリックから端末(ターミナル)を開く、とする。 そして

$ dd if=/dev/sda bs=64M of=recover.img

のようにする。 もし、圧縮も同時に行うのであれば

$ dd if=/dev/sda bs=64M | xz -z -d > recover.img.xz

のようにする。時間がかかっても圧縮率を上げるのであれば

$ dd if=/dev/sda bs=64M | xz -z -d -e -9 > recover.img.xz

とすれば良い。

なお、NASなどのネットワークドライブ上に保存する場合も似た手順で行うことができる。

ネットワーク上のLinuxホストに対して行う

もう、説明がいるのかどうかも怪しいが、計算機母艦となるLinuxを運用している場合は 受け取る側のLinuxホストではnetcatをインストールしておく。

そしてnetcatで通信を待機する。 次の例ではBSD netcatを使用する。

$ nc -N -l 22500 > recover.img

受け取り側で圧縮する場合は次のようにする。

$ nc -N -l 22500 | xz -z -d -e -9 > recover.img.xz

送り側はncなどはない場合が多いだろうし、ライブブートではインストールも難しい。 しかし、bashにはTCP機能があるため、これを利用する。

$ dd if=/dev/sda bs=64M > /dev/tcp/192.168.1.128/22500

注意点は、bashでなければならないこと、そしてファイルに書けばよいわけではなくリダイレクト機能を使わなければいけないことだ。

Linux的にWindowsをバックアップしてみる

ClonezillaやMondo Rescueを使えば一発だし簡単なのだけれど、ここはより手軽で確実な方法を模索する。

Clonezillaでいいのであれば(最大の欠点は、より小さいディスクへの復元ができないことだが)、Clonezillaがわかりやすく容易だし、Mondo Rescueも工夫次第でかなり柔軟だ。

しかし、今回はよりテクニカルかつ原始的に解決してみる。

バックアップを考える

ものすごく単純なバックアップ方法としてddがある。この場合、ディスクに記録されている全ビット情報をクローンする。完全なクローンが出来上がるが、20GBを使用している2TBディスクのクローンのために2TBの領域が必要となる。

これを緩和する方法として圧縮があるが、この場合使っていないが書き込まれている「ノイズ」が多い場合は有効に小さくならない。

NTFSであればntfscloneというプログラムによって、必要最低限の領域で正確にクローンができる。特殊イメージ形式は非常にコンパクトだ。

ここで、Windowsディスクの特性と、Windowsシステムの特性を見てみよう。

$ socat tcp-listen:20083 STDOUT
GPT fdisk (gdisk) version 1.0.1

Partition table scan:
  MBR: protective
  BSD: not present
  APM: not present
  GPT: present

Found valid GPT with protective MBR; using GPT.
Disk /dev/sdb: 625142448 sectors, 298.1 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 9AED5CE5-3A6D-469A-BCB0-51F33E062B6F
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 625142414
Partitions will be aligned on 2048-sector boundaries
Total free space is 2669 sectors (1.3 MiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048          206847   100.0 MiB   EF00  EFI system partition
   2          206848          468991   128.0 MiB   0C01  Microsoft reserved ...
   3          468992       625141759   297.9 GiB   0700  Basic data partition

これはUEFIシステムのWindows 7だ。

GPTが使用されており、sdb1がUEFI(ESP)で、FAT32またはFAT16である。

sdb2がMSR。sdb3がシステムパーティションだ。

ESPが2048ブロックから開始している。これより手前部分、GPTとUEFIであればなくてもそれほど苦労せずに復元できると思うのだが、一応残しておいたほうがいいだろう。

0, 1, 2はサイズが小さいため、単純にddでもそれほど問題ないだろう。

システムパーティションはWindows XP以降はNTFSであり、ntfscloneが利用可能だ。

Windowsのシステム起動については、ディスクレイアウトがバックアップ時と同じである必要がある。そのため、手前ブロックを含めて復元して同じレイアウトを再現する必要がある。

ただし、ディスクサイズに対してはある程度柔軟だ。大きいシステムパーティションが最後方にあるため、パーティション3を拡張したり切り詰めたりした上でntfscloneで復元することができる。MBRからGPTへの変換などは、同時にはできないと考えたほうが良い。

転送を考える

バックアップ先は色々考えられるだろう。光学ディスクへのバックアップ、外付けハードディスク、あるいはNAS。

今回の場合は、高性能でストレージ容量の大きいメインデスクトップへと転送する、という方法をとる。デスクトップとラップトップを併用している人などにとっては一般的な要求だろう。

ここでLAN接続の強みが出る。ネットワーク経由で転送することができ、USB2で転送するよりもはるかに速い。
ちなみに、USB1.1を採用するような古いマシンの場合でも通用する方法であり、100BASE-TXあたりのネットワークに接続できる(外部カードを使う方法を含めて)のであれば、USBドライブよりもはるかに高速だ。汎用性がある。

簡単さで考えればSSH経由が最も楽だ。例えば

$ ssh desktop.local -- sh -c 'cat > backup-sdb1' < /dev/sdb1

ただし、SSHによる負荷を嫌うのであれば、rshのほうがいいだろう。しかし、あまり最近はrshを許容していないのではないだろうか。

ほかにもZshを使うとか、FTPを使うとか、FTPFSを使うとか、方法は様々なのだが、ものすごくシンプルに考えると標準入出力渡しが良い。

nc(netcat)だ。

しかし、単純にサーバーで

$ nc -l -p 10000 > sdb1.img

クライアントで

$ nc desktop.local 10000 < /dev/sdb2

としても、転送終了でコネクションを切ってくれない。切るだけであれば、クライアントを

$ nc -c desktop.local 10000 < /dev/sdb2

(GNU netcatの場合)とすれば良いのだが、この場合ちゃんと転送が終わってから切ってくれないため、ちゃんとファイルが転送されない(!)

これを解決するには、クライアント側はsocatを使うようにすれば良い。

$ socat STDIN tcp:desktop.local:10000 < /dev/sdb2

別にサーバー側もsocatを使って良い。OPENよりもSTDOUTのほうが安定している。

$ socat tcp-listen:10000 STDOUT > sdb2.img

実際にやってみる

まずはオフセット分をコピーする。サーバー側:

$ nc -l -p 10000 > sdb0.img.xz

クライアント側:

$ dd if=/dev/sdb bs=512 count=2048 | xz -zev -T 4 | socat STDIN tcp:desktop.local:10000

このケースにおいてはクライアントに余力があるため、サーバーに余計な負担をかけないよう、xzはクライアントで行っているが、古いマシンならばサーバー側でしたほうがいいだろう。:

$ nc -l -p 10000 | xz -zev -T 8 > sdb0.img.xz

クライアント側:

$ dd if=/dev/sdb bs=512 count=2048 | socat STDIN tcp:desktop.local:10000

sdb1以降は少し楽。

$ xz -zev -T 4 < /dev/sdb1 | socat STDIN tcp:desktop.local:10000

sdb2も同様。sdb3はntfsclone。

$ ntfsclone --save-image --output - /dev/sdb3 | socat STDIN tcp:desktop.local:10000

NTFSクローンでもサーバー側ですることは変わらない。

クライアント側socatの代替

Pythonもやろうとしたが、辛いので諦めた。
とはいえ、この4つに対応していれば、だいたいの環境でいけるだろう。

もちろん、LuaやJavaScriptやPythonが好きな人は、即席でプログラムを書いても構わない。

Zsh

TCP Function Systemを使う。

$ autoload -U tcp_open
$ tcp_open desktop.local 10000
$ tcp_send -c < /dev/sdb1
$ tcp_close

TCP shootのほうが簡単

$ zmodload zsh/net/tcp &&  autoload -U tcp_point
$ tcp_shoot desktop.local 10000 < /dev/sdb1

あるいはzsh/net/tcpを使っても良い

$ zmodload zsh/net/tcp
$ ztcp -d3 desktop.local 10000 && ( cat /dev/sdb1 >&3 ) && ztcp -c 3

Perl

$ perl -MIO::Socket::INET -e 'BEGIN { $sock = IO::Socket::INET->new(PeerAddr => "desktop.local", PeerPort => 10000, Proto => "tcp")} while (read(STDIN, $buf, 524288)) { print $sock $buf }' < /dev/sdb1

Ruby

$ ruby -rsocket -e 'sock = TCPSocket.open("desktop.local", 10000); buf = "x" * 524288' -e 'sock.write buf while STDIN.read(524288, buf)' < /dev/sdb1

Bash

意外すぎる必殺技。Zshよりも簡単だったりするので恐ろしい。

$ cat /dev/sdb1 > /dev/tcp/desktop.local/10000

ただし、Bashはクライアントのみ。

書き戻しのnetcat代替

バックアップを使って書き戻す場合、bashが使えない。

仮にリモート側で

$ xz -dv sda0.img.xz | socat STDIN tcp:laptop:10000

とした状況としよう。

netcat

まずは基本

$ nc -l -p 10000 > /dev/sdb

socat

こちらも簡単

$ socat tcp-listen:10000 STDOUT > /dev/sdb

Zsh

$ zmodload zsh/net/tcp &&  autoload -U tcp_point
$ tcp_point 10000 > /dev/sda

もしくは

$ zmodload zsh/net/tcp
$ ztcp -ld3 10000 && ztcp -ad4 3 && cat <&4 > /dev/sdb && ztcp -c 4

Perl

$ perl -MIO::Socket::INET -e 'BEGIN{$l=IO::Socket::INET->new(LocalPort=>10000,Proto=>"tcp",Listen=>5,ReuseAddr=>1); $l=$l->accept} while (read($l, $buf, 524288)) { print $buf }' > /dev/sdb

Ruby

$ ruby -rsocket -e 's = TCPServer.open(10000).accept' -e 'buf = "x" * 524288 ' -e 'write buf while s.read(524288, buf)' > /dev/sdb

Bash

Bashはリスナーになれないので、リモート側を反転させる必要がある。

$ xz -dv sda0.img.xz | socat STDIN tcp-listen:10000

これを受け取る。

$ cat /dev/tcp/desktop.local/10000 > /dev/sdb

もちろん、この「クライアントがソケットを読む」デザインを他に適用しても良いのだが、多分あまりメリットがない。

この方法の意味と価値

まず、簡単の意味を考えなくてはならない。

この記事の内容は、基本的な知識を網羅していることを前提としている。当然ながら、システムバックアップをするためにLive Linuxを使おう、と発想するくらいには。

だが、理解するのは非常に容易だ。パイプとリダイレクションという、Unixerとしては初歩知識さえあれば十分理解できるレベルだ。

一般の人には難しいだろうと思うが、エンジニアを名乗るのであればこの程度の話は通じて然るべきだと思う。少なくとも、私はそう期待している。

「理解する気はないが、模倣して実行したいだけ」という場合は、わざわざこんな複雑な手順を取る必要はないと考えるだろう。実際、他にもバックアップソリューションはあるのだし、たとえコンピュータの後ろに手を伸ばしてでも、あるいは2度コピーする手間が生じたとしても、そのほうが良いと考えるだろう。

実際、即時参照できるネットワークドライブがあるにも関わらず、USBメモリーで渡せと言う人を、私は身近に知っている。

だが、思ったようにいかなかった時はどうすればいいだろう?例えば、Clonezillaが小さなディスクに復元できると思ったのにいざとなったらできなかった時だ。

特定のソリューションに依存するのは非常にリスクが高い。できるだけ汎用性のある方法で、かつ自分が理解できるもののほうがいい。

実際問題として、Mondo Rescueを使うという選択肢を取った場合は、バックアップ先をどうやってマウントするか、という問題が生じてしまう。

また、圧縮したいが、クライアントはリソースが非常に少ないのでできない、という場合に、一旦ディスクに保存してからの圧縮という方法をとらずに(恐らく復元する時は伸長してからの復元ということになる)行いたいといった要求に対応するのも、標準入出力を使うのであれば非常に容易だ。

技術を使う、知識を使うとはこういうことではないか。

特定の目的のために知識が技術を身に着けたところで、できるのはそれだけだ。それは単に慣れた、覚えたというだけの話であり、「知」ではないと思うのだ。

Windowsをアップグレードする前に元に戻せるようにスナップショットを取っておきたい、というのはごく当たり前にある状況だ。そのための機材が揃わないということも。

原理、物事の仕組みを理解し、方法を考えられることは非常に重要だ。すべてお膳立てされ、整った環境でなければできないのか?それがスキルなのか?

最低限ひとつの方法を知っていて、それで目的が達成できるということは有意なことだ。しかしそれは、楽をしようとして知らず犠牲を払っているということでもある。

覚えたひとつの方法でうまくいかない時、あるいは適切でないとき、適切な別の方法が考えられるか?そこが知なのだろう。

では、すべての人がそうした知識を持たなくてはならないのだろうか?私は、否であると考える。人には無限の時間が与えられているわけではない。知の獲得を志すことを是としても、そこにコンピュータに対するものが含まれているとは限らない。

だから、私の仕事があるのだ。ここに知がある。そして、それは独占するものではなく、分け与えられるべきものだ。私も生きなければならないので、無償で、というわけにはいかないが。

だからこそ、用意した方法が通用しない、しかし知を蓄える労力を払うべきという判断ではない、というのであれば、その時にこそ私を頼ってほしいのだ。

それだけの時間もお金もかけているのだ。それだけ真剣に努力を重ねてのものだ。

もちろん、自ら解決するための力をつけるために、私の手を借りてくれたって構わない。先人の肩を借りてその先までいくのは当然のことだ。このニュートンだって巨人の肩に乗るのだから。

FM2+KillerのLinuxセットアップ: 既存のManjaro Linux (Arch Linux系)環境をクローンする

あらまし

元々、メインのLinux環境はFM2+Killerだったが、Z400に移行したことで位置づけが変わった。

メインの作業環境はZ400上にあり、既に構築済みだ。
FM2+Killerは冗長環境として、同様にLinuxを構築し、いつでも使えるようにしておくのが望ましい。

可能であれば、ハードウェアも全く同じものを用意すれば、簡単にクローンできる。
だが、Z400とFM2+Killerでは色々と違いがある。特に大きいのはグラフィックスカードの種類と、ディスプレイの数の違いだ。

さらに、FM2+Killerは

  • 障害時にメインと同様に使うことができるアカウント
  • サテライト的に使う一時作業用アカウント
  • 彼女がうちにいる時に作業に使うアカウント

の3つをセットアップする

インストール

私が使っているのはManjaro Linuxである。当然ながら同じManjaro Linuxを導入する。

既にManjaro Linux 15.09がリリースされているが、15.09はUEFIにインストールすることができないバグがある。特に

0.8.13 XFceをインストールし、アップグレードする。
違いはあとから埋める。

通常どおり、yaourt -Syuuでアップグレードした上で、新しいカーネル(4.1)を導入する。4.2でないのは、AMDユーザーに勧めない、ということなので。

インストーラで作るユーザーはメイン環境と同じユーザーにすること。
でないと、UID/GIDの違いでディスクを差し替えただけでは動かなくなる。

パッケージを揃える

メイン環境と同じパッケージが入っていればもし作業環境を作るにしても、少ない手間で可能だ。

Arch Linuxにパッケージを揃える機能はなさそうだったので、スクリプトを書いた。

#!/usr/bin/ruby
# -*- mode: ruby; coding: UTF-8 -*-


PLIST = File.exist?(".previous_autoyaourt_target_list") ? File.open(".previous_autoyaourt_target_list", "r").each.map {|i| i.strip[/^\S+/] } : nil 
TLIST = ARGF.each.map {|i| i.strip[/^\S+/] }
EXCLUDE = [ /nvidia/i, /^linux/, /^libva/, /catalyst/, /maxthon/, /^opencl/, /^ocl-/, /^libcl/ ]

File.open(".previous_autoyaourt_target_list", "w") {|i| i.puts(TLIST) }


loop do
  clist = IO.popen("pacman -Q", "r") { |io| io.each.map {|i| i.strip[/^\S+/] } }
  to_install = ( TLIST - clist ).delete_if {|i| EXCLUDE.any? {|r| r === i } }
  
  if to_install.empty?
    break
  end
  
  system("yaourt", "-S", *to_install[0, 15]) or abort "Yaourt failed!!!"
end

if PLIST && ! ( dlist = PLIST - TLIST ).empty?
  
  puts "*******************************************"
  puts "CLEAN UP PHASE"
  puts "*******************************************"
  
  
  
  system("yaourt", "-R", *dlist)
end

Manjaro Linuxは標準でyaourtは入れているが、Rubyが入っていない。
Rubyをインストールし、元となる環境で

pacman -Q > target-paclist

のようにした上でこれを持ってくる必要がある。そして

ruby yaourtsyncer.rb target-paclist

のようにするわけだ。

--noconfirmオプションはつけていない。問題が発生することがあり、またひとつずつ確認したほうが安全だからだ。

別にパッケージデータそのものを持ってきて(/var/cache/pacman/pkg/以下にある)インストールする方法もあるのだが、今回は1台クローンするだけの話だし、健康にビルドしていく。

事前にsudoのタイムアウトを外しておいたほうが良いが、visudoはあるのにviがない。先にviをインストールしておく。gvimをインストールしてリンクしておいても構わない。

もし手間を短縮するなら--noconfirmをつけてもいいのだが、いずれにせよ手をかけねばならない状態になったり、失敗した時にいちいち外したりしないといけなかったりする。

これでうまくいかないのが、旧リポジトリからいれているパッケージ、失われたパッケージ、壊れてしまったパッケージだ。

旧リポジトリから入れているのがxcursor-lcd-*、なくなってしまったのがjoyutils、などなど。
これらはmakepkgでビルドしたものに関してはパッケージを持っているだろうし、pacmanやyaourtで入れたのなら/var/cache/pacman/pkg以下にある。これを導入する。

グラフィックスドライバに関連するものについては個別の環境によるため、除外している。
カーネルは元々yaourtでいれるようにはなっていないので、これも除外。

ちなみに、fcitx-mozc-utやJava関連、inkscape-gtk3-bzrはビルドが非常に長いので流用したほうが良い。

ユーザー

残り2つのユーザーを追加し、設定する。

これらユーザーはこのコンピュータのローカルなものなので、好きなように設定して構わない。

サテライト環境ではsshfsを用いてマウントすることで、UID/GIDの差を吸収できる。

冗長環境

当然ながらユーザーの設定も、元のPCに合わせたものにしたい。

私の環境ではホームディレクトリの下のディレクトリにbtrfsサブボリュームがマウントされており、また別のディレクトリがEncFSのマウントポイントにもなっている。

単純に持ってきてしまうと、btrfsの膨大なデータをコピーしてしまうし(5TB近い)、EncFSのデータを復号化したまま持ってきてしまう。

これを避けるため、マスター環境のサブボリュームマウントポイントを同様に作り、sshfsでマウントし、

$ rm -rf (^(.mountpoint))(#qD)

して

$ rsync -avH -x --exclude=/.cache -e ssh "$HOSTNAME":./ ~/

これはなるべく、コンソール上で作業したほうがよい。rootでは、FUSEを使うため支障がある。

~/.cacheは特に同期する必要がなく、同期するととても時間がかかるので省略。--delete-afterなどはとても危険。

私の場合、~/.sshがシンボリックリンクなので失敗する。
この状態で削除とリンクを同時に行えばよい。リンク自体は別にsshfsを解除していても-fオプションで可能。

なお、これで気がついたのだが、こうしてまっさらにしてコピーしても、Cinnamonの壁紙とテーマはなぜか反映されず、ローカルの設定が保たれる。ローカルで設定した後に吹き飛ばしてもだ。

なお、このテストの家庭で全データを吹き飛ばしかけた。
幸い気づくのが早く、重要なデータの損失はなかった。

rsyncのファイルシステムをこえない-xオプションは非常に便利。

なお、autostart関連は除外したかったのだが、うまくいかなかった。
これは--excludeの書き方の問題。

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

sshの問題

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

send/receiveはよく止まる

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

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

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

ZFSが遅い

先日、ZFS+rsyncでやっていくことにした、と報告したが、挫折した。

最初の7時間で300GBの進捗。まぁ、これはいい。だが、いくらなんでもそのあと3時間で40GBの進捗はない。それはひどすぎる。

これでは実用にならないのでBtrfsに変更。結果としては出だしで18倍以上の差となり、15時間ほどで2TBの転送を完了した。

ちなみに、サーバーはProliant MicoroServerで、メモリは4GB。Swapは使われていなかった。

不安定なZFSの劣化コピーとみなされがちなBtrfsだが、こうしてみるとZFSを凌ぐ部分が結構みつかる。特に、ZFS on Linuxと比較するとかなりだ。

Btrfsは既にかなり安定しているし、また速度的なアドバンテージがある。また、自動でストライピングするが、ミラーの場合でも1台単位で追加でき、1+0でもリバランスできる。これはZFSにはない特徴だ。

そして新たに知ったこととして、BtrfsにもZFS同様のSend/Recieveがあるということだ。Btrfs、かなり良い。

ストレージワーク:btrfs+EncFS / dm-crypt+ZFSでのリモートミラー

Btrfs上にEncFSを構成したマスターから、dm-crypt上にZFSを構築したスレーブへとミラーする、しかもそれらのホストはシャットダウンされる。これはかなり厳しい条件だった。

やはりシャットダウンされるためにHA(高可用)システムは使えない。シャットダウンする時点で障害発生とみなされるし、切り離された状態で単独でスタートアップして動けない。

さらに、EncFSはrootであってもアクセスを許さないため、非常にセキュアではあるが、GlusterFS GeoReplicationも使えないなど障害になった。

やはり無理な要求である、というのは

LinuxQuestionで聞いてみても
明らかになるだけだった。だが、ここでrsyncが大規模システムに耐えるということが分かったため、rsync(1)at(1)でいこうと決意を固めることができた。

rsync+sshはごくごく単純だ。

rsync -e ssh fromdir user@host:destdir

でいける。だが、まずはZeroconfでアクセスしたい。

Manjaroで/etc/nsswitch.confmdns_minimalを指定しても解決できない。これでホスト名を解決しようと思うと、nss-mdnsパッケージをインストールし、avahi-daemonを動かさなくてはいけない。

さらに、CentOS側が受け入れてくれない。これは、NICがひとつだとそのNICをpublicなゾーンのインターフェイスとみなすが、Avahiはhomeインターフェイスにしか許されていない。そのため、firewall-cmd --add-service=avahi --zone=public --permanentとしてAvahi-daemonへのアクセスを透過する。

これでZeroconfでのアクセスに成功、.localのホスト名でsshアクセスできる。ssh-keygenでパスフレーズなしのキーを作り、アクセスしようとする。ところが、ssh-copy-idしようとした段階でToo many authentication failures for …となり、sshアクセスできない。これは、sshの管理下にある鍵が多すぎる場合に生じるようだ。その数はsshdが登録、管理している鍵とファイルとして~/.ssh以下にある鍵の総数。とりあえずの方法としては、鍵ファイルがあるものだけを鍵として扱うため、~/.ssh/config

IdentitiesOnly yes

と書くと改善される。ただし、ファイル自体が多くなるとこれでもダメだろう。

これで鍵によるアクセスまでできるようになった。

鍵による実際のアクセスの前に、atとrsyncについて確認する。rsyncについては前述の通りで大丈夫。atは

$ <kbd>at now + 1 minutes &lt;&lt;&lt; ‘zsh -c "notify-send \"$(id)\""'</kbd>

すると自身のuidになっているので、atを実行したユーザーで実行されることが分かる。EncFSに対してアクセスするためにはこれは絶対条件だ。

そしてこのままrsyncするとうまくいく。だが、パスフレーズなしで自由にアクセスできる鍵というのは危険だ。

コマンドで制限すべきなのだが、rsyncのコマンドを受ける側がどうなっているのか、というのは非常にわかりにくい。rsync -vvv -au --delete-after -e ssh from destして、パスワードの前に表示されたconnectionの中からrsyncより前を削り、互いにvを削って設定する。

ただし、このままでは外部からファイルを消されるリスクがあるため、ホストも限定したほうがいいかもしれない。ただし、鍵がなければできないのだから、その時はバックアップ側は仕方がない、とも見れる。

今回の成果物も

GitHub
にて公開。

新しいストレージワーク(GlusterFS, AoE, lsyncd)

問題点

分かってはいたことだが、先日の件でbtrfs mirrorでは不十分だ。データの冗長化は次のようなことが考えられる。

  • ストレージの故障
  • ファイルシステムの故障
  • RAIDハードウェアの故障
  • 誤操作による喪失
  • コンピュータノードの故障
  • コンピュータノードの喪失(災害や盗難など)

基本的な対応は次の通り。

ストレージの故障
ミラーリングを行い、故障したストレージを置き換える
ファイルシステムの故障
異なるファイルシステム間でミラーするか、バックアップする。
RAIDハードウェアの故障
同一のRAIDハードウェアに再接続するか、バックアップによって復元する
誤操作
gitやpdumpfs、あるいはLogFSなどスナップショットの活用
ノードの故障
新規ノードにストレージを移設する。可用性が不要ならばデータの冗長化は不要
ノードの喪失
可用性の損失は避けられない。同一箇所に保管していると同時に損失する可能性は高いため、クラウドバックアップが有効

一般にノードの損失は稀なケースだと考えられる。だが、実際はそこそこ可能性はある。落雷によっても起こりうる。この場合のことを考えれば、データを諦められないのであればバックアップは必要だ。

これまでは、btrfsの2レッグミラーを採用していた。これはストレージの故障に対する冗長性・可用性を担保する。しかし、それ以外には対応できていない。特に怖いのは、未だexperimentalであるbtrfs自体の異常だ。ファイルシステムが壊れてしまえばミラーされていたものも含めてすべてのデータがアクセス不能になる。

少々複雑な話に聞こえるかもしれないが、btrfsは「壊れにくいファイルシステム」だ。日常的な読み書きで起こるビットエラーをbtrfsは発見し、ミラーリングを行っていれば訂正することすら可能だ。そのため、他のファイルシステムと比べ非常に壊れにくい。それは他のジャーナリングファイルシステムと比べてもだ。そのためた信頼性が高いと言われ、この点を買って私は採用している。一方で、btrfsはまだ完成度が低く、btrfs自体が壊れる挙動を示す可能性がある。そのため信頼性が低いとされている。

データが既に2TiBを超えている現状にあっては、バックアップやその復元も容易ではない。ちなみに、この次にハードルが上がるのは、ストレージ1台ではすまなくなる時だ。コストを度外視すれば、8TBということになる。

基本的にはバックアップというよりも、ミラーリングによって冗長性を持たせ、データが損失しないことを前提とする方向としている。今後も情報増加は進む予定だし、それに対応する構成としておかなくてはならないからだ。そして、データ量は一般的なシングルストレージで済む量に収まる見込みはない。ストレージ増加よりもデータ増加の方がはるかに速い。

現在の対応は次の通りだ。

  • btrfsによるジャーナリング、自動訂正によるビットエラー対策
  • btrfsミラーによる冗長化
  • btrfsによる柔軟なストレージ追加。1台単位でディスク容量を問わず追加し任意のボリュームを切り出せる
  • 必要な箇所をgitとすることで誤操作による損失の防止
  • 一部データのリモートgitへのclone

だが、先日はそのストレージを格納するコンピュータノードがダウンした。そのため、データ自体にアクセスできなくなった。現在でも最低限、4台のストレージを搭載しなければデータを使うことができない。Proliant Microserverは搭載ストレージ台数は4なので、システムドライブを含めるとそれを搭載することができない。

もちろん、データは損失していないのだからコンピュータノードを追加すれば良いのだが、その間仕事は完全に停止してしまう。やはりこの点も冗長性が必要だろう。

機能問題は別として、別のコンピュータがデータを触れれば良いのだ。コンピュータノード全体がダウンしても機能するようにすれば、1台のコンピュータが損失しても問題ない、ということになる。もっとも、実際に必要となるのは完全なクラウドバックアップだが、現在のところそれは月額で8万円程度が必要になるため、現実的でない。

GlusterFS

GlusterFSはペタバイト規模に対応するクラスタストレージ技術だ。ユーザースペースで動作するデーモンであり、大きなファイルの読み書きはネイティブなコードで行う。FUSEでマウントできるだけでなく、NFSやCIFSでもマウント可能。

分散ファイルシステムだがネイティブファイルシステムではなく、各コンピュータはマウントされた特定のディレクトリをGlusterFSデーモンによって公開する(blickと呼ぶ)。クライアントはblickを束ねてvolumeとしてマウントすることができる。中央サーバーがないのがGlusterFSの特徴だ。

CephやGFSと比べかなりお手軽に使えそうに見えるGlusterFSだが、実際には意外と厳しい。それは、blickの容量をみずにファイルベースで振り分けていくこと、GlusterFSのレプリケーションはblickを組みとして複製するものであることによる。このことから、実質的にはblickはすべて同一容量でなくてはならない。しかも、blickの追加はRAID化している数をunitとして行わなくてはいけない。例えばstriped replicated volumeだとしたら、4 blicksが追加単位となるのだ。ディスク単位のblickにしても4台のディスク、コンピュータ単位だと4台のコンピュータだ!

また、このような技術はノードの停止は「障害」であるため、HAの理屈に基づき稼働停止が許されなくなる。電気代で考えても厳しいし、デスクトップユースでの無停止はそもそも難しい。結構よく見えたのだが、GlusterFS適用はかなり難しいように見えた。

ただし、適用方法はある。それは、「そもそも2組のストレージにしてしまい、2 blicks GlusterFS volumeにする」という方法だ。例えばiSCSI+LVMなどで複数のコンピュータノードからなるひとつのファイルシステムを編成し、それをマウントし、それをblickにすれば良い。そうすればコンピュータを2組に分けることができ、片方の組のコンピュータが停止しても全体はダウンしない。ストレージ追加はLVMなどの単位で行えるためかなり柔軟だ。容量の問題は、単に合計容量が小さいほうの組を上限としてそれを越えないように運用する、という制約があるだけだ。

lsyncd

だが、やはりこのようなHA技術は少々重い。それであれば少なくともデスクトップは使う側にしてデスクトップにストレージをもたせるのはやめるべきだ。

任意に停止しゆるい同期を行えれば良い。つまり、「手動で同期サービスを開始・停止し、ファイル更新時に反映してくれれば良い」という考えだ。可用性については、短時間の停止なら十分許容できる。

原始的には定期的にrsyncを回す方法もあるが、せめてファイル更新を監視したい。別にそれをスクリプトにしてもいいのだが、ionotifyというLinuxの機能があるのだから、それを活かしたいもの。そこで「ファイル変更を監視して同期する」というものを探したところ(正確には「なんて名前だっけ」だった)、lsyncdが見つかった。

lsyncdはファイル同期デーモンだが、実際にはミラーリングを行う機能はなく、rsyncなどをバックエンドとして使う。つまり、lsysncdはionotifyによってファイル更新を受け取り、それをトリガーとしてrsyncなどを起動するデーモンだ。

デスクトップレベルではこれが順当なところではないだろうか。台数が増えるとオンオフも困難になるので普通にHAストレージでいいのかもしれないが。

台数が少ないうちは普通にイーサネットケーブルでつなぎ、それをまとめて2つのストレージを編成すればいいのだが、台数が増えてきた場合、それぞれキーになるノード(片方はデスクトップ)をシングルにつなぎ、それぞれがストレージ用ハブを持っておくと良いだろう。構成手順は次のとおり

  • デスクトップは現在btrfsミラー
  • キーサーバーAを接続し、ZFSでストレージを束ねる
  • キーサーバーAにrsyncで全データをバックアップ
  • デスクトップのbtrfsのミラーをやめて構成しなおす
  • キーサーバーAからrsyncでデスクトップにデータを戻す
  • デスクトップとキーサーバーAにGbEインターフェイスを追加する
  • 追加されたGbEインターフェイスをハブに接続する
  • ストレージサーバーノードをGbEでストレージ側ハブに接続する
  • ストレージサーバーノードのディスクをAoEを用いてそれぞれのキーノードのブロックデバイスとしてみえるようにする
  • 追加されたストレージサーバーノードのディスク(AoE)をbtrfs/ZFSノードに加える
  • btrfs/ZFSをリバランス

10GbEに置き換えるところまで考えればかなりの規模までこれでいけるように思う。ただし、グループノードのいずれかがダウンすると障害発生なので、「故障率」は当然あがる。これはグリッドシステムでは常に起こることだ。