動機
単一の Terraform state でリソースを構築していると、徐々にリソース数が増加し、コードの見通しが悪くなったり plan 時間が長くなったりといった問題が発生します。
- 単一 state で運用していたが、肥大化してきた。
・リソース量が多くて見通しが悪い。
・plan にかなり時間がかかる。
・多重度の違うリソース(例えば EKS クラスタ)をコピペして複製したくない。
そこで、モノリスに伴う問題を解消すべく state を適切な単位に分割する方法を考えます。
Terraform 構成に関する事例
Terraform state の構成は参考になる先行事例がいくつか存在します。
「それ、どこに出しても恥ずかしくないTerraformコードになってるか?」
「それ、どこに出しても恥ずかしくないTerraformコードになってるか?」 / Terraform AWS Best Practices
まとまりごとにモジュール化し、各環境でインポートして単一 state を構成する考え方です。
- 結局はモノリスなので、plan 時間の増大は避けられない。
- リソースにモジュールを使っていると、モジュール化することでネストが深くなる。
以下のようなディレクトリ構成で、環境ごとに state を作り、共通のモジュールを参照します。
. ├── envs │ ├── dev │ │ ├── backend.tf │ │ ├── local.tf │ │ ├── main.tf │ │ ├── provider.tf │ │ └── variables.tf │ └── prd │ └── (省略) └── modules └── sample-app ├── main.tf ├── outputs.tf └── variable.tf
『実践 Terraform』
実践Terraform AWSにおけるシステム設計とベストプラクティス
『実践 Terraform』の第21章に構造化について書かれているが、具体的な実装例は書かれていません。
これを実現したいと思います。
- コンポーネント分割
・安定度
・ステートフル
・影響範囲
・組織のライフサイクル - 依存関係の制御
本事例
無造作にコンポーネント分割していくと、state が増えた場合の運用が煩雑になるので、本事例では依存関係を一方向に制限することで見通しを良くし、運用をなるべくシンプルにすることを考えます。
アプローチ
ソフトウェアの SOLID 原則のように、コンポーネントを一方向の依存関係にする。
- ただし、SOLID 原則と違って、隣接コンポーネントを越えて依存してもよい。
- 上位コンポーネントで output として値を出力し、下位コンポーネントで
terraform_remote_state
を使って値を参照する。
02-vpc/outputs.tf
output "private_subnets" { description = "List of IDs of private subnets" value = module.vpc.private_subnets }
03-db/data.tf
data "terraform_remote_state" "vpc" { backend = "s3" config = { bucket = "${local.name}-terraform-state" key = "02-vpc/terraform.tfstate" region = local.region } } data "aws_subnet" "private" { for_each = toset(data.terraform_remote_state.vpc.outputs.private_subnets) id = each.value }
- トポロジカルソートして順番をディレクトリ名の接頭辞にする。
環境の出し分けは変数差し替えで実現する。
cd $ENV terraform -chdir=.. init -backend-config=$ENV/backend.tfvars terraform -chdir=.. apply -var-file=$ENV/terraform.tfvars
ディレクトリ構成
コンポーネントごとに state を分けます。
この際、上位(接頭辞の値が小さい)コンポーネントの output のみ参照できます。
. ├── 01-kms │ ├── dev │ │ ├── backend.tfvars │ │ └── terraform.tfvars │ ├── prd │ │ └── (省略) │ ├── backend.tf │ ├── main.tf │ ├── outputs.tf │ ├── provider.tf │ └── variables.tf ├── 02-vpc │ └── (省略) ├── 03-db ├── 04-route53 ├── 05-alb ├── 06-eks-log ├── 07-eks-cluster ├── 08-apps └── 09-system
利点
- state を適切なサイズに分割できる。
- 接頭辞を見ればコンポーネントの依存方向がわかる。
- 全体をモジュールでラップしなくて済む。
課題点
- 管理する state の数が多くなる。
- 依存する state での apply も必要になる。
・忘れがち。
・モノリスなら Terraform が担っていた処理を人力でやらないといけない。 - 間に state を追加すると、backend と terraform_remote_state を一斉に書き換えなければならない。
- リソースを綺麗に一方向の依存関係で分割できるかどうかは怪しい。
まとめ
肥大化した Terraform state への対処として、依存関係を明示しながらコンポーネント分割する方法を紹介しました。
この方法では、state を見通しのよい単位に分割できるメリットがあります。
その反面、管理対象の state の数が増えることによるデメリットもあります。
Terraform state 構成は正解がないですが、運用していてデメリットが顕在化してきた場合は、構成の改善を検討してみてはいかがでしょうか。
社内共有会で発表したときの質問・反応
- 削除はどうなるか?
構築の逆順(接頭辞の降順)に destroy する。 - apply 漏れが発生しそうだがどうか?
依存 state だけ apply すればよい。
ただ、作業者が毎回ちゃんとやってくれるかどうかは保証できないため、注意が必要。 - 運用でつらいところはないか?
編集で依存方向ミスは発生しうるため、レビューで弾くなど必要。
依存 state の自動 apply などできれば望ましい。 - plan 時間は短縮できたか?
初期構築はオーバーヘッド分時間が増加した。その後の更新は数分から数十秒になった。 - どのような方針でその分割単位にしたか?
log は eks-cluster で共通にしたかったので分けた。
alb はバックエンドの切り替えで細かく制御したいので分けた。
要件によって柔軟に変えてよい。 - モジュールを外部リポジトリに分けるかどうかの基準は?
モジュールを多数のリポジトリで共通化したい。
大規模になって,共通モジュールを別チームが管理するようになった。
インポート元リポジトリとは別のライフサイクルでバージョニングが必要。 - 移行はどうしたか?
必要なリソースだけ先に適切なコンポーネントに移動した。
新しいリソースを分割したコンポーネント内に作成した。