Files
llm-wiki/raw/articles/hatena-cloudfront-saas-manager-2026.md
T
2026-07-03 00:38:05 +09:00

295 lines
28 KiB
Markdown

---
source_url: "https://developer.hatenastaff.com/entry/2026/07/01/183904"
ingested: 2026-07-02
sha256: e270883e267bcbb19ee5e71ba80fe47f35f07d50bd72b49ee108c473ae78d243
discovered_from:
platform: discord
channel_id: "1028287639918497822"
channel_name: "chat"
message_id: "1522179894954692619"
author_id: "890908900520505354"
posted_at: "2026-07-02T09:59:27.692000000Z"
message_excerpt: |-
Hatena developer blog CloudFront SaaS Manager migration article
---
## はじめに
この記事は SRE 連載です。 前月の記事は [id:k1s1eee](http://blog.hatena.ne.jp/k1s1eee/) さんの [社内にLiteLLM Proxy(OSS版)を導入してマルチプロバイダLLM運用基盤を作った話](https://developer.hatenastaff.com/entry/2026/05/14/173453) でした。
[id:hagihala](http://blog.hatena.ne.jp/hagihala/) です。
去年から今年の上半期にかけてはてなブログに Amazon CloudFront SaaS Manager (以下 SaaS Manager) を導入し、ブログへのトラフィックを CloudFront 経由に移行しています。2025年9月にはてな所有のワイルドカードドメインの移行 (第1段階) を完了、2026年3月には独自ドメインの CNAME 方式の移行 (第2段階) を完了しました。
この記事でははてなブログへの SaaS Manager 導入の経緯や設計時に考えたこと、遭遇したハマりどころなどを紹介します。
なお、この記事の投稿時点ではネイキッドドメイン / A レコード方式の移行 (第3段階) は進行中です。本記事は第2段階完了時点の知見として読んでください。
## 背景
### はてなブログの現行構成
はてなブログへのリクエストは大まかに以下のような経路を辿ります。
![ブラウザ → Route 53 → 公開 NLB → nginx (HTTPS 終端) → Varnish → アプリケーション → Aurora/ElastiCache の現行構成図。証明書は CertKeeper (Step Functions・Lambda・DynamoDB) が管理](https://cdn-ak.f.st-hatena.com/images/fotolife/h/hatenatech/20260701/20260701183909.png)
ブラウザ → Route 53 → 公開 NLB → nginx (HTTPS 終端) → Varnish → アプリケーション → Aurora/ElastiCache の現行構成図。証明書は CertKeeper (Step Functions・Lambda・DynamoDB) が管理
ブログへのリクエストは公開 NLB を経由して nginx で動くプロキシサーバに届きます。nginx がクライアントとの TLS を終端し、 HTTP キャッシュおよびバックエンドへ HTTP でリクエストを転送します。独自ドメインの TLS 証明書は nginx が内製の証明書発行・管理システムである `CertKeeper` から動的に取得して使用します。
CertKeeper については以下の記事で詳しく解説されています。
[ブログサービスのHTTPS化を支えたAWSで作るピタゴラスイッチ / The construction of large scale TLS certificates management system with AWS - Speaker Deck](https://speakerdeck.com/aereal/the-construction-of-large-scale-tls-certificates-management-system-with-aws)
なお、画像・CSS・JavaScript などの静的アセットについては以前から CDN を導入しており、現在は CloudFront + S3 で配信しています。
この構成で長らくブログを運用してきましたが、いくつかの課題がありました。
### 導入の動機
主な目的は WAF の導入によるセキュリティ強化です。 CloudFront で AWS WAF を使用することで DDoS 対策や不正アクセスへの対応がしやすくなります。
また今回は副次的なものですが、通信の最適化や将来的には CloudFront でコンテンツをキャッシュすることによるパフォーマンスの向上や転送コストの削減も見込んでいました。
### なぜ今まで CDN を入れていなかったか
「なぜ今まで CDN を入れなかったのか」と思われるかもしれません。最大の理由は、はてなブログが **大量の独自ドメインを扱うサービス** だからです。
独自ドメインを持つブログの数字は非公開なので詳細な数字は避けますが、万単位の規模で存在しています。独自ドメインそれぞれに CloudFront ディストリビューションを作るのは現実的ではありません。また1つのディストリビューションに追加できる代替ドメイン名には上限があり、全てを収めることはできません。
それらのドメインの TLS 証明書を適切に発行、管理する仕組みも必要になります。
## SaaS Manager を選んだ理由
### SaaS Manager の仕組み
![Multi-tenant distribution / Distribution Tenant / Connection group / Shared certificate の関係図](https://cdn-ak.f.st-hatena.com/images/fotolife/h/hatenatech/20260701/20260701183914.png)
Multi-tenant distribution / Distribution Tenant / Connection group / Shared certificate の関係図
Amazon CloudFront SaaS Manager は、SaaS プロバイダが多数のテナント (顧客の独自ドメイン等) を1つの CloudFront ディストリビューションで管理できるようにするサービスです。主な概念は次のとおりです。
- **Multi-tenant distribution**: テンプレートとなる CloudFront ディストリビューション。キャッシュ設定やオリジン設定はここで一元管理する
- **Distribution Tenant** (以下 Tenant): Multi-tenant distribution のインスタンス。ドメインと ACM 証明書を持つ
- **Connection group**: Tenant を束ねる単位。DNS のレコードが向く先
- **Shared certificate**: 複数の Distribution Tenant 間で共有される ACM の TLS 証明書
- **Managed certificate**: Tenant に紐づく ACM の TLS 証明書。CloudFront と連携して HTTP 方式のバリデーションを行い自動で発行・更新される
証明書は場面によって使い分けます。Shared certificate は第1段階のワイルドカードドメインのように複数 Tenant で同じ証明書を使い回す可能性がある (例えば特定のサブドメインの Tenant を切り出すことも可能) 場面で、Managed certificate は第2段階以降の独自ドメインのように Tenant ごとに個別の証明書を発行する場面で利用します。
[マルチテナントディストリビューションの仕組みを理解する - Amazon CloudFront](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/distribution-config-options.html)
### SaaS Manager によって解決される問題
先程述べた通り CloudFront の通常のディストリビューション (Standard distribution) では、1つのディストリビューションに追加できる代替ドメイン名 (Alternate Domain Name) に上限があります。たくさんある独自ドメインをこの上限以内に収めることはできません。
また独自ドメインごとに自動で1つのディストリビューションを作って割り当てることも (AWS クォータ次第で) 可能かも知れませんが、大量のディストリビューションを管理するのは運用負荷が高く、アプリケーション側から個別に操作するコードも煩雑になります。
SaaS Manager の Multi-tenant distribution はまさにこの問題のために設計されています。1つのテンプレートに対してドメインごとに Tenant を作る構造です。設定は Multi-tenant distribution 側で一元管理でき、個別ドメインの差異は Tenant レベルでの最小限の設定に留まります。
### SaaS Manager が向くワークロードの条件
SaaS Manager は「多ドメインだが挙動はほぼ共通」なワークロードに強くフィットします。はてなブログは典型的にこの条件に当てはまります。
逆に向かないケースもあります。ドメインごとに異なるキャッシュ設定やオリジン設定を入れたいというケースがその一つです。このケースではパラメータ機能で対応可能なものも一部ありますが、 Multi-tenant distribution のテンプレートで表現しきれなくなります。プラン毎などパターンが限られていればそれぞれに別の Multi-tenant distribution を用意して Tenant を割り振る方法も取れますが、パターンが多くなると管理が煩雑になります。
### 当時の不安と踏み込んだ理由
2025年4月にリリースされ、5月に SaaS Manager の検証を始めた時点では、国内での導入事例はほぼなく、ドキュメントも整備途上の部分がありました。「現在運用しているブログ数に対してクォータが足りるのか」という不確実性がありました。
それでも踏み込んだのは、検証の過程で「はてなブログのワークロードに合致している」と確信できた、そして WAF の導入によるセキュリティ強化や転送量のコスト削減が見込めるためでした。クォータや機能のロードマップについて AWS 側と早い段階から会話し、必要な上限引き上げの見通しを立ててから本格導入に進みました。
## 移行戦略
全体の移行を3段階に分けて進めています。
| 段階 | 対象 | 主な技術課題 | 状態 |
| --- | --- | --- | --- |
| 第1段階 | はてな所有のワイルドカードドメイン | Tenant 設計、X-Forwarded-For、proxy 改修 | 完了 (2025/9) |
| 第2段階 | 独自ドメイン (CNAME 方式) | Tenant 自動ライフサイクル管理、ACM 共有証明書、CAA 周知 | 完了 (2026/3) |
| 第3段階 | 独自ドメイン (A レコード方式) | Anycast Static IP、ユーザー DNS 変更のための長い移行期間 | 進行中 |
### 段階分けの判断軸
段階を分けるにあたって、blog.hatenablog.com のような非独自ドメイン (はてな提供ドメイン) と独自ドメインという区別で段階を分けました。また独自ドメインの中でもその提供方法によって段階を分け、移行の効果が高く、かつ移行に必要な工数の小さいものから手を付けることにしました。
第1段階のはてな所有ワイルドカードドメインは、ユーザーへの周知なしにはてな側で完全にコントロールできます。問題があれば即座に切り戻せる、最もリスクの低い出発点でした。
第2段階の独自ドメイン CNAME 方式は、アプリケーション側での自動テナント管理が必要になります。ユーザーへの告知 (既存 CloudFront ディストリビューションとの重複の確認と解消) も必要でした。
第3段階の A レコード方式 (ネイキッドドメイン向け) は、ユーザーが自分で DNS レコードを変更しなければならないという性質上、移行期間が長期間になる見通しです。Anycast Static IP の確保という技術的・コスト的な課題もあり現在進行中となっています。
なお「これからの話」の節で説明しますが、CloudFront のキャッシュ有効化はスコープ外としています。
## 第1段階
### 複数のワイルドカードドメインを1 Tenant にまとめる
はてなブログが使うワイルドカードドメインは `*.hatenablog.com` 、 `*.hatenablog.jp` 、 `*.hateblo.jp` など数種類あります。これをどのように Tenant に割り当てるかを最初に検討しました。
検討の結果、これらのワイルドカードドメインを1つの Tenant にまとめることにしました。この時点では分けるメリットが実質ゼロに近かったことが理由です。
各ワイルドカードドメインごとに Tenant を分ければそれぞれの単位で WAF の個別設定などが可能になりますが、個別設定が必要になるシナリオがあるとすれば、各ワイルドカードドメイン単位よりは全ワイルドカードドメインまたは個別のサブドメイン単位になる可能性が高いです。
証明書についてはそれぞれのワイルドカードドメインのマネージド証明書を個別に取得するのではなく、まとめて取得して Shared certificate として登録しました。これについては、将来的に1サブドメイン1 Tenant 割り当てる構成にした際に Tenant 毎に証明書を発行せずに済む狙いもあります。
### Origin 構成
![CloudFront -> VPC Origin -> 内部 NLB -> proxy の構成図](https://cdn-ak.f.st-hatena.com/images/fotolife/h/hatenatech/20260701/20260701183905.png)
CloudFront -> VPC Origin -> 内部 NLB -> proxy の構成図
CloudFront の Origin として何を使うか、次の選択肢がありました。
| 選択肢 | メリット | デメリット |
| --- | --- | --- |
| 既存の Public NLB をそのまま使う | 構成変更が最小限 | CloudFront 以外のアクセスを分離しづらい |
| VPC Origin + 内部 NLB を新設 | CloudFront 以外からのアクセスを遮断しやすい、将来の Public IP 縮退が可能 | NLB 追加による固定費 |
VPC Origin + 内部 NLB 構成を取ることにしました。
決め手はセキュリティと将来性でした。内部 NLB と VPC Origin を組み合わせると NLB にはインターネットからのアクセスが届かなくなります。CloudFront を経由しないリクエストを構造的に遮断できる構成です。
Public NLB で既存のトラフィックを受け入れつつ CloudFront 経由のトラフィックは全て VPC Origin + 内部 NLB 構成を通すようにして、 CloudFront 移行が進むにつれて Public NLB 経由のトラフィックが減っていくようにしました。
### Route 53 加重ルーティングによる切り替え
切り替えはワイルドカードドメイン単位で Route 53 の加重ルーティングを使って段階的に行いました。
手順の概要:
1. 既存の NLB 宛 A レコード (Alias) を加重ルーティングに変換
2. CloudFront 宛 A レコード (Alias) を Weight: 1 で追加
3. CloudFront 宛のウエイトを段階的に上げ、最終的に全て置き換える
4. 問題がなければ NLB 宛レコードを削除してシンプルルーティングに戻す
キャッシュを使わない設定のため「キャッシュを温める」配慮は不要でした。影響を小さくするため、リクエスト数の少ないドメインから順に切り替えて様子を見ながら進めました。
## 第2段階
第1段階は手動で作成した数個の Tenant へのトラフィック切り替えでした。第1段階で扱うはてな所有のワイルドカードドメインは数種類のみで代替ドメイン名の上限にも収まるため、この時点の構成は Standard distribution でも実現可能なものであり、 SaaS Manager を使用する必然性は特にありません。
第2段階ではその様子が変わり、アプリケーション側で Tenant のライフサイクルを管理するフェーズに入ります。具体的には「はてなブログに独自ドメインを登録すると専用の Tenant を自動的に作成して証明書を発行・設置し、ドメインが解除されたら削除する」という処理が必要になります。
### 1ブログ1 Tenant の判断
導入するにあたって、 Tenant とブログ・独自ドメインの対応関係をどう設計するか最初に決める必要がありました。
採用した設計は「 **1 Distribution Tenant = 1ブログ = 1独自ドメイン** 」です。機能上は1つの Tenant に複数ドメインを割り当てることも可能ですが、それはしないという判断です。理由は2点あります。
1つ目は **管理の単純さ** です。「このドメインを持つ Tenant はどれか」を一意に決定できる構造は、運用操作や障害時の調査を簡単にします。
仮に複数のブログのドメインを1つの Tenant で扱おうとした場合、 Tenant ごとに適用可能な証明書は1つのため、 SAN (Subject Alternative Name) を用いて1つの証明書に異なるブログのドメインを含める必要が生じ、運用が一気に複雑になることが予想されます。
2つ目は **将来のキャッシュ Invalidation のため** です。「これからの話」の節で説明しますが、キャッシュを有効化したとき、ブログ単位のキャッシュ削除は Tenant 単位の Invalidation で行う設計になります。1ブログ = 1 Tenant の対応があってはじめて、この Invalidation が成立します。
### Step Functions を用いた Tenant のライフサイクル
Tenant の作成フローは次の手順を踏みます。
1. アプリケーションが独自ドメインの有効性を検証する
2. Tenant を作成する
- AWS 側でもドメインの有効性検証が行われる
- (切り替え前) self-hosted 方式でバリデーショントークンファイルを取得して公開する
3. ACM がマネージド証明書を発行するのを待つ (数十秒〜十数分)
4. 証明書を Tenant に適用する
![アプリケーションが Step Functions を起動し、Tenant の作成・更新、証明書の発行待ちループ、証明書のアタッチ、成否通知を行う作成フロー図](https://cdn-ak.f.st-hatena.com/images/fotolife/h/hatenatech/20260701/20260701183911.png)
アプリケーションが Step Functions を起動し、Tenant の作成・更新、証明書の発行待ちループ、証明書のアタッチ、成否通知を行う作成フロー図
この「数分待ちながら状態を管理する」処理を誰が担うか検討が必要でしたが、 AWS Step Functions を採用することで解決しました。Step Functions は状態管理と待機をネイティブにサポートしています。証明書発行待ちのウェイト、失敗時のリトライ設定、タイムアウト処理がステート定義で表現できます。アプリケーション側からは「State machine を起動する」だけで済み、状態管理の責任を AWS に委ねることができました。 作成用 State machine は冪等になるようにしたので、途中で失敗した場合や別のドメインに切り替えたい場合も作成用 State machine を実行するだけで済みます。
削除側も同様に Step Functions で実装しています (Tenant の存在を確認して削除するだけのシンプルなものなので図は省略)。
### マネージド証明書のバリデーション方式の選択について
Tenant 作成時のマネージド証明書発行の際のバリデーション (ドメイン所有確認) は HTTP で行われます。
方式 (`validationTokenHost`) には `cloudfront` と `self-hosted` の選択肢があり、通常運用では `cloudfront` を採用します。 `cloudfront` 方式では CloudFront がバリデーション用のトークンを配信し、ACM と連携してドメイン所有確認を進めてくれます。
ただ、今回のケースのように既存のブログを無停止で SaaS Manager 経由に切り替えたい場合は事前に証明書を発行しておく必要がありますが、切り替え前のタイミングではまだ DNS が CloudFront を向いていないため、そのままでは CloudFront が配信するトークンに到達できません。
そこで、 `self-hosted` 方式で ACM が払い出したバリデーショントークンを取得し、既存のブログのプロキシが `/.well-known/pki-validation/{validation-token}.txt` で配信できるよう S3 バケットに設置し、対象ドメインで配信することで、切り替え前でも HTTP 検証が通るようにしていました。
### 移行の際に発生した問題
独自ドメインを移行する過程では、想定外の出来事がいくつか発生しました。
#### ドメインの有効性のフラッピング
独自ドメインの設定時にはドメインの有効性の確認のために対象ドメインの CNAME または A レコードが正しく設定されているかの確認が行われるようになっています。また、その後も定期的に有効性の確認が行われます。
この有効性がフラッピング、つまりネームサーバの返すレコードが時とともに変化するため独自ドメインの有効性が valid と invalid を行き来しているブログが散見されました。
原因は DNS 設定変更直後の反映のラグによる一時的なものの他、おそらくネームサーバの設定の誤りによってネームサーバごとに異なる値を返すケースもありました。
これにより以下のような問題が発生しました。
- アプリケーション側のドメイン有効性検証に通って Tenant 作成処理が開始されても Tenant 作成時の AWS 側の検証が通らず `InvalidArgument` エラーで作成失敗することがあった
- 当初は独自ドメインの有効性が失われたブログの Tenant は即削除するようになっていたが、このフラッピングにより Tenant の作成・削除が繰り返されていた
前者については Tenant 作成をリトライすることで発生をほぼ防ぐことができました。 Step Functions の State の Retry フィールドを設定するだけで簡単に実装できます。
後者については Tenant 作成後にドメインの有効性が失われたタイミングでは削除せず、独自ドメイン設定が解除された時にのみ削除するよう変更することで対処しました。
#### 代替ドメイン名の重複 (CNAMEAlreadyExists)
独自ドメインの Tenant を作成しようとしたとき、そのドメインが別の CloudFront ディストリビューションに既に代替ドメイン名として登録されていると `CNAMEAlreadyExists` エラーになります。
過去にユーザー自身がディストリビューションを作成し、DNS は既に向いていないものの Distribution は削除されず残っているといったケースがこれに当たります。解決には2つの経路があります:
- ユーザー側で対象のディストリビューションを削除または代替ドメイン名を削除してもらう
- ドメインの所有証明のための TXT レコードを設定していただいた上で、はてなが代理で AWS サポートに移行の申請を行う
後者について、今回は実施しませんでしたが、重複先が AWS Amplify など AWS の別サービスが内部的に管理するディストリビューションである場合は通常の CloudFront ディストリビューションと異なる手順が必要となります。
#### ACM が発行できない ccTLD
非常にレアなケースですが、一部の国別トップレベルドメイン (ccTLD) について ACM が証明書を発行できないケースに遭遇しました。このようなドメインには Let's Encrypt で発行した証明書を ACM にインポートする手段を用意しました。
#### CAA レコードの追加依頼
CAA レコードはドメインの証明書を発行できる認証局 (CA) を DNS で制限する仕組みです。はてなブログではこれまで独自ドメインの証明書を Let's Encrypt で発行してきたため、CAA レコードを設定しているユーザーには `letsencrypt.org` の追加をお願いしていました。
SaaS Manager 経由では ACM が証明書を発行するため CAA に `amazon.com` の追加が必要になりますが、 CNAME 方式の場合は独自ドメインの親ドメインにも CNAME レコードが設定されているケースにおいてユーザー側での対応が困難であることが分かったため `hatenablog.com` に CAA レコードを設定することになりました。
[【追記あり:独自ドメインをご利用中の方】はてなブログへの CloudFront 導入に伴う設定確認・変更のお願い - はてなブログ開発ブログ](https://staff.hatenablog.com/entry/2026/01/08/142633)
## これからの話
### 第3段階
第3段階はネイキッドドメイン (A レコード方式) の移行です。ここには第1・第2段階にはない大きな課題があります。
**Anycast Static IP の確保** が必要です。CloudFront では通常固定 IP アドレスを使いません。しかし A レコードは CNAME と異なり名前解決の結果が IP アドレスである必要があります。CloudFront SaaS Manager では Anycast Static IP に対応しているため、これを使う方向で検討しています。ただし、既存インフラで使っている IP アドレスをそのまま流用できないため、IP アドレスの確保と切り替え計画が必要です。
もう一つの課題は **ユーザー側の DNS 変更** です。CNAME 方式では CNAME レコードのターゲットである hatenablog.com. の向き先を変更するだけで移行できましたが、A レコード方式ではユーザー側で設定している A レコードの IP アドレスを変更してもらう必要があります。ユーザーが任意のタイミングで変更するため、全員の移行が完了するまでに長い移行期間が必要になると見ています。
### キャッシュの有効化
今回の移行では CloudFront のキャッシュ有効化を見送りました。
理由はキャッシュの Invalidation の仕様にあります。SaaS Manager の環境では、キャッシュの削除対象をパス + クエリパラメータの組み合わせで指定します。しかし Host ヘッダ (つまり「どのブログのキャッシュを消すか」) を指定する仕組みがありませんでした。
はてなブログでは記事の更新時にそのブログのキャッシュを一括削除したいケースがあります。これを実現するには「1ブログ = 1 Tenant」の対応関係が前提で、はてな所有ドメインのブログにも Tenant を1対1で割り当てることが必要でした。そのためその前提が整ってからキャッシュを有効化する計画としていました。
なお2026年4月29日に実装された以下の機能によってこの前提が変わり、実装の選択肢が増えました。ただ現行の HTTP キャッシュの Invalidation 頻度がそのまま CloudFront にスライドする想定だと料金がボトルネックになる見込みです。
[Amazon CloudFront がキャッシュタグによる無効化のサポートを開始 - AWS](https://aws.amazon.com/jp/about-aws/whats-new/2026/04/cloudfront-invalidation-cache-tag/)
## まとめ
これまでの移行を振り返ると、以下3点が同様の取り組みをするチームへの知見として残ります。
### SaaS Manager は「多ドメイン・均質なルーティング」のワークロードに強い
これまで CDN の導入が困難だったはてなブログのワークロードに上手く嵌まりました。
その一方で、インフラ側の設計よりもアプリケーション側への Tenant ライフサイクルの組み込みが最も設計工数を要しました。Step Functions の採用でアプリケーション側の実装がシンプルになりましたが、「大量のドメインを自動管理する」ための設計の試行錯誤はそれなりの量になりました。
### クォータは早めに確認・引き上げる
Tenant 数、ACM の証明書発行レート、証明書の上限数は、大規模な移行では必ずボトルネックになり得ます。移行計画を立てる段階で上限を確認し、必要なら早めに引き上げを依頼しておくことをお勧めします。
### 既存の CloudFront distribution との代替ドメイン名の重複はユーザー所有のものも含め移行前に調査する
代替ドメイン名の重複問題は、ユーザーが使い終えて放置していたリソースに起因することが多く、事前の一括調査・告知が後の個別対応を大幅に減らします。
はてなブログの CloudFront 化はまだ道半ばです。第3段階のネイキッドドメイン対応、キャッシュの有効化と最適化と、やるべきことはまだあります。引き続き取り組んでいきます。