CloudFront に独自ドメインを設定する: CloudFormation と AWS CLI の両方の手順と、つながりの仕組み
INDEX
CloudFront のディストリビューションに、独自ドメイン(この記事では例として blog.example.com)を設定します。
ドメインの DNS が別の AWS アカウントにあり、こちらのアカウントでは証明書と CloudFront の設定だけを行って、DNS には「このレコードを登録してください」と依頼する場合を想定します。
- 登場するものと、つながりの仕組み
- 全体の流れ
- 方法 A: CloudFormation
- 方法 B: AWS CLI
- DNS に登録するレコード
- 登録の順番
- A レコードや AAAA レコードではだめなのか
登場するものと、つながりの仕組み#
独自ドメインの設定は、次の 3 つを同じドメイン名で結ぶ作業です。
| 登場するもの | 場所 | 役割 |
|---|---|---|
| DNS のレコード | ドメインの DNS(別アカウント) | blog.example.com を CloudFront に向ける。証明書の所有確認にも使う |
| ACM 証明書 | us-east-1 | blog.example.com の HTTPS 通信の証明 |
| CloudFront の別名(Alias) | CloudFront ディストリビューション | 「このドメイン名のアクセスを受け付ける」という宣言 |
ブラウザが https://blog.example.com/ を開いたとき、次のようにつながります。
ブラウザ
│ ① blog.example.com の IP は?
▼
DNS ── CNAME: blog.example.com → xxxx.cloudfront.net ──┐
│ ② CloudFront の IP が返る
┌────────────────────────────────────────────────────┘
▼
CloudFront(ディストリビューション xxxx)
│ ③ TLS の接続で「blog.example.com」と名乗る(SNI)
│ ④ 別名(Alias)に blog.example.com が入っているか? ← 入っていないと 403
│ ⑤ ACM 証明書(blog.example.com 用)を提示する
▼
ブラウザ: 証明書のドメイン名と、開いたドメイン名が一致 → HTTPS で表示
つまり、次の 3 つが同じ名前でそろって初めて繋がります。
- DNS:
blog.example.comが CloudFront のxxxx.cloudfront.netを指している - CloudFront の別名:
blog.example.comが入っている - ACM 証明書:
blog.example.com用で、CloudFront に設定されている
どれか 1 つが欠けると、表示されないか、証明書のエラーになります。
もう 1 本、別のつながりがあります。DNS の検証用 CNAMEです。
ACM ──「このドメインはあなたのものですか?」──▶ DNS の検証用 CNAME(_xxxx.blog.example.com)
ACM ◀──────── レコードがあれば、所有者と確認 ────────┘ → 証明書を発行(ISSUED)
ACM は、ドメインの DNS に指定のレコードがあるかどうかで所有者かどうかを判断します。 こちらのアカウントからは DNS を触れないので、このレコードも依頼して登録してもらいます。
全体の流れ#
① 証明書をリクエストする us-east-1 に ACM 証明書(DNS 検証)を作る
│
② 検証用の CNAME を登録 DNS 側で登録 → 証明書が ISSUED になる
│
③ CloudFront を更新 別名と証明書を設定する
│
④ サイト用の CNAME を登録 blog.example.com → xxxx.cloudfront.net
│
⑤ 表示を確認する
①と③の方法が 2 通りあります。CloudFormation で管理する方法(A)と、AWS CLI で直接設定する方法(B)です。②と④の DNS の依頼は、どちらでも同じです。
証明書は us-east-1 に作る#
CloudFront に使う ACM 証明書は、us-east-1(バージニア北部)にあるものに限られます。 CloudFront のスタックなどが東京リージョンなどにあっても、証明書だけは us-east-1 に作ります。
方法 A: CloudFormation#
① 証明書のスタック(us-east-1)#
証明書だけを、別のスタックにします。CloudFormation は 1 つのスタックを 1 つのリージョンに作るため、us-east-1 の証明書は、他のリージョンのスタックには入れられません。
# cert.yaml
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
DomainName:
Type: String
Default: blog.example.com
Resources:
Certificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref DomainName
ValidationMethod: DNS
Outputs:
CertificateArn:
Value: !Ref Certificate
aws cloudformation create-stack --region us-east-1 \
--stack-name cert --template-body file://cert.yaml
ValidationMethod: DNS の証明書は、DNS の検証が通るまでスタックの作成が終わりません(CREATE_IN_PROGRESS のまま待ちます)。
待っている間に、検証用のレコードを取り出して依頼します。
CERT=$(aws cloudformation describe-stack-resources --region us-east-1 \
--stack-name cert \
--query 'StackResources[0].PhysicalResourceId' --output text)
aws acm describe-certificate --region us-east-1 --certificate-arn "$CERT" \
--query 'Certificate.DomainValidationOptions[].ResourceRecord'
レコードが登録されると、数分で証明書が ISSUED、スタックが CREATE_COMPLETE になります。
③ CloudFront のスタックに別名と証明書を足す#
CloudFront を作っているテンプレートに、ドメインと証明書のパラメータを足します。
Parameters:
CustomDomainName:
Type: String
Default: blog.example.com
CertificateArn:
Type: String
Default: arn:aws:acm:us-east-1:<アカウント ID>:certificate/<ID>
Conditions:
UseCustomDomain: !And
- !Not [!Equals [!Ref CustomDomainName, ""]]
- !Not [!Equals [!Ref CertificateArn, ""]]
CloudFront の Aliases と ViewerCertificate を、条件で切り替えます。
DistributionConfig:
Aliases: !If [UseCustomDomain, [!Ref CustomDomainName], !Ref "AWS::NoValue"]
ViewerCertificate: !If
- UseCustomDomain
- AcmCertificateArn: !Ref CertificateArn
SslSupportMethod: sni-only
MinimumProtocolVersion: TLSv1.2_2021
- CloudFrontDefaultCertificate: true
sni-onlyにします。専用 IP(vip)は月額の費用がかかり、今のブラウザなら SNI で困りません。- 2 つのパラメータを空にすると、独自ドメインをやめて、CloudFront の既定のドメインと既定の証明書に戻せます。
- 証明書のスタックの出力は、別リージョンなので
Fn::ImportValueでは渡せません(エクスポートは同じリージョン内のみ)。パラメータで ARN を直接渡します。
適用の前に、変更セットで差分を確認します。
aws cloudformation deploy --region <CloudFront のスタックのリージョン> \
--template-file template.yaml --stack-name <スタック名> \
--capabilities CAPABILITY_NAMED_IAM --no-execute-changeset
差分が Distribution の Modify(置換なし)だけであることを確認して、実行します。
方法 A の特徴#
- 証明書・別名・パラメータがコードに残り、作り直せる。
- スタックを消せば、証明書もまとめて消える。
- 証明書が別リージョンのため、スタックが 2 つになる。ARN の受け渡しは手動。
方法 B: AWS CLI#
① 証明書をリクエストする#
CERT=$(aws acm request-certificate --region us-east-1 \
--domain-name blog.example.com \
--validation-method DNS \
--query CertificateArn --output text)
# 検証用のレコードが出るまで、数秒かかる
aws acm describe-certificate --region us-east-1 --certificate-arn "$CERT" \
--query 'Certificate.DomainValidationOptions[].ResourceRecord'
CLI はリクエストするだけで、その場で戻ります。検証が終わるのを待つなら次を使います。
aws acm wait certificate-validated --region us-east-1 --certificate-arn "$CERT"
③ CloudFront に別名と証明書を設定する#
CloudFront の設定の更新は、設定全体を取得して、書き換えて、丸ごと送り返す形です。更新には ETag が要ります。
ID=<ディストリビューション ID>
aws cloudfront get-distribution-config --id "$ID" > cfg.json
ETAG=$(jq -r .ETag cfg.json)
jq --arg d "blog.example.com" --arg c "$CERT" '
.DistributionConfig
| .Aliases = {Quantity: 1, Items: [$d]}
| .ViewerCertificate = {
ACMCertificateArn: $c,
SSLSupportMethod: "sni-only",
MinimumProtocolVersion: "TLSv1.2_2021",
CloudFrontDefaultCertificate: false
}' cfg.json > new.json
aws cloudfront update-distribution --id "$ID" \
--if-match "$ETAG" --distribution-config file://new.json
反映の状況は、ステータスが Deployed になるまで見ます。
aws cloudfront wait distribution-deployed --id "$ID"
方法 B の特徴#
- 手順が短く、1 つのアカウントで素早く試せる。
- 設定がコードに残らない。作り直しや引き継ぎで再現しにくい。
- ディストリビューションを CloudFormation で作っている場合は、CLI で変えると差分(ドリフト)が出ます。次にスタックを更新したとき、CLI で足した別名と証明書が消えます。CloudFormation で作ったものは、CloudFormation で変えます。
どちらを選ぶか#
| A: CloudFormation | B: AWS CLI | |
|---|---|---|
| 向く場面 | 継続して運用する・作り直す可能性がある | 試す・一時的・既に CLI で作っている |
| 記録 | コードに残る | 残らない(手順書が要る) |
| 証明書のリージョン | us-east-1 に別スタック | us-east-1 にそのまま作る |
| 後始末 | スタックの削除 | 証明書と設定を個別に戻す |
DNS に登録するレコード#
ドメインを管理しているのは別のアカウントなので、こちらからは登録できません。次の 2 つを、順番に依頼します。どちらの方法でも同じです。
1 件目: 証明書の検証用(CNAME)#
| 項目 | 値 |
|---|---|
| タイプ | CNAME |
| 名前 | _<ランダム>.blog.example.com. |
| 値 | _<ランダム>.<ランダム>.acm-validations.aws. |
登録すると、証明書が ISSUED になります。
このレコードは、証明書を自動更新するたびに ACM が参照するので、消さずに残します。
同じアカウントで同じドメインの証明書は、作り直しても検証用の CNAME の値が同じです。CLI で作った証明書を CloudFormation で作り直したときも、依頼したレコードをそのまま使えました。
2 件目: ドメインの向き先(CNAME)#
| 項目 | 値 |
|---|---|
| タイプ | CNAME |
| 名前 | blog.example.com |
| 値 | <ディストリビューション>.cloudfront.net |
値の https:// や末尾の / は付けません。値は、次で確認できます。
aws cloudfront get-distribution --id "$ID" --query 'Distribution.DomainName' --output text
登録の順番#
2 件目の CNAME は、CloudFront に別名と証明書を設定した後に登録します。
| 順番 | 起きること |
|---|---|
| 設定してから登録(正しい) | 登録した時点で、すぐ HTTPS で表示できる |
| 登録してから設定 | CloudFront が「この名前の別名は無い」と判断してアクセスを拒否する(403)。設定が終われば直る |
前の「つながりの仕組み」の ④ にあたります。CloudFront は、アクセスされたドメイン名が別名に入っているかを見ます。入っていない名前は、DNS が正しくても受け付けません。
A レコードや AAAA レコードではだめなのか#
結論から書くと、CNAME にしました。A レコードと AAAA レコードは試していません。 理由は、固定の IP アドレスを書く A / AAAA レコードでは、CloudFront の向き先を正しく指定できないからです。
固定の IP アドレスを書けない#
A レコードは「ドメイン名 → IPv4 アドレス」、AAAA レコードは「ドメイン名 → IPv6 アドレス」の対応です。 一方、CloudFront には自分のディストリビューション専用の IP アドレスがありません。
- 世界中のエッジロケーションが多数の IP アドレスを共有していて、どこに繋ぐかは、問い合わせた場所や混み具合で DNS が動的に返します。
- 返される IP アドレスは、予告なく変わります。
- AWS は CloudFront 全体の IP の範囲(
ip-ranges.json)を公開していますが、これは「CloudFront が使いうる全部の範囲」で、ディストリビューションに割り当てられた値ではありません。
ある時点で返った IP アドレスを A / AAAA レコードに手で書くと、エッジが入れ替わったときに繋がらなくなったり、遠いエッジに固定されたりします。運用できません。 (専用 IP を使える有料の機能もありますが、通常の用途には過剰です。)
AAAA レコードを別に書く必要もない#
IPv6 を有効にしたディストリビューションは、CNAME の向き先(xxxx.cloudfront.net)の側で、CloudFront が A と AAAA の両方を返します。
CNAME を 1 件登録するだけで、クライアントは IPv4 でも IPv6 でも繋がります。実際に名前を引くと、IPv6 のアドレスも返ります。
getent hosts blog.example.com
# 2600:9000:... xxxx.cloudfront.net blog.example.com
「A レコード」にできるケース: Route 53 のエイリアス#
DNS が Route 53 なら、エイリアスレコードという選択肢があります。 タイプは A(と AAAA)ですが、値は IP アドレスではなく「CloudFront のディストリビューション」を指定します。Route 53 が、その時点の正しい IP アドレスを返します。
| 方式 | 使える DNS | 特徴 |
|---|---|---|
| CNAME | どの DNS サービスでも可 | 汎用。サブドメイン専用(ゾーンの頂点には置けない) |
| エイリアス(A / AAAA) | Route 53 のみ | ゾーンの頂点(example.com そのもの)にも置ける。AAAA も別に作る。CloudFront へのエイリアスの問い合わせは無料 |
サブドメイン(blog.example.com)なら、CNAME で足ります。依頼する側も、DNS の種類を気にせず登録できます。
ゾーンの頂点(example.com そのもの)で公開したい場合は、CNAME が置けない(頂点には他の種類のレコードも必要なため)ので、Route 53 のエイリアスを使います。
確認#
DNS が反映されたら、HTTPS で表示できることを確認します。
curl -sI https://blog.example.com/ | head -3
# HTTP/2 200
後始末と注意#
- 検証用の CNAME(1 件目)は、削除しないでください。消すと、証明書の自動更新が失敗します。
- 独自ドメインをやめるときは、CloudFront の別名と証明書を外してから、証明書を削除します。CloudFront が使用中の証明書は削除できません。順番を逆にすると、エラーになります。
- 証明書のスタック(us-east-1)を別に作った場合は、CloudFront 側のスタックを更新してから、証明書のスタックを削除します。
まとめ#
| 項目 | 内容 |
|---|---|
| 結ぶもの | DNS のレコード、ACM 証明書、CloudFront の別名を、同じドメイン名でそろえる |
| 証明書の場所 | us-east-1(CloudFront の要件) |
| 方法 A(CloudFormation) | 証明書は別スタック。ARN はパラメータで渡す。コードに残る |
| 方法 B(AWS CLI) | request-certificate と update-distribution。手軽だが、設定が残らず、CloudFormation 管理のものとは併用しない |
| DNS への依頼 | CNAME を 2 件。検証用が先、向き先は CloudFront の設定後 |
| A / AAAA | 試していない。CloudFront に固定 IP が無いため、IP を書く方式は使えない。Route 53 のエイリアスなら A / AAAA になる |