エラーの内容
terraform apply を実行すると、処理が止まります:
Error: A resource with the ID "my-s3-bucket" already exists
on main.tf line 12, in resource "aws_s3_bucket" "my_bucket":
12: resource "aws_s3_bucket" "my_bucket" {
何が起きているか:Terraformがクラウドアカウントにすでに存在するリソースを作成しようとしています。そのリソースはステートファイルに存在しないため、Terraformはその存在を把握できていません。作成を試みますが、プロバイダーに拒否されます。
根本原因
クラウドプロバイダー上に存在するものと、Terraformが追跡しているものの間にズレが生じています。以下のようなケースで発生します:
- 誰かが手動でリソースを作成した — AWSコンソールの操作、
aws cliコマンド、GCPのUIなど。 - 別のツールが管理している:別のTerraformワークスペース、Pulumi、CloudFormation、カスタムスクリプトなど。
- ステートファイルが削除または破損した。リソースは動き続けているが、Terraformの記録は消えた。
terraform destroyが途中で失敗した。ステートは消去されたが、リソースは残った。- 新しいバックエンドに切り替えた際、ステートの移行手順をスキップした。
修正方法1:既存リソースをステートにインポートする(推奨)
リソースをインポートします。ステートに追加されると、Terraformはその存在を認識し、新規作成を試みなくなります。
ステップ1:クラウドプロバイダーでリソースIDを確認する
AWS S3の場合:
aws s3 ls | grep my-bucket-name
EC2インスタンスの場合:
aws ec2 describe-instances --filters "Name=tag:Name,Values=my-instance" \
--query 'Reservations[*].Instances[*].InstanceId' --output text
GCPの場合:
gcloud compute instances list --filter="name=my-instance"
ステップ2:terraform import を実行する
構文:
terraform import <resource_type>.<resource_name> <provider_resource_id>
実際の使用例:
# AWS S3バケット — バケット名がIDになる
terraform import aws_s3_bucket.my_bucket my-actual-bucket-name
# AWS EC2インスタンス — インスタンスID(i-...)を使用
terraform import aws_instance.web i-0abc1234def56789
# AWSセキュリティグループ
terraform import aws_security_group.app sg-0123456789abcdef0
# GCPコンピュートインスタンス — リソースのフルパス
terraform import google_compute_instance.vm projects/my-project/zones/us-central1-a/instances/my-instance
# Azureリソースグループ — ARM完全リソースID
terraform import azurerm_resource_group.rg /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-rg
ステップ3:terraform plan を実行してドリフトを修正する
インポート後、.tf の設定が実際のリソースと完全に一致しない場合があります。以下を実行してください:
terraform plan
差分が表示されます — Terraformが変更しようとしている属性です。実際の値に合わせて設定を更新してください:
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-actual-bucket-name"
# 不要な変更を抑制するために既存の設定を反映する
}
planの出力が以下になるまで調整を続けてください:
No changes. Infrastructure is up-to-date.
修正方法2:importブロックを使用する(Terraform 1.5以降)
Terraform 1.5では宣言型の import ブロックが追加されました。CLIコマンドは不要で、設定ファイルに記述するだけです:
import {
to = aws_s3_bucket.my_bucket
id = "my-actual-bucket-name"
}
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-actual-bucket-name"
}
その後:
terraform plan # インポート内容をプレビュー
terraform apply # インポートを実行
applyが成功するとブロックは消えます。オンボードしたリソースの記録としてバージョン管理に残すチームもあります。
修正方法3:リソースを削除してTerraformに再作成させる
リソースに重要なデータがなく、一時的なダウンが許容できる場合は、削除してTerraformにゼロから作成させましょう。
# 空のS3バケットを削除
aws s3 rb s3://my-actual-bucket-name
# 通常通りapplyを実行
terraform apply
**データベース、データが入ったバケット、または重要なリソースには使用しないでください。**空のバケットやテスト用セキュリティグループなど、本当に使い捨てのリソースにのみ適用してください。
修正方法4:別の名前を使用する
競合しない名前を選ぶのが最善の場合もあります。.tf ファイルの bucket、name、または id 引数を変更してください:
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-bucket-name-v2" # 一意な名前で競合なし
}
新しいリソースが必要で、既存のものを気にしない場合に有効です。
確認方法
インポート後、ステートに正しく反映されているか確認します:
# 追跡中のすべてのリソースを表示
terraform state list
# 特定のリソースを詳しく確認
terraform state show aws_s3_bucket.my_bucket
最終的なplanを実行します。変更がゼロであれば完了です:
terraform plan
# 期待される出力:
# No changes. Infrastructure is up-to-date.
まだ変更が表示される場合、.tf の設定にインポートしたリソースと一致しない属性があります。差分がなくなるまで調整を続けてください。
予防策
- **Terraformが管理するリソースを手動で作成しない。**コンソール操作やTerraform外でのCLIコマンドが、まさにこの問題を引き起こします。
- ロック機能付きのリモートステートを使用する — S3 + DynamoDBまたはTerraform Cloud。ローカルのステートファイルは、チームで共有する環境では消失・破損・上書きのリスクがあります。
- **既存インフラをオンボードする前にインポート計画を立てる。**すでに存在するリソースに対して新しく
resourceブロックを書いてそのままapplyするのは絶対に避けてください。 - **常に
terraform applyの前にterraform planを実行する。**すでに存在するはずのものを新規作成しようとしていたら、それが早期警告サインです。 - **リソースに
managed-by = terraformタグを付ける。**チームメンバーが手を加えてはいけないリソースを一目で把握できます。

