🎪

OpenTofuの外でRDSを入れ替える

に公開

概要

OpenTofuでリソースを作成しています。
RDSを特定の断面に戻するときに、スナップショットなどを利用して復旧をしますが、AWSのコンソール上などからは新しいDatabaseやClusterの作成となってしまいます。
復旧したDatabaseを前のDBと置き換えて利用したいというユースケースがあり、その際にOpenTofuでのリソースの差分が出るかを検証してみました。

結果

RDSの名称を入れ替えることで、OpenTofuでのリソースの差分は出ないようになりました。
ただし、内部的に一意のIDを持っているのか、外部でStateが変更されているよっていう警告が初回に出力されるようです。
なんで一意の名称を変えられるようになっているんでしょうね。不思議です。 → 内部的に DbiResourceId のIDを利用しているみたいですね。

詳細

前提条件

  • OpenTofuを利用してAWSのリソースを管理しています
    • 利用用途としては、ほぼTerraformと一緒なので、Terraformでも同様の動きになると思います(未検証)
  • RDSを利用しており、特定のユースケースにおいて、DBを特定の断面に戻す場合があります
  • RDSの機能としてPITRなどを利用することによって、特定の断面に戻すことは簡単ですが、新しいDatabaseを作成する必要があります
    • 通常はこの利用方法で問題ないのですが、アプリケーション側の接続先の修正や、OpenTofuのstateの管理外になってしまうなど、色々と面倒が多いです
  • そこで、もともとのDBと同じ接続情報や名称を持つように守勢することで、上記のような修正が発生しないようにできないかを検討しました

RDSの修正について

  • AWSのリソースは一意なID的なものが割り振られており、一度作成すると変更できない場合がほとんどです。
    • EC2のAMI IDなど
  • RDSはなぜだかわかりませんが、ARNでも利用しているDatabaseの名前を変更することができます

試しにコンソールで名前を変更してみたキャプチャです

詳細

OpenTofuで同じStateだと認識されるために以下の手順でバックアップを起動して、復元を行います

  • 対象のDatabase(元DB)のスナップショットより、Databaseを作成する(復元DB)
    • この時、パラメータなどは既存のDatabaseと極力同じになるようにする
  • 元DBの名称を変える。
  • 復元DBの名称を元DBと同じ名称にする
  • OpenTofuのplanを実行して、差分が出ないことを確認する
上記の実行シェル

上記の実行を試すにあたって、CLIで作成したものがあるので、共有します
restore-db-instance-from-db-snapshot という便利なコマンドがあったのですが、元の情報を持っていない部分も多いので注意が必要です。

restore.sh
#!/usr/bin/env bash

set -euo pipefail

wait_instance_available() {
  local identifier="$1"
  echo "[INFO] ${identifier} が利用可能になるまで待機します..."

  # インスタンスが認識されるまでリトライ
  until aws rds describe-db-instances --db-instance-identifier "${identifier}" >/dev/null 2>&1; do
    sleep 15
  done

  # available になるまで公式 waiter を利用
  aws rds wait db-instance-available --db-instance-identifier "${identifier}"
  echo "[INFO] ${identifier} が利用可能になりました。"
}

read -p "復元元のRDSインスタンス名を入力してください: " SOURCE_INSTANCE
read -p "使用するスナップショット識別子(DB Snapshot ID)を入力してください: " SNAPSHOT_ID

DB_INFO=$(aws rds describe-db-instances --db-instance-identifier $SOURCE_INSTANCE | jq -r '.DBInstances[0]')
DB_INSTANCE_CLASS=$(echo $DB_INFO | jq -r '.DBInstanceClass')
DB_SUBNET_GROUP_NAME=$(echo $DB_INFO | jq -r '.DBSubnetGroup.DBSubnetGroupName')
VPC_SECURITY_GROUP_IDS=$(echo $DB_INFO | jq -r '.VpcSecurityGroups[0].VpcSecurityGroupId')
DB_PARAMETER_GROUP_NAME=$(echo $DB_INFO | jq -r '.DBParameterGroups[0].DBParameterGroupName')

echo "\n=== ステップ 1: スナップショットから ${TARGET_INSTANCE} を作成 ==="
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier "${TARGET_INSTANCE}" \
  --db-snapshot-identifier "${SNAPSHOT_ID}" \
  --db-instance-class $DB_INSTANCE_CLASS \
  --db-subnet-group-name $DB_SUBNET_GROUP_NAME \
  --vpc-security-group-ids $VPC_SECURITY_GROUP_IDS \
  --db-parameter-group-name $DB_PARAMETER_GROUP_NAME \
  --no-publicly-accessible

wait_instance_available "${TARGET_INSTANCE}"

echo "\n=== ステップ 2: 元インスタンス ${SOURCE_INSTANCE} を ${ORIGINAL_INSTANCE} にリネーム ==="
aws rds modify-db-instance \
  --db-instance-identifier "${SOURCE_INSTANCE}" \
  --new-db-instance-identifier "${ORIGINAL_INSTANCE}" \
  --apply-immediately

wait_instance_available "${ORIGINAL_INSTANCE}"

echo "\n=== ステップ 3: リストアしたインスタンス ${TARGET_INSTANCE} を ${SOURCE_INSTANCE} にリネーム ==="
aws rds modify-db-instance \
  --db-instance-identifier "${TARGET_INSTANCE}" \
  --new-db-instance-identifier "${SOURCE_INSTANCE}" \
  --apply-immediately

wait_instance_available "${SOURCE_INSTANCE}"

echo "\n[INFO] リストアとリネーム処理完了"
echo "  旧環境: ${ORIGINAL_INSTANCE}"
echo "  新環境: ${SOURCE_INSTANCE}"

OpenTofuの実行結果

適当に作成したリポジトリを利用しつつリソースの作成、スナップショットの作成、名称変更を実行してみました。

https://github.com/merutin/opentofu-aws-sample

まずはリソースを作成します

cd environments/dev
tofu init
tofu apply

コンソールでスナップショットを作成後、上記で作成したシェルを利用して名称の変更を行います

bash restore.sh

復元DBが元DBの名前で作成されているので、planしたときの挙動を確認します

# リソースを変更したいわけではないので、planにしています
tofu plan

結果は、内部的にIDを保持しているらしく、元DBの名称が変更になったと検知されます。
→ Geminiに聞いてみたところ、内部的に DbiResourceId のIDを利用しているみたいで、それの値を優先して差分を取得しているようです

OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

OpenTofu will perform the following actions:

  # module.main.module.database.aws_db_instance.this will be updated in-place
  ~ resource "aws_db_instance" "this" {
        id                                    = "db-CZAQAIMYN4YIRBFGBRKINGLCMA"
      ~ identifier                            = "opentofu-aws-sample-dev-exampledb-original" -> "opentofu-aws-sample-dev-exampledb"
        tags                                  = {
            "Name" = "opentofu-aws-sample-dev-exampledb"
        }
        # (56 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

なので、元DBをコンソールから削除して再度実行すると結果が変ります。

No changes. Your infrastructure matches the configuration.

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

意図した通り、OpenTofu上では差分がない状態になりました。

結論

OpenTofuの外でRDSの名称の変更を行い、別のRDSに入れ替えたとしても、パラメータグループなどの設定が同じである場合はterraform planの結果は差分なしとすることができました。
ただし、何かしら内部で持っている値があるらしく、入れ替え元のDatabaseを削除しないと正しく認識されないことも分かりました。

DELTAテックブログ

Discussion