コンテナ使い捨て環境のつくりかた

コンテナで動くアプリの開発用環境を AWS に立てるとき、入口(配信の形)をどう選ぶか。その判断基準を人ではなくツールに埋め込む試み。課金形態・タイムアウト上限・認証方式・ルーティング単位の4軸を、リポジトリの走査 → 判定(純関数)→ 提示に分けて実装し、診断コマンドとして切り出すまで。

TL;DR

  1. コンテナで動くアプリの開発用環境をどう組むかは、毎回同じ判断を人がやり直している。 これをツールに埋め込めないか、という話
  2. 判断の中身は 課金形態・タイムアウト上限・認証方式・ルーティングの単位 の 4 軸で、 とくに 「共通基盤を共有すべきか」は入口の課金形態が決める
  3. リポジトリを読めば 8 割は自動で決まる。残りは人に聞く。 実装は「走査 → 判定(純関数)→ 提示」に分け、診断コマンドとして単体でも使えるようにした

何を支援したいのか

PR ごと・E2E ごとに使い捨てのコンテナ環境を AWS に立てる、という文脈。 毎回「Lambda に包むか、ECS で動かすか」「入口は ALB か API Gateway か」「認証はどうするか」を 考え直しており、しかも判断の根拠が暗黙知になっている。ここをツールが引き受けたい。

本番と違って、開発用環境には以下の性質がある。本番構成の縮小コピーが最適解にならないのは このためで、判断基準そのものが本番とは別物になる。

この 4 つが、後で効いてくる。

ツールが選ぶ対象: 入口は 5 通り

認証を前段で

compute あり・複数コンテナ

compute あり・単一

compute なし

オリジンは何でもよい

static

S3 + CloudFront

Lambda + Web Adapter

API Gateway

+ VPC Link + ECS

共有 ALB + ECS

CloudFront

+ Lambda@Edge

実測を並べるとこうなる(東京リージョン、環境 1 つあたり)。

入口固定費環境作成環境削除リクエスト上限
static(S3+CloudFront)なし数十秒数秒—
Lambda + LWAなし2〜3 分2〜3 分15 分
API Gateway + ECSなし3 分 40 秒2 分 18 秒30 秒
共有 ALB + ECS約 18 ドル/月2 分 46 秒3〜4 分4000 秒
CloudFront + Lambda@Edgeなし(前段のみ)—約 60 秒

判断基準 1: 課金形態が「共有すべきか」を決める

ここが一番大きい。固定費があるかどうかで、ベース(共通基盤)の設計が反転する。

ある(ALB)

ない(CloudFront / API Gateway)

入口に固定費はあるか

共有が必須

PR ごとに作ると PR 5 本で月 90 ドル

環境が作るのはリスナールールと

ターゲットグループだけ(無料)

共有しなくてよい

アプリごと・環境ごとに作っても

費用はほとんど増えない

CloudFront は従量課金、ACM は無料、Route53 レコードは事実上ゼロ。per-app でベースを 増やしても増えるのは初回の証明書検証 + 配信作成の待ち時間(約 15 分)だけで、 月額はほぼ変わらない。だから「アプリごとに 1 つ」が既定でよい。

ALB は逆で、置いておくだけで課金される。プレビュー環境と両立させるには ALB・証明書・DNS・ECS クラスタを共有し、環境が作るのはリスナールールと ターゲットグループ(どちらも無料)だけにする。

環境 pr-43

環境 pr-42(CFN スタック 1 個)

共有ベース(1 回だけ作る)

ALB(固定費)

ワイルドカード証明書

Route53 *.example.com

ECS クラスタ

リスナールール

host = pr-42.…

ターゲットグループ

ECS サービス(Fargate)

リスナールール

ターゲットグループ

ECS サービス

判断基準 2: タイムアウト上限が構成を殺すことがある

意外に見落としやすいのがこれ。WebSocket や長い処理があると、入口の候補が一気に減る。

入口上限WebSocket
API Gateway HTTP API30 秒実質不可(別途 WebSocket API が要る)
CloudFront約 60 秒(オリジン応答)通せるが長時間は不利
ALB4000 秒まで設定可✅
Lambda15 分単一コンテナなら可

つまり WebSocket / SSE / 長いバッチ API があるなら、固定費を払ってでも ALB、という 判断になる。逆にリクエストが常に短いなら、固定費ゼロ側を選ばない理由が無い。

判断基準 3: 認証方式 — 開発環境は社内限定が普通

「Google Workspace の社員だけ入れる」はよくある要件で、これが入口の選択を一番強く 縛る。ブラウザのログインリダイレクト(OIDC の認可コードフロー)を誰が担うかが 入口ごとに違うからだ。

アプリGoogle入口ブラウザアプリGoogle入口ブラウザGET /302 → Googleログインcodecodetoken 交換 + ID トークン検証セッション CookieGET /(Cookie あり)転送(+ claims をヘッダで)

この「E」を誰がやるか:

入口ブラウザのログイン実装量
ALBauthenticate-oidc アクションで標準搭載コード 0 行(設定のみ)
CloudFront + Lambda@Edgeviewer-request で自前実装コードあり。ただし static も守れる
API Gateway HTTP APIできない(JWT オーソライザーはトークン検証のみ)前段に CloudFront が要る
S3 単体できない同上

ALB の月 18 ドルは、この文脈では 「認証をコード 0 行で買う値段」 という意味を持つ。 一方 Lambda@Edge は固定費ゼロで、静的配信にも同じ認証をかけられる唯一の選択肢。 代わりに環境変数が使えない(シークレットをコードに焼くか SSM から読む)、 デプロイ伝播が数分かかる、という運用コストを払う。

