これは、なにをしたくて書いたもの?
前に、Terragruntを使ってTerraform Backendの定義をまとめてみました。
TerragruntをUbuntu Linux 20.04 LTSにインストールして、Terraform Backendの定義をまとめてみる - CLOVER🍀
今度は、Providerの定義をまとめてみたいと思います。
TerragruntでProviderの定義をまとめる
Providerの定義をまとめるとは、どういうことでしょう。
Quick startやTerragruntの機能に関するドキュメントを見てみます。
Quick start / Keep your provider configuration DRY
Keep your Terraform code DRY / DRY common Terraform code with Terragrunt generate blocks
Terraform Backendの時とは異なり、Providerの定義にはVariableを使うことができます。とはいえ、それでも同じような定義が
各Terraformのルートモジュールに散らばることになります。
再利用可能なTerraformモジュールを使う場合であっても、Providerの定義は通常モジュール定義には含まれないため、
Providerはルートモジュール側で定義することになります。
Terragruntのgenerateブロックを使用すると、このようなProviderの定義をまとめてコードをDRYに保つことができる、
ということのようです。
では、試していきましょう。
環境
今回の環境は、こちらです。
$ terraform version Terraform v0.14.6 $ terragrunt -v terragrunt version v0.28.6
Terraform Providerは、MySQL用のものを使用します。
Provider: MySQL - Terraform by HashiCorp
使用するMySQLは8.0.23とし、172.17.0.2で動作しているものとします。
また、最後でTerraform BackendとしてConsulも利用します。
お題
MySQL Providerを使い、以下のリソースを定義する2つのTerraformルートモジュールを作成します。
- データベース
- ユーザーおよび権限
最初はTerraformのみで実現し、その後にTerragruntでProvider定義をまとめてみましょう。
Terraformのみで構成してみる
では、Terraformでリソース定義を行っていきます。databaseとusersという2つのディレクトリを作成し、以下のような
ディレクトリ構成にしました。
$ tree database users database └── main.tf users └── main.tf 0 directories, 2 files
データベース側。
$ cd database
定義は、こんな感じです。
main.tf
terraform {
required_version = "0.14.6"
required_providers {
mysql = {
source = "terraform-providers/mysql"
version = "1.9.0"
}
}
}
provider "mysql" {
endpoint = "172.17.0.2:3306"
username = "root"
password = "password"
}
resource "mysql_database" "app" {
name = "my_database"
default_character_set = "utf8mb4"
default_collation = "utf8mb4_ja_0900_as_cs_ks"
}
output "database_name" {
value = mysql_database.app.name
}
実行できることだけ確認します。initしてapply。
$ terraform init $ terraform apply
リソースができました。
mysql_database.app: Creating... mysql_database.app: Creation complete after 0s [id=my_database] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: database_name = "my_database"
ユーザーや権限側も。
$ cd ../users
リソース定義。
main.tf
terraform {
required_version = "0.14.6"
required_providers {
mysql = {
source = "terraform-providers/mysql"
version = "1.9.0"
}
}
}
provider "mysql" {
endpoint = "172.17.0.2:3306"
username = "root"
password = "password"
}
locals {
database_name = "my_database"
}
resource "mysql_user" "admin_user" {
user = "admin"
plaintext_password = "password"
host = "%"
}
resource "mysql_grant" "admin_user" {
user = mysql_user.admin_user.user
host = mysql_user.admin_user.host
database = local.database_name
privileges = ["ALL"]
}
resource "mysql_user" "application_user" {
user = "appuser"
plaintext_password = "password"
host = "%"
}
resource "mysql_grant" "application_user" {
user = mysql_user.application_user.user
host = mysql_user.application_user.host
database = local.database_name
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
}
output "admin_user_name" {
value = mysql_user.admin_user.user
}
output "admin_user_privileges" {
value = mysql_grant.admin_user.privileges
}
output "app_user_name" {
value = mysql_user.application_user.user
}
output "app_user_privileges" {
value = mysql_grant.application_user.privileges
}
こちらも確認。
$ terraform init $ terraform apply
リソースができました。
mysql_user.admin_user: Creating... mysql_user.application_user: Creating... mysql_user.application_user: Creation complete after 0s [id=appuser@%] mysql_grant.application_user: Creating... mysql_user.admin_user: Creation complete after 0s [id=admin@%] mysql_grant.admin_user: Creating... mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`my_database`] mysql_grant.admin_user: Creation complete after 0s [id=admin@%:`my_database`] Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: admin_user_name = "admin" admin_user_privileges = toset([ "ALL", ]) app_user_name = "appuser" app_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ])
確認できたので、1度リソースを破棄します。
$ terraform destroy $ cd ../database $ terraform destroy $ cd ..
Terragruntを使って、Provider定義をまとめる
先ほどのTerraformの定義ファイルを見返すと、2つのルートモジュールでProviderの定義が重複していることがわかります。
provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" }
これをまとめてしまおうというのが、今回のお題です。
トップレベルのディレクトリ(databaseおよびusersの親ディレクトリ)で、以下のようなファイルを作成します。
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 }
generateブロックに関するリファレンスは、こちらです。
Configuration Blocks and Attributes / generate
generateの隣に書いているproviderというのは、このブロックの名前です。terragrunt.hclには複数のgenerateブロックを
含めることができるため、区別できるように指定します。
pathは、生成するファイル名。
if_existsは、ファイルが存在した場合の動作を指定します。overwrite_terragruntは、Terragruntにより生成されたファイルの場合は
上書きする、という意味になります。
contentsは、ファイルに出力する内容です。
ところで、required_providersも重複しているのでは?という話もあるのですが、issueになっています。
読んでみると、Providerと同じ方法でまとめられる雰囲気もあるようですが…。
Possibility to generate required_providers ? · Issue #1374 · gruntwork-io/terragrunt · GitHub
データベース定義側に移動。
$ cd database
terragrunt.hclというファイルを作成します。中身は、親ディレクトリを参照する以下の定義のみでOKです。
terragrunt.hcl
include {
path = find_in_parent_folders()
}
main.tfからは、Providerの定義を削除してしまいます。
main.tf
terraform {
required_version = "0.14.6"
required_providers {
mysql = {
source = "terraform-providers/mysql"
version = "1.9.0"
}
}
}
# provider "mysql" {
# endpoint = "172.17.0.2:3306"
# username = "root"
# password = "password"
# }
resource "mysql_database" "app" {
name = "my_database"
default_character_set = "utf8mb4"
default_collation = "utf8mb4_ja_0900_as_cs_ks"
}
output "database_name" {
value = mysql_database.app.name
}
コマンドをterraformからterragruntに切り替え、とりあえずplanを実行してみましょう。
$ terragrunt plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# mysql_database.app will be created
+ resource "mysql_database" "app" {
+ default_character_set = "utf8mb4"
+ default_collation = "utf8mb4_ja_0900_as_cs_ks"
+ id = (known after apply)
+ name = "my_database"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ database_name = "my_database"
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
すると、カレントディレクトリにprovider.tfというファイルが生成されます。
provider.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" }
親ディレクトリに作成した、terragrunt.hclのcontentsの内容が反映されていますね。
applyします。
$ terragrunt apply
動きました。
mysql_database.app: Creating... mysql_database.app: Creation complete after 1s [id=my_database] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: database_name = "my_database"
ユーザーおよび権限側も。
$ cd ../users
terragrunt.hclを作成する必要がありますが、内容はデータベース側と同じでいいのでコピーします。
$ cp ../database/terragrunt.hcl ./.
main.tfから、Provider定義を削除。
main.tf
terraform {
required_version = "0.14.6"
required_providers {
mysql = {
source = "terraform-providers/mysql"
version = "1.9.0"
}
}
}
# provider "mysql" {
# endpoint = "172.17.0.2:3306"
# username = "root"
# password = "password"
# }
locals {
database_name = "my_database"
}
resource "mysql_user" "admin_user" {
user = "admin"
plaintext_password = "password"
host = "%"
}
resource "mysql_grant" "admin_user" {
user = mysql_user.admin_user.user
host = mysql_user.admin_user.host
database = local.database_name
privileges = ["ALL"]
}
resource "mysql_user" "application_user" {
user = "appuser"
plaintext_password = "password"
host = "%"
}
resource "mysql_grant" "application_user" {
user = mysql_user.application_user.user
host = mysql_user.application_user.host
database = local.database_name
privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"]
}
output "admin_user_name" {
value = mysql_user.admin_user.user
}
output "admin_user_privileges" {
value = mysql_grant.admin_user.privileges
}
output "app_user_name" {
value = mysql_user.application_user.user
}
output "app_user_privileges" {
value = mysql_grant.application_user.privileges
}
今度は、いきなりapplyします。
$ terragrunt apply
OKです。
mysql_user.admin_user: Creating... mysql_user.application_user: Creating... mysql_user.application_user: Creation complete after 0s [id=appuser@%] mysql_grant.application_user: Creating... mysql_user.admin_user: Creation complete after 0s [id=admin@%] mysql_grant.admin_user: Creating... mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`my_database`] mysql_grant.admin_user: Creation complete after 0s [id=admin@%:`my_database`] Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: admin_user_name = "admin" admin_user_privileges = toset([ "ALL", ]) app_user_name = "appuser" app_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ])
Terragruntによって生成されたprovider.tfは、こんな感じです。
provider.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" }
Terragruntによって、Provider定義をまとめ、terragruntコマンドの実行とともにTerraformのProvider定義ファイルを生成できることを
確認しました。
次は、destroyして上位ディレクトリへ。
※terraform.tfstateを削除しているのは、この後のためです
$ terragrunt destroy $ rm terraform.tfstate* $ cd ../database $ terragrunt destroy $ rm terraform.tfstate* $ cd ..
ここまでで、こういうディレクトリ、ファイル構成になりました。
$ tree
.
├── database
│ ├── main.tf
│ ├── provider.tf
│ └── terragrunt.hcl
├── terragrunt.hcl
└── users
├── main.tf
├── provider.tf
└── terragrunt.hcl
Terraform BackendをConsulにしてみる
最後は、Terraform BackendとしてConsulを使い、Terraform BackendとProviderの定義の両方をまとめてみましょう。
Consulはバージョン1.9.3が172.17.0.2で動作しているものとし、MySQLはバージョン8.0.23が172.17.0.3で動作しているものとします。
※MySQLのIPアドレスは、先ほどから変更しています
トップディレクトリにあるterragrunt.hclに、remote_stateを加えます。
terragrunt.hcl
remote_state {
backend = "consul"
generate = {
path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
config = {
address = "172.17.0.2:8500"
scheme = "http"
path = "terraform/state/mysql/${path_relative_to_include()}"
lock = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "mysql" {
endpoint = "172.17.0.3:3306"
username = "root"
password = "password"
}
EOF
}
これで、Terraform Backendの定義とProviderの定義の両方がまとめられていることを確認します。
データベース側へ移動。
$ cd database
Terraform Backendが変わったので、今回はinitが必要になります(.terraformディレクトリも消しておけば良かったですね)。
$ terragrunt init
生成されたbackend.tf。
backend.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa
terraform {
backend "consul" {
address = "172.17.0.2:8500"
lock = true
path = "terraform/state/mysql/database"
scheme = "http"
}
}
provider.tfは、IPアドレスの変更が反映されています(172.17.0.2から172.17.0.3になりました)。
provider.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa provider "mysql" { endpoint = "172.17.0.3:3306" username = "root" password = "password" }
applyします。
$ terragrunt apply
リソースができました。
mysql_database.app: Creating... mysql_database.app: Creation complete after 0s [id=my_database] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: database_name = "my_database"
StateがConsulに保存されたことも確認しておきます。

次に、ユーザーや権限側の方も変更を適用しましょう。ディレクトリを移動して、initまでしておきます。
$ cd ../users $ terragrunt init
生成されたbackend.tf。
backend.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa
terraform {
backend "consul" {
address = "172.17.0.2:8500"
lock = true
path = "terraform/state/mysql/users"
scheme = "http"
}
}
provider.tfにも、変更が反映されます。
provider.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa provider "mysql" { endpoint = "172.17.0.3:3306" username = "root" password = "password" }
applyします。
$ terragrunt apply
こちらもOKです。
mysql_user.application_user: Creating... mysql_user.admin_user: Creating... mysql_user.admin_user: Creation complete after 1s [id=admin@%] mysql_grant.admin_user: Creating... mysql_user.application_user: Creation complete after 1s [id=appuser@%] mysql_grant.application_user: Creating... mysql_grant.admin_user: Creation complete after 0s [id=admin@%:`my_database`] mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`my_database`] Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: admin_user_name = "admin" admin_user_privileges = toset([ "ALL", ]) app_user_name = "appuser" app_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ])
Consol側にも、Stateが保存されたことを確認しておきます。

各ディレクトリのmain.tfはなにも触っていませんが、これでConsul BackendでStateが管理できるようになりました。
最後に、トップレベルのディレクトリからツリーを見ておきます。
$ cd ..
こうなりました、と。
$ tree
.
├── database
│ ├── backend.tf
│ ├── main.tf
│ ├── provider.tf
│ └── terragrunt.hcl
├── terragrunt.hcl
└── users
├── backend.tf
├── main.tf
├── provider.tf
└── terragrunt.hcl
まとめ
Terragruntを使って、Providerの定義をまとめてみました。
Terraform Backendをまとめた時と似たようなファイル生成の仕組みを使っているのですが、terragrunt.hclの書き方が違うので
扱いも異なるんでしょうね。
最終的にはTerraformの定義ファイルとして生成されるので、ファイルさえできてしまえばあとはTerraformのみでも使えたり、
Terragruntから離脱することも簡単そうですね。