1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TerraformのStateはなぜGitで管理しない? 「設計図」と「運用DB」の違いから理解する

1
Posted at

はじめに

TerraformコードはGitで管理するのに、terraform.tfstate はS3などのBackendに置く。
最初は「StateにSecretが入っていなければ、Gitで管理してもよいのでは?」と疑問でした。

調べていくと、StateをGitに置かない理由は機密性だけではありません。
StateはTerraformが実行を続けるために、一貫した最新版を読み書きする管理データでした。

TL;DR

  • Stateは、Terraform上のresourceと実物の対応関係などを保持する
  • JSONを直接編集すると、AWSより先にTerraformの管理認識が変わる
  • Stateは最新版共有と排他制御が必要なので、GitよりState管理向けのBackendと相性がよい

1. Stateは「AWSの現在状態を保存したファイル」だけではない

terraform.tfstate という名前を見ると、
「AWSの現在状態をJSONに保存しているファイル」と捉えたくなります。

実際、Stateにはresourceの属性値も保存されます。
ただ、Stateが必要な理由を理解するうえで先に知りたかったのは、
TerraformのConfigと実世界を結びつける役割でした。

image.png

1-1. aws_instance.webi-0123... は別の名前

TerraformのConfigには、たとえば次のように書きます。

resource "aws_instance" "web" {
  ami           = "ami-xxxxxxxx"
  instance_type = "t3.micro"
}

Terraform上では、このresourceを aws_instance.web として扱えます。

一方、AWS上にできるEC2には、

i-0123456789abcdef0

のようなinstance IDがあります。

AWS側に、
「このEC2はTerraformの aws_instance.web です」
という対応表が自動で存在するわけではありません。

そこでTerraformはStateに、

aws_instance.web
        ↕
i-0123456789abcdef0

というbindingを保持します。

HashiCorpもStateの目的を説明する中で、
Terraform configとreal worldを対応付けるために何らかのdatabaseが必要であり、
Stateがresourceとremote objectのmappingを持つと説明しています。
Purpose of Terraform State

1-2. mapping以外の情報も入っている

Stateにはbindingだけでなく、
resource dependencyなどのmetadataやresource attributeのcacheも保存されます。

たとえばConfigからresourceを削除した後でも、
TerraformはStateに残った情報を使って、
「どのremote objectを削除する必要があるか」
「どの順番で削除するか」
を判断できます。

なので、

State = AWSの現在状態を丸ごとコピーしたもの

と考えると少し狭いです。

自分の中では、
「Terraformが管理を継続するための運用DB」
と考えると一番整理しやすくなりました。

ここでいう「DB」は理解のための比喩です。RDBのようなデータベース製品という意味ではありません。

2. Stateを直接編集すると、先にTerraformの認識が変わる

StateがTerraformの管理情報なら、
「JSONファイルだからGitで編集してもよい」とは考えにくくなります。

ただし、Stateを書き換えた瞬間にEC2が消えるわけではありません。
ここは自分も最初に混乱したところでした。

image.png

2-1. Stateを書き換えても、AWSは即座には変わらない

たとえばStateに、

aws_instance.web ↔ i-0123456789abcdef0

というbindingがあるとします。

これを人間がJSONから手で消しても、
AWS上の i-0123456789abcdef0 はその場では残っています。

変わるのは、Terraform側が持っている管理情報です。

Configには aws_instance.web が存在する。
しかしStateには、それと結びつくremote objectがいない。

この状態で次に terraform plan を実行すると、
Terraformは変更後のStateを入力として判断します。

状況によっては、

+ aws_instance.web will be created

のように、新しいresourceを作るPlanが出ることもあります。

そのPlanを apply すれば、そこで初めて実インフラにも影響します。

つまり、

State手編集
  ↓
Terraformの管理認識が変わる
  ↓
planの判断が変わる
  ↓
applyで実物へ反映される

という順番です。

2-2. 直接JSONを編集せず、Terraformが用意した仕組みを使う

HashiCorpはStateについて、
JSON text fileではあるものの直接編集しないよう明記しています。
State | Terraform

Stateを変更する必要がある場合も、JSONを手で書き換えるのではなく、
Terraformが提供するCLIやconfiguration上の仕組みを使います。

確認だけなら、たとえば次のコマンドがあります。

terraform state list
terraform state show aws_instance.web

一方、resourceをTerraform管理から外したい場合、
現在のHashiCorpドキュメントでは terraform state rm より
removed blockを使う方法が推奨されています。

