
Terraform(AWS)の構成を公開します
はじめに
アプリボット SREチームの一条です。
弊社ではAWSやGCPの構築にTerraformを利用しています。
IaC(Infrastructure as Code)には欠かせないTerraformですが、長らく運用していく中で様々な課題に直面し、その度に構成や運用ルールを更新していきました。
しかし、まだ完璧な構成ではないと思っています。
なぜなら、会社・プロジェクト独自の事情もありますが、他社の事例を参考にしても運用方法は様々で、これといった正解がないと感じているからです。
今回は弊社のAWSにおけるTerraformの構成を公開しますので、事例の一つとして参考にしていただければと思います。
また、事例を世の中に増やすために、この記事を読んでくださった皆様も、構成や運用ルールを公開・共有していただけますと幸いです。
構成紹介
前提
弊社では1プロジェクトに対して、1~N個のAWSアカウントが存在します。
N個になる理由はプロジェクト次第ですが、大体は本番環境と開発環境でアカウントを分けたいという理由がほとんどです。
また、閲覧権限や可読性の問題から、1AWSアカウントに対して、1リポジトリで管理しています。
(コード管理はGitHubを利用)
これらの前提のもと、1リポジトリ内のフォルダ・ファイル構成を公開します。
フォルダ・ファイル構成
repo_root
├── 00_account
│ ├── 00_init
│ │ ├── data_sources.tf -> ../data_sources.tf
│ │ ├── provider.tf -> ../provider.tf
│ │ ├── terraform.tfvars -> ../terraform.tfvars
│ │ └── var.tf -> ../var.tf
│ ├── 01_additional
│ │ └── 同上
│ ├── data_sources.tf
│ ├── provider.tf
│ ├── terraform.tfvars
│ └── var.tf
├── 0N_environment
│ ├── Application
│ │ ├── data_sources.tf -> ../data_sources.tf
│ │ ├── provider.tf -> ../provider.tf
│ │ ├── terraform.tfvars -> ../terraform.tfvars
│ │ ├── var.tf -> ../var.tf
│ │ └── variables_from_module.tf -> ../variables_from_module.tf
│ ├── DB
│ │ └── 同上
│ ├── KVS
│ │ └── 同上
│ ├── Network
│ │ └── 同上
│ ├── data_sources.tf
│ ├── provider.tf
│ ├── terraform.tfvars
│ ├── var.tf
│ └── variables_from_module.tf
├── build_target
├── init
│ └── init.tf
├── terraform_exe.sh
└── utils
└── create_tfstate_s3.sh
この構成になった理由
この構成になった理由を説明するためには、デプロイ方式についての理解が必要であるため、先に説明します。
デプロイ方式を名付けるなら、「build_target方式」というもので、build_targetファイルにterraformコマンドを実行したいフォルダパスを記載し、terraform_exe.sh を実行します。
terraform_exe.sh の詳細は割愛しますが、主に以下のような処理を実行します。
- terraform init
- terraform plan or apply
例として、build_target ファイルが以下の状態でterraform_exe.sh を実行すると、00_account/01_additional 、01_dev/Application フォルダにあるリソースだけplan、applyが実施されます。
# 00_account/00_init
00_account/01_additional
# 01_dev/Network
01_dev/Application
# 01_dev/DB
# 01_dev/KVS
これらを踏まえて、なぜこのデプロイ方式を採用したのか、なぜこの構成となったのかを説明します。
tfstateを分割したいため
極論、この一言に尽きます。基本的に各フォルダ毎にtfstateが存在しています。
tfstateを分割するメリットとして以下が挙げられます。
- plan、applyの影響範囲を最小限にしたい
- plan、applyにかかる時間を削減したい
build_target方式であれば、これらのメリットを享受することができます。
また、CI/CDを実現する場合、通常であれば全環境・全フォルダでplan、applyを実行すると思います。
その場合、自分が修正した箇所以外のplan、applyの結果も見る必要がありますが、リソースが増えるとその量が多くなります。
build_target方式であれば、自分が明示した箇所の結果だけ確認すれば良くなるため、作業効率も良く、物理的に疎結合化することもできます。
これらのことから、弊社ではデプロイにbuild_target方式を採用しています。
次に、tfstateの分割単位であるフォルダや、共通ファイルについて説明します。
フォルダ・ファイル説明
第一階層 | 第二階層 | 種別 | 説明 |
---|---|---|---|
00_account | フォルダ | 初期設定及び、共通利用するリソースを管理する | |
00_account | 00_init | フォルダ | AWSアカウントで必要な初期設定のためのリソースを管理する (例:AWS CloudTrail、Amazon GuardDuty) |
00_account | 01_Additional | フォルダ | プロジェクト内で共通で利用するリソースを管理する (例:Amazon Route 53、AWS Certificate Manager、Amazon ECR) |
0N_environment | フォルダ | 各環境で利用するリソースを管理する フォルダ名は02_dev、03_stgなど | |
0N_environment | Network | フォルダ | ネットワークに関連するリソースを管理する (例:Amazon VPC、各リソースで使用するセキュリティグループ) |
0N_environment | Application | フォルダ | アプリケーションに関連するリソースを管理する かなり広い役割のため、ファイル数が多い (例:Amazon EC2、Amazon ECS、Elastic Load Balancing、Amazon CloudFront) |
0N_environment | DB | フォルダ | データベースに関連するリソースを管理する (例:Amazon RDS、Amazon Aurora) |
0N_environment | KVS | フォルダ | Key-Value Storeに関連するリソースを管理する (例:Amazon ElastiCache) |
00_account 0N_environment | data_sources.tf | ファイル | 共通ファイルセット TerraformのData Sourcesの機能を使用して、tfstateを跨いでリソースを参照するためのファイル |
00_account 0N_environment | provider.tf | ファイル | 共通ファイルセット Terraformの実行に必要なproviderファイル |
00_account 0N_environment | terraform.tfvars | ファイル | 共通ファイルセット Terraformで利用する変数の値を定義するファイル |
00_account 0N_environment | var.tf | ファイル | 共通ファイルセット Terraformで利用する変数を定義するファイル |
00_account 0N_environment | variables_from_module.tf | ファイル | 共通ファイルセット 外部のモジュールを定義するファイル |
init | init.tf | ファイル | tfstateの保存先を設定するためのファイル |
build_target terraform_exe.sh | ファイル | 前述のデプロイをするために必要なファイル | |
scripts | フォルダ | 各種スクリプトを管理する |
運用ルール
build_target方式を前提としたフォルダ構成でも課題はあり、それらをカバーするための運用ルールがいくつも存在しています。
この項では、build_target方式を用いつつ、チーム開発をする上で設けている運用ルールを一部紹介します。
plan結果に自身が想定していないリソースがある場合はチームに確認する
共同で作業している場合、他のメンバーが先にデプロイをすることで、ローカル環境でのplanにて差分がでる場合があります。
その場合は、念の為チームメンバーに確認するようにしています。
terraform_remote_stateは極力使用しない
tfstate間の受け渡しはData Sourcesを使用しています。
terraform_remote_stateを使用すると、outputを書く手間が増えるためです。
Data Sourcesで取れないものが出てきた場合はterraform_remote_stateを使用しても良いことになっていますが、今のところほとんどありません。
共通モジュールは専用のリポジトリで管理する
共通化できそうなモジュールがある場合、専用のリポジトリでモジュール化し、参照するようにしています。
Workspacesは使用しない
プロジェクトの性質上、各環境で構成が完全に一致することがないため、導入ハードルは非常に高いです。
この構成の良い点
デプロイ時間の短縮
tfstateを分割し、デプロイする対象フォルダを指定できるため、デプロイ時間が大幅に短縮できます。
疎結合化
全ファイルをapplyすることもなく、フォルダ単位で疎結合になるため、独立したデプロイが可能です。
Conflictは発生するが無事故
チームの性質もありますが、基本的に同じフォルダ内で作業することもないため、誤ってリソースを削除してしまうといったことは、今のところ発生していません。
この構成の課題・改善したい点
Applicationフォルダのtfstateが大きくなりがち
実際に運用してみた結果、Applicationという名前の役割が大きく、迷ったらこのフォルダに入れてしまいます。
しかし、他に良い分割サイズも見つかっていません。
Regionの概念が生まれた時にフォルダ構成を共通にできなさそう
環境依存(一つの環境で複数Regionを使用)する場合は、各環境配下にRegionのフォルダを切って運用しています。
しかし、環境依存せず、1AWSアカウントでRegionを跨いで複数プロダクトを作りたい場合、どのように構成すべきかが見えていません。
まとめ
Terraformの構成を言語化してみましたが、改めて正解がないと感じました。
余談になりますが、GCPにおいては今回のフォルダ構成が適用できないことも多く、さらに試行錯誤を重ねています。
これを読んでくださった皆様の参考になれば幸いです。
また、皆様が思う最強のフォルダ構成も公開してくださると、非常に嬉しいです!
この記事へのコメントはありません。