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 章の構成と読み進め方