メインコンテンツへスキップ
見出し画像

TerraformのState(tfstate)完全解説|チーム開発で絶対に知っておくべき仕組み

    Terraformを使い始めた人が最初に「よくわからない」と感じるのが State(状態管理) です

    しかし、ここを理解しないとチーム開発でとんでもない事故が起きます。逆に理解できれば、Terraformへの不安がほぼなくなります。しっかり押さえましょう。


    Stateとは何か

    Terraformはインフラの「現在の状態」をファイルで管理しています。これが tfstate(terraform.tfstate) ファイルです。

    // terraform.tfstate の中身(例)
    {
      "version": 4,
      "terraform_version": "1.9.8",
      "resources": [
        {
          "type": "aws_s3_bucket",
          "name": "my_bucket",
          "instances": [
            {
              "attributes": {
                "id": "my-example-bucket",
                "bucket": "my-example-bucket",
                "region": "ap-northeast-1",
                ...
              }
            }
          ]
        }
      ]
    }

    Terraformは terraform apply を実行するたびに、この「前回の状態(tfstate)」と「コードの定義」を比較して、差分だけを実行します。

    コード(.tf)       tfstate(前回の状態)
         ↓                    ↓
            差分を計算
                 ↓
           実際のAWSに適用

    なぜStateが必要なのか

    「AWSに直接問い合わせればいいのでは?」と思うかもしれません。

    しかし問題があります。

    ① リソースのマッピングが複雑 TerraformのコードIDとAWS側のリソースIDは一致しません。Stateがその対応表になっています。

    ② パフォーマンス 毎回すべてのAWSリソースに問い合わせると、数百・数千のリソースがある環境では実行に何分もかかります。

    ③ 依存関係の追跡 どのリソースがどのリソースに依存しているかをStateが記録しています。


    ローカルStateの問題点

    terraform apply を実行すると、デフォルトでは terraform.tfstate がローカルに作成されます。

    これには大きな問題があります。

    【ローカルStateの問題】
    開発者A: terraform apply → tfstateがAのPCに保存
    開発者B: terraform apply → BのPCにあるtfstateは古い!
                 ↓
          二重実行で環境が壊れる

    また、tfstateにはAWSリソースの情報(場合によってはパスワードなど機密情報)が入るため、Gitにコミットすることもできません。


    Remote State:S3でStateを管理する

    チーム開発では Remote State を使います。tfstateをS3に保存して全員で共有する方法です。

    S3バケットとDynamoDBテーブルを作る

    まずS3バケット(tfstate保存用)とDynamoDBテーブル(ロック用)を作成します。

    # bootstrap/main.tf
    # このリソース自体はコンソールで作るか、一度だけ別途applyする
    
    resource "aws_s3_bucket" "tfstate" {
      bucket = "my-tfstate-bucket-xxxxxxx"  # 世界でユニークな名前
    }
    
    resource "aws_s3_bucket_versioning" "tfstate" {
      bucket = aws_s3_bucket.tfstate.id
      versioning_configuration {
        status = "Enabled"  # バージョニングは必須!
      }
    }
    
    resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
      bucket = aws_s3_bucket.tfstate.id
      rule {
        apply_server_side_encryption_by_default {
          sse_algorithm = "AES256"
        }
      }
    }
    
    # 状態ロック用のDynamoDBテーブル
    resource "aws_dynamodb_table" "tfstate_lock" {
      name         = "terraform-state-lock"
      billing_mode = "PAY_PER_REQUEST"
      hash_key     = "LockID"
    
      attribute {
        name = "LockID"
        type = "S"
      }
    }

    backendの設定

    terraform {} ブロックに backend "s3" を追加します。

    terraform {
      required_version = ">= 1.9.0"
    
      backend "s3" {
        bucket         = "my-tfstate-bucket-xxxxxxx"
        key            = "production/terraform.tfstate"  # 保存先のパス
        region         = "ap-northeast-1"
        dynamodb_table = "terraform-state-lock"  # ロック用テーブル
        encrypt        = true
      }
    
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
      }
    }

    terraform init を実行するとS3に切り替わります。


    State Lockとは

    DynamoDBを使った State Lock は非常に重要な仕組みです。

    複数人が同時に terraform apply を実行すると、tfstateが壊れます。State Lockはそれを防ぎます。

    開発者A: terraform apply開始 → DynamoDBにロック取得
    開発者B: terraform apply開始 → ロック中!待機 or エラー
    開発者A: apply完了 → ロック解除
    開発者B: ロック取得 → apply開始

    Stateに関するコマンド

    state list:管理中のリソース一覧

    terraform state list
    
    # 出力例
    aws_instance.web
    aws_s3_bucket.my_bucket
    aws_vpc.main

    state show:リソースの詳細を確認

    terraform state show aws_s3_bucket.my_bucket

    state mv:リソースの名前変更

    コード内でリソース名を変えたとき、そのままだと「削除して再作成」になってしまいます。state mv を使うと再作成せず名前だけ変更できます。

    # コード上で aws_s3_bucket.old → aws_s3_bucket.new に変えた場合
    terraform state mv aws_s3_bucket.old aws_s3_bucket.new

    state rm:管理から外す

    Terraformの管理から外したいリソース(削除はしない)がある場合:

    terraform state rm aws_s3_bucket.my_bucket

    terraform import:既存リソースをState管理下に取り込む

    コンソールで作ったリソースをTerraformで管理し始めたい場合は import を使います。

    # Terraform 1.5以降はimportブロックで書ける
    import {
      to = aws_s3_bucket.existing
      id = "existing-bucket-name"
    }

    または古い方法:

    terraform import aws_s3_bucket.existing existing-bucket-name

    tfstateの注意点まとめ

    ✅ DO
    - S3 + DynamoDBでRemote State管理する
    - S3バケットのバージョニングを有効にする(誤操作からの復元用)
    - S3バケットの暗号化を有効にする
    
    ❌ DON'T
    - tfstateをGitにコミットしない(機密情報が含まれる)
    - tfstateを手動で編集しない
    - ローカルのtfstateをチームで使わない

    まとめ

    • tfstateはTerraformがインフラの「現在の状態」を記録するファイル

    • ローカル保存はチーム開発では使えない

    • S3 + DynamoDBでRemote State管理するのがベストプラクティス

    • State Lockで同時実行を防ぐ

    • terraform state コマンドでStateを操作できる

    次の記事ではTerraformの ディレクトリ構成 のベストプラクティスを解説します。小規模プロジェクトから大規模まで、どう整理すればいいかを具体的に見ていきます。

    あなたへのおすすめ