0. はじめに
- Terraformのディレクトリ構成について改めて考えてみたときのメモです。
- 今回久しぶりにTerraformを触るに際し、そろそろ『正解のディレクトリ構成』があるのかと思って調べて見ましたが、どうやら全ての状況に当てはまる正解というものはなく、構築するシステム構成に応じてベストなものを模索していかなければならない認識です。
- 前提として、中規模くらいのシステム・環境ごとの差異は多少ある(特にprod/stgとdev間)、を想定しています。
1. ディレクトリ構成
採用したディレクトリ構成案
最初に結論
ディレクトリ構成
.
├── env
│ ├── prod
│ │ ├── datastore
│ │ ├── network
│ │ │ ├── main.tf
│ │ │ └── output.tf
│ │ ├── opsserver
│ │ ├── routing
│ │ ├── service
│ │ └── storage
│ ├── stg01
│ └── dev01
└── modules
├── datastore
├── network
│ ├── main.tf
│ ├── output.tf
│ └── variable.tf
├── opsserver
├── routing
├── service
└── storage
modules/network/main.tf
data "aws_availability_zones" "available" {
state = "available"
}
#----------
# vpc
#----------
# vpc
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr
instance_tenancy = "default"
enable_dns_hostnames = true
tags = {
Name = "${var.system}-${var.env}-vpc"
}
}
# flow_log
resource "aws_flow_log" "flow_log" {
log_destination = var.flow_log_destination
log_destination_type = "s3"
traffic_type = "ALL"
vpc_id = var.vpc_cidr
}
#----------
# subnet public
#----------
# subnet_public
resource "aws_subnet" "public" {
count = length(var.subnet_public_cidr)
vpc_id = aws_vpc.vpc.id
cidr_block = element(var.subnet_public_cidr, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.system}-${var.env}-subnet-public-${count.index + 1}"
}
}
modules/network/variable.tf
variable "system" {}
variable "env" {}
variable "vpc_cidr" {}
variable "flow_log_destination" {}
variable "subnet_public_cidr" {}
modules/network/output.tf
output "vpc_id" {
value = aws_vpc.vpc.*.id[0]
}
output "subnet_public_id" {
value = aws_subnet.public.*.id[0]
}
env/prod/network/main.tf
#----------
# Terraform
#----------
terraform {
required_version = "0.13.2"
backend "s3" {
bucket = "system-prod-deploy-tf-999999999"
region = "ap-northeast-1"
key = "tfstate"
encrypt = true
}
}
#----------
# Provider
#----------
provider "aws" {
region = "ap-northeast-1"
}
#----------
# Remote State
#----------
data "terraform_remote_state" "XXX" {
backend = "s3"
config = {
region = "ap-northeast-1"
bucket = "system-prod-deploy-tf-999999999"
key = "tfstate.XXX"
}
}
#----------
# Resource - Network
#----------
module "network" {
source = "../../modules/network"
# common
system = "systemA"
env = "prod"
# vpc
vpc_cidr = "192.168.10.0/24"
# flow_log
flow_log_destination = data.terraform_remote_state.storage.outputs.s3_log_arn
# subnet_public
subnet_public_cidr = ["192.168.10.0/26", "192.168.10.64/26"]
env/prod/network/output.tf
output "vpc_id" {
value = module.network.vpc_id
}
output "subnet_public_id" {
value = module.network.subnet_public_id
}
採用した理由
- 選定基準は、①より安全な改修・デプロイができること・②環境ごとの差異を比較しやすいこと
-
How to Create Terraform Multiple EnvironmentsのSeparated Directoriesパターンを採用
- モジュール切り出し+環境分離+コンポーネント分割
- 公式通り、中規模なのでmodulesを利用する・環境の切り替えにWorkSpaceは利用しない
- 変更頻度・影響範囲・可読性・terraformコマンドの処理時間を考慮してstateファイルを分割するため、env配下もさらにmodulesに合わせてディレクトリを分割する
- コンポーネント分割の粒度はTerraformはどの単位で分割すべきかを参考にした
- ここは統合しても良いかもしれない(公式のcomplete-moduleに寄せる)。悩み中
(参考)ディレクトリ構成案
minimal-module
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
complete-module
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│ ├── nestedA/
│ │ ├── README.md
│ │ ├── variables.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ ├── nestedB/
│ ├── .../
├── examples/
│ ├── exampleA/
│ │ ├── main.tf
│ ├── exampleB/
│ ├── .../
2. 開発規約
- modules配下は、公式通りmain.tf/variable.tf/output.tfに分ける
- env配下は、環境ごとのdiffを取るためmain.tfに寄せる。output.tfのみmodulesに合わせて切り出す
- 変数名などは、
サービス名_役割_項目
、でつける。重複はしないようにしつつ、シンプルにする。- ex.
subnet_public_cidr
- ex.
- リソース名の命名規則は弊社で使っているAWSリソースの命名規則を紹介します参照
- ex.
{sysname}-{env}-{nlayer}-subnetXX
- ex.
- コメントの粒度をmodules側とenv側で合わせる
- 極力各リソースタイプに対応するData Sourceを使って参照する
data "aws_vpc" "vpc" {
tags = {
service = var.service
env = var.env
}
}
- 積極的に使う関数
- 繰り返し作成: for_each(countよりこちら), count, element
- 文字列結合: format
- プロバイダバージョン固定のため、
.terraform.lock.hcl
ファイルをリポジトリ管理対象とする - コミット前に
$terraform fmt -recursive
とterraform validate
は各自実行する - CIで実行するコマンド: terraform plan, terraform validate, tflint, tfsec