🏔️

マルチテナント構成のTerraform戦略

2023/12/10に公開

こんにちはへたれです。
株式会社アイデミーでエンジニアとして、Lab Bankという化学業界の研究室向けSaaSを開発、運用しています。

はじめに

Lab Bankは複数の会社、研究所に対して研究データ蓄積機能を提供するSaaSです。
顧客企業の研究データかなりセンシティブなので、扱いには相当気を使います...
そのため、Lab Bankではアプリケーションレベルでのアクセス制御だけではなく、ECSサービスレベルでの分離も行っています。

この構成で開発していく中で、VPCやALBなど共通で用意したい部分と、ECSなどのテナント毎に用意したい部分が出てきました。
そういった中で、どのようなTerraformのディレクトリ構成にたどり着いたのか書きます。

Terraformの運用

ディレクトリ構成はざっと以下のような形です。

.
├── docs ... デプロイフローなど、ドキュメント類
├── env ... 環境、テナント毎にmain.tfとtfvarsの用意
│  ├── dev
│  ├── stg
│  └── prd
│     ├── main.tf ... 共通化
│     ├── A
│     |   ├── main.tf ... A社向けテナントのリソース
│     |   ├── provider.tf
│     |   └── prd.tfvars ... クレデンシャル情報 (gitリポジトリでは管理しない)
│     ├── B
│     |   ├── main.tf ... B社向けテナントのリソース
│     |   ├── provider.tf
│     |   └── prd.tfvars
│     └── C
│         ├── main.tf ... C社向けテナントのリソース
│         ├── provider.tf
│         └── prd.tfvars
└── modules ... 構成要素をモジュール化したもの
   ├── infra ... 全てのテナントに共通
   └── common ... テナント毎に作成
      ├── audit_log_storage
      ├── ecs_app
      └── database
   

影響範囲の分離

env以下がTerraform実行ディレクトリとなっています。
変更を適用する際は以下のような使い方をします。

$ cd env/prd/A
$ terraform apply --var-file prd.tfvars

実行ディレクトリを

  • 共通部分
  • A社テナント向けリソース
  • B社テナント向けリソース
  • C社テナント向けリソース

としていることでtfstateを適切に分離し、変更差分を適用する際に影響範囲の限定と把握を容易にしています。

モジュール呼び出しで分けない理由

影響範囲を限定するもう一つの方法として、テナント構成全体をモジュール化し、モジュール呼び出しのパラメタ変更のみを行うという手法もあります。
例えばmain.tfに

module tenant_a {
  source = "../../../modules/app_tenant"
  
  param1 = "hoge-A"
  param2 = "foo-A"
}
module tenant_b {
  source = "../../../modules/app_tenant"
  
  param1 = "hoge-B"
  param2 = "foo-B"
}
...

とするようなケースです。

Lab Bankではモジュール呼び出しで影響範囲を分けず、Terraform実行環境で分けています。
理由としては、今後顧客ごとに構成が変化する可能性が大きいためです。
研究データの蓄積プラットフォームであるLab Bankですが、顧客の価値創出のためには蓄積から先のステップ、データの活用がが必要となります。
ただしそれぞれのニーズも異なるため、共通機能として盛り込むこと以外にも、追加サービスの提供や外部ツールとの連携といったニーズは必ず出てくると考えていました。
main.tfをテナント毎に分離し、柔軟でかつリスクを最低限にした上で実行できるような構成としました。

module ecs_app {
  source = "../../../modules/ecs_app"
}
# ここから下は追加するテナント、そうじゃないテナントがある
module power_bi {
  source = "../../../modules/power_bi_connection"
}
...

リモート参照

tfstateを分離したため、共通部分とテナント部分のtfstateでパラメタを共有する必要があります。
たとえば共通部分でALBを構築し、テナント部分でECS、ターゲットグループ、ALB Listener Ruleを作成する場合などです。
Terraformにある程度親しみのある方ならご存知だと思いますが、tfstateのリモート参照をすることでこれを解決しています。

やり方は非常に簡単で、共通部分のoutput.tfで

output "alb" {
  description = "ALB"
  value = {
    id                = aws_lb.main.id
    arn               = aws_lb.main.arn
    security_group_id = aws_security_group.alb.id
    dns_name          = aws_lb.main.dns_name
    zone_id           = aws_lb.main.zone_id
    listener_arn      = aws_lb_listener.main.arn
  }
}

のようにしておき、テナント部分のmain.tfで

data "terraform_remote_state" "infra" {
  backend = "s3"
  config = {
    region = "ap-northeast-1"
    bucket = "<共通部分のtfstateのバケット名>"
    key    = "<共通部分のtfstateのキー>"
  }
}

のようにするだけです。

※ 異なるAWSアカウントでリモート参照する際には、terraform applyするロールにバケット参照の許可を与える必要があります。

おわりに

今回はLab BankでのTerraform構成について紹介しました。
テナント毎の影響範囲の分け方など、参考になることがあれば嬉しいです。
これからも改善を積み重ねていきたいので、「もっとこうした方がいいよ〜」みたいなアイデアがあればコメントください!!

またアイデミーでは一緒に働く仲間を募集中です
ぜひご応募ください!

Aidemy Tech Blog

Discussion