ALB(Application Load Balancer)描述流量進入系統的第一站。它在 IaC 裡的接線責任是把三個層次釘清楚:listener 決定監聽哪些 port 與協定、target group 決定流量導向哪些運算後端、health check 決定後端是否健康到可以接流量。ALB 本身是 stateless 的 — 重建不會遺失資料,但會換掉它的 DNS 名稱,所以對外服務通常在它前面再掛一層穩定的 DNS 記錄(Route 53 alias 或 CNAME),讓使用者看到的網域不隨 ALB 重建而改變。

ALB 掛在 public subnet、引用專屬的 security group,security group 的入站通常只開 80 和 443 對 0.0.0.0/0(這是少數合理出現全開的位置,因為 ALB 的工作本來就是接收公開流量)。後端運算節點住在 private subnet,它們的 security group 入站只允許來自 ALB security group 的流量 — 這個 group-to-group 引用讓規則跟著成員身分走,不跟著 IP 走(見模組三:網路地基)。

ALB 與 listener 設定

ALB 資源本身描述的是它掛在哪些 subnet、用哪個 security group、是對外(internal = false)還是內部。Listener 則是掛在 ALB 上的監聽端點,每個 listener 綁定一個 port + protocol 的組合。

1resource "aws_lb" "api" {
2  name               = "api-${var.env}"
3  internal           = false
4  load_balancer_type = "application"
5  security_groups    = [aws_security_group.alb.id]
6  subnets            = [for s in aws_subnet.public : s.id]
7}

HTTP 到 HTTPS 的強制跳轉

正式服務通常同時建兩個 listener:port 443 接受 HTTPS 流量並轉發到後端,port 80 接收 HTTP 流量後直接回一個 301 redirect 到 HTTPS — 確保使用者即使用 http:// 開頭訪問也會被導到加密連線。

 1resource "aws_lb_listener" "https" {
 2  load_balancer_arn = aws_lb.api.arn
 3  port              = 443
 4  protocol          = "HTTPS"
 5  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
 6  certificate_arn   = aws_acm_certificate.api.arn
 7
 8  default_action {
 9    type             = "forward"
10    target_group_arn = aws_lb_target_group.api.arn
11  }
12}
13
14resource "aws_lb_listener" "http_redirect" {
15  load_balancer_arn = aws_lb.api.arn
16  port              = 80
17  protocol          = "HTTP"
18
19  default_action {
20    type = "redirect"
21    redirect {
22      port        = "443"
23      protocol    = "HTTPS"
24      status_code = "HTTP_301"
25    }
26  }
27}

ssl_policy 決定 ALB 接受哪些 TLS 版本與密碼套件。選擇以安全與相容性為取捨 — ELBSecurityPolicy-TLS13-1-2-2021-06 只接受 TLS 1.2 和 1.3,能阻擋過時協定的降級攻擊,但會拒絕仍在使用 TLS 1.0/1.1 的極舊用戶端。對面向公眾的 API 或網站,TLS 1.2 以上是合理的底線;如果有明確的舊用戶端需求(例如嵌入式設備),再往下調但要知道代價。

多服務共用 ALB

一個 ALB 可以掛多個 listener rule,用 host header 或 path 把流量分到不同的 target group。這讓多個微服務共用一個 ALB(省成本),而不需要每個服務各開一個:

 1resource "aws_lb_listener_rule" "auth" {
 2  listener_arn = aws_lb_listener.https.arn
 3  priority     = 10
 4
 5  condition {
 6    path_pattern { values = ["/auth/*"] }
 7  }
 8
 9  action {
10    type             = "forward"
11    target_group_arn = aws_lb_target_group.auth.arn
12  }
13}

一個常見的收斂機會:如果每個服務都各自開了一個 ALB,但流量都從同一個入口進來、只是路徑不同,可以收斂成一個 ALB 加 listener rule。每個 ALB 有固定的小時費,少開幾個月費就少幾筆。反過來,當不同服務的安全等級或流量特性差異大到需要獨立的 security group 和 WAF 規則時,分開 ALB 才合理。

target group 與健康檢查

