Terraform state管理 — Stateファイルの役割と安全な運用

1. 概要

この記事では、以下の内容を解説します。

  • Terraform Stateファイル(terraform.tfstate)の役割と構造
  • StateとAWSの実際のリソースの関係
  • Stateが壊れたときのリスクと対処法
  • terraform stateコマンドでStateを操作する方法
  • Stateファイルにおけるsensitiveデータのリスクと対策
  • State Lockによる同時実行防止の仕組み

Terraform Stateは「Terraformが管理するインフラの現在の状態を記録したファイル」です。Stateがなければ、Terraformはどのリソースを作成済みなのかを判断できません。Stateの正しい理解と管理は、Terraformを安全に運用するうえで最も重要な知識の一つです。


2. Stateとは何か

Terraformはコードを書いたあと、以下の流れでインフラを管理します。

1. HCLコード(.tf)  ← 「あるべき姿」を定義
2. State(.tfstate) ← 「前回applyした状態」を記録
3. AWSの実際のリソース ← 「現在の実態」

terraform plan = コード vs State を比較して差分を計算
terraform apply = 差分をAWSに適用してStateを更新

StateがないとTerraformは「何を作ったか」を知る方法がないため、毎回すべてのリソースを再作成しようとします。

Stateファイルの場所

バックエンド保存場所
local(デフォルト)プロジェクトディレクトリのterraform.tfstate
S3バックエンド指定したS3バケット + key
Terraform CloudTerraform Cloudのサーバー

チーム開発では必ずS3などのリモートバックエンドを使います(詳細は「backendの設定方法」参照)。


3. Stateファイルの構造

terraform.tfstateはJSON形式のファイルです。実際の中身を見てみます。

{
  "version": 4,
  "terraform_version": "1.9.0",
  "serial": 5,
  "lineage": "a1b2c3d4-...",
  "outputs": {
    "instance_id": {
      "value": "i-0abc1234def56789",
      "type": "string"
    }
  },
  "resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "web",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "id": "i-0abc1234def56789",
            "ami": "ami-0123456789abcdef0",
            "instance_type": "t3.micro",
            "tags": {
              "Name": "web",
              "Environment": "dev"
            }
            // ... すべての属性が記録される
          }
        }
      ]
    }
  ]
}

⚠️ 重要: Stateファイルにはすべてのリソース属性が平文で記録されます。sensitive = trueのoutputやパスワードも例外ではありません。Stateファイルをバージョン管理(Git)にコミットしないでください。


4. Stateとリソースの対応関係

TerraformはStaeteのresourcesセクションでリソースを管理します。リソースのアドレス(aws_instance.webなど)がStateのキーになります。

Stateのリソースアドレス

# 通常のリソース
aws_instance.web

# countを使った場合
aws_instance.servers[0]
aws_instance.servers[1]

# for_eachを使った場合
aws_instance.servers["web"]
aws_instance.servers["app"]

# モジュール内のリソース
module.vpc.aws_vpc.main
module.ec2.aws_instance.app

5. terraform stateコマンド

Stateを直接操作するコマンドです。通常の運用では使いませんが、リソースのインポートや修正作業で使います。

state list — Stateのリソース一覧

# Stateで管理しているリソースをすべて表示
terraform state list

# 出力例
aws_instance.web
aws_security_group.main
module.vpc.aws_vpc.main
module.vpc.aws_subnet.public["ap-northeast-1a"]

state show — リソースの詳細を表示

# 特定リソースの全属性を表示
terraform state show aws_instance.web

# 出力例
# aws_instance.web:
resource "aws_instance" "web" {
    ami                    = "ami-0123456789abcdef0"
    id                     = "i-0abc1234def56789"
    instance_type          = "t3.micro"
    private_ip             = "10.0.1.10"
    tags                   = {
        "Environment" = "dev"
        "Name"        = "web"
    }
    # ...
}

