1. ホーム
  2. 記事一覧
  3. terraform importコマンド&importブロックハンズオン!既存リソースをTerraformへ取り込んでみよう

2024.05.12

terraform importコマンド&importブロックハンズオン!既存リソースをTerraformへ取り込んでみよう

Terraformを利用してクラウドのリソースを運用していると、既存のリソースを管理しなければならない場面に遭遇します。実際の現場では、すでに運営されているサービスの運用を任され、IaCを実現するためTerraformで管理したい。といったことも少なくありません。

この記事では、そんな時に役立つTerraformの機能、importの使い方をハンズオン形式で実際に手を動かしながら学んでいきます。今回のハンズオンでは料金が発生しないよう、VPC、S3バケットの2つに絞って実践します。

terraform importとは

Terraformにおけるimportとは、Terraformで管理していない既存のリソースをtfstateファイルに取り込むことです。

AWSマネジメントコンソールやAWS CLIなどで作成されたリソースはTerraformの管理下にないため、tfstateファイルにリソースの情報を取り込まない限り認識することができません。

既存のリソースの情報をimportして取り込むことで、Terraformでの管理が可能になります。

このように、Terraformで管理していないリソースの情報を取り込んで管理できるようにするのが、Terraformにおけるimportです。

importは各リソースのIDを参照して行われます。EC2であればインスタンスID、AWS Route53ではゾーンIDが対象になります。実際に利用する際にはどのIDを指定すればよいか確認する必要があります。

tfstateに関しては次の記事で解説しています。

https://envader.plus/article/199

importには2つの種類がある

Terraformのimportには、以下2つの種類があります。

  • terraform importコマンド
  • tfファイル内にimportブロックを記述する

terraform importコマンド

terraform importコマンドでは、1つのリソースに対して1つのコマンドを実行する必要があります。少ないリソースであれば問題ありませんが、大規模な環境で複数のリソースをインポートする必要がある場合、作業量が多くなり時間がかかってしまいます。

また、tfファイルのコードは自動で作成されないため、terraform planterraform showコマンドの結果を見ながらソースコードの修正をしなければいけないため、こちらも作業量が増える要因になります。

# コマンド例
terraform import aws_instance.example i-1234567890abcdef0

https://developer.hashicorp.com/terraform/cli/commands/import

importブロック

Terraformバージョン1.5.0から、importブロックの記述が可能になりました。この場合、tfファイル内にimportブロックを記述し、applyを実行することでリソースの情報を取り込むことが可能です。

さらに、importブロックを使用するとtfファイルのソースコードも自動で生成してくれるため、terraform importコマンドで必要だったコードの修正が不要になります。

ただ、リソースIDなどが直接記述されてしまうことがあるため、変数に置き換えてあげるなど多少の修正は必要になります。

# importブロックの例
import {
  id = "i-1234567890abcdef0"
  to = aws_instance.example
}

https://developer.hashicorp.com/terraform/language/import

terraform importはなぜ必要か

terraform importを使うことにより、以下のようなメリットがあると考えます。

  • インフラリソースをコード化できる
  • 既存環境をTerraformで管理できる

インフラリソースをコード化できる

Terraformでは、HCL(HashiCorp Configuration Language)と呼ばれる独自の言語を使用し、インフラリソースをコード化することで、ドキュメント化と情報共有がしやすくなります。

また、コード上でリソースを管理することにより人的ミスを防ぐことができます。さらにインフラの自動デプロイが可能になるため、新規環境を数分で構築できるようになるメリットがあります。

このようなインフラ構成をコード化することはIaCInfrastructure as Code)と呼ばれています。

IaCの詳細は以下の記事で解説しています。

https://envader.plus/article/136

既存リソースをTerraformで管理できる

importすることで、既存リソースを徐々にTerraformに取り込むことができます。

1からTerraformを使って新しい環境を構築する必要がなくなり、リソースを移行するコストを削減することが可能です。

記事冒頭でも記載しましたが、すでにサービスを運営されているクライアントから運用を任されるケースも少なくありません。

