AWS
gcp
Terraform
terraformer

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

過去に Terraform Best Practices in 2017 という記事を書きました。

そのあと、2019年2月のJAWS Days Infrastructure as Codeに疲れたので、僕たちが本来やりたかったことを整理するを話しました。

その中で、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. インストール

# 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ユーザを利用ください。

対象が多い場合は少し時間がかかります。

$ 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ファイル等が作成されるみたいです。

$ 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を参照するようになっている。賢い。

$ 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 リソースなのか。

$ 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 リソースなのか、ハードコードなのか

$ 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 などグローバルで一意の値が必要ないものに関しては、簡単に流用できました。

# 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勉強しよう。