15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ウン十台の検証環境の管理を1つのWebコンソールに統合した話 〜HTMX + サーバレスで月額50円運用〜

15
Last updated at Posted at 2026-07-15

こちらの記事は 「Medley Summer Tech Blog Relay」3日目 の記事です :sunny:

はじめに

こんにちは。株式会社メドレーの SRE グループ所属の森川です⚙️

弊社が運営するサービス ジョブメドレー では開発者向けに「sb 環境」と呼ばれる検証環境を多数(数十台)運用しています。
開発者が sb 環境を利用する際、起動や設定の変更などを行うためには、複数のツールにまたがって操作する必要があり、より使いやすくできる余地があると感じていました。

そこで、sb 環境管理の入口を 1 つにまとめた「sb-console」という Web アプリケーションを提案し、構築・移行を行いました。

スクリーンショット 2026-07-08 15.00.51.png

本記事では、このアプリケーションのアーキテクチャの概要と、設計時に特に大事にした判断について記録します。

背景:散らばっていた sb 運用

sb 環境の管理は、長い間 用途ごとにツールが分散 したまま運用されていました。

  • 誰がどの sb 環境をどんな用途で使うかGoogle スプレッドシート
  • sb 環境の起動・停止・再起動Jenkins ジョブ
  • 定期的な DB データの更新をさせるかどうかAWS リソース上のタグの値

管理の正本がツールごとにバラバラで、まさに 運用でカバー な状態でした。

その結果、複数ツールを行き来する手間や、この運用方法のオンボーディングコスト、台帳と実態のズレとそれによる無駄な料金発生など、さまざまな負担が生じていました。

また、操作ログを調べようとなると、Jenkins のビルドログや CloudTrail など、こちらも複数のツールをまたいだ調査が必要になります。

実現したいこと

1 つの統合管理ツールから以下の操作がまとめて行える状態を目指しました。

  • 利用者・用途の台帳管理
  • 各 sb 環境の起動・停止・再起動の操作実行
  • 定期的な DB データの更新設定の ON/OFF 切り替え
  • 操作履歴を簡単に参照できる
  • 社員・社内 IP からのアクセスに限定

設計方針

このアプリケーションは開発者向けの操作 UI ですが、作って終わりではなく SRE が運用するツール という位置づけになります。設計の出発点に置いた運用観点は次のとおりです。

  • ランニングコストはなるべく少なく — 常時起動のサーバーは置かず、固定費が乗りにくいサーバレス構成にする
  • アプリコードのメンテナンスも軽く保つ — ランタイムやパッケージの定期的なバージョンアップ対応になるべく追われず、依存が少ない技術・構成にする
  • 既存 sb 環境インフラには手を入れず、操作の入口だけ足す — 今回のスコープはあくまで管理窓口の統合。既存の sb 環境のインフラ構成を変えることはしない

アーキテクチャ概要

採用技術

  • フロント — HTMX + Lambda SSR(HTML / fragment をサーバー側で生成)
  • バックエンド — Node.js、AWS SDK v3、Lambda(container image / arm64)
  • IaC — Terraform
  • AWS — CloudFront、CloudFront Functions、KeyValueStore、API Gateway、Lambda、DynamoDB、S3、ECR など
  • 認証 — Google OAuth(Workspace ドメイン限定)
  • デプロイ — インフラは Terraform apply、アプリ image は shell script
  • 検証環境 — ローカル preview サーバ(AWS 非接続・サンプルデータ)、npm test(必要に応じて Playwright)

全体構成図

sb-console_architecture.png

主要コンポーネント

レイヤ 技術 役割
エッジ CloudFront + CloudFront Functions TLS 終端、静的 JS 配信、Functions での IP 制限
API API Gateway (HTTP API) Lambda へのルーティング
コンピュート Lambda(container image) HTML 生成、インフラ操作、認証
認証 Google OAuth(Workspace ドメイン限定) 社員ログイン、セッション Cookie 発行
アプリデータ DynamoDB 台帳・操作ログ

典型フロー(ログイン〜ダッシュボード表示)

未ログインのユーザがアクセスした場合、Google OAuth 経由で認証してからダッシュボードが表示されます。

認証は Authorization Code Flow です。/auth/login で state を Cookie に保存して Google へリダイレクトし、/auth/callback で state を検証したうえで id_token を確認します。セッション Cookie は Lambda 側で署名し、時限的に有効です。許可ドメイン以外のユーザはここで弾かれます。

