これは、なにをしたくて書いたもの?
前に、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から離脱することも簡単そうですね。