Terraform使用列表来创建资源
Table of Contents
前
在使用 Terraform 在中级的水平之后, 动态管理状态这点将会成为一个很重要以及很常用的特性。比如创建类似的资源的时候,就不在需要one by one 的创建对应的 resource 在配置文件中。
使用一个列表 或者 一个map 来对资源来进行管理,只需要在配置文件中体验出差分的配置内容即可。
在这篇文章根据几个实际过程中部署使用的例子来记录下如何使用动态配置。
通过map来批量批量创建主机
这段落如何使用 Terraform 定义一个 Map,利用这个 Map 来批量创建具有相同基础属性但个别属性不同的主机资源。通过这种方式,可以配置各个主机的系统盘、操作系统类型、可用区等属性。更重要的是,通过修改 Map 的内容即可动态增减主机数量,而无需修改 Terraform 配置文件本身,从而实现更高效的批量资源管理。
主机模板以及变量定义
此部分记录了一些预定义的配置和初始资源模板的设置。预定义的内容包括子网的选择和动态分配策略,例如可以将一批主机均匀或随机地分配到两个不同的可用区,以实现跨可用区的部署。
资源模板则定义了所需资源的全部属性,包括标签、系统盘容量、数据盘容量、IAM(身份与访问管理)属性和子网选择等配置。通过该模板,可以标准化资源的配置并确保在创建时符合预设的资源规范。
子网分配
首先,介绍如何实现主机在可用区中的均匀分布配置。由于使用 Map 来定义主机的属性,我们可以利用 index 参数来获取每个主机的索引值。因为有两个可用区,可以通过将索引值除以 2 来获取可用区列表的索引,从而实现主机在两个可用区中的均匀分布。
实际上,更好的方法是使用哈希函数等稳定分配的方案,但由于没有找到具体配置,这里只能使用 index。需要注意的是,Map 的索引值并不稳定,因此在增加或减少主机后,索引可能会发生变化。不过,由于子网并非频繁修改的属性,并且在 AWS 中直接修改子网可能导致主机的销毁,因此我们使用了 lifecycle 中的 ignore_changes 属性来忽略子网的变化。这样一来,在调整子网或增减主机时,不会导致其他资源被意外终止。
所以最终的配置如下所示,这样的话就可以实现一个简单的子网平均分配的方法
locals {
node_secure_subnets = [aws_subnet.secure_subnet_a.id, aws_subnet.secure_subnet_b.id]
}
resource "aws_instance" "node_sec_template" {
#...
subnet_id = local.node_secure_subnets[index(var.node_sec_instance_config, each.value) % 2]
lifecycle {ignore_changes = [subnet_id]}
}
主机模板
可以看到这里的 for_each 会遍历一个映射(map)中的所有内容,然后根据每个元素的值填充到对应的资源字段中。例如,在下面的代码中,我们可以看到 tags 字段是根据 for_each 的内容动态生成的。
如果某个资源包含了另一个需要依赖的资源,比如 EBS_BLOCK_DEVICE,我们需要在主资源的定义中进行引用。通过这种方式,我们可以基于列表或映射来动态创建多个资源实例。
总结来说,通过 for_each 和列表,我们能够高效地生成动态的基础设施资源,比如动态创建多个主机实例,或者挂载 EBS 卷等。这种方式不仅减少了重复的配置代码,还大大提高了灵活性和可维护性。
resource "aws_instance" "node_sec_template" {
# for_each = { for idx, config in var.node_sec_instance_config : idx => config if length(keys(config)) > 0 }
for_each = local.node_config_map
ami = each.value.ami
instance_type = each.value.instance_type
tags = {
org = "xxx"
project = "aaa"
service = "Node"
module = each.value.tags.Module
Name = each.value.tags.Name
Monitoring = "False"
}
# 系统盘配置
root_block_device {
volume_size = each.value.root_volume_size
volume_type = "gp3"
tags = {
Name = "root-${each.value.tags.Name}"
}
}
# 数据盘配置
dynamic "ebs_block_device" {
for_each = each.value.data_volume_size > 0 ? [1] : [] # 如果 data_volume_size > 0 才创建数据盘
content {
device_name = "/dev/sdb"
volume_size = each.value.data_volume_size
volume_type = "gp3"
delete_on_termination = false
tags = {
Name = "data-${each.value.tags.Name}"
}
}
}
iam_instance_profile = "role-user"
key_name = "web-key"
subnet_id = local.node_secure_subnets[index(var.node_sec_instance_config, each.value) % 2]
vpc_security_group_ids = [aws_security_group.allow_outbound_all.id, aws_security_group.compiler_ssh_sg.id]
lifecycle {ignore_changes = [subnet_id]}
}
代码解析
- 主机信息映射
通过
for_each,我们将var.hosts转换为一个键值对(map)。每个主机的名称作为键,主机的详细信息作为值。这样可以确保资源的名称是唯一的,同时方便资源之间的引用。 - 动态创建 EC2 实例
在
aws_instance配置中,for_each遍历hosts列表,为每一个主机生成一个 EC2 实例,并使用主机对应的ami。 - 动态挂载 EBS 卷
在
aws_ebs_volume_attachment配置中,同样使用for_each遍历hosts列表。这里我们引用了实例的 ID,通过aws_instance.example[each.key].id动态关联 EBS 卷到正确的实例。 - 动态引用资源
如果某个资源需要依赖另一个动态生成的资源,比如这里的 EBS 卷依赖 EC2 实例,我们可以使用
each.key作为索引,精确地引用目标资源。
通过List来绑定目标组
在 Terraform 中,如果我们需要为每个 Target Group 绑定资源,直接为每个 Target Group 手动创建对应的绑定资源会显得非常冗余且繁琐。为了简化这一过程,可以利用列表(list)和 for_each 来实现批量绑定资源,从而避免重复的配置代码。
假设我们有一个包含多个 Target Group 的列表,每个 Target Group 都需要绑定到特定的目标主(Target Host)。以下是一个简化的例子:
在 var.tf 中定义一个列表来存储所有 Target Group 相关的信息,包括需要绑定的 ARN 和目标主的 ID:
locals {
target_groups = [
{
target_group_arn = aws_lb_target_group.mine_alph.arn
instance_id = aws_instance.node_alph[0].id
},
{
target_group_arn = aws_lb_target_group.mine_alph.arn
instance_id = aws_instance.node_alph[1].id
}
]
}
通过 for_each 遍历上面定义的列表,为每个 Target Group 动态创建对应的绑定资源
# 定义目标组和实例的映射列表
# 使用for_each合并创建target_group_attachment
resource "aws_lb_target_group_attachment" "target_list_attach" {
for_each = { for idx, tg_map in local.target_groups : idx => tg_map }
target_group_arn = each.value.target_group_arn
target_id = each.value.instance_id
port = 3333
}
代码解析
target_groups 列表
列表中的每个元素是一个映射,包含 Target Group 的名称(name)、ARN(arn)以及需要绑定的目标主 ID(target_id)。我们可以根据需求扩展这个结构,比如添加端口号或其他配置项。
for_each 遍历列表
在资源 aws_lb_target_group_attachment 中,for_each 会将列表转换成一个映射(map),以 name 作为键,整个元素作为值。这样,每个 Target Group 都会动态生成一个绑定资源实例。
动态引用
在资源配置中,each.value.arn 和 each.value.target_id 分别引用当前遍历元素的 arn 和 target_id。这样就避免了手动为每个 Target Group 写重复的绑定配置。
优势
简化配置 使用列表和 for_each 后,只需要定义一次资源逻辑,所有 Target Group 的绑定操作都可以自动完成。
灵活性高 如果需要新增一个 Target Group,只需要在列表中追加一个元素,而无需手动修改配置代码。
减少冗余 避免了为每个 Target Group 手动创建资源的繁琐工作,提升了代码的可读性和维护性。
后
在使用 TF 管理资源时,临配置重复和维护困难的问题,特别是当需要为一组相似的资源进行批量管理时。使用列表与 for_each 的结合就是最好的方式。这种方法不仅能提升开发效率,还能避免手动操作带来的错误。
IaC的最佳实践:让配置更具可读性、更易维护。也是从这个角度出发。