アプリを中心に据えたTerraformディレクトリ構成


この記事は[OpenSaaS Studio Advent Calendar 2019](https://adventar.org/calendars/3928)の17日目の記事です。

# はじめに

Terraformを利用していくうえで多くの人が悩むのがディレクトリ構成だと思います。
`terraform 構成`なんかでググるといろいろ参考になる情報は出てきますが、構築しているシステムや運用方法などが変われば求められることも当然変わるため、丸パクリはできません。
結局は自分で考えないといけないんですよね。
この記事もあくまで特定環境のひとつのサンプルとして、terraformディレクトリ構成の例と、何故そうしているかの理由を書いていきます。

Terraformのバージョンは0.12.14で、AWSの構成を行います。

# ディレクトリ構成

ざっくり2つのリポジトリに分けました。

- リソースの具体的な定義を記述した【共通モジュールリポジトリ】
- 共通モジュールを利用する【テナント環境構成用リポジトリ】

【テナント環境構成用リポジトリ】側から、`git submodule`で【共通モジュールリポジトリ】を利用しています。
ファイル名やディレクトリ名にauやらdocomoやらが出てきて「なんじゃこら？」となりますが後述します。

```
    【共通モジュールリポジトリ】
    ./modules...（terraform moduleの定義。ECSなど定義すべきresourceが多い場合はmoduleを定義している。）
    ./rds...(RDS専用ディレクトリ。)
    ./subscription 
    ├── au
    │   ├── main.tf
    │   ├── variable.tf
    │   ├── au.tf
    │   ├── au_variable.tf
    │   ├── au_charge_worker.tf
    │   ├── au_charge_worker_variable.tf
    │   └── task-definitions
    │       ├── au.json
    │       └── au-charge-worker.json
    │
    ├── creditcard ... (クレカ決済ディレクトリ)
    ├── docomo ... (ドコモ払いディレクトリ)
    ├── softbank ... （ソフトバンクまとめて支払いディレクトリ）
    │
    ├── main.tf
    ├── variable.tf
    ├── subscription.tf
    ├── subscription_variable.tf
    ├── batch.tf
    ├── batch_variable.tf
    ├── worker.tf
    ├── worker_variable.tf
    └── task-definitions
        ├── batch.json
        ├── subscription.json
        └── worker.json

----------------------------------------------------
    【テナント環境構成用リポジトリ】
    .
    ├── resources
    │   ├── dev　（省略）
    │   ├── stg　（省略）
    │   └── prd
    │       ├── common
    │       ├── rds
    │       └── subscription
    │           ├── au.tf
    │           ├── creditcard.tf
    │           ├── docomo.tf
    │           ├── softbank.tf
    │           ├── subscription.tf
    │           └── variable.tf
    └─── terraform-modules （↑の共通モジュールリポジトリをsubmoduleとしてリンク。）
    
```

# なんでこうなった

## アプリ（ECS）中心の構成
まず気になるのが`subscription`やら`softbank`やら`creditcard`などの、terraformのディレクトリでは見かけないようなファイル名ですよね。
前提として、私達のチームでは社内向けの課金決済基盤システムをECS Fargate上で動かしていて、その管理をTerraformで行っています。
`subscription`はサブスクリプション方式での決済のことで、`au`は`auかんたん決済`、`softbank`は`ソフトバンクまとめて支払い`を利用した課金機能のことを表しています。

たとえば `【共通モジュール】/subscription/au/au.tf`は以下のような内容となっています。

```
     resource "aws_lb_target_group" "subscription-au" {
      name     = "${var.env}-subscription-au"
      port     = 80
      protocol = "HTTP"
      ...
    }
    
    resource "aws_lb_listener_rule" "subscription-au" {
      listener_arn = ...
      condition {
        field  = "path-pattern"
        values = ["/subscription/au*"]
        ...
    }
    
    resource "aws_sns_topic" "subscription-au" {
      name = "${var.env}-subscription-au"
      ...
    }
    
   resource "aws_ecs_service" "service" {
     name    = "${var.env}-subscription-au"
     ...
   }

   ...subscription-auという機能に必要なその他諸々。
   ...aws_cloudwatch_log_group, aws_ecs_task_definition ...etc
```

`auかんたん決済`を利用したサブスクリプション機能に必要なAWSリソースをひとつのファイルに組み込んでいます。(listener_rulesやtarget_group、アプリが利用するSNSトピックやS3の定義、もちろんECSサービス・タスクの定義など、全部)

このような構成にした理由は機能を利用するための依存関係をまとめておくことで、利用する時にいちいち思い出さずに済むようにしたかったからです。
後述する理由で私達は何度もシステムの動作環境を作る必要があるのですが、そのたびに「この機能に必要なSNSトピックってなんだっけ？これは別の機能で使ってるやつだっけ？」みたいなことを考えていられなかったのですね。

## 機能を選択・詳細設定するリポジトリと、共通の部品を提供するリポジトリ

もうひとつの目につく特徴は2つのリポジトリが存在して、`git submodule`を使っていることでしょうか。
【共通モジュールリポジトリ】で機能ごとに依存関係がひとまとめにされたモジュールを定義し、【テナント環境構成用リポジトリ】側で必要な機能を選択し、その機能に環境特有の変数を与えるための設定を行う、という役割分割をしていますね。

私達が作っている社内決済基盤は、

- 複数の社内サービスがひとつの決済基盤システムを利用する「マルチテナント環境」の提供
- ひとつの社内サービスがひとつの決済基盤システムを利用する「シングルテナント環境」の提供

という提供方法に分かれていて、
たとえば「マルチテナント」「サービスA専用シングルテナント」「サービスB専用シングルテナント」はそれぞれ別のAWSアカウントのうえで構築されます。
(シングルテナントだと他サービスのアクセス増加の影響を受けることがなかったり、ある程度システムの柔軟性を保持できるなどのメリットがあります。あとはセキュリティ的な要求があって選択する場合もあります。）

ここで大変なのが、

- Subscriptionのauかんたん決済を使うのは「サービスA専用シングルテナント」だけ。
- ドコモ払いとソフトバンクまとめて支払いを使うのは「サービスB専用シングルテナント」だけ。

なんてことが起きてきます。
if文でこの状況を記述するのはなんとしても避けたいですよねー...ってことでこのリポジトリ構成に行き着きました。
↓は【共通モジュールリポジトリ】を利用する【XXXテナント構成用リポジトリ】の例です。

```
【マルチテナント構成用リポジトリ】（全部のせ）
    .
    ├── resources
    │   ├── dev
    │   ├── stg
    │   └── prd
    │       ├── common
    │       ├── rds
    │       └── subscription
    │           ├── creditcard.tf
    │           ├── au.tf
    │           ├── docomo.tf
    │           ├── softbank.tf
    │           └── subscription.tf
    └─── terraform-modules（git submodule）
-------------------------------------------------------------
    
【サービスAシングルテナント構成用リポジトリ】（auかんたん支払いしか使わない）
    .
    ├── resources
    │   ├── dev
    │   ├── stg
    │   └── prd
    │       └── subscription
    │           ├── au.tf
    │           └── subscription.tf
    └─── terraform-modules（git submodule）
-------------------------------------------------------------    

【サービスBシングルテナント構成用リポジトリ】（ドコモとソフトバンクしか使わない）
    .
    ├── resources
    │   ├── dev
    │   ├── stg
    │   └── prd
    │       └── subscription
    │           ├── docomo.tf
    │           ├── softbank.tf
    │           └── subscription.tf
    └─── terraform-modules（git submodule）
```

【共通モジュールリポジトリ】にはすべてが揃っているので、
【サービスA専用シングルテナント構成用リポジトリ】はauかんたん決済を使うための設定を書けばいいだけだし、【サービスB専用シングルテナント構成用リポジトリ】ではauかんたん決済の設定は含めず、ドコモ・ソフトバンクの設定を書くだけでOK。超ラク。


# まとめ

- リソースの具体的な定義を記述した【共通モジュールリポジトリ】と、それを利用する【XXXテナント環境構成用リポジトリ】を作った。
- 【共通モジュールリポジトリ】のディレクトリ構成は各テナント環境が利用する機能単位に対応。
- ECSタスクを中心に据えて、そのECSタスクが利用するAWSリソースを同一のファイルにまとめて定義することで、依存関係の理解を助ける + 利用時に依存関係を気にする必要をなくした。


よくあるTerraformの構成では「rds」とか「alb」などのリソース名が並んだりするものですが、課題を解決するためにいろいろ考えていると全然違うものが出来上がりました。
ある特定の環境ではこのようなトリッキーな構成も求められるんだなぁ、、というのが自分で作っておいて感じた感想です。
みなさんのそれぞれの環境での構成を考える際の参考になれば。