1.4.3.1. 输入变量
在前面的例子中,我们在代码中都是使用字面量硬编码的,如果我们想要在创建、修改基础设施时动态传入一些值呢?比如说在代码中定义 Provider 时用变量替代硬编码的访问密钥,或是由创建基础设施的用户来决定创建什么样尺寸的主机?我们需要的是输入变量。
如果我们把一组 Terraform 代码想像成一个函数,那么输入变量就是函数的入参。输入变量用 variable
块进行定义:
variable "image_id" {
type = string
}
variable "availability_zone_names" {
type = list(string)
default = ["us-west-1a"]
}
variable "docker_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 8300
external = 8300
protocol = "tcp"
}
]
}
这些都是合法的输入参数定义。紧跟 variable
关键字的就是变量名。在一个 Terraform 模块(同一个文件夹中的所有 Terraform 代码文件,不包含子文件夹)中变量名必须是唯一的。我们在代码中可以通过var.<NAME>
的方式引用变量的值。有一组关键字不可以被用作输入变量的名字:
source
version
providers
count
for_each
lifecycle
depends_on
locals
输入变量只能在声明该变量的目录下的代码中使用。
输入变量块中可以定义一些属性。
1.4.3.1.1. 类型 (type)
可以在输入变量块中通过 type
定义类型,例如:
variable "name" {
type = string
}
variable "ports" {
type = list(number)
}
定义了类型的输入变量只能被赋予符合类型约束的值。
1.4.3.1.2. 默认值 (default)
默认值定义了当 Terraform 无法获得一个输入变量得到值的时候会使用的默认值。例如:
variable "name" {
type = string
default = "John Doe"
}
当 Terraform 无法通过其他途径获得name的值时,var.name
的值为 "John Doe"
。
1.4.3.1.3. 描述 (description)
可以在输入变量中定义一个描述,简单地向调用者描述该变量的意义和用法:
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
}
如果在执行 terraform plan
或是 terraform apply
时 Terraform 不知道某个输入变量的值,Terraform 会在命令行界面上提示我们为输入变量设置一个值。例如上面的输入变量代码,执行 terraform apply
时:
$ terraform apply
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value:
为了使得代码的使用者能够准确理解输入变量的意义和用法,我们应该站在代码使用者而非代码维护者的角度编写输入变量的描述。描述并不是注释!
1.4.3.1.4. 断言 (validation)
输入变量的断言是 Terraform 0.13.0 开始引入的新功能,在过去,Terraform 只能用类型约束确保输入参数的类型是正确的,曾经有不少人试图通过奇技淫巧来实现更加复杂的变量校验断言。如今 Terraform 终于正式添加了相关的功能。
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
condition
参数是一个 bool
类型的参数,我们可以用一个表达式来定义如何界定输入变量是合法的。当 condition
为 true
时输入变量合法,反之不合法。condition
表达式中只能通过 var.\<NAME\>
引用当前定义的变量,并且它的计算不能产生错误。
假如表达式的计算产生一个错误是输入变量验证的一种判定手段,那么可以使用 can
函数来判定表达式的执行是否抛错。例如:
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
# regex(...) fails if it cannot find a match
condition = can(regex("^ami-", var.image_id))
error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
}
}
上述例子中,如果输入的 image_id
不符合正则表达式的要求,那么 regex
函数调用会抛出一个错误,这个错误会被 can
函数捕获,输出 false
。
condition
表达式如果为 false
,Terraform 会返回 error_message
中定义的错误信息。error_message
应该完整描述输入变量校验失败的原因,以及输入变量的合法约束条件。
1.4.3.1.5. 在命令行输出中隐藏值 (sensitive)
该功能于 Terraform v0.14.0 开始引入。
将变量设置为 sensitive
可以防止我们在配置文件中使用变量时 Terraform 在 plan
和 apply
命令的输出中展示与变量相关的值。
Terraform 仍然会将敏感数据记录在状态文件中,任何可以访问状态文件的人都可以读取到明文的敏感数据值。
声明一个变量包含敏感数据值需要将 sensitive
参数设置为 true
:
variable "user_information" {
type = object({
name = string
address = string
})
sensitive = true
}
resource "some_resource" "a" {
name = var.user_information.name
address = var.user_information.address
}
任何使用了敏感变量的表达式都将被视为敏感的,因此在上面的示例中,resource “some_resource” “a”
的两个参数也将在计划输出中被隐藏:
Terraform will perform the following actions:
# some_resource.a will be created
+ resource "some_resource" "a" {
+ name = (sensitive)
+ address = (sensitive)
}
Plan: 1 to add, 0 to change, 0 to destroy.
在某些情况下,我们会在嵌套块中使用敏感变量,Terraform 可能会将整个块视为敏感的。这发生在那些包含有要求值是唯一的内嵌块的资源中,公开这种内嵌块的部分内容可能会暗示兄弟块的内容。
# some_resource.a will be updated in-place
~ resource "some_resource" "a" {
~ nested_block {
# At least one attribute in this block is (or was) sensitive,
# so its contents will not be displayed.
}
}
Provider 还可以将资源属性声明为敏感属性,这将导致 Terraform 将其从常规输出中隐藏。
如果打算使用敏感值作为输出值的一部分,Terraform 将要求您将输出值本身标记为敏感值,以确认确实打算将其导出。
1.4.3.1.5.1. Terraform 可能暴露敏感变量的情况
sensitive
变量是一个以配置文件为中心的概念,值被不加混淆地发送给 Provider。如果该值被包含在错误消息中,则 Provider 报错时可能会暴露该值。例如,即使 "foo"
是敏感值,Provider 也可能返回以下错误:"Invalid value 'foo' for field"
如果将资源属性用作、或是作为 Provider 定义的资源 ID 的一部分,则 apply
将公开该值。在下面的示例中,前缀属性已设置为 sensitive
变量,但随后该值("jae"
)作为资源 ID 的一部分公开:
# random_pet.animal will be created
+ resource "random_pet" "animal" {
+ id = (known after apply)
+ length = 2
+ prefix = (sensitive)
+ separator = "-"
}
Plan: 1 to add, 0 to change, 0 to destroy.
...
random_pet.animal: Creating...
random_pet.animal: Creation complete after 0s [id=jae-known-mongoose]
1.4.3.1.6. 禁止输入变量为空 (nullable)
该功能自 Terraform v1.1.0 开始被引入
输入变量的 nullable
参数控制模块调用者是否可以将 null
赋值给变量。
variable "example" {
type = string
nullable = false
}
nullable
的默认值为 true
。当 nullable
为 true
时,null
是变量的有效值,并且模块代码必须始终考虑变量值为 null
的可能性。将 null
作为模块输入参数传递将覆盖输入变量上定义的默认值。
将 nullable
设置为 false
可确保变量值在模块内永远不会为空。如果 nullable
为 false
并且输入变量定义有默认值,则当模块输入参数为 null
时,Terraform 将使用默认值。
nullable
参数仅控制变量的直接值可能为 null
的情况。对于集合或对象类型的变量,例如列表或对象,调用者仍然可以在集合元素或属性中使用 null
,只要集合或对象本身不为 null
。
1.4.3.1.7. 对输入变量赋值
1.4.3.1.7.1. 命令行参数
对输入变量赋值有几种途径,一种是在调用 terraform plan
或是 terraform apply
命令时以参数的形式传入:
$ terraform apply -var="image_id=ami-abc123"
$ terraform apply -var='image_id_list=["ami-abc123","ami-def456"]'
$ terraform plan -var='image_id_map={"us-east-1":"ami-abc123","us-east-2":"ami-def456"}'
可以在一条命令中使用多个 -var
参数。
1.4.3.1.7.2. 参数文件
第二种方法是使用参数文件。参数文件的后缀名可以是 .tfvars
或是 .tfvars.json
。.tfvars
文件使用 HCL 语法,.tfvars.json
使用 JSON 语法。
以 .tfvars
为例,参数文件中用 HCL 代码对需要赋值的参数进行赋值,例如:
image_id = "ami-abc123"
availability_zone_names = [
"us-east-1a",
"us-west-1c",
]
后缀名为 .tfvars.json
的文件用一个 JSON 对象来对输入变量赋值,例如:
{
"image_id": "ami-abc123",
"availability_zone_names": ["us-west-1a", "us-west-1c"]
}
调用 terraform 命令时,通过 -var-file
参数指定要用的参数文件,例如:
terraform apply -var-file="testing.tfvars"
terraform apply -var-file="testing.tfvars.json"
有两种情况,你无需指定参数文件:
- 当前模块内有名为
terraform.tfvars
或是terraform.tfvars.json
的文件 - 当前模块内有一个或多个后缀名为
.auto.tfvars
或是.auto.tfvars.json
的文件
Terraform 会自动使用这两种自动参数文件对输入参数赋值。
1.4.3.1.7.3. 环境变量
可以通过设置名为 TF_VAR_<NAME>
的环境变量为输入变量赋值,例如:
$ export TF_VAR_image_id=ami-abc123
$ terraform plan
...
在环境变量名大小写敏感的操作系统上,Terraform 要求环境变量中的 <NAME>
与 Terraform 代码中定义的输入变量名大小写完全一致。
环境变量传值非常适合在自动化流水线中使用,尤其适合用来传递敏感数据,类似密码、访问密钥等。
1.4.3.1.7.4. 交互界面传值
在前面介绍断言的例子中我们看到过,当我们从命令行界面执行 terraform 操作,Terraform 无法通过其他途径获取一个输入变量的值,而该变量也没有定义默认值时,Terraform 会进行最后的尝试,在交互界面上要求我们给出变量值。
1.4.3.1.8. 输入变量赋值优先级
当上述的赋值方式同时存在时,同一个变量可能会被赋值多次。Terraform 会使用新值覆盖旧值。
Terraform 加载变量值的顺序是:
- 环境变量
terraform.tfvars
文件(如果存在的话)terraform.tfvars.json
文件(如果存在的话)- 所有的
.auto.tfvars
或者.auto.tfvars.json
文件,以字母顺序排序处理 - 通过
-var
或是-var-file
命令行参数传递的输入变量,按照在命令行参数中定义的顺序加载
假如以上方式均未能成功对变量赋值,那么 Terraform 会尝试使用默认值;对于没有定义默认值的变量,Terraform 会采用交互界面方式要求用户输入一个。对于某些 Terraform 命令,如果执行时带有 -input=false
参数禁用了交互界面传值方式,那么就会报错。
重要提示:在 Terraform 0.12 及更高版本中,类型为 map
或 object
的输入变量的读取行为与其他变量相同:后找到的值会覆盖之前的值。这与 Terraform 的早期版本不同,早期版本会合并 map
,而不是覆盖它们。
1.4.3.1.8.1. Terraform 测试中的输入变量值
在 Terraform 测试文件中,您可以在 variable
块中指定变量值,这些 variable
块可以嵌套在 run
块中,也可以直接在文件中定义。
以这种方式定义的变量在测试执行期间优先于所有其他机制,其中在 run
块中定义的变量优先于在文件中定义的变量。
1.4.3.1.9. 复杂类型传值
通过参数文件传值时,可以直接使用 HCL 或是 JSON 语法对复杂类型传值,例如 list
或 map
。
对于某些场景下必须使用 -var
命令行参数,或是环境变量传值时,可以用单引号引用 HCL 语法的字面量来定义复杂类型,例如:
export TF_VAR_availability_zone_names='["us-west-1b","us-west-1d"]'
由于采用这种方法需要手工处理引号的转义,所以这种方法比较容易出错,复杂类型的传值建议尽量通过参数文件。