FreeBSD 7.1 自宅サーバのバックアップ

さて、このサイトを提供している Atom330のべアボーンで作ってFreeBSD 7.1で動いている自宅サーバ。 先日、内蔵HDDと同じ型(日立 HDT721032SLA360、320GB)のディスクをUSB接続できるように必要なハードウェアを買ってきたのだが、 そこにバックアップを取る仕組みを設定したので、メモを残しておこう。

基本的な筋としては、UFS2 の snapshotを取って、それをUSBで接続したディスクのパーティションにrsyncでコピーするという方針。 で、いざディスクが壊れた際は、そのバックアップディスクをそのまま本体に突っ込めば動くという想定。

まず、内蔵ディスクはデバイスad4(SATA)、外付けのバックアップ用ディスクはSATA→USB2.0変換ケーブルで接続され、デバイスda0として見えている。それぞれディスク全体ad4s1とda0s1がFreeBSDでインストールされている。ちなみに「home%」の「home」はホスト名ね。

home% dmesg
...(略)...
atapci1: <Intel ICH7 SATA300 controller> port 0x30c8-0x30cf,0x30ec-0x30ef,0x30c0-0x30c7,0x30e8-0x30eb,0x30a0-0x30af irq 19 at device 31.2 on pci0
atapci1: [ITHREAD]
ata2: <ATA channel 0> on atapci1
ata2: [ITHREAD]
...(略)...
ad4: 305245MB <Hitachi HDT721032SLA360 ST2OA31B> at ata2-master SATA150
...(略)...
umass0: <vendor 0x05e3 USB Storage, class 0/0, rev 2.00/0.41, addr 2> on uhub4
da0 at umass-sim0 bus 0 target 0 lun 0
da0: <Hitachi HDT721032SLA360 0041> Fixed Direct Access SCSI-0 device 
da0: 40.000MB/s transfers
da0: 305245MB (625142448 512 byte sectors: 255H 63S/T 38913C)
...(略)...

まずはこのUSB経由で接続されたda0のFDISKスライスの作成とブートレコードの書き込みを行う。これはインストーラのsysinstall(8)から行うのが簡単。rootになって、

home# sysinstall

で、Configure→Fdiskすればディスク選択の画面となる。ここでda0を選択してOK。

sysinstall画面

で、コピー元のディスクと同様に「A」でディスク全体をFreeBSDスライスに設定し、「W」で書き込みを行う。「W」を押すと本当にやっていいの、という警告が出てくるのでここで「Yes」。

sysinstall画面

ブートレコードの選択は標準のBootMgrでよいだろう、ということでそのままOK。

sysinstall画面

次はBSDパーティション情報を作成。今後そのまま使うつもりのないディスクをsysinstall(8)からdisklabelを貼るのは今ひとつなので、元のディスクad4s1のdisklabel情報をそのままコピペでda0s1に貼ることにする。bsdlabel ad4s1でad4s1のdisklabel情報を得る。

home# bsdlabel ad4s1
# /dev/ad4s1:
8 partitions:
#        size   offset    fstype   [fsize bsize bps/cpg]
  a:  4194304        0    4.2BSD     2048 16384 28552 
  b: 16777216  4194304      swap                    
  c: 625137282        0    unused        0     0         # "raw" part, don't edit
  d:  4194304 20971520    4.2BSD     2048 16384 28552 
  e: 67108864 25165824    4.2BSD     2048 16384 28552 
  f: 268435456 92274688    4.2BSD     2048 16384 28552 
  g: 264427138 360710144    4.2BSD     2048 16384 28552 

もちろん、この表示はディスクのサイズやパーティションの切り方に応じて異なるので、環境によって全く異なるだろう。この強調部分が元ディスクのdisklabel情報なので、

home# bsdlabel -e da0s1

として、disklabelの編集モードに移行する。最初はディスク全体を示す「c:」の行しかないが、適切な場所でvi(1)の挿入モードに入り、この画面に上の強調部分をコピペすればよい。

disklabel編集

で、エディタを終了するとdisklabelが書き込まれるので、同じ結果になっているかbsdlabel da0s1 で確認しよう。

