前提
- 小規模というほどではないが決して大規模ではない環境を組むときにちょうどいいTerraformのディレクトリ構成について悩んだときのメモです。
- 環境ごとにVPCとかのネットワークを切って、それぞれにEC2(ECS)+RDS+S3とかとかを数台ずつ乗せていくくらい。
観点
- [効率]環境ごとの差異を比較しやすいこと
- dev→stg→prodとデプロイしていくとして、差分反映・確認がしやすいこと
- [安定]安全にデプロイしやすいこと
- 変更頻度や影響範囲を考慮
ディレクトリ構成
- はじめに結論
├── env │ └── stg01 │ |── network │ | └── main.tf │ └── service-aaa │ └── main.tf └── modules └── vpc └── main.tf
ファイル例
- env/network/stg01
#---------- # Terraform #---------- terraform { required_version = "0.13.2" backend "s3" { bucket = "system-stg01-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-stg01-deploy-tf-999999999" key = "tfstate.XXX" } } #---------- # Local Values #---------- locals { env = "stg01" } #---------- # Resource - Network #---------- # vpc module "vpc" { source = "../../modules/vpc" vpc_cidr_block = "192.168.10.0/24" vpc_tags_Name = "system-${local.env}-vpc" } # public subnet module "public-subnet01" { source = "../../modules/subnet" subnet_vpc_id = module.vpc.vpc_id subnet_cidr_block = "192.168.10.0/26" subnet_map_public_ip_on_launch = "true" subnet_tags_Name = "system-${local.env}-public-subnet01" } module "public-subnet02" { source = "../../modules/subnet" subnet_vpc_id = module.vpc.vpc_id subnet_cidr_block = "192.168.1.64/26" subnet_map_public_ip_on_launch = "true" subnet_tags_Name = "system-${local.env}-public-subnet02" }
- modules/vpc/main.tf
variable "vpc_cidr_block" {} variable "vpc_tags_Name" {} resource "aws_vpc" "this" { cidr_block = var.vpc_cidr_block instance_tenancy = "default" enable_dns_support = true enable_dns_hostnames = true tags = { Name = var.vpc_tags_Name } } output "vpc_id" { description = "The ID of the VPC" value = concat(aws_vpc.this.*.id, [""])[0] }
実装方針
- モジュール切り出し+環境分離+コンポーネント分割
- 変数の値は4箇所に集約(全て環境ファイルに)
- 環境ファイルに直書き
- 同一環境ファイルの別モジュールの値(output)
- 環境ファイルのLocal Values
- 別コンポーネントの値(outputをRemote Stateとして拾う)
- 全てmain.tfに寄せる
- 環境ファイルとmoduleファイル
- variable.tfとか切り出さない
検討経緯
モジュール切り出し+環境分離+コンポーネント分割
- 推奨されてそうなのでモジュール切り出しにして見た
- 今後のことを考えて受け身が取れるように
- モジュール切り出しは、ダイナミックとかロジック入れるときに役立つ?
- ただ、ロジックは極力使わずベタ書きしている
- EC2のcountとかあるが、tagのインデックスくらいならいいがEIPとか絡むとややこしくなる。だったら環境ファイル側でベタ書きする。
- →が、環境ごとに下手書きで十分な気がする(モジュールを繰り返し利用したところで、記述量が減らない)
- 結局、コードとは違う。設定値の羅列で、ロジックがないから。ベタ描きで十分 or 思い切ってWorkspace?
- Workspaceも、比較しやすい?
- 結局、コードとは違う。設定値の羅列で、ロジックがないから。ベタ描きで十分 or 思い切ってWorkspace?
- moduleはあくまでパーツ。システム側の都合を考慮せず、疎結合に組み合わせられるように実装する
- tf.stateの分割
- 変更が多い/少ない
- 非機能要件の違い
変数の値は4箇所に集約(全て環境ファイルに)
- 同一環境ファイルの別モジュールの値(output)
module.vpc.vpc_id
環境ファイルのLocal Values
- 比較しやすくなるように、環境変数(dev, prodなど)はLocal Valuesとして一度だけ定義する
別コンポーネントの値(outputをRemote Stateとして拾う)
- 呼び出し先のmoduleではなく、呼び出し元でoutputとして定義する必要あり
data.terraform_remote_state.network.outputs.vpc_vpc_id
全てmain.tfに寄せる
- 「観点」記載の通り、環境ごとの差分を比較しやすくしたいので、分けることにあまり意味がない
- それぞれのファイルの量も少ない
参考
共通コードのディレクトリにはclassの定義が、 環境ごとのディレクトリにはclassのインスタンスを作る際に渡す実際の値が定義されていると イメージすると分かりやすいと思います。
# コンポーネント分割例 * network * vpc,subnet * routing * route53,alb,acm * services * ecsのサービス単位(ecs cluster,taskdifinition,service,それに付随するec2系リソース ecr pipeline系) * opsserver * session manager,ec2 * events * やりたい処理単位(lambda cloudwatchEvents ここはApex+Terraform) * モニタリングとかもここに * state machine * step function * storage * s3 * datastore * elasticache,RDS * elasticache,RDSを別々に * iam user
# コンポーネント分割例 * network * ネットワーク系の設定VPC、Subnet、NAT、Route Tables等の設定を管理 * securitygroup * セキュリティグループの管理 * iam * IAMの管理 * s3 * S3回りの管理 * backend * DB等のデータストア関連のリソースを管理 * webservers * webサーバやALB等の管理 * opsservers * 運用系のサーバの管理
- Terraformに入門したメモ
- Workspaceを使わない理由