OOPのカプセル化の考え方を応用したTerraformディレクトリ構成の提案


はじめに

この記事は terraform Advent Calendar 2020 の17日目です。

IaCツールの1つであるTerraformのディレクトリ構成をどうするかについては、Terraformを利用しているエンジニアであれば一度は考えたことがあるのではないでしょうか。
そして、これについては先人の皆様によって様々な提案がなされてきました。

さて、私はCDPであったりデータ分析環境のためのS3やIAM設計に携わっています。
そんな中、以前にnoteに投稿した記事である「データ分析環境のためのaws S3設計」において、

bucketを分割するということは、責任分界点を定義するための行為

と述べました。
これについて オブジェクト指向に通ずるものがあるな... と感じました。

「Terraform上で、projectやproductといった単位でリソースをまとめ、その範囲に於ける責任を明確化し、project/product同士の関係性を定義できれば、より安全にdatalakeやDWHを構成できるのでは?」と思ったのです。

そして、これをコードで実現できないだろうか?と考えたところ、 Terraformのmodule機能が使えるのでは と思いつきました。

本記事では、 オブジェクト指向の考え方をTerraformのmodule構成に応用したディレクトリ構成 を提案します。

※私がCDPやデータ分析環境等のためのaws構成設計を主として取り組んできたため、本記事はその文脈での提案・説明となります。
ですが、きっと他のアプリケーションにも応用が効くのではないかと考えております!

提案内容

構成と、そのメリデメについて述べます。

構成

まず、構成図と構成における原則を述べます。
また、構成例として、github repositoryを公開しましたので参照ください。

構成図

(terraform)
├─ environments
│   ├─ prod # terraform コマンド実行時のroot
│   │   ├─ backend.tf
│   │   ├─ main.tf
│   │   ├─ main_shared.tf -> ../shared/main.tf
│   │   ├─ provider.tf
│   │   └─ variables.tf
│   │   └─ versions.tf    -> ../shared/versions.tf
│   ├─ shared
│   │   ├─ main.tf
│   │   └─ versions.tf
│   └─ stg # terraform コマンド実行時のroot
│       ├─ backend.tf
│       ├─ main.tf
│       ├─ main_shared.tf -> ../shared/main.tf
│       ├─ provider.tf
│       ├─ variables.tf
│       └─ versions.tf    -> ../shared/versions.tf
├─ modules # 汎用的なmoduleを配置
├─ projects # 分析projectごとに必要なリソースを定義
│   ├─ project_a
│   │   ├─ aws_iam.tf
│   │   ├─ aws_s3.tf
│   │   │    : 
│   │   ├─ locals.tf
│   │   ├─ output.tf
│   │   └─ variables.tf
│   ├─ project_b
│   │    : 
│   └─ project_z
└─ templates # 汎用的なtemplatesを配置

構成における原則

以下の原則を守ってコーディングを行います。

  • main.tf (もっと言えば environments/{env} 下)にはresourceを定義しない。
  • resource定義は原則 projects/{pj} 内部に書く。
    • main.tf には、定義されたproject moduleのインスタンスを定義する。
    • projects/{pj} には、projectで必要なresourceを定義する。
    • 他のprojectに存在するresourceを読みたいときは、 variable で必要な値を受け取る。
    • 外部からアクセスして良いresource(S3のread用IAM poilcyなど)を output として定義する。
      • 書き込み責任がproject内で閉じるようにするために、書き込み権限を持つIAM poilcyは output に定義しない。

構成例

projectとして datalake_sampleadhoc_sample を持つ例を用意しました。
adhoc_sample のいくつかのroleは datalake_sample のs3を参照するような構成です。

提案手法のメリット

projectの責任範囲が明確になります。
「projectにおいて何を作成するのか」
「このデータの管理はどのprojectなのか」
のかが明確になります。

また、 project間の関係性も明確化され、予期せぬアクセスを発見しやすくなります。
特に、書き込みをproject内部で閉じてしまうことで、他のprojectからの書き込み事故を防げます。

提案手法のデメリット

融通がきかない(ちょっと例外的なことをやろうとするとすぐに表現が複雑になる)という欠点があります。

例として、以下のシステムを考えます:

  • 「オンプレシステムAからs3にデータを集めdatalakeを作る」 # datalake_project とする
  • 「datalakeを読んで、aws上で処理した結果をオンプレに戻す」 # adhoc_project とする
  • 「オンプレに配置するIAM userは1つにしたい」

このとき、 オンプレに配置するIAM userはどのprojectに配置するべきだろうか? という問題が生まれます。

「datalakeは別にadhoc projectを読みたいわけじゃないし...でも複雑度増すのもなぁ...」
と言った具合に悩ましいのですが、解の例として、以下のように「 dtalake_project への書き込みを他のprojectに委譲する」というのを考えてみました。

  • onpreのためにIAM userを発行するprojectを用意する # connect_to_onpre_project とする
    • iam userを作成し、出力とする
  • datalake_projectdelegate_identities なる variable を用意し、 datalake_project 内部で書き込み権限を付与
    • 書き込みなどを委譲するidentityを渡すための引数
    • この variable 経由で、 connect_to_onpre_project で生成したiam userを渡す

実際に作成した例がありますのでご確認ください。

ちょっと複雑度が増すなぁ...と思いつつ、結局「何でもやるリソースが存在するとどうしても表現が難しくなる」というのは当然起こることなのかなと思います。
逆に、複雑なことをしようとするとコードも複雑になるため、 設計をシンプルにしようという暗黙的なパワーが働く とも言えるかもしれません。

おわりに

OOPの考え方をTerraformのmodule構成に応用したディレクトリ構成を提案しました。
責任範囲を明確化し、整理してリソースを構成するのにはとても便利な一方で、融通がきかないという欠点もあるかと思います。

また、考慮漏れもあるかと思いますので、本記事に対してのご意見・改善方法などお待ちしております!
今後もより良いTerraformのディレクトリ構成を模索していきましょう!