ある大企業のIT組織は、生成AIアプリケーションを5つ本番で動かし、3つのAzureサブスクリプションに散らばる11のモデルデプロイメントを抱え、月におよそ38,000ユーロを支払っている。そして、その金額を5つのうちどのアプリが使ったのか、確かな方法では答えられない。壊れているものは何もない。どのアプリも動いており、どのチームもリリースを果たし、どのセキュリティレビューも当時は通過した。存在しないのは、トークンが数えられ、呼び出し元が識別され、モデルが引退させられる単一の場所であり、その不在こそが本シリーズ全10回で構築するものだ。すべてのアプリケーションが通り、どのアプリケーションも迂回しない、ガバナンスの効いたGenAIゲートウェイをAzure上に建てる。

この会社と、実際に払っているもの

ありのままに呼ぶなら、歴史ある大企業のIT組織だ。約40のプロダクトチームにまたがるエンジニア約400名、ワークロードの大半はAzure、ランディングゾーンとCIテンプレートをすでに所有するプラットフォームグループがある。生成AIはいつも通りのやり方で、周縁から内側へと入ってきた。最初に出荷されたのはカスタマーサービスアシスタントで、プロダクトオーナーに予算があったからだ。続いてIT運用のサマライザー、社内向けコードアシスタント、製品ドキュメントに対するリテールナレッジ検索、そしてマーケティングコピーツール。5つのチーム、5つの意思決定、5つのAzure OpenAIへの直接統合である。

その一つひとつの判断は、単体では正しかった。だが合わさると、特定の、今ではおなじみの形が現れる。

  • 3つのサブスクリプションに11のモデルデプロイメント。うち2つは同一リージョンの同一モデルの重複で、あるチームが手持ちのリソースにクォータを付けてもらえなかったために作られた。
  • 月およそ38,000ユーロのモデル支出が、コスト管理上はサブスクリプション粒度でしか見えない。つまり財務には合計を伝えられるが、どのプロダクトオーナーにも自分の数字は伝えられない。
  • 5つのうち4つのアプリがアプリケーション設定にキーを保持している。1つはシークレットスキャナーに見つかるまでリポジトリに置いていた。エンジニアがそのアプリの設定を編集しない限り、どのアプリも個別に失効させられない。
  • 60日前のモデル引退通知がメールで届き、プラットフォームグループはどのアプリケーションが壊れるかを突き止めるためにリポジトリのgrepに2日を費やした。どのアプリがどのモデルバージョンを呼んでいるかを記録するものが何もなかったからだ。

そして、実際に決断を迫ったインシデントが起きる。マーケティングコピーツールのバッチジョブが、共有デプロイメントのtokens-per-minuteクォータの大半を約40分間にわたって消費した。同じデプロイメントを共有していたカスタマーサービスアシスタントは、実際の顧客にHTTP 429を返し始めた。どちらのチームも何も間違っていない。「ある呼び出し元は、別の呼び出し元が依存するクォータを使い尽くしてはならない」という制約を表現できる場所が経路上に存在しなかった。そもそも共有された経路自体がなかったからだ。

これがゲートウェイの正直な論拠だ。「中央集権的な制御は良いアーキテクチャだ」からではない。クォータ、コスト帰属、認証情報のローテーション、モデルのライフサイクルはどれも呼び出しの属性であり、すべての呼び出しを見るコンポーネントが存在しない限り、そのどれも紳士協定以外の手段では強制できない、ということだ。

アプリ個別方式が実際に払わせるコスト

直接統合は初日には本当に安い。SDKクライアント1つ、キー1つ、エンドポイント1つ、クリティカルパスにプラットフォームチームはいない。コストは後からやって来て、しかもそれを選んだチームとは別の場所に着地する。だからこそ選ばれ続ける。

