ACM 憑證、DNS 與 HTTPS 設定
HTTPS 的運作需要三個元件配合:一個管理網域記錄的 DNS zone、一張證明網域所有權的 TLS 憑證、以及一個用這張憑證終結 TLS 連線的入口(ALB listener)。這三者在 IaC 裡各自是獨立資源,但建立順序有依賴——zone 先存在、憑證才能用 DNS 驗證、驗證通過才能掛到 listener。把這條鏈路寫進 Terraform,讓憑證的申請、驗證與續期都在版本控制裡,是避免「憑證過期才發現沒人盯」的結構性做法。
Route 53 Hosted Zone
Hosted zone 是 Route 53 用來管理某個網域的 DNS 記錄集合。建立 zone 後,Route 53 會分配一組 NS(Name Server)記錄,網域的 DNS 解析就由這組 NS 負責。
Public vs Private Zone
Public hosted zone 對應的是可從網際網路解析的網域(如 example.com),用於對外服務的 A / CNAME / MX 記錄。Private hosted zone 只在指定的 VPC 內可解析,用於內部服務發現(如 db.internal.example.com 解析到 RDS 的 private IP)。多數專案兩者都需要:public zone 給對外流量、private zone 給內部服務互連。
1resource "aws_route53_zone" "public" {
2 name = "example.com"
3 tags = { Environment = "production" }
4}
5
6resource "aws_route53_zone" "private" {
7 name = "internal.example.com"
8
9 vpc {
10 vpc_id = aws_vpc.main.id
11 }
12
13 tags = { Environment = "production" }
14}子網域 delegation
當 dev / staging / prod 各用獨立帳號時,每個帳號建自己的 hosted zone 管理子網域(如 dev.example.com)。父網域的 zone 需要加一組 NS 記錄指向子網域的 zone,這個動作叫 delegation。
1resource "aws_route53_record" "dev_ns" {
2 zone_id = aws_route53_zone.public.zone_id
3 name = "dev.example.com"
4 type = "NS"
5 ttl = 300
6 records = aws_route53_zone.dev.name_servers
7}delegation 的 NS 記錄指向子帳號 zone 的 name server。子帳號內的所有 DNS 記錄(如 api.dev.example.com)由子帳號的 zone 管理,父帳號不需要逐條設定。跨帳號 delegation 需要兩邊的 Terraform 各自管理自己的 zone,NS 記錄在父帳號的 state 裡。
判讀設定是否正確:用 dig dev.example.com NS 查回的 name server 應該是子帳號 zone 的 NS,不是父帳號的。如果查回父帳號的 NS,代表 delegation 沒生效,子網域的 DNS 記錄不會被解析。
ACM 憑證申請與 DNS 驗證
AWS Certificate Manager(ACM)提供免費的 TLS 憑證,條件是透過 DNS 或 email 驗證網域所有權。DNS 驗證是 IaC 友善的方式——ACM 要求在指定網域下建一條 CNAME 記錄,記錄值由 ACM 提供,驗證通過後憑證自動簽發。
1resource "aws_acm_certificate" "main" {
2 domain_name = "example.com"
3 subject_alternative_names = ["*.example.com"]
4 validation_method = "DNS"
5
6 lifecycle {
7 create_before_destroy = true
8 }
9
10 tags = { Environment = "production" }
11}subject_alternative_names 加 *.example.com 讓同一張憑證涵蓋所有子網域(如 api.example.com、admin.example.com),省去為每個子網域各申請一張。
DNS 驗證記錄
ACM 簽發後會產出一組驗證用的 CNAME 記錄。用 Terraform 自動在 Route 53 建立這些記錄,讓驗證流程不需要手動操作:
1resource "aws_route53_record" "cert_validation" {
2 for_each = {
3 for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
4 name = dvo.resource_record_name
5 record = dvo.resource_record_value
6 type = dvo.resource_record_type
7 }
8 }
9
10 zone_id = aws_route53_zone.public.zone_id
11 name = each.value.name
12 type = each.value.type
13 ttl = 300
14 records = [each.value.record]
15
16 allow_overwrite = true
17}
18
19resource "aws_acm_certificate_validation" "main" {
20 certificate_arn = aws_acm_certificate.main.arn
21 validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
22}aws_acm_certificate_validation 資源會等到 ACM 確認驗證通過才算 apply 成功。如果 DNS 記錄設錯或 zone 的 NS delegation 有問題,這個資源會卡住直到 timeout——排查方向是先確認驗證 CNAME 記錄能被公網 DNS 解析。
create_before_destroy
lifecycle { create_before_destroy = true } 在憑證需要替換時(如增加 SAN、更換網域),讓 Terraform 先建新憑證、再刪舊憑證。沒有這個設定,預設行為是先刪後建——刪除的瞬間 ALB listener 失去憑證,HTTPS 連線全部中斷直到新憑證驗證通過(可能要幾分鐘到幾十分鐘)。
ALB HTTPS Listener
憑證驗證通過後,把它掛到 ALB 的 HTTPS listener:
1resource "aws_lb_listener" "https" {
2 load_balancer_arn = aws_lb.main.arn
3 port = 443
4 protocol = "HTTPS"
5 ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
6 certificate_arn = aws_acm_certificate_validation.main.certificate_arn
7
8 default_action {
9 type = "forward"
10 target_group_arn = aws_lb_target_group.app.arn
11 }
12}ssl_policy 決定 TLS 版本與加密套件。ELBSecurityPolicy-TLS13-1-2-2021-06 支援 TLS 1.2 和 1.3、停用已知不安全的舊版協定。選型判準是相容性與安全性的平衡——TLS 1.3-only policy 最安全但可能排除舊版客戶端,多數場景用 1.2+1.3 的組合。
certificate_arn 引用的是 aws_acm_certificate_validation 而非直接引用 aws_acm_certificate,確保 listener 只在憑證驗證通過後才建立。
HTTP → HTTPS 重導
同時建立一個 HTTP listener,把所有 80 埠流量重導到 443:
1resource "aws_lb_listener" "http_redirect" {
2 load_balancer_arn = aws_lb.main.arn
3 port = 80
4 protocol = "HTTP"
5
6 default_action {
7 type = "redirect"
8 redirect {
9 port = "443"
10 protocol = "HTTPS"
11 status_code = "HTTP_301"
12 }
13 }
14}301 永久重導讓瀏覽器記住後續直接走 HTTPS。security group 仍然需要開放 80 埠入站,否則重導不會發生——client 連 80 埠被擋、收到的是連線失敗而非重導回應。
多網域與 SAN 憑證
一張 ACM 憑證最多支援 10 個 SAN(Subject Alternative Name)。多數場景用主網域 + wildcard(example.com + *.example.com)就夠用。如果有多個不同根網域(如 example.com 和 example-app.com),可以加進同一張憑證:
1resource "aws_acm_certificate" "multi_domain" {
2 domain_name = "example.com"
3 subject_alternative_names = [
4 "*.example.com",
5 "example-app.com",
6 "*.example-app.com",
7 ]
8 validation_method = "DNS"
9
10 lifecycle {
11 create_before_destroy = true
12 }
13}每個 SAN 網域都需要獨立的 DNS 驗證記錄。如果不同網域在不同的 hosted zone 裡,驗證記錄的建立要分別指向各自的 zone。
當 SAN 數量超過 10、或不同網域的憑證需要獨立管理(不同 team 負責不同網域),改用 aws_lb_listener_certificate 額外掛載:
1resource "aws_lb_listener_certificate" "additional" {
2 listener_arn = aws_lb_listener.https.arn
3 certificate_arn = aws_acm_certificate.other_domain.arn
4}ALB 會根據 SNI(Server Name Indication)自動選擇匹配的憑證。
穩定的 DNS 別名記錄
ALB 重建後 DNS 名稱會改變,對外服務不應該直接用 ALB 的 DNS 名稱。用 Route 53 的 alias record 把穩定的網域名指向 ALB:
1resource "aws_route53_record" "app" {
2 zone_id = aws_route53_zone.public.zone_id
3 name = "api.example.com"
4 type = "A"
5
6 alias {
7 name = aws_lb.main.dns_name
8 zone_id = aws_lb.main.zone_id
9 evaluate_target_health = true
10 }
11}alias record 不收費(一般的 A/CNAME 記錄每百萬次查詢 $0.40,alias 到 AWS 資源免費),且支援 zone apex(如 example.com,一般 CNAME 不支援 zone apex)。evaluate_target_health = true 讓 Route 53 在 ALB 不健康時停止回應該記錄,配合 failover routing 使用。
憑證續期監控
ACM 的 DNS 驗證憑證會自動續期——條件是驗證用的 CNAME 記錄仍然存在且可解析。只要那條記錄沒被刪掉,憑證到期前 60 天 ACM 會自動續期。
自動續期失敗的常見原因:驗證 CNAME 記錄被手動刪除、hosted zone 的 NS delegation 失效、或 zone 本身被刪除重建導致 NS 改變。用 CloudWatch alarm 監控憑證到期日,在自動續期失敗時提前收到通知:
1resource "aws_cloudwatch_metric_alarm" "cert_expiry" {
2 alarm_name = "acm-cert-expiry-${aws_acm_certificate.main.domain_name}"
3 comparison_operator = "LessThanThreshold"
4 evaluation_periods = 1
5 metric_name = "DaysToExpiry"
6 namespace = "AWS/CertificateManager"
7 period = 86400
8 statistic = "Minimum"
9 threshold = 30
10 alarm_actions = [aws_sns_topic.oncall.arn]
11
12 dimensions = {
13 CertificateArn = aws_acm_certificate.main.arn
14 }
15}這個 alarm 在憑證距離到期不足 30 天時觸發。正常情況下 ACM 在到期前 60 天就會完成續期,收到 30 天警報代表自動續期失敗了、需要人工介入確認驗證記錄。
跨分類引用
- → 入口上 IaC — ALB:ALB listener、target group、健康檢查的完整設定
- → 模組三:網路地基:ALB 所在的 public subnet 與 security group 設計
- → 模組七:infra 走 PR 流程:憑證與 DNS 變更走 PR review