00
Article / Infra

このサイトの技術構成

hosoitoru.jp のインフラとデプロイの設計 — 細井 透(Toru Hosoi)

01

概要.

静的 HTML を AWS で配信するだけの、シンプルな構成です。S3 + CloudFront(OAC)でホスティング、GitHub Actions(OIDC)で main push のたびに自動デプロイ、インフラは全部 Terraform

GitHub → Actions → S3 → CloudFront → hosoitoru.jp ↑ OIDC(IAM Role を一時 assume)
02

S3 + CloudFront(OAC).

S3 バケットはパブリックアクセスを全部ブロックしています。直接 URL を叩くと 403。CloudFront 経由でしか到達できない作り。

OAC を使う理由

CloudFront から S3 へのアクセス制御には OAC(Origin Access Control)を使っています。OAI(Origin Access Identity)は旧世代で、AWS も OAC を推奨しています。OAC は SigV4 署名でリクエストを検証するので、より堅牢です。

さらにバケットポリシーで AWS:SourceArn を特定の CloudFront ディストリビューション ARN に縛って、同じアカウント内の別ディストリビューションからも読めないようにしています。最小権限の原則に沿った形です。

HTTPS と証明書

ACM でワイルドカード証明書(hosoitoru.jp + *.hosoitoru.jp)を発行。サブドメインを後から足すときに、証明書を再発行しなくていいのが地味に助かる。

CloudFront 用の ACM 証明書は us-east-1 でしか発行できないという独特の仕様があるので、Terraform 側で provider "aws" { region = "us-east-1" } を別エイリアスで定義してます。

404 / 403 の扱い

S3 はバケットが非公開だと存在しないパスにも 403 を返してくる。それを CloudFront で 404 に変換して、カスタムの 404 ページに飛ばしています。

03

GitHub Actions OIDC 認証.

デプロイのために IAM User は作っていません。アクセスキーを GitHub Secrets に置きたくなかったのが理由。漏れたら永続的に悪用されるので。

OIDC の仕組み

GitHub Actions は実行時に OIDC トークン(JWT)を発行してくれます。それを使って sts:AssumeRoleWithWebIdentity を呼び、一時的に IAM Role を assume する、という流れ。

  • 一時クレデンシャルなので、漏れてもすぐ切れる
  • GitHub に長期キーを持たせなくていい
  • IAM Role 側の信頼ポリシーで対象リポジトリを絞っているので、他リポジトリから assume はできない

IAM Role の権限

必要最小限にしてます。

  • S3: PutObject / GetObject / DeleteObject / ListBucket(デプロイ対象バケットのみ)
  • CloudFront: CreateInvalidation / GetInvalidation(対象ディストリビューションのみ)
04

Terraform 構成.

S3・CloudFront・Route 53・ACM・IAM(OIDC)まで全部 Terraform で。

state 管理

S3 バックエンド + use_lockfile(Terraform 1.10 以降のネイティブ S3 ロック)で管理してます。

backend "s3" {
  bucket       = "hosoitoru-jp-tfstate"
  key          = "infra/terraform.tfstate"
  region       = "ap-northeast-1"
  use_lockfile = true
}

以前は DynamoDB テーブルでロックしてましたが、S3 ネイティブのロックに切り替えました。DynamoDB テーブルの管理も、そのための IAM 権限付けも要らなくなって構成がシンプルに。

CloudFront 用 ACM のための us-east-1 プロバイダ

CloudFront に紐付ける ACM 証明書は us-east-1 でしか発行できない、という独特の制約があります。S3 など他のリソースは東京(ap-northeast-1)に置きたいので、ACM のためだけに provider "aws" をもう 1 つ us-east-1 エイリアスで定義して、そのリソースにだけアタッチしています。

ACM 検証レコードの重複除去

ルートとワイルドカードの ACM 検証 CNAME が同じ値になることがあるので、for_each + ...(spread)で重複を落としてから Route 53 に登録しています。これをやらないと Terraform が同一レコードを二重登録しようとしてコケる。

Route 53 ゾーンの条件作成

変数 route53_zone_id が空のときだけ count = 1 でゾーンを作る形にしています。既存ゾーンがあれば ID を渡すだけでよく、このモジュールを他プロジェクトに持っていっても動くようになっています。

05

デプロイの流れ.

main に push すれば終わり。手作業なし。

  1. 記事やトップページを編集して main に push
  2. GitHub Actions が走る → OIDC で IAM Role を assume
  3. aws s3 sync で差分だけ S3 にアップロード
  4. CloudFront の /* を invalidate
  5. 数十秒で本番に反映