Okada Makoto

Works / aws-serverless-corporate-site

システムアーキテクチャ設計

本サイトは、レンタルサーバーや常時稼働インスタンスを一切使用せず、AWSのサーバーレス・マネージドサービスのみで構成しています。 「どのサービスを使ったか」だけでなく、どのような要件・制約のもとで、なぜその構成を選び、どうIaC化・自動化したかという設計判断を軸に整理しています。 個人開発という制約下で、可用性・セキュリティ・コスト・運用性のバランスを取りながら、年間コスト約1,500円での運用を実現しました。

1. Architecture Overview

システム全体を役割ごとのレイヤーに分割して設計しています。各レイヤーはマネージドサービスで構成し、常時稼働サーバーを持たないことを前提にしています。

flowchart TD User(["👤 ユーザー / ブラウザ"]) Route53["🌐 Amazon Route 53"] ACM["🔒 ACM"] CloudFront["⚡ Amazon CloudFront"] S3[("🪣 Amazon S3")] APIGW["🚪 API Gateway"] Lambda["⚡ Lambda\nPython 3.12"] SES["✉️ Amazon SES"] Admin["👨💻 管理者"] ECR[("🐳 Amazon ECR")] GHA["🔄 GitHub Actions\nOIDC認証"] User -->|HTTPS| Route53 Route53 -.->|DNS検証| ACM Route53 --> CloudFront CloudFront -->|OAC| S3 User -->|フォーム送信| APIGW APIGW --> Lambda Lambda --> SES SES --> Admin GHA -->|S3同期 + CF無効化| S3 GHA -->|Dockerイメージ| ECR

Frontend / Static Delivery

静的コンテンツをS3に格納し、CloudFrontのエッジキャッシュ経由で配信。S3は非公開とし、OAC経由のアクセスのみを許可。

DNS / TLS

Route 53でドメインを名前解決し、ACM発行の証明書でTLSを終端。ホストゾーンは別AWSアカウントで管理。

Backend API

お問い合わせ処理をAPI Gateway (HTTP API) + Lambda (Python 3.12) で構成。リクエスト発生時のみ実行。

Email Delivery

LambdaからSES経由でお問い合わせ内容を管理者宛に送信。返信先には送信者アドレスを設定。

Infrastructure as Code

全AWSリソースをTerraformで定義。S3リモートステート + State Lockで構成変更を安全に管理。

CI/CD

GitHub ActionsからOIDCでAWSへ認証し、フロント配信・バックエンドのECRプッシュを自動化。

Monitoring

CloudWatch AlarmでLambdaエラー / API Gateway 5xxを監視し、検知時はSNS経由でメール通知。

2. Design Requirements / Constraints

この構成を設計する際に重視した要件・制約を明示します。以降の設計判断は、すべてこれらの前提から導いています。

3. Design Decisions

主要な構成要素について、【課題・要件】→【選択した構成】→【選択理由】→【得られたメリット】→【トレードオフ】の流れで設計判断を整理します。

① 静的配信層 ― S3 + CloudFront + OAC

課題・要件
静的コンテンツを低コストで配信しつつ、S3を直接インターネット公開したくない。
選択した構成
S3(非公開)+ CloudFront + OAC(Origin Access Control)。バケットポリシーはCloudFrontディストリビューションのSourceArn条件付きでs3:GetObjectのみ許可。
選択理由
S3を非公開にしたままCloudFront経由で配信でき、常時稼働サーバーが不要なため。OACはOAIの後継として推奨される方式。
メリット
エッジキャッシュによる高速配信、S3直アクセスの排除によるセキュリティ強化、サーバーレスによる低運用負荷を同時に実現。
トレードオフ
単純なS3静的ウェブサイトホスティングより構成要素が増え、キャッシュ無効化(Invalidation)の考慮が必要になる。

② DNS / TLS ― Route 53 + ACM(クロスアカウント構成)

課題・要件
DNSを管理するアカウントと、アプリ用リソースを構築するアカウントを分離しつつ、証明書のDNS検証を自動化したい。
選択した構成
Route 53ホストゾーンは管理アカウントで保持し、Terraformのassume_roleで操作。ACM証明書はCloudFront要件に合わせus-east-1で発行し、DNS検証レコードをRoute 53に自動作成。
選択理由
DNSのような共有度の高いリソースを別アカウントに隔離し、権限委譲(assume_role)で必要な操作だけを許可するため。
メリット
アカウント境界による影響範囲の分離と、証明書発行・DNS検証の完全自動化を両立。
トレードオフ
マルチプロバイダ + assume_roleの設定が必要となり、単一アカウント構成より初期設計が複雑になる。

③ バックエンド ― API Gateway (HTTP API) + Lambda + SES

