terraform import と Terraforming

先日 Terraform 0.7 がリリースされました。 Terraform 0.7 の目玉機能は、なんと言っても既存リソースの import terraform import ではないでしょうか。全世界の Terraform ユーザが長年待ちわびていた機能がついに搭載されたことになります。 あ、あと terraform.tfstate のバージョンが 1 から 3 に上がったので後方互換性が地味に失われているのも大きいですね…

さて、自分は1年以上前から既存リソースをコード化する手段として Terraforming を開発し今に至るまでメンテナンスしてきました。

github.com

現在ではそれなりの認知をいただき、リソース追加などで Pull Request も多くもらうようになりました。 そんなことをしていたので、既存リソース import の公式対応には注目している、むしろしなければならないような立ち位置となっています。

本記事では、terraform import を試しつつ Terraforming がこの先生きのこれるのかどうかを見ていきたいと思います。

terraform import

概要

terraform import は、既存のリソースを Terraform の管理下に置くための機能です。Terraform 0.7 時点では、tfstate(Terraform がリソース管理状態を把握する JSON)のみ生成できます。人間が書く tf ファイルの生成はできません。

公式ドキュメントはこれ => Import - Terraform by HashiCorp

This is a great way to slowly transition infrastructure to Terraform

とのことです。楽しみですね。

A future version of Terraform will fully generate configuration significantly simplifying this process.

とも言ってるので、いずれは tf の生成にも対応するのでしょう。

対応リソース

公式が提供しているだけあって、リリース時点で数多くのリソースが import 機能に対応しています。逆に言うとすべてのリソースが対応しているわけではありません。

対応リソース一覧 => Import: Resource Importability - Terraform by HashiCorp

上にあるリソースを数えてみたところ *1107種類のリソースが import に対応しています。AWS だけでなく、Azure や DigitalOcean、OpenStack など複数 provider に対応しているのも公式の強みですね。 意外と S3 系のリソースは対応していなかったりします。

実際に使ってみる

適当に EC2 インスタンスを1台立てました(以降、EC2 インスタンスの情報は一部マスキングした状態でお届けします)。

f:id:dtan4:20160818010212p:plain

おもむろに terraform import します。引数に Terraform 上でのリソース名 (tf ファイルの resource につける名前) と import するリソースの ID (ここでは EC2 instance ID) を指定してあげます。 実行すると、カレントディレクトリに terraform.tfstate が生成されます。

$ terraform import aws_instance.great-instance i-96163a09
aws_instance.great-instance: Importing from ID "i-96163a09"...
aws_instance.great-instance: Import complete!
  Imported aws_instance (ID: i-96163a09)
aws_instance.great-instance: Refreshing state... (ID: i-96163a09)

Import success! The resources imported are shown above. These are
now in your Terraform state. Import does not currently generate
configuration, so you must do this next. If you do not create configuration
for the above resources, then the next `terraform plan` will mark
them for destruction.

$ ls
terraform.tfstate

中身はこんな感じ。当たり前ですがちゃんと tfstate が生成されています。

{
    "version": 3,
    "terraform_version": "0.7.0",
    "serial": 0,
    "lineage": "c1f9c929-52e9-4b4a-897f-6c5be268e505",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.great-instance": {
                    "type": "aws_instance",
                    "primary": {
                        "id": "i-96163a09",
                        "attributes": {
                            "ami": "ami-374db956",
                            "availability_zone": "ap-northeast-1a",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "i-96163a09",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "key_name": "****",
                            "monitoring": "false",
                            "network_interface_id": "eni-********",
                            "private_dns": "ip-172-31-7-232.ap-northeast-1.compute.internal",
                            "private_ip": "172.31.7.232",
                            "public_dns": "ec2-52-196-13-225.ap-northeast-1.compute.amazonaws.com",
                            "public_ip": "52.196.13.225",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-********",
                            "tags.%": "1",
                            "tags.Name": "great-instance",
                            "tenancy": "default",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.**********": "sg-********"
                        },
                        "meta": {
                            "schema_version": "1"
                        }
                    },
                    "provider": "aws"
                }
            }
        }
    ]
}

ところで、terraform.tfstate がすでにある状態で terraform import するとどうなるのでしょうか。 もう一台インスタンスを立てて試してみました。

f:id:dtan4:20160818010244p:plain

import はちゃんとできます。

$ envchain dtan4 terraform import aws_instance.awesome-instance i-60103cff
aws_instance.awesome-instance: Importing from ID "i-60103cff"...
aws_instance.awesome-instance: Import complete!
  Imported aws_instance (ID: i-60103cff)
aws_instance.awesome-instance: Refreshing state... (ID: i-60103cff)

Import success! The resources imported are shown above. These are
now in your Terraform state. Import does not currently generate
configuration, so you must do this next. If you do not create configuration
for the above resources, then the next `terraform plan` will mark
them for destruction.

tfstate もきっちりマージされた状態になっています。serial もインクリメントされているのでこのまま terraform plan を実行しても問題ありません。