複利で膨らむコストは3つあり、どれも同じコストが服を着替えただけだ。変更コスト:モデルの引退、価格改定、リージョン移行、プロバイダ障害は、アプリケーションごとに1回ずつ、5つのバックログと5段階の温度差を持つ5つのチームが処理しなければならない。帰属コスト:支出はリソース単位では見えて消費者単位では見えないため、誰にも予算を与えられず、つまり誰にも上限を課せない。ガバナンスコスト:プロンプトのログ記録、PIIの秘匿化、EU AI Actの透明性要件といった新しい統制はどれも5回実装して5回検証しなければならず、レビューの労力は統制の数ではなくアプリケーションの数に比例して膨らむ。

ゲートウェイはこれらのコストを消し去るわけではない。プラットフォームチームが1回だけ払えば済む1つの場所へ移すだけだ。それが議論のすべてであり、それより景気のいい話にはむしろ疑いの目を向けるべきだ。

Azureでの自作か購入か

チームは3つの形を評価した。答えはエンジニアが望むものではない。

AIゲートウェイとしてのAzure API Management。API Managementは言語モデルのトラフィックを明確に狙った一連の機能を備えており、そのリストは読む前に大半の人が想像するより長い。llm-token-limitポリシーはtokens-per-minute、あるいは時・日・週・月・年単位のトークンクォータを任意のカウンターキー(サブスクリプションキー、呼び出し元IP、任意のポリシー式)に対して強制し、プロンプトのトークン数をゲートウェイ側で事前計算できるため、上限超過のリクエストはバックエンドに一切届かない。llm-semantic-cache-storeポリシーとllm-semantic-cache-lookupポリシーは、Azure Managed Redisや他のRediSearch互換キャッシュに対して補完結果のベクトル類似度キャッシュを行う。llm-emit-token-metricポリシーはトークン数をカスタムディメンション付きでApplication Insightsへ送出し、これが消費者別コスト帰属の原材料になる。バックエンドはラウンドロビン、重み付け、優先度ベース、セッションアウェアのロードバランシングをサポートし、circuit breakerのトリップ時間はバックエンド自身のRetry-Afterヘッダーを尊重するため、飽和したProvisioned Throughput Unitデプロイメントからあふれた分を、誰もリトライコードを書かずに従量課金エンドポイントに吸収させられる。

ここではプロバイダのカバレッジが重要で、しかも誤解しやすい。API ManagementはOpenAIのChat CompletionsおよびResponsesスキーマ、Anthropic Messages API(v2ティア)、Google Vertex AI APIに準拠する言語モデルAPIを仲介し、対象はMicrosoft Foundryにデプロイされたモデルにも、Amazon Bedrockを含むMicrosoft以外のプロバイダにも及ぶ。執筆時点でプレビューのunified model APIは、単一のOpenAI互換エンドポイントの背後に複数のバックエンドを公開してフォーマットを変換するため、1つのガバナンスポリシーセットがプロバイダ横断で適用される。Anthropic Claude、Mistral、Llamaには、それぞれ個別の統合を作るのではなく、この層を通して到達できる。

AKS上のオープンソースプロキシ。LiteLLMや同種のプロジェクトは、完全に自分の管理下にあるコンテナでプロバイダ変換とルーティングを提供する。欲しい機能への近道になり、プレビューへの依存もない。その代わり、全社のあらゆるAIリクエストの前に座ることになったコンポーネントのオンコールローテーションと、API Managementがすでに認証取得済みのアイデンティティ・ネットワーク・監査の作業を引き継ぐことになる。

独自のFastAPIサービス。最大限の制御が手に入り、あらゆる障害が自分のものになる唯一の選択肢でもある。本当に特殊な要件を持つコンポーネントには妥当だが、トークンを数えるリバースプロキシはそれに当たらない。