このときは、対象のresource blockを残したまま横に追加するのではなく、
既存のresource blockをremoved blockへ置き換えます

removed {
  from = aws_instance.web

  lifecycle {
    destroy = false
  }
}

removed blockなら通常の plan / apply workflowの中で
「Stateから外す変更」を確認してから適用できます。
HashiCorpも、結果をpreviewできるため
terraform state rm より安全な方法として推奨しています。
Remove a resource from Terraform state

resourceやmoduleのaddressを変更するリファクタリングでも、
moved blockというconfiguration上の仕組みがあります。
Refactor modules

terraform state rmterraform state mv 自体が間違いなわけではありません。
CLIが必要な場面もあります。

ここで持ち帰りたいのは、
Stateを変更する必要があっても、State JSONを直接編集するのではなく、
Terraformが提供する操作経路を使う

という境界です。

3. planapply の中でStateはどう扱われるのか

Stateを「Terraformが使う管理データ」と捉えると、
普段の planapply でいつ読まれ、いつ更新されるのかが気になります。

ここを時系列で見ると、
StateをGitの成果物として扱わない理由がさらに分かりやすくなりました。

image.png

3-1. plan はStateを手がかりに現在状態を確認する

通常の terraform plan では、
TerraformはStateにあるbindingを使って管理対象を特定します。

たとえばStateに、

aws_instance.web ↔ i-0123456789abcdef0

があれば、Provider経由でそのEC2のcurrent stateを読み取ります。

そのうえでConfigと照合し、
「変更なし」「update」「replace」「destroy」などの変更案を作ります。

Stateだけを見てPlanを決めるわけでも、
Configだけを見てPlanを決めるわけでもありません。

3-2. apply が終わるとStateも更新される

terraform apply はPlanに基づいてremote systemを変更します。

保存済みPlanを渡さない通常の terraform apply では、
Terraformはその場で新しいexecution planを作ってから実行します。
saved planを渡した場合は、そのPlanを実行します。

変更が完了すると、
Terraformはその結果をStateへ反映します。

prior State
    ↓
plan
    ↓
apply
    ↓
実インフラ変更
    ↓
new State

このStateが、また次回のTerraform実行で使われます。

なのでStateは、
「人間がレビューして完成させたらcommitするファイル」というより、
Terraformのrunとrunの間をつなぐ管理データ
と見る方が自然でした。

4. 2人が同時に apply したらどうなるのか

Stateをチームで共有するなら、
AさんとBさんが同時にTerraformを実行する可能性があります。

ここで必要になるのがState lockingです。

4-1. lockは「Stateを書き込む瞬間だけ」ではない

Backendがlockingをサポートしている場合、
TerraformはStateを書き得るoperationでlockを取得します。

HashiCorpの説明でも、
state lockingはstateへwriteし得るすべてのoperationで自動的に行われ、
lock取得に失敗した場合は処理を継続しません。
State: Locking

たとえばAさんが apply を開始してlockを取っている間に、
Bさんも同じStateへ apply しようとします。

Bさんはlockを取得できないため、
そのまま競合する処理を進められません。

-lock-timeout を指定すれば、
指定時間はlock取得をretryできます。

terraform apply -lock-timeout=30s

それでも取得できなければエラーになります。
Aさんの処理が終わった後に改めて実行すれば、
その時点の最新Stateから処理を始められます。

4-2. Git conflictで後から解決するのでは遅い

Gitなら、AさんとBさんが同じファイルを変更しても、
pushやmergeの段階でconflictを解消できます。

Stateは性質が違います。

Gitで管理するコード Terraform State
複数branchで変更できる 同じ管理対象では一貫した最新版が必要
diffを見て人間がmergeする Terraformの管理状態を人間がmergeするのは危険
変更後にconflictを解消できる 競合するoperationを先に止めたい

もしAさんとBさんがそれぞれ古いStateを持って、
両方ともAWSへ変更を加えた後に、

「StateのJSONがconflictしたのでmergeしよう」

となっても遅いです。

すでにremote system側の変更が走っているからです。

Terraform Stateでは、
競合した後にファイルをmergeすることより、
競合するwriterを同時に進めないこと

が必要になります。

4-3. 保存したPlanも永遠に使えるわけではない

terraform plan -out=tfplan で保存したPlanも、
Stateと無関係な静的ファイルではありません。

Planは作成時点のStateを前提にしています。

Aさんが別のrunを apply してStateが変わると、
それ以前のStateを前提にしたsaved planはstaleになり得ます。

ここでは内部の seriallineage までは覚えなくても、
PlanとStateはセットで整合している必要がある
と分かれば十分だと思います。

