27
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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コマンドを実行した単位です。
dev環境, stg環境, 本番環境ごとにterraform applyしているならば、
tfstateファイルはdev.tfstate, stg.tfstate, prd.tfstateの3つになります。

他にも、リソースの種類ごとにコマンド実行単位を分ける方法があると思います。
これなら変更頻度が高そうな、EC2, ECSなどのアプリレイヤに近いリソース更新と、
あまりバシバシ変えたくないVPCなどのネットワークレイヤのリソース更新を完全に分けることができますね。

私が関わったプロジェクトだと、環境で分けて、かつ、リソースでも分ける方式が多かったです。
tfstateファイルを保存しているs3を見てみるとこんなかんじ。
バケット自体を環境ごとに分けて、リソースごとにtfstateファイルがあります。

スクリーンショット 2019-12-21 14.16.49.png

(simplyというのは今私が関わってるプロジェクトの名前です :pray: )

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対応してくれ) :pray: :pray: :pray:

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
27
Help us understand the problem. What are the problem?