大家好,在云原生和自動化運維的世界里,Terraform無疑是基礎設施即代碼(IaC)領域的王者。它強大的聲明式語法讓我們能夠輕松地描述和管理云資源。然而,即使是經驗豐富的工程師,在面對某些動態場景時也可能會遇到挑戰。
今天,我們就來深入探討一個非常經典且實用的場景:如何使用 for_each
表達式,優雅地自動化處理 AWS ACM (Certificate Manager) 證書的 DNS 驗證,特別是當一個證書包含多個域名時。
問題的提出:多域名證書的DNS驗證之痛
在現代Web應用中,一個服務擁有多個訪問域名是非常普遍的。例如,主域名 test.core.org
,以及一系列子域名如 admin-test.core.org
、agent-test.core.org
等。為了啟用HTTPS,我們需要為所有這些域名申請一個SSL/TLS證書。AWS ACM提供了一個便捷的方式,就是將這些域名都作為“主題備用名稱”(Subject Alternative Names, SANs)添加到一個證書中。
讓我們來看一下申請這樣一個證書的Terraform代碼:
resource "aws_acm_certificate" "cloudfront" {provider = aws.cloudfrontdomain_name = "test.core.org"validation_method = "DNS"subject_alternative_names = ["admin-test.core.org","agent-test.core.org","mmts-test.core.org","static-test.core.org"]tags = {Environment = "test"}lifecycle {create_before_destroy = true}
}
代碼很直觀,我們定義了一個主域名和四個備用域名。我們選擇了 DNS
作為驗證方法,這意味著AWS會為 每一個 域名(包括主域名和所有SANs)生成一個唯一的CNAME記錄,我們需要將這些記錄發布到我們的DNS服務商(比如AWS Route 53)的DNS區域(Zone)中,以證明我們對這些域名的所有權。
問題來了:AWS為5個域名生成了5組不同的驗證信息。我們該如何為這5個域名動態創建對應的DNS記錄呢?手動創建?當然不行,這違背了IaC的初衷。寫5個 aws_route53_record
資源塊?代碼冗余且難以維護。
這時,for_each
就該閃亮登場了。
解決方案:for_each
的魔法
for_each
是 Terraform 0.12.6 版本引入的重大特性,它允許我們基于一個 map 或 set of strings 來創建多個相似資源的實例。這正是解決我們問題的完美工具。
當我們創建 aws_acm_certificate
資源后,Terraform會通過其 domain_validation_options
屬性導出一組對象,其中包含了每個域名驗證所需的所有信息。 這是一個列表(list),每個對象包含 domain_name
、resource_record_name
、resource_record_type
和 resource_record_value
等關鍵信息。
我們的目標就是遍歷這個列表,為每個元素創建一個 aws_route53_record
資源。
下面就是使用了 for_each
的解決方案代碼,也是本次講解的核心:
data "aws_route53_zone" "selected" {name = "core.org."private_zone = false
}resource "aws_route53_record" "cloudfront_validation" {# for_each 的魔法在這里!for_each = {for dvo in aws_acm_certificate.cloudfront.domain_validation_options : dvo.domain_name => dvo}zone_id = data.aws_route53_zone.selected.zone_idname = each.value.resource_record_nametype = each.value.resource_record_typerecords = [each.value.resource_record_value]ttl = 60
}
讓我們來逐行解析這段代碼的精髓。
深入解析 for_each
表達式
for_each
的核心在于它接受一個 map 類型的值。然而,aws_acm_certificate.cloudfront.domain_validation_options
是一個 list of objects。因此,我們需要使用Terraform的 for
表達式將其轉換成一個 map。
{for dvo in aws_acm_certificate.cloudfront.domain_validation_options : dvo.domain_name => dvo
}
這行代碼做了什么?
for dvo in ...
: 這是一個循環,遍歷domain_validation_options
列表中的每一個對象,并將當前對象賦值給臨時變量dvo
。... : dvo.domain_name => dvo
: 這是for
表達式生成map的關鍵部分。它遵循key => value
的語法。dvo.domain_name
作為 map 的 key。我們使用每個待驗證的域名(例如 “test.core.org”)作為鍵。這非常重要,因為for_each
要求的 map key 必須是唯一的字符串,而域名正好滿足這個條件。dvo
作為 map 的 value。我們將完整的驗證信息對象dvo
作為值。
經過這個轉換,我們就得到了一個類似下面這樣的map(值為示意):
{"test.core.org" = { domain_name = "test.core.org", resource_record_name = "_c123.test.core.org", ... },"admin-test.core.org" = { domain_name = "admin-test.core.org", resource_record_name = "_c456.admin-test.core.org", ... },...
}
使用 each
對象引用值
一旦 for_each
接收到這個map,Terraform就會為map中的每一個鍵值對創建一個 aws_route53_record
資源的實例。在資源塊內部,我們可以通過特殊的 each
對象來訪問當前實例的 key 和 value。
each.key
: 在我們的例子中,它會是 “test.core.org”, “admin-test.core.org” 等域名字符串。each.value
: 它會是我們賦給 map value 的dvo
對象。
因此,代碼中的這幾行就變得非常好理解了:
name = each.value.resource_record_name
: 設置DNS記錄的名稱,例如_c123.test.core.org
。type = each.value.resource_record_type
: 設置DNS記錄的類型,通常是CNAME
。records = [each.value.resource_record_value]
: 設置DNS記錄的值,這是AWS提供的用于驗證的唯一字符串。
通過這種方式,我們僅用一個資源塊就動態地為所有需要驗證的域名創建了對應的DNS記錄,代碼簡潔、可讀性高,且易于擴展。如果未來證書增加了新的SAN,我們只需要修改 aws_acm_certificate
資源中的 subject_alternative_names
列表,apply
時 Terraform 會自動為新域名創建驗證記錄。
實用建議與注意事項
- 依賴關系:Terraform足夠智能,它能理解
aws_route53_record.cloudfront_validation
依賴于aws_acm_certificate.cloudfront
。它會先創建證書請求,獲取domain_validation_options
,然后再創建DNS記錄。 - 等待驗證完成:請注意,上面的代碼只負責創建DNS記錄。證書的狀態此時仍然是
PENDING_VALIDATION
。在實際生產中,你還需要使用aws_acm_certificate_validation
資源來明確告訴Terraform,需要等待DNS記錄生效且證書驗證通過后,才能繼續創建依賴此證書的其他資源(如Load Balancer Listener或CloudFront Distribution)。 - key 的選擇:將
for
表達式轉換為map時,選擇一個穩定且唯一的字符串作為key至關重要。dvo.domain_name
是一個絕佳的選擇。
結論
Terraform 的 for_each
循環遠不止是創建多個資源的語法糖。它是一種處理動態、集合類基礎設施的強大范式。通過今天對AWS ACM證書DNS驗證案例的剖析,我們可以看到:
- 提升了代碼的簡潔性:避免了大量重復的資源定義。
- 增強了代碼的可維護性:當域名列表變化時,無需修改DNS記錄部分的代碼。
- 體現了IaC的核心思想:將基礎設施的邏輯關系清晰地表達出來,實現了真正的自動化。
希望這篇文章能幫助大家更深入地理解 for_each
的強大之處,并將其應用到我們的日常工作中,解決更多自動化運維的難題。