Overview
terraformのコードをDRYに保ったり、CLI実行を簡素化できるツール terragrunt
を検証します。
ユースケースとしては workspace
管理されているterraform環境を terragrunt
に移植するようなケースを想定します。
Terraformのworkspace機能とは
Terraform標準機能のひとつで、複数環境で同じTerraformコードを共有する際に利用できるソリューションです。
backendを自動で環境別で管理できたり、Terraformコード内に ${terraform.workspace}
変数を用いて環境名を注入できるので、DRY性高くTerraformのコードを記述できます。
Terragruntとは
TerraformのコードをDRYでメンテナンス容易に行えるツールみたいです。
今回は backend.tf
などの共通テンプレートファイルをDRYにする機能について検証していきます。
モチベーション
そもそも workspace
を用いてDRYに構成されたTerraform環境を、どういうモチベーションで脱したいのかの前提を記載します。
workspace
の使い方次第ではありますが、過度にDRYを追求すると下記の様な弊害が生まれがちです
- 特定の環境だけ少し構成を変更することが難しい
-
terraform
コマンドの実行ディレクトリが環境ごとに分離されておらず、CI/CDツールとの相性が悪い
これは持論なのですが、Terraformはそもそもインフラストラクチャを宣言的に記述するためのツールで、アプリケーションコードと違い、環境ごとに過度なDRYを追求する必要性はなく、ある程度はコードの重複管理を許容しながら柔軟に管理していくのが好ましいと考えています。
このようなモチベーションで、過度にDRYに構成されたTerraform環境を脱し、適度にDRYにコード管理が可能な terragrunt
を導入していくことを検討します。
基準環境構築
Terraformプロジェクトの作成
terragrunt
に移植する前の、基準となるTerraform環境を構築します。
要件は下記です
- 本番環境と開発環境を
env
配下のtfvars
ファイルで分離 - backendはS3を指定
- 基本となるリソースはプロジェクト直下の
main.tf
と、そこから呼び出されるmodules
ディレクトリ配下で定義- 簡単のため、作成するリソースはS3のみにしています
作成したコードは下記です。
-
https://github.com/kzk-maeda/terraform-ci/commit/69451d2eb449be25b352a0ab24897e28e18a9a96
├── Taskfile.yml ├── env │ ├── dev │ │ └── terraform.tfvars │ └── prod │ │ └── terraform.tfvars ├── module │ └── s3 │ ├── main.tf │ ├── output.tf │ ├── provider.tf │ └── variables.tf ├── backend.tf ├── main.tf ├── provider.tf └── variable.tf
各環境にapply
最初に terraform workspace new
コマンドを用いて必要なworkspaceを作成します。
terraform workspace new dev
terraform workspace new prod
plan / applyのコマンドは Taskfile.yml
にまとめていますが、基本的にはworkspaceをselectして、引数に環境ごとの terraform.tfvars
を渡す形になります。
terraform plan -var-file ./env/`terraform workspace show`/terraform.tfvars
terraform apply -var-file ./env/`terraform workspace show`/terraform.tfvars
すると、 bachend.tf
に定義したバケットに対応し、remote backendに下記のようにstateファイルが作成されます。
s3://env:/dev/path/to/terraform.tfstate
s3://env:/prod/path/to/terraform.tfstate
ここでのポイントは、 terraform workspace
に対応する環境が自動的に :env/dev
や :env/prod
というKeyに置き換えられているということです。
terraform workspace
では、このようにして複数環境のstateファイルを分割管理します。
これでworkspace管理されたTerraformのプロジェクトが作成されたので、次にこれを terragrunt
管理のものに変更していきます。
Terragruntの導入
インストール
インストールは下記のサイトを参照の上環境に合わせて行います
基本的にはパッケージマネージャーで管理されているので難しくないはずです。
stateファイルの移動
terraform workspace
で作成されたstateファイルを terragrunt
で管理できる場所に移動します。
前述の通り、 terraform workspace
では下記のようなKeyの下にstateファイルが作成されます。
s3://env:/dev/path/to/terraform.tfstate
s3://env:/prod/path/to/terraform.tfstate
この env:dev
の階層が曲者で、 terragrunt
を用いる場合はこのKeyをそのまま利用できません
なので下記のKeyの下にstateファイルを移動します。
s3://env/dev/path/to/terraform.tfstate
s3://env/prod/path/to/terraform.tfstate
env:
が env
に変わりました。
terragrunt
では後述する設定ファイルで定義する ${path_relative_to_include()}
関数が env/dev
などの環境別階層に置き換えられてstateファイルを参照するので、その形に合わせてstateファイルを移動します。
terragruntに対応したコードに修正(dev)
次に作成済みのterraformコードを、 terragunt
で管理可能な形式に修正します。
必要なステップは下記です。
-
*.tf
ファイルを環境別ディレクトリに移動 -
*.tf
ファイル内でterraform.workspace
を用いて値を埋めている箇所を、vars
経由で注入できる様に修正 -
terragrunt
でDRYに管理できるbackend.tf
やprovider.tf
を git ignore -
terragrunt
設定ファイルを作成・配置
1. *.tf
ファイルを環境別ディレクトリに移動
上述のディレクトリ構成を、下記の様に変更します。
├── Taskfile.yml
├── env
│ ├── dev
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terragrunt.hcl
│ │ └── terraform.tfvars
│ └── prod
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terragrunt.hcl
│ │ └── terraform.tfvars
├── module
│ └── s3
│ ├── main.tf
│ ├── output.tf
│ ├── provider.tf
│ └── variables.tf
├── .gitignore
└── terragrunt.hcl
main.tf
などが、プロジェクト直下から環境別ディレクトリの下に移動しています
terragrunt.hcl
などのファイルについては後述します、今はこの階層にこういうファイルが作られるものと認識してください。
2. *.tf
ファイル内で terraform.workspace
を用いて値を埋めている箇所を、 vars
経由で注入できる様に修正
元の main.tf
では下記の様な記述が散見されていました。
# main.tf
resource "aws_s3_bucket" "this" {
bucket = "${terraform.workspace}-${var.bucket_name}-${data.aws_caller_identity.current.account_id}"
}
${terraform.workspace}
で記述されている箇所は、指定されたworkspace名( dev
など)が入るのですが、workspaceから脱却するにあたってこの指定は使えなくなります。
(厳密にはbackendに記述された env:
Keyの下の値を参照しています)
これをvars経由で取得する形式に変更します。
# main.tf
resource "aws_s3_bucket" "this" {
bucket = "${var.env}-${var.bucket_name}-${data.aws_caller_identity.current.account_id}"
}
あとは variables.tf
など修正してvars経由で env
変数を注入できるようにすればOKです。
3. terragrunt
でDRYに管理できる backend.tf
や provider.tf
を git ignore
次のstepで terragrunt
を導入しますが、 terragrunt
は実行すると対象のディレクトリに backend.tf
や provider.tf
を作成しにいきます。
せっかくこれらのファイルをDRYに管理できるようにしようとしているので、作成するたびにgit管理下に置かれると結局管理対象ファイルが増えてしまい、メリットが享受できません。
なので .gitignore
でこれらのファイルをignoreします。
# .gitignre
backend.tf
provider.tf
4. terragrunt
設定ファイルを作成・配置
terragrunt
設定ファイルを、プロジェクト直下と環境ごとのディレクトリの2ヶ所に配置します。
設定ファイル名はどちらも terragrunt.hcl
です。
まず、プロジェクト直下の設定ファイルは下記の様に記述します。
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
bucket = "kzk-sandbox-terraform-tfstate"
key = "${path_relative_to_include()}/terragrunt/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
region = "ap-northeast-1"
version = "~>4.0"
shared_credentials_file = ".aws/credential"
profile = "ci"
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
EOF
}
remote_state
ブロックでは、 backend.tf
に記述する内容のテンプレートを書きます。
"${path_relative_to_include()}/
に注目してください。
この階層が、前述した env/dev
などのKeyに置換されることになります。
generate "provider"
ブロックでは、 provider.tf
に記述する内容を書きます。
次に、環境別ディレクトリ配下の設定ファイルを記述します。
include "root" {
path = find_in_parent_folders()
}
これら2つの設定ファイルを配置することで、環境別に分散したterraformコードのうち、共通で利用できるものをDRYに記述することができます。
stateファイルを指定してplanを実行
terragrunt
は専用コマンドを実行することで terraform
ライクに操作することが可能です。
環境別のディレクトリに移動して、 terragrunt
コマンドを実行します。
cd path/to/env/dev/
terragrunt init
terragrunt plan
terragrunt apply
terragrunt init
を実行することで、 env/dev
ディレクトリの下に backend.tf
と provider.tf
が作成されるのが見えるかと思います。
その後 plan
/ apply
とおなじみのコマンドを実行して環境に差分を適用しましょう。
この手順で terragrunt
を当てることができたかと思います。
参考の差分はこちらです。
最後に
terraform workspace
から terragrunt
に移行する手順を記載しました。
複数環境のTerraformコードをDRYに管理するのに、最初はmoduleの導入から始め次にworkspaceへ、という流れを踏んでいるところは一定数あるかと思いますが、過度にDRYに記述する弊害を感じてworkspaceから脱却したい、ただ、無駄に同じファイルを重複管理したくない、といった時に利用できるソリューションとして terragrunt
を試してみました。
Terraformのベストプラクティスに則った管理はまだまだ自分の中でも勉強中で、今回導入検証した terragrunt
は、環境ごとの柔軟性を担保しつつ、ある程度DRYにコード管理できるちょうどいいソリューションに感じました。
同じ悩みを感じている方になんらかの参考になれば幸いです。