state rm — StateからリソースをState管理から外す

# リソースをStateから削除(AWSのリソース自体は消えない)
terraform state rm aws_instance.web

# for_eachリソースの一部を外す
terraform state rm 'aws_instance.servers["web"]'

⚠️ 用途: Terraform管理外に移したいとき(手動管理に戻すなど)に使います。state rm後にそのリソースが.tfコードに残っていると、次のplanで「新規作成」と判断されます。

state mv — State内でリソースアドレスを変更

# リソースの名前変更(コードを変えたときにStateを合わせる)
terraform state mv aws_instance.old aws_instance.new

# モジュール間の移動
terraform state mv aws_instance.web module.ec2.aws_instance.web

💡 注意: Terraform 1.1以降ではstate mvよりmovedブロックを使うことが推奨されています。movedブロックはコードで宣言的に管理できるため、チームでの作業に適しています。

state pull / push — Stateを直接取得・更新

# リモートバックエンドのStateをローカルに取得
terraform state pull > backup.tfstate

# ローカルのStateをリモートバックエンドに強制アップロード(危険)
# terraform state push backup.tfstate  ← 通常は使わない

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

手動で作成したリソースをTerraform管理下に移行するにはterraform importを使います。

# HCLコードに定義したリソースに既存のAWSリソースをインポート
terraform import aws_instance.web i-0abc1234def56789

手順:

# ステップ1: まずHCLコードにリソースを定義する
resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  tags = {
    Name        = "web"
    Environment = "prod"
    ManagedBy   = "terraform"
  }
}
# ステップ2: 既存のEC2インスタンスIDを指定してインポート
terraform import aws_instance.web i-0abc1234def56789

# ステップ3: planを実行して差分がないか確認
terraform plan
# → HCLコードと実際のリソースが一致していれば "No changes"

7. Stateに関するリスクと対策

リスク1: Stateファイルのセキュリティ

⚠️ Stateには以下の機密情報が平文で含まれる可能性があります:
- RDSのマスターパスワード
- IAMアクセスキー
- random_passwordで生成したパスワード
- sensitive = true のoutput値

対策:

  • Gitの.gitignore<em>.tfstate</em>.tfstate.backupを追加する
  • S3バックエンドで保存時暗号化(encrypt = true)を有効にする
  • S3バケットのパブリックアクセスをブロックする
  • IAMでStateファイルへのアクセスを最小権限に制限する

リスク2: Stateの破損・消失

⚠️ Stateが消えると、TerraformはAWSのリソースを「存在しない」と判断して
   次のapply時に再作成しようとします(データが消える危険があります)。

対策:

  • S3バックエンドでバージョニングを有効にする(以前のStateに戻せる)
  • 作業前にterraform state pull > backup.tfstateでバックアップを取る

リスク3: 同時実行によるState破損

⚠️ 2人が同時にapplyするとStateファイルが上書きされて壊れる可能性があります。

対策:

  • DynamoDBによるState Lockを必ず設定する(S3バックエンドのdynamodb_table指定)
  • CIからのapplyを一本化して並行実行を防ぐ

8. 関連記事


9. まとめ

  • Stateは「前回applyした状態」を記録するJSON。Terraformはこれとコードの差分を計算する
  • ローカル保存はチーム開発に不向き。S3バックエンド + DynamoDB Lockが定番
  • Stateには機密情報が平文で含まれる。Gitにコミット禁止・S3暗号化が必須
  • terraform state list/show/rm/mvでStateを直接操作できる(通常作業では使わない)
  • 既存リソースのインポートはterraform import。Terraform 1.5以降はimportブロックも使える
  • Stateの消失・破損に備えてS3バージョニングと作業前のバックアップを徹底する

動作確認バージョン: Terraform >= 1.9 / AWS Provider ~> 5.0 対象リージョン: ap-northeast-1(東京) 公式ドキュメント: https://developer.hashicorp.com/terraform/language/state