hd はヒントであって強制ではない

Google の OIDC で「Workspace のドメインだけ」を実現するとき、認可リクエストに hd=example.com を付ける方法がよく紹介される。これは UI のヒントで、強制ではない。 きちんと締めるなら ID トークンの hd クレームを検証する。ALB なら x-amzn-oidc-data(署名付き JWT)がアプリに渡ってくるので、そこを見る。

判断基準 4: ルーティングの単位 — URL を変えないために

プレビューの URL をパス型にすると、アプリ側に影響が出る。

https://preview.example.com/pr-42/...   ← basePath 対応、Cookie スコープ、
                                           SPA ルーティングが全部影響を受ける
https://pr-42.app.example.com/...       ← 本番とほぼ同じ挙動

Host ベースにするのが基本。ここで入口ごとの制約が出る:

API Gateway: 環境ごとに API

API(pr-42)

env pr-42

API(pr-43)

env pr-43

ALB: 1 本を共有し Host で分岐

ALB

rule: pr-42.…

env pr-42

rule: pr-43.…

env pr-43

ちなみにワイルドカード証明書は 1 ラベルしか覆えない。*.example.com は pr-42.example.com に一致するが pr-42.todo.example.com には一致しない。 1 つのドメインを複数アプリで共有したいなら、todo--pr-42.example.com のように project を同じラベルに畳むか、アプリを足すたびに証明書へ SAN を追加することになる。

4 軸を 1 枚のフローに落とす

ここまでの判断基準を、ツールが実行できる形にするとこうなる。

無い(ビルド成果物だけ)

不要

必要

ある

必要

ある

ない

はい / 既に ALB がある

いいえ

不要

ある

ない

複数

単一

リポジトリを読む

サーバはあるか

認証は要るか

static

S3 + CloudFront

CloudFront + Lambda@Edge

(ALB は S3 を守れない)

認証は要るか

WebSocket / 長い処理

共有 ALB

authenticate-oidc はコード 0 行

固定費を許容できるか

共有 ALB

CloudFront + Lambda@Edge

WebSocket / 長い処理

共有 ALB

30 秒・60 秒の壁を越える唯一の道

サービスは複数か

API Gateway + ECS

Lambda + Web Adapter

原則は 「安くて早い方から降り、その構成では無理な理由があるときだけ次へ」。 プレビュー環境は本番と同じ構成である必要がないので、本番が ALB でもプレビューは API Gateway で足りることは多い。

入力はリポジトリから取れる

ツール化で一番効いたのはここで、判定の入力はほとんどリポジトリに書いてある。

判定に要る事実どこから読むか
サーバがあるかDockerfile / EXPOSE / compose の ports
サービス数compose の services / cmd/*/main.go / Dockerfile の数
WebSocket / SSEgorilla/websocket, socket.io, EventSource などの依存
listen ポートEXPOSE(マルチステージなら最後)/ compose
DB を使うかmysql2, pg, go-sql-driver/mysql などの依存

逆にリポジトリからは絶対に分からないこともある。

だから 自動判定ではなく「検出で既定を決める推薦」 にするのが正しい。 機械が決めるのは既定値だけで、決められない部分は聞く。そして選ばなかった選択肢の 理由を見せることが大事で、「なぜ Lambda ではなく ECS なのか」が分からないまま 生成されるより、1 行の理由がある方がずっといい。

実装: 走査・判定・実行を分ける

kagerou(PR や E2E で使い捨てるコンテナ環境を 作る OSS)に実装した。支援ツールとして成立させる鍵は、層を分けることだった。

リポジトリ

appscan.Scan

ファイルを読むだけ

stdlib のみ

Facts

(事実)

recommend.Entry

純関数

AWS も設定も触らない

Options

(認証が要るか等)

Choice

既定 + 選ばなかった理由

ウィザード(既定値として使う)

diagnose(表示するだけ)

事実の収集・判定・実行を分けておくと、判定だけを単体で使える。実際 kagerou diagnose は、どのリポジトリでもファイルを 1 つも書かず AWS も呼ばずに 実行できる:

$ kagerou diagnose --dir ../some-app
kagerou diagnose  acme/shop

detected
  framework    go
  services     3
  port         8080
  dockerfile   yes

recommended
❯ apigateway  複数サービスを固定費ゼロで動かせる(リクエストは 30 秒まで)
  alb         上限は無いが、月 18 ドル前後の固定費がかかる
  lambda      単一コンテナ向け(複数サービスを検出)

no files were written and no AWS calls were made.

仮定を変えて試せるのも実用的で、--auth を付けると既定が入れ替わる:

$ kagerou diagnose --dir ../some-app --auth
❯ edge        固定費ゼロ。static も同じ認証で守れる(認証は自前実装)
  alb         認証はコード 0 行だが、月 18 ドル前後の固定費がかかる
  apigateway  ブラウザのログインリダイレクトができない

判定が純関数なので、テストは表で書ける。これが一番の利点だと思う—— 「WebSocket があるなら API Gateway を既定にしない」のような設計判断そのものを テストで固定できる。

まとめ

コンテナ開発環境の構成を決める作業は、思ったより機械化できる。

支援ツールとして大事なのは、決められることは決め、決められないことは聞き、 選ばなかった理由を残すこと。全部自動にしようとすると、リポジトリからは分からない 組織の事情(固定費を許容できるか、社内限定にするか)で必ず破綻する。

関連: PR ごとに現れて消える環境を OSS にした、 信頼ポリシーが正しいのに OIDC が拒否される

← Back to all posts