NGW開発BLOG
← 記事一覧へ
·#072

毎晩3時に、ひとりでに立ち直る無人運用でつまずいた5つの落とし穴

現地に技術者はいない。Mac mini の上で動く Linux を、毎晩決まった時刻に止めて、立ち上げ直す。仕組みは半日で書けた。そのあとが長かった。ポートの奪い合い、SSHホスト鍵の食い違い、macOSの許可。どれも動いているように見えて、ある日静かに止まる種類だった。5つ潰した。実測ダウンタイムは67秒。

現地に、直せる人はいない

NGW は自治体の閉じたネットワークの中で動く。サーバはマシン室の片隅のラックに置かれた Mac mini が2台。MAIN と CLONE。少し前に建てた Linux はその Mac mini の上に仮想マシンとして載り、NGW 本体はさらにその中で動く。Mac の上に Linux。Linux の中にグループウェア。三段構えだ。CLONE は MAIN の控えで、壊れた日に入れ替えて使う。

この構成には弱点がある。現地に、直せる人がいない。

夜中にメモリが詰まって重くなる。朝までそのままだ。だから毎晩決まった時刻に止めて、立ち上げ直す。落ちていたら気づいて起動し直す監視も置く。考え方は前から決まっていた。シェルスクリプトと launchd で動く形もできていた。

今回やったのは、それを 保守コンソール のボタンから設置できるようにすることだ。USB でキットを配る。テキストエディタで VM 名を書き換える。ターミナルで launchctl を叩く。この現地作業を、画面のボタン4つに畳む。

仕掛けは半日で書けた。本当の作業は、そのあとだった。

まず、呼び方を決めた

作業を始めてすぐ、話が噛み合わなくなった。私が「サーバを再起動して」と言う。Claude が返してくる。

それは Mac mini 本体のことですか、その中の仮想マシンのことですか。同じIPアドレスなので、指示のままだと逆をやります。

そのとおりだった。仮想マシンは自分の LAN アドレスを持たない。Mac 側のポート転送で外と繋がる。:22 は Mac 本体。:2222:80 は仮想マシン。同じ住所に二人が住んでいて、部屋番号でしか区別できない。 私の頭の中では区別がついていた。口に出した言葉には出ていなかった。

呼び方を5語に固定した。MacStudio(開発機)、MAIN の Mac 本体CLONE の Mac 本体、MAIN の VM、CLONE の VM。「母艦」「ゲスト」はやめた。画面表示も、コードのコメントも、ドキュメントも、この5語に揃えた。

保守コンソールには、いま設定しているのがどの機体かを、機種名つきで常に画面上部に出すようにした。

保守コンソールの「Mac設定」タブ。画面上部に「この画面の設定先: MAIN = Mac mini (Apple M1)」と表示され、その下に「この画面はLinuxではなく、それを載せている Mac 本体の設定です」という注意書き、無人運用ジョブ(再起動ジョブ 毎日03:00・ウォッチドッグ ログイン時+2分毎・停電ガード 常駐)の登録状態が並ぶ

親切のためではない。設定を別の機体に入れる事故を防ぐためだ。実際このあと、MacStudio のつもりで MAIN の設定を触りかけた。一度ではない。

呼び方を揃えるのは地味な作業だ。だが設計の一部だ。

落とし穴1:MAMPが動いていると、VMが起動すらしない

現地の Mac mini は、もともと MAMP で NGW を動かしていた機体だ。移行期間中は MAMP も残る。

MAMP の Apache は :80 :443 を使う。MySQL は :3306。仮想マシンのポート転送も同じポートを使う。同じポートを二つのプロセスが取り合う。

MAMP を起動したまま VM を起動した。

Could not set up host forwarding rule 'tcp::80-:80'

VM が起動しない。 データは壊れない。エラーもはっきり出る。これはマシな方だった。

問題はもう一方だ。監視処理は http://localhost/ の応答で VM の反応を見ている。MAMP が :80 で応答すると、VM が止まっていても「動いている」と判定する。 監視は何もしない。朝まで NGW は止まったまま。ログにも何も残らない。

確認用の URL を :8080 に変えた。仮想マシン専用に空けた転送ポートだ。MAMP は 8080 を使わない。応答がある=VM が動いているが確実に成り立つ。

コンソールに検査も足した。lsof でそのポートを握っているプロセスを見る。仮想マシン(qemu)か、Mac 側の別プロセス(httpd など)かを判定する。

✗ このポートは Mac 側の httpd が使っています(MAMP等)。VMが停止していてもこれが応答するため、監視がVMを起動できません

