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にコード管理できるちょうどいいソリューションに感じました。
同じ悩みを感じている方になんらかの参考になれば幸いです。