原文:https://www.terraform.io/language/modules/develop/composition
在只有一個根模塊的簡單 Terraform 配置中亿笤,我們創(chuàng)建一組資源并使用 Terraform 的表達式語法來描述這些資源之間的關(guān)系:
resource "aws_vpc" "example" {
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "example" {
vpc_id = aws_vpc.example.id
availability_zone = "us-west-2b"
cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 4, 1)
}
當我們引入模塊時,我們的配置開始變得分層而不是扁平化:每個模塊都包含自己的一組資源栋猖,可能還有自己的子模塊净薛,這可能會創(chuàng)建一個深層、復雜的資源配置樹蒲拉。
但是肃拜,在大多數(shù)情況下,我們強烈建議保持模塊樹扁平化:只有一層子模塊雌团,并使用類似于上述的技術(shù)燃领,使用表達式來描述模塊之間的關(guān)系:
module "network" {
source = "./modules/aws-network"
base_cidr_block = "10.0.0.0/8"
}
module "consul_cluster" {
source = "./modules/aws-consul-cluster"
vpc_id = module.network.vpc_id
subnet_ids = module.network.subnet_ids
}
我們將這種扁平化的模塊使用方式稱為模塊組合,因為它需要多個可組合的構(gòu)建塊模塊并將它們組裝在一起以產(chǎn)生更大的系統(tǒng)锦援。
模塊不是嵌入其依賴項猛蔽,創(chuàng)建和管理自己的副本,而是從根模塊接收其依賴項灵寺,因此可以以不同的方式連接相同的模塊以產(chǎn)生不同的結(jié)果曼库。
依賴倒置
在上面的示例中,我們看到了一個名為 consul_cluster
的模塊替久,它可能描述了在 AWS VPC 網(wǎng)絡(luò)中運行的 HashiCorp Consul
服務(wù)器集群凉泄,因此它需要 VPC 和該 VPC 內(nèi)的子網(wǎng)標識符作為參數(shù)。
另一種設(shè)計是讓 consul_cluster
模塊描述它自己的網(wǎng)絡(luò)資源蚯根。如果我們這樣做后众,那么 Consul 集群將很難與同一網(wǎng)絡(luò)中的其它基礎(chǔ)設(shè)施共存,所以我們希望盡可能保持模塊相對小颅拦,并傳遞它們的依賴項蒂誉。
這種依賴倒置方法還提高了未來重構(gòu)的靈活性,因為 consul_cluster
模塊不知道也不關(guān)心調(diào)用模塊如何獲取這些標識符距帅。 未來的重構(gòu)可能會將網(wǎng)絡(luò)創(chuàng)建分離到自己的配置中右锨,因此我們可以將這些值從數(shù)據(jù)源傳遞到模塊中:
data "aws_vpc" "main" {
tags = {
Environment = "production"
}
}
data "aws_subnet_ids" "main" {
vpc_id = data.aws_vpc.main.id
}
module "consul_cluster" {
source = "./modules/aws-consul-cluster"
vpc_id = data.aws_vpc.main.id
subnet_ids = data.aws_subnet_ids.main.ids
}
有條件的創(chuàng)建對象
在跨多個環(huán)境使用同一個模塊的情況下,通常會看到一些必要的對象已經(jīng)存在于某些環(huán)境中碌秸,但在其他環(huán)境中還需要創(chuàng)建绍移。
例如悄窃,這可能出現(xiàn)在開發(fā)環(huán)境場景中:出于成本原因,某些基礎(chǔ)架構(gòu)可能會在多個開發(fā)環(huán)境中共享蹂窖,而在生產(chǎn)環(huán)境中轧抗,基礎(chǔ)架構(gòu)是唯一的,并由生產(chǎn)配置直接管理瞬测。
我們建議采用依賴倒置的方式:讓模塊通過輸入變量接受它需要的對象作為參數(shù)横媚,而不是嘗試編寫一個檢測其存在并創(chuàng)建它的模塊。
例如月趟,考慮一個 Terraform 模塊基于磁盤映像部署計算實例的情況灯蝴,并且在某些環(huán)境中有一個專用磁盤映像可用,而其他環(huán)境共享一個公共基礎(chǔ)磁盤映像孝宗。與其讓模塊本身處理這兩種情況穷躁,不如為表示磁盤映像的對象聲明一個輸入變量。以 AWS EC2 為例碳褒,我們可以聲明 aws_ami 資源類型和數(shù)據(jù)源模式的公共子類型:
variable "ami" {
type = object({
# 僅使用模塊所需的屬性子集聲明對象折砸。
# Terraform 將允許任何至少具有這些屬性的對象看疗。
id = string
architecture = string
})
}
該模塊的調(diào)用者現(xiàn)在可以自己直接表示這是要內(nèi)聯(lián)創(chuàng)建的 AMI 還是要從其他地方檢索的 AMI:
# 這種情形下我們將自己管理 AMI
resource "aws_ami_copy" "example" {
name = "local-copy-of-ami"
source_ami_id = "ami-abc123"
source_ami_region = "eu-west-1"
}
module "example" {
source = "./modules/example"
ami = aws_ami_copy.example
}
# 或者沙峻,AMI 已經(jīng)在某處存在了
data "aws_ami" "example" {
owner = "9999933333"
tags = {
application = "example-app"
environment = "dev"
}
}
module "example" {
source = "./modules/example"
ami = data.aws_ami.example
}
這與 Terraform 的聲明式風格一致:我們并不構(gòu)建條件分支復雜的模塊,而是直接描述應(yīng)該存在的內(nèi)容以及希望 Terraform 管理的內(nèi)容两芳。
通過遵循這種風格摔寨,我們可以確定在哪些情況下應(yīng)該 AMI 存在,哪些情況下不應(yīng)該存在怖辆。維護配置的人以后可以了解這些配置的意圖是复,而無需檢查云上的狀態(tài)。
在上面的示例中竖螃,要創(chuàng)建或讀取的對象非常簡單淑廊,可以作為單個資源內(nèi)聯(lián)提供,但是在依賴項本身足夠復雜以從中受益的情況下特咆,我們也可以將多個模塊組合在一起季惩,如本頁其他地方所述的一樣。
多云(Multi-cloud)抽象
Terraform 本身不會嘗試抽象不同供應(yīng)商提供的類似服務(wù)腻格,因為我們希望在每個產(chǎn)品中開放全部功能画拾,但在單個接口后面統(tǒng)一多個產(chǎn)品往往需要“最小公分母”方法。
但是菜职,通過 Terraform 模塊的組合青抛,可以通過自己權(quán)衡哪些平臺功能對您很重要來創(chuàng)建自己的輕量級多云抽象。
在多個供應(yīng)商實現(xiàn)相同概念酬核、協(xié)議或開放標準的任何情況下蜜另,都會出現(xiàn)這種抽象的機會适室。例如,域名系統(tǒng)的基本功能在所有供應(yīng)商中都是通用的举瑰,盡管一些供應(yīng)商通過地理定位和智能負載平衡等獨特功能來區(qū)分自己亭病,但您可能會得出結(jié)論,在您的用例中您愿意避開這些功能作為對創(chuàng)建模塊的回報嘶居,這些模塊將多個供應(yīng)商的通用 DNS 概念抽象化:
module "webserver" {
source = "./modules/webserver"
}
locals {
fixed_recordsets = [
{
name = "www"
type = "CNAME"
ttl = 3600
records = [
"webserver01",
"webserver02",
"webserver03",
]
},
]
server_recordsets = [
for i, addr in module.webserver.public_ip_addrs : {
name = format("webserver%02d", i)
type = "A"
records = [addr]
}
]
}
module "dns_records" {
source = "./modules/route53-dns-records"
route53_zone_id = var.route53_zone_id
recordsets = concat(local.fixed_recordsets, local.server_recordsets)
}
在上面的示例中罪帖,我們以“記錄集”的形式創(chuàng)建了一個輕量級的抽象。這個抽象包含描述應(yīng)該可映射到任何 DNS 供應(yīng)商的 DNS 記錄的一般概念的屬性邮屁。
然后整袁,我們將該抽象實例化為一個模塊。在本例中將記錄集部署到 AWS 的 Route53 服務(wù)上佑吝。
如果你想以后切換到不同的 DNS 供應(yīng)商坐昙,只需將 dns_records 模塊中的內(nèi)容替換為新供應(yīng)商的實現(xiàn),從而使記錄集中定義的所有記錄配置保持不變芋忿。
你可以在 Terraform 通過定義代表所涉及概念的對象炸客,然后將這些對象類型用于模塊輸入變量來創(chuàng)建像這樣的輕量級抽象。 在這種情況下戈钢,所有的“DNS 記錄”實現(xiàn)都將聲明以下變量:
variable "recordsets" {
type = list(object({
name = string
type = string
ttl = number
records = list(string)
}))
}
DNS 只是一個簡單的示例痹仙,但仍有更多機會利用供應(yīng)商之間的通用元素。 一個更復雜的例子是部署 Kubernetes 集群殉了,現(xiàn)在有許多不同的供應(yīng)商提供托管的 Kubernetes 集群服務(wù)开仰,甚至還有更多運行 Kubernetes 的方法。
如果所有這些實現(xiàn)中的通用功能足以滿足您的需求薪铜,您可以選擇實現(xiàn)一組不同的模塊來描述特定的 Kubernetes 集群實現(xiàn)众弓,并且都具有將集群的主機名導出為輸出值的共同特征:
output "hostname" {
value = azurerm_kubernetes_cluster.main.fqdn
}
然后,您可以編寫僅期望 Kubernetes 集群主機名作為輸入的其他模塊隔箍,并將它們與您的任何 Kubernetes 集群模塊互換使用:
module "k8s_cluster" {
source = "modules/azurerm-k8s-cluster"
# (Azure-specific configuration arguments)
}
module "monitoring_tools" {
source = "modules/monitoring_tools"
cluster_hostname = module.k8s_cluster.hostname
}
只讀模塊
大多數(shù)模塊都包含 resource
部分谓娃,它描述了要創(chuàng)建和管理的基礎(chǔ)設(shè)施。有時編寫根本不描述任何新基礎(chǔ)設(shè)施蜒滩,而只用來檢索有關(guān)使用data sources
在其他地方創(chuàng)建的基礎(chǔ)設(shè)施信息也是一種常見的方式滨达。
作為模塊的使用約定,我們建議僅在模塊以某種方式提高抽象級別時才用這種用法帮掉。在這種情況下會通過精確封裝的數(shù)據(jù)的檢索方式弦悉。
這種技術(shù)的一個常見用途是當一個系統(tǒng)被分解為幾個子系統(tǒng)配置,但某些基礎(chǔ)設(shè)施在各子子系統(tǒng)之間共享的時候蟆炊。例如一個公共 IP 網(wǎng)絡(luò)稽莉。 在這種情況下,我們可能會編寫一個名為 join-network-aws
的共享模塊涩搓,當部署在 AWS 中時污秆,任何需要共享網(wǎng)絡(luò)信息的配置都可以調(diào)用該模塊:
module "network" {
source = "./modules/join-network-aws"
environment = "production"
}
module "k8s_cluster" {
source = "./modules/aws-k8s-cluster"
subnet_ids = module.network.aws_subnet_ids
}
網(wǎng)絡(luò)模塊本身可以通過多種不同的方式檢索這些數(shù)據(jù):它可以使用 aws_vpc
和 aws_subnet_ids
數(shù)據(jù)源直接查詢 AWS API劈猪,或者它可以使用 consul_keys
從 Consul 集群中讀取保存的信息,或者它可以直接從 使用 terraform_remote_state
管理網(wǎng)絡(luò)的配置狀態(tài)良拼。
這種方法的主要好處是战得,此信息的來源可以隨時間變化,而無需更新依賴它的每個配置庸推。 此外常侦,如果您將純數(shù)據(jù)模塊設(shè)計為具有與相應(yīng)管理模塊相似的一組輸出,則在重構(gòu)時可以相對輕松地在兩者之間進行切換贬媒。
(完)