設置ボタンを押す前に、この判定で止まる。画面にも同じ注意書きを出した。

保守コンソールの設置手順。上部に「MAMP が :80 を握ったままだと、VMが停止していてもMAMPが応答してしまい、VMが起動しません。UTM の転送専用ポート(例 :8080)を使えば、応答=VMが起動している、と確実に判定できます」という注意書き。以下、①スクリプトを設置する ②前提設定(スリープ無効・専用ssh鍵の作成・FileVault・自動ログイン) ③無人再起動の有効化/解除(毎日の再起動時刻と停電ガード) ④動作確認、と手順が並ぶ

落とし穴2:22→22 の転送が、Mac 自身のSSHを奪う

ポート転送の設定に 22 → 22 という行があった。Mac の :22 を VM の :22 に繋ぐ設定だ。

これがあると、Mac 本体に SSH で入ったつもりが VM に繋がる。Mac 本体には入れない。さらに Mac のリモートログインを ON にすると、qemu が :22 を確保できない。落とし穴1と同じ理由で VM が起動しなくなる

VM への SSH は 2222 → 22 で足りている。22→22 は不要だ。不要どころか有害だった。

削除しようとした。設定画面でその行を選択できない。設定ファイル(plist)を直接編集した。この作業は機体ごとに発生する。だから削除ツールをキットに同梱した。plutil だけで動く。追加の依存はない。VM が起動中なら中止する。変更前にバックアップを取る。添字がずれないよう後ろから消す。

[5] ホスト:22 → VM:22   ← 削除対象
✓ [5] を削除しました
plist 検証: OK

コンソールにも検査を足した。:22 を転送が握っていたら警告し、そのまま貼れる削除コマンドを表示する。

落とし穴3:ホスト鍵が変わると、止めずに再起動してしまう

これが一番危なかった。

夜間の再起動は、この順序で動く。

  1. Mac から VM に SSH で入り、systemctl poweroff を投げる(MySQL がフラッシュされる)
  2. VM の応答が消えたのを確認する
  3. Mac 本体を再起動する

手元で 1 を叩いた。

@@@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @@@
Host key verification failed.

VM を作り直したので、SSH のホスト鍵が変わっていた。スクリプトには StrictHostKeyChecking=no が付いている。だがこのオプションは「初めて見る鍵」を黙って受け入れるだけだ。「前と変わった鍵」は拒否する。中間者攻撃を検出する仕組みだから、当然の挙動だ。

つまりこうなる。VM を作り直したあとの最初の夜、poweroff は空振りする。スクリプトは90秒待つ。「まだ動いているが仕方ない」と Mac を再起動する。VM は電源ごと落ちる。MySQL はフラッシュされない。

エラーは表に出ない。ログに WARN が一行残るだけだ。翌朝サーバは動いている。誰も気づかない。DB が壊れるとしたら、そのときだ。

対策は一行だった。VM への SSH に -o UserKnownHostsFile=/dev/null を足す。接続先は自分の Mac の中の VM、127.0.0.1 のポート転送先だ。経路は外に出ない。鍵を保存も照合もしないのが正しい。

修正が効いた瞬間は、実地検証のログに残っている。

Warning: Permanently added '[127.0.0.1]:2222' (ED25519) to the list of known hosts.
18:40:04 [utmReboot] guest HTTP down after 3s (guest powered off cleanly)

対策前ならここで止まっていた。代わりに、3秒で正常停止した記録が残った。

落とし穴4:macOS の許可は、アプリごとに別

macOS では、あるアプリが別のアプリを操作するのに明示の許可がいる。VM の起動に使うコマンドは仮想化アプリを操作する。だから許可が要る。

ここで妙なことが起きた。保守コンソールから叩くと VM が見つからない、という。

Error: Virtual machine not found.

名前は合っている。ターミナルから同じコマンドを叩くと、ちゃんと出る。合っているのに、無い。

で、私は何をしていたかというと、VM 名の綴りを三回見直していたのである。大文字小文字まで一字ずつ指でなぞって、合っている、やっぱり合っている、とやっていた。四回目でようやく気づいた。許可が無いと、VM の一覧が空で返ってくる。 権限エラーの顔をしない。「そんな VM は無い」という顔で失敗する。これは分からない。しばらく椅子にもたれて天井を見た。

設定画面を開くと、こう並んでいた。

NGW保守コンソール
    └ System Events   ON
    (仮想化アプリの項目が無い)
ターミナル
    └ 仮想化アプリ     ON

