共通基盤チームはどこまで持つか — 「接続済み VPC」を製品にする境界設計

マルチアカウント TGW + internal ALB のプライベート API を題材に、共通基盤チームとアプリチームの責任境界を設計する。『接続済み VPC』を製品として渡し、SSM パラメータを契約書にし、Terraform の apply 順序を承認フローにする — 組織の統制を申請書ではなくアーキテクチャで表現するための整理。

問い: 「どこまでが基盤チームの仕事か」

マルチアカウント AWS でネットワークを Transit Gateway (TGW) で繋ぐ設計や、TGW 上のアクセス制御を多層で積む話は以前書きました。今回はその続きで、技術レイヤーではなくチーム境界の話をします。

問いはシンプルです。

共通基盤チームとして担当するのは、どこまでにするのが良いか?

これは技術の問題に見えて、実際は**「変更のたびに誰に依頼が飛ぶか」の設計**です。境界を間違えると、ポートを 1 つ開けるたびにチケットが飛ぶ組織になります。

題材として、次の構成を使います。Dev アカウントのクライアントから、Prod アカウントの internal ALB で受けるプライベート API を呼ぶ、マルチアカウントの定番構成です。

Prod アカウント

Network アカウント

Dev アカウント

Prod VPC (2AZ)

Egress VPC

HTTPS :443

dev-rt の経路

0.0.0.0/0 集約

クライアント
(EC2 / ECS)

Transit Gateway
(RAM で組織共有)

NAT Gateway

Internet Gateway

Route 53 PHZ
api.internal.example.com

internal ALB :443

ECS Service (AZ-a)

ECS Service (AZ-c)

原則: 「届き得るか」と「許可するか」は別のレイヤー

境界を引く前に、通信制御が 2 層に分かれていることを押さえます。TGW ルートテーブルとセキュリティグループ (SG) は、どちらも「通信を制御するもの」に見えますが、性質がまったく違います。

レイヤー 2: 許可 — アプリの仕様

レイヤー 1: 到達可能性 — 組織のポリシー

経路がなければ SG 以前に届かない

TGW ルートテーブル
『dev から prod に届き得るか』
変更頻度: 低 / 影響範囲: 全社

セキュリティグループ
『この API は 8080 で、呼んでいいのは ALB だけ』
変更頻度: 高 / 影響範囲: 自サービス

この 2 層の切れ目が、そのままチームの境界になります。

基盤チーム = 到達可能性まで。アプリチーム = 許可から。

逆に言うと、SG を基盤チームが持ってはいけない。SG はアプリ仕様なので、基盤が握ると全アプリチームの変更が基盤チームのキューに並びます。環境分離はルートテーブルで既に担保されている (dev から prod へはそもそも経路がない) のだから、SG はアプリに委ねて安全です。

基盤チームの製品は「接続済み VPC」

この原則を Terraform の構造に落とすと、基盤チームが提供する「製品」の形が決まります。もう繋がっている VPC です。

アプリチームの領域 = ALB から下

SG 3 着
(client / alb / ecs)

internal ALB + ターゲットグループ

ECS サービス

ACM 証明書 + PHZ レコード

基盤チームの製品 = 接続済み VPC

VPC + サブネット
(app 用 2AZ + TGW アタッチ用 /28 ×2AZ)

TGW アタッチメント

VPC ルートテーブル
(他スポーク → TGW / 0.0.0.0/0 → TGW)

═══ 引き渡し線: SSM パラメータ ═══

この切り方は AWS の文脈で VPC vending と呼ばれるパターンです。嬉しさは 2 つあります。

  1. アプリチームがルーティングを壊せない。 ルートテーブルもアタッチメントも基盤の state にあるので、アプリ側の誤 apply で「戻り経路が消えて全断」という事故が構造的に起きません。
  2. 障害の一次切り分けが境界と一致する。 「VPC 内で通らない → アプリ」「VPC を出て通らない → 基盤」。責任境界とデバッグ境界が同じ線になります。

引き渡しの契約書は SSM パラメータ

境界を引いたら、境界をまたぐ受け渡しを契約として明文化します。基盤は VPC 払い出し後に SSM パラメータを公開し、アプリチームはそれだけを読む。基盤の state やリソースを直接参照しません。

/platform/<env>/vpc_id
/platform/<env>/private_subnet_ids   # ALB / ECS 用 (2AZ)
/platform/<env>/vpc_cidr

アプリ state

基盤 state

書き出し

これが契約書

VPC 一式

