これは、なにをしたくて書いたもの?
Terragruntを学ぶシリーズ。
今回は、Terragruntを使って環境ごとに作るTerraformの構成ファイルを減らしてみます。
Terragruntで、環境ごとの構成ファイルを減らす
Terraformを使って、複数の環境を構築、管理するのにはいくつかの方法があります。
Terraform自身が提供するのは、Workspaceですね。
State: Workspaces - Terraform by HashiCorp
WorkspaceはStateを切り替えることができるもので、こちらとVariablesなどで環境差異を表現することになります。
他には、割り切って環境ごとにTerraform構成ファイルを独立して管理する方法もあります。この方法だと、ファイルのコピーが増えることに
なりますね。
ちょうど、今回使うTerragruntのドキュメントにも例があります。
Quick start / Promote immutable, versioned Terraform modules across environments
こういう感じのものです。環境ごとにファイルが分離されていますし、Stateもそれぞれ独立して管理されます。
├── prod
│ ├── app
│ │ ├── main.tf
│ │ └── outputs.tf
│ ├── mysql
│ │ ├── main.tf
│ │ └── outputs.tf
│ └── vpc
│ ├── main.tf
│ └── outputs.tf
├── qa
│ ├── app
│ │ ├── main.tf
│ │ └── outputs.tf
│ ├── mysql
│ │ ├── main.tf
│ │ └── outputs.tf
│ └── vpc
│ ├── main.tf
│ └── outputs.tf
└── stage
├── app
│ ├── main.tf
│ └── outputs.tf
├── mysql
│ ├── main.tf
│ └── outputs.tf
└── vpc
├── main.tf
└── outputs.tf
Terragruntの場合では、環境ごとにファイルを独立させる考えを取りつつも、リソース定義はモジュール化して参照する方法を提示しています。
さらに、モジュールに対するVariableの設定をTerraformの構成ファイルなしで実現できます。
そのあたりが書かれているのが、こちらのドキュメントです。
Quick start / Promote immutable, versioned Terraform modules across environments
Terragruntを使うと、リソース定義をモジュールにまとめた上で、こういった形で.tfファイルなしで表現できます。
├── prod
│ ├── app
│ │ └── terragrunt.hcl
│ ├── mysql
│ │ └── terragrunt.hcl
│ └── vpc
│ └── terragrunt.hcl
├── qa
│ ├── app
│ │ └── terragrunt.hcl
│ ├── mysql
│ │ └── terragrunt.hcl
│ └── vpc
│ └── terragrunt.hcl
└── stage
├── app
│ └── terragrunt.hcl
├── mysql
│ └── terragrunt.hcl
└── vpc
└── terragrunt.hcl
今回は、こちらの機能を見ていこうと思います。
環境
今回の環境は、こちらです。
$ terraform version Terraform v0.14.7 $ terragrunt -v terragrunt version v0.28.7
Terraform Providerは、MySQL用のものを使用します。
Provider: MySQL - Terraform by HashiCorp
使用するMySQLは8.0.23とし、172.17.0.2および172.17.0.3で動作しているものとします。
また、今回はGitリポジトリが必要になります。Gitリポジトリのソフトウェアはあまり関係ありませんが、GitLab 13.9を使います。
GitLabは192.168.0.3で動作しているものとします。
お題
MySQL Providerを使った、以下の2つのモジュールを定義します。
- データベース
- ユーザーおよび権限
これらを環境別のディレクトリから参照し、それぞれ使用するモジュールは同じ、構築する情報(=Variable)は異なる状態を実現してみます。
環境はdevelopmentとproductionの2つとし、リソース構築先のMySQLはdevelopment用を172.17.0.2、production用を172.17.0.3とします。
また、今回はTerragruntを使わないパターンは作りません。長くなるので…。
では、始めていきましょう。
Terraformモジュールを作成する
今回は、Terraformモジュールがないと始まりません。
GitLab上にリポジトリを作成して(名前はmysql-terraform-moduleにしました)、こちらのモジュールを登録します。
git cloneして
$ git clone http://192.168.0.3/kazuhira/mysql-terraform-module.git $ cd mysql-terraform-module
モジュール用のディレクトリを作成。
$ mkdir -p modules/{database,users}
以下のような構成にします。
$ tree
.
└── modules
├── database
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
└── users
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
3 directories, 8 files
ファイルは、それぞれこんな感じです。
データベース用。
modules/database/main.tf
resource "mysql_database" "this" { name = var.database_name default_character_set = var.default_character_set default_collation = var.default_collation }
必須なのは、データベース名だけにしました。
modules/database/variables.tf
variable "database_name" { type = string } variable "default_character_set" { type = string default = "utf8mb4" } variable "default_collation" { type = string default = "utf8mb4_ja_0900_as_cs_ks" }
modules/database/outputs.tf
output "database_name" {
value = mysql_database.this.name
}
modules/database/versions.tf
terraform {
required_version = ">= 0.14.7"
required_providers {
mysql = {
source = "terraform-providers/mysql"
version = ">= 1.9.0"
}
}
}
ユーザーおよび権限用。
管理用のユーザーと、アプリケーションユーザーの2種類を作成します。
modules/users/main.tf
resource "mysql_user" "administrator" { user = var.administrator_username plaintext_password = var.administrator_password host = var.administrator_allow_host } resource "mysql_grant" "administrator" { user = mysql_user.administrator.user host = mysql_user.administrator.host database = var.database_name privileges = ["ALL"] } resource "mysql_user" "application_user" { user = var.application_user_username plaintext_password = var.application_user_password host = var.application_user_allow_host } resource "mysql_grant" "application_user" { user = mysql_user.application_user.user host = mysql_user.application_user.host database = var.database_name privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] }
必須のものは、データベース名と、各ユーザーのパスワードにしました。
modules/users/variables.tf
variable "database_name" { type = string } variable "administrator_username" { type = string default = "admin" } variable "administrator_password" { type = string } variable "administrator_allow_host" { type = string default = "%" } variable "application_user_username" { type = string default = "appuser" } variable "application_user_password" { type = string } variable "application_user_allow_host" { type = string default = "%" }
modules/users/outputs.tf
output "administrator_username" { value = mysql_user.administrator.user } output "administrator_password" { value = mysql_user.administrator.plaintext_password sensitive = true } output "administrator_allow_host" { value = mysql_user.administrator.host } output "application_user_username" { value = mysql_user.application_user.user } output "application_user_password" { value = mysql_user.application_user.plaintext_password sensitive = true } output "application_user_allow_host" { value = mysql_user.application_user.host }
modules/users/versions.tf
terraform {
required_version = ">= 0.14.7"
required_providers {
mysql = {
source = "terraform-providers/mysql"
version = ">= 1.9.0"
}
}
}
作成したモジュールを、Gitリポジトリに登録します。
$ git add modules $ git commit -m 'add, modules' $ git push origin master
タグもつけておきましょう。
$ git tag v0.0.1 $ git push origin v0.0.1
ここまでは、単にTerraformモジュールを作成しただけですね。
環境別にモジュールを利用し、かつTerraform構成ファイルは作成しない
ここから、Terragruntが出てきます。
まずは環境用のディレクトリと、各モジュールを使うためのディレクトリを作成しましょう。
$ mkdir -p environments/{development,production}
$ mkdir -p environments/development/{database,users}
$ mkdir -p environments/production/{database,users}
最終的に、できあがったディレクトリおよびファイルは、こんな感じです。いずれのディレクトリにもファイルはterragrunt.hclしか
存在しません。
$ tree environments
environments
├── development
│ ├── database
│ │ └── terragrunt.hcl
│ ├── terragrunt.hcl
│ └── users
│ └── terragrunt.hcl
└── production
├── database
│ └── terragrunt.hcl
├── terragrunt.hcl
└── users
└── terragrunt.hcl
6 directories, 6 files
developmentと名付けた方の環境から見ていきましょう。
$ cd environments/development
このディレクトリにあるterragrunt.hclファイルの中身は、こんな感じです。
terragrunt.hcl
generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <<EOF provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" } EOF }
Providerの定義をまとめる機能を使っています。データベース用、ユーザーおよび権限用のモジュールがありますが、接続先のMySQLサーバーは
どちらも同じなので。
Terragruntを使って、TerraformのProvider定義をまとめてみる - CLOVER🍀
データベース側に移動します。
$ cd database
こちらに配置しているterragrunt.hclの中身です。
terragrunt.hcl
include {
path = find_in_parent_folders()
}
terraform {
source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/database?ref=v0.0.1"
}
inputs = {
database_name = "development_database"
}
Providerの定義を参照するために、上位ディレクトリにあるterragrunt.hclファイルを参照するようにしています。
include {
path = find_in_parent_folders()
}
terraformブロックのsource属性は、このディレクトリで使うモジュールを指定します。
terraform {
source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/database?ref=v0.0.1"
}
Configuration Blocks and Attributes / terraform
記述方法自体は、通常のTerraformと同じですね。//以下で、Gitリポジトリ内のモジュールを指定しています。タグはrefで指定します。
Module Sources - Terraform by HashiCorp
モジュールに与えるVariableは、inputsで指定します。
inputs = {
database_name = "development_database"
}
Configuration Blocks and Attributes / inputs
ここまでで、このディレクトリについては準備ができたので、applyしてみましょう。
$ terragrunt apply
リソースができました。
mysql_database.this: Creating... mysql_database.this: Creation complete after 0s [id=development_database] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: database_name = "development_database"
ちなみに、この時のディレクトリ内のファイルやディレクトリは、このようになっています。
$ ll 合計 20 drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 ./ drwxrwxr-x 4 xxxxx xxxxx 4096 2月 23 22:05 ../ -rw-r--r-- 1 xxxxx xxxxx 1194 2月 24 00:05 .terraform.lock.hcl drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 .terragrunt-cache/ -rw-rw-r-- 1 xxxxx xxxxx 218 2月 23 23:15 terragrunt.hcl
続いて、ユーザーおよび権限側へ。
$ cd ../users
terragrunt.hclは、こちら。使い方自体は、データベース側と同じです。
terragrunt.hcl
include {
path = find_in_parent_folders()
}
terraform {
source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/users?ref=v0.0.1"
}
inputs = {
database_name = "development_database"
administrator_password = "admin_password"
application_user_password = "appuser_password"
}
applyしておきましょう。
$ terragrunt apply
リソースができました。
mysql_user.administrator: Creating... mysql_user.application_user: Creating... mysql_user.application_user: Creation complete after 0s [id=appuser@%] mysql_grant.application_user: Creating... mysql_user.administrator: Creation complete after 0s [id=admin@%] mysql_grant.administrator: Creating... mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`development_database`] mysql_grant.administrator: Creation complete after 0s [id=admin@%:`development_database`] Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: administrator_allow_host = "%" administrator_password = <sensitive> administrator_username = "admin" application_user_allow_host = "%" application_user_password = <sensitive> application_user_username = "appuser"
production側の内容も見てみましょう。くどくなるので、applyの実行の様子は載せません。
$ cd ../../production
Providerの定義をしている、terragrunt.hcl。developmentの時とは、接続先が異なります。
terragrunt.hcl
generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <<EOF provider "mysql" { endpoint = "172.17.0.3:3306" username = "root" password = "password" } EOF }
データベース側。
database/terragrunt.hcl
include {
path = find_in_parent_folders()
}
terraform {
source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/database?ref=v0.0.1"
}
inputs = {
database_name = "production_database"
}
ユーザーおよび権限側。
users/terragrunt.hcl
include {
path = find_in_parent_folders()
}
terraform {
source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/users?ref=v0.0.1"
}
inputs = {
database_name = "production_database"
administrator_password = "rZRuLj0xx2Z1M"
application_user_password = "gSudLAJt4icZL"
}
これで、同じモジュールを使って、リソースを構築するための設定が異なるという状態を、Terraform構成ファイルなしで実現できました。
Stateやprovider.tfは?
developmentのデータベース側の定義に戻ってみます。
$ cd ../../development/database
先ほどTerragruntを使ってapplyした時にディレクトリを、provider.tfがありませんでした。今回はTerraform Backendを特に設定して
いないので、Stateがファイルで管理されているはずなのですが、それもありません。
$ ll 合計 20 drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 ./ drwxrwxr-x 4 xxxxx xxxxx 4096 2月 23 22:05 ../ -rw-r--r-- 1 xxxxx xxxxx 1194 2月 24 00:05 .terraform.lock.hcl drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 .terragrunt-cache/ -rw-rw-r-- 1 xxxxx xxxxx 218 2月 23 23:15 terragrunt.hcl
provider.tfについては、親ディレクトリにあるterragrunt.tfの内容からして、こちらのディレクトリに作成されているのでは?と思うのですが。
実は、こんなディレクトリにできていたりします。
.terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/provider.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" }
こういう状態です。
$ tree .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4
.terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4
└── modules
├── database
│ ├── main.tf
│ ├── outputs.tf
│ ├── provider.tf
│ ├── terraform.tfstate
│ ├── terragrunt.hcl
│ ├── variables.tf
│ └── versions.tf
└── users
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
3 directories, 11 files
Stateまで、こちらに入っているんですね。
findすると、こんな感じになっています。
※ .gitディレクトリ除く
.terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/.terragrunt-source-version .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/outputs.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/versions.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/main.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/variables.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/outputs.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/provider.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/terraform.tfstate .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/versions.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/main.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/terragrunt.hcl .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/.terraform.lock.hcl .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/.terragrunt-module-manifest .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/variables.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/.terraform/providers/registry.terraform.io/terraform-providers/mysql/1.9.0/linux_amd64/terraform-provider-mysql_v1.9.0_x4
実は、この構成だとterragrunt applyなどを実行する時に.terragrunt-cache配下の一時ディレクトリに移動しているようなのです。
Keep your Terraform code DRY / Important gotcha: working with relative file paths
このため、-var-fileやterragrunt.hcl内で書くパスは、絶対パスにしておかないとうまくいかないようです。
モジュールが更新された場合
こちらの内容です。
Keep your Terraform code DRY / Important gotcha: Terragrunt caching
sourceの指定をリモートにした場合、Terragruntはモジュールのダウンロードを1度しか行いません。
terraform init -upgrade、terragrunt init -upgradeなどしても、意味がありません。
モジュールを再度ダウンロードするには、--terragrunt-source-updateオプションを使用します。
$ terragrunt apply --terragrunt-source-update
ローカルのモジュールを使う場合
--terragrunt-sourceオプションを使用することで、一時的にsourceで指定しているモジュールの参照先を変更できるようです。
Keep your Terraform code DRY / Working locally
これで、ローカルのモジュールを利用して作業ができます、と。
まとめ
Terragruntを使った、環境ごとのリソース定義方法(?)を見てみました。
どうなんでしょうね、確かにすごくすっきりした構成になりますし、モジュールをリポジトリ管理して参照するのも良いと思うのですが。
terragrunt.hclを使って変更できる範囲が、ちょっと少ないような…。
環境差異がVariablesで吸収できる範囲なら、有効なのでしょうか?
Workspaceを使わない、この考え方自体は押さえておいた方がよいかなと思います。