Target group 定義一組接收流量的後端(ECS task、EC2 instance 或 IP),以及判斷這些後端是否健康的檢查邏輯。它是 ALB 和實際運算之間的橋樑。

 1resource "aws_lb_target_group" "api" {
 2  name        = "api-${var.env}-tg"
 3  port        = 8080
 4  protocol    = "HTTP"
 5  vpc_id      = aws_vpc.main.id
 6  target_type = "ip"
 7
 8  health_check {
 9    path                = "/healthz"
10    interval            = 15
11    healthy_threshold   = 2
12    unhealthy_threshold = 3
13    timeout             = 5
14    matcher             = "200"
15  }
16}

健康檢查的閾值設計

健康檢查的路徑與閾值是最常被忽略的判讀點。各參數之間的交互作用決定了兩個時間窗口:新後端多久後開始接流量、壞後端多久後被移出。

healthy_threshold = 2interval = 15 代表一個新啟動的後端要等 30 秒(兩次通過)才開始接流量。unhealthy_threshold = 3 代表連續三次失敗(45 秒)才被移出。閾值太寬鬆會把壞掉的後端留在輪替裡,讓部分使用者持續收到錯誤;太嚴格會在部署瞬間 — 新容器啟動、應用還在初始化 — 就判定不健康,反覆移出移入,使用者看到間歇性失敗。

參數過小的風險過大的風險起點建議
intervalALB 對後端造成額外負擔壞後端被偵測到的延遲增加15-30 秒
healthy_threshold還沒完全就緒就接流量部署後等太久才開始分流2-3 次
unhealthy_threshold暫時性波動導致健康的後端被移出壞後端繼續收流量太久2-3 次
timeout正常但偏慢的回應被誤判為失敗確實掛了卻要等很久才確認5 秒

健康檢查路徑的選擇

path 指向的端點應該能反映應用是否確實能服務請求,而不只是 process 還活著。一個只回 200 的空端點(所謂 liveness check)證明 HTTP server 在跑,但不代表它能連到資料庫、能讀到必要的 config。較合理的做法是讓 /healthz 至少檢查核心依賴的連線(例如 ping 一下 DB),失敗時回 503。代價是健康檢查會跟著核心依賴一起報不健康 — 如果 DB 暫時斷了,所有後端都會被判定不健康,ALB 會回 503 給使用者。這是正確的行為:如果應用確實無法服務請求,把它標成不健康比假裝健康好。

判讀方式:部署後觀察 target group 裡的 healthy / unhealthy 轉換次數。如果每次部署都看到新 target 在 healthy 與 unhealthy 之間跳動,代表初始等待不夠 — 應用的啟動時間超出 healthy_threshold * interval,考慮加大 healthy_threshold 或設定 ECS 的 startPeriod(啟動寬限期)讓健康檢查在應用初始化期間暫停。

TLS 憑證:ACM 簽發、DNS 驗證與自動續期

HTTPS listener 引用的 TLS 憑證也屬於 ALB 的接線。用 ACM(AWS Certificate Manager)簽發的憑證在 IaC 裡完整描述 — 涵蓋網域與 DNS 驗證方式 — 讓「憑證存在、驗證、掛載」整條鏈都進版本控制,而非在 Console 手動上傳一份會過期沒人盯的憑證。

ACM 簽發的憑證使用 DNS 驗證時,ACM 要求在指定的 DNS 記錄上放一段驗證值。Terraform 可以自動建立這段記錄並等待驗證通過:

 1resource "aws_acm_certificate" "api" {
 2  domain_name       = "api.${var.domain}"
 3  validation_method = "DNS"
 4
 5  lifecycle { create_before_destroy = true }
 6}
 7
 8resource "aws_route53_record" "cert_validation" {
 9  for_each = {
10    for dvo in aws_acm_certificate.api.domain_validation_options : dvo.domain_name => dvo
11  }
12  zone_id = data.aws_route53_zone.main.zone_id
13  name    = each.value.resource_record_name
14  type    = each.value.resource_record_type
15  records = [each.value.resource_record_value]
16  ttl     = 60
17}
18
19resource "aws_acm_certificate_validation" "api" {
20  certificate_arn         = aws_acm_certificate.api.arn
21  validation_record_fqdns = [for r in aws_route53_record.cert_validation : r.fqdn]
22}

create_before_destroy 的必要性

create_before_destroy = true 確保憑證更新(例如加 SAN 或續期觸發重建)時先建新的再刪舊的,避免 listener 在交接期間沒有可用憑證。Terraform 預設行為是先刪後建,會造成一個短暫的 HTTPS 中斷窗口 — listener 找不到憑證、所有 HTTPS 連線失敗直到新憑證簽發並驗證完畢。

