概要

はじめに

業務でTerraformのimportブロックでのリソース取り込みを行う機会があったので、使い方を記載します。

terraform importとは?

  • terraform importとは、Terraform管理外で作成されたリソースをtfstateに取り込むことを指します。
    • Terraform管理外とはAWSマネジメントコンソールやAWS CLIなどのTerraform以外のインフラ構築ツールのことです。
    • tfstateとは、Terraform管理対象のインフラストラクチャについて、その構成に関する状態を保存しておくファイルのことです。
      • terraformコマンド実行時に作成・更新・削除対象となるリソースは、このファイルに書かれたリソースのみとなります。
      • terraform importコマンドについてはこちらを参照
  • Terraform以外から作成したリソースは、tfstateに取り込まない限り、terraformは認識してくれません。
    • 例:マネジメントコンソールで作成したリソースについて、terraformコードに記載してterraform applyを実行した場合の挙動
      • terraform importを実行してリソースの情報をtfstateに取り込み済みの場合→terraformはコードとtfstateを比較し、差分なしと判断、何も実行されない
      • terraform importを実行しておらず、リソースの情報をtfstateに取り込んでいない場合→terraformはコードとtfstateを比較し、コードに記載されたリソースは作成されていないと判断。新規リソースとして作成を試みるが、実際には存在するリソースのため、エラーとなる
  • (参考)既存リソースをterraform管理下に置くのがterraform importコマンドですが、逆にリソースをterraform管理下から外すのにはterraform state rmコマンドを使います。

Terraformのimportブロックとは?

  • これまでは、リソースをTerraform管理下に取り込むには、terraform importコマンドを使うしか方法がありませんでした。
  • Terraformバージョン1.5から、importブロックをコード内に記載し、terraform applyコマンドを実行することで、取り込みが行えるようになりました。
    • これにより、Terraform管理外の既存リソースに更新を加えたい場合に、terraform importコマンド実行をする必要がなくなりました。
    • たくさんのリソースをTerraformコードに取り込みたい時に、これまではリソースごとにterraform importコマンドを実行する必要がありました。
    • これからはそのリソース分、importブロックをコードに記載すれば、一回のterraform applyコマンドで複数リソースを取り込むことができるようになり、選択肢が増えました。
    • importブロックについての公式ドキュメントはこちら

Terraformのimportブロックでのリソース取り込み①:使用方法確認

今回やりたいこと

  • まずはシンプルにimportブロックで取り込めることを確認してみます。
  • マネジメントコンソールで作成したS3バケットをimportブロックを使ってtfstateに取り込みます。

使用環境

Terraform v1.5.2

やってみる

取り込み対象のS3バケットをマネジメントコンソールから作成

名前とリージョン以外はすべてデフォルトのS3バケットを作成します。

  • S3バケット名:test-dev-terraform-import-1
  • リージョン:東京
  • それ以外はデフォルト

S3バケットの情報をTerraformコードに記載

先ほどマネジメントコンソールから作成したS3バケットと同じ名前のS3バケットをTerraformコードに記載します。

  • s3.tf
# Bucket

# terraform import test
resource "aws_s3_bucket" "terraform-import" {
  bucket = "test-dev-terraform-import-1"
}

terraform applyでエラーになることを確認

このコードにimportブロックを記載すれば取り込みが可能ですが、その前に、この状態でterraform applyをやったらどうなるか確認してみましょう。
まずはterraform planを実行して作成予定のリソースを確認します。