SSM Parameter Store
/platform/prod/*

data source で読む

ALB / SG / ECS

「SSM が契約書」という形にしておくと、基盤が内部構造を変えても (サブネットの切り直しなど)、パラメータの互換さえ守ればアプリチームは無風です。remote state の直接参照にしないのは、state の内部構造 (output 名やモジュール構成) への密結合を避けるためです。

トレードオフも明記しておきます。この方式ではサブネットの IP 枯渇や追加サブネットの要望が基盤へのリクエストになる。だから VPC の標準形 (サブネットサイズ・AZ 数) を最初に固めて「製品のバリエーション」として管理する必要があります。ここが雑だと結局チケット地獄になるので、vending 方式の成否は VPC 標準形の設計次第です。

リクエストが通るまで — 責任境界をフローで見る

1 本のリクエストが通るまでの流れを、どのステップが誰の管轄かと合わせて追います。

ECS Service(アプリ)internal ALB(アプリ)Transit Gateway(基盤)Route 53 PHZ(アプリ: Prod 側が所有)Dev クライアント(アプリ)ECS Service(アプリ)internal ALB(アプリ)Transit Gateway(基盤)Route 53 PHZ(アプリ: Prod 側が所有)Dev クライアント(アプリ)PHZ はクロスアカウント関連付けでDev VPC からも引ける (仕組みは基盤が用意)dev-rt に Prod VPC への経路があるから届く。prod-rt にも Dev への戻り経路が必要 (忘れると片道通行)SG: client-SG からの 443 のみ許可TLS: ACM 公開証明書で終端api.internal.example.com を解決ALB のプライベート IPHTTPS :443 (VPC ルートテーブルの経路で TGW へ)Prod VPC へ転送:8080 (2AZ に分散)応答 (SG はステートフルなので戻りは自動)

DNS・SG・ALB はアプリの管轄、TGW の経路は基盤の管轄。途中の Note に書いたとおり、このフローが崩れる典型は「戻り経路の入れ忘れ」と「PHZ 関連付け忘れ (ping は通るのに名前が引けない)」で、どちらが起きたかで問い合わせ先が変わることもフローから読み取れます。

構築フロー — apply 順序がそのまま承認フローになる

この構成の面白いところは、Terraform の apply 順序がチーム間の承認フローとして機能することです。ハブ (TGW) はスポークの存在を知らないと経路を書けないため、初回構築は次の順序になります。

アプリチーム(spoke state)基盤チーム(network state)アプリチーム(spoke state)基盤チーム(network state)default assoc/prop は無効にしておく(アタッチしただけでは何処にも届かない)③ が実質の「承認」。基盤が RT に載せて初めて開通する① apply: TGW + RAM 共有 + Egress VPCTGW ID を SSM で公開② apply: VPC + TGW アタッチメント作成(Name タグを付与)アタッチメント完成 (タグが目印)③ apply (2 回目): タグでアタッチメントを拾いルートテーブルに association / propagation④ apply: ALB / SG / ECS

ポイントは②と③の間です。TGW の default route table association / propagation を無効にしてあるので、スポークがアタッチしただけでは、どこにも届きません。ルートテーブルへの関連付けは TGW 所有者 = 基盤チームにしかできない。つまり「繋ぎたい」というアプリチームの意思表示 (アタッチ) と、「この環境ポリシーで繋ぐ」という基盤の判断 (RT 関連付け) が、IAM とリソース所有権のレベルで分離されています。

申請書やレビュー会ではなく、アーキテクチャで統制する。これがこの構成の一番の価値だと思っています。

「network を 2 回 apply」は一見ダサいですが、ハブ&スポークの構造的な性質 (ハブはスポークを知らないと経路を書けない) の素直な表現なので、受け入れて CI で直列に流すのが正解です。

SG はアプリチームのもの — 3 着のチェーン

引き渡し線の下、アプリチーム側の SG 設計も図にしておきます。基本形は別 SG + 参照のチェーンです。

Prod アカウント (アプリ)

Dev アカウント (アプリ)

参照される

参照される

client-SG
out: 443

alb-SG
in: 443 from client-SG
out: 8080 to ecs-SG

ecs-SG
in: 8080 from alb-SG
out: 443 (ECR 等)

SG には「着る」(そのリソースの身分証になる) と「参照する」(ルールの相手欄に書く) の 2 つの役割があり、このチェーンは「alb-SG は client-SG を着た者からの 443 だけ受ける」という参照の連鎖です。IP アドレスが 1 つも登場しないので、スケールしても IP が変わっても追従します。

ここで 1 つアップデートの補足を。以前は「TGW 越えの SG 参照は不可なので CIDR で書く」が定石でしたが、2024 年 9 月に TGW 越えの SG 参照が GA になりました。使うための条件があります。

項目内容
TGW 本体SecurityGroupReferencingSupport を有効化 (デフォルト無効)
VPC アタッチメント同設定が必要 (こちらはデフォルト有効)
方向インバウンドルールのみ (アウトバウンドは非対応)
範囲同一リージョン・同一 TGW に接続された VPC 間
クロスアカウント"account-id/sg-id" 形式で参照
料金追加なし

運用としては、まず CIDR で通してから SG 参照に切り替えるのがおすすめです。SG 参照が通らないとき (有効化漏れ・リージョンまたぎ・provider が古い) に、SG のせいかルーティングのせいかを切り分けやすくなります。

グレーゾーンの判定 — リトマス試験紙

境界の原則を決めても、個別の判断で迷うものは残ります。そのときは次の 3 条件で判定します。

① まとめると得をする ② いずれ統制したくなる ③ チームごとの差がない — 3 つ揃ったら基盤に引き取る

Yes

No

Yes

No

Yes

No

① まとめると得?

② いずれ統制したい?

アプリチーム

③ チーム差がない?

基盤チーム

対象① 集約効果② 統制欲求③ 無差別性判定
Egress (NAT 集約)○ NAT 費用○ 出口統制は必ず来る○ 「外に出たい」だけ基盤
VPC エンドポイント (各 VPC 配置)アプリ (モジュールのオプション)
ACM / 証明書×△ 規約のみ× ドメインはアプリ固有アプリ (発行規約は基盤が決める)
SG×× (RT で担保済み)× アプリ仕様そのものアプリ
監視ベースライン (フローログ・必須タグ)基盤

Egress は 3 条件が綺麗に揃う代表例です。NAT 集約の節約効果は全チーム分をまとめて初めて生まれ、「外向き通信を絞りたい」は監査の文脈でほぼ確実にやってきて、そのとき出口が 1 箇所なら基盤側だけの変更で済む。逆に SG は 3 条件とも満たさないので、どれだけ「ネットワークっぽく」見えてもアプリ側です。

まとめ

1 人で全部やっている環境でも、この線で state を切っておくと「基盤の自分」と「アプリの自分」が分離できて、将来チームに分かれるときそのまま渡せます。

← Back to all posts