ACM 簽發的憑證自動續期:只要 DNS 驗證記錄還在(由 Terraform 管理,所以會一直在),ACM 在到期前 60 天自動續期。這是把憑證管理成本降到接近零的做法 — 不需要排程提醒、不需要手動下載上傳。判讀訊號:如果 CloudWatch 出現 DaysToExpiry 降到 30 以下的 alarm,代表自動續期失敗,通常是 DNS 驗證記錄被手動刪了或 Route 53 zone 變了。

多網域憑證(SAN)

一張 ACM 憑證可以涵蓋多個網域(Subject Alternative Names),例如 api.example.comadmin.example.com 共用一張。在 IaC 裡用 subject_alternative_names 列舉:

1resource "aws_acm_certificate" "multi" {
2  domain_name               = "api.${var.domain}"
3  subject_alternative_names = ["admin.${var.domain}", "*.internal.${var.domain}"]
4  validation_method         = "DNS"
5
6  lifecycle { create_before_destroy = true }
7}

共用一張還是分開簽取決於生命週期:如果這幾個網域總是一起上下線、一起變更,共用一張省維護;如果各自獨立演進,分開簽讓變更範圍更小。

DNS zone 管理與 ALB 的銜接

Hosted zone:DNS 記錄的容器

Route 53 的 hosted zone 是一個網域下所有 DNS 記錄的容器。public hosted zone 管理對外可見的網域(如 example.com),private hosted zone 管理只在 VPC 內可解析的內部網域(如 internal.example.com),讓服務之間用 DNS 名稱互連而不靠 IP。

多環境的 DNS 管理常用子網域 delegation:production 用 example.com(主 zone),dev 和 staging 各用 dev.example.comstaging.example.com(子 zone)。子 zone 可以放在不同帳號、由不同團隊管理,主 zone 只需要一組 NS 記錄指向子 zone。這讓環境之間的 DNS 邊界跟帳號邊界對齊。

 1resource "aws_route53_zone" "main" {
 2  name = var.domain
 3}
 4
 5resource "aws_route53_zone" "staging" {
 6  name = "staging.${var.domain}"
 7}
 8
 9resource "aws_route53_record" "staging_ns" {
10  zone_id = aws_route53_zone.main.zone_id
11  name    = "staging.${var.domain}"
12  type    = "NS"
13  ttl     = 300
14  records = aws_route53_zone.staging.name_servers
15}

hosted zone 也是 ACM 憑證 DNS 驗證的依賴 — ACM 簽發憑證時需要在對應的 zone 寫入一條驗證記錄,zone 不存在或不在同帳號就接不上。把 zone 的建立排在 ACM 之前,讓依賴圖自然正確。

ALB 的穩定 DNS 記錄

ALB 重建後 DNS 名稱會改變。穩定對外的方式是在 Route 53 建一條 alias 記錄指向 ALB,使用者連的是 api.example.com,DNS 自動解析到 ALB 目前的位址:

 1resource "aws_route53_record" "api" {
 2  zone_id = data.aws_route53_zone.main.zone_id
 3  name    = "api.${var.domain}"
 4  type    = "A"
 5
 6  alias {
 7    name                   = aws_lb.api.dns_name
 8    zone_id                = aws_lb.api.zone_id
 9    evaluate_target_health = true
10  }
11}

evaluate_target_health = true 讓 Route 53 在 ALB 所有 target 都不健康時把這條記錄標為不健康。如果有多個 region 的 ALB 做了 failover routing,這個設定能讓 DNS 層自動切換到健康的 region — 屬於跨區域容災的地基,在 devops 模組展開。

WAF 與下一步

ALB 支援掛載 AWS WAF(Web Application Firewall),在流量進到應用之前先過一層規則 — 擋已知惡意 IP、防 SQL injection / XSS 的常見模式、限制單一 IP 的請求速率。WAF 的規則也可以寫進 IaC,讓「哪些流量被擋」成為可審查的程式碼而非 Console 上的設定。WAF 的詳細設計屬於安全層的範圍(見 backend 模組七:資安與資料保護),這裡只確認它的掛載點是 ALB。

四類核心服務的 IaC 描述到此完成。下一步是讓這些服務可被觀測——log、metric、alarm 跟資源同生命週期建立,見模組六:可觀測性與 log

跨分類引用