Terraform HCL 语法
一、什么是 HCL?
Hashicorp 早期考虑过 YAML、JSON 作为配置文件语言,但最终两者都不能令其满意,所以他们设计出了他们认为的可读性更高的 HCL(Hashicorp Configuration Language)
terraform 早期只支持 HCL 语法的 tf 文件,但发展到现在也支持了 JSON,并且他们修改了 JSON 解释器,使得他们的 JSON 支持注释,不过相较于 JSON,HCL 的可读性更高,而 JSON 更适合于其他工具(平台)集成
二、语法规则
2.1 类型
terraform 数据类型分为 原始类型 与 复杂类型
2.1.1 原始类型
原始类型分为 3 种:
- string:代表一组 Unicode 字符串,例如:
"hello"
- number:代表数字,可为 整数 或 小数
- bool:代表布尔值,
true
或false
需要注意的是,terraform 中某些类型是可以隐式转换的,例如以下几个
- bool <-> string
- number <-> string
2.1.2 复杂类型
所谓 “复杂类型”,指的是由 一组值组成的类型,复杂类型分为 2 种
(1)集合类型
一个 “集合变量” 在构造时便要求确定 “集合类型”,组内各元素类型皆为同一种类型,terraform 支持 3 种集合
list()
:列表是一组值的连续集合,支持用下标方式访问,如:声明变量 l 是 list 类型l = list(number)
,用l[0]
即可访问到列表第一个元素map()
:字典类型(映射类型),k/v 键值对,“键” 类型必须为 string,“值” 的类型随意,如:map(number),意为 键为string
类型而值为number
类型set()
:集合类型,代表一组不重复的值,如果set
的元素是string
,那么将按照字段顺序排列;其他类型的元素不承诺任何特定的排列顺序
以上集合支持通配类型,如:list(any) 可简写为 list
、map(any) map()
、set(any) set()
(2)结构化类型
terraform 支持两种结构化类型
object
:可以理解为面向对象编程中的 “对象”,它由一组由具有名称和类型的属性所构成的符合类型,如object({age=number, name=string})
,需要注意,赋值时必须包含所有属性object({age=number,name=string})
{ age=18 } 非法赋值
{ age=18, name=”john”, gender=”male” } 合法赋值,不过 gender 属性会被丢弃
tuple
:类似list
,是一组值的连续集合,但每个元素都有独立的类型,如:tuple([string, number, bool])
对应值可以为["a", 15, true]
复杂类型也是可以进行隐式转换的,例如
object
<->map
tuple
<->list
<->set
这里给出一个示例,用以了解
variabels.tf:声明变量
variable "dns_env_host_rr" {
# 声明变量类型为 map 字典(映射)类型,元素为 string 类型
type = map(string)
description = "各环境域名"
}
# Env list
variable "env_list" {
# 声明变量为 list 类型,列表元素为 string 类型
type = list(string)
}
# ecs info
variable "ecs_info" {
# 声明变量为 object,属性为 string 类型
type = object({ ecs_name = string, ecs_image = string})
}
terraform.tfvars:定义变量
dns_env_host_rr = {
"dev" = "dev.tf-demo"
"stag" = "stag.tf-demo"
"prod" = "prod.tf-demo"
}
env_list = ["dev", "stag", "prod"]
ecs_info = {
ecs_name = "myecs"
ecs_image = "centos_7_5_x64_20G_alibase_20211130.vhd"
}
output.tf:输出变量到终端
output "dns_host_rr" {
value = var.dns_env_host_rr
}
output "env_list" {
value = var.env_list
}
output "ecs_info" {
value = var.ecs_info
}
终端输出
$ tf apply -auto-approve
Outputs:
dns_host_rr = tomap({
"dev" = "dev.tf-demo"
"prod" = "prod.tf-demo"
"stag" = "stag.tf-demo"
})
ecs_info = {
"ecs_image" = "centos_7_5_x64_20G_alibase_20211130.vhd"
"ecs_name" = "myecs"
}
env_list = tolist([
"dev",
"stag",
"prod",
])
2.1.3 特殊类型
null 无类型,null
代表数据缺失,常用语条件表达式中做判断,例如在某项条件不满足时跳过对某参数的赋值
2.2 语法
2.2.1 变量
(1)输入变量
输入变量通过关键字 “variable” 声明定义,而它的值可以使用 默认值、CLI 选项、环境变量 等方式设置,基于此我们可以实现无需变更模块的源代码,就能灵活修改配置
① 定义输入变量
处于规范考虑,推荐将输入变量写在 variables.tf
文件中,如下所示:
// 关键字 变量名称
// 变量名称不允许为 source、version、providers、count、for_each、lifecycle、depends_on、locals
variable "iamge_id" {
type = string
description = "image id of Ubuntu 1804"
}
// 关键字 变量名称
variable "availability_zone_name" {
type = string
default = "cn-north-1a"
}
// 关键字 变量名称
variable "availability_zone_names" {
type = list(string)
default = ["cn-north-1a"]
}
variable "docker_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [{
internal = 8300
external = 8300
protocol = "tcp"
}]
}
variable
块内支持以下参数:
type
:指定变量的类型,默认为 stringdescription
:指定变量的描述信息,用于描述变量的用途default
:指定变量的默认值,存在默认值的变量可视为可选变量validation
块:指定变量的自定义验证规则
② 自定义验证规则
上面提到了一个 validatino 块,通过该块我们可以实现对输入变量做规则验证,如下所示:
variable "user_password" {
type = string
description = "The password for user to log in."
validation {
// 要求用户密码必须才超过 8 位
condition = length(var.user_password)>=8
// 否则返回错误提示
error_message = "The password is too short."
}
}
我们还可以借以 can
函数来做更丰富的判断,例如正则表达式
variable "iam_user_name" {
type = string
description = "This name is used for iam user to log in."
validation {
# regex(...) 如果匹配失败将返回错误
condition = can(regex("([a-zA-Z])", var.iam_user_name))
# 用户名不符合 a-zA-Z 肯定是不正常的,所以返回错误
error_message = "Incorrect user name. Please check whether it contains upper and lower case letters."
}
}
若 condition 的结果为 false
,terraform 将产生一条错误消息,其内容为 error_message
所定义的字符串,字符串内容应以大写字母开头,以 .
或者 ?
结尾
③ 引用输入变量
引用输入变量的方法很简单,语法:var.<变量名称>
# variables.tf
variable "vpc_cidr" {
type = string
description = "the CIDR of VPC"
}
# main.tf
resource "huaweicloud_vpc" "vpc_example" {
name = "my_vpc"
cidr = var.vpc_cidr
}
④ 设置变量
上面提供大致来说有三种,CLI
、环境变量
、.tfvars
**命令行 CLI 设置变量 **
$terraform apply -var='vpc_name=my_vpc' terraform apply -var='vpc_name=my_vpc' -var='vpc_cidr=192.168.0.0/16' terraform apply -var='availability_zone_names=["cn-north-1a", "cn-north-1c"]'
文件 .tfvars 定义变量:适用于配置中存在很多变量,使用时通过
-var-file
指明该文件$ terraform apply -var-file="testing.tfvars" $ cat testing.tfvars vpc_name = "my_vpc" vpc_cidr = "192.168.0.0/16" availability_zone_names = [ "cn-north-1a", "cn-north-1c", ]
补充说明一点,terraform 会自动加载特定 文件名称 或 后缀 的变量定义文件
文件名称为
terraform.tfvars
、terraform.tfvars.json
文件后缀为
.auto.tfvars
、.auto.tfvars.json
JSON 文件内需使用 JSON 语法
{ "vpc_name": "my_vpc" "availability_zone_names": ["cn-north-1a", "cn-north-1c"] }
环境变量设置:以
TF_VAR_
为前缀的系统环境变量来设置输入变量,适用于云环境、自动化场景$ export TF_VAR_vpc_name=my_vpc $ export TF_VAR_availability_zone_names='["cn-narth-1a", "cn-north-1c"]' $ terraform plan
⑤ 变量优先级
- 环境变量
terraform.tfvars
、terraform.tfvars.json
*.auto.tfvars
、*.auto.tfvars.json
- 命令行中的 -var 和 -var-file 选项
PS:不能在单个源中为同一个变量分配多个值
(2)本地变量
本地变量 可以理解为模块中的 “临时变量”,变量作用域仅限于 “模块内”,适用于 配置中存在定义相同值或表达式的场景,不过过渡使用本地变量会导致实际值被隐藏、代码晦涩、不利于维护,建议合理使用
本地变量 通过 locals
关键字 声明,如下所示:
locals {
service_name = "forum"
owner = "Community"
dns_list = concat(huaweicloud_vpc_subnet.subnet_1.dns_list, huaweicloud_vpc_subnet.subnet_2.dns_list)
common_tags = {
Service = local.service_name
Owner = local.owner
}
}
引用 locals 变量的方式也很简单 local.<变量名称>
resource "huaweicloud_obs_bucket" "bucket_demo" {
...
tags = local.common_tags
}
(3)输出变量
输出变量可以理解为 “模块” 的返回值,通过 output
关键字 声明,向外公开某些信息,如 CLI 输出结果,又或者向父模块返回属性值
模块暂时理解为一个 tf 目录,例如 demo1
$ tree . . ├── alicloud_security_group.tf ├── alicloud_vpc.tf ├── main.tf ├── terraform.tfstate ├── terraform.tfstate.backup ├── variables.tf └── versions.tf
① 声明输出变量
输出变量通过会定义在 variables.tf
文件中,主要包含三个参数:
- value:需要对外输出的变量值,通常为 属性、变量
- description:描述信息
- sensitive:变量值是否为敏感项(CLI 中是否显示明文)
- depends_on:指定输出变量的依赖关系,通常来说用不到,毕竟 output 只是导出数据的一种手段,几乎不存在与其他资源、数据的依赖关系
如下所示:
variable "ip_addr" {
type = string
default = "8.8.8.8"
}
variable "password" {
type = string
default = "Passw0rd"
}
output "ip" {
value = var.ip_addr
description = "output ip address"
}
output "pwd" {
value = var.password
description = "output password"
sensitive = true
}
定义完成后 refresh 刷新重新读取配置
// 不执行 refresh 是不会读取最新配置的
$ tf output
ip = "8.8.8.8"
$ tf refresh
Outputs:
ip = "8.8.8.8"
pwd = <sensitive>
通过 terraform output 命令获取输出变量
$ tf output
ip = "8.8.8.8"
pwd = <sensitive>
关于 sensitive 需要补充说明下,这种隐藏只是针对 CLI 的,而且很是粗浅,实际上还有以下几种方式获取原始数据
直接读取当前目录 state 文件
$ cat terraform.tfstate { "version": 4, "terraform_version": "1.1.9", "serial": 2, "lineage": "7e31725c-bffa-d38b-d19f-719ecaeb8c43", "outputs": { "ip": { "value": "8.8.8.8", "type": "string" }, "pwd": { "value": "Passw0rd", "type": "string", "sensitive": true } }, "resources": [] }
子模块敏感输出变量 被 父模块 引用,父模块后续 输出、资源引用 都可以拿到明文以及在 CLI 中显示
2.2.1 赋值
通常是在 vars 文件、命令行、环境变量 中为变量进行赋值,就是将一个值赋给一个已声明的特定的变量
image_id = "abc123"
参数赋值时,会检查参数值类型是否匹配,参数值可以为 字面量硬编码、表达式、其他值加以计算得出结果值
2.2.2 块
块,指包含一组其他内容的容器
resource "aws_instance" "example" {
ami = "abc123"
network_interface {
# ...
}
}
一个 “块” 有一种块类型,上例类型为 “resource”,每个块类型都会定义关键字后是否需要标签,以及需要多少标签,以 resource 为例,它需要两个标签:aws_instance
、example
,接下来后面一对花括号中间就是 “块体”,在块体中可以进一步定义各种参数和其他的块
类型关键字 "标签1" "标签N" {
// 块体
// 类型关键字
network_interface {
// 嵌套块
}
}
terraform 定义了多个顶级块类型,如:resource
,variable
,output
,data
2.2.3 注释
terraform 支持三种注释
#
单行注释,其后的内容为注释//
单行注释,其后的内容为注释/*
和*/
,多行注释,可以注释多行
默认情况下单行注释优先使用 #
,自动化格式整理工具会自动把 //
替换成 #
2.2.3 表达式
所谓表达式,用于 引用 或 计算 配置中的 值,最简单的表达式是 “文字表达式”,如:"hello world"
或 5
,terraform 支持多种表达式,包括 运算符、条件表达式 以及丰富的 内置函数
执行 terraform console
命令打开交互式的控制台,通过该控制台进行学习实验
(1)运算符
terraform 支持以下类型的运算符
- 算术运算符:操作数据和结果都为数字类型,包括:
+
,-
(减法),*
,/
,%
,-
(负数) - 关系运算符:操作数据为任意类型,结果为布尔值,包括:
==
,!=
- 比较运算符:操作数据为数字类型,结果为布尔值,包括:
>
,>=
,<
,<=
- 逻辑运算符:操作数据和结果都为布尔类型,包括:
||
,&&
,!
(2)条件表达式
条件表达式采用布尔表达式的值进行二选一,语法为:condition ? true_value : false_value
// 如果 var.ret 不为空,获取 var.data,否则使用 默认值
var.ret != "" ? var.data : "default-data"
看个小例子
variables.tf
variable "region" {
type = string
}
outputs.tf
output "server_location" {
value = var.region == "beijing" ? "beijing server." : "other region server"
}
terraform.tfvars
region = "beijing"
执行 init、apply
$ tf init
$ tf apply
Changes to Outputs:
+ server_location = "beijing server"
(3)for 表达式
for 循环表达式用于遍历 集合类型(list、set、map)中的各个元素,并对元素进行处理,最后将结果输出为一个新的集合类型,结果类型取决于所使用的括号
- 使用 [] 将生成一个列表
- 使用 {} 将生成一个字典(映射)
由于我并未找到在 tf console
创建变量的方法,所以这里使用 tf 文件定义,内容如下:
$ cat for_var_demo1.tf
variable "mylist" {
type = list(string)
default = ["a", "BB", "cCc"]
}
创建完毕后,在当前目录执行 tf console
进入控制台,然后尝试获取变量 mylist
$ tf console
> var.mylist
tolist([
"a",
"BB",
"cCc",
])
OK,接下来我们尝试去遍历
> [for i in var.mylist : lower(i)]
[
"a",
"bb",
"ccc",
]
我们倘若使用 {}
在加上一点处理,则可以结果生成为一个字典
> {for i in var.mylist : i => lower(i)}
{
"BB" = "bb"
"a" = "a"
"cCc" = "ccc"
}
同时我们还可以在遍历的时候做 if 判断
> [ for i in var.mylist : upper(i) if length(i) >= 2 ]
[
"BB",
"CCC",
]
了解完 for list 的简单操作后,我们在看下 for map,老样子,首先创建变量声明及默认值
$ cat for_var_demo2.tf
variable "mymap" {
type = map(string)
default = {item1="aaa", item2="bbb", item3="ccc"}
}
进入控制台,查看变量,并执行 for map 遍历
> var.mymap
tomap({
"item1" = "aaa"
"item2" = "bbb"
"item3" = "ccc"
})
// 遍历元素并转为大写
> { for k, v in var.mymap : k => upper(v) }
{
"item1" = "AAA"
"item2" = "BBB"
"item3" = "CCC"
}
// if 判断
> { for k, v in var.mymap : k => upper(v) if v == "bbb" }
{
"item2" = "BBB"
}
(4) splat 表达式
声明变量
variable "ecs_spec" {
type = list(map(string))
}
定义变量
ecs_spec = [
{
"id" = "100"
"ecs_name" = "server001",
"type" = "ecs.n1.tiny",
},
{
"id" = "101"
"ecs_name" = "server002",
"type" = "ecs.n1.tiny",
}
]
控制台体验一下
> var.ecs_spec.*.ecs_name
tolist([
"server001",
"server002",
])
> var.ecs_spec.*.id
tolist([
"100",
"101",
])
> var.ecs_spec[*].id
tolist([
"100",
"101",
])
> var.ecs_spec[0].id
"100"
# 不支持-1获取最后元素
> var.ecs_spec[-1].id
╷
│ Error: Invalid index
│
│ on <console-input> line 1:
│ (source code not available)
│
│ The given key does not identify an element in this collection value: a negative number is not a valid index for a sequence.
2.2.4 常见函数
(1)字符串函数
函数名称 | 函数描述 | 样例 | 运行结果 |
---|---|---|---|
format |
字符串格式化 | format(“Hello, %s!”, “Huaweicloud”) | Hello, Huaweicloud! |
lower |
将字符串中的字母转换为小写 | lower(“HELLO”) | hello |
upper |
将字符串中的字母转换为大写 | upper(“hello”) | HELLO |
join |
使用自定义字符将列表拼接成字符串 | join(“, “, [“One”, “Two”, “Three”]) | One, Two, Three |
split |
根据分隔符拆分字符串 | split(“.”, “www.baidu.com") | [“www”, “baidu”, “com”] |
substr |
通过偏移量和长度从给定的字符串中提取一个子串 | substr(“hello world!”, 1, 4) | ello |
replace |
把字符串中的 str1 替换成 str2 | replace(“hello, huaweicloud!”, “h”, “H”) | Hello, Huaweicloud! |
(2)计算函数
函数名称 | 函数描述 | 样例 | 运行结果 |
---|---|---|---|
abs |
计算绝对值 | abs(-12.4) | 12.4 |
max |
计算最大值 | max(12, 54, 6) max([12, 54, 6]…) |
54 54 |
min |
计算最小值 | min(12, 54, 6) min([12, 54, 6]…) |
66 |
log |
计算对数 | log(16, 2) | 4 |
power |
计算x的y次幂 | power(3, 2) | 9 |
(3)集合函数
函数名称 | 函数描述 | 样例 | 运行结果 |
---|---|---|---|
element |
通过下标从列表中检索对应元素值 | element([“One”, “Two”, “Three”], 1) | Two |
index |
返回给定值在列表中的索引,如果该值不存在将报错 | index([“a”, “b”, “c”], “b”) | 1 |
lookup |
使用给定的键从映射表中检索对应的值。如果给定的键不存在,则返回默认值 | lookup({IT=”A”, CT=”B”}, “IT”, “G”) lookup({IT=”A”, CT=”B”}, “IE”, “G”) |
A G |
merge |
合并 map,相同 key 会被覆盖 | erge({name=”Da”, age=”19”}, {name=”Da”, age=”20”}) | {“age” = “20”,”name” = “Da”} |
flatten |
展开列表中的嵌套元素 | flatten([[“a”, “b”], [], [“c”]]) | [“a”, “b”, “c”] |
keys |
返回 map 中的所有 key | keys({a=1, b=2, c=3}) | [“a”, “b”, “c”] |
length |
获取 列表、映射、字符串 的长度 | length([“One”, “Two”, “Three”]) length({IT=”A”, CT=”B”}) length(“Hello, Huaweicloud!”) |
3 2 19 |
concat | 合并 list | concat([1,2], [3,4]) | [1,2,3,4] |
distinct | 去除 list 重复元素 | distinct([1,1,2,3]) | [1,2,3] |
去除 list 空元素 | compat([“1”, “2”, “”]) | [1,2] |
(4)类型转化函数
函数名称 | 函数描述 | 样例 | 运行结果 |
---|---|---|---|
toset | 将列表类型转换为集合类型 | toset([“One”, “Two”, “One”]) | [“One”, “Two”] |
tolist | 将集合类型转换为列表类型 | toset([“One”, “Two”, “Three”]) | [“One”, “Two”, “Three”] |
tonumber | 将字符串类型转换为数字类型 | tonumber(“33”) | 33 |
tostring | 将数字类型转换为字符串类型 | tostring(33) | “33” |
(5)编码函数
函数名称 | 函数描述 | 样例 | 运行结果 |
---|---|---|---|
base64encode | 将UTF-8字符串转换为base64编码 | base64encode(“Hello, Huaweicloud!”) | SGVsbG8sIEh1YXdlaWNsb3VkIQ== |
base64decode | 将base64编码解码为UTF-8字符串(结果非UTF-8格式会报错) | base64decode(“SGVsbG8sIEh1YXdlaWNsb3VkIQ==”) | Hello, Huaweicloud! |
base64gzip | 将UTF-8字符串压缩并转换为base64编码 | base64gzip(“Hello, Huaweicloud!”) | H4sIAAAAAAAA/ |
(6)哈希和加密函数
函数名称 | 函数描述 | 样例 | 运行结果 |
---|---|---|---|
sha256 | 计算字符串的SHA256值(16进制) | sha256(“Hello, Huaweicloud!”) | bbb76b2eb48a661… |
sha512 | 计算字符串的SHA512值(16进制) | sha512(“Hello, Huaweicloud!”) | 61f1ce05848b7dd… |
base64sha256 | 计算字符串的SHA256值,并转换为base64编码 | base64sha256(“Hello, Huaweicloud!”) | u7drLrSKZhDByHyIK… |
base64sha512 | 计算字符串的SHA512值,并转换为base64编码 | base64sha512(“Hello, Huaweicloud!”) | YfH0BYSLfdeyPubtXz… |
md5 | 计算MD5值 | md5(“hello world”) | 5eb63bbbe01eeed093… |
(6)文件操作函数
函数名称 | 函数描述 | 样例 | 运行结果 |
---|---|---|---|
abspath | 计算文件的绝对路径 | abspath(“./hello.txt”) | /home/demo/test/terraform/hello.txt |
dirname | 计算字符串中包含的路径 | dirname(“foo/bar/baz.txt”) | foo/bar |
basename | 计算字符串中的文件名 | basename(“foo/bar/baz.txt”) | baz.txt |
file | 读取文件并返回文件内容 | file(“./hello.txt”) | Hello, Huaweicloud! |
filebase64 | 读取文件并返回文件内容的base64编码 | filebase64(“./hello.txt”) | SGVsbG8sIEh1YXdlaWNsb3VkIQ== |
2.2.5 编码
terraform 配置文件必须始终使用 UTF-8 编码,terraform 兼容 Unix 风格的换行符 以及 Windows 风格的换行符,但理想状态下应使用 Unix 风格换行符
2.2.6 约定
为了确保不同团队编写的文件和模块的风格一致性,建议用户遵循以下约定
每个嵌套级别,缩进两个空格
多个单行参数在同一嵌套级别连续出现时,建议将等号对齐
name = "myinstance" availability_zone = "cn-north-1a"
使用空行分隔块中的逻辑参数组
元参数(meta-arguments) 放在块内置顶,并用空行与其它参数隔开
元参数块(meta-argument blocks) 放在块底部,并用空行与其它参数隔开
resource "huaweicloud_obs_bucket" "demo" { count = 1 bucket = "bucket_demo" acl = "public-read" tags = { foo = "bar" env = "test" } lifecycle { create_before_destroy = true } }
顶层块之间使用空行将彼此隔开
建议将相同类型的嵌套块放在一起,不同类型的嵌套块使用空行隔开
2.3 配置
terraform 配置文件以 .tf
后缀结尾,文件内主要由 provider
、resource
、data source
、variable
组成
2.3.1 Provider
terraform 通过插件机制与 Provider 进行交互,每一个 Provider 代表一个服务提供商,用 provider
关键字声明,例如以下华为云 Provider
terraform {
required_providers {
huaweicloud = {
source = "huaweicloud/huaweicloud"
version = "1.20.0"
}
}
required_version = ">= 0.13"
}
provider 可以定义多个,例如,我们需要集成多个云平台商,如下所示
首先,通过 required_providers
指明 Provider 的 registry 源 于 version,默认从 Terraform 官方仓库下载最新版本(建议构建私有 Registry 避免墙的问题)
terraform {
required_providers {
huaweicloud = {
source = "huaweicloud/huaweicloud"
version = "1.20.0"
}
alicloud = {
source = "aliyun/alicloud"
version = "1.197.0"
}
tencentcloud = {
source = "tencentcloudstack/tencentcloud"
version = ">=1.60.18"
}
}
required_version = ">= 1.1.9"
}
创建 Provider 定义
provider "huaweicloud" {
region = "cn-north-1"
# secret_id = "my-secret-id"
# secret_key = "my-secret-key"
}
provider "alicloud" {
alias = "aliyun-bj"
region = "cn-beijing"
# secret_id = "my-secret-id"
# secret_key = "my-secret-key"
}
provider "tencentcloud" {
alias = "txyun-gz"
region = "ap-guangzhou"
# secret_id = "my-secret-id"
# secret_key = "my-secret-key"
}
alias 关键字用来给 provider 设置别名,未被设置别名的 Provider 为非默认配置,以上面配置为例,创建资源未指明 Provider 时 默认采用 华为云 Provider 管理资源
此外,alias 还有一个应用场景,那就是同一个云平台提供个性化的 Provider,例如不同地域
provider "huaweicloud" {
region = "cn-north-1"
}
provider "huaweicloud" {
alias = "guangzhou"
region = "cn-south-1"
}
上例中声明了 北京(cn-north-1) 和 广州(cn-south-1) 的华为云 provider,并对广州地区的 provider 增加了别名(guangzhou),在创建资源时,我们就可以利用到 alias 来加以区分
resource "huaweicloud_vpc" "example" {
provider = huaweicloud.guangzhou
name = "terraform_vpc"
cidr = "192.168.0.0/16"
}
当然,各大云厂商都支持在 Resource 中指定 region 参数,以此实现在不同的地区创建资源,相比于 alias + provider
这种方式更灵活简单
provider "huaweicloud" {
region = "cn-north-1"
...
}
resource "huaweicloud_vpc" "example" {
region = "cn-south-1"
name = "terraform_vpc"
cidr = "192.168.0.0/16"
}
2.3.2 Resource
Resource 是 Teraform 中最重要的关键字,用来定义各类 “资源”,包括:ECS、VPC、DNS 等等
// VPC 网络定义
resource "alicloud_vpc" "vpc" {
vpc_name = "tf_vpc_demo1"
cidr_block = "172.16.0.0/12"
}
// switch 交换机
resource "alicloud_vswitch" "vsw" {
// 参数资源引用:<resource type>.<name>.<attribute>
vpc_id = alicloud_vpc.vpc.id
cidr_block = "172.16.0.0/21"
zone_id = "cn-beijing-b"
}
资源之间可以通过 “关系型资源” 进行关联,例如:
resource "alicloud_instance" "ecs_instance" {
// 声明 ecs 资源
}
resource "alicloud_eip_address" "eip" {
// 声明 ecs 资源
}
resource "alicloud_eip_association" "eip_asso" {
// 通过 alicloud_eip_association 为 ecs 与 eip 资源创建关联关系,实现为 ecs 分配 eip 功能
allocation_id = alicloud_eip_address.eip.id
instance_id = alicloud_instance.ecs_instance.id
}
资源引用语法:<资源类型>.<名称>.<属性>
,以华为云为例,假设我们创建好一台 名称为 myinstance 的 ecs,获取这个实例属性的方法如下:
# 实例ID
> huaweicloud_compute_instance.myinstance.id
55534eaa-533a-419d-9b40-ec427ea7195a
# 实例安全组
> huaweicloud_compute_instance.myinstance.security_groups
["default", "internet"]
# 实例第一个网卡的IP地址
> huaweicloud_compute_instance.myinstance.network[0].fixed_ip_v4
192.168.0.245
# 实例所有网卡的IP地址
huaweicloud_compute_instance.myinstance.network[*].fixed_ip_v4
["192.168.0.24", "192.168.10.24"]
# 标签key的值
> huaweicloud_compute_instance.myinstance.tags["key"]
value
2.3.3 Data Source
Data Source 可以理解为一种特殊的 Resource,通过 data 关键字声明,用于查询 Provider 提供的 Resource 资源信息,例如:我们打算创建一台 ECS,不过我们暂时不知道镜像的完整名称是什么,这时我们就可以使用 Data Source 功能来查询所有镜像的信息,输出到 “控制台“ 或 “文件”中
如下所示,我们要查询名称为 “Ubuntu 18.04 server 64bit” 的镜像,查询结果保存至 myimage 中
// 查询符合条件的镜像
// 数据类型 名称
data "alicloud_images" "images_ds" {
# 官方镜像
owners = "system"
# 正则表达式 匹配 名称符合规则的镜像
name_regex = "^centos_7"
# 64 位
architecture = "x86_64"
# 查询结果输出到文件中,data 需要自己手动创建
output_file = "data/images.json"
}
定义完 data 查询后,有两种方式查看
通过文件查看,执行
tf plan
,此时不出意外会生成data/images.json
[ { "architecture": "x86_64", "creation_time": "2022-09-14T09:10:32Z", "description": "", "disk_device_mappings": null, "id": "centos_7_9_uefi_x64_20G_scc_20220906.vhd", "image_id": "centos_7_9_uefi_x64_20G_scc_20220906.vhd", "image_owner_alias": "system", "image_version": "", "is_copied": false, "is_self_shared": "", "is_subscribed": false, "is_support_io_optimized": true, "name": "centos_7_9_uefi_x64_20G_scc_20220906.vhd", "os_name": "CentOS 7.9 64位 SCC版", "os_name_en": "CentOS 7.9 64 bit for SCC", "os_type": "linux", "platform": "CentOS", "product_code": "", "progress": "100%", "size": 20, "state": "Available", "status": "Available", "tags": null, "usage": "instance" }, # ...
通过终端查看,创建 output 定义
# 控制体打印所有匹配到的 images output "image_name" { # 引用 data 查询结果,输出结果列表 value = data.alicloud_images.images_ds.images }
通过以上两种任意一种都可以获取到我们想要的镜像名称(ID),这时我们就可以在 ECS 资源定义中指明所要用的镜像,例如:
resource "alicloud_instance" "default" {
// 使用 data 查询结果中的第一个镜像
image_id = "centos_7_9_uefi_x64_20G_scc_20220906.vhd"
# ...
}
又或者使用 data 结果引用
resource "alicloud_instance" "default" {
// 使用 data 查询结果中的第一个镜像
// data.<数据类型>.<名称>.<属性>
image_id = data.alicloud_images.images_ds.images[0].id
# ...
}
2.3.4 Variable
详见 变量
2.3.5 Modules
详见 Terrform 模块
2.3.6 Backend
详见 Terrform 模块
2.3.7 Metadata
详见 Terrform 元参数