Infrastructure as Codeを少し楽にするterraformerが出た

過去に [Terraform Best Practices in 2017](https://qiita.com/shogomuranushi/items/e2f3ff3cfdcacdd17f99) という記事を書きました。

そのあと、2019年2月のJAWS Days [Infrastructure as Codeに疲れたので、僕たちが本来やりたかったことを整理する](https://speakerdeck.com/shogomuranushi/infrastructure-as-code-is-very-tired)を話しました。

その中で、Dev環境を手で作業して、その内容をExportして、Stg, Prodに展開出来たら良いのになー。という話をしていたのですが、それの兆しが見えるツールが登場したので紹介します。

# terraformer
2019年5月頭にひっそりと terraformer という何かと間違えるようなツールが出ていました。
少し驚いたのが、提供元がGoogleCloudPlatformでした。

https://github.com/GoogleCloudPlatform/terraformer

> CLI tool to generate tf and tfstate files from existing infrastructure (reverse Terraform).

既存の環境を確認してtfファイルとtfstateファイルを出力してくれるとな。
しかも、GoogleCloudPlatformが出しているだけあってGCPが中心だけど、AWSやKubernetes、Github、Datadogまで同じように出力可能だと。公開1ヶ月で1,000スター超え。中々なお手前で。
ちなみに、まだ公開1ヶ月程度なので対象となるリソースはそこまで多くない。けど1ヶ月にしては十分なリソースをサポートしている。

# やってみた
## 1. インストール
```console
# Terraform入れてない人は
$ brew install terraform

# ここからterraformer
$ git clone https://github.com/GoogleCloudPlatform/terraformer.git
$ cd terraformer
$ GO111MODULE=on go mod vendor
$ go build -v
$ echo 'provider "aws" {}' > init.tf
$ terraform init
```

## 2. サンプル通りにVPCとSubnetを出力
注）SwitchRoleはまだ対応していません。IAMユーザを利用ください。
対象が多い場合は少し時間がかかります。

```console
$ terraformer import aws --resources=vpc,subnet --connect=true --regions=eu-west-1
2019/06/08 14:11:45 aws importing region eu-west-1
2019/06/08 14:11:45 aws importing... vpc
2019/06/08 14:12:04 aws importing... subnet
2019/06/08 14:12:12 aws Connecting....
2019/06/08 14:12:12 aws save vpc
2019/06/08 14:12:12 [DEBUG] New state was assigned lineage "24ce6300-2fa2-48cc-ec79-0e85f0b06293"
2019/06/08 14:12:12 aws save tfstate for vpc
2019/06/08 14:12:12 aws save subnet
2019/06/08 14:12:12 [DEBUG] New state was assigned lineage "271b76a2-eb11-73e8-d834-f69de988381e"
2019/06/08 14:12:12 aws save tfstate for subnet
```

## 3. 中身を確認
こんな感じのディレクトリなんだけれども、 generated ってディレクトリにtfファイル等が作成されるみたいです。

```console
$ ls -l
total 214976
-rw-r--r--   1 shogo.muranushi  staff        302  6  6 15:17 AUTHORS
-rw-r--r--   1 shogo.muranushi  staff       3210  6  6 15:17 CODE_OF_CONDUCT.md
-rw-r--r--   1 shogo.muranushi  staff       1100  6  6 15:17 CONTRIBUTING.md
-rw-r--r--   1 shogo.muranushi  staff      11357  6  6 15:17 LICENSE
-rw-r--r--   1 shogo.muranushi  staff      15473  6  6 15:17 README.md
drwxr-xr-x  11 shogo.muranushi  staff        352  6  6 15:17 cmd
drwxr-xr-x   3 shogo.muranushi  staff         96  6  6 15:17 docs
drwxr-xr-x   3 shogo.muranushi  staff         96  6  8 14:12 generated
-rw-r--r--   1 shogo.muranushi  staff       5726  6  6 15:17 go.mod
-rw-r--r--   1 shogo.muranushi  staff      58248  6  6 15:17 go.sum
-rw-r--r--   1 shogo.muranushi  staff         18  6  6 15:25 init.tf
-rw-r--r--   1 shogo.muranushi  staff        783  6  6 15:17 main.go
drwxr-xr-x   8 shogo.muranushi  staff        256  6  6 15:17 providers
drwxr-xr-x  12 shogo.muranushi  staff        384  6  6 15:17 terraform_utils
-rwxr-xr-x   1 shogo.muranushi  staff  102264716  6  6 15:22 terraformer
drwxr-xr-x   7 shogo.muranushi  staff        224  6  6 15:17 tests
drwxr-xr-x  10 shogo.muranushi  staff        320  6  6 15:18 vendor
```

面白いのが、aws/subnet/eu-west-1 単体で terraform plan とかできるように provider.tf とか生成されているんですよね。

```
$ tree generated
generated
└── aws
    ├── subnet
    │   └── eu-west-1
    │       ├── outputs.tf
    │       ├── provider.tf
    │       ├── subnet.tf
    │       ├── terraform.tfstate
    │       └── variables.tf
    └── vpc
        └── eu-west-1
            ├── outputs.tf
            ├── provider.tf
            ├── terraform.tfstate
            └── vpc.tf
```

さらに、subnetの中を見ると、vpc_idはちゃんと aws/vpc/eu-west-1/outputs.tfを参照するようになっている。賢い。

```console
$ cat generated/aws/subnet/eu-west-1/subnet.tf
resource "aws_subnet" "subnet-93463df7" {
  assign_ipv6_address_on_creation = false
  cidr_block                      = "172.31.0.0/20"
  map_public_ip_on_launch         = true
  tags                            {}
  vpc_id                          = "${data.terraform_remote_state.vpc.aws_vpc_vpc-0d593d69_id}"
}

resource "aws_subnet" "subnet-ceddabb8" {
  assign_ipv6_address_on_creation = false
  cidr_block                      = "172.31.16.0/20"
  map_public_ip_on_launch         = true
  tags                            {}
  vpc_id                          = "${data.terraform_remote_state.vpc.aws_vpc_vpc-0d593d69_id}"
}

resource "aws_subnet" "subnet-de4acf86" {
  assign_ipv6_address_on_creation = false
  cidr_block                      = "172.31.32.0/20"
  map_public_ip_on_launch         = true
  tags                            {}
  vpc_id                          = "${data.terraform_remote_state.vpc.aws_vpc_vpc-0d593d69_id}"
}
```

なるほど。これはExportとしては優秀だ。以前からあった terraforming はここまでやってくれなかったような。

こんな感じで各リソースと連動してExportしてくれると、ハードコードもなくなるし書かなくてよくなるな（そんなん出来るんかいな）。と思いつつ、他のリソースを取得してみた。

## 4. Route53のレコードとALBは連動してくれるのか
Route53でALBとドメインを紐付けることも多いと思います。ALBが変わったらRoute53のレコードを自動で更新するような事もあると思います。
上のように data リソースで参照すると自動更新はできるのですが、ハードコードだとできません。
果たしてハードコードなのか、 data リソースなのか。

```console
$ terraformer import aws --resources=route53,alb --connect=true --regions=us-west-2
2019/06/08 14:26:45 aws importing region us-west-2
2019/06/08 14:26:45 aws importing... route53
2019/06/08 14:27:13 aws importing... alb
2019/06/08 14:27:47 aws Connecting....
2019/06/08 14:27:47 aws save alb
2019/06/08 14:27:48 [DEBUG] New state was assigned lineage "f8158b24-0428-83ff-e206-18fda16fff3a"
2019/06/08 14:27:48 aws save tfstate for alb
2019/06/08 14:27:48 aws save route53
2019/06/08 14:27:48 [DEBUG] New state was assigned lineage "a5815164-aba8-639e-396f-4629b26d2adc"
2019/06/08 14:27:48 aws save tfstate for route53
```

さて、 Route53 のレコードの値は data リソースなのか、ハードコードなのか

```console
$ cat generated/aws/route53/us-west-2/route53_record.tf
resource "aws_route53_record" "ZZZZZZZZZZZZZZ_api--test--dev--abeja--test--_A" {
  alias {
    evaluate_target_health = false
    name                   = "internal-xxx-internal-alb-123456789.us-west-2.elb.amazonaws.com"
    zone_id                = "ZXXXXXXXXXXXXX"
  }

  name    = "api.test.dev.abeja.test"
  type    = "A"
  zone_id = "${aws_route53_zone.ZZZZZZZZZZZZZZ_dev--abeja--test.zone_id}"
}
```

残念でした。ALBの値はハードコードでした。ただ、 Route53 内の zone_id はリソース指定で参照してますね。ということは、ただ値を出力するだけのツールではなさそう。
これから連動する部分が増えてくると使い回しやすくなるので嬉しい。

# terraformerのまとめ
**Exporterとしてはかなり優秀。GCPやKubernetes、Github、Datadogもあるしね**

以下のユースケースの際に Infrastructure as Code を維持しようとするとかなりしんどいんですよね。

- 手作業で作った割と大きくなった既存環境をコード化する
- コード化を維持して頑張ってるんだけど、誰かがたまに手作業をしてしまったので整合取る必要がある

**特に前者の場合は、既存の環境をそのままコード化できるので、かなり入り口としては低くなった印象です。**

# で、Dev環境を手で作業して、その内容をExportして、Stg, Prodに展開出来たら良いのになー。は実現出来るのか？

## 現時点では全てを実施するには **厳しい**

というのも、上の例で行くと ALB の接続先はハードコードなので、そのままstg環境に plan/apply してもdevの接続先に向くので意味ありません。

また、s3バケットも出力できましたが、s3バケット名はグローバルでユニークである必要があるため、ハードコードされたコードを実行しても重複エラーが出ます。

つまり、グローバルでユニークな値が存在するサービス（URLやS3バケット名など）に関してはまだ難しい状態です。

## ただし、ものによっては実現可能
ただし、VPC や Subnet などグローバルで一意の値が必要ないものに関しては、簡単に流用できました。

```console
# Dev 環境の VPC, Subnet を Export
$ terraformer import aws --resources=vpc,subnet --connect=true --regions=eu-west-1
# IAM を Stg 環境に変更
$ terraform init
$ terraform plan
$ terraform apply
```

でも、SecurityGroupのように VPC ID を指定するようなものも、Dev環境のVPC IDがハードコードされたりして、たぶんダメだと思う。ものによる。

## どのようになれば実現可能か
### 1. VPCとSubnetのように サービス間で data リソースで受け渡しが実現される
- Route53 は ALB の outputs.tf の ALB DNSレコードを参照する（ハードコードじゃない）
- Security Group は VPC の outputs.tf の vpc_id を参照する
- そうすることで、dev, stg, prod環境のvpc_id違いなどを吸収する

### 2. グローバルで固定になりそうな値（S3バケット名など）はvariable.tfに書き込まれる

- （そんなこと出来たら良いなと思いつつ）workspace名(dev,stg)などと一緒にvariableの値が生成される

## 上の二つが実現できると、夢が広がる
そうなると reverse Terraform どころか、**automate creation** Terraform じゃん。
terraformer がリソース間連携を少し実装してくれてるし、みんなでコミットすれば良い感じのところまでいけそう。

よし、Golang勉強しよう。