{
    "version": 3,
    "terraform_version": "0.7.0",
    "serial": 1,
    "lineage": "c1f9c929-52e9-4b4a-897f-6c5be268e505",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.awesome-instance": {
                    "type": "aws_instance",
                    "primary": {
                        "id": "i-60103cff",
                        "attributes": {
                            "ami": "ami-374db956",
                            "availability_zone": "ap-northeast-1a",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "i-60103cff",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "key_name": "****",
                            "monitoring": "false",
                            "network_interface_id": "eni-********",
                            "private_dns": "ip-172-31-15-63.ap-northeast-1.compute.internal",
                            "private_ip": "172.31.15.63",
                            "public_dns": "ec2-52-198-18-169.ap-northeast-1.compute.amazonaws.com",
                            "public_ip": "52.198.18.169",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-********",
                            "tags.%": "1",
                            "tags.Name": "awesome-instance",
                            "tenancy": "default",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.**********": "sg-********"
                        },
                        "meta": {
                            "schema_version": "1"
                        }
                    },
                    "provider": "aws"
                },
                "aws_instance.great-instance": {
                    "type": "aws_instance",
                    "primary": {
                        "id": "i-96163a09",
                        "attributes": {
                            "ami": "ami-374db956",
                            "availability_zone": "ap-northeast-1a",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "iam_instance_profile": "",
                            "id": "i-96163a09",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "key_name": "****",
                            "monitoring": "false",
                            "network_interface_id": "eni-********",
                            "private_dns": "ip-172-31-7-232.ap-northeast-1.compute.internal",
                            "private_ip": "172.31.7.232",
                            "public_dns": "ec2-52-196-13-225.ap-northeast-1.compute.amazonaws.com",
                            "public_ip": "52.196.13.225",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "0",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-********",
                            "tags.%": "1",
                            "tags.Name": "great-instance",
                            "tenancy": "default",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.**********": "sg-********"
                        },
                        "meta": {
                            "schema_version": "1"
                        }
                    },
                    "provider": "aws"
                }
            }
        }
    ]
}

あとは、この tfstate や Management Console で得られる情報を元に tf ファイルを書き、terraform plan で差分が出なければ既存リソースの import は完了です(今回はそこまでしません…)。

Terraforming との比較

それぞれ機能の比較をしてみました。

terraform import Terraforming
メンテナ HashiCorp @dtan4
対応リソース数 107 37
(↑うち AWS 67 35
AWS 以外の provider Azure, DigitalOcean, Fastly, OpenStack, Triton DNSimple, Datadog
全リソース一括 import x o (resource type 単位)
リソースを指定した import o x
tfstate の import o o
tf の import x o

メンテナ

まず、terraform import は言わずもがな HashiCorp 本家がやっているので安心感があります。Terraform でのリソースパラメータ追加にもリアルタイムで追従できるものと思われます。 たまにあるんですよね。Terraform のバージョンが上がってパラメータ足されたから、Terraforming の生成結果が仕様を満たさなくなることが…。 一方で Terraforming は @dtan4 が(ほぼ)プライベートの時間でメンテナンスをしています。最近 Issue, PR の消化が追いつかなくなってきて危機感を感じています。

リソース数

対応リソース数は歴然とした差があります。特に Terraforming は AWS 以外がめっぽう弱いです。AWS も基本自分が触るリソースを中心に対応しているので、ある程度偏りが出てしまうのは否めません。

AWS 以外の provider

AWS 以外の provider は、terraform import と Terraforming できっかり分かれました。ちなみに DNSimple と Datadog は、会社で使っているので特別に開発したという経緯があります。

全リソース / リソースを指定した import 機能

terraform import は全リソース一括 import ができないのが痛いですね。数台程度ならまだしも、多くの場合だと数十から数百のリソースを一気にコードに落とし込みたいケースになるのではないでしょうか。 awscli と連携すればできなくもなさそうですが…。この辺、Terraforming は普通に全リソースを攫ってくるようになっています。

逆に Terraforming リソース個別の import には対応していません。前々から要望はあったりするのですが、なかなか導入する気になれず…

tf の import

terraform import は tf の生成ができないのも現時点だと難しいですね。tfstate に比べるとまだ書きやすいですが、terraform plan で差分が出ないようきっちり書こうと思うとかなり神経を使います。 Terraforming は既存リソースの import をすべてツールに任せる思想で初めから開発していたので、tf 生成は対応しています。もちろん terraform plan で差分が出ないようチェックも行っています。

というわけで

自分の結論としては、現時点ではまだ Terraforming が生き残れるということです。

安心感とリソース対応数という点では完敗です。しかし、いま実際既存 AWS インフラのリソースを Terraform コードに落とし込みたい場合に使うとなると圧倒的に Terraforming の方がお手軽だといえます(リソースが対応している場合)。 コマンド一発で自分のアカウントが管理するリソースを一括 import する機能と tf の import、この2つが terraform import に実装される日までは Terraforming が使われて続けていくでしょう。使われ続けてほしい。雑なまとめだ!

今後も多くの場面で使ってもらえるよう、Terraforming のメンテナンスは精力的に続けていきます。みなさん引き続きご支援をよろしくお願いいたします。Issue, Pull Request 大歓迎です!

とりあえず Terraforming v0.10 をいい加減今週中に出さないとですね…

*1:Chrome DevTool Console で $('#main-content > div > ul > li').length