AWSコンソール、AWS CLIなどで環境構築されていた場合、クラウド上のリソースを手動で管理することになるため、サブネットの重複作成やセキュリティグループの設定ミスなど人的ミスが発生しやすくなります。

このimport機能をうまく活用することで、こういったリスクを低減することができるようになります。

terraform importの注意点

importすることで得られるメリットは大きいです。

ただ、importするだけでコード化を完了させることは難しいでしょう。

importコマンドではtfstateファイルに既存のリソース情報を取り込むため、実際のコード化作業は手動で実行しなければなりません。

具体的な流れとしては、terraform importコマンドでtfstateファイルにリソース情報を取り込み、terraform showコマンドで既存リソースの情報を取得、それを元にtfファイルの内容を作成するといった流れです。

# 既存のEC2インスタンスをインポート
terraform import aws_instance.example i-1234567890abcdef0

# tfstateからリソース情報を取得
terraform show

# この情報を元にtfファイルを作成していく

対してimportブロックでは、コード化作業をある程度自動で実行することが可能です。しかし、リソースのIDがそのまま記載されてしまうため変数に置き換える必要がある、importブロックで対応していないリソースの場合は、コード化が自動でできないなどの課題があります。

# importブロックで自動生成されたコード例
resource "aws_instance" "example" {
  ami = "ami-12345678"
  instance_type = "t2.micro"
  # インポートされたIDが直接記述されている
  subnet_id = "subnet-12345678"
}

# 修正例
variable "subnet_id" {
  description = "ID of the subnet"
}

resource "aws_instance" "example" {
  ami = "ami-12345678"
  instance_type = "t2.micro"
  # 変数に置き換える
  subnet_id = var.subnet_id
}

これらの課題があることを理解し、import作業を行う必要があります。

importコマンドの実践

ここからは実際にterraform importコマンドを使ってVPC、S3バケットをインポートします。

今回はコンソールではなく、AWS CLIを使ってリソースを作成し、そのリソースをインポートしていきます。

versions.tfの作成

はじめにTerraformのバージョン、プロバイダーのバージョンを指定するためversions.tfファイルを作成します。

# versions.tf

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.3.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

importブロックを実践する際にTerraformバージョン1.5.0以上が必要になるため、このバージョンを指定します。

ファイルを作成後、terraform initコマンドでプロジェクトの初期化を行います。

terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "5.3.0"...
- Installing hashicorp/aws v5.3.0...
- Installed hashicorp/aws v5.3.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

init時にエラーが発生した場合は、ローカルのTerraformバージョンが1.5.0以下の可能性があります。Terraformのインストール方法やバージョン管理方法は、以下リンク記事にて解説しています。

https://envader.plus/article/162

VPCの作成

次のコマンドを実行し、VPCを作成します。

タグのValueは任意の名前で問題ありません。

# VPCの作成
aws ec2 create-vpc --cidr-block 10.0.0.0/16 --query 'Vpc.VpcId' --output text

# VPCにタグを付与 Valueは任意の名前を付与することが可能
aws ec2 create-tags --resources vpcid --tags Key=Name,Value=import-vpc

続いてVPCが作成できているか確認します。

# Nameタグに"import-vpc"を持つVPCを検索

aws ec2 describe-vpcs --filters "Name=tag:Name,Values=import-vpc"

VPCの情報が出力されることを確認できたら次へ進みます。

S3バケットの作成

次にS3バケットを作成します。

S3バケットは世界で一意の名前にする必要があるため、作成時にエラーが発生した場合は適宜変更してバケットを作成します。

# S3バケットの作成
aws s3 mb s3://import-s3-bucket

# 以下のエラーが発生した場合は、バケット名に日付を付け足すなどして変更する
make_bucket failed: s3://import-s3-bucket An error occurred (BucketAlreadyExists) when calling the CreateBucket operation: The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.

aws s3 mb s3://import-s3-bucket-xxxxxx

# バケットが作成できたか確認する
aws s3 ls

VPCのインポート

作成したVPCをインポートします。

まず、main.tfファイルを作成します。

touch main.tf

main.tfに以下を記述します。

# {}の中は空でOK
resource "aws_vpc" "import_vpc" {
}