ログイン後に表示されるダッシュボードは、sb 環境の一覧表です。起動・停止などの操作後も、HTMX が Lambda から返された HTML の一部分(fragment)だけを差し替えて画面に反映します。ページ全体を再読み込みする必要はありません。

設計で大事にしたこと

設計・実装を進める上で特に大事にした判断についてまとめます。

  1. UI は HTMX + Lambda SSR
  2. アクセス制限は WAF ではなく CloudFront Functions
  3. Lambda は container image ランタイム
  4. インフラは Terraform、BE コードは別管理でデプロイ分離
  5. 操作ログは画面から確認できる

1. UI は HTMX + Lambda SSR

フロントエンドの構成は、設計段階でいくつかの選択肢を比較しました。

メリット 懸念
React SPA + Vite コンポーネント分割、フロントエンドエコシステムが豊富 npm 依存が厚い、ビルドパイプラインが増える
素の HTML + 少量 JS 最もシンプル 部分更新の実装コスト
HTMX + Lambda SSR 部分更新が宣言的、ビルドチェーンが薄い SSR なので Lambda が HTML 生成を担う

HTMX + Lambda SSR を採用しました。

最近、npm パッケージを介したサプライチェーン攻撃の話題も多く、AI 支援で依存追加が加速しやすい環境では、フロントのビルドチェーンを意図的に薄く保つ ことに価値を感じています。HTMX 本体は npm 依存にせず vendor/htmx/2.0.4/htmx.min.js として S3 に配置し、CloudFront から配信しています。

layout.js ではその script タグに Subresource Integrity(SRI)を指定し、CDN 経由で取得する HTMX ファイルの改ざんを検知できるようにしています。CSS や画面用の JavaScript は Vite 等の bundler を使わず、Lambda が HTML 生成時にソースファイルを読み込んで <style> / <script> へ埋め込む構成です。

SSR なので HTML は Lambda が都度生成しますが、sb-console の UI は「一覧テーブル + ドロワー + HTMX 部分更新」が中心で、React ほどのクライアント状態管理は不要でした。同一 CloudFront オリジンから HTML も API も返るため、CORS 設定も不要です。

スクリーンショット 2026-07-08 15.05.40.png

2. IP 制限は WAF ではなく CloudFront Functions

当初は CloudFront と API Gateway の両方に WAF を付け、社内 IP allowlist を実現する方法を考えていました。しかし、WAF の Web ACL は 月額固定費が約 $12 かかり、リッチなルールは不要かつ対象ユーザが社内数十人程度の当ツールにはオーバーだと判断しました。

最終構成は次のとおりです。

  • CloudFront Functions(viewer-request)で event.viewer.ip を allowlist と照合
  • allowlist の CIDR は CloudFront KeyValueStore に格納
  • API Gateway は CloudFront の背後に置き、CloudFront が付与するカスタムヘッダを Lambda 側で検証して 直 URL アクセスを拒否

CloudFront Functions の実行単価は $0.10/百万リクエスト程度で、WAF の固定費と比べると桁が違います。トレードオフとして、WAF 固有のマネージドルール(Bot 対策など)は使えませんが、社内 IP 限定の内部ツールとしては十分でした。

3. Lambda は container image ランタイム

Lambda の package 形式は container image を選びました。

言語ランタイムを使う場合、AWS がサポートするバージョンの EOL に合わせて定期的なランタイム更新が必要になります。container image なら Dockerfile 側で言語バージョンを管理でき、アップデートのタイミングをハンドリングできるため、EOL に追われるような運用を避けることができます。

Lambda は全部で 11 関数 ありますが、1 つの共通イメージ を使用するようにしています。Lambda ごとに起動時の handler 指定で役割を切り替え、画面表示・sb 操作・認証などを分担しています。IAM ロールも関数ごとに分離し、最小権限を守る構成にしています。

4. インフラは Terraform、BE コードは別管理でデプロイ分離

インフラ(API Gateway、Lambda 定義、IAM、CloudFront、DynamoDB、ECR など)は Terraform で管理しています。一方、Lambda の コンテナ image の更新は shell script から実行するようにしました。

Terraform 側では aws_lambda_function の定義に lifecycle { ignore_changes = [image_uri] } を入れ、image の実体更新は Terraform の外に出しています。

resource "aws_lambda_function" "this" {
  for_each     = var.lambda_functions
  package_type = "Image"
  image_uri    = var.image_uri  # 初期値のみ。以降は deploy-backend.sh が更新

  image_config {
    command = [each.value.command]  # 11 関数で handler を切替
  }

  lifecycle {
    ignore_changes = [image_uri]
  }
}

