大致了解Terraform的tfstate
本文是OpenSaaS Studio Advent Calendar 2019第20天的文章。
首先
-
- なんとなくplanとapplyが動かせるからいいかな〜ってノリでTerraformを使ってる人々(かつての自分)のためのメモです。
-
- tfstateが分からないと、Terraformの構成を変えたい!と思ったときにリファクタリングすることもできない。
Terraformの構成を最初からうまいこと作るのって本当に難しい。。。
リファクタリングのしにくさがTerraformの課題のひとつだと思う。
因此,我将它总结起来了。
tfstate 的中文本地化表述: 状态文件
tfstate文件是由Terraform管理的资源的当前状态的表示文件。
默认情况下,tfstate文件将在本地生成,但我认为许多现场都是使用远程后端(如S3)进行管理。
tfstate的用途何在
当执行terraform plan时,它会显示当前状态和所描述定义之间的差异,
这实际上是显示了tfstate和HCL定义之间的差异。
我觉得我们应该查看实际资源而不是tfstate中的差异!
但从性能和资源依赖的角度来看,Terraform本身必须对状态进行追踪,
这一点在官方文档中有所提及。
https://www.terraform.io/docs/state/purpose.html
创建 tfstate 文件的单位
就本身而言,是执行了terraform apply命令的单位。
如果针对开发环境、测试环境和生产环境分别执行了terraform apply,
tfstate文件将变为dev.tfstate、stg.tfstate和prd.tfstate这三个文件。
我认为还有一个方法可以根据资源类型将命令执行单元分开。
这样一来,我们就能完全区分高频更改的应用层资源更新,如EC2、ECS等,和不太想频繁更改的网络层资源更新,比如VPC。
我参与的项目中,有很多采用将环境和资源分开的方式。看看保存着tfstate文件的s3,情况大致如下:
桶本身按照环境进行了划分,并且每个资源都有对应的tfstate文件。

「简而言之」是我当前参与的项目的名称。
当tfstate文件被分开时,如前所述,可以将变更范围限定在特定的部分,从而获得安全性和执行时间缩短等优势。然而,并非仅有优点,也存在着一些缺点。
当需要改变Terraform的目录结构时,也会需要改变tfstate文件的创建单位。这意味着旧的tfstate文件需要将管理的资源移动到新的tfstate文件中,或者重新导入。这个过程可能会变得复杂。
在这种情况下,如果不了解tfstate结构,就无法有效地进行工作。
tfstate文件的结构
嗯,实际的tfstate内容如下所示的JSON文件。
这是ECS服务的tfstate。省略了不必解释的部分(resources.instances以下的…部分),所以实际文件可能会有所不同。
{
"version": 4,
"terraform_version": "0.12.14",
"serial": 1,
"lineage": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"outputs": {},
"resources": [
{
"module": "module.ecs",
"mode": "managed",
"type": "aws_ecs_service",
"name": "xxx-api",
"provider": "provider.aws",
"instances": [
{
"schema_version": 0,
"attributes": {
"cluster": "arn:aws:ecs:ap-northeast-1:xxxxxxx:cluster/dev",
"desired_count": 1,
"id": "arn:aws:ecs:ap-northeast-1:xxxxxxx:service/dev-xxx-api",
"launch_type": "FARGATE",
"name": "dev-xxx-api",
"task_definition": "dev-xxx-api:1",
...
},
"dependencies": [
"module.ecs_service.xxx-api.aws_ecs_task_definition.task"
]
}
]
},
...
]
}
如果在Terraform 0.12.14上执行,tfstate文件的版本将变为4。
版本4的tfstate文件格式如下。
type stateV4 struct {
Version stateVersionV4 `json:"version"`
TerraformVersion string `json:"terraform_version"`
Serial uint64 `json:"serial"`
Lineage string `json:"lineage"`
RootOutputs map[string]outputStateV4 `json:"outputs"`
Resources []resourceStateV4 `json:"resources"`
}
实际上被管理的资源信息将被添加到resources目录下。
"module": "module.ecs",
"mode": "managed",
"type": "aws_ecs_service",
"name": "xxx-api",
"provider": "provider.aws",
"instances": [ {...}, ...]
有这样的描述,但分别表示以下的意思。
-
- module: moduleタグの名前。 module “ecs” { … }のHCL記述に対応。
-
- mode: managed or dataの値になる。managedはresourceタグで管理しているもの。dataはdataタグで管理しているもの。
-
- type: resourceタグのtype。resource “type” “name” { … }のHCL記述に対応。
-
- name: resourceタグのname。resource “type” “name” { … }のHCL記述に対応。
-
- provider: このリソースのプロバイダ。
- instances: 管理されているリソースの情報。
在Terraform中,要惟一确定一个资源,必须使用`${module}.${type}.${name}`。
顺便提一下,如果未使用module标签,则module部分本身不会包含在tfstate中。
resources下面的数据结构是代码中的这一部分。
type resourceStateV4 struct {
Module string `json:"module,omitempty"`
Mode string `json:"mode"`
Type string `json:"type"`
Name string `json:"name"`
EachMode string `json:"each,omitempty"`
ProviderConfig string `json:"provider"`
Instances []instanceObjectStateV4 `json:"instances"`
}
在上面提到的tftate文件中,并不包含EachMode,但如果使用Terraform 0.12中新增的for_each功能,该参数也会被包含进来。
https://www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each/
总结
对Terraform的目录结构进行了考虑,想要进行更改的强烈愿望是促使我查看tfstate的原因。
要更改目录结构,需要执行用于编辑tfstate的terraform state命令,或者使用terraform import命令将现有资源导入到tfstate中。但是,只要略微了解tfstate的概述,就可以考虑并提高工作效率,这真是太好了。
但是,即便如此,手动编辑tfstate仍然是一项艰巨的任务。
希望terraformer等工具能不断发展(同时支持模块化),这是我的祝愿。