Terraformのフォルダ構成ってどうしたらいい? → 最小限の影響で済むことを意識したトレードオフ分析を行う
はじめに
まず、Terraformのフォルダ構成をどうしたらいいかは、プロダクトやチームのコンテキストによって異なるため正解はありません。
しかし、意識した方がいい方針はあるので、それを理解した上でプロダクトの規模、自分のチームのコンテキストに合わせて、トレードオフを考えていくのがいいと思います。
「約5年開発してきた自社サービス(チケット販売、配信、EC、データ基盤など)にIaCを導入する」という課題があり、実際にIaCとして、Terraformを本番運用しているサービスにゼロから導入したコンテキストから学んだことを自分やチームに向けて記載しています。
Terraformハンズオンについて書いた記事
Terraformハンズオン - できるだけ何も考えず、とりあえず使ってみよう
また、大前提としてこの記事の内容は、O'Reilly Japan - 詳解 Terraform 第3版 を参考としているので、こちらを合わせて読むのが一番おすすめです。
この記事を読むと嬉しいことがあるかもしれない人
- terraformはなんとなくわかったし導入もしたい。でも実際に作るとなるとどういったフォルダ構成にしたら良いんだろうか分かってない
この記事で書かないこと
- 具体的なterraformのコード。サンプルリポジトリは載せますmm
フォルダ構成 - 最小限の影響で済むことを意識したトレードオフ分析を行う
まず大前提ですが、「最小限の影響で済むことを意識したトレードオフ分析を行う」。
ポイントはこれだけです。
Terraformでは、stateファイル(tfstate)ごとにインフラの構築を行います。
例えば、tfstateにDBのインフラリソースとAPIインスタンスのインフラリソースが一緒に定義されていれば、tfファイルからのインフラ適用(terraform apply
)時に同時に更新をかけることになります。それぞれのインフラリソースを別のtfstateとして定義すれば、それぞれで terraform apply
してインフラ適用することになります。
つまり、以下のようなトレードオフが発生します。
- インフラリソースをtfstateにまとめる
- 一括で変更を加えられる利便性がある一方で、変更に関係のないインフラリソースまで更新対象となりインフラが壊れるリスクが生じる
- インフラリソースをtfstateに分ける
- 変更を加えたいインフラリソースのみが更新対象となるため安全に更新をかけられる一方で、tfstateごとに
terraform apply
を行う必要がある、tfstate間の依存関係を考慮する必要がある、インフラ適用に時間がかかるといったことが発生する
- 変更を加えたいインフラリソースのみが更新対象となるため安全に更新をかけられる一方で、tfstateごとに
このトレードオフをもとに、どのようなフォルダ構成が良いか、プロダクトやチームのコンテキストで検討していくことになると思います。
以下で自分が実際にプロダクト導入するにあたって検討したフォルダ構成をみてみます。
※フォルダ構成について考える前に、大前提として、terraformはtfstateフォルダ直下をフラットに扱うため、ファイル名が何であっても関係なく読み込まれます(1ファイルに全部ぶっ込んでもいい)。ただ、ファイル名を適切に分離することで、どういった目的のものがそこにあるか分かりやすくなるため、方針を決めてファイル名をつけています。
> tree
.
├── README.md
├── env
│ ├── dev
│ │ ├── vault
│ │ │ └── postgresql # DBリソース
│ │ │ ├── import.tf
│ │ │ ├── main.tf
│ │ │ ├── outputs.tf
│ │ │ ├── terraform.tf
│ │ │ └── variables.tf
│ │ └── service
│ │ ├── blog_api # Webサーバリソース
│ │ │ ├── main.tf
│ │ │ ├── terraform.tf
│ │ │ └── variables.tf
│ │ └── github_deploy # GitHub Actionで利用する、workload identityなどのリソース
│ │ ├── main.tf
│ │ ├── terraform.tf
│ │ └── variables.tf
│ └── prod
│ ├── vault
│ │ └── postgresql # DBリソース
│ │ ├── import.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── terraform.tf
│ │ └── variables.tf
│ └── service
│ ├── blog_api # Webサーバリソース
│ │ ├── main.tf
│ │ ├── terraform.tf
│ │ ├── dependencies.tf
│ │ └── variables.tf
│ └── github_deploy # GitHub Actionで利用する、workload identityなどのリソース
│ ├── main.tf
│ ├── terraform.tf
│ └── variables.tf
├── module
│ └── service
│ ├── google_github_workload_identity # githubデプロイ用のworkload identity
│ │ ├── main.tf
│ │ └── variables.tf
│ └── cloud_run_service # cloud run service
│ ├── main.tf
│ └── variables.tf
フォルダ分け(= tfstate管理分け)の方針
-
最小限の影響で済むように、dev・prodなどの開発環境やデータストア、サービスごとにtfstateの管理を分ける
- 環境で分ける理由
- 開発環境への変更で、本番環境のインフラが壊れるリスクをなくしたい
- 開発環境ではスペックを落としたり、開発環境でのみ適用するリソースがあったりする。その分岐処理を盛り込むのはコードが煩雑になるため避けたい。
- インフラの重要度で分ける理由
- 例えばDBやDNSのホストゾーンは、壊れた時の影響度が大きく、また変更を頻繁に加えることがない一方で、Webサーバのデプロイは頻繁になるため、Webサーバのデプロイのたびに重要なリソースに影響が出ないかを気にしたくない。
- 環境で分ける理由
- ※ただし、分けすぎると、前述した通りtfstate間の依存関係の管理が複雑になるというトレードオフがあるため、インフラリソースの重要度やチーム規模に応じて判断するといいです。
- チームによってはデータストアを管理するチームと、アプリケーションを管理するチームが別となっていて、権限的にリポジトリが別となっている方が嬉しいケースもあると思います。チームに応じてフォルダ分けするかリポジトリ分けするか判断してください(たぶんここが一番むずいです。所感としては、1チームでインフラ管理するなら1リポジトリで良くて、フォルダ管理さえ適切にしておけば後々リポジトリ分割は比較的楽なのかなーと思っています)
フォルダ、ファイル構成
- env(dev, prod)
- 環境ごとにフォルダを分け、別のtfstate管理とする
- vault
- データストアやホストゾーンといった重要なインフラリソースを扱う。
- 上の例ではさらにpostgresqlフォルダに分けて管理している。PostgreSQLなど、この環境内で動かすデータストア用。今回の例では他のデータストとはtfstateの管理を分離する方針をとっている
- service
- web api のアプリケーションなど、この環境で動かすアプリケーションあるいはマイクロサービス。他のアプリケーションとはtfstateの管理を分離するようにする
- module
- 各アプリケーションが共通で使う。ここにはアプリケーション依存のものは入り込まないようにする。tfstateの管理も行わない。
- terraform.tf
- terraform bucket の定義、providerの定義
- main.tf
- インフラリソース、データソースの定義
- 複雑なserviceになると、main.tfが肥大化する傾向にあるので、適宜ファイル分割するのもいいです
-
main.tf
はmodule
を利用する箇所とし、moduleを利用しないresouceを定義する場合は、同階層に<resouce_name>.tf
(例: google_iam.tf)
と定義するなど。
-
- variables.tf
- 入力変数
- outputs.tf
- 出力変数
- dependencies.tf
- データソース。外部のtfstateに依存している部分をわかりやすくする。
- import.tf
- すでにインフラリソースがある場合にimportを行う
まったく同じフォルダ構成ではないですが、パルワールド専用サーバーを構築するためのterraformコードを参考までに置いておきます。
my-palworld/infra/terraform at main · sbleru/my-palworld
Terraformを使ってパルワールド - Palworld の専用サーバーをGoogle Cloudにさくっとたてたい
命名規則
- Naming conventions - Terraform Best Practices
- スネークケース
- フォルダ名、ファイル名、module名、すべてスネークケースの方針が楽
とはいえtfstate分けると依存関係の管理大変なんだがどうにかできない?
wip…
自分はまだできていないですが、Terragrunt を利用することが O'Reilly Japan - 詳解 Terraform 第3版 でも記載されています。tfstateの一括適用、その依存管理などができるようです。
Terramateという選択肢もあるみたい
(hashicorpがtfstate分離推奨言ってるから、Terraform標準でなんか出てきてくれないかな)
おわりに
Terraformのフォルダ構成は、最小限の影響で済むことを意識したトレードオフ分析を行うと良さそうというのを説明しました。
自分はTerraform導入時に、IaCってアプリケーションコードよりも後戻りしづらいからフォルダ構成めっちゃ大事なんではって思っていろいろ調べて、「最小限の影響で済むように構成する」という意識をもとにトレードオフ分析をするのがしっくりきました。
今回提示したフォルダ構成は、コンテキストによっては決して正解ではないので、プロダクト規模、チーム構成に合わせてトレードオフを検討し決めていくのが良いと思います。
(ここで書かなかったこと一個埋めた〜 Terraformハンズオン - できるだけ何も考えず、とりあえず使ってみよう - 「インフラ構築となると、コードの構成によっては改修するの大変なんじゃないか?最適なフォルダ構成があるんだろうか?」
Discussion