Route 53 と ACM: DNS委任とHTTPS化で独自ドメインを公開する
前章で、Tasuku は HTTP で初めて外部に公開されました。ただし HTTP のままでは、通信の中身が誰にでも読める状態です。この章では、独自ドメインを Route 53 に委任し、ACM で SSL 証明書を発行し、ALB の 443 番ポートで HTTPS を終端します。HTTP で公開してしまってから、それを HTTPS へ置き換える章です。
7.1. DNS委任とRoute 53ホストゾーンの基礎
Route 53は、ドメイン名を IP アドレスなどのリソースに変換する DNS サービスです。ドメインを Route 53 で管理するには、まずホストゾーンを作ります。ホストゾーンは、そのドメイン配下の DNS レコードをまとめる入れ物です。
ホストゾーンを作成すると、Route 53 はNSレコードとSOAレコードを自動的に生成します。1 NS レコードは、そのドメインの名前解決をどのネームサーバーに問い合わせればよいかを示すレコードです。ドメインをレジストラで取得している場合、この NS レコードの内容をレジストラ側に設定して初めて、世界中の DNS リゾルバが Route 53 へ問い合わせるようになります。この設定をDNS委任と呼びます。SOA レコードは、ゾーンの管理情報 (プライマリネームサーバーやシリアル番号など) を持つレコードです。
自動生成された NS レコードと SOA レコードには、手を加えません。AWS は、これらのレコードを追加したり削除したりしないよう明示的に案内しています。2 技術的にも、Terraform で同じ内容の NS/SOA レコードを別リソースとして宣言すると、既に存在するレコードとの間で競合が生じます。第6章までに登場したリソースは、いずれも自分でゼロから作るものでした。この章で初めて、AWS が自動的に作ったものにあえて触れないという判断が出てきます。
ホストゾーンは、作成した時点から月額の課金が発生するリソースです。3 ACM 証明書自体は、ALB のように証明書を統合的に使える AWS のサービスと組み合わせる限り無料です。4 具体的な料金は改定されるため本文には書きません。試す場合はRoute 53の料金ページで現行の料金を確認してください。
7.2. ドメインの準備: tasuku.exampleが使えない理由
ここまでの章で使ってきたtasuku.exampleというドメインには、他の章のリソース名にはない制約があります。.exampleはRFC 2606で、文書やサンプルコードの中でだけ使うことを目的として予約されたトップレベルドメインです。実在するレジストリもレジストラも存在せず、誰もこのドメインを所有できません。第1章で「独自ドメインは第 7 章以降で使うが、本文のドメインはそのままでは登録できない」と予告した理由が、ここにあります。5
そのため、この章からvar.domain_nameという変数を導入します。この変数には既定値を設定しません。読者が自分の判断で、所有している実ドメインを明示的に指定しない限り、コードは動きません。第2章のvar.alert_emailと同じ考え方です。
AWS の利用規約は、所有権や管理権限を持たないドメインのホストゾーンを作成することを禁じています。6 tasuku.exampleは RFC 2606 で予約された架空のドメインであり、誰も所有していません。レジストラでの DNS 委任という手順自体、委任先が実在しないため成立しません。この章のコードを実際に試す場合は、必ず自分が所有する実ドメインをvar.domain_nameに指定してください。
実際に apply する場合、この章の手順は 1 回では完結しません。ホストゾーンを作った直後は、証明書の DNS 検証がまだ完了できる状態にないためです。
1 回目はterraform apply -target=aws_route53_zone.mainのように対象を絞り、aws_route53_zoneだけを作ります。terraform output route53_zone_name_serversでネームサーバーの一覧を取得し、ドメインを取得したレジストラの管理画面で、そのネームサーバーへの委任を設定します。委任がインターネット全体に伝播するまで、しばらく時間を置きます。委任が反映されたら 2 回目の apply を行います。今度は DNS の問い合わせが実際に Route 53 まで届くため、ACM の DNS 検証が完了し、HTTPS リスナーへの証明書のアタッチまで進みます。
7.3. ACMによる証明書のDNS検証
ACM (AWS Certificate Manager) は、SSL 証明書の発行と更新を管理するサービスです。証明書の発行には、そのドメインを本当に管理しているという証明が要ります。この証明の方式には、メールとDNS検証の 2 つがあり、この章では DNS 検証を使います。ACM が指定する CNAME レコードを Route 53 に登録するだけで検証が完了し、更新時も同じ仕組みで自動的に再検証されるためです。
この章で発行する証明書は、tasuku.exampleという apex ドメインと、*.tasuku.exampleというワイルドカードの両方をカバーします。ワイルドカードを含めておくことで、第 10 章でapi.tasuku.exampleを使うときに、証明書を追加で発行し直す必要がありません。
apex ドメインとワイルドカードドメインは、どちらも同じ CNAME レコード名・レコード値で検証されます。7 ACM が検証用に払い出すdomain_validation_optionsは、証明書がカバーするドメインの数だけエントリを持ちますが、apex とワイルドカードの組み合わせでは、この 2 つのエントリが指す先が同一のレコードになります。そのため、Terraform で検証用レコードを作るコードは、同じレコードを 2 回作ろうとする形になります。これは想定内の挙動で、レコード作成のallow_overwriteを有効にしておけば、2 回目の作成が 1 回目を上書きするだけで問題は起きません。7 将来、証明書がカバーするドメインの構成を変える場合は、この収束の仕組みを踏まえてレコードの管理方法を見直してください。
7.4. 証明書からリスナーまでの依存関係
証明書の発行を要求してから、実際に HTTPS リスナーで使えるようになるまでには、いくつかの段階があります。証明書を要求すると、ACM はまず検証用の CNAME レコードの情報を返します。そのレコードを Route 53 に登録し、ACM が DNS 越しにそのレコードを確認できて初めて、証明書は発行済みの状態になります。
この一連の流れを Terraform で表現するとき、リスナーが参照する証明書の ARN は、証明書を要求した直後のリソースからではなく、検証が完了したことを表すリソースから取り出します。8 検証がまだ終わっていない証明書をリスナーにアタッチしようとする状態を、コード上の依存関係として避けるためです。
証明書からリスナーまでの依存は、ゾーンの作成から検証レコードの登録、証明書の検証完了、リスナーへのアタッチという一方向の連なりで、途中で行き来する関係はありません。第5章5.3 節で扱った、backend の設定がその backend 自身の管理下にあるバケットを要求してしまう「鶏と卵」の問題とは異なり、この章の依存関係は Terraform が実行順序を自動的に解決できる範囲に収まっています。
実際に apply する場合、DNS 委任が済んでいない状態で証明書の検証を待つリソースは、既定で 75 分間待ち続けたのちにタイムアウトします。9 前節で述べた「1 回目の apply でゾーンだけを作る」という手順は、このタイムアウトを避けるための実務的な理由でもあります。
7.5. 443番の追加と80番のリダイレクト化
第4章4.7 節のセキュリティグループチェーンの図は、連載が完成した後の姿として 443 番ポートを示していました。ここでその約束を果たします。ALB 用セキュリティグループに 443 番の ingress を追加します。
resource "aws_vpc_security_group_ingress_rule" "alb_https" {
security_group_id = aws_security_group.alb.id
cidr_ipv4 = "0.0.0.0/0"
from_port = 443
to_port = 443
ip_protocol = "tcp"
description = "Internetからの443番 (HTTPS)"
}
第6章で作った 80 番のリスナーは、固定レスポンスを返すリスナーでした。この章から、80 番の役割を変えます。80 番で受けたリクエストは、すべて 443 番へのリダイレクトとして扱い、実際の応答は 443 番の新しいリスナーに任せます。連載を通じて意図的に後戻りする箇所は、この 80 番のリダイレクト化と、第 10 章で固定レスポンスから実際の転送に切り替える箇所の 2 つだけです。
443 番のリスナーには、証明書に加えてSSLポリシーの指定が要ります。SSL ポリシーは、ALB が対応する TLS のバージョンや暗号スイートの組み合わせです。指定を省略した場合の既定値は、AWS マネジメントコンソールから作成したときと、Terraform のような API 経由で作成したときとで異なります。10 Terraform 経由の既定値はELBSecurityPolicy-2016-08という古いポリシーのままで、AWS が現在推奨するポリシーではありません。AWS は、耐量子暗号に対応した新しい TLS ポリシーの採用を推奨しています。11 このポリシーは、量子コンピュータへの対応が可能なクライアントと、TLS 1.3 のみのクライアント、TLS 1.2 までしか対応しないクライアントのいずれとも通信できるよう設計されており、既存のクライアントとの互換性を保ったまま段階的に移行できます。この章では、明示的にこのポリシーを指定します。
ここで指定する SSL ポリシーの値は、AWS 側が管理する推奨値であり、随時更新される可能性があります。この連載が「執筆時点の Terraform・provider のバージョンで固定し、連載完成まで更新しない」と定めている方針とは別の軸のリスクです。試す場合は、AWSの現行の推奨ポリシーを確認してください。
7.6. apexへのaliasレコード
apex ドメイン (tasuku.example自体) から ALB への向き先は、aliasレコードという仕組みで設定します。通常の DNS では、ドメインの頂点である apex に CNAME レコードを置くことはできません。12 エイリアスレコードは、Route 53 が用意する拡張機能で、ALB のような他の AWS リソースを apex から直接指すことができます。エイリアスレコードには、対象リソースの DNS 名とホストゾーン ID に加え、対象の健全性チェックを反映するかどうかの指定が必須です。13 TTL はエイリアス特有の仕組みにより 60 秒に固定され、明示的な指定はできません。14
resource "aws_route53_record" "apex" {
zone_id = aws_route53_zone.main.zone_id
name = var.domain_name
type = "A"
alias {
name = aws_lb.main.dns_name
zone_id = aws_lb.main.zone_id
evaluate_target_health = true
}
}
AAAAレコード (IPv6 向けのエイリアス) は用意しません。ALB が IPv6 のエイリアスを提供するのは、ALB 自体を dualstack モードで動かしている場合に限られます。15 第4章の VPC は IPv6 の CIDR ブロックを割り当てておらず、第6章の ALB もip_address_typeを指定していないため、dualstack の前提条件を満たしていません。IPv6 対応は、この連載のスコープには含めません。
7.7. Terraform実装
ここまでの内容を、route53-acm.tfという新しいファイルに書きます。この連載で確立してきた「章の主なリソースを 1 つのファイルにまとめる」方針を踏襲し、SG・リスナーの変更は既存のalb.tfに、それ以外の新規リソースはこのファイルに置きます。
resource "aws_route53_zone" "main" {
name = var.domain_name
tags = {
Name = "${local.name_prefix}-zone"
}
}
resource "aws_acm_certificate" "main" {
domain_name = var.domain_name
subject_alternative_names = ["*.${var.domain_name}"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
tags = {
Name = "${local.name_prefix}-cert"
}
}
resource "aws_route53_record" "cert_validation" {
for_each = {
for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
zone_id = aws_route53_zone.main.zone_id
name = each.value.name
type = each.value.type
records = [each.value.record]
ttl = 60
}
resource "aws_acm_certificate_validation" "main" {
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
}
aws_acm_certificateにはlifecycle { create_before_destroy = true }を指定します。証明書がリスナーで使用中の状態から別の証明書に置き換わるとき、先に新しい証明書を作ってから古い証明書を削除する順序にするためです。16
alb.tf側は、第6章で作った 80 番のリスナーのdefault_actionを書き換え、443 番のリスナーを新設します。
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-Res-PQ-2025-09"
certificate_arn = aws_acm_certificate_validation.main.certificate_arn
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
status_code = "200"
message_body = "Tasuku ALB is up (${var.env})"
}
}
}
443 番のリスナーのdefault_actionも、第6章と同じ固定レスポンスのままです。ターゲットグループは第 10 章まで作らないため、転送先を持つforwardアクションはまだ書けません。
最後に、後続の章が参照する値をoutputs.tfに追加します。
output "route53_zone_id" {
description = "Route 53ホストゾーンID (将来のレコード追加、動作確認のdig等で参照)"
value = aws_route53_zone.main.zone_id
}
output "route53_zone_name_servers" {
description = "ホストゾーンのネームサーバー一覧 (実際に運用する場合、ドメインレジストラでのNS委任=ドメイン準備パス1で使う)"
value = aws_route53_zone.main.name_servers
}
output "acm_certificate_arn" {
description = "ACM証明書のARN (apex+ワイルドカードSAN、第10章のapiサブドメインもこの証明書でカバーする)"
value = aws_acm_certificate_validation.main.certificate_arn
}
output "alb_listener_https_arn" {
description = "443番リスナーのARN (第10章のリスナールール追加、固定レスポンス→forward切替で参照)"
value = aws_lb_listener.https.arn
}
7.8. 動作確認
terraform fmt -check -recursive -diffとterraform validateを実行し、構文とプロバイダスキーマの整合を確認します。17 この章の検証も、これまでと同じく validate までで、planやapplyは対象に含めません。
この章は、これまでの章よりも検証の谷が深い章です。VPC や S3、ALB は、名前がフィクションでも実際に apply すれば正しく動きます。この章のホストゾーン作成や ACM の DNS 検証は、実在するドメインへの委任という、Terraform の外側にある作業が完了して初めて意味を持ちます。validateが確認するのは構文とスキーマの整合だけであり、DNS 委任が実際に機能するかどうかや、証明書の検証が完了するかどうかは、この連載の検証範囲に含まれません。
実際に自分の実ドメインで apply する場合は、7.2 節で述べた 2 回の apply の手順を踏んだうえで、curl https://<自分のドメイン>/を実行すると、200 番のステータスとともにTasuku ALB is up (dev)という本文が返ってきます。http://でアクセスした場合は、301 のリダイレクトとともにhttps://へ転送されることも確認できます。
本章のまとめ
- Route 53 のホストゾーン作成で自動生成される NS・SOA レコードには手を加えません
tasuku.exampleは実在しないドメインのため、この章からvar.domain_nameという既定値なしの変数を導入し、読者自身の実ドメインへの置き換えを前提にしました- apex+ワイルドカード SAN の証明書は、DNS 検証用の CNAME レコードが同一の値に収束するため、
allow_overwriteでこれを吸収します - 証明書の検証完了を表すリソースから ARN を取り出すことで、検証未完了の証明書をリスナーにアタッチしてしまう状態を避けています
- 80 番リスナーを固定レスポンスから 443 番へのリダイレクトに切り替え、443 番の新しいリスナーに HTTPS を終端させました
- apex への A レコードはエイリアスで設定し、IPv6 の AAAA レコードは dualstack の前提条件を満たさないため省略しました
次に読む
- 第 8 章: ECS 前編: ECR リポジトリとクラスタ、タスク定義、2 種類の IAM ロールでコンテナ実行基盤を整える
- 第 6 章: ALB: L7 ロードバランサーの基礎と HTTP での最初の公開
- 連載の目次: 全 12 章の構成と読み進め方