Terraform
使用变量自定义 Terraform 配置
输入变量通过允许最终用户分配值来自定义配置,从而使 Terraform 配置更加灵活。它们提供了一个一致的接口来改变给定配置的行为方式。
与编程语言中的变量不同,Terraform 的输入变量在 Terraform 运行(例如计划、应用或销毁)期间不会更改值。相反,它们允许用户在执行开始之前为变量分配不同的值,而不是手动编辑配置文件,从而更安全地自定义其基础架构。
在本教程中,您将使用 Terraform 在 AWS 上部署一个 Web 应用程序。支持的基础架构包括 VPC、负载均衡器和 EC2 实例。您将使用 Terraform 输入变量参数化此配置。最后,您将把变量插值到字符串中,将变量与函数一起使用,并使用变量进行验证。

前提条件
您可以使用 Terraform Community Edition 或 HCP Terraform 以相同的流程完成本教程。HCP Terraform 是一个平台,可用于管理和执行您的 Terraform 项目。它包含远程状态和执行、结构化计划输出、工作区资源摘要等功能。
选择 HCP Terraform 标签页以使用 HCP Terraform 完成本教程。
本教程假定您熟悉 Terraform 工作流程。如果您是 Terraform 新手,请先完成 入门系列。
为了完成本教程,您需要以下条件
- 本地安装的 Terraform v1.2+ 。
- 一个配置了本地凭证的AWS 账户,这些凭证已配置为与 Terraform 一起使用。
创建基础架构
克隆本教程的 Learn Terraform variables GitHub 仓库。
$ git clone https://github.com/hashicorp-education/learn-terraform-variables
切换到仓库目录。
$ cd learn-terraform-variables
main.tf 中的配置定义了一个 Web 应用程序,包括 VPC、负载均衡器和 EC2 实例。
初始化此配置。
$ terraform init
Initializing the backend...
##...
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
现在应用配置。在确认提示符中输入 yes 来创建示例基础架构。
$ terraform apply
##...
Plan: 43 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ public_dns_name = (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
##...
Apply complete! Resources: 43 added, 0 changed, 0 destroyed.
Outputs:
public_dns_name = "lb-CVB-project-alpha-dev-1778105170.us-west-2.elb.amazonaws.com"
参数化您的配置
变量声明可以出现在您的配置文件中的任何位置。但是,我们建议将它们放入一个名为 variables.tf 的单独文件中,以方便用户理解他们如何自定义配置。
要使用输入变量参数化参数,您必须先定义变量,然后在配置中用对该变量的引用替换硬编码的值。
在 variables.tf 中添加一个声明名为 aws_region 的变量的块。
variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}
变量块有三个可选参数。
- Description:一个简短的描述,用于记录变量的用途。
- Type:变量中包含的数据类型。
- Default:默认值。
我们建议为所有变量设置描述和类型,并在可行的情况下设置默认值。
如果您没有为变量设置默认值,则必须在 Terraform 应用配置之前为其分配一个值。Terraform 不支持未赋值的变量。您将在本教程后面回顾一些为变量赋值的方法。
变量值必须是字面值,并且不能使用计算值,如资源属性、表达式或其他变量。您可以使用 var.<variable_name> 在配置中引用变量。
编辑 main.tf 中的提供商块,以使用新的 aws_region 变量。
main.tf
provider "aws" {
- region = "us-west-2"
+ region = var.aws_region
}
在 variables.tf 中添加另一个用于 vpc_cidr_block 变量的声明。
variables.tf
variable "vpc_cidr_block" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
现在,在 main.tf 中,将 VPC 的 CIDR 块的硬编码值替换为变量。
main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.7.0"
- cidr = "10.0.0.0/16"
+ cidr = var.vpc_cidr_block
## ...
}
应用更新后的配置。由于这些变量的默认值与它们所替换的硬编码值相同,因此不会发生任何更改。
$ terraform apply
##...
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
设置实例数量
除了 string 之外,Terraform 还支持多种变量类型。
使用 number 类型来定义此配置支持的实例数量。将以下内容添加到 variables.tf。
variables.tf
variable "instance_count" {
description = "Number of instances to provision."
type = number
default = 2
}
在 main.tf 中更新 EC2 实例以使用 instance_count 变量。
main.tf
module "ec2_instances" {
source = "./modules/aws-instance"
depends_on = [module.vpc]
- instance_count = 2
+ instance_count = var.instance_count
## ...
}
当 Terraform 解析值(无论是硬编码的还是来自变量的)时,如果可能,它会将其转换为正确的类型。因此,instance_count 变量使用字符串("2")而不是数字(2)也可以正常工作。我们建议在变量定义中使用最合适的类型,以帮助您的配置用户了解应使用的数据类型,并及早捕获配置错误。
切换 VPN 网关支持
除了字符串和数字之外,Terraform 还支持几种 变量类型。类型为 bool 的变量表示 true/false 值。
使用 bool 类型变量来控制是否为 VPC 配置 VPN 网关。将以下内容添加到 variables.tf。
variables.tf
variable "enable_vpn_gateway" {
description = "Enable a VPN gateway in your VPC."
type = bool
default = false
}
通过编辑 main.tf,在 VPC 配置中使用此新变量,如下所示。
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.7.0"
## ...
enable_nat_gateway = true
- enable_vpn_gateway = false
+ enable_vpn_gateway = var.enable_vpn_gateway
## ...
}
将 enable_nat_gateway 的值保留为硬编码。在任何配置中,都可能存在一些您希望用户通过变量进行配置的值,而另一些您希望硬编码的值。
在开发 Terraform 模块时,您通常会使用变量来使模块的属性可配置,从而使模块更灵活。
相比之下,在为特定项目编写 Terraform 配置时,如果您不想让用户配置某些属性,可以选择硬编码这些属性。
列出公共和私有子网
到目前为止您使用的变量都是单个值。Terraform 将这些类型的变量称为简单类型。Terraform 还支持集合变量类型,其中包含多个值。Terraform 支持多种集合变量类型。
- List:同一类型值的序列。
- Map:一个查找表,将键与值匹配,所有值类型相同。
- Set:同一类型值的无序唯一值集合。
在本教程中,您将使用列表和映射,它们是最常用的类型。当需要唯一值集合且集合中项目的顺序无关紧要时,Set 非常有用。
使用列表变量的一个可能场景是为 VPC 设置 private_subnets 和 public_subnets 参数。使用列表和 slice() 函数可以使此配置更易于使用和自定义。
将以下变量声明添加到 variables.tf。
variables.tf
variable "public_subnet_count" {
description = "Number of public subnets."
type = number
default = 2
}
variable "private_subnet_count" {
description = "Number of private subnets."
type = number
default = 2
}
variable "public_subnet_cidr_blocks" {
description = "Available cidr blocks for public subnets."
type = list(string)
default = [
"10.0.1.0/24",
"10.0.2.0/24",
"10.0.3.0/24",
"10.0.4.0/24",
"10.0.5.0/24",
"10.0.6.0/24",
"10.0.7.0/24",
"10.0.8.0/24",
]
}
variable "private_subnet_cidr_blocks" {
description = "Available cidr blocks for private subnets."
type = list(string)
default = [
"10.0.101.0/24",
"10.0.102.0/24",
"10.0.103.0/24",
"10.0.104.0/24",
"10.0.105.0/24",
"10.0.106.0/24",
"10.0.107.0/24",
"10.0.108.0/24",
]
}
请注意,列表变量的类型为 list(string)。这些列表中的每个元素都必须是字符串。列表元素必须全部是同一类型,但可以是任何类型,包括像 list(list) 和 list(map) 这样的复杂类型。
与大多数编程语言中的列表和数组一样,您可以通过索引(从 0 开始)按索引引用列表中的单个项。Terraform 还包含几个允许您操作列表和其他变量类型的函数。
使用 slice() 函数获取这些列表的子集。
Terraform console 命令会打开一个交互式控制台,您可以在其中评估配置上下文中的表达式。在处理和排查变量定义时,这会非常有用。
使用 terraform console 命令打开控制台。
$ terraform console
>
现在使用 Terraform 控制台检查私有子网块的列表。
通过名称引用变量以返回整个列表。
> var.private_subnet_cidr_blocks
tolist([
"10.0.101.0/24",
"10.0.102.0/24",
"10.0.103.0/24",
"10.0.104.0/24",
"10.0.105.0/24",
"10.0.106.0/24",
"10.0.107.0/24",
"10.0.108.0/24",
])
使用方括号按索引检索列表中的第二个元素。
> var.private_subnet_cidr_blocks[1]
"10.0.102.0/24"
现在使用 slice() 函数从列表中返回前三个元素。
> slice(var.private_subnet_cidr_blocks, 0, 3)
tolist([
"10.0.101.0/24",
"10.0.102.0/24",
"10.0.103.0/24",
])
slice() 函数接受三个参数:要切片的列表、起始索引和结束索引(不包含)。它会返回一个新列表,其中包含从原始列表中复制(“切片”)的指定元素。
通过输入 exit 或按 Control-D 退出控制台。
> exit
现在,通过编辑 main.tf 中的 VPC 模块配置,使用 slice 函数为您的公共和私有子网配置提取 CIDR 块列表的子集。
main.tf
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.7.0"
cidr = var.vpc_cidr_block
azs = data.aws_availability_zones.available.names
- private_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
- public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
+ private_subnets = slice(var.private_subnet_cidr_blocks, 0, var.private_subnet_count)
+ public_subnets = slice(var.public_subnet_cidr_blocks, 0, var.public_subnet_count)
## ...
}
这样,此配置的用户就可以指定他们想要的公共和私有子网数量,而无需担心定义 CIDR 块。
映射资源标签
在 main.tf 中声明的每个资源和模块都包含两个标签:project_name 和 environment。使用 map 变量类型分配这些标签。
在 variables.tf 中声明一个用于资源标签的新 map 变量。
variables.tf
variable "resource_tags" {
description = "Tags to set for all resources"
type = map(string)
default = {
project = "project-alpha",
environment = "dev"
}
}
将类型设置为 map(string) 告诉 Terraform 期望映射中的值为字符串。映射键始终是字符串。与编程语言中的字典或映射一样,您可以使用相应的键从映射中检索值。通过 Terraform 控制台查看此工作原理。
启动控制台。
$ terraform console
>
从 resource_tags 映射中检索 environment 键的值。
> var.resource_tags["environment"]
"dev"
通过输入 exit 或按 Control-D 退出控制台。
> exit
现在,在 main.tf 中,用对新变量的引用替换硬编码的标签。
main.tf
- tags = {
- project = "project-alpha",
- environment = "dev"
- }
+ tags = var.resource_tags
## ... replace all five occurrences of `tags = {...}`
请确保替换配置中所有五个硬编码标签的引用。
应用这些更改。同样,由于变量的默认值与原始硬编码值匹配,因此不会有任何更改。
$ terraform apply
您使用的列表和映射是集合类型。Terraform 还支持两种结构类型。结构类型具有固定数量的值,这些值可以是不同的类型。
- Tuple:指定类型的固定长度值序列。
- Object:一个查找表,将固定数量的键与指定类型的值匹配。
为变量赋值
Terraform 要求为每个变量提供一个值。有几种方法可以分配变量值。
使用命令行标志
在前面的示例中,所有变量定义都包含默认值。向 variables.tf 添加一个没有默认值的新变量。
variables.tf
variable "ec2_instance_type" {
description = "AWS EC2 instance type."
type = string
}
替换 main.tf 中对 EC2 实例类型的引用。
main.tf
module "ec2_instances" {
source = "./modules/aws-instance"
instance_count = var.instance_count
- instance_type = "t2.micro"
+ instance_type = var.ec2_instance_type
## ...
}
现在应用此配置,使用 -var 命令行标志来设置变量值。由于您输入的值与旧值相同,因此不会应用任何更改。
$ terraform apply -var ec2_instance_type=t2.micro
## ...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
public_dns_name = "lb-3Bn-project-alpha-dev-542453763.us-west-2.elb.amazonaws.com"
使用 Terraform Community Edition 时,如果您未在配置中设置变量,或未向 Terraform 命令传递变量,Terraform 会提示您输入值。
使用文件分配值
手动输入变量值既耗时又容易出错。相反,您可以将变量值捕获到文件中。
创建名为 terraform.tfvars 的文件,其中包含以下内容。
terraform.tfvars
resource_tags = {
project = "project-alpha",
environment = "dev",
owner = "me@example.com"
}
ec2_instance_type = "t3.micro"
instance_count = 3
Terraform 会自动加载当前目录中名称完全为 terraform.tfvars 或匹配 *.auto.tfvars 的所有文件。您还可以使用 -var-file 标志按名称指定其他文件。
这些文件使用类似于 Terraform 配置文件 (HCL) 的语法,但它们不能包含诸如资源定义之类的配置。与 Terraform 配置文件一样,这些文件也可以包含 JSON。
使用这些新值应用配置。
$ terraform apply
##...
Plan: 6 to add, 19 to change, 4 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
##...
在确认提示符中输入 yes 以应用这些更改。
除了命令行标志和变量文件之外,您还可以使用环境变量来设置输入变量。请查看 文档了解更多详情。如果通过这些方法为变量分配了不同的值,Terraform 将使用它找到的最后一个值,按优先级顺序。
在字符串中插值变量
Terraform 配置支持字符串插值 — 将表达式的输出插入字符串。这允许您使用变量、本地值和函数输出来创建配置中的字符串。
更新安全组的名称,以使用 resource_tags 映射中的 project 和 environment 值。
main.tf
module "app_security_group" {
source = "terraform-aws-modules/security-group/aws//modules/web"
version = "4.17.0"
- name = "web-sg-project-alpha-dev"
+ name = "web-sg-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
## ...
}
module "lb_security_group" {
source = "terraform-aws-modules/security-group/aws//modules/web"
version = "4.17.0"
- name = "lb-sg-project-alpha-dev"
+ name = "lb-sg-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
## ...
}
module "elb_http" {
source = "terraform-aws-modules/elb/aws"
version = "4.0.1"
# Ensure load balancer name is unique
- name = "lb-${random_string.lb_id.result}-project-alpha-dev"
+ name = "lb-${random_string.lb_id.result}-${var.resource_tags["project"]}-${var.resource_tags["environment"]}"
## ...
}
应用更新后的配置。由于插值字符串的值与硬编码值相同,因此不会有任何更改。
$ terraform apply
##...
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and
found no differences, so no changes are needed.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
public_dns_name = "lb-CVB-project-alpha-dev-1778105170.us-west-2.elb.amazonaws.com"
验证变量
此配置存在潜在问题。AWS 负载均衡器有命名限制。负载均衡器名称最长不得超过 32 个字符,并且只能包含一组有限的字符。
现在,使用变量验证来限制 project 和 environment 标签的可能值。
用以下代码片段替换 variables.tf 中现有的 resource tags 变量,其中包含验证块,用于强制对 project 和 environment 值进行字符限制和字符集检查。
variables.tf
variable "resource_tags" {
description = "Tags to set for all resources"
type = map(string)
default = {
project = "my-project",
environment = "dev"
}
validation {
condition = length(var.resource_tags["project"]) <= 16 && length(regexall("[^a-zA-Z0-9-]", var.resource_tags["project"])) == 0
error_message = "The project tag must be no more than 16 characters, and only contain letters, numbers, and hyphens."
}
validation {
condition = length(var.resource_tags["environment"]) <= 8 && length(regexall("[^a-zA-Z0-9-]", var.resource_tags["environment"])) == 0
error_message = "The environment tag must be no more than 8 characters, and only contain letters, numbers, and hyphens."
}
}
regexall() 函数接受一个正则表达式和一个要测试的字符串,并返回字符串中找到的匹配项列表。在这种情况下,正则表达式将匹配一个包含字母、数字或连字符以外任何内容的字符串。
这确保了负载均衡器名称的长度不超过 32 个字符,或不包含无效字符。使用变量验证可以很好地及早发现配置错误。
应用此更改以添加对这两个变量的验证。由于基础架构配置没有更改,因此不会应用任何更改。
$ terraform apply
##...
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no
differences, so no changes are needed.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
public_dns_name = "lb-CVB-project-alpha-dev-1778105170.us-west-2.elb.amazonaws.com"
现在通过指定过长的环境标签来测试验证规则。请注意,命令将失败并返回验证块中指定的错误消息。
$ terraform apply -var='resource_tags={project="my-project",environment="development"}'
random_string.lb_id: Refreshing state... [id=CVB]
data.aws_availability_zones.available: Reading...
data.aws_availability_zones.available: Read complete after 1s [id=us-west-2]
Planning failed. Terraform encountered an error while generating this plan.
╷
│ Error: Invalid value for variable
│
│ on variables.tf line 72:
│ 72: variable "resource_tags" {
│ ├────────────────
│ │ var.resource_tags["environment"] is "development"
│
│ The environment tag must be no more than 8 characters, and only contain
│ letters, numbers, and hyphens.
│
│ This was checked by the validation rule at variables.tf:85,3-13.
╵
清理基础架构
在继续之前,请运行 terraform destroy 命令来销毁您创建的基础架构。请记住使用 yes 确认操作。
$ terraform destroy
## ...
Plan: 0 to add, 0 to change, 45 to destroy.
Changes to Outputs:
- public_dns_name = "lb-CVB-project-alpha-dev-1778105170.us-west-2.elb.amazonaws.com" -> null
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
## ...
Apply complete! Resources: 0 added, 0 changed, 45 destroyed.
如果您在本教程中使用了 HCP Terraform,在销毁资源后,请从您的 HCP Terraform 组织中删除 workspace-name 工作区。
下一步
在本教程中,您定义并使用了 Terraform 变量。向配置添加变量可以使其在整个生命周期中更易于修改。变量还可以使配置更容易重用给其他项目,或将其转换为模块。它还可以使您的配置更通用,让您将项目特定的配置保存在变量文件中。
请阅读以下资源,以了解有关变量的更多信息以及如何使您的 Terraform 配置更加灵活。
- 阅读 输入变量文档。
- 阅读 本地变量文档。
- 了解如何创建和使用 Terraform 模块。
- 了解如何 使用自定义条件验证模块。
- 阅读有关结构类型的文档.
- 了解如何在 HCP Terraform 中 管理变量集。