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
定义类型,例如:
variable "name" {
type = string
}
variable "ports" {
type = list(number)
}
定义了类型的输入变量只能被赋予符合类型约束的值。
1.4.3.1.2. 默认值
默认值定义了当Terraform无法获得一个输入变量得到值的时候会使用的默认值。例如:
variable "name" {
type = string
default = "John Doe"
}
当Terraform无法通过其他途径获得name的值时,var.name
的值为"John Doe"
。
1.4.3.1.3. 描述
可以在输入变量中定义一个描述,简单地向调用者描述该变量的意义和用法:
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. 断言
输入变量的断言是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类型的参数,我们可以用一个表达式来定义如何界定输入变量是合法的。当contidion为true时输入变量合法,反之不合法。condition表达式中只能通过var.\
假如表达式的计算产生一个错误是输入变量验证的一种判定手段,那么可以使用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. 在命令行输出中隐藏值
该功能于 Terraofrm 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. 禁止输入变量为空
该功能自 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要求环境变量中的\
环境变量传值非常适合在自动化流水线中使用,尤其适合用来传递敏感数据,类似密码、访问密钥等。
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
参数禁用了交互界面传值方式,那么就会报错。
1.4.3.1.9. 复杂类型传值
通过参数文件传值时,可以直接使用HCL或是JSON语法对复杂类型传值,例如list或map。
对于某些场景下必须使用-var
命令行参数,或是环境变量传值时,可以用单引号引用HCL语法的字面量来定义复杂类型,例如:
export TF_VAR_availability_zone_names='["us-west-1b","us-west-1d"]'
由于采用这种方法需要手工处理引号的转义,所以这种方法比较容易出错,复杂类型的传值建议尽量通过参数文件。