この記事は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
コマンドを実行した単位です。
dev環境, stg環境, 本番環境ごとにterraform apply
しているならば、
tfstateファイルはdev.tfstate, stg.tfstate, prd.tfstateの3つになります。
他にも、リソースの種類ごとにコマンド実行単位を分ける方法があると思います。
これなら変更頻度が高そうな、EC2, ECSなどのアプリレイヤに近いリソース更新と、
あまりバシバシ変えたくないVPCなどのネットワークレイヤのリソース更新を完全に分けることができますね。
私が関わったプロジェクトだと、環境で分けて、かつ、リソースでも分ける方式が多かったです。
tfstateファイルを保存しているs3を見てみるとこんなかんじ。
バケット自体を環境ごとに分けて、リソースごとにtfstateファイルがあります。
(simplyというのは今私が関わってるプロジェクトの名前です )
tfstateファイルが分かれていると、前述の通り、変更の範囲を限定することができるので安全性や実行時間の短縮などのメリットが得られます。
しかしメリットばかりではなく、デメリットもあります。
Terraformのディレクトリ構成を変える必要に迫られた際に、tfstateファイルの作成単位も変わるケースがあり、
旧tfstateファイルが管理するリソースを新tfstateファイルに移動したり、改めてimportしなおしたりする必要があるわけですが、この作業が複雑になりがちです。
このときにtfstateの構造を知っておかないと効率的に作業をすすめられません。
tfstateの構造
さて、tfstateの実際の内容は以下のようなjsonファイルです。
ECSのserviceの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ファイルのversionは4になります。
version4の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
以下に追加されていきます。
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/
まとめ
tfstateを調べた経緯はTerraformのディレクトリ構成を変えたいという強い気持ちがあったからでした。
ディレクトリ構成を変更するためには、tfstateを編集するためのterraform state
コマンドや、既存リソースをtfstateに取り込むterraform import
コマンドを実行する必要がありますが、tfstateの概要をさらっとでも知っておくことで、作業の効率化を考えたりできて良かったですね。
ただ、それでもtfstateを自分で編集していく作業は苦行です。
terraformerなどのツールが進化していくことを祈ります(module対応してくれ)