5. GitとBackendの境界は「更新主体」だけでは決まらない

ここまでの話を、タイトルの言葉に戻して整理します。

Config側は「インフラをこうしたい」と人間が宣言し、
レビューや変更履歴を残す設計図に近いデータです。
一方のStateは、Terraformがrunをまたいで管理状態を引き継ぐ運用DBに近いデータです。

ただし、
「人間が編集するものはGit、Terraformが編集するものはBackend」
と覚えるだけでは .terraform.lock.hcl が説明できません。

image.png

.terraform.lock.hclterraform init などでTerraformが作成・更新しますが、
HashiCorpはGitへcommitすることを推奨しています。

一方、terraform.tfstate はcommitしないよう案内しています。
Terraform Style Guide

なので境界は、
「誰がファイルを書き換えるか」だけではありません。

自分は次の2つで考えると整理できました。

対象 欲しい管理特性 管理先
main.tf review・履歴・branchで変更したい Git
variables.tf review・履歴を残したい Git
Backend設定 保存先設定を共有・reviewしたい Git
.terraform.lock.hcl Provider選択を再現・reviewしたい Git
terraform.tfstate 一貫した最新版を共有したい Backend
terraform.tfstate 競合する書き込みを排他したい Backend
Secret実値 アクセスを制御したい Secret管理基盤

StateについてHashiCorpも、
lockingやsecure access controlを持たないversion control systemへ保存しないよう勧めています。
State | Terraform

そして、冒頭で触れたSecretもGitにStateを置かない理由の一つです。

Stateにはpasswordやprivate keyなどのsensitive dataが保存される場合があります。
sensitive = true にして画面表示を隠しても、
通常はその値がStateから消えるわけではありません。

HashiCorpのStyle Guideでも、
Stateにはsecretなどのsensitive informationが含まれ得ることと、
VCSにはState lockingがないことの両方を理由に
terraform.tfstate をcommit対象から外しています。
Terraform Style Guide

つまり、

Secretが入る可能性があるからGitを避ける

も正しいです。

ただしそれだけでは、
「SecretがないStateならGitでよいのか?」
「複数人が同時にapplyしたら?」
という問題までは説明できません。

この記事でStateを運用DBとして捉えてきたのは、
機密性に加えて、最新版共有と排他制御まで同じモデルで説明するためです。

5-1. 「StateはこのS3に置く」という設定はGitでよい

たとえば次のようなBackend設定です。

terraform {
  backend "s3" {
    bucket = "my-tfstate-bucket"
    key    = "production/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

これはState本体ではありません。

「Stateをどこへ保存するか」というconfigurationなので、
変更履歴を残したりreviewしたりする性質を持っています。

そのためGit管理と相性があります。

一方、S3に保存される実際の terraform.tfstate は、
Terraformがrunごとに読み書きする管理データです。

5-2. S3 Backendにしただけではlockingは有効にならない

ここは2026年時点で気をつけたいところでした。

S3 BackendはState lockingをサポートしていますが、
lockingはopt-inです。

現在の公式ドキュメントでは、
S3 lockfileを使う場合は use_lockfile = true を設定します。

terraform {
  backend "s3" {
    bucket       = "my-tfstate-bucket"
    key          = "production/terraform.tfstate"
    region       = "ap-northeast-1"
    use_lockfile = true
  }
}

use_lockfile のdefaultは false です。
また、以前よく使われていたDynamoDB-based lockingはdeprecatedになっています。
S3 backend

S3 Backendを使う理由を、

S3に置けば自動でlockingされる

と覚えるのは少し危ないです。

「共有Stateを置ける」ことと、
「lockingを有効にして競合を制御する」ことは分けて考える必要があります。

Backend configurationにaccess keyやsecret keyをハードコードする話は別です。
HashiCorpはBackendのcredentialsなどのsensitive dataを環境変数などから渡すことを推奨しています。

おわりに

StateをGitに置かない理由を、
「Secretが入る可能性があるから」だけで覚えると、
SecretがなければGitでもよさそうに見えました。

自分が一番納得できたのは、
StateをTerraformのrunとrunをつなぐ運用DBとして見ることでした。

だからStateには、
Gitのbranch・diff・mergeより、
最新版の共有・locking・アクセス制御
の方が必要になります。

「TerraformコードはGit、StateはBackend」という分け方も、
設計図はreview・履歴管理、運用DBは最新版共有・locking
と役割から見ると、ファイル名の暗記ではなく理由から理解できるようになりました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?