home# bsdlabel da0s1
# /dev/da0s1:
8 partitions:
#        size   offset    fstype   [fsize bsize bps/cpg]
  a:  4194304        0    4.2BSD     2048 16384 28552 
  b: 16777216  4194304      swap                    
  c: 625137282        0    unused        0     0         # "raw" part, don't edit
  d:  4194304 20971520    4.2BSD     2048 16384 28552 
  e: 67108864 25165824    4.2BSD     2048 16384 28552 
  f: 268435456 92274688    4.2BSD     2048 16384 28552 
  g: 264427138 360710144    4.2BSD     2048 16384 28552 

さて、私のサーバ環境では/etc/fstabでは当初、次のようになっている。

# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/ad4s1b             none            swap    sw              0       0
/dev/ad4s1a             /               ufs     rw              1       1
/dev/ad4s1f             /data           ufs     rw              2       2
/dev/ad4s1d             /tmp            ufs     rw              2       2
/dev/ad4s1e             /usr            ufs     rw              2       2
/dev/ad4s1g             /var            ufs     rw              2       2
/dev/acd0               /cdrom          cd9660  ro,noauto       0       0

ここに、da0s1の各BSDパーティションをマウントする場所を用意する。ただし、バックアップはパーティション単位で取得するため、元のディスクと同じ階層を作るのではなく、/backupというディレクトリに各パーティションのマウントポイントを作り、該当するda0s1上のディスクをnewfs(8)でフォーマットする。

home# mkdir /backup
home# mkdir /backup/root
home# mkdir /backup/data
home# mkdir /backup/tmp
home# mkdir /backup/usr
home# mkdir /backup/var
home# newfs da0s1a
/dev/da0s1a: 2048.0MB (4194304 sectors) block size 16384, fragment size 2048
	using 12 cylinder groups of 183.77MB, 11761 blks, 23552 inodes.
