インフラ構成
概要
Jester は AWS 上にサーバーレスアーキテクチャで構成される。 API Gateway + Lambda を中心に、VPC 内の Private Subnet に各 Lambda と DocumentDB を配置し、 S3・SQS・Bedrock を組み合わせた非同期処理パイプラインを採用する。
生成処理は「生成(AI)」と「HTML 構築・保存(レンダリング)」を SQS で分離した2段構成とし、 記事・ブログそれぞれに独立したパイプラインを持つ。 WAF による前段保護、VPC Endpoint による閉域通信、Session Manager 経由の運用アクセスを組み合わせ、 セキュアな構成とする。
構成図
flowchart LR
Client([管理画面 / クライアント]) -->|HTTPS| WAF[AWS WAF]
WAF --> APIGW[API Gateway]
subgraph AWSCloud[AWS Cloud]
subgraph VPC[VPC]
subgraph AppSubnet[Private Subnet: App]
AppLambda[App Lambda<br/>odds_poc_app]
end
subgraph GenSubnet[Private Subnet: 生成系 Lambda]
ArticleGen[article_generation]
BlogGen[blog_generation]
BlogRewriter[blog_rewriter]
ThumbLambda[thumbnail_generation]
end
subgraph BuildSubnet[Private Subnet: ビルド系 Lambda]
ArticleBuilder[article_builder]
BlogBuilder[blog_builder]
end
subgraph DBSubnet[Private Subnet: DB]
DocumentDB[(Amazon DocumentDB)]
EC2[Amazon EC2<br/>踏み台]
EC2 --> DocumentDB
end
VPCE_Int[VPC Endpoint<br/>Interface]
VPCE_S3[VPC Endpoint<br/>Gateway: S3]
end
SQS_ArticleGen[SQS<br/>article-generation]
SQS_ArticleBuild[SQS<br/>article-builder]
SQS_BlogGen[SQS<br/>blog-generation]
SQS_BlogRewrite[SQS<br/>blog-rewriter]
SQS_BlogBuild[SQS<br/>blog-builder]
SQS_Thumb[SQS<br/>thumbnail-generation]
S3[(Amazon S3)]
CloudFront[Amazon CloudFront]
Bedrock[(Amazon Bedrock)]
VPCE_Op[VPC Endpoint<br/>Session Manager]
end
APIGW --> AppLambda
AppLambda --> VPCE_Int
VPCE_Int --> SQS_ArticleGen
VPCE_Int --> SQS_BlogGen
VPCE_Int --> SQS_BlogRewrite
VPCE_Int --> SQS_Thumb
SQS_ArticleGen --> ArticleGen
SQS_BlogGen --> BlogGen
SQS_BlogRewrite --> BlogRewriter
SQS_Thumb --> ThumbLambda
ArticleGen --> SQS_ArticleBuild
BlogGen --> SQS_BlogBuild
BlogRewriter --> SQS_BlogBuild
SQS_ArticleBuild --> ArticleBuilder
SQS_BlogBuild --> BlogBuilder
AppLambda --> VPCE_S3
VPCE_S3 --> S3
S3 --> ArticleGen
S3 --> BlogGen
S3 --> BlogRewriter
S3 --> ThumbLambda
S3 --> CloudFront
CloudFront --> Client
ArticleGen --> Bedrock
BlogGen --> Bedrock
BlogRewriter --> Bedrock
ThumbLambda --> Bedrock
AppLambda --> Bedrock
AppLambda --> DocumentDB
ArticleGen --> DocumentDB
BlogGen --> DocumentDB
BlogRewriter --> DocumentDB
ArticleBuilder --> DocumentDB
BlogBuilder --> DocumentDB
ThumbLambda --> DocumentDB
Compass([MongoDB Compass]) --> SessionMgr[Session Manager]
SessionMgr --> VPCE_Op
VPCE_Op --> EC2
AWS サービス一覧
| サービス | 用途 |
|---|---|
| AWS WAF | API Gateway 前段の Web アプリケーションファイアウォール |
| Amazon API Gateway | REST API のエンドポイント管理 |
| AWS Lambda | サーバーレスコンピューティング(7関数。すべてコンテナイメージでデプロイ) |
| Amazon DocumentDB | メインデータベース(MongoDB 互換)。VPC 内の Private Subnet に配置 |
| Amazon EC2 | DocumentDB 接続用の踏み台インスタンス。Session Manager 経由でアクセス |
| Amazon S3 | レース動画・コース画像・アップロードメディア・サムネイルの保存 |
| Amazon CloudFront | 非公開 S3 バケットのサムネイル画像を配信 |
| Amazon SQS | Lambda 間の非同期メッセージキュー(DLQ 設定あり) |
| Amazon Bedrock | 生成 AI の推論(Nova Pro / Claude Sonnet / Titan Text Embeddings v2) |
| Amazon ECR | Lambda のコンテナイメージレジストリ |
| VPC Endpoint | VPC 内リソースから S3・SQS 等への閉域通信。Interface 型 / Gateway 型を用途別に配置 |
| AWS Systems Manager Session Manager | EC2 への運用アクセス経路 |
Lambda 一覧
| Lambda 名 | 役割 | 言語 | トリガー |
|---|---|---|---|
App(odds_poc_app) |
REST API の処理。CRUD 操作・各種非同期ジョブの SQS 投入・関連情報の生成 | Python | API Gateway |
article_generation |
レース結果10秒サマリーを生成し、article-builder キューへ送信 |
Python | SQS(article-generation) |
article_builder |
生成結果を React コンポーネントで HTML 化し、article.body_html へ保存 |
TypeScript | SQS(article-builder) |
blog_generation |
レース回顧・予想ブログを書き下ろし、blog-builder キューへ送信 |
Python | SQS(blog-generation) |
blog_rewriter |
過去ブログをリライトし、blog-builder キューへ送信 |
Python | SQS(blog-rewrite / blog-rewriter) |
blog_builder |
ブログ本文を記事テンプレートで HTML 化し、blog.body_html へ保存 |
TypeScript | SQS(blog-builder) |
thumbnail_generation |
レース動画から AI が選んだ瞬間のフレームを切り出し、S3 へ保存 | Python | SQS(thumbnail-generation) |
各 Lambda は VPC 内の Private Subnet に配置され、S3・SQS・Bedrock との通信は VPC Endpoint を経由する。
Bedrock 推論プロファイルとコントロールプレーン
VPC 内 Lambda は Bedrock のコントロールプレーン(bedrock.*.amazonaws.com)へ到達できないため、
推論プロファイル ARN を使う際は base_model_id を明示して GetInferenceProfile の呼び出しを
スキップしている。明示しないとタイムアウトする。
SQS キュー一覧
| キュー名 | 送信元 | 受信先 | 備考 |
|---|---|---|---|
article-generation |
App Lambda | article_generation | 記事生成・再生成リクエスト時に送信 |
article-builder |
article_generation | article_builder | 記事生成完了時に生成結果を送信 |
blog-generation |
App Lambda | blog_generation | レース回顧・予想ブログの生成リクエスト時に送信 |
blog-rewrite |
App Lambda | blog_rewriter | POST /v1/blogs/generation(過去ブログからブログを作る旧経路)時に送信。ペイロードは blog_id / old_blog_id のみでレースを含まない |
blog-rewriter |
App Lambda | blog_rewriter | POST /v1/old_blogs/{old_blog_id}/rewrite 時に送信。ペイロードに race_id / racing_type を含む |
blog-builder |
blog_generation / blog_rewriter | blog_builder | ブログ本文の生成完了時に送信 |
thumbnail-generation |
App Lambda | thumbnail_generation | サムネイル生成リクエスト時に送信 |
各キューには DLQ(Dead Letter Queue)を設定し、最大試行回数を超過したメッセージを転送する。
リライトのキュー URL は2つある
App Lambda はリライトの投入先として BLOG_REWRITER_QUEUE_URL(POST /v1/old_blogs/{id}/rewrite)と
BLOG_REWRITE_QUEUE_URL(POST /v1/blogs/generation)の2つの環境変数を持つ。
受信側はどちらも blog_rewriter Lambda で、同じ形式のメッセージを処理する。
最終試行の扱い
各 Lambda は SQS レコードの ApproximateReceiveCount と、キューの redrive_policy.maxReceiveCount
(環境変数 SQS_MAX_RECEIVE_COUNT。既定 2)から「この配信が最終試行か」を判定する。
| 状況 | 挙動 |
|---|---|
| 最終試行以外での失敗 | 例外を再送出し、SQS の再配信による自己復旧に委ねる |
| 最終試行での失敗(記事) | article を status=failed にして理由を保存。DLQ には残さない |
| 最終試行での失敗(ブログ) | status=generating の blog を物理削除。DLQ には残さない |
DLQ に残さない理由
同じ入力で作り直しても同じ理由で失敗するだけであり、対象レコードも存在しない(または 失敗として記録済み)ため、DLQ に残しても再処理する価値がない。DB とキューの状態を揃える方針。
S3 バケット構成
| 用途 | オブジェクトキー |
|---|---|
| レース動画 | auto_racing/video/{race_id}.mp4 / bicycle_racing/video/{race_id}.mp4 / horse_racing/video/{race_id}.mp4 |
| コース画像(競馬のみ) | horse_racing/course_image/{race_id}.gif |
| サムネイル(記事) | {racing_type}/thumbnail/{race_id}.jpg |
| サムネイル(ブログ) | {racing_type}/thumbnail/blog_{blog_id}.jpg |
| アップロードメディア | {media.name} |
S3 へのアクセスは VPC Endpoint(Gateway 型)経由で行う。 バケットは非公開(Block Public Access)のため、ブラウザから参照する画像は次のいずれかで配信する。
| 用途 | 配信方法 |
|---|---|
| メディア API のレスポンス | 有効期限 1 時間の S3 署名付き URL を都度生成 |
| ブログ本文に埋め込むサムネイル | CloudFront 経由の公開 URL |
VPC 構成
- VPC 内に用途別の Private Subnet を配置
- App Lambda 用 Subnet
- 生成系 Lambda 用 Subnet(article_generation / blog_generation / blog_rewriter / thumbnail_generation)
- ビルド系 Lambda 用 Subnet(article_builder / blog_builder)
- DocumentDB / EC2(踏み台)用 Subnet
- VPC Endpoint
- Interface 型: Lambda から SQS その他 AWS API への通信に使用
- Gateway 型: 各 Lambda から S3 への通信に使用
- 運用アクセス用: Session Manager から EC2 への通信に使用
デプロイ
各 Lambda は コンテナイメージとしてデプロイする。 GitHub Actions が Docker イメージをビルドして ECR へプッシュし、Lambda の関数イメージを更新する。
| Lambda | ECR リポジトリ / 関数名(dev) | ワークフロー |
|---|---|---|
| App | dev-jester-odds-poc-app / dev-jester-backend-odds-poc-app |
dev-odds-poc-app-deploy.yml |
| article_generation | dev-jester-article-generation |
dev-article-generation-deploy.yml |
| article_builder | dev-jester-article-builder |
dev-article-builder-deploy.yml |
| blog_generation | dev-jester-blog-generation |
dev-blog-generation-deploy.yml |
| blog_rewriter | dev-jester-blog-rewriter |
dev-blog-rewriter-deploy.yml |
| blog_builder | dev-jester-blog-builder |
dev-blog-builder-deploy.yml |
| thumbnail_generation | dev-jester-thumbnail-generation |
dev-thumbnail-generation-deploy.yml |
TypeScript の Lambda(article_builder / blog_builder)は、イメージビルド前に
npm run typecheck / npm run lint を実行する。
管理画面(jester-user-web)は静的ファイルとしてビルドし、S3 + CloudFront へ同期する。
運用アクセス
- Lambda 間および AWS サービス間の通信は IAM ロールによるサービス間認証で保護する
- DocumentDB への運用アクセスは MongoDB Compass → Session Manager → VPC Endpoint → EC2 → DocumentDB の経路で行う
- DocumentDB への接続は TLS(
global-bundle.pem)を使用し、retryWrites=Falseで接続する(DocumentDB はリトライ可能書き込みに非対応)