terraform-HCL语法
本文记录一些关于terraform的语法,只记录一下我认为有必要记录的东西,本文绝大部分来自教程: https://lonegunmanb.github.io/introduction-terraform/
数据类型
原始类型
- string
- number
- bool
复杂类型
list(type)
: 下标从0
开始,list(number)/list(string)/list(bool)
,用于声明时指定list的元素类型map(...)
: 字典/映射,key必须是string
,value任意类型.声明方式推荐使用=
:{foo="bar", bar="baz"}
set(...)
: 集合类型,代表一组不重复的值
结构化类型
一个结构化类型允许多个不同类型的值组成一个类型.结构化类型需要提供一个
schema
结构信息作为参数来指明元素的结构.
object(...)
: 允许多个不同类型作为值的map1
2
3
4# 声明{<KEY\=<TYPE>, <KEY>=<TYPE>,...}
object({age=number, name=string})
# 赋值时必须赋全,可以多,多的会被忽略
{ age=18, name="john", gender="male" } # 多出来的gender会被忽略tuple(...)
: 允许多个不同类型作为值的list1
2
3
4# 声明
tuple([string, number, bool])
# 赋值时类型和数量要一致
["a", 15, true]
any
terraform中一个特殊的类型约束,本身不是一个类型而是一个占位符
,每当一个值被赋予any
这个类型约束时,terraform会自动识别一个最精确的类型去取代any
.
例如把["a", "b", "c"]
赋值给list(any)
, terraform最终赋值的类型是list(string)
.
1 |
|
null
无类型
,代表数据缺失,如果我们将一个参数设置为null
, terraform会认为你忘记给它赋值,如果有默认值就用默认值,没有默认值但是又是必填则会报错.
一般用于条件表达式中,在某项条件不满足时跳过对某参数的赋值.
optional
terraform >= 1.3
引入的功能
1 |
|
optional
有两个参数:
- 类型: 必填,
第一个参数
标明属性的类型 - 默认值: 选填,如果object没有定义该属性,就使用默认值,没有默认值就使用
null
所以optional作用
如下: 定义这个参数的类型和默认值,如果没有赋值就使用默认值,如果没有默认值就赋值null.
例子一
1 |
|
这里定义嵌套比较复杂,只是为了演示用法.
首先
buckets
是一个类型为list
的变量,list
里面元素类型是object
website
是一个optional
属性,类型为object
,里面又有三个optional
属性.下面是一个赋值例子:
1 |
|
上面定义三个存储桶
production
桶配置了一条重定向的路由规则
archived
桶使用默认配置,但是是关闭的
docs
桶使用文本文件取代索引页和错误页
例子二
下面结合表达式
去有条件地设置一个默认值
1 |
|
变量
声明
1 |
|
赋值
按以下优先级排序
环境变量:
TF_VAR_name
terraform.tfvars
: 使用HCL语法1
2
3
4
5image_id = "ami-abc123"
availability_zone_names = [
"us-east-1a",
"us-west-1c",
]terraform.tfvars.json
: 使用json语法1
2
3
4{
"image_id": "ami-abc123",
"availability_zone_names": ["us-west-1a", "us-west-1c"]
}*.auto.tfvars
或*.auto.tfvars.json
,以文件名字母排序,同样一个HCL一个json-var
参数或者-var-filr
交互界面获取值
调用
1 |
|
输出
1 |
|
关于
depends_on
: 依赖关系是通过terraform的provider去分析的,一般是不需要显式定义,但有时候它不能计算出来(比如terraform或者provider的版本过旧,对一些新资源不能很好地计算,就需要用到depends_on)
locals
locals
块用于定义本地变量,只能在同一模块
内的代码中引用.
1 |
|
一般来说,只有需要重复引用同一个复杂表达式的场景才使用locals
resource
定义具体资源的块,也是接触最多的块.不同平台的不同资源的定义都有所不同,具体要查看具体平台具体资源的文档.
1 |
|
local_name只能在同一个模块内被引用,resource_type+local_name在同一个模块内必须是唯一
资源行为
当terraform对一个resource块执行apply操作,创建一个新的基础设施对象,会给这个对象分配一个
id
,并写入statefile.有了这个id,后续就可以对它对更新修改删除等操作.
resource的输出
通过以下语法获取resource的输出
1 |
|
具体的resource有哪些attribute
供访问可以通过查阅registry文档获取.
依赖与并行
depends_on
用于显式声明资源的依赖关系.一般情况下,terraform自身可以通过分析resource块内的
表达式
,根据表达式的引用链
获取资源在创建,更新,销毁时的执行顺序.但是有些时候是计算不出来的:
例如,Terraform必须要创建一个访问控制权限资源,以及另一个需要该权限才能成功创建的资源。
后者的创建依赖于前者的成功创建
,然而这种依赖在代码中没有表现为数据引用关联
,此时就需要depends_on
默认情况下,terraform可以并行创建
10
个无依赖关系
的资源.
元参数
定义在resource块内,用于改变资源的行为,上面提到的depends_on
就是其中一个.
depends_on
显示声明资源之间的依赖关系,只有当资源间确实存在依赖关系,但是彼此间又没有数据引用的场景下才有必要使用
count
count
指创建多少个相似的对象,创建的对象有单独的id和下标(0开始)
1 |
|
count.index
:代表当前对象对应的下标- 访问:
<TYPE>.<NAME>[<INDEX>]
- count的值可以是
任意自然数
也可以是表达式
,但是不能引用任何其他资源的输出属性,不过可以应用data
查询出来的输出属性(只要该data不依赖其他resource查询)
for_each
用于创建多个相似但有些属性不一样的资源
terraform >= 0.12.6, 不能与
count
同时用在一个resource块内
for_each
的值可以是map
(通过each.key
和each.value
获取值)或者set(string)
(通过each.key
获取值)
for_each
所使用的键集合不能够包含或依赖非纯函数,也就是反复执行会返回不同返回值的函数(uuid, bcrypt, timestamp)
1 |
|
provider
resource块内可以通过provider关键字声明该资源使用的provider.默认情况下,使用资源类型第一个单词
所对应的provider.
比如google_compute_instance
的默认provider就是google
,aws_instance
的默认Provider就是aws
.
lifecycle
针对一个资源对象定义不一样的行为方式
1 |
|
lifecycle和它的参数都属于元参数,可以被声明于任意类型的资源块内部.有如下的参数:
create_before_destroy
(bool
): 默认情况下,需要修改一个由于服务端API限制无法直接升级的资源时,terraform会先删除旧对象
再创建新对象
.本参数可以修改这个行为.设置为true
,就会先创建新对象,新对象创建成功并取代老对象后再删除老对象.默认
是false,因为大部分技术设施资源都需要有一个唯一
的标识来防止冲突,即使在新老对象并存的时候也有有这个约束.但有些资源可以为每个对象添加一个随机的前缀来避免冲突,具体要看具体的资源类型在这方面的约束.prevent_destroy
(bool
): 这是一个保险措施
,避免资源因为错误的执行terraform destroy
或者因为错误地修改了某个参数导致terraform决定删除并重建新的实例
这种情况下资源被删除.ignore_changes
(list(string)
):忽略改变,某些场景下,某资源可以会被第三方控制而不停被修改,此时terraform每次都会把它改回来,如果需要避免这种情况,可以使用这个参数指定要忽略的某些属性
的变更.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23# 忽视tags属性变更
resource "aws_instance" "example" {
# ...
lifecycle {
ignore_changes = [
# Ignore changes to tags, e.g. because a management agent
# updates these based on some ruleset managed elsewhere.
tags,
]
}
}
# 忽视map中特定元素的变更,必须确保map中含有这个元素
resource "aws_instance" "example" {
tags = {
Name = "placeholder"
}
lifecycle {
ignore_changes = [
tags["Name"],
]
}
}除了使用
list(string)
指定忽略的属性还可以使用all
忽略所有属性的变更replace_triggered_by
: 手动指定资源的哪些属性被修改时,强制触发替换资源
这个操作.一般情况下用不到.
data
data
,数据源,用于查询或计算数据供其他地方使用.数据源是provider定义的,不同的平台有不同的数据源可供查询.因此数据源的具体定义可以通过查registry文档获取.
1 |
|
dynamic块
比如在resource块中,如果某些资源包含了可重复的内嵌块,此时可以使用dynamic循环赋值.
1 |
|
下面举个例子:
1 |
|
上面的例子中,首先定义了一个
subnets
的变量,由子网所需属性(name,cidr,zone,resource)的对象构成.(list(object)
)然后定义
subnet
这个dynamic块.for_each
负责迭代对象var.subnets
content
负责给属性赋值值通过
subnet.value.zone
形式引用,注意引用值第一节字符串用的是dynamic块的名字
过多的dynamic块会导致代码难以阅读和维护,建议只在需要构造可重用的模块代码时使用.
check
terraform >= 1.5
,check 块中可以定义一个有限作用范围的data块
以及至少一个
断言.用于验证基础设状态
.
check会在每次plan
或apply
后执行的自定义验证,作为plan或apply的最后一步执行.
assert
是一种在check
块中使用的断言机制。check
块用于定义验证规则,而assert
块用于在验证规则中定义断言逻辑。断言是一种用于检查条件是否为真的方法。在check
块中,你可以使用assert
块来添加额外的断言逻辑,以确保特定条件为真。如果断言条件为假,Terraform 将输出错误消息。
1 |
|
module
创建
所有包含terraform代码文件的文件夹都是一个terraform模块.直接执行terraform plan/apply
的文件夹称为根模块
举个例子说明模块的结构:
1 |
|
模块结构不宜过深,保持一层嵌入就可以了
引用
通过module
块的source
关键字来指定模块的源,支持的模块源有:
本地
本地路径必须以
./
或../
为前缀来声明使用的是本地路径,除了本地源,其他源的模块引用都要先下载代码才能使用1
2
3module "consul" {
source = "./consul"
}
terraform registry
官方仓库: 公共仓库,也可以通过terraform cloud维护一个私有模块仓库,或者通过实现 Terraform 模块注册协议来实现一个私有仓库.
1
2
3
4
5
6
7
8
9
10module "consul" {
# 公共仓库的的模块可以用 <NAMESPACE>/<NAME>/<PROVIDER> 形式的源地址来引用,在公共仓库上的模块介绍页面上都包含了确切的源地址
source = "hashicorp/consul/aws"
version = "0.1.0"
}
# 托管在其他仓库的模块,在源地址头部添加 <HOSTNAME>/ 部分,指定私有仓库的主机名
module "consul" {
source = "app.terraform.io/example-corp/k8s-cluster/azurerm"
version = "1.1.0"
}
github
以
gitHub.com
为前缀指定1
2
3
4
5
6
7
8# 默认使用HTTPS协议克隆仓库
module "consul" {
source = "github.com/hashicorp/example"
}
# 使用ssh协议
module "consul" {
source = "git@github.com:hashicorp/example.git"
}
bitbucket
bitbucket.org
为前缀
git/mercurial仓库
git仓库:
git::
为前缀1
2
3
4
5
6
7
8# https, ref参数指定分支
module "vpc" {
source = "git::https://example.com/vpc.git?ref=v1.2.0"
}
# ssh
module "storage" {
source = "git::ssh://username@example.com/storage.git"
}Mercurial 仓库:
hg::
前缀
HTTP地址
S3
s3::
GCS
gcs::
引用子文件夹中的模块,例如:
hashicorp/consul/aws//modules/consul-cluster
git::https://example.com/network.git//modules/vpc
https://example.com/network-module.zip//modules/vpc
s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/network.zip//modules/vpc
使用
引用后直接再module块内给模块的变量赋值就能使用
1 |
|
引用模块输出值
1 |
|
模块内的provider
一般来说,旨在复用的模块内是不能定义任何provider的.
隐式继承
默认情况下子模块会自动从调用者那里继承默认的provider配置.
显式声明
如果不同的子模块需要不同的provider,或者子模块需要的provider与调用者自己使用的不同时,则需要显式声明.
比如一个模块配置了两个aws区域之间的网络打通,所以需要配置一个源区域provider和目标区域provider.根模块代码看起来应该是这样的:
1 |
|
而模块tunnel中必须包含下面的例子那样声明provider别名
,声明模块调用者必须使用这些名字传递provider
1 |
|
alias
是provider代理配置的参数,它是一个模块间传递provider配置的占位符.
实践例子
有条件创建
这是很常用的一个例子,使用count+表达式实现
1 |
|
-target参数
多人团队执行terraform时,建议以module为单位
运行.
1 |
|
–plugin-dir
所处网络环境比较严格,只能手动下载provider等插件,需要手动指定插件目录
1 |
|