super-block backups (for fsck -b #) at:
 160, 376512, 752864, 1129216, 1505568, 1881920, 2258272, 2634624, 3010976,
 3387328, 3763680, 4140032
home# newfs da0s1f
...(略)...
home# newfs da0s1d
...(略)...
home# newfs da0s1e
...(略)...
home# newfs da0s1g
...(略)...

そして、/etc/fstabに、以下のエントリを追加しよう(当然ながら、この辺りの操作は元ディスクのパーティションをどのように切っているかに応じて全く異なる)。noautoオプションを忘れずに。

/dev/da0s1a             /backup/root    ufs     rw,noauto       3       3
/dev/da0s1f             /backup/data    ufs     rw,noauto       3       3
/dev/da0s1d             /backup/tmp     ufs     rw,noauto       3       3
/dev/da0s1e             /backup/usr     ufs     rw,noauto       3       3
/dev/da0s1g             /backup/var     ufs     rw,noauto       3       3

バックアップの作業用(スナップショットのマウントポイント)も作成しておく。 なんか冗長なようだが、こういう構成にしないとうまくrsyncが取れない。

home# mkdir /backup2
home# mkdir /backup2/data
home# mkdir /backup2/tmp
home# mkdir /backup2/usr
home# mkdir /backup2/var

ルートファイルシステムは、softupdatesが効いていないのでsnapshotが取れない。そのため、ルートファイルシステムに存在しているマウントポイントは作ってあげる必要がある。/backup/tmpのパーミッションを1777にしてあげる事をお忘れなく(/backup2以下のマウントポイントはrsyncで同期できるので作る必要はない)。

home# mount /backup/root
home# mkdir /backup/root/backup
home# mkdir /backup/root/backup/root
home# mkdir /backup/root/backup/data
home# mkdir /backup/root/backup/tmp
home# mkdir /backup/root/backup/usr
home# mkdir /backup/root/backup/var
home# mkdir /backup/root/data
home# mkdir /backup/root/dev
home# mkdir /backup/root/tmp
home# mkdir /backup/root/usr
home# mkdir /backup/root/var

あとは、こんな感じのPerlスクリプトをroot権限で走らせるだけ(実行にはports/net/rsyncが必要)。 別にcronで取りたいわけではなく、時々取りたくなった時にバックアップを取る感じで。 Coppermine以外のコンテンツ自体はMacbookの方に置いてあるので、 わざわざしょっちゅうバックアップを取るまでもない…。

バックアップ対象の設定は、スクリプト中の以下の部分で行う。 それぞれ、

  • $rootfsがルートファイルシステムのバックアップをマウントする位置
  • $snapfsbackbaseがその他のファイルシステムのバックアップをマウントするベース位置 (つまり、ここが/backupであれば、/usrのバックアップは/backup/usrにマウントして取得するということ)
  • $snapfssrcbaseがsnapshotをマウントするベース位置 (つまり、ここが/backup2であれば、/usrのsnapshotは/backup2/usrにマウントしてそこからrsyncするということ)
  • @snapfslistがスナップショットでバックアップを取得するファイルシステムのリスト
  • @excluderootlistがスナップショットが取れないルートファイルシステムにおいて、 rsyncの対象外とする必要のあるディレクトリ・ファイルのリスト

を示している。

my $rootfs = '/backup/root';
my $snapfsbackbase = '/backup';
my $snapfssrcbase = '/backup2';
my @snapfslist = ('/tmp', '/var', '/usr', '/data');
my @excluderootlist = ('.snap', 'backup', 'dev', 'tmp', 'var', 'usr', 'data');

肝心のsnapshotからのバックアップ取得を行っている部分は、このあたりね。 多分、こういうスクリプトは自分で書いた方がいいと思うので、 あくまでご参考までに…(あと、きれいなPerlスクリプトを書くつもりは毛頭なく、 動けばいいや的ノリで作ってますのでそこんとこよろしく)。

    print STDERR "Creating snapshot for $i\n";
    psystem("$mksnap_ffs $i $snapimage");
    psystem("$mdconfig -a -t vnode -o readonly -f $snapimage -u $mdnum");
    psystem("$mount -o ro /dev/md$mdnum $snapfssrc");
    print STDERR "Mounted snapshot for $i on $snapfssrc\n";
    my $backdir = "$snapfsbackbase$i";
    open my $excludefile, "> $exclude" or die "Cannot open exlucde file";
    print $excludefile <<EOT;
- $i/.snap
EOT
    open my $find, "cd $snapfssrcbase; $find $i -type b -or -type c -or -type p -or -type s |" or die "cannot open find(1)";
    while (<$find>) {
        chomp;
        print $excludefile "- /$_\n";
    }
    close $find;
    close $excludefile;
    print STDERR "Syncing $snapfssrc to $backdir\n";
    psystem("cd / ; $rsync -avH --delete --numeric-ids --filter='. $exclude' $snapfssrc $snapfsbackbase");
    print STDERR "Removing snapshot for $i\n";
    psystem("$umount $snapfssrc");
    psystem("$mdconfig -d -u $mdnum");
    psystem("rm -f $snapimage");

あと、ルートディレクトリ以外にファイルシステムをマウントしている場合 (/usr/localや/var/logを別パーティションにしている場合) などもうまく動かないと思うのでその辺りは自分でなんとかしてね。

ちなみに、psystem()はこれだけ。

sub psystem ($) {
    my $cmd = shift;
    print STDERR "$cmd\n";
    return system $cmd;
}

ということで、このスクリプトを走らせると、最初の一回はフルバックアップ、次回からは差分バックアップがUSBのディスクに取られて行く事になります。cronで回したければ、print STDERRやrsyncの-vオプションなどを適宜モードをわけて表示を抑制すればOKですね。

home# ./backup.pl
mounting filesystems
/sbin/mount /backup/root
/sbin/mount /backup/tmp
/sbin/mount /backup/var
/sbin/mount /backup/usr
/sbin/mount /backup/data
mounted!
backup root filesystem
cd / ; /usr/local/bin/rsync -avH --delete --numeric-ids --filter='. /tmp/backup-pl.13932' backup2 bin boot cdrom compat COPYRIGHT dist entropy etc home lib libexec media mnt proc rescue root sbin sys .cshrc .profile  /backup/root
sending incremental file list
root/
root/.history
...(略)...
sent 53984 bytes  received 167 bytes  36100.67 bytes/sec
total size is 723522487  speedup is 13361.20
Creating snapshot for /tmp
/sbin/mksnap_ffs /tmp /tmp/.snap/snapshot
/sbin/mdconfig -a -t vnode -o readonly -f /tmp/.snap/snapshot -u 8
/sbin/mount -o ro /dev/md8 /backup2/tmp
Mounted snapshot for /tmp on /backup2/tmp
Syncing /backup2/tmp to /backup/tmp
cd / ; /usr/local/bin/rsync -avH --delete --numeric-ids --filter='. /tmp/backup-pl.13932' /backup2/tmp /backup
sending incremental file list
tmp/
tmp/.spamassassin9005ZwmDjltmp
...(略)...
sent 3567771 bytes  received 7450 bytes  90511.92 bytes/sec
total size is 16232290094  speedup is 4540.22
Removing snapshot for /data
/sbin/umount /backup2/data
/sbin/mdconfig -d -u 8
rm -f /data/.snap/snapshot
/sbin/umount /backup/tmp
/sbin/umount /backup/var
/sbin/umount /backup/usr
/sbin/umount /backup/data
/sbin/umount /backup/root
home#

という感じでバックアップが取れます。このまま入れ替えて起動は…するんじゃないかな。すると思うよ。まちょっと覚悟はしとけ(笑)。

一応、ちょっとだけ確認すると…。

home# mount /backup/root
home# mount /dev/da0s1f /backup/root/data
home# mount /dev/da0s1d /backup/root/tmp
home# mount /dev/da0s1e /backup/root/usr
home# mount /dev/da0s1g /backup/root/var
home# df
Filesystem  1K-blocks     Used     Avail Capacity  Mounted on
/dev/ad4s1a   2026030   268400   1595548    14%    /
devfs               1        1         0   100%    /dev
/dev/ad4s1f 129990756 16094640 103496856    13%    /data
/dev/ad4s1d   2026030     2338   1861610     0%    /tmp
/dev/ad4s1e  32494668  6516220  23378876    22%    /usr
/dev/ad4s1g 128051716  3529748 114277832     3%    /var
devfs               1        1         0   100%    /var/named/dev
/dev/da0s1a   2026030   268410   1595538    14%    /backup/root
/dev/da0s1f 129990756 16094672 103496824    13%    /backup/root/data
/dev/da0s1d   2026030     2340   1861608     0%    /backup/root/tmp
/dev/da0s1e  32494668  6515770  23379326    22%    /backup/root/usr
/dev/da0s1g 128051716  3530950 114276630     3%    /backup/root/var
home# ls -l /backup/root
total 68
-rw-r--r--   2 root  wheel      793 12 17 13:29 .cshrc
-rw-r--r--   2 root  wheel      260 12 17 13:29 .profile
drwxrwxr-x   2 root  operator   512  1 26 09:33 .snap
-r--r--r--   1 root  wheel     6197 12 17 13:31 COPYRIGHT
...(略)...
drwxrwxrwt  13 root  wheel     1024  1 26 09:35 tmp
drwxr-xr-x  17 root  wheel      512 12 28 11:00 usr
drwxr-xr-x  24 root  wheel      512  1 24 22:36 var
home# ls -l /backup/root/var
total 44
drwxrwxr-x   2 root    operator   512  1 26 09:35 .snap
drwxr-xr-x   2 root    wheel      512 10 13 10:58 account
drwxr-xr-x   4 root    wheel      512 10 13 13:45 at
...(略)...
drwxrwxrwt   5 root    wheel      512  1 26 09:11 tmp
drwxr-xr-x   2 root    wheel      512  1 11 23:10 yp

…どうやら大丈夫そうだね。 /backup/root/tmpのパーミッションもちゃんとrwxrwxrwtになっているし、 dfの結果も誤差の範囲で一緒だし、 階層もちゃんと作られている。 正確に言うとchflags(1)が再現されていないんだけど、 これはディスク交換後に「make buildworld buildkernel installkernel installworld」をやり直せば元に戻るね。

今、確認するために仮にマウントした部分は元に戻しておきましょう。

home# umount /backup/root/data
home# umount /backup/root/tmp
home# umount /backup/root/usr
home# umount /backup/root/var
home# umount /backup/root

てなわけで、バックアップ設定、とりあえず一段落。

…こういうのって自動化するツールを作ったら需要あるかね? っていうか既にある?