《Terraform 101 从入门到实际》这本小册在南瓜慢说官方网站和GitHub两个中央同步更新,书中的示例代码也是放在GitHub上,不便大家参考查看。
介绍了Terraform一些比拟根底的概念后,咱们能够先理解一下Terraform的语法,也就是HCL的语法。
变量Variables
变量是实现代码复用的一种形式,同样的代码不同的变量往往会有不同的成果。而在Terraform里,有一个概念十分重要,就是变量都是从属于模块的。变量无奈跨模块援用。即在模块A定义的变量X,无奈在模块B中间接援用。但父模块的变量,能够作为子模块的入参;而子模块的输入变量能够被父模块获取。
变量类型
从语言角度
跟任何编程语言一样,变量都是有类型的,Terraform的变量类型从语言的角度可分为两大类:根本类型和组合类型,具体如下:
根本类型:
- 字符串string,如
"pkslow.com"
- 数字number,如
319
或5.11
- 布尔值bool,如
true
组合类型:
- 列表list(<T>),如
["dev", "uat", "prod"]
- 汇合set(<T>),如
set(...)
- 映射map(<T>),如
{name="Larry", age="18"}
- 对象object({name1=T1, name2=T2})
- 元组tuple([T1,T2,T3...])
如果不想指定某个类型,能够用any
来示意任意类型;或者不指定,默认为任意类型。
从性能角度
从性能角度来看,变量能够分为输出变量、输入变量和本地变量。
输出变量是模块接管内部变量的形式,它定义在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" } ]}
输入变量定义了一个模块对外返回的变量,通过output
块来定义,如下:
output "instance_ip_addr" { value = aws_instance.server.private_ip}
本地变量是模块内定义且可援用的长期变量,在locals
块中定义,如下:
locals { service_name = "forum" owner = "Community Team"}
输出变量Input Variable
输出变量是定义在variable
块中的,它就像是函数的入参。
定义输出变量
定义variable
有很多可选属性:
- 类型type:指定变量是什么类型;如果没有指定,则能够是任意类型;
- 默认值default:变量的默认值,定义后能够不必提供变量的值,留神它的值的类型要与type对应上;
- 阐明description:阐明这个变量的作用和用处;
- 校验validation:提供校验逻辑来判断输出的变量是否非法;
- 敏感性sensitive:定义变量是否敏感,如果是则不会显示;默认为
false
; - 可空nullable:如果为true则能够为空,否则不能。默认为
true
。
所有属性都显性指定如上面例子所示:
variable "env" { type = string default = "dev" description = "environment name" sensitive = false nullable = false validation { condition = contains(["dev", "uat", "prod"], var.env) error_message = "The env must be one of dev/uat/prod." }}
这个变量名为env
,示意环境名,默认值为dev
,这个值必须为dev
、uat
和prod
中的其中一个。如果输入一个非法的值,会报错:
$ terraform plan -var="env=sit"╷│ Error: Invalid value for variable│ │ on input.tf line 1:│ 1: variable "env" {│ │ The env must be one of dev/uat/prod.
应用输出变量
只有定义了变量才能够应用,应用的形式是var.name
。比方这里定义了两个变量env
和random_string_length
:
variable "env" { type = string default = "dev"}variable "random_string_length" { type = number default = 10}
则应用如下:
resource "random_string" "random" { length = var.random_string_length lower = true special = false}locals { instance_name = "${var.env}-${random_string.random.result}"}output "instance_name" { value = local.instance_name}
传入变量到根模块
要从内部传入变量到根模块,有多种形式,常见的有以下几种,按优先级从低到高:
- 环境变量
export TF_VAR_image_id=ami-abc123
terraform.tfvars
文件;terraform.tfvars.json
文件;*.auto.tfvars
或*.auto.tfvars.json
文件;- 命令行参数
-var
传入一个变量;命令行参数-var-file
传入一个变量的汇合文件;
在实践中,最罕用的还是通过命令行来传入参数,因为个别须要指定不同环境的特定变量,所以会把变量放到文件中,而后通过命令行指定特定环境的主文件:
$ terraform apply -var="env=uat"$ terraform apply -var-file="prod.tfvars"
而prod.tfvars
的内容如下:
env = "prod"random_string_length = 12
咱们能够定义dev.tfvars
、uat.tfvars
和prod.tfvars
等,要应用不同环境的变量就间接扭转文件名即可。
输入变量Output Variable
有输出就有输入,输入变量就像是模块的返回值,比方咱们调用一个模块去创立一台服务,那就要获取服务的IP,这个IP当时是不晓得,它是服务器创立完后的后果之一。输入变量有以下作用:
- 子模块的输入变量能够裸露一些资源的属性;
- 根模块的输入变量能够在apply后输入到控制台;
- 根模块的输入变量能够通过
remote state
的形式共享给其它Terraform配置,作为数据源。
定义输入变量
输入变量须要定义在output
块中,如下:
output "instance_ip_addr" { value = aws_instance.server.private_ip}
这个value
能够是reource的属性,也能够是各种变量计算后的后果。只有在执行apply的时候才会去计算输入变量,像plan是不会执行计算的。
还能够定义输入变量的一些属性:
description
:输入变量的形容,阐明分明这个变量是干嘛的;sensitive
:如果是true
,就不会在控制台打印进去;depends_on
:显性地定义依赖关系。
残缺的定义如下:
output "instance_ip_addr" { value = aws_instance.server.private_ip description = "The private IP address of the main server instance." sensitive = false depends_on = [ # Security group rule must be created before this IP address could # actually be used, otherwise the services will be unreachable. aws_security_group_rule.local_access, ]}
援用输入变量
援用输入变量很容易,表达式为module.<module name>.<output name>
,如果后面的输入变量定义在模块pkslow_server
中,则援用为:module.pkslow_server.instance_ip_addr
。
本地变量Local Variable
本地变量有点相似于其它语言代码中的局部变量,在Terraform模块中,它的一个重要作用是防止反复计算一个值。
locals { instance_name = "${var.env}-${random_string.random.result}-${var.suffix}"}
这里定义了一个本地变量instance_name
,它的值是一个简单的表达式。这时咱们能够通过local.xxx
的模式援用,而不必再写简单的表达式了。如下:
output "instance_name" { value = local.instance_name}
这里要特地留神:定义本地变量的关键字是locals
块,外面能够有多个变量;而援用的关键字是local
,并没有s
。
个别咱们是倡议须要反复援用的简单的表达式才应用本地变量,不然太多本地变量就会影响可读性。
对变量的援用
定义了变量就须要对其进行援用,后面的解说其实曾经讲过了局部变量的援用,这些把所有列出来。
类型 | 援用形式 |
---|---|
资源Resources | <Resource Type>.<Name> |
输出变量Input Variables | var.<NAME> |
本地变量Local Values | local.<NAME> |
子模块的输入 | module.<Module Name>.<output Name> |
数据源Data Sources | data.<Data Type>.<Name> |
门路和Terraform相干 | path.module :模块所在门路path.root :根模块的门路path.cwd :个别与根模块雷同,其它高级用法除外terraform.workspace :工作区名字 |
块中的本地变量 | count.index :count循环的下标;each.key /each.value :for each循环的键值;self :在provisioner的援用; |
下面都是单值的援用,如果是List或Map这种简单类型,就要应用中括号[]
来援用。
aws_instance.example[0].id
:援用其中一个元素;
aws_instance.example[*].id
:援用列表的所有id值;
aws_instance.example["a"].id
:援用key为a
的元素;
[for value in aws_instance.example: value.id]
:返回所有id为列表;
运算符
与其它语言一样,Terraform也有运算符能够用,次要是用于数值计算和逻辑计算。以下运算符按优先级从高到低如下:
!
取反,-
取负*
乘号,/
除号,%
取余+
加号,-
减号>
,>=
,<
,<=
:比拟符号==
等于,!=
不等于&&
与门||
或门
当然,用小括号能够扭转这些优良级,如(1 + 2) * 3
。
留神:对于结构化的数据比拟须要留神类型是否统一。比方var.list == []
按理说应该返回true
,而list
为空时。当[]
理论示意是元组tuple([])
,所以它们不匹配。能够应用length(var.list) == 0
的形式。
条件表达式
条件表达式的作用是在两个值之间选一个,条件为真则选第一个,条件为假则选第二个。模式如下:
condition ? true_value : false_value
示例如下:
env = var.env !="" ? var.env : "dev"
意思是给env
赋值,如果var.env
不为空就把输出变量var.env
的值赋给它,如果为空则赋默认值dev
。
for表达式
应用for
表达式能够创立一些简单的值,而且能够应用一些转换和计算对值计算再返回。如将字符串列表转化成大写:
> [for s in ["larry", "Nanhua", "Deng"] : upper(s)][ "LARRY", "NANHUA", "DENG",]
能够获取下标和值:
> [for i,v in ["larry", "Nanhua", "Deng"] : "${i}.${v}"][ "0.larry", "1.Nanhua", "2.Deng",]
对于Map的for表达式:
> [for k,v in {name: "Larry Deng", age: 18, webSite: "www.pkslow.com"} : "${k}: ${v}"][ "age: 18", "name: Larry Deng", "webSite: www.pkslow.com",]
通过条件过滤数据:
> [for i in range(1, 10) : i*3 if i%2==0][ 6, 12, 18, 24,]
动静块Dynamic Block
动静块的作用是依据变量反复某一块配置。这在Terraform是会遇见的。
resource "aws_elastic_beanstalk_environment" "tfenvtest" { name = "tf-test-name" application = "${aws_elastic_beanstalk_application.tftest.name}" solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6" dynamic "setting" { for_each = var.settings content { namespace = setting.value["namespace"] name = setting.value["name"] value = setting.value["value"] } }}
比方这里的例子,就会反复setting
块。反复的次数取决于for_each
前面跟的变量。