VISASQ Dev Blog

ビザスク開発ブログ

今一度改めて Terraform ディレクトリ構造のベストプラクティスを考えてみた件

こんにちは!フルサポート開発チーム兼 Embedded SRE の高畑(Sorarinu (@int_sorarinu) / Twitter)です。

最近、スノーボードだけでなくキャンプにも手を出してしまった結果、どんどん沼にハマっていっている私ですが皆さまいかがお過ごしでしょうか。

先日のシルバーウィークで 3 連休それぞれキャンプに出かけていたのですが、両日とも台風の影響で生憎の大雨となり雨濡れ耐性が格段に向上してしまいました。

さて今回は、弊社でも利用している Terraformディレクトリ構成について、改めてベストプラクティスってなんだっけというものを考えてみたのでご紹介いたします。

事の発端

今ではマイクロサービス化も進み、各サービスそれぞれで Terraform のコードが増えてきている状態となっています。 既存の Terraform では変数( creation フラグ)を用いて開発環境では作成しない・本番環境では作成するを制御するなど、割と Terraform Workspace に近い構成となっており、変数の見通しがちょっと悪くなっているといった状況でした。(variables の定義だけで数百行あります)

経験上 Terraform Workspace のような運用は全体的なコード量が減るといったメリットはあるものの、変数の取り回しでコードの可読性が悪くなったり段々と辛くなってくることが多いと感じていて、SRE 内でも「リファクタがしづらい」、「Terraform のコードを読んでもわからない」等の声がチラホラ見られるようになりました。

tech.visasq.com

また、Terraform のリソース作成部分を module に切り出しているのですが、 modules ディレクトリの中に API など個別に定義して呼び出すような構成となっていて、 変更のたびにあっちもこっちも編集しなければいけなく module のメリットをあまり活かせていない構成となっていました。

.
├── Makefile
├── README.md
├── backends
│   ├── dev.tfvars
│   ├── prod.tfvars
│   └── stg.tfvars
├── gcp_cloud_build.tf
├── gcp_cloud_logging.tf
├── gcp_cloud_run.tf
├── gcp_cloud_tasks.tf
├── gcp_loadbalancer.tf
├── gcp_secret_manager.tf
├── main.tf
├── modules
│   ├── app-server
│   │   ├── batch
│   │   │   ├── main.tf
│   │   │   ├── output.tf
│   │   │   └── variable.tf
│   │   ├── api
│   │   │   ├── main.tf
│   │   │   ├── output.tf
│   │   │   └── variable.tf
:
:

そこで、自分自身が新しく Terraform を用意することになったこともあり、このタイミングで今一度改めて Terraform のディレクトリ構成ってどうするべきなのかを考えることにしました。

ディレクトリ設計について

Terraform のディレクトリ構成については明確にベストプラクティスがあるという訳ではないため、Terraform を触る誰しもが悩む問題でもあると思います。 ただ、世の中に皆の考えたベストプラクティス的なものがある程度出回っていることもあり、それに乗っかっていくことが望ましいのではないかと考えました。

Google もこう言ってるし。

cloud.google.com

じゃあどうしたら良いんだろう?

前述した通り、経験上 Terraform Workspace 的な運用は後々辛くなってくるので、ざっくりと環境(dev、staging、production...)ごとにディレクトリを切ってしまい、このようなディレクトリ構成にしてみました。

.
├── Makefile
├── backends
│   ├── dev.tfvars
│   ├── prod.tfvars
│   └── stg.tfvars
├── environments
│   ├── dev
│   │   ├── Makefile
│   │   ├── cloudflare_dns.tf
│   │   ├── data.tf
│   │   ├── gcp_cloud_build.tf
│   │   ├── gcp_cloud_run.tf
│   │   ├── gcp_loadbalancer.tf
│   │   ├── gcp_monitoring.tf
│   │   ├── gcp_secret_manager.tf
│   │   ├── locals.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── terraform.tfvars
│   │   └── variables.tf
│   ├── prod
│   │   └── terraform.tfvars
│   └── stg
│       └── terraform.tfvars
└── modules
    ├── cloudflare
    │   └── dns
    │       ├── main.tf
    │       ├── outputs.tf
    │       └── variables.tf
    └── gcp
        ├── cloudbuild
        │   ├── main.tf
        │   ├── outputs.tf
        │   └── variables.tf
        ├── cloudrun
        │   ├── main.tf
        │   ├── outputs.tf
        │   └── variables.tf
        ├── loadbalancer
        │   ├── main.tf
        │   ├── outputs.tf
        │   └── variables.tf
        ├── monitoring
        │   ├── main.tf
        │   ├── outputs.tf
        │   └── variables.tf
        └── secretmanager
            ├── main.tf
            ├── outputs.tf
            └── variables.tf

ディレクトリはそれぞれ以下のようにしています。

backends

tfstate を配置するための GCS 情報が書かれていて、 terraform init が実行されるタイミングで読み込まれて main.tf の GCS 設定を override しています。

environments

環境ごとの terraform ファイルをここに配置しています。

前述した従来の creation フラグは単純にここに定義が書かれているか・書かれていないかで切り替えるようにしているため可読性がかなり良くなりました。

terraform.tfvarsGCP のプロジェクト情報などコアなものに限定し、環境ごとに異なる値である変数は基本的に locals.tf へ集約するようにしています。

modules

environments にある terraform ファイルから呼び出すモジュールを「汎用的に利用できるモジュール」として定義しています。

modules のなかに api とか batch とかそういった個別のケースでディレクトリは切らないことが重要で、モジュールを呼び出す側で必要な変数を渡すことで一部のリソースを作成する・しないを選べるようにしています。

モジュールは再利用性を考えて作ろう!

ディレクトリ構成を今一度考えてみて

環境ごとに Terraform ファイルのディレクトリを切り、module の汎用性を高めたことにより一つの変更に対する修正範囲がガクンと狭まりました。

dev、staging、production と同じようなコードをそれぞれ書かなければいけないのかという声もありそうですが、基本的に dev 等で動作検証さえできてしまえば後はそれぞれの環境にコピーして持ってくるだけなのでそこまで複雑ではないと思っています。

おわりに

いかがでしたでしょうか!

Terraform のディレクトリ構成は本当に悩ましいところで、本当にこれがベストだとは言い切れないものではありますが、これが「ビザスクでのベストプラクティスだ!」と胸を張って言えるように今後も考えていければ良いと思っています。

皆さんもぜひ参考にしてみてください!(こここうしたらより良い等の意見がありましたら教えていただけると嬉しいです!)