これにより、UI の HTML を直して Lambda の image を更新するたびに Terraform plan に image 差分が出ることを防いでいます。インフラもアプリコードも SRE 管理の対象ではあるものの、BE 側の開発サイクルにインフラの plan/apply を巻き込まない、という意図です。

デプロイ script がやっていることは本質的に docker build → ECR push → update-function-code の 3 ステップだけなので、SAM や CDK を追加するほどの複雑さではありません。

5. 操作ログは画面から確認できる

ユーザが操作を行った際のログは DynamoDB に保管するようにしました。このログはアプリケーション上からも参照できるようにしており、「誰がどの sb をいつ操作したか」を 利用者がすぐに確認 することができます。

Lambda の実行時エラー(IAM 不足、API 失敗など)は CloudWatch Logs に流れます。

操作ログと実行ログは用途が違うので、保存先を分けています。

観点 DynamoDB CloudWatch Logs
用途 アプリの監査ログ Lambda の実行ログ
UI からの Query sb 単位・全体時系列で GSI 設計が容易 Logs Insights は別 UI、クエリ学習コスト
スキーマ sb + ts + action + actor を明示 非構造テキスト
保持期間 TTLで自動削除 保持設定は Log Group 単位

スクリーンショット 2026-07-08 15.16.40.png

コスト

試算条件

  • 利用規模: 1 日 20-30 人(社内のみ)。1 セッションあたり平均 30 リクエスト → 約 2 万リクエスト/月
  • リージョン: ap-northeast-1(ACM のみ us-east-1、CloudFront Functions はエッジロケーションで実行)
  • アクセス制限: WAF 不使用、CloudFront Functions で IP 判定

内訳

試算は 約 2 万リクエスト/月 を前提に、従量課金の主要サービスを積み上げています。

サービス 月額(USD) 備考
WAF $0 当初案 ~$12/月 → 不使用
CloudFront ~$0 社内トラフィックは無料枠内
CloudFront Functions ~$0 2 万 invocations × \$0.10/100 万 ≒ \$0.002
API Gateway (HTTP API) ~$0.02 2 万 req × $1.17/100 万
Lambda ~$0 2 万回・短時間実行は無料枠内
DynamoDB (オンデマンド) ~$0.05 台帳・監査ログの読み書き
S3 / ECR / CloudWatch Logs ~$0.15 ECR イメージ保管・Log Group が主
合計 約 $0.2〜0.3/月 約 ¥30〜50/月

WAF を CloudFront Functions に置き換えたことで 固定費がほぼ消滅し、総額は 月 1 ドル未満 に収まります。

統合して変わったこと

sb-console を使い始めてから、日常の sb 運用は次のように変わりました。

  • 操作入口が 1 つになった — 利用者管理、起動・停止などの操作、DB データ更新ハンドリングが 1 つの画面からできる。もう複数画面を行き来する必要はありません! :ok_woman:
  • どの sb 環境が起動中か一目で分かるようになった — 「起動している = コスト発生している」なので、コスト管理の観点からも開発メンバーの意識付けに一役買ってくれています :moneybag:
  • 操作者の記録が UI からすぐ確認できる — 誰がいつどんな操作をしたのか、画面上ですぐ確認できます :pencil:
  • 運用コストはほぼ気にならない — ランニングコストも格安ですし、依存技術が少ないためセキュリティリスクも比較的少なくなります :blush:

主な利用者である開発メンバーだけではなく、インフラを管理している SRE グループへの恩恵も出始めています!

まとめ

sb 環境の操作を Web コンソールに集約する sb-console を構築しました。学びを 3 点にまとめます。

  1. HTMX + SSR は十分戦える — npm ビルドチェーンを薄く保ちつつ、部分更新 UI が作れる。設計時に SPA も比較したが、規模と要件なら SSR で足りた
  2. WAF が必須とは限らない — IP allowlist だけが要件なら CloudFront Functions + KVS で固定費をほぼゼロにできる
  3. Terraform とアプリデプロイの分離は ignore_changes でシンプルに — インフラと BE 開発の変更頻度差を、無理に 1 本のパイプラインにまとめなくてよい

利用メンバーから改善案のフィードバックをもらうなど、よいサイクルも生まれています。運用コストは抑えつつ、今後も改善していければと思います。


Medley Summer Tech Blog Relay 4日目の記事は林田さんです!

明日もお楽しみに!✨


We're hiring!

メドレーでは各種エンジニアを絶賛募集中です!
カジュアル面談いつでも Welcome ですので、どうぞお気軽にお問い合わせください🍉🌻

15
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?