Terraformerを使ってTerraformに既存インフラのリソースをインポートする
目次
インフラエンジニアの寺岡です。
前回Terraformに既存リソースをインポートする方法として
以下の記事でterraform importコマンドをご紹介しました。
この記事のまとめにも記載していますが
importコマンドで書き換えてくれるのはtfstateのみであり
tfファイルはtfstateとの差分を見ながら自力で書いていく必要があります。
数が多ければ途方もない時間がかかりそうです、これは困った。
そんな皆さんに朗報です。
terraformerというツールがOSSとして公開されています。
https://github.com/GoogleCloudPlatform/terraformer
CLI tool to generate terraform files from existing infrastructure (reverse Terraform). Infrastructure to Code
このように書かれている通り既存のインフラからTerraformのファイルを自動生成するCLIツールのようです。
使い方もGithubに記載されているので実際に使ってみましょう。
インストール
Macの場合はbrewコマンドでインストールできます。
$ brew install terraformer $ terraformer version Terraformer v0.8.7
ここまでは簡単ですね。
今回はv0.8.7を使っていきます。
インフラ構成
terraformerでインポートする対象のインフラを事前に作成しておきました。
https://github.com/beyond-teraoka/terraform-aws-multi-environment-sample
構成図
同一のAWSアカウントに3つの環境があります。
- develop
- production
- manage
さらに各環境ごとに以下のリソースがあります。
- VPC
- Subnet
- Route Table
- Internet Gateway
- NAT Gateway
- Security Group
- VPC Peering
- EIP
- EC2
- ALB
- RDS
図には書いていないですが各環境のリソースにはEnvironmentタグを付与しており
それぞれdev,prod,mngが値として設定されています。
認証情報の用意
AWSの認証情報を用意しておきます。
こちらは皆さんの環境に合わせて用意してください。
$ cat /Users/yuki.teraoka/.aws/credentials [beyond-poc] aws_access_key_id = XXXXXXXXXXXXXXXXXXXX aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [beyond-poc-admin] role_arn = arn:aws:iam::XXXXXXXXXXXX:role/XXXXXXXXXXXXXXXXXXXXX source_profile = beyond-poc
terraformerの実行
まずはGitHubに書いている例の通りに実行してみます。
$ terraformer import aws --resources=alb,ec2_instance,eip,ebs,igw,nat,rds,route_table,sg,subnet,vpc,vpc_peering --regions=ap-northeast-1 --profile=beyond-poc-admin 2020/05/05 18:29:14 aws importing region ap-northeast-1 2020/05/05 18:29:14 aws importing... vpc 2020/05/05 18:29:15 open /Users/yuki.teraoka/.terraform.d/plugins/darwin_amd64: no such file or directory
terraform initした時のプラグインのディレクトリが必要みたいですね。
init.tfを用意してinitします。
$ echo 'provider "aws" {}' > init.tf $ terraform init
もう一度実行してみます。
$ terraformer import aws --resources=alb,ec2_instance,eip,ebs,igw,nat,rds,route_table,sg,subnet,vpc,vpc_peering --regions=ap-northeast-1 --profile=beyond-poc-admin
インポートできたようです。
先ほどまではなかったgeneratedというディレクトリが出来上がっています。
ディレクトリ構造
$ tree . └── aws ├── alb │ ├── lb.tf │ ├── lb_listener.tf │ ├── lb_target_group.tf │ ├── lb_target_group_attachment.tf │ ├── outputs.tf │ ├── provider.tf │ ├── terraform.tfstate │ └── variables.tf ├── ebs │ ├── ebs_volume.tf │ ├── outputs.tf │ ├── provider.tf │ └── terraform.tfstate ├── ec2_instance │ ├── instance.tf │ ├── outputs.tf │ ├── provider.tf │ ├── terraform.tfstate │ └── variables.tf ├── eip │ ├── eip.tf │ ├── outputs.tf │ ├── provider.tf │ └── terraform.tfstate ├── igw │ ├── internet_gateway.tf │ ├── outputs.tf │ ├── provider.tf │ ├── terraform.tfstate │ └── variables.tf ├── nat │ ├── nat_gateway.tf │ ├── outputs.tf │ ├── provider.tf │ └── terraform.tfstate ├── rds │ ├── db_instance.tf │ ├── db_parameter_group.tf │ ├── db_subnet_group.tf │ ├── outputs.tf │ ├── provider.tf │ ├── terraform.tfstate │ └── variables.tf ├── route_table │ ├── main_route_table_association.tf │ ├── outputs.tf │ ├── provider.tf │ ├── route_table.tf │ ├── route_table_association.tf │ ├── terraform.tfstate │ └── variables.tf ├── sg │ ├── outputs.tf │ ├── provider.tf │ ├── security_group.tf │ ├── security_group_rule.tf │ ├── terraform.tfstate │ └── variables.tf ├── subnet │ ├── outputs.tf │ ├── provider.tf │ ├── subnet.tf │ ├── terraform.tfstate │ └── variables.tf ├── vpc │ ├── outputs.tf │ ├── provider.tf │ ├── terraform.tfstate │ └── vpc.tf └── vpc_peering ├── outputs.tf ├── provider.tf ├── terraform.tfstate └── vpc_peering_connection.tf 13 directories, 63 files
ディレクトリの構造に注目してほしいのですが
terraformerはデフォルトで「{output}/{provider}/{service}/{resource}.tf」という構造でインポートするようです。
これはGitHubにも記載されています。
Terraformer by default separates each resource into a file, which is put into a given service directory.
The default path for resource files is {output}/{provider}/{service}/{resource}.tf and can vary for each provider.
この構造では以下の問題が出てきます。
- Terraformのリソースごとにtfstateが分割されているため、小規模な変更でも複数回のApplyが必要になる
- 全ての環境のリソースが同一のtfstateに記録されているため、1つの環境での変更が全環境に影響を及ぼす可能性がある
できればtfstateの分割基準はdevelop,production,manageなどの環境ごとにして
各環境のリソースは全て同一のtfstateに記録されるようにしたいですね。
これができないか調べてみると以下のことがわかりました。
- --path-patternオプションで階層構造を明示的に指定できる
- --filterオプションで指定したタグが付与されているリソースのみをインポートできる
この2つを組み合わせると実現できそうです、やってみましょう。
$ terraformer import aws --resources=alb,ec2_instance,eip,ebs,igw,nat,rds,route_table,sg,subnet,vpc,vpc_peering --regions=ap-northeast-1 --profile=beyond-poc-admin --path-pattern {output}/{provider}/develop/ --filter="Name=tags.Environment;Value=dev" $ terraformer import aws --resources=alb,ec2_instance,eip,ebs,igw,nat,rds,route_table,sg,subnet,vpc,vpc_peering --regions=ap-northeast-1 --profile=beyond-poc-admin --path-pattern {output}/{provider}/production/ --filter="Name=tags.Environment;Value=prod" $ terraformer import aws --resources=ec2_instance,eip,ebs,igw,route_table,sg,subnet,vpc,vpc_peering --regions=ap-northeast-1 --profile=beyond-poc-admin --path-pattern {output}/{provider}/manage/ --filter="Name=tags.Environment;Value=mng"
インポートが終わった後のディレクトリ構造は以下です。
ディレクトリ構造
$ tree . └── aws ├── develop │ ├── db_instance.tf │ ├── db_parameter_group.tf │ ├── db_subnet_group.tf │ ├── eip.tf │ ├── instance.tf │ ├── internet_gateway.tf │ ├── lb.tf │ ├── lb_target_group.tf │ ├── nat_gateway.tf │ ├── outputs.tf │ ├── provider.tf │ ├── route_table.tf │ ├── security_group.tf │ ├── subnet.tf │ ├── terraform.tfstate │ ├── variables.tf │ └── vpc.tf ├── manage │ ├── instance.tf │ ├── internet_gateway.tf │ ├── outputs.tf │ ├── provider.tf │ ├── route_table.tf │ ├── security_group.tf │ ├── subnet.tf │ ├── terraform.tfstate │ ├── variables.tf │ ├── vpc.tf │ └── vpc_peering_connection.tf └── production ├── db_instance.tf ├── db_parameter_group.tf ├── db_subnet_group.tf ├── eip.tf ├── instance.tf ├── internet_gateway.tf ├── lb.tf ├── lb_target_group.tf ├── nat_gateway.tf ├── outputs.tf ├── provider.tf ├── route_table.tf ├── security_group.tf ├── subnet.tf ├── terraform.tfstate ├── variables.tf └── vpc.tf 4 directories, 45 files
リソースが環境ごとに分割されていますね。
試しにdevelop以下のvpc.tfを見てみると対応する環境のVPCのみがインポートされています。
develop/vpc.tf
resource "aws_vpc" "tfer--vpc-002D-0eea2bc99da0550a6" { assign_generated_ipv6_cidr_block = "false" cidr_block = "10.1.0.0/16" enable_classiclink = "false" enable_classiclink_dns_support = "false" enable_dns_hostnames = "true" enable_dns_support = "true" instance_tenancy = "default" tags = { Environment = "dev" Name = "vpc-terraformer-dev" } }
tfstateに関しては各環境ごとのリソースが1ファイルに全て記録されているのでこちらも問題なさそうです。
中身は長いので省きます。
懸念点
リソースのID値がハードコーディングされてしまっている箇所がある
以下のaws_security_groupのvpc_idのように
リソースのID値がハードコーディングされてしまっている箇所が複数存在します。
resource "aws_security_group" "tfer--alb-002D-dev-002D-sg_sg-002D-00d3679a2f3309565" { description = "for ALB" egress { cidr_blocks = ["0.0.0.0/0"] description = "Outbound ALL" from_port = "0" protocol = "-1" self = "false" to_port = "0" } ingress { cidr_blocks = ["0.0.0.0/0"] description = "allow_http_for_alb" from_port = "80" protocol = "tcp" self = "false" to_port = "80" } name = "alb-dev-sg" tags = { Environment = "dev" Name = "alb-dev-sg" } vpc_id = "vpc-0eea2bc99da0550a6" }
新しくHCLを書く場合は「vpc_id = aws_vpc.vpc.id」のように動的参照するのですが
インポート時にそこまで補完するのはまだまだ難しいようです。
この部分はtfstateには既にVPCのIDが記録されているのでtfファイルをのみを修正すれば良いです。
terraform_remote_stateの記述が0.12に対応していない
以下のaws_subnetのvpc_idのようにHCLの記述がTerraformの0.11系のものになっている箇所があります。
resource "aws_subnet" "tfer--subnet-002D-02f90c599d4c887d3" { assign_ipv6_address_on_creation = "false" cidr_block = "10.1.2.0/24" map_public_ip_on_launch = "true" tags = { Environment = "dev" Name = "subnet-terraformer-dev-public-1c" } vpc_id = "${data.terraform_remote_state.local.outputs.aws_vpc_tfer--vpc-002D-0eea2bc99da0550a6_id}" }
この状態で0.12系でApplyすると実行はできますが警告が発生します。
また、--path-patternオプションで環境ごとにディレクトリを分けるように変更すると
tfstate自体は1ファイルにまとめて出力されますが
tfファイル内でリソースを参照する時は相変わらずterraform_remote_stateで参照されています。
GitHubを見ると以下の記載があるので仕様ですね。
Connect between resources with terraform_remote_state (local and bucket).
上記のvpc_idの場合はaws_vpcとaws_subnetが同じtfstateに記録されているので
単純に「vpc_id = aws_vpc.tfer--vpc-002D-0eea2bc99da0550a6.id」のみで参照可能です。
この部分も自力で修正する必要がありそうです。
まとめ
いかがでしたでしょうか。
terraformerを使えば既存インフラのインポートも捗りそうですね。
いくつか懸念点はありますが、さほど修正は難しくないので許容できる範囲ではないでしょうか。
私も以前からterraform import辛いなと思っていたので
それをいい具合に解決してくれるツールを作成した「Waze SRE」の方々は素直に尊敬します。
皆さんも是非利用してみてください。