Terraform stateが壊れた時の復旧手順|5つの実践的な対処法

Terraform stateファイルが壊れると、インフラストラクチャの管理状態が不整合になり、デプロイに失敗する危険があります。本記事では、実際に発生しやすい破損パターンと、それぞれの復旧手順を段階的に解説します。バックアップから復元する方法から、部分的な状態修正まで、すぐに使える対処法を5つ紹介します。

Terraform stateが壊れる主な原因

stateファイルの破損は、以下のようなシナリオで発生します。復旧方法を選ぶ前に、原因を特定することが重要です。

1. ファイルの同時編集による競合

複数の開発者が同時にterraform applyを実行すると、stateファイルが中途半端な状態で保存されることがあります。特にローカルストレージを使用している場合、ロック機構がないため危険性が高まります。

2. 不完全なアップロード/ダウンロード

ネットワーク接続が不安定な環境でS3やTerraform Cloudに対してstate操作を行うと、ファイルが途中で切断され、JSONフォーマットが破損することがあります。

3. 手動によるstateファイル編集の失敗

terraform state rmやjqコマンドでstateを直接編集する際、JSONの構文エラーやネストの誤りが発生しやすいです。

4. ストレージの障害

S3のデータ破損やディスク障害により、保存されたstateファイル自体が読み込み不可能になる場合があります。

復旧前の重要な確認事項

復旧作業を開始する前に、必ず以下を確認してください。

バックアップの有無を確認する

まず最初に、バックアップが存在するかを確認します。定期的にバックアップを取っていれば、復旧は簡単です。

# S3のバージョニング機能でバックアップを確認
aws s3api list-object-versions --bucket your-bucket-name --prefix terraform.tfstate

# Terraform Cloudの場合、UIから過去のstate履歴を確認可能

ローカルの.terraform/terraform.tfstateファイルをチェック

既にいくつかのリソースをデプロイしている場合、ローカル実行環境の古いstateコピーが残っている可能性があります。

# ローカルの古いstate確認
ls -la .terraform/terraform.tfstate*
ls -la terraform.tfstate*

復旧方法1: バックアップからの復元(最も推奨)

バックアップが存在する場合、これが最も安全で確実な復旧方法です。

S3バックアップからの復元手順

# ステップ1: 現在の壊れたstateをバックアップ
cp terraform.tfstate terraform.tfstate.broken

# ステップ2: S3から最後の正常なバージョンを取得
aws s3 cp s3://your-bucket-name/terraform.tfstate.backup ./terraform.tfstate

# ステップ3: 復元したstateの整合性を確認
terraform state list

# ステップ4: プランを実行して差分を確認
terraform plan

Terraform Cloudでの復元

Terraform Cloudを使用している場合、Web UIから状態を直接復元できます。

# Terraform Cloudの履歴APIで過去のstateを取得
curl -H "Authorization: Bearer $TF_API_TOKEN" \
  https://app.terraform.io/api/v2/workspaces/your-workspace/state-versions

# 特定のバージョンを復元
terraform state pull > current.state
# UIから以前のバージョンを"Current"に設定

復旧方法2: JSONファイルの構文エラーを修正

stateファイルが部分的に破損しており、JSONの構文エラーのみの場合、修正可能です。

破損箇所を特定する

# JSONの構文検証
python3 -m json.tool terraform.tfstate > /dev/null
# または
jq . terraform.tfstate > /dev/null

# エラー位置を確認
cat terraform.tfstate | jq . 2>&1 | head -20

よくある破損パターンと修正方法

# パターン1: 末尾の括弧や波括弧が不完全な場合
# 修正前: { "version": 4, "terraform_version": "1.0",
# 修正後: { "version": 4, "terraform_version": "1.0" }

# パターン2: ダブルクォートが閉じられていない
# jqで自動修正を試みる
jq . terraform.tfstate 2>/dev/null || echo "修正不可能"

# パターン3: 不正なエスケープシーケンス
# テキストエディタで手動確認・修正
sed -i 's/\\//g' terraform.tfstate

修正後の検証

# 構文確認後、stateが読み込み可能かテスト
terraform state list

# もしエラーが出た場合、以下でstate内容を表示
terraform state show

復旧方法3: 部分的な状態の再構築

一部のリソースだけが不整合の場合、該当リソースのみ状態を削除して再度importできます。

破損したリソースを特定する

# 現在のstate内のすべてのリソースを表示
terraform state list

# 特定のリソースの詳細情報を確認
terraform state show aws_instance.example

# JSON形式で表示(より詳細)
terraform state show -json aws_instance.example

問題のあるリソースを削除して再importする

# ステップ1: stateから特定リソースを削除
terraform state rm aws_instance.example

