既存リソースを取り込む

この講座では、既存リソースの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/2410.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_blocktags など)を追記して、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公式ドキュメントのImportCommand: importに記載があります。

5. まとめ

この講座では、既存リソースをTerraformに取り込む方法について学びました。

  • dataブロックで既存リソースの情報を「参照」できる(Read Only)
  • dataで参照したリソースはterraform destroyで削除されない
  • importコマンドでStateファイルにリソースを取り込める(コードは手動作成が必要)
  • importブロックでコードの自動生成(-generate-config-outオプション)が可能
  • 基本的にはimportブロックの使用を推奨