コンテンツにスキップ

インフラ構成

概要

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 はリトライ可能書き込みに非対応)