Terraform 実践 #10 ドメイン・HTTPS・完成 — Route 53、ACM 検証、CloudFront と provider alias
最後のピースです。今のサービスは ALB の長い自動生成アドレスで HTTP 接続しかできません。今回はドメインをつなぎ、証明書を発行して HTTPS を有効にし、静的アセットに CDN を取り付けて、#1 で描いた完成形に到達します。Terraform の観点で新しく学ぶことは 2 つです。ACM の DNS 検証をリソースで表現するイディオムと、リージョンの異なるリソースを 1 つのコードで扱う provider alias です。ドメインはすでに Route 53 にホストゾーンがある前提で進めます(購入とゾーンの概念はドメイン・DNS の基礎を参照してください)。
ホストゾーンはデータソースで #
ドメイン自体はこのプロジェクトが作ったものではないので、基礎 #4 の原則どおり、管理権限を持たずに読むだけにします。
# dns.tf(新規ファイル)
data "aws_route53_zone" "main" {
name = "example.com"
}ACM 証明書と DNS 検証イディオム #
ACM 証明書は申請だけでは発行されず、ドメイン所有の検証を経ます。コンソールでは「レコードを作成」ボタンをクリックするあの過程が、Terraform ではリソース 3 つのイディオムになります。申請、検証レコード、検証待ちです。
resource "aws_acm_certificate" "alb" {
domain_name = "app.example.com"
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
resource "aws_route53_record" "alb_cert_validation" {
for_each = {
for dvo in aws_acm_certificate.alb.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
record = dvo.resource_record_value
}
}
zone_id = data.aws_route53_zone.main.zone_id
name = each.value.name
type = each.value.type
ttl = 60
records = [each.value.record]
}
resource "aws_acm_certificate_validation" "alb" {
certificate_arn = aws_acm_certificate.alb.arn
validation_record_fqdns = [for r in aws_route53_record.alb_cert_validation : r.fqdn]
}真ん中のリソースの for_each は、基礎 #4 の for 式が実務で最もよく使われる形です。証明書が要求する検証レコードの一覧をマップに変換してレコードを作ります。最後の aws_acm_certificate_validation は実際のリソースを作るのではなく、検証完了まで待つ待機装置です。これを参照するリソースは、証明書の発行が完了した後に作られます。初見では戸惑いますが、ACM をコードで扱うあらゆる場面で遭遇する標準イディオムです。
HTTPS リスナーとリダイレクト #
発行された証明書を ALB に取り付けます。#4 で予告したとおり、80 番のリスナーはリダイレクトに変わります。
# lb.tf を修正
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.main.arn
port = 443
protocol = "HTTPS"
certificate_arn = aws_acm_certificate_validation.alb.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.ecs.arn
}
}
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"
}
}
}443 リスナーが証明書 ARN を aws_acm_certificate_validation から取ることが、先ほどのイディオムの仕上げです。ALB のセキュリティグループに 443 のインバウンドを追加するのも忘れません。あとは https://app.example.com へ向かう最後のつなぎ、エイリアスレコードを取り付けます。
resource "aws_route53_record" "app" {
zone_id = data.aws_route53_zone.main.zone_id
name = "app.example.com"
type = "A"
alias {
name = aws_lb.main.dns_name
zone_id = aws_lb.main.zone_id
evaluate_target_health = true
}
}provider alias — CloudFront の us-east-1 要件 #
静的アセットの番になると、実務で有名な落とし穴に遭遇します。CloudFront に取り付ける証明書は、リージョンに関係なく必ず us-east-1 の ACM に存在しなければなりません。今の provider は ap-northeast-2 ですが、どうすればよいでしょうか。答えが provider alias です。同じ provider を別の設定でもう 1 セット登録する機能です。
# providers.tf に追加
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
default_tags {
tags = {
Project = "myapp"
Env = var.env
ManagedBy = "terraform"
}
}
}resource "aws_acm_certificate" "cdn" {
provider = aws.us_east_1
domain_name = "static.example.com"
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}リソースに provider = aws.us_east_1 を指定すると、そのリソースだけバージニアに作られます。検証レコードと待機装置は先ほどのイディオムと同じ形で付きます(レコード自体はデフォルトの provider で、証明書と待機は alias で作る点にだけ注意します)。CloudFront ディストリビューションは OAC(Origin Access Control)で #5 の閉じた assets バケットを読みます。ディストリビューションのリソースは引数が多いので全文は載せませんが、骨組みは単純です。origin が S3 バケット、証明書が今作った cdn 証明書、バケットポリシーが「このディストリビューションから来る GetObject だけ許可」です。バケットは最後までパブリックにならず、世界と接するのは CDN だけです。
完成、そして振り返り #
static.example.com のエイリアスレコードまで取り付ければ、#1 の図がすべてコードになりました。10 本の道のりをリソースではなく決定で振り返るとこうなります。タグとバックエンドを初日に立て(#1)、ネットワーク階層を基準にセキュリティを設計し(#2・#3)、トラフィックを受けながら階層を乗り換え(#4・#8)、データには安全装置をかけ(#5)、シークレットを記録から消し(#6)、権限を参照で絞り(#7)、構造を再作成なしにリファクタリングして環境を複製しました(#9)。これらの決定は Terraform の文法ではなくインフラ運用の判断であり、Terraform はその判断を、レビューと履歴のあるコードにしてくれた道具です。
実習を終えたら片付けます。dev は destroy 1 回で済み、これで料金の心配も終わりです。残る問いは 1 つです。このコードをチームで一緒に、人の手の apply なしに運用するには何が必要でしょうか。
まとめ #
この記事で扱った内容です。
- すでにあるホストゾーンはデータソースで読みます。ACM は申請、検証レコード(for_each イディオム)、検証待ちのリソース 3 つで発行します
- HTTPS リスナーは検証待ちリソースから証明書 ARN を受け取って発行完了後に作られ、80 番リスナーは 301 リダイレクトに変わります
- CloudFront 用の証明書は必ず us-east-1 に必要で、provider alias で 1 つのコードから 2 つのリージョンを扱います
- CloudFront は OAC で閉じたバケットを読みます。バケットは最後までパブリックになりません
- 10 本の成果物はリソースの一覧ではなく、コードに残った運用判断です。実習の片付けは destroy 1 回です
次は Terraform 運用講座です。PR で plan をレビューして CI が apply を実行するチームワークフローから、ドリフト対応、ポリシーとコストのガードレールまで、7 本で扱います。