インストールは成功したが、ポートはすでに他人のものだった
一言でいうと
事前チェック(preflight)は、インストールの前に、顧客のホストが要件を満たしているかを、読み取りだけで判定し、その結果を、人ではなく次の段階の自動化が読める報告書と終了コードとして残すことです。
なぜ必要なのか
顧客の現場のインストールの時間枠は、たいてい一度きりです。木曜の夜22時にインフラチームが扉を開けてくれて、午前0時には閉まります。こうした場で、インストールのスクリプトが「完了」で終わったという事実は、ほとんど何も保証しません。ファイルは所定の場所にコピーされても、エージェントが使おうとしたポートは社内プロキシがすでに握っていて、起動した途端に落ちることがありますし、顧客が渡した証明書は、インストールの3週間前に期限が切れているかもしれません。どちらも、インストールのあとでログを調べてはじめてわかりますが、インストールの前なら、5分でわかる事実です。
FDEが顧客に引き渡すのは、コードではなく動作する結果です。そのため、インストールの手順の前に、「このホストで、インストールが成功する条件がそろっているか」を、別に尋ねる段階を置きます。この段階を人のチェックリストにすると、毎回誰かが抜かします。スクリプトにして結果をファイルに残せば、顧客に、「インストールを妨げるのはこの2つで、残りは警告です」と、根拠を添えて伝えられます。
どう動くのか
チェッカーは、要件の仕様を入力として受け取り、チェックごとに、pass・warn・failのどれかと観測値を出します。観測値が重要です。「ポート失敗」とだけ書かれた報告書は、反論できませんが、検証もできません。observed: in_useと、測ったパス・空きMiB・notAfterの時刻が一緒にあってはじめて、顧客の担当者が、自分の目で確認し直せます。
終了コードは、監視プラグインの慣例を借りると、説明しやすくなります。Monitoring Pluginsの開発ガイドラインは、0 OK、1 Warning、2 Critical、3 Unknownを使い、3は、引数が間違っていた場合や、チェッカー自身が動けなかった場合に限定しています。名前解決の失敗やソケットのタイムアウトのような、上位のエラーを、Unknownに上げないように書いている点が、注目に値します。ホスト名が解決できないのは「わからない」ではなく、インストールを妨げる事実だからです。
チェック1つ1つに、よくある落とし穴があります。
- ポート。 空いているかを見るには、実際にbindしてみます。ところが、オプションなしでbindすると、いま終わったばかりの接続がTIME_WAITとして残っているポートも、使用中と出ます。Pythonのsocketのドキュメントは、
create_server()の説明で、TIME_WAITとして残ったアドレスをすぐに再利用するために、POSIXでSO_REUSEADDRを有効にすると書いています。ラボのイメージで実測したところ、リッスンしているソケットがあるポートは、このオプションを有効にしてもEADDRINUSEで、TIME_WAITだけが残ったポートは、オプションを有効にしてはじめてbindできました。有効にしないチェッカーは、問題のないインストールを妨げます。 - ディスク。 shutil.disk_usageは、total・used・freeをバイトで返します。実測では、freeは、statvfsの
f_bavail(権限のないユーザーが使えるブロック)に、フラグメントのサイズを掛けた値と同じでした。まだないインストール先のパスは、作らずに、存在する最も近い親を測り、測ったパスを報告書に書きます。 - 証明書。 Pythonのsslモジュールには、PEMファイルを読んでnotAfterを取り出す公開関数がありません。代わりに、
ssl.cert_time_to_seconds()が、"%b %d %H:%M:%S %Y %Z"の形のnotAfterの文字列を、エポック秒に変えてくれます。文字列は、openssl x509の-enddateで得ます。同じドキュメントの-checkendは、指定した秒の間に期限が切れると、0以外の値で終了しますが、残り日数を報告書に書くには、日付を自分で計算したほうがよいです。 - Pythonのバージョン。 「3.12.3」と「3.9」を文字列で比べると、3.12のほうが小さくなります。整数のタプルで比べます。
- 書き込み権限。 osのドキュメントは、
os.access()で確認してから開く方式は、確認と使用の間に隙を作るとして、EAFP、つまり実際にやってみて例外を受け取る方式を勧めています。読み取り専用のマウントのように、権限ビットが教えてくれない理由もあるので、一時ファイルを実際に作って、すぐに消します。
명세(spec.json) ──▶ preflight.py ──▶ report.json (점검마다 status + observed)
│
└──▶ 종료 코드 0 · 1 · 2 (명세를 못 읽으면 3, 보고서 없음)
現場での姿
最もよく見る失敗は、チェッカーが直してしまうことです。データディレクトリがなければ作り、ポートを握っているプロセスを殺し、期限切れの証明書を自己署名のものに差し替えます。その瞬間、チェックの結果は、「もともとこのホストがどうだったか」を失います。そのうえ、ディレクトリの所有権、ポートを握っていた社内プロキシ、証明書の発行の手続きは、すべて顧客の組織の決定です。事前チェックは事実を明らかにし、直す作業は、担当者が合意した手続きで行います。今回の顧客メモにも、作業ディレクトリはインフラチームが作ってくれることになっていると書かれています。
2つ目は、警告と失敗を混ぜることです。空き容量が、サプライヤーの最小値は超えていても、運用チームの基準より少ないなら、インストールはできます。これを失敗にまで上げると、インストールの時間枠を無駄にし、まったく知らせなければ、2か月後にディスクが満杯になります。警告の等級を別に設ける理由です。
実務で本当に大切なこと
- 報告書は、もう一度測れるものでなければなりません。判定だけを書かず、観測値と測った対象を一緒に残します。
- チェッカーは、ホストを変更しません。書き込みの試験の一時ファイルまで消します。
- 終了コードは約束です。インストールの自動化が、1は続行、2は中断と読むようにします。
- 報告書が古くなったら、根拠ではありません。インストールの直前にもう一度実行し、判定の記録に、どの報告書だったかのハッシュを残します。
次のラボですること
顧客メモを仕様に移し、ポート・TIME_WAIT・ディスク・証明書・ファイル・ホスト・書き込みのチェックを、順に実装します。採点ツールは、毎回異なるポートと基準値、自分で作った期限間近・期限切れの証明書で、あなたのチェッカーを実行し、判定と観測値を照合します。最後に、顧客の仕様で実際の判定を下し、go / no-goの記録を残します。