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。
で、コピー元のディスクと同様に「A」でディスク全体をFreeBSDスライスに設定し、「W」で書き込みを行う。「W」を押すと本当にやっていいの、という警告が出てくるのでここで「Yes」。
ブートレコードの選択は標準のBootMgrでよいだろう、ということでそのままOK。
次は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が書き込まれるので、同じ結果になっているか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
てなわけで、バックアップ設定、とりあえず一段落。
…こういうのって自動化するツールを作ったら需要あるかね? っていうか既にある?