半年くらい前からterraformでAWS環境の構成管理を運用していてこれが一番楽かなー、という奴をまとめようまとめようと思いつつしばらく経ってしまってたので、今更ながらまとめてみる。
terraformを利用し始めた当初は、あまり複数環境での実運用例がなかったものの、最近はクラメソさんのこの記事とかも出てきたりしたのでとても有り難いですね🙏
結論
modulesと、環境ごとのtfvarsを組み合わせて使う。
具体的には?
├── global # global 環境用tfコード
│ ├── cf-oai.tf
│ ├── global.tfvars
│ ├── iam.tf
│ ├── infra-builder.tf
│ ├── remote_state.tf
│ ├── user_data.tpl
│ ├── variables.tf
│ └── vpc.tf
├── modules # 各module用のコード
│ ├── cdn
│ │ ├── bucket_policy.json.tpl
│ │ ├── cdn.tf
│ │ └── variables.tf
│ ├── instances
│ │ ├── assume_role_policy.json
│ │ ├── iam_instance_profile.tf
│ │ ├── iam_role_policy.json.tpl
│ │ ├── key_pair.tf
│ │ ├── user_data.tpl
│ │ ├── variables.tf
│ │ └── web.tf
│ ├── load_balancers
│ │ ├── elb.tf
│ │ └── variables.tf
│ ├── network
│ │ ├── nat.tf
│ │ ├── rt.tf
│ │ ├── sg.tf
│ │ ├── sn.tf
│ │ └── variables.tf
│ ├── rds
│ │ ├── rds.tf
│ │ └── variables.tf
│ └── route53
│ ├── r53.tf
│ └── variables.tf
├── prod # prod 環境用tfvars、modules.tfを配置
│ ├── modules.tf
│ ├── prod.tfvars
│ ├── secret-prod.tfvars -> ../credentials/tfvars/secret-prod.tfvars # symlink
│ └── variables.tf
├── dev # dev 環境用tfvars、modules.tfを配置
├── sandbox # sandbox 環境用tfvars、modules.tfを配置
├── pr_test # pullrequest test 環境用tfvars、modules.tfを配置
└── credentials # 秘匿情報管理リポジトリ(rdsのパスワードとか)へのsubmodule
└── tfvars
└── secret-prod.tfvars # rdsパスワードなど秘匿情報変数を定義
こんな感じにしています。
実際にterraformコマンドを実行する場合は、
# prod環境ディレクトリに移動
$ cd prod/
# modulesの取り込み
$ terraform get
# remote state取り込み
$ terraform remote pull
# plan実行
$ terraform plan -var-file=dev.tfvars -var-file=secret-dev.tfvars
# apply実行
$ terraform apply -var-file=dev.tfvars -var-file=secret-dev.tfvars
このように、環境用のディレクトリに移動してから実行しています。
※実際は自分の担当しているプロダクトでは、インフラ作業の定型業務用にjenkinsサーバーを立てて、そこからterraformを実行しているので手動で実行はしていませんが。
global環境について
ここは試行錯誤した感じのところですが、modules化しにくいものや各環境で統一のものなどを定義しておき、そこで作成したもの(vpcとか)のIDをterraform_remote_stateを用いて各modulesにて再利用する、みたいなことをやっています。
例えば👇のような感じ。
resource "aws_vpc" "vpc" {
cidr_block = "10.0.1.0/24"
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
tags {
"Name" = "vpc"
}
# 万が一意図せずに壊してしまうのを防ぐため
lifecycle {
prevent_destroy = true
}
}
-----
output "vpc_id" {
value = "${aws_vpc.vpc.id}"
}
とvpcを定義しておいて、remote stateを有効にしておいた状態でmodules/networkの方では下記のように扱う、といった感じ。
# global/vpc.tfで定義したVPCのIDを取得する
data "terraform_remote_state" "vpc" {
backend = "s3"
config {
region = "${var.region}"
bucket = "infra-s3"
key = "global.tfstate"
}
}
resource "aws_subnet" "public1a-subnet" {
vpc_id = "${data.terraform_remote_state.vpc.vpc_id}"
cidr_block = "${var.public1a_subnet_cidr}"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = false
tags {
"Name" = "${var.env}-public1a-subnet"
}
lifecycle {
create_before_destroy = true
}
}
これをその他のmodulesでもglobal環境で作成した生成物のIDなどのAttributesを再利用したい時に使用しています。
秘匿情報リポジトリについて
なるべくはオープンなリポジトリでコードを管理したいが、どうしてもrdsのパスワードなど、権限を絞った上で管理したい、ということからこのようにしています。
具体的には、credentials
という、秘匿情報を参照しても良いメンバーだけがチームに所属しているプライベートリポジトリを作成し、そこにtfvarsだけを置き、オープンリポジトリの方ではgit submoduleで秘匿情報リポジトリを追加。
そして、各環境のディレクトリ配下でシンボリックリンクをひく、みたいなトリッキーな形?に落ち着きました。
ここについては、もうちょっと良い案があるかなぁ、とも考えていたりします。。。
おわりに
terraform0.7系になってからdataリソースとか、terraform importコマンドとかstateコマンドとか、0.6系の頃よりも色々と扱いやすくなってきているので、もっとterraform流行るといいなーと個人的には思っています。
また、実際の環境への適用を安全に行えるようにjenkinsのpullrequest builder pluginを利用していたりするので、それも別の機会にまとめたいと思っています。