% terraform plan

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_s3_bucket.terraform-import will be created
    + resource "aws_s3_bucket" "terraform-import" {
      + acceleration_status = (known after apply)
      + acl = (known after apply)
      + arn = (known after apply)
      + bucket = "test-dev-terraform-import-1"
      + bucket_domain_name = (known after apply)
      + bucket_prefix = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy = false
      + hosted_zone_id = (known after apply)
      + id = (known after apply)
      + object_lock_enabled = (known after apply)
      + policy = (known after apply)
      + region = (known after apply)
      + request_payer = (known after apply)
      + tags_all = (known after apply)
      + website_domain = (known after apply)
      + website_endpoint = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

バケットが1つ作られる旨の出力が出ました。
このバケットは実際はすでに作成済みなんですが、terraform importを実行していないので、Terraformは新規作成リソースと認識しています。
それではterraform applyを実行してみます。まだリソース情報を取り込んでないので新規作成しようとしてバケット名重複エラーになるはずです。

% terraform apply

---terraform planの結果と同じのため省略---

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

aws_s3_bucket.terraform-import: Creating...
╷
│ Error: creating Amazon S3 (Simple Storage) Bucket (test-dev-terraform-import-1): BucketAlreadyOwnedByYou: Your previous request to create the named bucket succeeded and you already own it.
│ status code: 409, request id: {IDのためマスク}, host id: {IDのためマスク}
│
│ with aws_s3_bucket.terraform-import,
│ on s3.tf line 4, in resource "aws_s3_bucket" "terraform-import":
│ 4: resource "aws_s3_bucket" "terraform-import" {

思った通りエラーになりました!
「そのバケットはすでにあなたが作成して所有済みです(BucketAlreadyOwnedByYou)」と警告してくれています。

importブロックを記載してterraform applyを実行し取り込み

それでは本題のimportブロックを試してみましょう。
S3について定義していたTerraformコードにimportブロックを追加してみます。

  • s3.tf
# Bucket

# import
import {
  id="test-dev-terraform-import-1"
  to=aws_s3_bucket.terraform-import
}

# terraform import test
  resource "aws_s3_bucket" "terraform-import" {
  bucket="test-dev-terraform-import-1"
}

importブロックの記載方法は以下の通りです。

  • idには取り込み対象リソースの識別子を記載
    • ここでの識別子は文字列リテラルでないといけないとのことで、今回はS3バケット名を直接記入しています
    • var.bucketのように、他の場所で定義した変数を指定することはできないということですね
    • バージョン1.6からはこの制約はなくなったとのこと(あとで試します!)
  • toにはterraformコード内のインポート先を記載
    • “リソースタイプ.ローカル名”の形式で記載します

この状態でterraform applyを行えばインポートができますが、その前に現時点でのtfstateの中身を確認してみます。
tfstateに記載されたリソースを一覧出力してくれるコマンドterraform state listを実行します。

% terraform state list
%

何も出力されない、ということはtfstateには何のリソースも記述されておらず、terraform管理下のリソースが何もないことを表しています。
importブロックによる取り込みを実行した後どのように変わるのか、後ほど確認します。

それではterraform applyに移ります。まずはterraform planで作成予定のリソースを確認します。

% terraform plan
aws_s3_bucket.terraform-import: Preparing import... [id=test-dev-terraform-import-1]
aws_s3_bucket.terraform-import: Refreshing state... [id=test-dev-terraform-import-1]

Terraform will perform the following actions:

  # aws_s3_bucket.terraform-import will be imported
    resource "aws_s3_bucket" "terraform-import" {
      arn = "arn:aws:s3:::test-dev-terraform-import-1"
      bucket = "test-dev-terraform-import-1"
      bucket_domain_name = "test-dev-terraform-import-1.s3.amazonaws.com"
      bucket_regional_domain_name = "test-dev-terraform-import-1.s3.ap-northeast-1.amazonaws.com"
      hosted_zone_id = {IDのためマスク}
      id = "test-dev-terraform-import-1"
      object_lock_enabled = false
      region = "ap-northeast-1"
      request_payer = "BucketOwner"
      tags = {}
      tags_all = {}

      grant {
        id = {IDのためマスク}
        permissions = [
          "FULL_CONTROL",
        ]
        type = "CanonicalUser"
      }

      server_side_encryption_configuration {
        rule {
          bucket_key_enabled = true

          apply_server_side_encryption_by_default {
            sse_algorithm = "AES256"
          }
        }
      }

      versioning {
        enabled = false
        mfa_delete = false
      }
    }

    Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

取り込み対象が1件(1 to import)と表示されているので想定通りです。
terraform applyを実行し、取り込みを行います。

% terraform apply
aws_s3_bucket.terraform-import: Preparing import... [id=test-dev-terraform-import-1]
aws_s3_bucket.terraform-import: Refreshing state... [id=test-dev-terraform-import-1]

Terraform will perform the following actions:

  # aws_s3_bucket.terraform-import will be imported
    resource "aws_s3_bucket" "terraform-import" {
        arn                         = "arn:aws:s3:::test-dev-terraform-import-1"
        bucket                      = "test-dev-terraform-import-1"
        bucket_domain_name          = "test-dev-terraform-import-1.s3.amazonaws.com"
        bucket_regional_domain_name = "test-dev-terraform-import-1.s3.ap-northeast-1.amazonaws.com"
        hosted_zone_id              = {IDのためマスク}
        id                          = "test-dev-terraform-import-1"
        object_lock_enabled         = false
        region                      = "ap-northeast-1"
        request_payer               = "BucketOwner"
        tags                        = {}
        tags_all                    = {}

        grant {
            id          = {IDのためマスク}
            permissions = [
                "FULL_CONTROL",
            ]
            type        = "CanonicalUser"
        }

        server_side_encryption_configuration {
            rule {
                bucket_key_enabled = true

                apply_server_side_encryption_by_default {
                    sse_algorithm = "AES256"
                }
            }
        }

        versioning {
            enabled    = false
            mfa_delete = false
        }
    }

Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket.terraform-import: Importing... [id=test-dev-terraform-import-1]
aws_s3_bucket.terraform-import: Import complete [id=test-dev-terraform-import-1]

Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.

取り込みが成功したようです。
それではterraform state listでtfstateの中身を確認してみます。

% terraform state list
aws_s3_bucket.terraform-import

取り込んだS3バケットが表示されました!

Terraformのimportブロックでのリソース取り込み②:バージョン1.6でのアップデート内容確認

今回やりたいこと

  • importブロックでの基本的な取り込み方法は上の検証で理解することができました
  • 続いてバージョン1.6で可能になった文字列リテラル以外を指定した取り込みを試してみます
    • 上の検証ではバージョン1.5を使用したため、importブロックでの取り込み対象S3バケットの指定はバケット名を直接記載するしかありませんでした。
    • バージョン1.6から文字列リテラル以外でも指定が可能になったようなので、変数で指定ができるかどうか試してみます。

使用環境

Terraform v1.6.2

やってみる

準備作業

  • 上で作成したS3バケットを削除&再作成
    • もう一度importを行いたいので、terraform destroyでバケットを削除し、再度マネジメントコンソールから作成を行いました
  • terraformのバージョンを1.6にアップグレード

Terraformコード記載

まずはimportブロックで指定するための変数をvariables.tfに記載していきます。

  • variables.tf
variable "bucket-name" {
  type = map(string)
  default = {
    bucket_name_1 = "test-dev-terraform-import-1"
  }
}

続いてimport先のS3バケットについての定義をs3.tfに記載します。
バケット名はvariables.tfに定義した変数”bucket-name”を使用して指定します。

  • s3.tf
# Bucket
# terraform import test
resource "aws_s3_bucket" "terraform-import" {
  bucket = var.bucket-name.bucket_name_1
}

変数とimport先が記入できたので、importブロックを記載していきます。
先ほどはimportブロックをS3の定義の書かれたs3.tfに記載していましたが、今回はインポート用にファイルを分けてみました。

  • import.tf
# S3 bucket
import {
  id = var.bucket-name.bucket_name_1
  to = aws_s3_bucket.terraform-import
}

terraform applyを実行し取り込み

現時点で何も取り込みが行われていないため、tfstateの中身は空です。
terraform state listコマンドで確認します。

% terraform state list
% 

出力が何も返ってこないため、tfstateの中身が想定通り空であることがわかります。

それではTerraformコードの準備もできているので、terraform applyを実行して取り込みを行います。
その前にterraform planでコードの反映が想定通り行えるかどうかを確認してみます。

% terraform plan
aws_s3_bucket.terraform-import: Preparing import... [id=test-dev-terraform-import-1]
aws_s3_bucket.terraform-import: Refreshing state... [id=test-dev-terraform-import-1]

Terraform will perform the following actions:

  # aws_s3_bucket.terraform-import will be imported
    resource "aws_s3_bucket" "terraform-import" {
        arn                         = "arn:aws:s3:::test-dev-terraform-import-1"
        bucket                      = "test-dev-terraform-import-1"
        bucket_domain_name          = "test-dev-terraform-import-1.s3.amazonaws.com"
        bucket_regional_domain_name = "test-dev-terraform-import-1.s3.ap-northeast-1.amazonaws.com"
        hosted_zone_id              = {IDのためマスク}
        id                          = "test-dev-terraform-import-1"
        object_lock_enabled         = false
        region                      = "ap-northeast-1"
        request_payer               = "BucketOwner"
        tags                        = {}
        tags_all                    = {}

        grant {
            id          = {IDのためマスク}
            permissions = [
                "FULL_CONTROL",
            ]
            type        = "CanonicalUser"
        }

        server_side_encryption_configuration {
            rule {
                bucket_key_enabled = true

                apply_server_side_encryption_by_default {
                    sse_algorithm = "AES256"
                }
            }
        }

        versioning {
            enabled    = false
            mfa_delete = false
        }
    }

Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

作成済みのS3バケット1件が取り込まれる旨が出力されています。
想定通りのため、terraform applyを実行します。

% terraform apply 
aws_s3_bucket.terraform-import: Preparing import... [id=test-dev-terraform-import-1]
aws_s3_bucket.terraform-import: Refreshing state... [id=test-dev-terraform-import-1]

Terraform will perform the following actions:

  # aws_s3_bucket.terraform-import will be imported
    resource "aws_s3_bucket" "terraform-import" {
        arn                         = "arn:aws:s3:::test-dev-terraform-import-1"
        bucket                      = "test-dev-terraform-import-1"
        bucket_domain_name          = "test-dev-terraform-import-1.s3.amazonaws.com"
        bucket_regional_domain_name = "test-dev-terraform-import-1.s3.ap-northeast-1.amazonaws.com"
        hosted_zone_id              = {IDのためマスク}
        id                          = "test-dev-terraform-import-1"
        object_lock_enabled         = false
        region                      = "ap-northeast-1"
        request_payer               = "BucketOwner"
        tags                        = {}
        tags_all                    = {}

        grant {
            id          = {IDのためマスク}
            permissions = [
                "FULL_CONTROL",
            ]
            type        = "CanonicalUser"
        }

        server_side_encryption_configuration {
            rule {
                bucket_key_enabled = true

                apply_server_side_encryption_by_default {
                    sse_algorithm = "AES256"
                }
            }
        }

        versioning {
            enabled    = false
            mfa_delete = false
        }
    }

Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_s3_bucket.terraform-import: Importing... [id=test-dev-terraform-import-1]
aws_s3_bucket.terraform-import: Import complete [id=test-dev-terraform-import-1]

Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.

無事importできたようです。
tfstateの中身を確認してみます。

% terraform state list
aws_s3_bucket.terraform-import
% 

S3バケットの情報が出力されたため、無事S3バケットがTerraform管理下にimportできたことが確認できました!

取り込み完了後のimportブロックについて

無事に取り込みが行われたことが確認できたらimportブロックは削除してOKです。
上記の例ではimport.tfというファイルでimportブロックを書いていたので、ファイルごと削除してみます。

import.tfを削除後にterraform planを実行してみて、削除による影響がないことを確認してみました。

% terraform plan
aws_s3_bucket.terraform-import: Refreshing state... [id=test-dev-terraform-import-1]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

差分が出力されなかったため、importブロックを削除しても影響がないことがわかります。

おわりに

今回は既存リソースをTerraform管理下に取り込むためのimportブロックを試してみました。
コマンド&コード両方で同じことが実行可能だとデプロイの選択肢が広がりそうで良いですね。

for_eachと組み合わせてループで取り込みを行えるという内容をTerrraform公式ドキュメントで確認できたので、そのうち試してみたいと思います。