Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Sansan Labs 開発での Terraform ディレクトリ構成

初めまして。あけましておめでとうございます。
DSOC R&D アーキテクトの鈴木賢志です。

Sansan Labs*1 では、新しい働き方の実現を目指し実験的な機能を一部公開しています。私は普段、それらの開発を中心に、R&D から生まれるサービスの品質改善などに取り組んでいます。
今回は、Sansan Labs で活用している Terraform のディレクトリ構成と、どのような理由でこの形になっていったかを紹介したいと思います。

Terraformのディレクトリ構成

Sansan Labs ではクラウドに AWS をメインで使用しており、昨年からインフラ構成管理のツールとして Terraform の利用を始めました。

初めて Terraform を使い始めた時に、最も悩んだのが .tf ファイルやディレクトリをどのように分けるか、という点でした。
これに対し色々考えた結果*2、以下のように落ち着きました。

terraform_project_root
├── env
│   ├── dev
│   │   └── main.tf
│   └── prod
│       └── main.tf
└── modules
    ├── module_1
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    ├── module_2
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    ├── ...

簡単にまとめると Terraform の Module という、構成をテンプレート化して再利用できるようにする機能を用いて、 環境ごとに分離したmain.tfファイルに、実際のインフラを表現していくといった戦略です。

このような形にした理由

システム開発の現場ではよくあることかもしれませんが、

  • dev / stg / prod といった環境ごとに、少しだけ異なる構成を表現したい

という要件がありました。

これを実現するような機能として、Terraform にはWorkspacesという、 Workspace の切り替えによって、同じコードで複数の環境に対して構成管理できる機能があります。

リソースの名前や数など、数行レベルの差異であれば上記の利用でも良かったのですが、Sansan Labs の開発では、Dev環境では自分たちで作成する必要があるが、 Prodなど一部の環境では別途管理されているため作成しなくて良いリソース*3が存在します。

これを Workspace で表現しようとすると、複雑な記載が増え、 かえって可読性や保守性が低下してしまうため、それぞれの環境別に main.tf を置き、 必要となる Module を素直に記述していくことで実現することにしました。

続きからは、.tf ファイルのサンプルなど、更に詳細を記載していきます。

運用方法

実行時の手順

コマンドの羅列になりますが、以下のように使用します。

# Dev環境の場合
cd env/dev
terraform init --backend-config="profile=Dev環境のProfile名"

# Prod環境の場合
cd env/prod
terraform init --backend-config="profile=Prod環境のProfile名"

# terraform init後
terraform apply --var="profile=Profile名"

例えば CI 環境など、実行する環境、および Credential が自明な場合は backend-config のオプションは不要です。

.tf ファイルの内容

大きく簡略化した、あくまでサンプル版となりますがご了承ください。*4

env/xxx/main.tf

# terraform{} や provider{} 等々は省略

module "application_ec2_instance_role" {
  source       = "../../modules/ec2_instance_role_app"
  role_name    = "${local.application_name}"
}

module "application_ec2_instance" {
  source        = "../../modules/ec2_instance"
  instance_name = "${local.application_name}"
  instance_role = "${module.application_ec2_instance_role.name}"
}

このように、modules ディレクトリ に定義した Module を source として使用し、環境ごとの構成を書いていきます。基本的に各環境の main.tf はほぼ同じ内容ですが、module に渡す変数で環境別の差異を表現しています。

例えば instance_role が Prod 環境では事前準備されている場合、env/prod/main.tfにはapplication_ec2_instance_roleの記述は不要となり、既存の instance_role 名を直接書くことで指定します。

環境毎に異なる変数や、変更の可能性があったり複数回登場したりするような変数は、Local Values で定義しています。 当初は env/xxx/variables.tf を用意し、*.auto.tfvars に記述することでコード上で管理しようとしましたが、1 つのファイルに情報がまとまっていた方が読みやすいと考えたため、この形にしました。 このあたりは実行する環境も考慮して、チームにあったやり方が良いかと思います。

Modules

Module には、実際に必要となるリソースを記述していくだけとなりますが、どの粒度まで Module を分けるかは悩ましい点でした。

これについて正解はないと思いますが、汎用性を意識しすぎて必要以上に細かく分けるよりも、 再利用したいレベルを念頭に、適度に抽象化して作成すると使いやすくなるかと思っています。

例えば、上記の例では ec2_instance_role_appec2_instance といったように、Role と Instance が別れていますが、これら Role と Instance を個別に再利用する必要がなく、必ずセットで成り立つサービスであれば、1 つの Module にまとめてしまって良いと思います。

終わりに

以上が現状の Sansan Labs 開発での Terraform ディレクトリ構成です。

他のチームがどのような運用をしているのか、どうしてそのようにしているのかというのは、個人的に気になるポイントなので、今回こうして自分たちのやり方を記事にしてみました。

ちなみに Terraform を選んだ背景には、 他のツールと比較した時の可読性の高さ*5や、AWS 以外のクラウドリソースにも流用しやすい等々の理由があり、 選定までの議論の中では「まだチーム内で使ったことがなかったからやってみたい」という声もありました。

このように正当性も議論しつつ、新しい事へチャレンジする機会もあるのは、R&D 開発の楽しいところです。

最後までお読みいただきありがとうございました。


buildersbox.corp-sansan.com

buildersbox.corp-sansan.com

*1:Sansan Webサービス内の右上にある、フラスコアイコンから利用できます。

*2:検討する上で一番はじめにヒットし参考にさせていただいたのは、クラスメソッド様のこちらの記事になります。ありがとうございます。

*3:IAM Roleやネットワークなど、インフラチームによって厳格に管理されている部分です。

*4:ちなみに省略しましたが私たちは.tfstateファイルをS3で管理しています。これも環境毎にBucketが異なる場合、この構成だと分けて記載しやすいです。

*5:AWSメインのため、大きな対案として CloudFormation がありました。今回は Terraform についての記事でしたが、サーバレス周りでは CloudFormation の拡張である SAM を使ったりもしています。

© Sansan, Inc.