課題・要件
お問い合わせフォームの送信処理が必要だが、そのために常時稼働するサーバーは持ちたくない。
選択した構成
API Gateway (HTTP API) + Lambda (Python 3.12) + SES。POST /contactをLambdaプロキシ統合で処理し、SESでメール送信。
選択理由
リクエスト発生時のみ課金されるサーバーレス構成が、低頻度のフォーム処理に最も適しているため。REST APIより安価なHTTP APIを採用。
メリット
アイドル時のコストがほぼゼロ。スケーリングやパッチ適用をマネージドサービスに委譲でき、運用負荷が低い。
トレードオフ
コールドスタートが発生しうる。低頻度・非同期に近いフォーム用途では許容できると判断。

④ IaC ― Terraform(リモートステート + State Lock)

課題・要件
インフラ構成を再現可能にし、複数プロバイダ・複数アカウントをまたぐ構成を安全に管理したい。
選択した構成
Terraformで全リソースを定義。ステートはS3バケットに保存(バージョニング + SSE有効)し、State Lockで排他制御。ロック用DynamoDBテーブルはPITRを有効化。
選択理由
構成をコード化することで変更履歴とレビュー性を確保し、ステートの競合・破損リスクを排他ロックとバージョニングで低減するため。
メリット
環境の再現性・変更容易性が高まり、構成変更を差分ベースで安全に適用できる。
トレードオフ
ステート管理基盤(S3 / DynamoDB)自体の初期構築とライフサイクル保護(prevent_destroy)が必要になる。

⑤ CI/CD ― GitHub Actions + OIDC

課題・要件
デプロイを自動化したいが、長期有効なAWSアクセスキーをGitHubに保存したくない。
選択した構成
GitHub ActionsからOIDCでAWS IAMロールをAssumeRoleWithWebIdentity。フロント/バックエンドで別ロールを用意し、sub条件でリポジトリを限定。
選択理由
短命な一時認証情報を都度発行することで、シークレットの漏洩・失効管理のリスクを構造的に排除するため。
メリット
保存すべき長期シークレットが存在しない。ロールを分離することで用途ごとに最小権限を割り当てられる。
トレードオフ
OIDCプロバイダ・信頼ポリシー・条件(sub / aud)の初期設定が必要になる。

4. AWS Service Selection

本構成では、以下の理由からサーバーレス・マネージドサービス中心の選択を採用しています。一般的な代替案との対比で、選択の意図を示します。

一般的な代替案 採用した構成 この用途で採用した理由
S3 Static Website Hosting S3 + CloudFront + OAC S3を非公開のままTLS・エッジキャッシュ・独自ドメインを利用でき、セキュリティと配信性能を両立できるため。
EC2 等の常時稼働サーバー Lambda(サーバーレス) 低頻度のフォーム処理にはアイドルコストが発生しないサーバーレスが適し、パッチ・スケーリングを委譲できるため。
API Gateway REST API API Gateway HTTP API 今回必要な機能はシンプルなPOST処理であり、より低コスト・低レイテンシなHTTP APIで要件を満たせるため。
GitHub Actions に長期アクセスキーを保存 GitHub Actions OIDC 長期シークレットを保持せず、短命な一時認証情報でデプロイできるため、漏洩・失効管理のリスクを構造的に排除できる。
DNSを同一アカウントで管理 Route 53 を別アカウント + assume_role 共有度の高いDNSをアカウント境界で分離し、権限委譲で必要な操作のみを許可することで影響範囲を限定できるため。

5. Security Design

セキュリティは特定のレイヤーではなく、配信・認証・権限・通信の各所に組み込んで設計しています。以下はいずれも実際に実装している内容です。

S3 Public Access Block

4項目(ACL / ポリシー両面)すべてを有効化し、バケットのパブリック公開を完全に遮断。

CloudFront OAC

SigV4署名でCloudFrontからのみS3オリジンにアクセス。バケットポリシーはSourceArn条件で該当ディストリビューションに限定。

IAM(最小権限)

Lambda実行ロールはCloudWatch Logs + SESのみ。デプロイ用ロールはS3 / CloudFront / ECRの必要なアクションに限定。

クロスアカウント assume_role

DNS管理アカウントへは専用ロールへの権限委譲でアクセスし、アカウント境界を維持。

GitHub Actions OIDC

Web Identityフェデレーションで一時認証。信頼ポリシーのsub / aud条件で対象リポジトリを限定。

シークレットレス構成

長期AWSアクセスキーをGitHub Actionsに保存しない。認証はロールARNの参照のみ。

TLS(ACM)

ACM証明書でHTTPS化し、redirect-to-https + 最小プロトコルTLSv1.2_2021を設定。

ステート保護

TerraformステートS3はバージョニング + SSE(AES256)。ロック用DynamoDBはPITRを有効化。

