🌀

TerraformのStateマージやってみた

に公開

このブログで知ること

このブログでは知れないこと

  • 大量リソースのStateマージ

やってみる

想定する初期状況と構成

  • Terraformバージョンは 1.10.0 以上である(DDBロックテーブルが不要)
  • 以下のようにそれぞれ fuga と hoge ディレクトリ内で別々にS3でリモートState管理されている
  • マージされたState管理するS3バケットは作成されている
  • fuga と hoge のdynamodbのリソースIDが一意である(それぞれのリソースIDで重複するものはない。なお、もしもリソースIDが重複する場合は、Rename and move a resource を参考にIDを更新すると良い。統合する際に、 moved ブロックを削除する。)
.
├── fuga
│   ├── fuga_dynamodb.tf
│   └── terraform.tf
└── hoge
    ├── hoge_dynamodb.tf
    └── terraform.tf

対応手順

  1. 各リモートStateを各ディレクトリのローカルに取得する(Obtain both state files)
  2. 1のバックアップを作成する (Backup creation)
  3. terraform初期化していない新しいディレクトリを作成し、マージしたいStateとリソースファイルを配置する (Create a new, empty directory)
  4. マージの被対象と対象先を確認する (Identify which state will be the end state)
  5. Stateで管理されているリソースを確認する (View Resources)
  6. リソース・モジュールのステートを移行する (Move top-level resources and top-level modules)
  7. 移行元ステートと移行先ステートファイルを確認する (View source state and destination state files)
  8. 対象先のステートファイルの serial の値を1増やす (increment the "serial" value by 1)
  9. マージされたステートファイルをアップロードする (Upload the new, merged state to its final destination)
  10. リモートステートのリソースを確認 (Review)

完了後の構成

.
├── fuga
│   ├── fuga_dynamodb.tf
│   ├── source.tfstate.cp # バックアップ
│   └── terraform.tf
├── hoge
│   ├── destination.tfstate.cp # バックアップ
│   ├── hoge_dynamodb.tf
│   └── terraform.tf
└── merge
    ├── destination-resources.txt
    ├── destination.tfstate
    ├── destination.tfstate.xxxxxx.backup
    ├── fuga_dynamodb.tf # マージ非対称のリソース
    ├── hoge_dynamodb.tf # マージ対象先のリソース
    ├── source-resources.txt
    ├── source.tfstate
    ├── source.tfstate.xxxxxx.backup
    └── terraform.tf

1. 各リモートStateを各ディレクトリのローカルに取得する

# fuga
terraform state pull > source.tfstate

# hoge
terraform state pull > destination.tfstate

2. 1のバックアップを作成する

# fuga
cp source.tfstate source.tfstate.cp

# hoge
cp destination.tfstate destination.tfstate.cp

3. 新しいディレクトリを作成し、マージしたいStateとリソースファイルを配置する

└── merge
    ├── destination.tfstate
    ├── fuga_dynamodb.tf # マージ非対称のリソース
    ├── hoge_dynamodb.tf # マージ対象先のリソース
    └── source.tfstate

4. マージ被対象と対象先を確認する

今回は、fuga から hoge にマージさせます。

5. Stateで管理されているリソースを確認する

# merge
terraform state list -state=source.tfstate > source-resources.txt
# aws_dynamodb_table.fuga

terraform state list -state=destination.tfstate > destination-resources.txt
# aws_dynamodb_table.hoge

6. リソース・モジュールのステートを移行する

ローカルで実行する場合は、 -state=source.tfstate -state-out=destination.tfstate をオプションで付けろということなのでその通りにする。

# manual
terraform state mv [options] SOURCE DESTINATION

# 今回のケース
terraform state mv -state=source.tfstate -state-out=destination.tfstate aws_dynamodb_table.fuga aws_dynamodb_table.fuga

7. 移行元ステートと移行先ステートファイルを確認する

# merge
terraform state list -state=source.tfstate
# 何も表示されない

# merge
terraform state list -state=destination.tfstate
# aws_dynamodb_table.fuga
# aws_dynamodb_table.hoge

8. 対象先のステートファイルの serial の値を1増やす

今回だと destination.tfstateserial の値を増やします。

9. マージされたステートファイルをアップロードする

mergeディレクトリに (別のリモートState管理のバケットを指定した) terraform.tf 配置して以下を実行する。

# merge
terraform init
terraform state push destination.tfstate

10. リモートステートのリソースを確認

# merge
terraform state list                           
# aws_dynamodb_table.fuga
# aws_dynamodb_table.hoge

terraform plan
# No changes. Your infrastructure matches the configuration.

# Terraform has compared your real infrastructure against your configuration
# and found no differences, so no changes are needed.

思ったこと

  • 6の手順において、リソースが何十とあるとしんどくなってくると思うけど、 terraform state list -state=source.tfstate > source-resources.txt で出力されたリソース名を読み込んで mv させる、例えば以下のようなスクリプトを実行すれば大量リソースがあってもそこまで工数かけずにいけるのだろうか。
#!/bin/bash

# 引数チェック
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <state-file> <resource-file>"
    echo "Example: $0 hoge.tf source-resources.txt"
    exit 1
fi

STATE_FILE="$1"
RESOURCE_FILE="$2"

# ファイル存在チェック
if [ ! -f "$RESOURCE_FILE" ]; then
    echo "Error: Resource file '$RESOURCE_FILE' not found"
    exit 1
fi

if [ ! -f "$STATE_FILE" ]; then
    echo "Error: State file '$STATE_FILE' not found"
    exit 1
fi

# ファイルから行を読み取ってループ
while IFS= read -r resource; do
    # 空行スキップ
    if [ -z "$resource" ]; then
        continue
    fi
    
    echo "Moving resource: $resource"
    terraform state mv \
        -state="$STATE_FILE" \
        -state-out=destination.tfstate \
        "$resource" "$resource"
done < "$RESOURCE_FILE"

Discussion