Terraformの最適(≠最強)なディレクトリ構成を考えてみた
はじめに
私が起業して自社の Web サービスインフラを新規構築 & IaC するにあたり、Terraform を採用しました。
基本的な Terraform の知識を学んだ後、早速開発に取り掛かったのですがそこで Terraform のディレクトリ構成をどうするかという問題に直面しました。
自分なりに資料を漁ったり、実際に開発するなかでブラッシュアップしながら最適なディレクトリ構成を検討したのでご紹介します。
ディレクトリ構成の前提条件
ディレクトリを検討するにあたり、以下の自社 Web サービスの特性を前提条件としました。
- 小・中規模のシステム
十数個程度の AWS サービスを組み合わせて作るシステム - 2つ以上の環境を使用する
dev/prod 環境が必要 - システム全体で見ると各環境間でリソースの差分ははあるが、AWS サービス単位やまとまった機能単位でみると差分がない箇所もある
上記の前提を踏まえて開発のしやすさ、保守性等を考慮してディレクトリ構成を検討しました。
似たような条件に当てはまるシステムやプロジェクトであれば参考になると思います。
ディレクトリ構成
早速結論ですが、結果としては以下のようなディレクトリ構成になりました。
.
├── env1
│ ├── backend.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── variables.tf
│ ・・・
├── env2
│ ├── backend.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── variables.tf
│ ・・・
├── modules
│ └── example1
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── shared
└── provider.tf
・・・
各フォルダの役割
各フォルダの役割は以下の通りです。
- env1,env2:各環境ごとの tf ファイルを格納するフォルダ(環境の数分作成する)
- modules:各環境で共通だが設定値に差分があるリソースをモジュール化したものを格納するフォルダ
- shared:各環境で設定値が全く同じリソースの tf ファイルを格納するフォルダ
tf ファイルの作成・分割を行いながら上記のいずれかのフォルダに tf ファイルを格納します。
なお、Terraform の公式や他の記事ではルートディレクトリの配下に environments フォルダを作成し、その中で各環境ごとのフォルダを作成している例がよくあります。
私の場合は後述しますがディレクトリ内で Symbolic Link を使う関係上必要以上にディレクトリ階層を深くしたくなかったため、ルートディレクトリの直下に各環境のフォルダを作成しています。
環境の数がそこまで多くない場合はルートディレクトリもそこまで汚くならないので気にならないと思います。
tf ファイルの分割基準
tf ファイルを作成するにあたってポイントとなるのが
”どういった単位で tf ファイルを分割するのか”
です。
この分割基準については明確なルールやベストプラクティスはないので、開発者やチーム内での決めの問題になります。
個人的な考えとしては、中規模以上のシステムであれば基本的に main.tf は使用せずに AWS リソースごとやまとまりのある機能単位で tf ファイルを分割するのがいいと思います。
おすすめの方法はまずは最も細かい粒度として AWS サービス単位で分けて tf ファイルを作成し、後から1つのファイルにまとめた方がいいかを検討して FIX する方法です。
環境ごとの差分によって tf ファイルの管理方法を決める
tf ファイルが完成したら、各環境ごとにどの程度差分があるかによって管理方法を決めます。
- 環境ごとの差分なし
→①shared フォルダ + Symbolic Link - リソースの各属性への設定値に一部差分がある
→② モジュール化 - リソースの有無などの大きな差分がある
→③ 環境ごとに tf ファイルを分割
tf ファイル管理方法詳細
上記の管理方法 3 パターンそれぞれでどのようなケースが該当するのかを例を交えて説明します。
1.shared フォルダ + Symbolic Link
この管理方法は環境ごとに内容に差分が出ない tf ファイルで使用します。
代表的な例としては Terraform の provider 情報を定義する tf ファイル等があると思います。
この例では以下のような provider.tf というファイルを作成し、shared フォルダの直下に配置します。
terraform {
required_version = "1.2.4"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16.0"
}
}
}
variable "aws_region" {
default = "ap-northeast-1"
}
provider "aws" {
region = var.aws_region
}
上記ファイルを配置後、各環境フォルダに移動して Symbolic Link を作成します。
# 他の環境についても以下と同様にSymbolic Linkを作成する。
root $ cd env1
env1 $ ln -s ../shared/provider.tf
こうすることで各環境から同じ tf ファイルを参照することができるため、
同じ tf ファイルをそれぞれのフォルダに作成する必要がなくなります。
そのためコードに変更があった場合でも shared フォルダの tf ファイルを更新するだけでいいので、DRY な構成にすることができます。
補足:環境ごとにリソース名等を変えたい場合
リソースの内容は全く同じでもリソース名やタグの内容だけは環境ごとに変えたいというケースもあると思います。(各環境で 1 つの AWS アカウントを共有している場合等)
そういったケースでは以下の様に環境ごとにユニークな値をリソース名やタグに付与するような定義にしてください。
variable "environment" {
description = "environment prefix"
}
locals {
prefix = var.environment
common_tags = {
"Terraform" = "true"
"Environment" = var.environment
}
}
environment = "prod"
resource "aws_s3_bucket" "bucket" {
bucket = "${local.prefix}-bucket"
tags = local.common_tags
}
# bucket_id = prod-bucket
2.モジュール化
この管理方法は環境ごとに共通するリソースだが、一部の属性の設定値に差分がある tf ファイルで使用します。
代表的な例としては VPC・サブネットを定義する tf ファイル(CIDR の値を環境ごとに変える場合)、EC2 や RDS を定義する tf ファイル(インスタンスタイプやディスク容量を環境ごとに変える場合)があると思います。
こういった例では Terraform のベストプラクティスに則ってモジュールを活用します。
ここではモジュールの実装方法の詳細については解説しませんので、まだよく知らないという方は調べてみてください。
3.環境ごとに tf ファイルを分割
この管理方法は環境ごとに内容が大きく異なる tf ファイルで使用します。
この場合は素直に環境ごとに別々の tf ファイルを作成しましょう。
まとめ
今回は私の考える Terraform の最適なディレクトリ構成についてご紹介しました。
結局のところは使用するリソースの種類や規模に応じて最適なディレクトリ構成は変わるため、全てのプロジェクトで採用できる最強のディレクトリ構成は存在しません。
当記事の内容が皆さんがご自身のプロジェクトにあったディレクトリ構成を考える際の参考になれば嬉しいです。
当記事で紹介しているディレクトリ構成のテンプレートを GitHub に公開しているのでよろしければご利用ください。
参考
Discussion