importコマンドを実行し、VPCの情報をtfstateファイルに取り込みます。

# importコマンドでは、vpcのIDを指定する
terraform import aws_vpc.import_vpc vpc-xxxxxxxxxx

aws_vpc.import_vpc: Importing from ID "vpc-xxxxxxxxxxx"...
aws_vpc.import_vpc: Import prepared!
  Prepared aws_vpc for import
aws_vpc.import_vpc: Refreshing state... [id=vpc-xxxxxxxxxxxx]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

Import successful!と表示されればインポート成功です。

tfstateファイルの中身を確認し、VPCの情報が記載されていることを確認します。

# tfstateファイル
{
  "version": 4,
  "terraform_version": "1.5.7",
  "serial": 1,
  "lineage": "cd2dba49-6811-38d6-93e8-a27e007dcb1d",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_vpc",
      "name": "import_vpc", # 作成したVPCの情報が取り込まれていることが分かる
      "provider": "provider[\\"registry.terraform.io/hashicorp/aws\\"]",
      "instances": [
        {
          "schema_version": 1,

...........以下略

VPCのソースコードを記述する

ここまででVPCの情報をtfstateファイルに取り込むことができましたが、肝心のソースコードはまだ記載できていません。

planを実行してコードを記述していく方法もありますが、今回はterraform showコマンドを使ってコードを作成します。

terraform show
# aws_vpc.import_vpc:
resource "aws_vpc" "import_vpc" {
    arn                                  = "arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:vpc/vpc-xxxxxxxxxxx"
    assign_generated_ipv6_cidr_block     = false
    cidr_block                           = "10.0.0.0/16"
    default_network_acl_id               = "acl-0ea21b1e2dc80fef6"
    default_route_table_id               = "rtb-00d4acadaea3e298c"
    default_security_group_id            = "sg-0fb3828819addf2b5"
    dhcp_options_id                      = "dopt-0a462db4507e948aa"
    enable_dns_hostnames                 = false
    enable_dns_support                   = true
    enable_network_address_usage_metrics = false
    id                                   = "vpc-0e84453a3f44de071"
    instance_tenancy                     = "default"
    ipv6_netmask_length                  = 0
    main_route_table_id                  = "rtb-00d4acadaea3e298c"
    owner_id                             = "xxxxxxxxxxxxx"
    tags                                 = {
        "Name" = "import-vpc"
    }
    tags_all                             = {
        "Name" = "import-vpc"
    }
}

このコードをコピーし、main.tfファイルのaws_vpcリソース内へ貼り付けます。

この時、不要な属性を削除する必要があるため、plan結果を見ながら適宜編集します。

# main.tf
resource "aws_vpc" "import_vpc" {
    assign_generated_ipv6_cidr_block     = false
    cidr_block                           = "10.0.0.0/16"
    enable_dns_hostnames                 = false
    enable_dns_support                   = true
    enable_network_address_usage_metrics = false
    instance_tenancy                     = "default"
    tags                                 = {
        "Name" = "import-vpc"
    }
    tags_all                             = {
        "Name" = "import-vpc"
    }
}

planの結果がNo Changesになれば完了です。

No changes. Your infrastructure matches the configuration.

S3バケットのインポート

S3バケットをインポートするため、main.tfにS3バケットリソースを記述します。

# {}の中は空でOK
resource "aws_s3_bucket" "import_s3" {
}

VPCと同様に、importコマンドでS3バケットの情報を取り込みます。

# S3バケットの場合、バケット名を指定してインポートする
terraform import aws_s3_bucket.import_s3 import-s3-bucket-1129

aws_s3_bucket.import_s3: Importing from ID "import-s3-bucket-xxxx"...
aws_s3_bucket.import_s3: Import prepared!
  Prepared aws_s3_bucket for import
aws_s3_bucket.import_s3: Refreshing state... [id=import-s3-bucket-xxxx]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

インポートが成功したら、tfstateファイルを確認します。

"resources": [
    {
      "mode": "managed",
      "type": "aws_s3_bucket",
      "name": "import_s3",
      "provider": "provider[\\"registry.terraform.io/hashicorp/aws\\"]",
      "instances": [
        {
          "schema_version": 0,
          "attributes": {
            "acceleration_status": "",
            "acl": null,
            "arn": "arn:aws:s3:::import-s3-bucket-xxxx",
            "bucket": "import-s3-bucket-xxxx",
......以下略

無事インポートできていることが確認できたら、terraform showコマンドでリソースの情報を取得し、main.tf内にコードを記述します。

# 今回はAWS CLIで作成したため、バケット名のみを記述する
resource "aws_s3_bucket" "import_s3" {
    bucket = "import-s3-bucket-1129" # ここを追加する
}

planを実行し、No Changesになることを確認します。

削除作業

importブロックの実践に入るために、取り込んだ情報とソースコードの削除を行います。

main.tf内のコードは全て削除し、tfstateファイル内のリソース情報は次のコマンドで削除を実行します。

terraform state rm リソースタイプ.リソース名

# 例
terraform state rm aws_vpc.import_vpc

terraform state rm aws_s3_bucket.import_s3

tfstateファイルの内容を確認し、VPC、S3の情報が削除されていることを確認します。

importブロックの実践

続いてimportブロックを記述し、作成したリソースをインポートします。

importブロックを使用する場合、以下のようにtoidの引数を記述します。

# 例
import {
  to = aws_instance.example # リソースタイプとリソース名を記述
  id = "i-abcd1234" # 対象リソースのIDを記述
}

https://developer.hashicorp.com/terraform/language/import

VPCとS3バケットのインポート

main.tf内にimportブロックを記述します。

import {
  id = "vpc-xxxxxxxxxxx"
  to = aws_vpc.import_vpc
}

import {
  id = "import-s3-bucket-xxxx" # S3バケットの場合、作成したバケット名を指定する
  to = aws_s3_bucket.import_s3
}

記述完了後、planを実行します。この時、-generate-config-out=xxxx.tfというオプションを指定します。こうすることで、新しくファイルが作成されその中にソースコードが生成されます。

ファイル名は任意の名前にすることができますが、すでにファイルが存在している場合エラーになるため注意が必要です。次のコマンドでは、import.tfファイルが作成されます。

terraform plan -generate-config-out=import.tf

コマンド実行時、以下のエラーが発生しますがインポート自体は完了しています。


│ Error: Missing required argument
│   with aws_vpc.import_vpc,
on import.tf line 12:
│   (source code not available)
"ipv6_netmask_length": all of `ipv6_ipam_pool_id,ipv6_netmask_length` must be specified

新しく作成されたファイルにコードが作成されていることが確認できたら、上記の該当箇所をコメントアウトします。

# import.tf
resource "aws_vpc" "import_vpc" {
  assign_generated_ipv6_cidr_block     = false
  cidr_block                           = "10.0.0.0/16"
  enable_dns_hostnames                 = false
  enable_dns_support                   = true
  enable_network_address_usage_metrics = false
  instance_tenancy                     = "default"
  ipv4_ipam_pool_id                    = null
  ipv4_netmask_length                  = null
  ipv6_cidr_block                      = null
  ipv6_cidr_block_network_border_group = null
  # ipv6_ipam_pool_id                    = null
  # ipv6_netmask_length                  = 0

この状態ではtfstateファイルにはリソースの情報が記載されていないため、情報を取り込むためapplyを実行します。

# 2 importedとなっていることが確認できる
Apply complete! Resources: 2 imported, 0 added, 0 changed, 0 destroyed.

tfstateファイルの内容を確認します。

インポートしたリソースの内容が取り込まれていることが確認できればインポートの完了です。

まとめ

こちらの記事では、Terraformの管理下ではない既存リソースを取り込むためのterraform importコマンド、importブロックについて解説、実践しました。

実際の業務の中では、既存のリソースを取り込まなければいけないシチュエーションも少なくないため、インポートへの理解は重要です。

運用しているTerraformのバージョンや環境に左右されるため、どちらを選択するかは検討が必要になります。

この記事を参考に、インポートへの理解を深めていただければ幸いです。

エンベーダー編集部

エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

関連記事