「今日はここまでにしましょう」とAIに言われた完璧なバックアップは、たぶん無い
手順書をなぞって確認するだけの半日、のはずだった。終わったのは夜7時半で、コミットは20本を超えていた。5分ごとに静かに失敗していた同期、macOSが毎晩出す消せない警告、デモ機がCLONEを上書きしかけた事故。ついでにUSBケーブルが1本ポンコツだったせいで、新機能まで一つできた。そして最後に、疲れを知らないはずの相手から「今日はここまでにしましょう」と言われたのである。
朝の予定はこうだった。
「レプリケーションの再設定を、手順書どおりにやって、動くことを確かめる」。
それだけである。前日までに MySQL のレプリケーションは組んであって、82万行が MAIN と CLONE で一致することも確認済みだった。今日は手順書をなぞり、画面を撮り、マニュアルを仕上げる。半日で終わる。終わるはずだった。
終わったのは夜の7時半で、コミットは20本を超えていた。
「動いている」と「動いているように見える」
朝いちばん、保守コンソールのダッシュボードに出ていたのはこれだけだ。
バックアップcron が未登録
CLONE のバックアップが登録されていない。登録すればいい。5分で終わる話である——のはずが、追いかけたら判定の側が間違っていた。ngw-backup という文字列で crontab を検索していたので、ウォッチドッグの行に混じっているディレクトリ名(/home/administrator/ngw-backup/ngw-watchdog.sh)にも当たっていたのだ。登録されているのに未登録と出る。あるいはその逆。
直した。ついでに、その隣にあった警告も見た。
reachable CRIT — clone がダウンの可能性
CLONE は元気に動いている。ちゃんと応答もする。なのに MAIN からは「落ちている」と見えている。理由は SSH のホスト鍵だった。CLONE のイメージを作り直したときに鍵が変わり、accept-new は変わった鍵を受け入れてはくれないので、そこで弾かれる。しかも cron から動いているから、その警告文を誰も読まない。
この日は、こういうものが次から次に出てきたのである。ぞろぞろ、である。共通しているのは、どれも画面の上ではちゃんと正常に見えていることだった。
5分ごとに、静かに失敗していた
昼前、実データでやろうという話になった。MAIN で起案書を作り、添付ファイルをアップロードし、プログラムも少し直す。それが CLONE に届いているかを見る。素朴な確認である。
ここで気づいた。添付ファイルは、レプリケーションでは運ばれない。
MySQL のレプリケーションが同期するのは DB だけだ。起案書の PDF そのものは /var/www/html/ngw/ の下にあって、別の仕組み(ngw-sync.sh)が5分ごとに送っている。私は最初これを「未設定ですね」と報告してしまったのだが、実際には cron にちゃんと登録済みで、5分ごとに失敗し続けていたのだった。
[10:40:01] ==== sync start clone=clone-213 ====
[10:40:01] ✖ clone に SSH 到達不可。中断。
[10:40:01] ==== sync end result=error errors=1 ====
原因は、設定ファイルのたった1行である。
CLONE_SSH="ssh -o BatchMode=yes administrator@<CLONEのMac>"
↑ ポート指定がない
この構成では、Mac 本体の :22 は Mac 本体であり、その中の仮想マシンは :2222 にいる。ポートを書かないと Mac 本体の SSH を叩きにいく。そして現地の Mac は方針としてリモートログインを切ってあるので、当然どこにも届かない。届かないまま、5分ごとに、律儀に。
さらに厄介だったのは、到達確認は通るのに転送だけ失敗するという状態になったことだ。
✓ プリフライトOK (clone到達可 / 未昇格)
✖ webroot同期 失敗
到達確認は設定ファイルの値を見にいく。ところが実際の転送コマンドには別の宛先が直接書いてあった。定義が2箇所にあれば、いずれ食い違う。ならば必ず食い違う。症状と原因がずれているやつが、いちばん時間を食うのである。宛先の定義を1箇所に寄せて、これは終わった。
ついでに、レプリケーションを入れる前の名残で DB同期の cron が二重に登録されていたことも分かった。CLONE は読み取り専用なので、こちらも毎回きれいに失敗している。失敗し続けるジョブというのは、本当の異常を隠す。消した。
USBケーブルがポンコツだったので、新機能を作った
世代バックアップは外付けディスクに置く設計になっている。仮想マシンの中のディスクに置いていると、DBダンプ27世代と添付ファイルでいずれ容量が尽きるし、そのディスクが壊れた日には本体とバックアップを同時に失う。それでは「戻せるバックアップ」にならない。
ところが前日、外付けSSDへのコピーが異様に遅かった。30MB/s しか出ない。USB 2.0 相当である。ちんたら、ちんたら、である。調べたらケーブルの不良だった。ケーブルである。
「じゃあネットワーク経由でコピーできないか」という話になり、新しい Mac へ仮想マシンのイメージを LAN 越しに転送する機能を作った。59GB のバンドルを、外付けディスクを介さずに別の機体へ配れる。新しい機体を作るたびに SSD を持ち歩く必要がなくなった。
ケーブルが1本壊れたおかげで、キッティングの手順がひとつ楽になったのである。
もっとも、実装したその日にテストしたので、不具合が4つ続けて出た。
expectに渡した引数がシェルのクォート付きのまま ssh に渡り、宛先が'user@host'になった- macOS の ssh は
Password:と大文字で聞いてくるので、パターンにマッチせず、パスワードを送っていなかった - 秘密鍵が root しか読めない場所にあり、一般ユーザーで動くアプリからは読めなかった
- 「起動時に自動接続」が有効だったせいで、仮想マシン起動の瞬間に外付けディスクが Mac から外れ、参照していたファイルごと消えた
どれも実機でしか出ない類のものだ。全部潰した。潰したが、4番目のときは正直ちょっと嫌な汗が出た。
macOS が毎晩出す、消せない警告
外付けディスクを仮想マシンに直接渡すと、Mac を再起動するたびにこの一枚が出る。
接続したディスクは、このコンピュータで読み取れないディスクでした。
[取り出す] [初期化...] [無視]
macOS は ext4 を読めないのだから、仕様どおりの正しい動作である。正しいのだが、無人運用では誰もこのダイアログを閉じない。そして本当に見るべき警告が、この一枚の下に埋もれる。
消そうとして、4つ試した。
| 試したこと | 結果 |
|---|---|
tccutil reset AppleEvents で許可を出し直す | 変わらず |
| GPT パーティションにする(テーブル無しが原因かと思った) | 変わらず |
| AppleScript でダイアログの「無視」を自動クリック | ダイアログがどのプロセスにも属していなかった |
| macOS の設定で抑制 | Apple 公式に「DiskArbitration API でプログラムを書け」 |
3番目が面白かった。全プロセスを総当たりでウィンドウ列挙したのに、そのダイアログはどこにも出てこない。Accessibility API から見えない層で描かれているのである。操作できないものは、自動化もできない。 そりゃそうだ、と言われればそりゃそうなのだが、総当たりの結果として突きつけられると、なかなかのものがある。
5案目で解決した。ディスクをそのまま渡すのをやめたのである。
外付けSSD(APFS のまま。Mac が普通にマウントする)
└ ngw-backup.img ← このファイルを仮想マシンにディスクとして見せる
└ GPT + ext4 ← 仮想マシンの中では普通のディスク
macOS から見ればただのファイルだから警告は出ない。仮想マシンから見れば普通のディスクだから、ext4 もハードリンクも所有権もそのまま使える。副産物として、あの「起動時に自動接続」への依存も消えた。4つ試して駄目で、5つ目でやっと、である。
移行後の数字はきれいだった。
Total file size: 79,222,687,444 ← 論理 79.2GB
Total transferred file size: 23,111,169,109 ← 実体 23.1GB
speedup is 3.43
論理 79GB が実体 23GB に収まっている。rsync --link-dest で世代同士がハードリンクを共有しているからで、これが崩れると7世代が実サイズ分だけ膨らむ。ext4 でなければならない理由が、この3行に出ている。
デモ機が、CLONE を上書きしかけた
夕方、できたての新機能を使ってデモ機を作った。MAIN のイメージを LAN 越しにコピーして、別の Mac で起動する。うまくいった。転送も速い。よしよし、である。
起動したデモ機のダッシュボードを見て、血の気が引いた。
reachable OK 接続OK (clone)
デモ機が、本番の CLONE を見ていた。
当たり前である。MAIN の完全なコピーなのだから、crontab も設定ファイルもそっくりそのまま引き継いでいる。5分ごとに ngw-sync.sh が動き、その宛先は本番の CLONE を指している。しかもこの同期は真のミラー(RSYNC_DELETE=1)だ。デモ機で何か消せば、本番の CLONE からも消える。
すぐ止めた。
構造的な原因は、役割の選択肢が「NGWメイン」と「NGWクローン」の2つしか無かったことだ。デモ機はそのどちらでもない。メインを選べば同期の cron が入るし、相手機のIPを空欄にして保存しても既存の設定は消えない。空欄は「消す」ではなく「触らない」だったのである。
そこで 「単独稼働(デモ機・検証機)」という役割を足した。 これを選ぶと、相手機を指す設定を空で上書きし、定期実行は登録せず、既にある行があれば削除する。空欄にするだけでは足りない、というのが今日いちばんの学びだった。
完璧なバックアップは、たぶん無い
今日いじった仕組みを並べると、こうなる。
- MySQL レプリケーション(DB を常時同期)
ngw-sync.sh(添付ファイルとプログラムを5分ごとに同期)- 世代バックアップ(DBダンプ27世代 + 添付ファイル7世代)
- 外付けディスクへの退避
- 毎晩の無人再起動と、止まったら立て直すウォッチドッグ
これだけ重ねてある。それでも今日は穴が3つ見つかった。
- 添付ファイルの同期が5分ごとに失敗していた(DB だけが同期されていた)
- CLONE の監視が、鍵の食い違いで誤報を出していた
- デモ機が本番の CLONE を上書きしかけた
どれも「設定した時点では正しかった」ものである。鍵を作り直した、レプリケーションを入れた、機体を複製した——構成を変えたときに、古い前提だけが静かに取り残される。取り残されたことは、誰も教えてくれない。
完璧なバックアップというものは、たぶん無い。あるのは「壊れたときに気づけるバックアップ」と「気づけないバックアップ」の二種類だけである。今日やったことの大半は、バックアップを増やすことではなく、失敗が失敗として見えるようにすることだった。
- 失敗し続ける cron を消す(本当の異常が埋もれるから)
- 「未登録」を役割ごとに書き分ける(MAIN では未登録が正常、という項目がある)
- 起動途中の
Connectingを「停止しています」と表示しない(毎晩通る正常な状態だから) - ALERT の累計だけでなく直近の日時を出す(累計だけでは増えたのか分からないから)
地味な変更ばかりである。ばかりであるが、今日いちばん時間を使ったのはここだ。
AIとの協働作業での学び
夜7時を回ったころ、次の課題が見えていた。「コピーされたイメージが最初に起動したとき、自分で cron を消す仕組み」である。複製すると MAC アドレスが変わることは実測で分かっていたから、それを手がかりにすれば作れる。作れるものは作りたくなる。
作りましょうか、と聞いたら、こう返ってきた。
実装は明日以降にしませんか。cron を自動で消す仕組みは、誤検知すると正規の機体のバックアップが止まります。慎重に作りたいので、今日の疲れた頭で入れるべきではないと思います。
疲れているのは私であって、AI ではない。
疲れを知らないはずの相手から「今日の疲れた頭で入れるべきではない」と言われたのは、正直に言ってウケた。ウケたが、理屈は完全に正しいのである。バックアップを自動で止める仕組みを、判断力の落ちた時間に入れるのは、どう考えても筋が悪い。
今日はこの相手に何度か助けられ、何度か間違えられた。「データが入っていません」と断定されて COUNT(*) で反証したこともあったし、macOS の警告は消せますと言われて4回空振りもした。それでも、20本のコミットの大半は実機で踏んだ地雷から生まれたもので、その地雷を一緒に踏んでくれる相手がいたのは、やはり大きかった。AIには日記がないから、今日のこの疲れは私の側にしか残らないのだけれど。
明日の宿題は決まっている。
- コピー検知で cron を止める仕組み
- 単独稼働向けの完了メッセージ
- 現地でのIP切り替え(保守用)
……というわけで、半日で終わるはずの確認作業は11時間半かかり、確認するはずだった手順書は、結局まだ1行も書き直していないのである。マニュアル仕上げ、である。あれはどこへ行ったのか。
今日はここまでにしましょう。