インストールは完了と出たのに、エージェントは起動直後に落ちた
目標
顧客のホストのインストール要件をチェックし、pass・warn・failをJSONの報告書と終了コードで出す、事前チェックのスクリプトを作ります。採点ツールが用意したさまざまな環境で判定が正しく、チェックは何も直してはいけません。
なぜ重要なのか
顧客の現場のインストールの時間枠は、一度きりの場合が多いです。前回のインストールは「完了」で終わりましたが、ポートは社内プロキシが使っていて、証明書はすでに期限が切れていました。事前チェックは、こうした事実を、インストールの前に根拠とともに明らかにするツールです。 判定だけでなく観測値を残してはじめて、顧客の担当者が確認し直せ、終了コードが一定であってはじめて、インストールの自動化が、続行と中断を分けられます。 採点ツールは、あなたの文言を信じません。一時ディレクトリに、占有されたポート・TIME_WAITのポート・期限間近または期限切れの証明書・存在しないディレクトリを自分で作り、ポート番号・基準値・パスを実行のたびに変えて、スクリプトを実行します。
ステップ
- 顧客メモを読み、要件7種を、契約のキー名で記録します(ファイル: /root/preflight/spec.json)。古い文書の値と、サポートのポリシーを区別します。
- python_minとportsのチェック、報告書(summary・exit_code・checks)、終了コード0・1・2を実装します(ファイル: /root/preflight/preflight.py)。仕様にないキーのチェックは行いません。
- preflight.pyのポートのチェックが、TIME_WAITだけが残ったポートを、使用中と誤検出しないように直します。リッスンしているポートは、引き続きfailでなければなりません。
- preflight.pyに、diskのチェックを入れます。空きMiBをobservedに、測ったパスをmeasured_pathに書き、まだないパスは作らずに、最も近い親を測ります。
- preflight.pyに、certsのチェックを入れます。notAfterをエポック秒でobservedに、残り日数をdays_leftに書き、期限切れ・読めない場合はfail、warn_days未満はwarnです。
- preflight.pyに、files・hosts・writable_dirsのチェックを入れます。observedは契約の状態名を使い、書き込みの試験のファイルは消し、存在しないディレクトリは作りません。
- 正常・警告・失敗のホストの完全な仕様と、壊れた仕様を区別できるかを確認します。終了コードは0・1・2で、仕様を読めない場合は、報告書なしで3です。
- spec.jsonでこのホストをチェックして、報告書を残し(ファイル: /root/preflight/report.json)、報告書のハッシュ・判定(go・go-with-warnings・no-go)・blockers・warningsを記録します(ファイル: /root/preflight/decision.json)。
参考
- 用意するもの: 顧客メモ /opt/lab/p1a-preflight/customer-host.md、実行契約 /opt/lab/p1a-preflight/CONTRACT.md(仕様のキー・チェックid・observed・終了コード)、顧客が渡した設定・証明書 /opt/lab/p1a-preflight/host/etc/stocksync/
- 直接の試験: python3 /root/preflight/preflight.py --spec /root/preflight/spec.json --report /tmp/r.json; echo $?
- 占有ポートの模擬: python3 -m http.server 18080 &で起動したあと、そのポートを仕様に入れてみます。終わったら、そのジョブのPIDだけを選んで停止します。
- 証明書の作り方: openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -keyout /tmp/k.pem -out /tmp/c.crt -days 5 -subj /CN=t
- よくある間違い: バージョンを文字列で比べる、SO_REUSEADDRなしでbindする、存在しないディレクトリを作ってしまう、書き込みの試験のファイルを残す。
- 事前チェックの通過は、インストールの成功の保証ではありません。仕様に書いた条件だけを確認します。
顧客メモを仕様に移す
顧客メモの要件7種を、契約のキー名で記録してください(ファイル: /root/preflight/spec.json)。
メモには、古い文書の値と、「動くには動いた」という体験談が混ざっています。チェックの基準は、今回のバージョンと、サプライヤーのサポートのポリシーです。ポートは整数の配列、disk・certsはオブジェクトの配列です。
Pythonのバージョンとポートから判定する
python_min・portsをチェックし、報告書と終了コード0・1・2を出すようにしてください(ファイル: /root/preflight/preflight.py)。
ポートが空いているかは、0.0.0.0に実際にbindしてみればわかります。バージョンの比較は、sys.version_infoと整数のタプルで行います。summaryは、failが1つでもあればfailです。採点ツールは、毎回異なるポートとバージョンの基準で実行します。
終わった接続を占有と誤検出しない
ポートのチェックが、TIME_WAITだけが残ったポートはfree、リッスンしているポートはin_useと区別するようにしてください(ファイル: /root/preflight/preflight.py)。
サーバー側が先に閉じた接続は、そのポートにTIME_WAITを残します。オプションなしのbindは、これも妨げられます。ソケットオプション1つで、TIME_WAITは通過し、本物のリスナーは引き続き妨げられるようにできます。
空き容量を警告と失敗に分ける
diskのチェックを入れ、observed(空きMiB)・measured_path・pass/warn/failを出すようにしてください(ファイル: /root/preflight/preflight.py)。
shutil.disk_usageのfreeを1024*1024で割った商です。パスがなければ、os.path.dirnameで上へたどりながら、存在する場所を探しますが、作ってはいけません。採点ツールは、いまの空き容量を基準にして、基準値を変えながら、不足したディスクを模擬します。
証明書の残り日数をnotAfterで測る
certsのチェックを入れ、observed(notAfterのエポック秒)・days_left・判定を出すようにしてください(ファイル: /root/preflight/preflight.py)。
openssl x509 -enddate -noout -in <ファイル>の出力の、notAfter=のあとを、ssl.cert_time_to_secondsで変換します。読めなかったファイルは、observedがnullでfailです。採点ツールは、余裕のある・期限間近・期限切れ・壊れた証明書を作って混ぜます。
設定・ホスト名・書き込み権限を確認する
files・hosts・writable_dirsのチェックを入れてください(ファイル: /root/preflight/preflight.py)。存在しないディレクトリは作らず、書き込みの試験のファイルは消します。
ファイルは、存在しない・ファイルではない・空のファイルを区別します。名前解決は、socket.getaddrinfoが失敗すると、socket.gaierrorを出します。書き込みは、os.accessの代わりに、tempfile.mkstempで実際に作ってみて、消します。
ホスト3台と、壊れた仕様を区別する
全体の仕様で、正常・警告・失敗を終了コード0・1・2で、読めない仕様を報告書なしで3で終えるかを確認してください(ファイル: /root/preflight/preflight.py)。
仕様のキーの順序が変わっても、チェックidは、仕様に書かれたものと正確に同じでなければなりません。仕様のJSONが壊れていたなら、判定する根拠がないので、報告書は書きません。
顧客のホストにgo / no-goを下す
spec.jsonでチェックして報告書を残し(ファイル: /root/preflight/report.json)、report_sha256・decision・blockers・warningsを記録してください(ファイル: /root/preflight/decision.json)。
decisionは、summaryから決まります(pass→go、warn→go-with-warnings、fail→no-go)。blockersはfailのチェックid、warningsはwarnのチェックidです。採点ツールは、いま再びチェックして、報告書が古くなっていないかと、ハッシュを確認します。妨げとなる項目を直して通過させる段階ではありません。