1. 既存ツールをサイトへ載せる、という作業
Network CheckをOSIIXへ組み込む作業は、新しいツールを作ることではなかった。すでに動いているcheck logicを、OSIIXの見た目とURL体系の中に置き直すことが仕事の実体だった。
この種の作業でまず突き当たる問いは、機能をどう実装するかではない。既存のcheck処理、API、テスト済みの安全性検証はすでに存在している。問題は、それらをどこまで触ってよいか、そしてどこからをサイト固有の変更として扱うべきか、という線引きにある。
本記事はNetwork Check自体の機能紹介ではない。Network Checkが何をするツールかは、すでにProjectページで説明されている。ここで扱うのは、既存Coreを維持したままサイト固有の差分をどこへ置いたか、その境界が実装・検証・障害切り分けにどう効いたか、という一点である。
2. 最初に決めたのは「何を変えないか」だった
Git記録を確認する限り、OSIIX向けのcomposition / UI実装にあたって、少なくとも次のものは変更対象から外れている。Gitから確認できるのは、当時の心理ではなく変更境界そのものである。この記事では、その境界が結果としてどの責務を既存側に残し、どの差分をサイト固有側へ置いたのかを見る。
- check core本体
- API contract
- check IDs
- 実行順序
- SSRF / TOCTOU対策
- timeout policy
- usage metricsの実装
一方で変更されたのは、composition、templates、CSS、presentation、そしてpublic base pathである。
ここで注意したいのは、「回帰を防ぐためにあらかじめこの設計を選んだ」という当時の意図を記録から断定することはできない、という点だ。Git上で確認できるのは、結果として何が変更対象から外れ、何が変更されたかという事実のみである。ただし、この分離が結果としてCore側の変更範囲・回帰確認範囲を広げにくい形になっていた、とは言える。これは実装意図の再現ではなく、事実から導ける解釈にとどまる。
3. OSIIX固有差分をcompositionへ置く
OSIIX版は、Network Check本体のforkではない。apps/osiix.pyはOSIIX固有のASGI composition rootであり、shared側のapps/composition.pyが提供するcreate_network_check_app()を呼び出す形で組み立てられている。
apps/osiix.pyが担っているのは、主に次の3点である。
templates/osiix/をshared templateより優先させる- shared側の
apps/composition.pyが提供するcreate_network_check_app()を呼び出す - usage metrics routeをOSIIX composition側から除外する
つまり、OSIIX用に新しいcheck処理を書いたのではなく、OSIIX固有の組み立て方をapps/osiix.pyへ置き、その下でsharedなapps/composition.pyを再利用する形になっている。OSIIX固有なのはapps/osiix.pyとtemplates/osiix/のようなpresentation側の差分までであり、apps/composition.pyより下の層は複数のsource entrypointから共有されるsharedな実装である。
4. 「Core / UI」の二分だけでは説明しきれない
ここまでの説明は「CoreとUIを分けた」という単純なモデルで語りたくなるが、実装を正確に表すにはもう一段階細かい区別が要る。
Individual checksとDomain Multi Checkでは、実際の経路が異なる。すべてのcheckが同じApplication層を通過するわけではない。
図の通り、Individual checksはApplication層を経由せず直接Checks / Coreへつながる一方、Domain Multi CheckはApplication層を経由してChecks / Coreへつながる。この経路の違いが、単純な二層モデルでは表現しきれない部分である。したがって、この記事で扱っているのは「UI層とCore層の二層分離」ではなく、サイト固有のpresentationと既存check実装の間に、必要なcomposition / application境界を置いたという話である。少なくとも、この実装を説明するためにClean ArchitectureやMVCといった分類へ無理に当てはめる必要はない。実際の経路と責務の違いをそのまま見る方が、この構成を正確に説明できる。
5. URLとbase pathも設計の一部だった
サイトへ組み込むという作業は、見た目だけの話では終わらない。OSIIXでは/tools/nc/という公開パスの下にNetwork Checkが配置されており、このbase pathはtemplate内のリンク、static asset、API呼び出し、guide routeのすべてに波及する。
tests/test_osiix_composition.pyでは、PUBLIC_BASE_PATH=/tools/ncを設定した状態でrenderを行い、少なくとも次のパスが結果に含まれることを確認している。
/tools/nc/static/osiix-network-check.css/tools/nc/api/multi-check/run/tools/nc/static/multi_check.js/tools/nc/network-check/guide/
この事実が示しているのは、base pathがデプロイ後にnginx側だけで調整するような後付けの問題ではなく、presentation側の実装・テストの入力そのものだということである。base pathを変数として扱い、templateやAPI呼び出しへ正しく伝播することをテストできる状態にしておく。サイト統合では、URL体系も実装後に調整する付帯事項ではなく、あらかじめ検証すべき設計入力になる。
6. Core/UIを分けても、まだ公開は完成しない
CoreとUIの境界を整理しても、それだけでは「サイトに公開されている状態」を保証したことにはならない。実際の経路はもう少し長い。ソースコード上でapplicationが正しく組み立てられていることと、そのapplicationが実際にservice として起動し、reverse proxyを経由して公開URLとして到達可能であることは、別の検証対象である。ロジックの正常性と公開経路の正常性は、同じテストで一括して確認できるものではない。
7. application正常性と公開経路正常性は、別々に確認されている
CoreとUIの境界を分けても、それがそのまま「公開経路まで正常」を意味するわけではない。実際、OSIIX Network Checkの運用記録では、application/serviceの正常性と、公開経路の正常性が、明確に別々の確認項目として扱われている。loopback上のhealth endpointへの直接アクセス、systemd serviceの起動状態、そしてhttps://osiix.com/tools/nc/などpublic HTTPS経由でのアクセス結果は、それぞれ独立に記録されている。
/tools/NCという大文字パスから正規の/tools/nc/へのcompatibility redirectも、この公開経路側の確認対象の一つとして記録されている。canonical routeと表記ゆれへの対応は、application側のcheck logicとは別の公開経路上の論点として、route contractの設計段階から区別されていた。
ここで重要なのは、application・service・reverse proxy・static/public routingという層を、それぞれ独立に確認すべき対象として扱う必要があるという点である。local/directでapplicationにアクセスして正常性を確認する、serviceが起動しているかを確認する、reverse proxy経由でpublic URLへアクセスして結果を確認する。これらは別々の確認作業であり、どこまでを正常と確認できていて、どこから先が未確認なのかを言えるようにしておくことが、障害切り分けの実質的な手順になる。
8. 分離すると、障害の探索範囲も狭くなる
境界を分けておくことの効果は、変更範囲を限定できることだけではない。何かを「正常」と確認したとき、その確認が実際にはどこまでの範囲を保証しているのかを、境界ごとに考えられるようになる。
たとえば、source側のテストがPASSすることは、source上の実装についての証拠にはなるが、それだけでruntime公開経路が正常であることまでは証明しない。loopback上のhealth checkがPASSすることは、application / serviceが起動して応答している証拠にはなるが、reverse proxy以降の経路が正常であることまでは証明しない。public URLへ正常にアクセスできることは、公開経路として到達できている証拠にはなるが、check結果の中身が意味的に正しいことまでは証明しない。
つまり、それぞれの境界での検証結果は、「全部正常」という一つの判定ではなく、その地点までについての部分的な証拠として積み上がっていく。
たとえば、公開URLが404を返しているとしても、それだけでcheck logic本体に原因があるとは限らない。application route、reverse proxy、static routingなど、HTTP経路上のどの境界まで正常なのかを一つずつ切り分けていく必要がある。逆に、APIが返すcheck結果の中身がおかしい場合は、application層やcheck実装側へ戻って確認する必要がある。
境界を分けておけば、最後に正常と確認できた境界と、最初に異常が確認された境界の間へ、調査対象を絞ることができる。これは今回の実装から導ける一般化された教訓であり、Network Check固有の話ではない。境界は、実装時の変更範囲を制限するだけでなく、障害発生時に「どこから先を疑えばよいか」という探索範囲も同時に定義している。
9. 既存ツールを別サイトへ組み込むときの設計原則
今回の実装経験から抽出できる原則を、一般化した形で挙げる。これらはNetwork Check固有の結論ではなく、既存のCLI/API/内部ツールを別のWebサイトへ組み込む際に再利用できる観点として整理したものである。
設計時
- まず「変えない責務」を先に決める
- サイト固有の差分は、composition / presentation側へ寄せる
- URL / base pathを、architectureの入力として扱う
検証時
- application正常性と公開経路正常性を、別々に検証する
- reverse proxyやserviceも、実行architectureの一部として扱う
運用時
- 既存ロジックを、新しいUIの都合で複製しない
- rollbackの境界も、同じ責務単位で考えておく
7について補足すると、今回の実装で完全なrollback手順が整備済みであるとは言えない。ここで挙げているのは、境界を明確にしておけば、将来rollbackを設計する際にも同じ責務単位で考えやすくなる、という一般化した課題である。
10. まとめ
今回の統合で重要だったのは、Coreを触らないことそのものではなく、変更理由ごとに差分を置く境界を決めることだった。
変更対象は、check core、API contract、check IDs、実行順序、SSRF / TOCTOU対策、timeout policyといった既存側の責務と、composition、template、CSS、public base pathといったサイト固有側の責務に分かれていた。URLやbase pathも、デプロイ後の後付け事項ではなく、composition・templateへの入力として設計・テストの対象になった。application正常性と公開経路正常性は、それぞれ別の検証対象として扱われ、そのことが障害発生時に疑うべき範囲を絞り込むことにもつながった。
既存ツールを別のサイトへ載せる作業は、多くの場合「UIをどう作るか」ではなく、「どこからをサイト固有の変更として扱うか」を決める作業になる。
本稿は個人の開発経験に基づく記録であり、一般規格や唯一の正解を示すものではありません。対象プロジェクトの実名・実インフラ情報(サーバーパス、IPアドレス、認証情報等)は意図的に含めていません。サイト全体の免責事項はこちらをご覧ください。