許可は要求したアプリごとに作られる。手では追加できない。コンソールは Dock に出ない常駐アプリだ。要求が許可ダイアログにならないまま弾かれていた。

方針を分けた。

  • VM 名の確認は、仮想マシンのバンドルを直接読む。ファイルを見るだけなので許可が要らない
  • VM の起動はコマンドに頼る。ただしこれを叩くのは launchd 経由の監視処理だ。コンソールではない

監視処理の側は、許可が通った。夜間の自動復帰には影響しない。

許可が外れたまま動き続けるのも防いだ。監視処理自身がログに残す。

ALERT: 仮想化アプリを操作できません(オートメーション許可が未設定の可能性)

静かに失敗し続けるより、朝ログを見れば分かる方がいい。

落とし穴5:ビルドしたアプリが上書きできない

小ネタである。ビルドしたコンソールを scp で現地機に送った。全ファイルで弾かれた。

scp: dest open ".../Contents/MacOS/ngwctl": Permission denied

macOS の「App管理」保護だ。起動していないアプリでも、既存の .app を別プロセスが書き換えるには許可が要る。 対処は簡単だ。新しいパスに置いてから Finder で差し替える。

OS は年々厳しくなる。この種の制約は現地作業の手順書に効いてくる。書き留めておく価値がある。

実測:ダウンタイム67秒

落とし穴を潰した。本番と同じ流れを実機で走らせた。検証のため再起動時刻を一時的に 18:40 に設定して、待った。

時刻出来事
18:40:00夜間ジョブ発火
18:40:04VM が正常停止(3秒。MySQL フラッシュ済み)
18:40:04Mac 本体を再起動
18:40:26Mac 起動完了kern.boottime で確認)
18:40:48自動ログイン → 監視処理が起動
18:41:07VM 復帰

総ダウンタイム 約67秒。 外部から10秒間隔で観測した記録とも一致した。以後80回連続で正常。

「Mac が本当に再起動したのか」は kern.boottime で裏を取った。スクリプトが「再起動した」とログに書くのは簡単だ。機械の側に残った事実で確かめないと、検証にならない。

コンソールには予行演習のボタンも付けた。Mac の再起動だけを省いた同じ流れを、数分で走らせる。各段階の所要秒数が出る。3時を待たずに、その機体で無人運用が成立するか確かめられる。MAMP が割り込んでいれば、途中で止まる。

AIとの協働作業での学び

仕掛けを作る時間より、壊れる条件を潰す時間の方が長かった。今回の5つは、どれも動いているように見えて、ある日静かに止まる種類だった。ポートの奪い合い。鍵の食い違い。OS の許可。普段は表に出ない。

ほとんどは事前に気づけるものだった。だから検査をコンソールに埋め込んだ。設置ボタンを押す前に、VM 名は実在するか、確認URLは本当に VM を見ているか、ポートを奪い合っていないか、いま設定しようとしているのは本当にこの機体か。確かめて、駄目なら止まる。運用の知識を、人の記憶ではなく道具の側に置く。 現地に行くのは私とは限らない。半年後の私は今日のことを覚えていない。

Claude との作業でいちばん効いたのは、前回と同じで「動いた」で終わらせない問いだった。落とし穴1は、私が「MAMP と共存させたい」と言ったときの返しから出てきた。

その監視は、VM が止まっているときに「止まっている」と判定できますか。MAMP が :80 に応答していると、VM の起動と無関係に成功します。

言われて初めて気づいた。確認URLは「NGW が見えるか」を見る道具ではない。「VM が起動しているか」を見る道具だ。同じ :80 でも意味が違う。ポートを 8080 に変えたのは、この一言のおかげである。

こうして MAIN と CLONE の2台で、毎晩3時に「VM を正常停止 → Mac 再起動 → 自動復帰」が回るようになった。停電時は VM だけを安全に止めて、Mac は UPS で粘る。日中に VM が落ちても2分以内に立て直す。設置は保守コンソールのボタン4つ。USB のキットも、テキストエディタでの書き換えも、もう要らない。

無人運用というのは、無人で動く仕組みを作ることではない。人がいないと直せないものを、一つずつ減らすことだ。おかげで現場は誰も何もしなくてよくなった。

……で、当の私はどうしているかというと、朝いちばんにログを開いて ALERT 0 を確かめているのである。無人でいいと自分で書いておいて、これだ。三日も続けば飽きるだろうと思っていたが、まだ飽きない。無人運用の完成度は、私の分だけまだ足りていない。