# ステップ2: Terraformコード(.tf)には定義が残ったまま
# ステップ3: 実際のAWSリソースIDを確認
aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId'

# ステップ4: リソースをstateに再度追加(import)
terraform import aws_instance.example i-1234567890abcdef0

# ステップ5: planで差分を確認
terraform plan

一括importの自動化スクリプト

#!/bin/bash
# 複数リソースの一括import

RESOURCES=(
  "aws_instance.web:i-1234567890abcdef0"
  "aws_instance.db:i-0987654321fedcba0"
  "aws_security_group.main:sg-12345678"
)

for resource in "${RESOURCES[@]}"; do
  tf_path="${resource%%:*}"
  aws_id="${resource##*:}"
  
  echo "Importing $tf_path with ID $aws_id"
  terraform import "$tf_path" "$aws_id"
  
  if [ $? -ne 0 ]; then
    echo "Failed to import $tf_path"
    exit 1
  fi
done

echo "All imports completed"
terraform plan

復旧方法4: リモートstateをリセット

S3やTerraform Cloudのリモートstateが完全に破損している場合、リセットして再構築する方法があります。

S3リモートstateのリセット

# ステップ1: バックエンド設定を確認
cat backend.tf

# ステップ2: 現在のlocal stateに切り替え(一時的)
terraform init -migrate-state
# プロンプトで "yes" を入力

# ステップ3: S3の破損したstateファイルを削除
aws s3 rm s3://your-bucket-name/terraform.tfstate
aws s3 rm s3://your-bucket-name/terraform.tfstate.backup

# ステップ4: 新しいlocal stateから全リソースをimport
# importコマンドで既存リソースを再度登録

# ステップ5: 再度リモートバックエンドに切り替え
terraform init
# プロンプトで "yes" を入力してlocal stateをS3にアップロード

Terraform Cloudでのロック解除とリセット

# Terraform Cloudが「ロック状態」で動けない場合
terraform force-unlock LOCK_ID

# ロックIDを確認する方法
curl -H "Authorization: Bearer $TF_API_TOKEN" \
  https://app.terraform.io/api/v2/workspaces/your-workspace/state-locks

復旧方法5: 新しいstateから出発する

既存stateが完全に復旧不可能な場合、既存リソースを全てimportして新しいstateを構築する方法があります。これは時間がかかりますが、完全な再スタートが可能です。

既存リソースの一覧取得と自動import

#!/bin/bash
# AWS EC2インスタンスをすべてimportするスクリプト例

# 既存インスタンス一覧を取得
INSTANCES=$(aws ec2 describe-instances \
  --query 'Reservations[].Instances[].[InstanceId,Tags[?Key==`Name`].Value|[0]]' \
  --output text)

# 各インスタンスをimport
echo "$INSTANCES" | while read instance_id instance_name; do
  if [ -z "$instance_id" ]; then
    continue
  fi
  
  # Terraform設定に リソース定義を追加
  cat >> main.tf << EOF
resource "aws_instance" "imported_${instance_id}" {
  # 既存リソースの設定をここに記述
}
EOF

  # importを実行
  terraform import aws_instance.imported_${instance_id} $instance_id
done

importしたリソースの設定を確認

# importしたリソースの詳細情報を取得
terraform state show aws_instance.imported_i1234567890abcdef0

# JSON形式で全リソースを確認
terraform state pull | jq '.resources[].instances[0].attributes' | head -50

今後の破損を防ぐための対策

定期的なバックアップ設定

# S3バージョニングを有効化
aws s3api put-bucket-versioning \
  --bucket your-bucket-name \
  --versioning-configuration Status=Enabled

# Terraformで使用するS3バックアップポリシー
resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  versioning_configuration {
    status = "Enabled"
  }
}

# 定期的なバックアップをCron設定
0 2 * * * aws s3 cp s3://your-bucket-name/terraform.tfstate s3://your-bucket-name/backups/terraform.tfstate.$(date +\%Y\%m\%d)

stateファイルのロック機構を有効化

# DynamoTableを使用したstate locking
resource "aws_dynamodb_table" "terraform_locks" {
  name           = "terraform-locks"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

# backend設定で dynamodb_tableを指定
terraform {
  backend "s3" {
    bucket         = "your-bucket-name"
    key            = "terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

stateファイルの暗号化設定

# S3での暗号化設定
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# または KMS暗号化を使用(より安全)
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state_kms" {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm      = "aws:kms"
      kms_master_key_id  = aws_kms_key.terraform.arn
    }
  }
}

よくある質問

Q1: terraform state pullとpushの違いは何ですか?

terraform state pullはリモートstateをローカ

K
AWS・Python・生成AIを専門とするソフトウェアエンジニア。AI・クラウド・開発ワークフローの実践ガイドを執筆しています。詳しく見る →