Terraform
使用变量自定义 Terraform 配置
通过定义值,输入变量使您的 Terraform 配置更具灵活性,最终用户可以分配这些值来定制配置。它们提供了一个一致的接口来更改给定配置的行为。
与编程语言中的变量不同,Terraform 的输入变量在 Terraform 运行期间(如 plan、apply 或 destroy)不会改变值。相反,它们允许用户在执行开始之前为变量分配不同的值,而不是手动编辑配置文件,从而更安全地自定义其基础设施。
在本教程中,您将使用 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 中的 provider 块以使用新的 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
}
更新 EC2 实例以在 main.tf 中使用 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:无序的唯一值集合,所有值都具有相同类型。
在本教程中,您将使用列表和映射,这是这些类型中最常用的。当需要唯一的值集合且集合中项目的顺序无关紧要时,集合非常有用。
使用列表变量的一个可能地方是设置 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 不会提出任何更改,因为您的变量的默认值与原始硬编码值匹配。
$ 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 映射的项目和环境值。
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 个字符,并且只能包含有限的字符集。
现在,使用变量验证来限制项目和环境标签的可能值。
在 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 中管理变量集。