状況
Terraformリポジトリを整理していました — プロバイダーを単一のルートモジュールに統合し、子モジュールから重複したproviderブロックを削除していました。設定は問題なく見えました。そしてterraform planを実行すると、このエラーが発生しました:
Error: Provider configuration not present
To work with aws_s3_bucket.my_bucket its original provider configuration
at module.storage.provider["registry.terraform.io/hashicorp/aws"] is
required, but it has been removed. This occurs when a provider
configuration is removed while objects created by that provider
still exist in the state.
ステートには、コード上に存在しなくなったプロバイダーパスに紐づくリソースが残っています。その設定がなければ、Terraformはどの認証情報やリージョンを使えばよいか判断できないため、何もできない状態になります。
なぜこのエラーが発生するのか
terraform.tfstate内のすべてのリソースは、プロバイダータイプだけでなく、モジュールスコープやエイリアスを含む完全なパスである特定のプロバイダー設定アドレスにリンクされています。そのパスを削除すると、ステートの参照が壊れます。
よくある原因:
- モジュールから
providerブロックを削除した - 独自のプロバイダー設定を持つモジュールを削除またはリネームした
- プロバイダーエイリアスを削除した(例:
provider "aws" { alias = "us-east" }) - ステートを更新せずにモジュール間でリソースを移動した
Terraformは推測しません。ステートがmodule.storage.provider["registry.terraform.io/hashicorp/aws"]を指しているのに、そのアドレスが.tfファイルに存在しない場合、他の評価を行う前に即座にエラーになります。
デバッグ:孤立しているリソースを見つける
まずステート内のすべてのリソースをリストアップします:
terraform state list
次に特定のリソースを確認します:
terraform state show module.storage.aws_s3_bucket.my_bucket
出力のproviderフィールドを確認してください。module.storage.provider["registry.terraform.io/hashicorp/aws"]のようなパスが表示されます。そのパスが設定に存在しているか、または存在するプロバイダーにリソースを再リンクする必要があります。
ステート内のすべてのプロバイダー参照を素早くスキャンするには:
cat terraform.tfstate | grep '"provider"'
モジュール全体が削除されて20以上のリソースが孤立している場合など、リソースを1つずつ確認するよりもこの方法が速いです。
修正方法1 — プロバイダーブロックを一時的に復元する
最も素早くブロックを解消する方法:削除されたプロバイダー設定をそのまま元通りに追加します。
# module/storage/main.tf に — 削除されたものを復元する
provider "aws" {
region = var.aws_region
}
terraform init && terraform planを実行します。プランに予期しない変更が表示されなければ、ブロックは解消されています。ここから、焦らずに正しい移行作業を進めることができます。
これは永続的な修正ではありません — 正しい移行を行うための時間を稼ぐ、安全なロールバックです。
修正方法2 — ステート内のリソースを新しいプロバイダーに移動する
プロバイダー設定をルートモジュールにリファクタリングしている場合は、terraform state replace-providerを使用します。実際のインフラに触れることなく、リソースを新しいプロバイダーアドレスに再リンクします:
terraform state replace-provider \
"module.storage.registry.terraform.io/hashicorp/aws" \
"registry.terraform.io/hashicorp/aws"
リソースはルートレベルのAWSプロバイダーを指すようになります。AWS上では何も変わらず、ステートファイルのみが更新されます。
修正を確認します:
terraform state show module.storage.aws_s3_bucket.my_bucket
# providerフィールドがルートレベルのプロバイダーパスを示すはずです
ほとんどのエンジニアは、このエラーに当たるまでこのコマンドの存在を知りません。プロバイダーの場所を再編成する際に最もクリーンな方法です。
修正方法3 — ステート内のリソースアドレスを移動する
リソース自体を移動した場合(プロバイダーだけでなく)、ステートのアドレス全体も更新する必要があります:
terraform state mv \
module.storage.aws_s3_bucket.my_bucket \
aws_s3_bucket.my_bucket
これはTerraformに「同じ実際のS3バケットが、ルートレベルのプロバイダーの下でこの新しいアドレスで管理されるようになった」と伝えます。
# 移動が成功したことを確認する
terraform state list | grep s3
terraform plan # 設定と実態が一致していれば変更なしと表示されるはずです
修正方法4 — ステートからリソースを削除する
場合によっては、単純にトラッキングをやめることが正解です。リソースがすでに削除済みの場合、または手動で削除する予定の場合、ステートから削除します:
terraform state rm module.storage.aws_s3_bucket.my_bucket
Terraformはリソースを忘れます。削除しようとはせず、単に管理を停止します。Terraform外ですでにクリーンアップされたインフラを廃止する際に便利です。
影響を受けるリソースが多い場合の対処
モジュール全体を削除して30のリソースが孤立している場合は、一括で処理します:
# 古いモジュールパス以下のすべてを確認する
terraform state list | grep 'module.storage'
# 各リソースをルートレベルに移動する
terraform state mv module.storage.aws_s3_bucket.my_bucket aws_s3_bucket.my_bucket
terraform state mv module.storage.aws_dynamodb_table.locks aws_dynamodb_table.locks
# ... 各リソースに対して繰り返す
移動後、新しいアドレスに合わせて.tfファイルを更新します。そしてterraform planを実行し、「No changes」と表示されれば、ステートと設定が再び同期しています。
作業開始前にステートをバックアップする
リファクタリング中にプロバイダー設定に触れる前に、スナップショットを取得します:
cp terraform.tfstate terraform.tfstate.backup-$(date +%Y%m%d)
リモートステートの場合は、まずローカルコピーを取得します:
terraform state pull > state-backup.json
ステートファイルはプレーンJSONです。追加のツールをインストールせずにプロバイダー参照を検索したり構造を調べたりしたい場合、ToolCraftのYAML ↔ JSONコンバーターが便利です — JSONを貼り付けてブラウザで閲覧できます。アップロードは一切行われないため、ステートファイルにIAM認証情報やリソースARNが含まれている場合でも安心です。
検証
上記のいずれかの修正を適用した後、このチェックリストを実行してください:
# プロバイダーを再初期化する
terraform init
# 予期しない変更がないことを確認する
terraform plan
# 特定のリソースが実際のインフラに正しくマッピングされているか確認する
terraform state show <resource_address>
プランの出力に「No changes」と表示されれば、ステートとプロバイダー設定が適切に整合しており、このプロセスでインフラが変更されていないことを意味します。
得られた教訓
- **プロバイダーブロックを削除する前にステートを移行する。**常にステートを先に更新してください。設定の削除は後です。逆の順序で行うことが、まさにこのエラーに陥る原因です。
- **
terraform state replace-providerは知られざるコマンドです。**プロバイダーのリファクタリングのために設計されており、他の方法よりもはるかにクリーンです — しかし、このエラーに当たるまでその存在を知る人はほとんどいません。 - **モジュールスコープのプロバイダーは密結合を生み出します。**独自の
providerブロックを持つモジュール内のリソースは、ステートでそのパスに固定されます。代わりにproviders引数を使って明示的にプロバイダーを渡してください — 将来のリファクタリングがはるかに楽になります。 - **planの実行時には、
.tfファイルではなくステートが真実の情報源です。**Terraformはステートのアドレスと設定のアドレスを照合します。それらが乖離すると、何も実行されません。まずステートを修正してください。