6. IaC / Terraform Design

Terraformは単なる構築ツールではなく、「構成を再現可能・レビュー可能な状態に保つ」ための設計の中心に位置づけています。

なぜIaC化するのか

手動構築ではなくコードで宣言することで、構成の変更履歴・レビュー・再現性を確保し、同じ環境を差分ベースで安全に再構築できるようにするため。全AWSリソース(S3・CloudFront・Route 53・ACM・API Gateway・Lambda・SES・IAM・ECR・DynamoDB・CloudWatch・SNS)をTerraformで定義しています。

ステート管理と State Lock

ステートファイルはS3バケットに保存し、バージョニングとサーバーサイド暗号化(AES256)を有効化。State Lockによりapplyの競合を防止し、複数実行時のステート破損を回避します。ステート保存用バケットはprevent_destroyで誤削除を防止しています。

クロスアカウント / マルチプロバイダ構成

3つのAWSプロバイダを定義:デフォルト(ap-northeast-1)、CloudFront/ACM用(us-east-1)、DNS管理アカウント用(assume_role)。各リソースにproviderを明示指定することで、リージョン制約とアカウント分離を1つのコードベースで表現しています。

GitHub Actionsからのデプロイ

OIDCで発行された一時認証情報を用いてTerraform適用・デプロイを実行。長期シークレットを持たずにインフラ変更を自動化できます。

Terraformコードを見る(GitHub)

7. CI/CD Design

デプロイはGitHub Actionsで自動化し、AWSへの認証はOIDCによるシークレットレス方式を採用しています。認証フローは以下の通りです。

flowchart LR GH["📦 GitHub\n(push / PR)"] GHA["🔄 GitHub Actions"] OIDC["🎫 OIDC\nWeb Identity Token"] IAM["🔐 AWS IAM Role\n(sub条件で限定)"] TF["🛠️ Terraform / Deploy"] AWS["☁️ AWS リソース"] GH --> GHA GHA --> OIDC OIDC --> IAM IAM --> TF TF --> AWS

フロントエンドパイプライン

frontend/配下の変更をトリガーに、S3同期とCloudFrontキャッシュ無効化(/*)を自動実行。

バックエンドパイプライン

Goのテスト・ビルド後、Dockerイメージをgithub.shaとlatestタグでECRへプッシュ。

なぜ長期アクセスキーではなくOIDCか

長期アクセスキーは保存・ローテーション・失効管理の運用負担と漏洩リスクを伴います。OIDCではジョブ実行時に短命な一時認証情報を発行し、信頼ポリシーのsub条件で対象リポジトリを限定するため、保持すべき長期シークレットそのものを排除できます。

8. Cost Design

年間コスト約1,500円は、単なる「安さ」のアピールではなく、 「個人開発という制約下で、必要な可用性・セキュリティ・運用性を維持しながらコストを抑える」という設計思想の結果です。

常時稼働サーバーを持たずサーバーレス・マネージドサービスに寄せることで、アイドル時のコストをほぼゼロに抑えつつ、 TLS・非公開配信・監視・IaC・自動デプロイといった品質要素は妥協せずに維持しています。 コストは「削るもの」ではなく、アーキテクチャ選択の結果として最適化される対象と位置づけています。

9. Operational Design

「構築して終わり」にしないため、運用負荷を下げる仕組みを設計に組み込んでいます。

監視・通知(CloudWatch + SNS)

Lambdaエラー / API Gateway 5xxをCloudWatch Alarmで監視し、閾値超過時にSNS経由でメール通知。異常を能動的に検知します。

マネージドサービスへの委譲

スケーリング・パッチ適用・可用性の維持をAWSマネージドサービスに委ねることで、日常的な運用作業を最小化。

構成変更の安全性(Terraform)

変更はplanによる差分確認 → applyで適用。State Lockとバージョニングで安全に構成を更新できます。

デプロイの自動化(CI/CD)

変更対象(frontend / backend)ごとにパイプラインを分離し、手動オペレーションを排除。ヒューマンエラーの余地を減らします。

10. Architecture Summary

使用技術スタック

カテゴリ 技術・サービス
CDN・静的配信Amazon CloudFront, Amazon S3
DNS・SSLAmazon Route 53, AWS Certificate Manager
サーバーレスAPIAmazon API Gateway (HTTP API), AWS Lambda (Python 3.12)
メールAmazon SES
監視Amazon CloudWatch, Amazon SNS
コンテナAmazon ECR, Docker (マルチステージビルド)
IaCTerraform (マルチプロバイダ・クロスアカウント)
CI/CDGitHub Actions (OIDC認証)
バックエンドGo, Python 3
フロントエンドHTML5, Tailwind CSS, JavaScript
GitHubで見る Homeに戻る