本シリーズが追う決定はこうだ。API Managementがデータプレーンであり、API Managementがやらないことは、AKS上の小さなPythonコントロールプレーンとして構築する。この分割こそ腹に落としておく価値がある。ゲートウェイプロジェクトの大半がつまずくのはここだからだ。チームはすべてをポリシーXMLで表現しようとして誰もテストできない保守不能な成果物を生むか、トラフィックを自前のサービスに通してレート制限を下手に再実装するかのどちらかに陥る。ゲートウェイはリクエストを運ぶ。コントロールプレーンは状態を運ぶ。どのモデルが存在するか、誰がそれを呼べるか、呼び出しの費用はいくらか、モデルが引退したら何が起きるか、である。

それでも自分で作るもの

5つあり、その一つひとつが本シリーズの後の回になる。

  • モデルカタログとライフサイクル。どの論理モデル名が存在し、どの物理デプロイメントがそれを支え、どれが非推奨でいつ消えるのか。アプリケーションはデプロイメント名ではなく必ずchat-defaultを呼ばなければならない。さもなければ60日前の引退メールが再び60日間の移行プロジェクトになる。
  • テナントのアイデンティティとオンボーディング。チームはクライアントクレデンシャル、クォータ、モデル許可リスト、コストセンターを受け取る。チケットではなく、コードとしてプロビジョニングされる形で。
  • コスト帰属とチャージバック。テナントディメンション付きのトークンメトリクスは入力にすぎない。それを財務が受け入れるチーム別の月次数値に変えるのは、独自のテストを持つデータパイプラインだ。
  • パイプラインの評価ゲート。新しいモデルバージョンへの昇格はデプロイである。評価結果が添えられないままモデルの挙動を変えるデプロイは、顧客に発見されるのを待つ障害だ。
  • 非同期および長時間の処理。サポートチケットのバッチ要約は、最初のトークンを待つ顧客と同じ同期経路に載せるべきではない。この分離はキューであって、タイムアウト設定ではない。

目標アーキテクチャ

このプラットフォームは意図的に地味で、意図的に付加的な設計だ。コントロールプレーンが停止しても、データプレーンは最後に知っている構成のままトラフィックを処理し続ける。コントロールプレーン障害でフェイルクローズするゲートウェイは、全社障害だからだ。

