Terraformで既存のインフラリソースをインポートする方法


インフラエンジニアの寺岡です。
皆さんは「既存のインフラ構成もTerraformで管理できるようにしたい」と考えたことはないでしょうか。
Terraformではそれを実現するコマンドが用意されているのでこの記事で紹介します。

事前にTerraformを利用せずにVPCを1つ作成しておきました。

今回はこちらをTerraformにインポートしてみます。

ディレクトリ構成

$ tree
.
├── README.md
├── provider.tf
├── terraform.tfstate
├── variables.tf
└── vpc.tf

0 directories, 5 files

providerとvariableを記述する

インポートする前にproviderとvariableは最低限必要なので記述します。

provider.tf

provider "aws" {
  access_key = var.access_key
  secret_key = var.secret_key
  region     = var.region

  assume_role {
    role_arn = var.role_arn
  }
}

variables.tf

####################
# Provider
####################
variable "access_key" {
  description = "AWS Access Key"
}

variable "secret_key" {
  description = "AWS Secret Key"
}

variable "role_arn" {
  description = "AWS Role Arn"
}

variable "region" {
  default = "ap-northeast-1"
}

AWSのアクセスキーなどは環境変数に保存しておきましょう。

$ export TF_VAR_access_key=XXXXXXXXXXXXXXXXXXXX
$ export TF_VAR_secret_key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
$ export TF_VAR_role_arn=arn:aws:iam::XXXXXXXXXXXX:role/XXXXXXXXXXXXXXXXXXX

planを実行する

試しにPlanを実行します。
今は何もインポートしていないので「No changes.」で実行が終わります。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

importを実行する

Terraformで既存リソースをインポートする場合は「terraform import」コマンドを実行します。
コマンドの書式はTerraformのマニュアル(※1)に記載があるのでそちらを参考にして以下のコマンドを実行します。

$ terraform import aws_vpc.vpc vpc-09a9f1827bfd851f4
Error: resource address "aws_vpc.vpc" does not exist in the configuration.

Before importing this resource, please create its configuration in the root module. For example:

resource "aws_vpc" "vpc" {
  # (resource arguments)
}

エラーが出てしまいました。
インポートするときは「aws_vpc.vpc」のようにリソースの種類と名前を指定することになり
ここで指定したtfファイル上のリソースに対して既存リソースの情報がインポートされることになります。
そのため、予め対応するリソースをtfファイルに記述しておく必要があります。

vpcリソースを記述する

以下のように記載しておきます。

vpc.tf

####################
# VPC
####################
resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "vpc-tfstate-test"
  }
}

再度importを実行する

再度実行してみます。
今度は問題なくインポートできましたね。

$ terraform import aws_vpc.vpc vpc-09a9f1827bfd851f4
aws_vpc.vpc: Importing from ID "vpc-09a9f1827bfd851f4"...
aws_vpc.vpc: Import prepared!
  Prepared aws_vpc for import
aws_vpc.vpc: Refreshing state... [id=vpc-09a9f1827bfd851f4]

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.

このterraform importは何をやってくれているのでしょうか。
コマンドを実行すると「terraform.tfstate」という名前のファイルが作成されていることがわかります。

terraform.tfstate

{
  "version": 4,
  "terraform_version": "0.12.24",
  "serial": 1,
  "lineage": "c0359eb1-d905-e252-2d8d-525710adddb1",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_vpc",
      "name": "vpc",
      "provider": "provider.aws",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "arn": "arn:aws:ec2:ap-northeast-1:485076298277:vpc/vpc-09a9f1827bfd851f4",
            "assign_generated_ipv6_cidr_block": false,
            "cidr_block": "10.0.0.0/16",
            "default_network_acl_id": "acl-0cd8abfed52e0e951",
            "default_route_table_id": "rtb-08c13269b1b26c9b8",
            "default_security_group_id": "sg-007ef29e563b6f9c7",
            "dhcp_options_id": "dopt-6c0f430b",
            "enable_classiclink": false,
            "enable_classiclink_dns_support": false,
            "enable_dns_hostnames": true,
            "enable_dns_support": true,
            "id": "vpc-09a9f1827bfd851f4",
            "instance_tenancy": "default",
            "ipv6_association_id": "",
            "ipv6_cidr_block": "",
            "main_route_table_id": "rtb-08c13269b1b26c9b8",
            "owner_id": "485076298277",
            "tags": {
              "Name": "vpc-tfstate-test"
            }
          },
          "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="
        }
      ]
    }
  ]
}

中身を見ると先ほどインポートしたリソースの情報が保存されていますね。
Terraformは実行したときに新規作成 or 変更 or 削除を自動で判断してリソースを作成してくれますが
この判断は実行時に読み込んだtfファイルとtfstateに書かれている内容の差分を元に行われています。
つまり、指定した既存リソースをTerraform上で管理するためにはtfstateの内容を変更する必要があり
「指定した既存リソースの情報を読み込みtfstateに追加してくれるコマンド」がterraform importです。

再度planを実行する

terraform importを実行したので再度planを実行してみましょう。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

aws_vpc.vpc: Refreshing state... [id=vpc-09a9f1827bfd851f4]

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

aws_vpc.vpcがTerraform上のリソースとして読み込まれていますね。
enable_dns_hostnamesをfalseに変更してみましょう。

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

aws_vpc.vpc: Refreshing state... [id=vpc-09a9f1827bfd851f4]

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_vpc.vpc will be updated in-place
  ~ resource "aws_vpc" "vpc" {
        arn                              = "arn:aws:ec2:ap-northeast-1:485076298277:vpc/vpc-09a9f1827bfd851f4"
        assign_generated_ipv6_cidr_block = false
        cidr_block                       = "10.0.0.0/16"
        default_network_acl_id           = "acl-0cd8abfed52e0e951"
        default_route_table_id           = "rtb-08c13269b1b26c9b8"
        default_security_group_id        = "sg-007ef29e563b6f9c7"
        dhcp_options_id                  = "dopt-6c0f430b"
        enable_classiclink               = false
        enable_classiclink_dns_support   = false
      ~ enable_dns_hostnames             = true -> false
        enable_dns_support               = true
        id                               = "vpc-09a9f1827bfd851f4"
        instance_tenancy                 = "default"
        main_route_table_id              = "rtb-08c13269b1b26c9b8"
        owner_id                         = "485076298277"
        tags                             = {
            "Name" = "vpc-tfstate-test"
        }
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

差分のみを変更しようとする挙動になっていますね。

まとめ

importコマンドの説明ページ(※2)にも記載されていますが
コマンドで書き換えてくれるのはあくまで「tfstateのみ」です。
つまり、実際のtfファイルについてはtfstateとの差分を見ながら自身で記述する必要があります。
将来的にはtfファイルも自動生成されるように機能追加されるようですが
現在のところは対象が多ければ多いほど辛くなってきます。。。

それを解決するためにterraformer(※3)がOSSとして公開されています。
次回はこちらの使い方をブログにまとめようと思います。

参考URL

※1 https://www.terraform.io/docs/providers/aws/r/vpc.html
※2 https://www.terraform.io/docs/import/index.html
※3 https://github.com/GoogleCloudPlatform/terraformer


この記事をかいた人

About the author

寺岡佑樹

2016年新卒入社、現在5年目。
SREとして、社内の運用業務の仕組みの検討・実装をしつつ
社外で技術的な登壇を行うエバンジェリスト的な側面も持つ。

・GitHub
https://github.com/nezumisannn

・登壇経歴
https://github.com/nezumisannn/my-profile

・発表資料(SpeakerDeck)
https://speakerdeck.com/nezumisannn