自宅でルータとして動かしているLinuxマシンは、ファイルサーバとしても利用しています。本来ならば、境界ルータは分離すべきである事は分かっているのですが、自宅に何台もPCを24時間稼働させておくのはちょっと妻から許可がでなさそうです。
1台のホストに仮想マシンを構築して機能分離するのも良さそうだけれど、システムが壊れたときの復旧が大変そうで・・・結局、昔ながらの物理ホスト1台に全て詰め込み運用をしています。
ホストにはファイルサーバ用スペースとしてHDDを1台増設してあります。写真、動画、スキャンした書籍など、ロストすると痛いデータが増えてきた事もあり、定期的なバックアップを取得したいと感じるようになりました。
別な用途の為に一部データはクラウド上のオブジェクトストレージへ同期はしています。しかし、全てではないし、先日の京都大学でのデータロストの件でもわかるように、自前のバックアップ取得も大切な事なのです。
Contents
バックアップ方針
バックアップと言っても様々な方法があります。バックアップ方法を考える前に、必要と考える項目を書き出すのは大切なことです。
- 無停止でバックアップしたい
- バックアップ対象はデータ領域のみ
- 少なくとも2世代はバックアップを取得したい
- 投資額はなるべく低く抑えたい
無停止でバックアップを取得したい
個人用途なので、バックアップする毎にシステムを停止しても問題はないのですが、毎回となると面倒だし、飽きっぽいわたしは、すぐに作業をしなくなってしまいそうです。無停止で自動、若しくは簡単な操作でバックアップの取得作業を行いたいのです。
バックアップ対象はデータ領域のみ
Debianを長く使っているのでシステム部分は古い構成が一部に残り、利用していないパッケージや新しい仕組みに移行できていない部分がいくつもあります。
そんなこともあり、システム部分が破損した場合は、むしろ僥倖。最新の仕組みへ乗り換える良いチャンスなんて考えています。管理に使っている自作スクリプト等はgithubに上げてあるので、いつでも復旧できます。
なので、バックアップを取得するのは、データ領域のみで十分なのです。
少なくとも2世代はバックアップを取得したい
現在データ領域としてmountしているHDDが2TBなので、バックアップを2世代取ると4TBの領域が必要です(※ 使用率は50%程度なので、2TBでも十分ですが)。
一番お手軽なのはクラウドストレージへ送ってしまうことですが、4TBを確保すると月額いくらかかるのか考えると計算するまでもなく、却下です。
バックアップ取得は、ローカルに接続されていて、それなりに大容量なメデイアということになります。大分、方法が絞られてきました。
投資額はなるべく低く抑えたい
NAS, DASとかあれば使いたいけれど、個人向けではないし、消費電力も騒音も価格もすんごい。テープドライブなんてのも興味があるけれど、個人用途になり得るまで価格が下がりませんでしたネ。
結局、バックアップの保存先は、ホストに接続されたHDDを利用するのが個人向けではベストな選択な気がします。
前世紀末頃、アメリカでJazドライブという物が売れていると聞いた時にあり得ない!と驚いたのを覚えています。それが20年後に同じような事をしようとしている事に自分でも驚きを禁じ得ません。
ホットプラグ(ホットスワップ)すれば、HDDの寿命も電気代も節約できて家計にも優しいです。
部品を買い集める
ここまでの考察で形が見えてきました。ネット上で製品を物色すると、この2つを購入すれば良さげです。秋葉原まで行けばもっと安く買えるのかもしれませんが、運賃とコロナウイルスの事を考えると、Amazonでポチるのが楽ちんです。
5400rpmと遅めですがバックアップ用途です。問題ありません。8TBなので2TBづつ分割して、4世代バックアップが可能となります。
SATA3接続のHDDリムーバブルケースです。他の製品と違い、一部アルミ製でチョットだけ堅牢です。アルミのおかげで熱対策も万全だそうです。
それより大切なのが、付属の鍵でHDDへのON/OFFができること。
つまり、バックアップを行う時にONにして、終わったらOFFにしておけば、みんな幸せです。内蔵のFANが結構うるさいので。。。
組み立て
金曜にポチったら、翌日の土曜日に届きました。連休初日に届いて早速マシンに取り付けます。
東京ではコロナの陽性者数がとても増えているそうで、秋葉原へいくより結果的に良かったです。
HDDケースは5inchベイに取り付けるタイプで、ライトグレーとブラックがあったのですが、なぜかグレーを選んでしまいました。ケースは黒いのに(笑)
HDDトレイに購入したHDDをマウント(しっかりとネジ留め)してから、ケースに差し込みます。引っかかりも無くスムーズに出し入れできそうです。
とはいっても、ぼくの今回の用途では電源ON/OFFをするだけなので、抜き差しする事はほぼ無さそうですが。
古いケースを流用しているのでCore2Duo のシールが貼ってありますが中身はi5です。黒いケースで今回購入したHDDケースが浮いています。やっぱりブラックを買えば良かったかなぁ。
ドライブを差し込んで鍵を回すと、ドライブケースがロックされると同時にHDDの電源が入ります。OSのdmesgを確認するとHDDが /dev/sdc として認識されたのがわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[96889.766418] ata5: link is slow to respond, please be patient (ready=0) [96894.026405] ata5: COMRESET failed (errno=-16) [96897.498398] ata5: SATA link up 6.0 Gbps (SStatus 133 SControl 300) [96897.511820] ata5.00: ACPI cmd ef/10:06:00:00:00:00 (SET FEATURES) succeeded [96897.511826] ata5.00: ACPI cmd f5/00:00:00:00:00:00 (SECURITY FREEZE LOCK) filtered out [96897.511831] ata5.00: ACPI cmd b1/c1:00:00:00:00:00 (DEVICE CONFIGURATION OVERLAY) filtered out [96897.550256] ata5.00: ATA-10: ST8000DM004-2CX188, 0001, max UDMA/133 [96897.550261] ata5.00: 15628053168 sectors, multi 16: LBA48 NCQ (depth 32), AA [96897.586120] ata5.00: ACPI cmd ef/10:06:00:00:00:00 (SET FEATURES) succeeded [96897.586126] ata5.00: ACPI cmd f5/00:00:00:00:00:00 (SECURITY FREEZE LOCK) filtered out [96897.586130] ata5.00: ACPI cmd b1/c1:00:00:00:00:00 (DEVICE CONFIGURATION OVERLAY) filtered out [96897.624532] ata5.00: configured for UDMA/133 [96897.624768] scsi 4:0:0:0: Direct-Access ATA ST8000DM004-2CX1 0001 PQ: 0 ANSI: 5 [96897.625123] sd 4:0:0:0: [sdc] 15628053168 512-byte logical blocks: (8.00 TB/7.28 TiB) [96897.625128] sd 4:0:0:0: [sdc] 4096-byte physical blocks [96897.625138] sd 4:0:0:0: [sdc] Write Protect is off [96897.625141] sd 4:0:0:0: [sdc] Mode Sense: 00 3a 00 00 [96897.625155] sd 4:0:0:0: [sdc] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA [96897.625213] sd 4:0:0:0: Attached scsi generic sg2 type 0 [96897.674844] sd 4:0:0:0: [sdc] Attached SCSI disk |
diskを使っていない事を確認してから、鍵を回してHDDの電源を落とします。
1 2 3 4 5 6 7 8 9 |
[98790.806677] ata5: SATA link down (SStatus 0 SControl 300) [98796.269153] ata5: SATA link down (SStatus 0 SControl 300) [98801.645789] ata5: SATA link down (SStatus 0 SControl 300) [98801.645798] ata5.00: disabled [98801.645827] ata5.00: detaching (SCSI 4:0:0:0) [98801.647612] sd 4:0:0:0: [sdc] Synchronizing SCSI cache [98801.647644] sd 4:0:0:0: [sdc] Synchronize Cache(10) failed: Result: hostbyte=DID_BAD_TARGET driverbyte=DRIVER_OK [98801.647647] sd 4:0:0:0: [sdc] Stopping disk [98801.647654] sd 4:0:0:0: [sdc] Start/Stop Unit failed: Result: hostbyte=DID_BAD_TARGET driverbyte=DRIVER_OK |
無事にdetachされました。
バックアップの準備
2TB以上の容量を持つパーティションを作るには、手に馴染んだ fdisk コマンドではうまく扱えず、partedやGUIなgpartedを使います。
新しいHDDは /dev/sdc として認識されたとして、話を進めていきます。
まず、パーティションを切ります。
増設したHDDは8TBなので4つに分割し、2TBの領域を4つ作ります。パーティション数は4つなので、primary partitionでいきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# parted /dev/sdc Disk /dev/sdc: 7.28 TiB, 8001563222016 bytes, 15628053168 sectors Disk model: ST8000DM004-2CX1 Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 4096 bytes I/O size (minimum/optimal): 4096 bytes / 4096 bytes (parted) p Error: /dev/sdc: unrecognised disk label Model: ATA ST8000DM004-2CX1 (scsi) Disk /dev/sdc: 8002GB Sector size (logical/physical): 512B/4096B Partition Table: unknown Disk Flags: |
fdiskで一度パーティション作成をしてしまったので、disk labelにDOSが書き込まれてしまい、エラーが表示されています。
disk labelをgptにしてから、25%づつで領域を切っていきます。ファイルシステムはなんとなく xfs にしてみました。
1 2 3 4 5 |
(parted) mklabel gpt (parted) mkpart p1 xfs 0% 25% (parted) mkpart p2 xfs 25% 50% (parted) mkpart p3 xfs 50% 75% (parted) mkpart p4 xfs 75% 100% |
無事に切れたか確認
1 2 3 4 5 6 7 8 9 10 11 |
(parted) p Model: ATA ST8000DM004-2CX1 (scsi) Disk /dev/sdc: 8002GB Sector size (logical/physical): 512B/4096B Partition Table: gpt Disk Flags: Number Start End Size File system Name Flags 1 1049kB 2000GB 2000GB xfs p1 2 2000GB 4001GB 2000GB xfs p2 3 4001GB 6001GB 2000GB xfs p3 4 6001GB 8002GB 2000GB xfs p4 |
だいじょぶそうなので、mkfsします。
1 |
# for i in {1..4};do mkfs.xfs /dev/sdc${i};done |
これでHDD側の準備は完了です。
1 2 3 4 5 6 7 8 9 10 11 12 |
# smartctl -t long /dev/sdc smartctl 7.2 2020-12-30 r5155 [x86_64-linux-4.19.0-6-amd64] (local build) Copyright (C) 2002-20, Bruce Allen, Christian Franke, www.smartmontools.org === START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION === Sending command: "Execute SMART Extended self-test routine immediately in off-line mode". Drive command "Execute SMART Extended self-test routine immediately in off-line mode" successful. Testing has begun. Please wait 953 minutes for test to complete. Test will complete after Mon Jan 10 16:09:03 2022 JST Use smartctl -X to abort test. # |
使い始める前に、念のためしっかりとディスクチェックをかけると…953分!? 15時間くらいですか。ずいぶんとかかるんですねぇ。
バックアップスクリプトの設置
バックアップ作業そのものは、rsync を実行するだけですから大した作業ではありません。ただし定期的に実行するのならば半分自動にしておかなければ、ほぼ確実に忘れてしまうことでしょう。
設計方針としてこんな機能を考えてみました。
- cronから定期的に実行される。
- 実行時にHDDケースの電源が入っていない(バックアップ先のHDDが認識されていない)場合は「HDDの電源を入れろ」とアラートを発報し、6時間後に自分自身の実行jobを登録する。
- 6時間後に実行した際もHDDケースの電源が入っていなければ、1-1を再度実行する。
- バックアップ作業が完了後、完了を通知する。正常完了通知が届いた後、HDDの電源を切って完了。
- HDDの電源OFF以外でエラーが発生した場合は、失敗の通知を行い、jobの登録は行わない。
- rsyncは、–delete オプションを利用し効率化する。
- 通知はメールではなくて、LINEやSlack等のメッセンジャー経由でもいいかもしれない。
このスクリプトを仕込めば、毎月1日(cronで登録した日)の前にHDDの電源を入れ、完了通知が来たらHDDの電源をOFFにするだけです。
電源のONを忘れたらアラートメールが届くので、電源をONにしておけば、設定した時間後に再度実行されて完了を確認したら、HDDの電源をOFFにすればOKです。
素直にスクリプトを書いていけば、とくに危ない所はありませんが、rsyncの引数は注意深く確かめてテストする必要があります。
rsyncのオプション
今回はローカル同期なので、オプションは -a --delete
でOK。
1 |
rsync [オプション] SRC DEST |
SRCとDESTの書き方は、どのようにバックアップ(同期)を取りたいのかによります。
まず、こんな構造をしたディレクトリを `/tmp/bk-test/ 以下に同期したいとします。
1 2 3 4 5 6 7 8 |
$ tree /tmp/test01 /tmp/test01 ├── a │ ├── 1 │ └── 2 │ └── foo └── b └── bar |
(1) srcディレクトリの最期に / を付けない場合
/tmp/bk-test/以下に test01 というディレクトリが同期されます。
1 2 3 4 5 6 7 8 9 10 11 |
$ rsync -a /tmp/test01 /tmp/bk-test/ $ tree /tmp/bk-test /tmp/bk-test └── test01 ├── a │ ├── 1 │ └── 2 │ └── foo └── b └── bar |
(2) srcディレクトリの最期に / を付けた場合
前の(1)との違いは一目瞭然で、/tmp/bk-test/ 以下に test01の中身が同期されます。
1 2 3 4 5 6 7 8 9 10 |
$ rsync -a /tmp/test01/ /tmp/bk-test/ $ tree /tmp/bk-test /tmp/bk-test ├── a │ ├── 1 │ └── 2 │ └── foo └── b └── bar |
どちらの形でバックアップを取るのが良いのかは、使い方次第でしょう。
ディスクを丸ごとコピーしたいという要望なら(2)がベストマッチですが、1つのディスクにいくつかの領域を保存するのならば(1)です。
これを混ぜて大変な目にあってる人を幾人もみてきました。
–dry-run オプションが用意されているので、必ず実行前に確認しましょう。
rsyncにはniceを
バックアップは、なるべくサーバの使用率が低い時間帯を狙って行う物ですが、たまたま長引いてバックアップ実行の予定時間に何かの処理が走っている事があるかもしれません。
そんなときには、rsyncの優先度を下げてようにしておくと少しはましになるかもしれません。
1 2 3 4 5 6 7 |
do_backup(){ nice -n 19 rsync -a --delete /home/share /srv/backup/ [ $? -eq 0 ] || F_ERROR=$((F_ERROR | 2#1000)) nice -n 19 rsync -a --delete /home/share2 /srv/backup/ [ $? -eq 0 ] || F_ERROR=$((F_ERROR | 2#10000) } |
前回実行からの経過時間計算
人に優しいフォーマットである必要はありませんので、UNIX epoch で記録しておくと計算が楽です。UNIX epoch というのは、1970年01月01日00時00分(UTC)からの経過秒数です。
バックアップ処理が正常に終わった後の処理で、date +%s
の数値を記録しておきます。
1 2 3 4 5 6 7 8 |
post_backup(){ NOW=$(date +%s) rm -f ${LOG_DIR}/TIMESTAMP ${LOG_DIR}/BK_NUMBER echo "${NOW}" > ${LOG_DIR}/TIMESTAMP echo "${BK_NUMBER}" > ${LOG_DIR}/BK_NUMBER cp -f ${LOG_DIR}/{TIMESTAMP,BK_NUMBER} ${MOUNT_POINT}/ sync;sync;sync } |
そして、スクリプト実行の初期段階で、現在の数値と比較します。
1 2 3 4 5 6 7 8 9 10 11 12 |
check_timestamp(){ if [ -f ${LOG_DIR}/TIMESTAMP ];then LAST_BACKUP=$(cat ${LOG_DIR}/TIMESTAMP) fi diff=$(echo "${NOW} - ${LAST_BACKUP}" | bc) if [ ${diff} -lt $((60*60*24*25)) ];then # 25days F_ERROR=$((F_ERROR | 2#010)) return ${F_ERROR} fi return 0 } |
なお、UNIX epochから人に優しいフォーマットへの変換は簡単で、dateコマンド一発で変換できます。
1 2 |
$ date --date="@$(cat /var/log/m-backup/TIMESTAMP)" 2022年 1月 15日 土曜日 05:45:39 JST |
JOBを時間指定で登録
定期的にJOBを実行させる仕組みとして cron があります。それとは別に、一度だけ日時を指定してJOBを実行させる at コマンドがあります。
atコマンドなんていうと、昔のモデムの制御命令みたいですね。
このatコマンドですが、引数は割と柔軟なのに -f オプションでコマンドを指定した場合に起動されるshellは /bin/sh
という仕様です。redhat系ディストリビューションの /bin/sh
の実体は bash なのですが、debian系では dash になっているんですね。
1 2 3 |
$ at -M now + 1min -f /tmp/a.sh warning: commands will be executed using /bin/sh job 10 at Sun Jan 16 00:36:00 2022 |
以前、freebsdを使っていた頃は、shellスクリプトを書くときはbash依存ではなく、sh の機能だけで書くようにしていましたが、今ではすっかり堕落して bash じゃないとスクリプト書けない状態になってしまっています(いろいろと便利なので)
では、/bin/sh が bash ではない処理系で bash のスクリプトを at コマンドで job登録したい時はどうしたら良いのでしょうか。
DESCRIPTION
at and batch read commands from standard input or a specified file which are to be executed at a later time, using /bin/sh.
標準入力からコマンドを流し込めば良いそうです 🙂
早速、動作を検証してみます。bash依存のスクリプトを書いて…まずは -f オプションで実行してみます。うん。bash依存の命令は実行できていないのが分かります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ cat /tmp/c.sh #!/usr/bin/env bash date >> /tmp/c.log for i in {1..5};do echo $i;done >> /tmp/c.log $ at -M now + 1min -f /tmp/c.sh warning: commands will be executed using /bin/sh job 12 at Sun Jan 16 00:41:00 2022 $ cat /tmp/c.log 2022年 1月 16日 日曜日 00:41:00 JST {1..5} |
では、stdin から流し込んでみます。bash依存の命令が実行されているのが分かります。
1 2 3 4 5 6 7 8 9 10 11 |
$ echo 'bash /tmp/c.sh' |at -M now + 1min warning: commands will be executed using /bin/sh job 13 at Sun Jan 16 00:43:00 2022 $ cat /tmp/c.log 2022年 1月 16日 日曜日 00:43:00 JST 1 2 3 4 5 |
というわけで、バックアップスクリプト実行時にHDDの電源が入っていなかった場合のエラー処理はこのようになりました。${SELF}はスクリプトの最初に SELF=${0}として、自分自身のファイル名を保存してあります。
1 2 3 4 5 |
report_hdd_power_off(){ send_alert 'hdd-power-off' echo "'/bin/bash ${SELF}'" | at -M now + 6 hours exit 1 } |
cronに登録して完了
スクリプトが完成したらcronに登録し、バックアップ設定は完了です。
バックアップの条件(デバイス名、必要なパーティション数、世代管理等)は、個々の環境で異なると思います。バックアップ作業で事故はとても怖いのでスクリプトは公開しません。
しかし、電源のON/OFFができるHDDケースを使うことで、半自動のローカルバックアップが簡単にできるという事が分かってもらえたと思います。