graph TD
  subgraph consumers["Consumer apps"]
    A1["customer-service assistant"]
    A2["IT-ops summarizer"]
    A3["code assistant"]
    A4["retail knowledge search"]
    A5["marketing copy tool"]
  end

  subgraph gw["Data plane: API Management"]
    POL["policies:
llm-token-limit, semantic cache,
emit-token-metric, JWT validate"] LB["backend pool:
priority + weighted, circuit breaker"] end subgraph cp["Control plane: Python on AKS"] CAT["model catalog"] TEN["tenant registry"] COST["cost attribution job"] end subgraph models["Model layer"] FDRY["Microsoft Foundry:
GPT deployments, PTU + PAYG"] ANTH["Anthropic Claude"] OSS["Mistral, Llama"] end ID["Microsoft Entra ID"] RED["Azure Managed Redis
semantic cache"] SRCH["Azure AI Search
retrieval"] MON["Azure Monitor +
Application Insights"] A1 --> POL A2 --> POL A3 --> POL A4 --> POL A5 --> POL ID -. "OAuth2 client credentials" .-> POL POL --> LB POL <--> RED LB --> FDRY LB --> ANTH LB --> OSS POL -- token metrics, prompt logs --> MON CAT -. "backend + policy config" .-> gw TEN -. "subscriptions, quotas" .-> gw MON --> COST A4 -. "grounding data" .-> SRCH

この図には、荷重を支える性質が2つある。第一に、モデル層へ向かうすべての矢印はAPI Managementの内側から出ている。つまりFoundryリソースのネットワークルールはそれ以外をすべて拒否する。ゲートウェイは推奨事項ではなく、経路を持つ唯一のルートだ。第二に、コントロールプレーンはゲートウェイに構成を書き込み、Azure Monitorからテレメトリを読み出すが、リクエスト経路には決して座らない。真っ昼間に再デプロイできる。

スタックを一望する

全10回が触れるものをここにまとめ、シリーズの形がこの時点から見えるようにしておく。Infrastructure as CodeはTerraformで、BicepおよびARMとの比較は前提として片付けず第2回で正直に行う。コントロールプレーンにはDockerイメージとAKS。パイプライン、バージョニング、リリース管理にはAzure DevOps。ロジックが住む場所はすべてPython。Microsoft Entra IDとOAuth2 client credentials、そしてレガシーな呼び出し元がそれ以上のことをできない場合にはAPIキー。検索にはAzure AI Searchと専用のベクトルデータベースを、宣言ではなく比較の上で使う。セマンティックキャッシュにはAzure Managed Redis。テレメトリにはAzure MonitorとApplication Insights、加えてLLM固有の可観測性としてLangSmithとOpenlayer、データサイエンスチームがすでに住んでいる場所としてDataiku。オーケストレーションにはLangChainを、フレームワークがその席に値する1つの回と、値しない複数の回の両方で扱う。ストリーミングはserver-sent eventsで、セッションが本当に双方向ならWebSockets、いま答えが要らないものはすべてメッセージキューへ。

全10回の順序はこうだ。ゲートウェイの意義、TerraformとBicepおよびARMの比較、プロバイダ抽象化とストリーミング、非同期およびイベント駆動の処理、アイデンティティ・クォータ・チャージバック、セマンティックキャッシュ、Azure AI Searchと実際のベクトルデータベースを比べる検索、ゲートウェイ上のオーケストレーション、Azure DevOpsでのCI/CDとモデルライフサイクル、そして可観測性とAIガバナンスである。

完成形はどう見えるか

最終回はこれらの数字に立ち返る。冒頭の重労働の棚卸しで挙げた数字を、測定可能な形にしたものだ。

  • どのプロダクトオーナーも、当月の自分のトークン支出を、発生から1日以内に、プラットフォームチームに尋ねることなく見られる。
  • 長寿命のモデル認証情報を保持するアプリケーションはゼロ。あるチームのアクセス失効はテナントレジストリの構成変更であり、そのチームのアプリ設定の編集ではない。
  • モデル引退の通知は調査ではなくクエリを生む。直近30日にどのテナントがそのデプロイメントに解決されたかを、カタログはすでに知っている。
  • あるテナントが別のテナントのクォータを使い尽くすことはできない。上述の429インシデントをゲートウェイへの負荷テストとして再現して検証する。
  • ゲートウェイが加えるレイテンシの中央値は、非キャッシュ呼び出しの95パーセンタイルで60ミリ秒未満に収まり、セマンティックキャッシュのヒットはモデル呼び出しのごく一部の時間で返る。

初日から注視すべき失敗モード

3つあり、Terraformを1行も書く前に名指ししておく価値がある。ゲートウェイは単一障害点になる。唯一の経路になった瞬間からそうであり、それこそが狙いなのだから、その可用性目標は背後で最も要求の厳しいアプリケーションより高くなければならず、そのデプロイは最初のインシデントの後ではなく最初のapplyからゾーン冗長でなければならない。ポリシーの無秩序な増殖は静かにやって来る。ポリシーXMLはコードであり、すべてのリクエストで実行され、誰もレビューしない場所に書くのは簡単だ。プラットフォームの他の部分と同じくバージョン管理に置き、パイプラインで適用し、ポータルでは決して編集しない。そして迂回経路は他のすべてを無効にする。Foundryエンドポイントへの直接キーを持ち続けるチームが1つあるだけで、すべての帰属数値とすべてのクォータ保証が近似値になる。これはポリシー文書ではなく、ネットワークルールとアラートである。

次に読む

これを大規模に運用する際のインフラおよびプラットフォーム側の話はercan.cloudのフィールドノートに、ハブはercanermis.comにある。

参考資料