既存リソースを取り込む
この講座では、既存リソースのTerraform管理について学びます。
- data source(Data Sources)による既存リソースの参照
- terraform importによる既存リソースの取り込み
- importブロックによる宣言的なインポート
- dataとimportの使い分け
1. data
1.1 概要
Data Sources とは
これまで resource ブロックを使って、新しくAWSリソースを作成・管理してきました。 しかし、実務では 「すでに存在するリソースの情報を使いたい」 という場面が多々あります。
- 「管理コンソールで手動作成したVPCの中に、Terraformでサーバを立てたい」
- 「AWSが提供している最新のAmazon LinuxのAMI IDを知りたい」
このように、Terraformの外にある情報や、既存のリソース情報を 「参照」(Read Only) するために使うのが data ブロックです。
| 💡 ポイント |
|---|
data(参照のみ)と import(管理下に取り込む)の違いに注意してください。data はリソースを「見るだけ」で、terraform destroy しても削除されません。一方、import は既存リソースをTerraformの管理下に置くため、terraform destroy で削除対象になります。 |
1.2 記載方法
resource とよく似ていますが、先頭が data になります。 検索条件(フィルター)を指定して、対象のリソースを特定します。
data "リソース型" "名前" {
# 検索条件などを記載
filter {
name = "条件の項目名"
values = ["探したい値"]
}
}
参照する際は、resource の時と同様に、以下のように記述します。
data.リソース型.名前.属性
1.3 サンプルコード
data の最も便利な使い方のひとつが、「最新のAMI IDの取得」です。 通常、EC2のAMI ID(ami-0abcdef...)は不定期に変更されるため、コードに直接書くと古くなってしまいます。data を使うことで、常に最新のIDを自動で取得できます。
以下の例では、name が "al2023-ami-2023.*-x86_64" となっているものを data として取り込みます。
data "aws_ami" "latest_al2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-x86_64"]
}
}
取り込んだ内容を、EC2インスタンス作成時のAMIで使用することができます。
resource "aws_instance" "web" {
# dataからIDを参照する
ami = data.aws_ami.latest_al2023.id
instance_type = "t2.micro"
}
1.4 実際に使ってみる(ハンズオン)
今回は、すでに作成済みのVPCを data によりTerraformに取り込み、その属性を利用するハンズオンを実施します。
VPCを作る
まず、data で取り込みするためのVPCを作成します。
VPCのダッシュボードを開き、左側のメニューからお使いのVPCを選択した上で、VPCを作成をクリックします。
名前タグに test-vpc、IPv4 CIDRに 10.0.0.0/16 を入力し、VPCを作成をクリックします。
無事に test-vpc が作成されたら、VPC-IDを控えておきます。
コードの記載
コードを記載します。main.tf を以下の内容に書き換えてください。
vpc-xxxxxxxxxxxxxxxxx の部分は、先ほど控えたVPC IDに置き換えてください。
provider "aws" {
region = "ap-northeast-1"
}
# -----------------------------------------------------------
# 1. 既存のVPCを取り込む (Data Source)
# -----------------------------------------------------------
data "aws_vpc" "selected" {
# ★ここに手動で作ったVPCのIDを記述してください
id = "vpc-xxxxxxxxxxxxxxxxx"
}
# -----------------------------------------------------------
# 2. そのVPCの中にサブネットを作成 (Resource)
# -----------------------------------------------------------
resource "aws_subnet" "private" {
# data経由でVPC IDを参照
vpc_id = data.aws_vpc.selected.id
# ★VPCのCIDR範囲内で、かつ重複しない値を指定してください
cidr_block = "10.0.1.0/24"
# 任意: Availability Zoneの指定
availability_zone = "ap-northeast-1a"
tags = {
Name = "my-terraform-subnet"
}
}
このコードは、data ブロックでVPCを取り込み、そのIDを新しく作るサブネットとして指定しています。
リソースの作成
apply コマンドで、実際にリソースを作成します。
terraform apply
| ⚠️ 「InvalidVpcID.NotFound」エラーが出る場合 |
|---|
| 指定したVPC IDが見つかりません。 AWSコンソールでVPCの一覧を確認し、正しいVPC IDをコードに記載してください。VPC IDは vpc-で始まる文字列です。 |
| ⚠️ 「InvalidSubnet.Conflict」エラーが出る場合 |
|---|
指定したCIDRブロック(10.0.1.0/24)が既存のサブネットと重複しています。別のCIDR(例: 10.0.2.0/24や10.0.10.0/24)に変更してください。 |
terraform apply の plan 部分を見ると、VPCは特に変更されず、新しいサブネットの作成のみが変更として検知されます。
data.aws_vpc.selected: Reading...
data.aws_vpc.selected: Read complete after 0s [id=vpc-0e594e9849ae57c4b]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_subnet.private will be created
+ resource "aws_subnet" "private" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-1a"
+ availability_zone_id = (known after apply)
+ cidr_block = "10.0.1.0/24"
+ enable_dns64 = false
+ enable_resource_name_dns_a_record_on_launch = false
+ enable_resource_name_dns_aaaa_record_on_launch = false
+ id = (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
+ ipv6_native = false
+ map_public_ip_on_launch = false
+ owner_id = (known after apply)
+ private_dns_hostname_type_on_launch = (known after apply)
+ region = "ap-northeast-1"
+ tags = {
+ "Name" = "my-terraform-subnet"
}
+ tags_all = {
+ "Name" = "my-terraform-subnet"
}
+ vpc_id = "vpc-0e594e9849ae57c4b"
}
Plan: 1 to add, 0 to change, 0 to destroy.
このように、VPCは特に変化せず、サブネットのみが作成される形となります。
実際に作成されるサブネットを見ると、VPCの部分に無事に test-vpc のIDが設定されていればOKです。
リソースの削除
確認が終わったら削除しておきます。
terraform destroy
ここでポイントとなるのは、data ブロックは「参照しているだけ」なので、destroy してもVPC自体が消えることはありません。あくまで作成したサブネットだけが消えます。
data.aws_vpc.selected: Reading...
data.aws_vpc.selected: Read complete after 1s [id=vpc-0e594e9849ae57c4b]
aws_subnet.private: Refreshing state... [id=subnet-0e93b71d216abffb6]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_subnet.private will be destroyed
- resource "aws_subnet" "private" {
- arn = "arn:aws:ec2:ap-northeast-1:125605020607:subnet/subnet-0e93b71d216abffb6" -> null
- assign_ipv6_address_on_creation = false -> null
- availability_zone = "ap-northeast-1a" -> null
- availability_zone_id = "apne1-az4" -> null
- cidr_block = "10.0.1.0/24" -> null
- enable_dns64 = false -> null
- enable_lni_at_device_index = 0 -> null
- enable_resource_name_dns_a_record_on_launch = false -> null
- enable_resource_name_dns_aaaa_record_on_launch = false -> null
- id = "subnet-0e93b71d216abffb6" -> null
- ipv6_native = false -> null
- map_customer_owned_ip_on_launch = false -> null
- map_public_ip_on_launch = false -> null
- owner_id = "125605020607" -> null
- private_dns_hostname_type_on_launch = "ip-name" -> null
- region = "ap-northeast-1" -> null
- tags = {
- "Name" = "my-terraform-subnet"
} -> null
- tags_all = {
- "Name" = "my-terraform-subnet"
} -> null
- vpc_id = "vpc-0e594e9849ae57c4b" -> null
# (4 unchanged attributes hidden)
}
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_subnet.private: Destroying... [id=subnet-0e93b71d216abffb6]
aws_subnet.private: Destruction complete after 1s
Destroy complete! Resources: 1 destroyed.
$
2. importコマンド
2.1 概要
import とは
通常、Terraformでコードを書くと、「新しくリソースを作成」しようとします。もし、手動(マネジメントコンソール)で作成したリソースがある状態で、コードにリソースを定義して terraform apply をすると、Terraformはそれを「全く新しいリソース」として作成しようとします。その結果、リソース名の重複禁止制約(S3バケット名など)に引っかかりエラーになるか、あるいは意図せず二重にリソースが作成されてしまいます。
そのため、Terraformに 「このコード上のリソース定義は、AWS上にあるあのリソース(ID: xxx)のことです」 と紐付けを行う必要があります。そのために、Stateファイルに情報を記録させる作業が import です。
2.2 前提
terraform import コマンドには、以下の特徴があります。
- AWS上のリソース情報をTerraformの記憶(tfstate)に書き込むだけで、Stateファイルを作る処理に閉じています。
.tfファイルへの記述は自動生成されず、自分で行う必要があります。
つまり、「先にリソース定義(resourceブロック)を書く」→「コマンドで紐付ける」→「設定値をコードに書き写して埋める」という手順が必要になります。
この後説明する import ブロックを使うと、Terraformのコード生成も自動で行うことができます。ひとまずは、従来の方法である import コマンドを使ってみます。
2.3 コマンドの構文
terraform import [オプション] アドレス ID
アドレスにはコード上のリソース名を指定します(例:aws_s3_bucket.my_bucket)IDにはAWS上のリソースIDを指定します(例: バケット名やインスタンスID)
なお、IDとしてどの属性(値)を指定するかは、リソースのタイプによって異なります。
2.4 実際に使ってみる
今回は、先ほど作成した test-vpc をTerraformに取り込む手順を体験してみましょう。
コードの受け皿を用意 (main.tf)
Terraform側に、「これに取り込みたい」という定義場所(受け皿)を作ります。 main.tf に以下を記述してください。
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_vpc" "import_vpc" {
cidr_block = "10.0.0.0/16"
tags = {
"Name" = "test-vpc"
}
}
importコマンドの実行
ターミナルで以下のコマンドを実行し、リソースを取り込みます。[VPC_ID] の部分は、先ほど作った test_vpc のIDに置き換えてください。
terraform import aws_vpc.import_vpc [VPC_ID]
| ⚠️ 「Cannot import non-existent」エラーが出る場合 |
|---|
| 指定したVPC IDが存在しません。 AWSコンソールでVPCの一覧を確認し、正しいIDを指定してください。 リージョンが異なる場合も同様のエラーが発生します。 |
| ⚠️ 「Resource already managed」エラーが出る場合 |
|---|
このリソースはすでにTerraformの管理下にあります。terraform state listコマンドで現在の状態を確認し、必要に応じてterraform state rmでStateから除外してから再度importしてください。 |
成功すると、Import successful! と表示されます。 これで、TerraformはこのVPCを「自分が管理しているもの」として認識しました。
整合性の確認 (terraform plan)
正しく取り込めたか、そしてコードと現状にズレがないか確認します。
terraform plan
もし No changes. と表示されたら、コードと実物が一致しています。
もし ~ update in-place(変更あり)と表示された場合、それは「手動で作った時の設定」と「現在のコード」の間に差があることを意味します。
この場合、Terraformは「コードに合わせて設定を戻そう(変更しよう)」としてしまいます。そのため、この plan の結果を見ながら、コード側に足りない設定(cidr_block や tags など)を追記して、No changes になるように修正作業を行います。
リソースの削除
Terraform管理下に入ったので、terraform destroy で削除することができます。
terraform destroy
これで、手動で作ったVPCもTerraform経由できれいに削除されました。
3. importブロック
3.1 import ブロックとは
コマンドラインで単発実行するのではなく、main.tf などのファイル内に「このリソースを取り込みたい」という宣言(import ブロック)を記述する方式です。
これにより、以下のメリットがあります。
- オプションを使うことで、リソース定義(HCL)を自動生成できる。
- 「何をインポートしようとしているか」がコードとして履歴に残るため、レビューしやすい。
3.2 記載方法
main.tf に以下のように記述します。
import {
# to: Terraform上で何という名前にするか(これから作るリソース名)
to = aws_s3_bucket.this
# id: AWS上のリソースID(バケット名など)
id = "manual-test-bucket-12345"
}
3.3 コード生成コマンド
import ブロックを書いた状態で、以下のオプションを付けて plan を実行すると、コードが生成されます。
terraform plan -generate-config-out=generated.tf
-generate-config-out=ファイル名: 指定したファイル名で、リソース定義のコードを出力します。
3.4 実際に使ってみる
これが現在、既存リソースを取り込む最も推奨される簡単な方法です。その威力を体験してみましょう。
手動でリソース作成
import ブロックのテストに使う既存リソースとして、今後は手動でS3バケットを作成します。
S3のダッシュボードを開き、左側のメニューから汎用バケットを選択し、中央にあるバケットの作成をクリックします。
バケット名は、my-import-test などのバケット名を入力します。S3のバケット名は全世界で一意になる必要があるため、日付など任意の文字を追加してください。
その他の項目はデフォルトで構いませんので、バケットの作成をクリックします。
importブロックの記述
main.tf に、以下の import ブロックだけ を記述します。
provider "aws" {
region = "ap-northeast-1"
}
import {
# Terraform上での名前を定義
to = aws_s3_bucket.my_imported_bucket
# 手動で作ったバケット名を指定
id = "my-import-test-20260101"
}
コード生成の実行
ターミナルで以下のコマンドを実行します。
これにより、「importブロックの指示に従ってAWSを見に行き、その設定内容を generated.tf というファイルに書き出す」という処理が行われます。
terraform plan -generate-config-out=generated.tf
| ⚠️ 「Cannot write configuration」エラーが出る場合 |
|---|
generated.tfファイルがすでに存在しています。既存のファイルを削除するか、別のファイル名(例: generated2.tf)を指定してください。 |
| ⚠️ 「NoSuchBucket」エラーが出る場合 |
|---|
| 指定したS3バケットが存在しません。 AWSコンソールでバケット名を確認し、 importブロックのidの値を正しいバケット名に修正してください。 |
生成されたコードの確認
コマンドが成功すると、カレントディレクトリに新しく generated.tf というファイルができているはずです。中身を見てみましょう。
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "my-import-test-20260101"
resource "aws_s3_bucket" "my_imported_bucket" {
bucket = "my-import-test-20260101"
bucket_prefix = null
force_destroy = false
object_lock_enabled = false
region = "ap-northeast-1"
tags = {}
tags_all = {}
}
このように、AWS上の設定値がすべて反映されたコードが自動生成されました!
手書きで苦労していた作業が一瞬で終わります。
3.5 適用 (Apply)
現在は「コードを作った」段階で、Stateファイル(Terraformの記憶)への取り込みは完了していません。
apply を実行して、取り込みを確定させます。
terraform apply
これで、既存リソースの取り込みとコード化が完了しました。
3.6 リソースの削除
最後に、取り込んだリソースを削除しておきます。
terraform destroy
なお、terraform apply が成功して Stateファイルへの取り込みが完了したら、main.tf に書いた import ブロックは削除して構いません。(残しておいても害はありませんが、役割は終わったため消すのが一般的です)
4. importの使い分け
| 機能 | 特徴 | 向いている場面 |
|---|---|---|
import コマンド |
手軽だが、コードは自分で書く必要がある。 | リソースが少なく、設定が単純な場合。バージョンが古い(Terraform 1.5.0より低い)場合(importブロックが未対応) |
import ブロック |
コードを自動生成できる。 構成管理しやすい。 | 基本はこちらを推奨。 複雑なリソースや大量のインポートを行う場合。 |
import ブロック・terraform import コマンドそれぞれの詳しい仕様は、Terraform公式ドキュメントのImportとCommand: importに記載があります。
5. まとめ
この講座では、既存リソースをTerraformに取り込む方法について学びました。
- dataブロックで既存リソースの情報を「参照」できる(Read Only)
dataで参照したリソースはterraform destroyで削除されない- importコマンドでStateファイルにリソースを取り込める(コードは手動作成が必要)
- importブロックでコードの自動生成(
-generate-config-outオプション)が可能 - 基本的には
importブロックの使用を推奨