2019/12/23 更新
本記事はベストプラクティスかもしれないけど複雑だったので、2020年度版(0.12対応版)として Terraform 0.12 のコードを黒魔術にしないために心がけたこと ~ 自分への戒めを込めて ~ を書きました。こちらも参照ください。
Terraform Best Practices in 2017
以下のブログをベースにver0.10の新機能のworkspaceや、backend、remote stateを活用してベストプラクティスを考えた。
細かい話は以下のブログを参照いただき、ver0.10に対応した内容だけ記載します。
Terraformにおけるディレクトリ構造のベストプラクティス | Developers.IO
サンプルコード
サンプルコードを置きましたので、イメージが付かない場合は以下を見てみて下さい。
(適当に作ったので間違えてたらプルリクください)
https://github.com/shogomuranushi/oreno-terraform
ディレクトリ構造
├── environments
│ ├── not_immutable
│ │ ├── provider.tf
│ │ ├── backend.tf
│ │ ├── variable.tf
│ │ ├── main.tf
│ │ └── output.tf
│ └── immutable
│ ├── provider.tf
│ ├── backend.tf
│ ├── variable.tf
│ ├── main.tf
│ └── output.tf
└── modules
├── compute
│ ├── ec2.tf
│ ├── elb.tf
│ ├── output.tf
│ ├── userdata.sh
│ └── variable.tf
├── db
│ ├── main.tf
│ ├── output.tf
│ └── variable.tf
└── vpc
├── main.tf
├── output.tf
└── variable.tf
ディレクトリ構造のポイント
1. environments配下の分け方
- tfstateファイルで管理する範囲が大きいと問題があった際の影響範囲が大きくなるため実行単位を小さくする
- 今回の場合は、not_immutableとimmutableで分けて、それぞれの配下でterraformを実行する
- terraformの実行単位を分けるとterraform間での値の受け渡しが通常とは異なり、
remote state
機能を利用する必要がある- 以下のようにdataを定義することで、remote側のoutputを参照できるようになる
- remote側のoutputを簡素化させる方法はこちらを参照 -> Terraformのoutputでmapを利用する方法 - Qiita
- 注意点
- 制約としてremote先のmoduleの先のoutputは読み取れないのでmodule直下(root)でoutputを定義する必要がある
- tfstateの管理方法をs3にした状態でstate environmentsを使った時のs3のprefixは
env:/<environment>/
なるため以下のように記述する
- 以下のようにdataを定義することで、remote側のoutputを参照できるようになる
data "terraform_remote_state" "not_immutable" {
backend = "s3"
config {
bucket = "< backetname >"
key = "env:/${terraform.workspace}/not_immutable/terraform.tfstate"
region = "< region >"
}
}
module "compute" {
source = "../../modules/compute"
vpc = "${data.terraform_remote_state.not_immutable.vpc}"
}
2. dev/stg/prodなどの環境の分け方
-
ver0.9以前はdev,stg,prodなどはディレクトリを分けることで、tfstateを競合させないようにしていたが、ver0.10で追加された
workspace
を利用して環境を分ける -
terraform workspace new dev
を打つことでdevの環境が作られる- デフォルトでは直下に
terraform.state.d
というディレクトリができ、その配下に環境毎にtfstateが管理される
- デフォルトでは直下に
-
terraform workspace list
を打つことで現在のenviromentを参照可能
$ terraform workspace new dev
$ terraform workspace list
default
* dev
stg
- その前にtfstateはs3に置いたほうが良いと思うので、以下の記述も入れて
terraform init
を実行することでtfstateをs3で管理出来る状態になる。その後にterraform apply
を実行することでtfstateが生成される- なお、s3をbackendにすると
/env:/< environment >
が補完され< backetname >/env:/< environment >/immutable/terraform.tfstate
のように管理される
- なお、s3をbackendにすると
terraform {
backend "s3" {
bucket = "< backet name >"
key = "immutable/terraform.tfstate"
region = "us-west-2"
}
}
$ terraform init
workspaceの活用方法
今まではdevやstg、prodを別のディレクトリで管理していたため、それぞれのディレクトリにvariableを置くような形だったが、workspaceの登場により1つのディレクトリで複数の環境を扱えるようになった。
そこで如何に効率的に複数の環境を扱えるか考えた結果、以下になった。
- map関数をガンガン使う
- environments配下のvariable.tfには以下のようにmapで定義する
- map関数のkeyの部分をドット区切りでenv情報を入れる
- 環境毎に値を定義出来る
- env毎の切り替え方法は、値取得時に
"vpc-${lookup(var.common, "${terraform.workspace}.region", var.common["default.region"])}" }
のように${terraform.workspace}
にdevやstgが入りvalueとして参照可能になる
- env毎の切り替え方法は、値取得時に
- envの値が無ければdefaultを参照するように定義する方法は以下
- defaultの指定方法は
"vpc-${lookup(var.common, "${terraform.workspace}.region", var.common["default.region"])}" }
のように${lookup(key, value, default)
で指定可能
- defaultの指定方法は
- 環境毎に値を定義出来る
それらを踏まえたコードは以下
variable "common" {
default = {
default.region = "us-west-2"
default.project = "oreno-project"
dev.region = "us-west-2"
stg.region = "us-west-2"
prd.region = "ap-northeast-1"
}
}
# VPC
variable "vpc" {
type = "map"
default = {
default.cidr = "10.0.0.0/16"
default.public-a = "10.0.0.0/24"
default.public-c = "10.0.1.0/24"
default.private-a = "10.0.2.0/24"
default.private-c = "10.0.3.0/24"
}
}
module "vpc" {
source = "../../modules/vpc"
common = "${var.common}"
vpc = "${var.vpc}"
}
resource "aws_vpc" "vpc" {
cidr_block = "${lookup(var.vpc, "${terraform.workspace}.cidr", var.vpc["default.cidr"])}"
enable_dns_support = "true"
enable_dns_hostnames = "true"
tags {
Name = "vpc-${lookup(var.common, "${terraform.workspace}.project", var.common["default.project"])}"
}
}
provider "aws" {
region = "${lookup(var.common, "${terraform.workspace}.region", var.common["default.region"])}"
}
まとめ
- 影響範囲を小さくするため、terraformの実行単位は小さくしましょう
- terraform間の受け渡しは remote state を使いましょう
- workspace を使って環境を分けましょう
- map関数を使ってmodule等に渡す時などのコードを簡素化しましょう
- map関数 & workspace & default定義を使ってvariableを効率化させましょう