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

# はじめに
この記事は [terraform Advent Calendar 2020](https://qiita.com/advent-calendar/2020/terraform) の17日目です。

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

- [Terraformなにもわからないけどディレクトリ構成の実例を晒して人類に貢献したい](https://www.m3tech.blog/entry/2020/07/27/150000)
- [Terraformのディレクトリ構成の模索](https://blog.engineer.adways.net/entry/2020/07/03/150000)
- [Terraformのディレクトリパターン集](https://qiita.com/reireias/items/253529c889cafb3fa4c7)
- [Terraformにおけるディレクトリ構造のベストプラクティス](https://dev.classmethod.jp/articles/directory-layout-bestpractice-in-terraform/)
- etc...

さて、私はCDPであったりデータ分析環境のためのS3やIAM設計に携わっています。
そんな中、以前にnoteに投稿した記事である「[データ分析環境のためのaws S3設計](https://note.com/hkak03key/n/n7dcdaaea0a98)」において、
> **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_sample` と `adhoc_sample` を持つ例を用意しました。
`adhoc_sample` のいくつかのroleは `datalake_sample` のs3を参照するような構成です。

- https://github.com/hkak03key/terraform-advent-calendar-2020

## 提案手法のメリット
**projectの責任範囲が明確になります。**
「projectにおいて何を作成するのか」
「このデータの管理はどのprojectなのか」
のかが明確になります。

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

## 提案手法のデメリット
**融通がきかない（ちょっと例外的なことをやろうとするとすぐに表現が複雑になる）という欠点があります。**

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

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

このとき、 **オンプレに配置するIAM userはどのprojectに配置するべきだろうか？** という問題が生まれます。
![スクリーンショット 2020-12-16 23.10.09（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223584/20783b2b-0aba-e850-1c65-96ff8cc8eb49.png)

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

- onpreのためにIAM userを発行するprojectを用意する <font color="#aaaaaa"># `connect_to_onpre_project` とする</font>
    - iam userを作成し、出力とする
- `datalake_project` に `delegate_identities` なる `variable` を用意し、 `datalake_project` 内部で書き込み権限を付与
    - 書き込みなどを委譲するidentityを渡すための引数
    - この `variable` 経由で、 `connect_to_onpre_project` で生成したiam userを渡す

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

- https://github.com/hkak03key/terraform-advent-calendar-2020/tree/example/delegate
- [main branchとの差分](https://github.com/hkak03key/terraform-advent-calendar-2020/compare/main..example/delegate)

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

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

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