for_eachで書いた.tfをterraform importする

terraform importを使って、for_eachで書いたものをそのままimportする方法を紹介します。既存環境をコード化したいけど、ベタ書きは嫌!なるべくスマートに書きたい。という方に向けた記事です。
2021.06.16

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちわ、ネクストモードの島川です。

terraform importを使って、for_eachで書いたものをそのままimportする方法を紹介します。既存環境をコード化したいけど、ベタ書きは嫌!なるべくスマートに書きたい。という方に向けた記事です。

環境

$ terraform -v
Terraform v1.0.0

せっかくなので最近リリースされたv1.0.0を使用します。

importする際に必要な情報

基本的なことになりますが、terraform importをする際に必要な情報はユーザーが定義したnameであったり自動生成されたidになります。実際のリソースの種類によって変わるのでそこは事前にTerraformのドキュメントを参照してください。

ちなみにAWSのEC2であればインスタンスIDがimportの際に必要な情報になります。(例:i-12345678)

ドキュメントはここ

importする

AWSのEC2をimportする例を今回は紹介します。

for_each

tfファイルの準備

まずはimportするためのtfファイルを書いていきます。分かるのであれば最初から情報を埋めてもいいですし、分からないのであればリソース定義だけしておくでも大丈夫です。今回はリソースの定義だけ最初にしておいてあとから埋める方法で進めようと思います。

  • import前のコードの状況

    main.tf

    locals {
      ec2_name = { 
        hoge-server-1 = {
          instance_type = "t3.micro"
          ami           = "ami-xxxxxxxxxx"
        },
        hoge-server-2 = {
          instance_type = "t2.micro"
          ami           = "ami-xxxxxxxxxx"
        }
      }
    }
    
    resource "aws_instance" "foo" {
      for_each      = local.ec2_name
    }

for_eachをimportする

terraform importコマンドを使ってimportしていきます。for_eachのリソースは「リソースの種類.リソース名["配列名"]」で表されます。今回の場合は以下のように表すことができます。

  • aws_instance.foo["hoge-server-1"]
  • aws_instance.foo["hoge-server-2"]

インスタンスIDを確認したのち、実際にimportをしていきます。一点だけ注意点があり、UNIX系OSの場合は要素全体を''シングルクォーテーションで囲んであげる必要があります。

  • 参考

$ terraform import 'aws_instance.foo["hoge-server-1"]' i-1111122222
$ terraform import 'aws_instance.foo["hoge-server-2"]' i-aaaaabbbbb

成功するとこんな表示がでます。

aws_instance.foo["hoge-server-1"]: Importing from ID "i-1111122222"...
aws_instance.foo["hoge-server-1"]: Import prepared!
  Prepared aws_instance for import
aws_instance.foo["hoge-server-1"]: Refreshing state... [id=i-1111122222]

importされた内容を確認する。

まずはterraform state listでリソースがあるか確認します。

$ terraform state list
aws_instance.foo["hoge-server-1"]
aws_instance.foo["hoge-server-2"]

次にterraform state showで中身を確認していきます。ここでも要素全体を''シングルクォーテーションで囲む必要があります。

$ terraform state show 'aws_instance.foo["hoge-server-1"]'
# aws_instance.foo["hoge-server-1"]:
resource "aws_instance" "foo" {
    ami                                  = "ami-xxxxxxxxxx"
    arn                                  = "arn:aws:ec2:ap-northeast-1:123456789123:instance/i-xxxxxxxxxx"
    associate_public_ip_address          = false
    availability_zone                    = "ap-northeast-1a"
    cpu_core_count                       = 1
    cpu_threads_per_core                 = 2
    disable_api_termination              = false
    ebs_optimized                        = true
    get_password_data                    = false
    hibernation                          = false
    id                                   = "i-xxxxxxxxxx"
    instance_initiated_shutdown_behavior = "stop"
    instance_state                       = "stopped"
    instance_type                        = "t3.micro"
    ipv6_address_count                   = 0
    ipv6_addresses                       = []
    key_name                             = "ec2keyname"
    monitoring                           = false
    primary_network_interface_id         = "eni-xxxxxxxxxxxx"
    private_dns                          = "ip-10-210-10-67.ap-northeast-1.compute.internal"
    private_ip                           = "10.210.10.67"
    secondary_private_ips                = []
    security_groups                      = []
    source_dest_check                    = true
    subnet_id                            = "subnet-xxxxxxxxxxxx"
    tags                                 = {
        "Name" = "hoge-server"
    }
    tags_all                             = {
        "Name" = "hoge-server"
    }
    tenancy                              = "default"
    vpc_security_group_ids               = [
        "sg-xxxxxxxxxxxx",
    ]

    capacity_reservation_specification {
        capacity_reservation_preference = "open"
    }

    credit_specification {
        cpu_credits = "standard"
    }

    enclave_options {
        enabled = false
    }

    metadata_options {
        http_endpoint               = "enabled"
        http_put_response_hop_limit = 1
        http_tokens                 = "optional"
    }

    root_block_device {
        delete_on_termination = true
        device_name           = "/dev/xvda"
        encrypted             = true
        iops                  = 100
        kms_key_id            = "arn:aws:kms:ap-northeast-1:123456789123:key/xxxxxxxxxxxx"
        tags                  = {
            "Name" = "hoge-server"
        }
        throughput            = 0
        volume_id             = "vol-xxxxxxxxxxxxx"
        volume_size           = 20
        volume_type           = "gp2"
    }

    timeouts {}
}

.tfファイルを書き足していく

このままterraform planをしても必要最低限の情報が無いため失敗します。また必要最低限のamiやinstance typeを追記してもその他の情報がないためインスタンスを作り直す動きをしてしまいます。そのためterraform state showの内容を元に.tfを書き足していく必要があります。サブネット情報等も必要になるので場合によってはそこからimportをかけていく必要もあります。

まとめ

$ terraform import 'リソースの種類.リソース名["要素名"]' リソース識別子

で、for_each形式の状態でimportすることができます。同じ要領でcountやmoduleもimportすることができます。terraformで作成されたリソースの完全名を把握しておくことが大事です。またこのimportの注意点として本来であれば自動で生成されるはずだったリソースに関しても.tfに記述していくことになりますので、結構時間がかかります。terraformerというツールもありますので、for_eachじゃなくてベタ書きでもいいという場合はその選択肢も考えてみてください。