《Terraform 101 从入门到实际》这本小册在南瓜慢说官方网站和 GitHub 两个中央同步更新,书中的示例代码也是放在 GitHub 上,不便大家参考查看。
军书十二卷,卷卷有爷名。
为什么须要状态治理
Terraform 的次要作用是治理云平台上的资源,通过申明式的 HCL 配置来映射资源,如果云平台上没有资源则须要创立,如果有则不必。那 Terraform 要实现这个性能有多种形式。
一种是每次执行 apply 命令时都调用 API 接口检查一下近程的云资源是否与配置文件统一,如果没有则创立,如果有但不同则须要批改,如果有且雷同则不必变更。这种机制能保障云平台的资源与 HCL 配置是统一的。毛病也是非常明显的,每次都须要调用 API 去查看近程资源,效率很低,特地是当资源特地多的场景。
另一种形式是每次变更资源的时候,都会创立一个映射文件,它保留云平台资源的状态。这样每次执行 apply
命令时,只须要查看 HCL 配置与映射文件的差别即可。
Terraform 抉择的是第二种形式,通过映射文件来保留资源状态,在 Terraform 的世界里叫状态文件。Terraform 这样做是基于以下思考:
- 云平台实在状态的映射,解析状态文件即能够晓得真实情况。
- 元数据存储,如资源之间的依赖关系,须要通过依赖关系来晓得创立或销毁程序。
- 晋升性能,特地是在大规模云平台上,屡次调用 API 去查问资源状态是很费时的。
- 同步状态,通过近程状态文件来同步状态,这也是 Terraform 最佳的实际。
讲到这里,曾经答复了之前在第一章留下的思考题:
如果再次执行 apply 会不会再次创立一个文件呢?还是创立失败,因为文件已存在?为什么?
答案:不会创立,因为通过状态文件记录了变更,Terraform 判断不再须要创立了。
状态治理的示例
为了更多注意力放在状态治理上,咱们还是应用最简略的例子local_file
,具体代码如下:
resource "local_file" "terraform-introduction" {
content = "https://www.pkslow.com"
filename = "${path.root}/terraform-guides-by-pkslow.txt"
}
咱们以实际操作及景象来解说状态文件的作用和工作原理:
操作 | 景象及阐明 |
---|---|
terraform apply | 生成资源:第一次生成 |
terraform apply | 没有变动:状态文件生成,不须要再创立 |
terraform destroy | 删除资源:依据状态文件的内容删除 |
terraform apply | 生成资源:状态显示没有资源,再次生成 |
删除状态文件 | 没有变动 |
terraform apply | 生成资源:没有状态文件,间接生成资源和状态文件(插件做了容错解决,已存在也会新生成笼罩) |
删除状态文件 | 没有变动 |
terraform destroy | 无奈删除资源,没有资源存在的状态 |
咱们始终在讲状态文件,咱们先来看一下它的真面目。首先它的默认文件名是 terraform.tfstate
,默认会放在当前目录下。它是以json
格局存储的信息,示例中的内容如下:
{
"version": 4,
"terraform_version": "1.0.11",
"serial": 1,
"lineage": "acb408bb-2a95-65fd-02e6-c23487f7a3f6",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "local_file",
"name": "test-file",
"provider": "provider[\"registry.terraform.io/hashicorp/local\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"content": "https://www.pkslow.com",
"content_base64": null,
"directory_permission": "0777",
"file_permission": "0777",
"filename": "./terraform-guides-by-pkslow.txt",
"id": "6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1",
"sensitive_content": null,
"source": null
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
}
]
}
能够看到它记录了 Terraform 的版本信息,还有资源的详细信息:包含类型、名字、插件、属性等。有这些信息便可间接从状态文件里解析出具体的资源。
状态治理命令
能够通过 terraform state
做一些状态治理:
显示状态列表:
$ terraform state list
local_file.test-file
查看具体资源的状态信息:
$ terraform state show local_file.test-file
# local_file.test-file:
resource "local_file" "test-file" {
content = "https://www.pkslow.com"
directory_permission = "0777"
file_permission = "0777"
filename = "./terraform-guides-by-pkslow.txt"
id = "6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1"
}
显示以后状态信息:
$ terraform state pull
重命名:
$ terraform state mv local_file.test-file local_file.pkslow-file
Move "local_file.test-file" to "local_file.pkslow-file"
Successfully moved 1 object(s).
$ terraform state list
local_file.pkslow-file
要留神这里只是批改状态文件的名字,代码里的 HCL 并不会批改。
删除状态里的资源:
$ terraform state rm local_file.pkslow-file
Removed local_file.pkslow-file
Successfully removed 1 resource instance(s).
近程状态
状态文件默认是在本地目录上的 terraform.tfstate
文件,在团队应用中,每个人的电脑环境独立的,那么须要保障每个人以后的状态文件都是最新且与事实资源实在对应,几乎是天方夜谭。而状态不统一所带的劫难也是极其可怕的。所以,状态文件最好是要存储在一个独立的大家可独特拜访的地位。对于状态的治理的配置,Terraform 称之为Backends
。
Backend
是两种模式,别离是 local
和remote
。local
模式很好了解,就是应用本地门路来存储状态文件。配置示例如下:
terraform {
backend "local" {path = "pkslow.tfstate"}
}
通过这样配置后,不再应用默认的 terraform.tfstate
文件,而是应用自定义的文件名pkslow.tfstate
。
对于 remote
模式,则有多种配置形式,Terraform 反对的有:
- s3
- gcs
- oss
- etcd
- pg
- http
- kubernetes
等,能满足支流云平台的需要。每一个配置能够参考官网,在本地我采纳数据库 postgresql 的形式,让大家都能疾速试验。
我通过 Docker 的形式启动 PostgreSQL,命令如下:
$ docker run -itd \
--name terraform-postgres \
-e POSTGRES_DB=terraform \
-e POSTGRES_USER=pkslow \
-e POSTGRES_PASSWORD=pkslow \
-p 5432:5432 \
postgres:13
在 terraform
块中配置backend
,这里指定数据库连贯信息即可,更多参数请参考:https://www.terraform.io/lang…
terraform {
backend "pg" {conn_str = "postgres://pkslow:pkslow@localhost:5432/terraform?sslmode=disable"}
}
当然,把敏感信息间接放在代码中并不适合,能够间接在命令行中传入参数:
terraform init -backend-config="conn_str=postgres://pkslow:pkslow@localhost:5432/terraform?sslmode=disable"
执行 init 和 apply 之后,连贯数据库查看,会创立一个叫 terraform_remote_state
的 Schema,在该 Schema 下有一张 states 表来存储对应的状态信息,如下:
表中字段 name 是 namespace,而 data 是具体的状态信息,如下:
{
"version": 4,
"terraform_version": "1.0.11",
"serial": 0,
"lineage": "de390d13-d0e0-44dc-8738-d95b6d8f1868",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "local_file",
"name": "test-file",
"provider": "provider[\"registry.terraform.io/hashicorp/local\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"content": "https://www.pkslow.com",
"content_base64": null,
"directory_permission": "0777",
"file_permission": "0777",
"filename": "./terraform-guides-by-pkslow.txt",
"id": "6db7ad1bbf57df0c859cd5fc62ff5408515b5fc1",
"sensitive_content": null,
"source": null
},
"sensitive_attributes": [],
"private": "bnVsbA=="
}
]
}
]
}
Workspace 工作区
如果咱们用 Terraform 代码生成了 dev 环境,但当初须要 uat 环境,该如何解决呢?
首先,不同环境的变量个别是不一样的,咱们须要定义各种的变量文件如 dev.tfvars
、uat.tfvars
和prod.tfvars
等。但只有各自变量是不够的,因为还有状态。状态也必须要隔离,而 Workspace
就是 Terraform 用来隔离状态的形式。默认的工作区为 default
,如果没有指定,则示意工作于default
工作区中。而当指定了工作区,状态文件就会与工作区绑定。
创立一个工作区并切换:
$ terraform workspace new pkslow
切换到已存在的工作区:
$ terraform workspace select pkslow
而当咱们处于某个工作区时,是能够获取工作区的名字的,援用为:${terraform.workspace}
,示例如下:
resource "aws_instance" "example" {count = "${terraform.workspace =="default"? 5 : 1}"
# ... other arguments
}
之前讲过默认的状态文件名为 terraform.tfstate
;而在多工作区的状况下(只有你创立了一个非默认工作区),状态文件就会存在terraform.tfstate.d
目录下。而在近程状态的状况下,也会有一个映射,Key 为工作区名,Value 个别是状态内容。
敏感数据
本地状态文件都是明文存储状态信息的,所以要爱护好本人的状态文件。对于近程状态文件,有些存储计划是反对加密的,会对敏感数据(sensitive
)进行加密。
状态锁
本地状态文件下不须要状态锁,因为只有一个人在变更。而近程状态的状况下,就可能呈现竞争了。比方一个人在 apply,而另一个人在 destroy,那就乱了。而状态锁能够确保近程状态文件只能被一个人应用。但不是所有近程状态的形式都反对锁的,个别罕用的都会反对,如 GCS、S3 等。
所以,每当咱们在执行变更时,Terraform 总会先尝试去拿锁,如果拿锁失败,就该命令失败。能够强制解锁,但要十分小心,个别只倡议在本人明确晓得平安的时候才应用,比方死锁了。
共享状态 - 数据源
既然近程状态文件是能够共享的,那状态信息也是能够共享的。这样会带来的一个益处是,即便两个根模块,也是能够共享信息的。比方咱们在根模块 A 创立了一个数据库,而根模块 B 须要用到数据库的信息如 IP,这样通过近程状态文件就能够共享给根模块 B 了。
留神这里我强调的是根模块,因为如果 A 和 B 在同一个根模块下,那就不须要通过近程状态的形式来共享状态了。
近程状态的示例:
data "terraform_remote_state" "vpc" {
backend = "remote"
config = {
organization = "hashicorp"
workspaces = {name = "vpc-prod"}
}
}
resource "aws_instance" "foo" {
# ...
subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id
}
本地状态的示例:
data "terraform_remote_state" "vpc" {
backend = "local"
config = {path = "..."}
}
resource "aws_instance" "foo" {
# ...
subnet_id = data.terraform_remote_state.vpc.outputs.subnet_id
}
要留神的是,只有根模块的输入变量能力被共享,子模块是不能被获取的。