Claude Code ランタイムモデル¶
採用方針: automedia は bootstrap メタプロジェクト + AWS 上の中央サービス として構築する完全 AWS 化モデル(Phase 1b)。Webhook 受信・スケジュール配信・Claude 実行のすべてを spin-dd AWS アカウント(profile=
spindd, regionap-northeast-1)に集約し、認証は Claude Platform on AWS の IAM 統合で完結させる。GH Actions は OpenTofu deploy と CI 検証のみに使う。
なぜこのモデルか¶
Webhook 即応 5 秒以内の要件¶
LINE follow 直後の挨拶を 5 秒以内に reply したい。spin-dd/spindd-hubspot-theme で GH Actions 経路を実測したところ約 1 分 かかった(queue + provisioning が本質的に削れない)。Webhook 即応経路は AWS Lambda 必須。
Claude Platform on AWS の発見¶
Claude Platform on AWS は Anthropic API そのもの に AWS の IAM 認証・請求・監査を統合した形態。Bedrock とは別物。
| Claude Platform on AWS | Bedrock | Anthropic API(直接) | |
|---|---|---|---|
| 運営 | Anthropic | AWS | Anthropic |
| データ処理場所 | Anthropic | AWS 内 | Anthropic |
| 認証 | AWS IAM | AWS IAM | API キー |
| 請求 | AWS 集約 | AWS 集約 | Anthropic 個別 |
| 監査 | CloudTrail | CloudTrail | Anthropic ログ |
| 最新モデル | 最速(同等) | 数週〜数ヶ月遅れ | 最速 |
| Anthropic ネイティブ機能 | フル | 一部のみ | フル |
| 価格 | Claude API と同価格 | 別建て | 公式価格 |
Claude Platform を使えば「最新モデル即時利用 + AWS 統合のメリット」を両取りでき、API キーの Secrets 管理が不要になる。
全 AWS 化の利点¶
| 観点 | 値 |
|---|---|
| Webhook 反応時間 | ≤ 5 秒 (ウォーム時 300〜500 ms) |
| スケジュール時刻精度 | 秒精度 (EventBridge 動的 at()) |
| 認証管理 | AWS IAM 一本 (Claude / 全 secret は IAM Role 経由) |
| 課金 | AWS 請求書に集約 |
| 監査ログ | CloudTrail + CloudWatch に集約 |
| 運用ハード | spin-dd 側ゼロ (AWS マネージド) |
メタプロ + 中央サービスの両立¶
automedia リポは以下を兼ねる:
- bootstrap メタプロ: 各テーマリポの
.automedia/雛形・スキルソース・CLI を配布 - AWS 中央サービス: API Gateway + Lambda 群 + EventBridge + Secrets Manager + S3 を抱える
テーマリポ側の責務は最小化:
.automedia/project.yml— テナント識別 + Backlog プロジェクトキー + LINE Channel ID.automedia/templates/— メッセージテンプレ.automedia/rules/— 配信ルール(時間帯ガード、上限等)
「テナント境界」は IAM ロール / Secrets Manager prefix / S3 prefix = 境界。Phase 2 で per-tenant AWS アカウント分散も可能。
実行ホスト一覧¶
| 処理 | 実行ホスト |
|---|---|
| LINE Webhook 即応 | AWS Lambda + API Gateway HTTP API (spin-dd) |
| Backlog Webhook (status / コメント) | AWS Lambda + API Gateway HTTP API (spin-dd) |
| 配信時刻トリガ(動的 schedule) | AWS EventBridge Scheduler(課題ごとに at(投稿日時) で登録) |
| 配信走査(日次セーフティネット) | AWS EventBridge Scheduler rate(1 day) → Scan Lambda(Webhook 取りこぼし救済) |
| 配信実行(Claude 呼び出し) | AWS Lambda (Node, Claude Agent SDK) (spin-dd) |
| 重処理(長尺生成時) | AWS ECS Fargate Task(Phase 1b 後半検討) |
| Claude モデル呼び出し | Claude Platform on AWS(IAM 統合) |
| テナント設定同期 | AWS Lambda(GitHub Push Webhook 駆動) |
| OpenTofu deploy 実行 | GitHub Actions(OIDC で AWS ロール assume) |
| bootstrap / upgrade CLI | 開発者ローカル |
| Backlog / LINE / HubSpot API | 各 SaaS |
automedia リポは AWS 上の全リソースを OpenTofu で管理する。GH Actions は配信ランタイムには関与しない。
全体図¶

実行フローはすべて AWS 内で完結。各テーマリポは設定の SoT (Source of Truth) に専念。
AWS 構成図(詳細)¶
AWS 内部のリソース構成と依存関係を 8 レイヤーに分けて整理した詳細図:

レイヤー構成¶
| レイヤー | リソース | 役割 |
|---|---|---|
| Inbound | Route 53 / ACM / API Gateway (HTTP API) | カスタムドメイン api.automedia.spin-dd.com で TLS 終端、/line/webhook/{channel_id} /backlog/webhook /github/webhook の 3 エンドポイント |
| Compute | Lambda × 5(webhook-line / webhook-backlog / sync / scan / deliver) + ECS Fargate(長尺ジョブ用、Phase 1b 後半) |
配信フローの実体。deliver のみが Claude Platform on AWS を呼ぶ |
| Async / Trigger | SQS + DLQ / EventBridge Schedule (at(投稿日時) 動的) / EventBridge Scheduler (rate(1 day) 日次) |
非同期実行と時刻トリガ |
| State / Storage | S3(テンプレ cache) / Secrets Manager(provider 単位 JSON) / DynamoDB(アプリ層 deliver locks) / OpenTofu state backend (S3 のみ、use_lockfile) |
永続化層 |
| AI | Claude Platform on AWS | Claude Agent SDK を IAM 認証で呼び出し(API キー不要) |
| Observability | CloudWatch Logs / Alarm | 全 Lambda のログ集約、Webhook 失敗率・Lambda エラー・Secret rotation 期限を監視 |
| IAM & Identity | IAM Roles × 6(Lambda 用 5 + deploy 用 1) | Lambda ごとに最小権限。deploy は GitHub OIDC で assume |
| Deploy | GitHub Actions (aws-deploy.yml) → OpenTofu (aws/tofu/) |
OIDC で AWS にロール assume → 全リソースを tofu apply |
主要フロー(色分け)¶
- 🟢 LINE 系:
LINE Platform → API GW → webhook-line Lambda → (reply ≤5s) / SQS → deliver - 🔵 Backlog 系:
Backlog → API GW → webhook-backlog Lambda → EventBridge Schedule at(投稿日時) → deliver(コマンド即時の場合は直接 deliver invoke) - ⚫ GitHub Push 系:
GitHub → API GW → sync Lambda → S3 - 🔴 Claude 呼び出し:
deliver Lambda → Claude Platform on AWS(IAM) - 🟩 LINE Reply (5s 制約):
webhook-line → LINE Platform(破線で 5 秒の SLO を明示)
設計の要点(図中サマリ)¶
- 反応時間: LINE follow ≤5s(Webhook Lambda)、Backlog トリガ 秒精度(Webhook +
at()Schedule) - 冪等性: Backlog「自動配信実行日時」+ DynamoDB lock の二重防御
- テナント分離: IAM ロール × Secrets Manager prefix × S3 prefix の三重分離
- Secret 管理:
/automedia/<tenant>/<provider>に provider 単位 JSON で格納 - 観測: CloudWatch Logs / Alarm 集約 + Backlog コメントへの結果書き戻し
- セーフティネット: 日次 Scan Lambda が Webhook 取りこぼしを救済(通常は検知 0 件)
コンポーネント¶
1. automedia リポ(メタプロ + AWS 中央サービス)¶
メタプロ責務:
- 設計ドキュメント(このサイト)
- 共有スキル
skills/automedia-*/— SDK 呼び出しから読まれる MD - 雛形
templates/.automedia/— テーマリポの初期構造 - bootstrap CLI
automedia init/automedia upgrade
AWS 中央サービス責務:
aws/lambdas/webhook-line/— LINE Webhook 即応(Node + TypeScript)aws/lambdas/webhook-backlog/— Backlog Webhook 受信(status 変化 / コメントコマンド / 投稿日時更新)。Deliver invoke または EventBridge Schedule のat(投稿日時)登録/更新/削除aws/lambdas/scan/— 日次セーフティネット(Webhook 取りこぼしを救済、rate(1 day)起動)aws/lambdas/deliver/— Claude 呼び出し + 各 SaaS 連携aws/lambdas/sync/— GitHub Push を受けて.automedia/を S3 にミラーaws/tofu/— OpenTofu モジュール群(API Gateway / Lambda / EventBridge / Secrets Manager / S3 / SQS / IAM / CloudWatch).github/workflows/aws-deploy.yml— GitHub OIDC で AWS ロール assume →tofu apply
2. 各テーマリポ spin-dd/<key>-hubspot-theme¶
ランタイム責務なし。設定 SoT + コントロールプレーン (Claude Code セッションの操作起点) を兼ねる。
.automedia/project.yml— テナント識別子 / Backlog プロジェクトキー / LINE Channel ID.automedia/templates/line/*.yml— メッセージテンプレ.automedia/rules/*.yml— 配信ルール(時間帯ガード、上限等).automedia/state/— オーディエンスキャッシュ等(commit 不要なら gitignore).claude/skills/automedia-*— automedia への指示用スキル群 (read 系は直接呼び出し、write 系は Backlog 承認経由)
GitHub Push → automedia の Sync Lambda が S3 prefix s3://automedia-tenants/<tenant>/ にミラー。テーマリポ Claude Code セッションをコントロールプレーンとする設計、Backlog 承認フロー、役割境界などは コントロールプレーンと責務分担 を参照。
3. Webhook Lambda¶
POST /line/webhook/{channel_id} を受ける。
// aws/lambdas/webhook/handler.ts (概念)
export const handler = async (event: APIGatewayProxyEventV2) => {
await verifyLineSignature(event); // ~50ms
const tenant = await lookupTenant(event.pathParameters!.channel_id!); // S3 cache ~10ms
const body = JSON.parse(event.body!) as LineWebhook;
for (const ev of body.events) {
if (ev.type === 'follow') {
const welcome = await loadTemplate(tenant, 'line/welcome.yml'); // S3 ~30ms
await lineReply(ev.replyToken, welcome); // ~200ms
} else if (requiresGeneration(ev)) {
await sqs.send({ tenant, event: ev }); // 非同期、ACK のみ
}
}
return { statusCode: 200, body: '' };
};
- 挨拶パスは Claude を経由しない(5 秒予算確保)
- 重処理は SQS → Deliver Lambda へ非同期
- LINE Channel Secret / Access Token は Secrets Manager のテナント別 prefix
4. Backlog Webhook Lambda¶
POST /backlog/webhook を受ける。Phase 1b の主トリガ(status 変化・コメントコマンド・投稿日時更新を即応で拾う)。
// aws/lambdas/webhook-backlog/handler.ts (概念)
export const handler = async (event: APIGatewayProxyEventV2) => {
await verifyBacklogSignature(event); // 共有シークレットで検証
const payload = JSON.parse(event.body!) as BacklogWebhookPayload;
switch (payload.type) {
case 'IssueUpdated': {
const issue = payload.content;
if (issue.status === 'automedia承認') {
// 即時送信も予約送信も常に EventBridge Schedule を経由する。
// 即時の場合は at(now + 5s) で登録 → 短時間のうちに deliver が起動する。
// これにより監査ログが EventBridge に統一され、誤承認時の Schedule 削除による
// 取り消しも一貫した方法で可能になる。
const scheduled = parseJST(issue.customField('投稿日時'));
const fireAt = new Date(Math.max(scheduled.getTime(), Date.now() + 5_000));
await upsertSchedule(issue.key, fireAt); // EventBridge Schedule `at(fireAt)`
} else if (issue.previousStatus === 'automedia承認') {
await deleteSchedule(issue.key); // cancel (invalidate / 取り消し)
}
break;
}
case 'IssueCommented': {
// コメントコマンドも同様に Schedule 登録/削除/preview に集約する。
const cmd = parseAutomediaCommand(payload.content.comment);
if (cmd?.action === 'send') await upsertSchedule(cmd.issueKey, new Date(Date.now() + 5_000));
if (cmd?.action === 'cancel') await deleteSchedule(cmd.issueKey);
if (cmd?.action === 'preview') await invokePreview({ ...cmd }); // preview は read 系
break;
}
}
return { statusCode: 200, body: '' };
};
- 動的 schedule (
at(投稿日時)) は秒精度 - コマンド (
/automedia send等) も EventBridge Scheduleat(now+5s)で登録 (即時系も統一) - 冪等性は既存の Backlog カスタムフィールド「自動配信実行日時」で担保
5. Scan Lambda(日次セーフティネット)¶
rate(1 day) で起動し、Webhook 取りこぼしの最終防衛線として動作。
// aws/lambdas/scan/handler.ts (概念)
export const handler = async () => {
for (const tenant of await listAllTenants()) {
const missed = await backlogIssuesFiltered(tenant, {
status: 'automedia承認',
issueType: 'コンテンツ運用',
customField: { '投稿日時': '<= now()', '自動配信実行日時': 'IS NULL' },
});
for (const issue of missed) {
await invokeDeliver({ tenant, issueKey: issue.key });
await notifyMissedWebhook(tenant, issue); // CloudWatch Alarm 連動
}
}
};
通常運用では検知件数 0 になるはず。検知された場合は Backlog Webhook 配信の問題として調査対象になる。
5. Deliver Lambda(Claude 呼び出し)¶
Claude Platform on AWS の IAM 統合で認証。Claude Agent SDK を Node でネイティブ呼び出し(CLI ではなく)。
// aws/lambdas/deliver/handler.ts (概念)
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
// Claude Platform on AWS を IAM 認証で叩く
awsRegion: 'ap-northeast-1',
});
export const handler = async (event: { tenant: string; issueKey: string }) => {
const skill = await loadSkill('automedia-deliver'); // S3 から MD を読む
const context = await loadContext(event.tenant, event.issueKey); // Backlog 課題 + .automedia/
const result = await client.messages.create({
model: 'claude-opus-4-7',
max_tokens: 8192,
system: skill,
messages: [{ role: 'user', content: buildPrompt(context) }],
tools: defineTools(event.tenant), // line_push / hubspot_create_post / backlog_comment 等を TS で実装
});
await applyToolCalls(event.tenant, event.issueKey, result);
};
Claude Code CLI ではなく Claude Agent SDK 呼び出し を採る理由:
- Lambda は container 不要、起動軽量
.claude/skills/文化を MD のまま継承しつつ、SDK の system / tools として展開できる- 実行制御(タイムアウト、リトライ、tool 実行)が TypeScript で書ける
長尺ジョブ (>15 分) は ECS Fargate Task に escalate(Phase 1b 後半検討)。
6. Tenant Sync Lambda¶
GitHub Push Webhook を受け、.automedia/ を S3 にミラー。
// aws/lambdas/sync/handler.ts (概念)
export const handler = async (event: GitHubPushEvent) => {
const tenant = resolveTenantFromRepo(event.repository.full_name);
await syncFromGitHub(event.repository.full_name, '.automedia/', `s3://automedia-tenants/${tenant}/`);
};
これで Lambda 群は S3 を SoT として読める(GitHub Raw API への都度 fetch は不要)。
ワークフロー:LINE Webhook 即応(≤ 5 秒)¶
LINE → API Gateway → Webhook Lambda
├ 署名検証 (~50ms)
├ tenant lookup (S3 cache ~10ms)
├ event type 判定
│ ├ follow → welcome.yml → LINE reply (~200ms)
│ └ message → 同上 or SQS enqueue
└ 200 OK
ウォーム時 ~300ms、コールドスタート時でも 1.5〜3 秒。5 秒予算に余裕。provisioned concurrency = 1 を Phase 1b 後半で検討。
ワークフロー:Backlog Webhook トリガ(主経路)¶
Backlog (status → automedia承認 / コメント / カスタムフィールド更新)
↓ webhook
API Gateway ──→ Backlog Webhook Lambda
├ 署名検証
├ event type 判定
│ ├ IssueUpdated (status → automedia承認)
│ │ └ EventBridge Schedule at(max(投稿日時, now+5s)) 登録
│ │ (即時送信も予約送信も常に Schedule 経由)
│ ├ IssueUpdated (status ← automedia承認 / 内容変更 invalidate)
│ │ → Schedule 削除
│ ├ IssueUpdated (投稿日時 更新) → Schedule update
│ └ IssueCommented (/automedia …) → Schedule 登録 / 削除 / Preview
└ 200 OK
ワークフロー:スケジュール時刻到達¶
EventBridge Schedule `at(投稿日時)` 到達
↓ (秒精度)
Deliver Lambda ──→ Claude Platform on AWS (IAM)
──→ LINE / HubSpot / Backlog API
──→ CloudWatch Logs
──→ Backlog コメント (実行報告) + 「自動配信実行日時」更新
ワークフロー:日次セーフティネット¶
EventBridge Scheduler `rate(1 day)` ──→ Scan Lambda
├ 全テナント Backlog scan
├ 「automedia承認 + 投稿日時 ≤ now() + 自動配信実行日時 IS NULL」を抽出
├ 検知件数 > 0 なら CloudWatch Alarm (Webhook 異常検知)
└ Deliver Lambda invoke (漏れ救済)
通常運用では Scan Lambda の検知件数は 0。検知されたら Backlog Webhook の配信失敗 / 設定漏れを示すシグナルとして扱う。
ワークフロー:テナント設定同期¶
spin-dd/bato-hubspot-theme (push of .automedia/**)
↓
GitHub Webhook → API Gateway → Sync Lambda
↓
S3: s3://automedia-tenants/bato/
bootstrap / upgrade フロー¶
# 初回セットアップ(テーマリポで実行)
cd bato-hubspot-theme
npx @spin-dd/automedia init
# ↳ .automedia/{project.yml, templates/, rules/} を生成
# ↳ GitHub Push hook URL を出力(管理者が登録)
# ↳ automedia の tenants.yml に追記する PR を spin-dd/automedia へ作る
git checkout -b chore/automedia-bootstrap && git add . && git commit
.claude/skills/, .github/workflows/ はテーマリポに作らない(automedia 中央集約)。
スキル設計の指針(SDK 呼び出し対応)¶
スキルは MD で管理し、Deliver Lambda が S3 から読んで Claude Agent SDK の system プロンプトに展開する。
---
name: automedia-deliver
description: Backlog 課題から配信先に応じた SNS 投稿を実行
tools: [backlog_get, line_push, hubspot_create_post, backlog_comment]
---
## 役割
…
## 配信前チェック
…
## 配信
…
## 失敗ハンドリング
…
tools: は MD で宣言し、Deliver Lambda が SDK の tool 定義として展開。実装は Lambda 内 TypeScript(旧設計の Bash + curl を fetch に置き換え)。
状態の在処¶
| 状態 | 在処 |
|---|---|
| ContentTask 論理状態 | Backlog 課題(SoT) |
| 配信実行履歴 | CloudWatch Logs + Backlog コメント |
| Webhook 受信履歴 | CloudWatch Logs |
| テナント設定 SoT | 各テーマリポの .automedia/ (GitHub) |
| テナント設定 cache | S3 automedia-tenants/<tenant>/ |
| Channel ID → tenant マップ | automedia リポ aws/config/tenants.yml → S3 sync |
| シークレット | AWS Secrets Manager /automedia/<tenant>/<provider> (provider 単位 JSON) |
| Claude API キー | 不要(Claude Platform on AWS の IAM 統合) |
| Lambda コード | automedia リポ aws/lambdas/(OpenTofu deploy) |
GH Actions Secrets は OpenTofu deploy 用の OIDC ロール ARN のみ。
リソース命名規約¶
AWS リソースは全て automedia- プレフィックスで統一する。spin-dd アカウント内の他リソースと混在しても識別可能、IAM ポリシーが prefix match で書ける、CloudWatch / Cost Explorer でフィルタしやすい。
| リソース種別 | 命名規則 | 例 |
|---|---|---|
| Lambda function | automedia-<function> (ZIP, Node 20, arm64) |
automedia-webhook-line, automedia-webhook-backlog, automedia-deliver, automedia-scan, automedia-sync |
| IAM Role | automedia-<purpose>-role |
automedia-deliver-role, automedia-deploy-role |
| IAM Policy | automedia-<purpose>-policy |
automedia-deliver-secrets-policy |
| API Gateway HTTP API | automedia-api |
(1 個) |
| S3 Bucket(グローバル一意) | spin-dd-automedia-<purpose> |
spin-dd-automedia-tenants, spin-dd-automedia-tofu-state |
| Secrets Manager | /automedia/<tenant>/<provider> |
(セキュリティ・シークレット で既に決定済み) |
| DynamoDB | automedia-<purpose> |
automedia-deliver-locks |
| SQS | automedia-<purpose> |
automedia-deliver-queue, automedia-deliver-dlq |
| EventBridge Schedule Group | automedia-deliver-schedules |
動的 schedule のグループ |
| EventBridge Schedule(動的) | automedia-deliver-<tenant>-<issueKey> |
配信予約用 |
| EventBridge Scheduler(rate) | automedia-scan-daily |
日次セーフティネット |
| CloudWatch Log Group | /aws/lambda/automedia-<function> |
Lambda 標準 |
| CloudWatch Alarm | automedia-<resource>-<metric> |
automedia-webhook-line-errors |
| Route 53 Record | api.automedia.spin-dd.com |
既存ドメイン spin-dd.com の sub |
| ACM Certificate | automedia-api-cert |
(name tag) |
共通タグ¶
全リソースに以下のタグを付与(OpenTofu の default_tags で一括設定)。
# aws/tofu/provider.tf
provider "aws" {
region = "ap-northeast-1"
default_tags {
tags = {
Project = "automedia"
ManagedBy = "opentofu"
Environment = "prod"
Owner = "spin-dd"
}
}
}
これで Project = automedia タグで全リソースが Cost Explorer / Resource Groups で集計可能になる。
locals 例¶
# aws/tofu/locals.tf
locals {
project_name = "automedia"
prefix = "${local.project_name}-"
global_prefix = "spin-dd-${local.project_name}-" # S3 等のグローバル一意リソース用
}
resource "aws_lambda_function" "webhook_line" {
function_name = "${local.prefix}webhook-line"
# ...
}
resource "aws_s3_bucket" "tenants" {
bucket = "${local.global_prefix}tenants"
# ...
}
OpenTofu Bootstrap(backend 自己ホスト)¶
OpenTofu の state backend である S3 bucket 自体も OpenTofu で管理する。chicken-and-egg 問題(state bucket を作るリソースが、その state bucket を必要とする)は bootstrap module を分離 + state migration で解消する。
ディレクトリ構成¶
aws/tofu/
├── bootstrap/ # backend (S3 bucket) を作る最小モジュール
│ ├── README.md # 初回手順
│ ├── main.tf # spin-dd-automedia-tofu-state + versioning + encryption + bucket policy
│ ├── backend.tf # 初回は local → apply 後に S3 へ migrate
│ ├── provider.tf
│ ├── variables.tf
│ └── outputs.tf # bucket name / arn
├── modules/ # 再利用モジュール
│ ├── lambda-function/
│ ├── webhook-api/
│ └── ...
├── backend.tf # bootstrap で作った bucket を指す(use_lockfile = true)
├── provider.tf
├── locals.tf
├── lambda-webhook-line.tf
├── lambda-webhook-backlog.tf
├── lambda-deliver.tf
├── lambda-scan.tf
├── lambda-sync.tf
├── apigateway.tf
├── eventbridge.tf
├── secretsmanager.tf
├── s3.tf # tenants bucket 等(tofu-state bucket は bootstrap 側)
├── dynamodb.tf # automedia-deliver-locks
├── sqs.tf
├── iam.tf
├── route53.tf
├── acm.tf
├── cloudwatch.tf
└── variables.tf
bootstrap/main.tf(概念)¶
# aws/tofu/bootstrap/main.tf
resource "aws_s3_bucket" "tofu_state" {
bucket = "spin-dd-automedia-tofu-state"
}
resource "aws_s3_bucket_versioning" "tofu_state" {
bucket = aws_s3_bucket.tofu_state.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_server_side_encryption_configuration" "tofu_state" {
bucket = aws_s3_bucket.tofu_state.id
rule {
apply_server_side_encryption_by_default { sse_algorithm = "AES256" }
}
}
resource "aws_s3_bucket_public_access_block" "tofu_state" {
bucket = aws_s3_bucket.tofu_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# TLS 強制 bucket policy
resource "aws_s3_bucket_policy" "tofu_state" {
bucket = aws_s3_bucket.tofu_state.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "DenyInsecureTransport"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [aws_s3_bucket.tofu_state.arn, "${aws_s3_bucket.tofu_state.arn}/*"]
Condition = { Bool = { "aws:SecureTransport" = "false" } }
}]
})
}
backend.tf(bootstrap / main 共通形式)¶
# aws/tofu/bootstrap/backend.tf
terraform {
backend "s3" {
bucket = "spin-dd-automedia-tofu-state"
key = "bootstrap/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
use_lockfile = true # ← S3 native locking、DynamoDB 不要
}
}
# aws/tofu/backend.tf
terraform {
backend "s3" {
bucket = "spin-dd-automedia-tofu-state"
key = "phase1b/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
use_lockfile = true
}
}
初回 Bootstrap 手順(手動・1 回だけ)¶
cd aws/tofu/bootstrap
# 1. backend.tf を一時的に無効化(local state で初回 apply)
mv backend.tf backend.tf.disabled
# 2. local state で apply(state bucket を作る)
tofu init
tofu apply
# ↳ spin-dd-automedia-tofu-state バケットが作られる(state は手元の terraform.tfstate)
# 3. backend.tf を有効化 → state を S3 に migrate
mv backend.tf.disabled backend.tf
tofu init -migrate-state
# ↳ "Do you want to copy existing state to the new backend?" → yes
# 4. ローカル state を削除
rm terraform.tfstate terraform.tfstate.backup
# 5. 以降 bootstrap module も S3 backend で管理される
cd ../
tofu init
tofu apply
# ↳ メインスタックも同じ bucket の別 key (phase1b/) で管理される
以降の deploy は bootstrap の差分は no-op(変化なし)、main 側だけが変わる。
GitHub Actions からの deploy¶
# .github/workflows/aws-deploy.yml
- name: Tofu init & apply (bootstrap)
working-directory: aws/tofu/bootstrap
run: |
tofu init
tofu plan -detailed-exitcode
tofu apply -auto-approve
- name: Tofu init & apply (main)
working-directory: aws/tofu
run: |
tofu init
tofu plan
tofu apply -auto-approve
bootstrap は通常差分ゼロで終わる。state bucket 設定を変えた時のみ更新される。
自己ホスト分離のトレードオフ¶
| bootstrap 分離(採用) | bootstrap なし(state を別管理) | |
|---|---|---|
| chicken-and-egg | 回避(初回手動 1 回のみ) | 常時手動 |
| 削除順序 | 明確(bootstrap は最後) | 不明瞭 |
| 権限分離 | bootstrap は初回 Admin、main は専用ロール | 全部 Admin |
| 運用負荷 | 初回 1 回手動、以降自動 | 永続的に手動管理 |
→ bootstrap 分離を採用。
Lambda デプロイ形式¶
Lambda 群は全て ZIP package でデプロイする。container image (ECR) は使わない。
| 観点 | 採用案 |
|---|---|
| パッケージング | ZIP(OpenTofu の archive_file で dist/ を zip 化) |
| Runtime | nodejs20.x |
| Architecture | arm64(Graviton、コスト・性能で有利) |
| サイズ上限 | 250MB(展開後)— automedia の各 Lambda は 50MB 以下に収まる |
| Container (ECR) | 使わない(Lambda 用途では)。ECS Fargate Task のみ ECR を使う(長尺ジョブ向け、Phase 1b 後半検討) |
サイズ見積もり¶
| Lambda | 主要依存 | 推定 ZIP サイズ |
|---|---|---|
automedia-webhook-line |
@aws-sdk/client-secrets-manager, @aws-sdk/client-sqs, crypto (built-in) |
~5MB |
automedia-webhook-backlog |
@aws-sdk/client-scheduler, @aws-sdk/client-lambda |
~5MB |
automedia-sync |
@aws-sdk/client-s3, @octokit/request |
~3MB |
automedia-scan |
@aws-sdk/client-lambda, fetch |
~3MB |
automedia-deliver |
@anthropic-ai/sdk, @aws-sdk/client-secrets-manager, @aws-sdk/client-dynamodb |
~15MB |
最大の deliver Lambda でも 15MB 程度。ZIP の 250MB 上限に対し十分余裕。
Lambda リソース例(OpenTofu)¶
# aws/tofu/lambda-webhook-line.tf
data "archive_file" "webhook_line" {
type = "zip"
source_dir = "${path.module}/../lambdas/webhook-line/dist"
output_path = "${path.module}/.build/webhook-line.zip"
}
resource "aws_lambda_function" "webhook_line" {
function_name = "${local.prefix}webhook-line"
filename = data.archive_file.webhook_line.output_path
source_code_hash = data.archive_file.webhook_line.output_base64sha256
runtime = "nodejs20.x"
handler = "handler.handler"
role = aws_iam_role.webhook_line.arn
timeout = 10
memory_size = 256
architectures = ["arm64"]
environment {
variables = {
AWS_NODEJS_CONNECTION_REUSE_ENABLED = "1"
}
}
}
source_code_hash で差分検出されるので、dist/ の中身が変わった時だけ Lambda が更新される。
GitHub Actions の build step¶
- name: Build Lambda functions
run: |
for fn in webhook-line webhook-backlog deliver scan sync; do
pushd aws/lambdas/$fn
npm ci --production
npm run build # tsc → dist/
popd
done
- name: Tofu apply
working-directory: aws/tofu
run: |
tofu init
tofu apply -auto-approve
OpenTofu の archive_file が dist/ を zip 化、aws_lambda_function が source_code_hash 差分で更新を判定。
ECR を採用する場面(将来)¶
- ECS Fargate Task(Amazon ECS の Fargate launch type、EKS Fargate ではない): 長尺ジョブ(>15 分 Lambda タイムアウト超過)を escalate する場合
- ネイティブバイナリ依存(headless Chrome 等)が増えた場合の Lambda 検討(現状なし)
ECS Fargate Task を追加する場合の構成(参考)¶
| リソース | 命名 | 役割 |
|---|---|---|
| ECS Cluster | automedia-cluster |
Fargate の管理単位(料金無料) |
| ECS Task Definition | automedia-deliver-long |
image_uri / cpu / memory / 環境変数 |
| ECR Repository | automedia-deliver-long |
container image の保存先 |
| VPC + Subnet | automedia-vpc |
Fargate は VPC 必須(Lambda と違う) |
| Security Group | automedia-deliver-long-sg |
egress: Anthropic API / AWS endpoints |
| 起動方式 | — | Deliver Lambda or Step Functions が RunTask API でオンデマンド起動 |
VPC 設定が追加で必要(Lambda は VPC 不要)。Phase 1b 着地時点では Lambda 5 個で完結し、Fargate は使わない。長尺ジョブが必要になった時点で VPC + ECS Cluster + automedia-<purpose>-long ECR repository をまとめて追加する想定。
ECS Cluster / Fargate Task の課金モデル¶
ECS には「常駐サービス」と「ad-hoc タスク」の 2 つの実行モデルがある。automedia は 後者(ad-hoc) を使うので、タスクが動いている時間だけ課金、アイドル時は完全 0 円 になる。
| ECS Service(常駐) | ECS Task / RunTask(ad-hoc) ← automedia | |
|---|---|---|
| 用途 | Web サーバ / API / 常時 worker | バッチ・長尺生成(処理が終わったら消える) |
desired_count |
常時 N タスク維持 | 不要(呼び出しごとに 1 タスク) |
| Auto Scaling / ALB | あり | 不要 |
| アイドル時の課金 | あり(タスクが常時動いている) | 0 円 |
| 起動方法 | Service Definition で常時起動 | RunTask API(Deliver Lambda or Step Functions から都度起動) |
ECS Cluster 自身は料金無料(タスクが所属する論理スコープに過ぎない)。「Cluster を作ったら月額固定が発生する」という誤解は不要。
実行パターン:
Deliver Lambda
├─ ジョブが 15 分以内 → そのまま Lambda で完結(通常経路)
└─ ジョブが 15 分超過の見込み → ecs.RunTask({ cluster: "automedia-cluster", ... })
↓
Fargate Task が起動(数秒のコールドスタート)
↓
処理完了 → タスク終了 → 課金停止
コスト試算(ap-northeast-1, arm64 / Graviton)¶
- vCPU: 約 $0.05056/時(arm64 で約 20% 安)
- メモリ: 約 $0.00553/GB/時
- 1 vCPU + 2GB × 5 分/ジョブ ≒ $0.005 ≒ 0.8 円/ジョブ
- 月 100 ジョブ → 約 80 円/月
Lambda メイン構成なので Fargate コストはほぼ無視できる規模。コスト懸念で Cluster 作成を躊躇する必要はない。
セキュリティ・権限¶
AWS(spin-dd account)¶
- AWS profile:
spindd(account695590128753) - Region:
ap-northeast-1 - IaC: OpenTofu(
tofuCLI、aws/tofu/配下)。state backend は S3 native locking、bootstrap module は別ディレクトリで自己ホスト - IAM ロール(全て
automedia-<purpose>-role命名): automedia-webhook-line-role— LINE Webhook Lambda 用。Secrets Manager read / S3 read / SQS send / CloudWatch / Claude Platform invokeautomedia-webhook-backlog-role— Backlog Webhook Lambda 用。Secrets Manager read / S3 read / EventBridge Scheduler write / Lambda invoke (deliver) / CloudWatchautomedia-scan-role— Scan Lambda 用。S3 read / Lambda invoke (deliver) / CloudWatchautomedia-deliver-role— Deliver Lambda 用。Secrets Manager read / S3 read / DynamoDB write (lock) / Claude Platform invoke / CloudWatchautomedia-sync-role— Sync Lambda 用。S3 write / GitHub Raw fetch / CloudWatchautomedia-deploy-role— GitHub Actions OIDC で assume、OpenTofu apply 権限- Secrets Manager prefix
/automedia/<tenant>/<provider>に provider 単位の JSON 形式 で格納(LINE / HubSpot / Backlog 等)。詳細は セキュリティ・シークレット - Claude 認証は Claude Platform on AWS の IAM 統合(API キー Secrets 配置不要)
GitHub¶
- 各テーマリポの Actions Secrets は 不要(ランタイム責務なし)
- automedia リポの Secrets: AWS OIDC ロール ARN + GitHub Webhook シークレット(Sync Lambda 署名検証用)
テナント分離¶
- IAM ポリシーで Secrets / S3 prefix をテナント別に制限
- Lambda は invoke 時に
tenantを引数で受け、対応する prefix のみアクセス - Phase 2 で per-tenant AWS アカウントへ cross-account assume で分散可能
Phase 1b → Phase 2 (進化方向)¶
Phase 1b (現行) は単一 AWS アカウント内のテナント分離 (IAM Role + Secrets / S3 prefix)。
Phase 2 の選択肢:
- per-tenant AWS account への cross-account assume で完全分離 (ISMS / SOC2 要件があれば)
- per-tenant Lambda / Schedule を OpenTofu module で展開
- ZDR (Zero Data Retention) 対応
詳細は ロードマップ を参照。
このモデルが向かないケース¶
- データレジデンシー要件で「データを Anthropic 側で処理させたくない」場合 → Bedrock 経由に切り替え(Claude Platform on AWS は Anthropic 側処理)
- spin-dd AWS アカウントが使えない、または AWS 採用ポリシー上の制約 → Hybrid 案にフォールバック
- 完全オフライン環境 → 対象外
決定事項¶
- automedia = bootstrap メタプロ + AWS 中央サービス(Phase 1b、2026-05-20 確定)
- AWS account = spin-dd 既存(profile=
spindd, account695590128753)、region =ap-northeast-1(2026-05-20 確定) - Claude 認証 = Claude Platform on AWS の IAM 統合(API キー管理不要、2026-05-20 確定)
- Claude 呼び出し方式 = Claude Agent SDK ネイティブ(CLI ではなく、Lambda 適合)(2026-05-20 確定)
- IaC = OpenTofu(
tofuCLI、aws/tofu/配下にモジュール管理)(2026-05-20 確定) - リソース命名規約 =
automedia-プレフィックス統一(グローバル一意な S3 等はspin-dd-automedia-)(2026-05-20 確定) - OpenTofu state backend は自己ホスト(
aws/tofu/bootstrap/で S3 bucket を OpenTofu 自身で管理)(2026-05-20 確定) - Lambda デプロイ形式 = ZIP(Node 20 / arm64)。ECR (container image) は ECS Fargate Task のみ(長尺ジョブ、Phase 1b 後半検討)(2026-05-20 確定)
- Webhook (LINE) / Webhook (Backlog) / Scan / Deliver / Sync すべて AWS Lambda(2026-05-20 確定)
- Backlog Webhook を主トリガに採用(status 変化 / コメントコマンド / 投稿日時更新の即応)(2026-05-20 確定)
- 配信時刻トリガは EventBridge 動的
at(投稿日時)schedule。rate(5 minutes)ポーリングは採らない(2026-05-20 確定) - Scan Lambda は日次セーフティネット(
rate(1 day)、Webhook 取りこぼし救済)(2026-05-20 確定) - GH Actions の役割は OpenTofu deploy と CI 検証のみ。配信ランタイムには関与しない
- テナント設定 SoT は 各テーマリポの
.automedia/。GitHub Push → Sync Lambda → S3 mirror
未決¶
- Lambda 言語: Node + TypeScript(推奨、SDK ネイティブ) / Python
- OpenTofu state 管理: S3 backend + native locking (
use_lockfile = true、OpenTofu 1.9+) の bucket 命名と bootstrap 手順。DynamoDB lock table は不要 - Backlog Webhook 仕様確認: Backlog (spindd.backlog.com) の Webhook 再送ポリシー / 署名方式 / IssueUpdated の payload 仕様
- 長尺ジョブ対応: 15 分超過時に ECS Fargate Task に escalate するかどうか
- テナント設定 sync 方式: GitHub Webhook → Sync Lambda(推奨) / Lambda が起動時に GitHub Raw を都度 fetch
- Secrets 保管: Secrets Manager vs SSM Parameter Store
- 非同期キュー: SQS / EventBridge Bus どちらを採るか
- bootstrap CLI の配布形態: npm (
npx @spin-dd/automedia) / GitHub Action /degitテンプレート - Phase 1b 移行スケジュール: 馬頭ゴルフ MVP (Minimum Viable Product) 配信稼働前に間に合うか
- Bedrock fallback 手順: データレジデンシー要件が出た場合の切替パスを事前定義