これは、なにをしたくて書いたもの?
Terragruntを学ぶシリーズ。今回でひと区切りの予定です。
最後は、Terragruntを使って複数のTerraformモジュールの操作を1回のコマンド実行で行ってみます。
Terragruntで、Terraformモジュールの操作を1度に行う
Terraformで環境を作っていく際に、適用する(ルート)モジュールが複数あると、その数だけterraform apply
を実行してリソースを
作成したり、あるいはterraform destroy
して環境を破棄したりすることになります。
Terragruntのドキュメントの記載例ですが、以下の記載例だとterraform
コマンドを少なくとも5回は実行することになります。
また、各モジュールの間には依存関係もあるでしょう。
root ├── backend-app │ └── main.tf ├── frontend-app │ └── main.tf ├── mysql │ └── main.tf ├── redis │ └── main.tf └── vpc └── main.tf
Terragruntは、このような状況下でのTerraformの実行を簡単にしてくれるようです。
Execute Terraform commands on multiple modules at once
今回は、こちらの機能を試してみたいと思います。
環境
今回の環境は、こちらです。
$ 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で動作しているものとします。
最後にオマケで、Consulも追加します。
お題
MySQL Providerを使った、以下の3つのルートモジュールを定義します。
- データベース
- ユーザー
- 権限
これらの3つのモジュールに、依存関係を定義しつつTerragruntを使って一括で操作してみましょう。
Terragruntなしで構成する
最初は、TerragruntなしでシンプルにTerraformのみで構築してみたいと思います。
各モジュール用のディレクトリを作成。
$ mkdir database users roles
中身は、こんな感じになりました。
$ tree . ├── database │ ├── main.tf │ └── terraform.tfvars ├── grants │ ├── main.tf │ └── terraform.tfvars └── users ├── main.tf └── terraform.tfvars 3 directories, 6 files
データベース用モジュール。
$ cd database
main.tf
terraform { required_version = "0.14.7" required_providers { mysql = { source = "terraform-providers/mysql" version = "1.9.0" } } } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" } variable "database_name" { type = string } resource "mysql_database" "app" { name = var.database_name default_character_set = "utf8mb4" default_collation = "utf8mb4_ja_0900_as_cs_ks" } output "database_name" { value = mysql_database.app.name }
変数は、あらかじめ用意しておきます。
terraform.tfvars
database_name = "my_database"
ユーザー用モジュール。
$ cd ../users
main.tf
terraform { required_version = "0.14.7" required_providers { mysql = { source = "terraform-providers/mysql" version = "1.9.0" } } } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" } variable "administrator_username" { type = string } variable "administrator_password" { type = string } variable "administrator_allow_host" { type = string } variable "application_user_username" { type = string } variable "application_user_password" { type = string } variable "application_user_allow_host" { type = string } resource "mysql_user" "administrator_user" { user = var.administrator_username plaintext_password = var.administrator_password host = var.administrator_allow_host } resource "mysql_user" "application_user" { user = var.application_user_username plaintext_password = var.application_user_password host = var.application_user_allow_host } output "administrator_username" { value = mysql_user.administrator_user.user } output "administrator_password" { value = mysql_user.administrator_user.plaintext_password sensitive = true } output "administrator_allow_host" { value = mysql_user.administrator_user.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 }
terraform.tfvars
administrator_username = "admin" administrator_password = "admin_password" administrator_allow_host = "%" application_user_username = "appuser" application_user_password = "appuser_password" application_user_allow_host = "%"
権限用モジュール。
$ cd ../grants
main.tf
terraform { required_version = "0.14.7" required_providers { mysql = { source = "terraform-providers/mysql" version = "1.9.0" } } } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" } variable "database_name" { type = string } variable "administrator_username" { type = string } variable "administrator_allow_host" { type = string } variable "application_user_username" { type = string } variable "application_user_allow_host" { type = string } resource "mysql_grant" "administrator_user" { user = var.administrator_username host = var.administrator_allow_host database = var.database_name privileges = ["ALL"] } resource "mysql_grant" "application_user" { user = var.application_user_username host = var.application_user_allow_host database = var.database_name privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] } output "administrator_username" { value = mysql_grant.administrator_user.user } output "administrator_allow_host" { value = mysql_grant.administrator_user.host } output "administrator_privileges" { value = mysql_grant.administrator_user.privileges } output "application_user_username" { value = mysql_grant.application_user.user } output "application_user_allow_host" { value = mysql_grant.application_user.host } output "application_user_privileges" { value = mysql_grant.application_user.privileges }
terraform.tfvars
database_name = "my_database" administrator_username = "admin" administrator_allow_host = "%" application_user_username = "appuser" application_user_allow_host = "%"
いずれも、リソース定義と必要なVariableは用意してあるので、単純にinit
してapply
すればリソースが構築されます。
$ cd ../database $ terraform init $ terraform apply $ cd ../users $ terraform init $ terraform apply $ cd ../grants $ 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" mysql_user.administrator_user: Creating... mysql_user.application_user: Creating... mysql_user.administrator_user: Creation complete after 0s [id=admin@%] mysql_user.application_user: Creation complete after 0s [id=appuser@%] Apply complete! Resources: 2 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" mysql_grant.administrator_user: Creating... mysql_grant.application_user: Creating... mysql_grant.administrator_user: Creation complete after 0s [id=admin@%:`my_database`] mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`my_database`] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: administrator_allow_host = "%" administrator_privileges = toset([ "ALL", ]) administrator_username = "admin" application_user_allow_host = "%" application_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ]) application_user_username = "appuser"
動作確認したので、いったんリソースを破棄しましょう。
$ cd ../grants $ terraform destroy $ cd ../users $ terraform destroy $ cd ../database $ terraform destroy
この時、apply
の逆順で実行しているわけですが、データベースとユーザーは独立しているのですが、権限の方は先に他のモジュールが
適用されていることが実は前提になっていたりします。
それに、こう何回もapply
したりdestroy
したりするのは面倒に思うかもしれません。このあたりをなんとかしようとするのが
Terragruntの機能のひとつです。
Terragruntを使って一括実行する
では、Terragruntを使っていきましょう。
先ほどのTerraformのみで実行した構成をまるっとコピーして、Terragrunt用のファイルを加えた以下の構成を作りました。
$ tree . ├── database │ ├── main.tf │ └── terragrunt.hcl ├── grants │ ├── main.tf │ └── terragrunt.hcl ├── terragrunt.hcl └── users ├── main.tf └── terragrunt.hcl 3 directories, 7 files
トップレベルのディレクトリにあるterragrunt.hcl
では、Provider定義をまとめておきました。
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の定義を削除できます。代表で、データベース用のモジュールだけ載せておきましょう。
database/main.tf
terraform { required_version = "0.14.7" required_providers { mysql = { source = "terraform-providers/mysql" version = "1.9.0" } } } # provider "mysql" { # endpoint = "172.17.0.2:3306" # username = "root" # password = "password" # } variable "database_name" { type = string } resource "mysql_database" "app" { name = var.database_name default_character_set = "utf8mb4" default_collation = "utf8mb4_ja_0900_as_cs_ks" } output "database_name" { value = mysql_database.app.name }
各ディレクトリ内のterragrunt.hcl
には、上位ディレクトリにあるterragrunt.hcl
ファイルへの参照と、Variablesをまとめました。
なので、いずれのディレクトリからもterraform.tfvars
ファイルは削除しています。
database/terragrunt.hcl
include {
path = find_in_parent_folders()
}
inputs = {
database_name = "my_database"
}
ユーザー用モジュール。
users/terragrunt.hcl
include { path = find_in_parent_folders() } inputs = { administrator_username = "admin" administrator_password = "admin_password" administrator_allow_host = "%" application_user_username = "appuser" application_user_password = "appuser_password" application_user_allow_host = "%" }
権限用モジュール。
grants/terragrunt.hcl
include { path = find_in_parent_folders() } inputs = { database_name = "my_database" administrator_username = "admin" administrator_allow_host = "%" application_user_username = "appuser" application_user_allow_host = "%" }
ここで、トップディレクトリにいる状態で
$ ll 合計 24 drwxrwxr-x 5 xxxxx xxxxx 4096 2月 27 15:12 ./ drwxrwxr-x 5 xxxxx xxxxx 4096 2月 27 15:05 ../ drwxrwxr-x 3 xxxxx xxxxx 4096 2月 27 15:16 database/ drwxrwxr-x 3 xxxxx xxxxx 4096 2月 27 15:16 grants/ -rw-rw-r-- 1 xxxxx xxxxx 209 2月 27 15:15 terragrunt.hcl drwxrwxr-x 3 xxxxx xxxxx 4096 2月 27 15:16 users/
run-all
を使うことで、コマンドを一気に実行できます。たとえばplan
。
$ terragrunt run-all plan
こんな感じで、複数のモジュールに対するplan
が一気に実行されます。
INFO[0000] Stack at /path/to: => Module /path/to/database (excluded: false, dependencies: []) => Module /path/to/grants (excluded: false, dependencies: []) => Module /path/to/users (excluded: false, dependencies: []) 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_grant.administrator_user will be created + resource "mysql_grant" "administrator_user" { + database = "my_database" + grant = false + host = "%" + id = (known after apply) + privileges = [ + "ALL", ] + table = "*" + tls_option = "NONE" + user = "admin" } # mysql_grant.application_user will be created + resource "mysql_grant" "application_user" { + database = "my_database" + grant = false + host = "%" + id = (known after apply) + privileges = [ + "DELETE", + "INSERT", + "SELECT", + "UPDATE", ] + table = "*" + tls_option = "NONE" + user = "appuser" } Plan: 2 to add, 0 to change, 0 to destroy. Changes to Outputs: + administrator_allow_host = "%" + administrator_privileges = [ + "ALL", ] + administrator_username = "admin" + application_user_allow_host = "%" + application_user_privileges = [ + "DELETE", + "INSERT", + "SELECT", + "UPDATE", ] + application_user_username = "appuser" ------------------------------------------------------------------------ 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. 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. 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_user.administrator_user will be created + resource "mysql_user" "administrator_user" { + host = "%" + id = (known after apply) + plaintext_password = (sensitive value) + tls_option = "NONE" + user = "admin" } # mysql_user.application_user will be created + resource "mysql_user" "application_user" { + host = "%" + id = (known after apply) + plaintext_password = (sensitive value) + tls_option = "NONE" + user = "appuser" } Plan: 2 to add, 0 to change, 0 to destroy. Changes to Outputs: + administrator_allow_host = "%" + administrator_password = (sensitive value) + administrator_username = "admin" + application_user_allow_host = "%" + application_user_password = (sensitive value) + application_user_username = "appuser" ------------------------------------------------------------------------ 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. INFO[0003] Initializing the backend... Initializing provider plugins... - Finding terraform-providers/mysql versions matching "1.9.0"... - Installing terraform-providers/mysql v1.9.0... - Installed terraform-providers/mysql v1.9.0 (signed by HashiCorp) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. INFO[0003] Initializing the backend... Initializing provider plugins... - Finding terraform-providers/mysql versions matching "1.9.0"... - Installing terraform-providers/mysql v1.9.0... - Installed terraform-providers/mysql v1.9.0 (signed by HashiCorp) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. INFO[0003] Initializing the backend... Initializing provider plugins... - Finding terraform-providers/mysql versions matching "1.9.0"... - Installing terraform-providers/mysql v1.9.0... - Installed terraform-providers/mysql v1.9.0 (signed by HashiCorp) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary.
一緒にinit
も実行されています。
結果、provider.tf
も自動生成されました。
$ tree . ├── database │ ├── main.tf │ ├── provider.tf │ └── terragrunt.hcl ├── grants │ ├── main.tf │ ├── provider.tf │ └── terragrunt.hcl ├── terragrunt.hcl └── users ├── main.tf ├── provider.tf └── terragrunt.hcl 3 directories, 10 files
database/provider.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" }
run-all apply
してみましょう。
$ terragrunt run-all apply
うまくリソースが構築できたようです。
INFO[0000] Stack at /path/to: => Module /path/to/database (excluded: false, dependencies: []) => Module /path/to/grants (excluded: false, dependencies: []) => Module /path/to/users (excluded: false, dependencies: []) Are you sure you want to run 'terragrunt apply' in each folder of the stack described above? (y/n) y mysql_user.administrator_user: Creating... mysql_user.application_user: Creating... mysql_grant.administrator_user: Creating... mysql_grant.application_user: Creating... mysql_user.application_user: Creation complete after 0s [id=appuser@%] mysql_database.app: Creating... mysql_user.administrator_user: Creation complete after 0s [id=admin@%] Apply complete! Resources: 2 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" mysql_database.app: Creation complete after 0s [id=my_database] mysql_grant.administrator_user: Creation complete after 0s [id=admin@%:`my_database`] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: database_name = "my_database" mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`my_database`] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: administrator_allow_host = "%" administrator_privileges = toset([ "ALL", ]) administrator_username = "admin" application_user_allow_host = "%" application_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ]) application_user_username = "appuser"
今度は、run-all destroy
で一気に破棄。
$ terragrunt run-all destroy
すると、エラーになりました。権限モジュールがうまくdestroy
できないようです。
INFO[0000] Stack at /path/to: => Module /path/to/database (excluded: false, dependencies: []) => Module /path/to/grants (excluded: false, dependencies: []) => Module /path/to/users (excluded: false, dependencies: []) WARNING: Are you sure you want to run `terragrunt destroy` in each folder of the stack described above? There is no undo! (y/n) y mysql_database.app: Destroying... [id=my_database] mysql_user.application_user: Destroying... [id=appuser@%] mysql_user.administrator_user: Destroying... [id=admin@%] mysql_grant.administrator_user: Destroying... [id=admin@%:`my_database`] mysql_grant.application_user: Destroying... [id=appuser@%:`my_database`] mysql_database.app: Destruction complete after 0s mysql_user.application_user: Destruction complete after 0s mysql_user.administrator_user: Destruction complete after 0s Destroy complete! Resources: 1 destroyed. Error: error revoking GRANT (REVOKE GRANT OPTION ON `my_database`.* FROM 'appuser'@'%'): Error 1141: There is no such grant defined for user 'appuser' on host '%' Error: error revoking GRANT (REVOKE GRANT OPTION ON `my_database`.* FROM 'admin'@'%'): Error 1141: There is no such grant defined for user 'admin' on host '%' ERRO[0002] Module /path/to/grants has finished with an error: Hit multiple errors: Hit multiple errors: exit status 1 prefix=[/path/to/grants] Destroy complete! Resources: 2 destroyed. ERRO[0002] Encountered the following errors: Hit multiple errors: Hit multiple errors: exit status 1
これは、破棄するリソースの順番に依存関係があるからですね。現時点だと、Terragruntはその依存関係を認識していないのです。
仕方ないので、1度apply
しなおして
$ terragrunt run-all apply
各モジュールを順を追って個別にdestroy
します。
$ cd grants $ terragrunt destroy $ cd ../users $ terragrunt destroy $ cd ../database $ terragrunt destroy
トップディレクトリへ戻ります。
$ cd ..
モジュール間に依存関係を作る+Outputを引き継ぐ
先ほどdestroy
が失敗したのは、Terragruntがモジュール間の依存関係を認識しておらず、他のモジュールが必要とするリソースを
先に破棄してしまったことが原因でした。
よって、Terragruntに依存関係を教える必要があります。
Execute Terraform commands on multiple modules at once / Dependencies between modules
現在のモジュール間の依存関係はないので、graph-dependencies
で見るとそれぞれが独立していることがわかります。
$ terragrunt graph-dependencies digraph { "database" ; "grants" ; "users" ; }
図にした場合。
$ terragrunt graph-dependencies | dot -Tsvg > graph.svg
ドキュメントを参考に、権限用モジュールに対して、データベースおよびユーザー用のモジュールへの依存関係を単純に
定義してみましょう。dependencies
を使い、paths
で依存するモジュールを指定します。
grants/terragrunt.hcl
include { path = find_in_parent_folders() } dependencies { paths = ["../database", "../users"] } inputs = { database_name = "my_database" administrator_username = "admin" administrator_allow_host = "%" application_user_username = "appuser" application_user_allow_host = "%" }
dependencies
では、依存するモジュールを複数定義できます。
Configuration Blocks and Attributes / dependencies
依存グラフは、このように変化しました。
$ terragrunt graph-dependencies digraph { "database" ; "grants" ; "grants" -> "database"; "grants" -> "users"; "users" ; }
ところで、権限用モジュールのVariableはデータベースおよびユーザー用のモジュールのOutputの値をそのまま使うことができます。
これをうまく利用する方法も、Terragrantは備えています。
Execute Terraform commands on multiple modules at once / Passing outputs between modules
dependencies
をdependency
に変更し、依存するモジュールに対して名前を与えます。モジュールへのパスはconfig_path
で
指定するように変更します。
Configuration Blocks and Attributes / dependency
grants/terragrunt.hcl
include { path = find_in_parent_folders() } dependency "database" { config_path = "../database" } dependency "users" { config_path = "../users" } inputs = { database_name = dependency.database.outputs.database_name administrator_username = dependency.users.outputs.administrator_username administrator_allow_host = dependency.users.outputs.administrator_allow_host application_user_username = dependency.users.outputs.application_user_username application_user_allow_host = dependency.users.outputs.application_user_allow_host }
dependencies
と異なり、dependency
では他のモジュールのOutputを利用することができます。
他のモジュールのOutputを利用するには、dependency.[dependencyの名前].outputs.[Output名]
で指定できます。
database_name = dependency.database.outputs.database_name
依存グラフを、もう1度見てみましょう。
$ terragrunt graph-dependencies digraph { "database" ; "grants" ; "grants" -> "database"; "grants" -> "users"; "users" ; }
図にもしてみます。こちらにも、依存関係が表現されていますね。Terragruntは、この依存関係の下から適用していくことになります。
$ terragrunt graph-dependencies | dot -Tsvg > graph.svg
再度、run-all plan
を実行。
$ terragrunt run-all plan
こちらにも、依存関係が反映されています。
INFO[0000] Stack at /path/to: => Module /path/to/database (excluded: false, dependencies: []) => Module /path/to/grants (excluded: false, dependencies: [/path/to/database, /path/to/users]) => Module /path/to/users (excluded: false, dependencies: [])
その他の内容。
An execution plan has been generated and is shown below. 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: Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # mysql_database.app will be created # mysql_user.administrator_user will be created + resource "mysql_database" "app" { + resource "mysql_user" "administrator_user" { + host = "%" + default_character_set = "utf8mb4" + id = (known after apply) + 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. + plaintext_password = (sensitive value) + tls_option = "NONE" + user = "admin" } # mysql_user.application_user will be created + resource "mysql_user" "application_user" { + host = "%" + id = (known after apply) + plaintext_password = (sensitive value) + tls_option = "NONE" + user = "appuser" } Plan: 2 to add, 0 to change, 0 to destroy. Changes to Outputs: + administrator_allow_host = "%" + administrator_password = (sensitive value) + administrator_username = "admin" + application_user_allow_host = "%" + application_user_password = (sensitive value) + application_user_username = "appuser" ------------------------------------------------------------------------ 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. WARN[0000] Could not parse remote_state block from target config /path/to/database/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants] WARN[0000] Could not parse remote_state block from target config /path/to/users/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants] ERRO[0000] Module /path/to/grants has finished with an error: /path/to/users/terragrunt.hcl is a dependency of /path/to/grants/terragrunt.hcl but detected no outputs. Either the target module has not been applied yet, or the module has no outputs. If this is expected, set the skip_outputs flag to true on the dependency block. prefix=[/path/to/grants] INFO[0000] INFO[0000] INFO[0000] ERRO[0000] Encountered the following errors: /path/to/users/terragrunt.hcl is a dependency of /path/to/grants/terragrunt.hcl but detected no outputs. Either the target module has not been applied yet, or the module has no outputs. If this is expected, set the skip_outputs flag to true on the dependency block. ERRO[0000] Unable to determine underlying exit code, so Terragrunt will exit with error code 1
ちょっと警告が出ていますね…。
WARN[0000] Could not parse remote_state block from target config /path/to/database/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants] WARN[0000] Could not parse remote_state block from target config /path/to/users/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants]
あと、エラーもあります。こちらは、plan
の時点では他のモジュールのOutputが決まっていない場合があるからです。
まだapply
もしていませんからね。
ERRO[0000] Module /path/to/grants has finished with an error: /path/to/users/terragrunt.hcl is a dependency of /path/to/grants/terragrunt.hcl but detected no outputs. Either the target module has not been applied yet, or the module has no outputs. If this is expected, set the skip_outputs flag to true on the dependency block. prefix=[/path/to/grants]
Terragruntは、この事象を回避するために、他のモジュールのOutputが利用できない時にモックを使う機能もあります。
今回はこちらは気にせず、そのままrun-all apply
してみましょう。
$ terragrunt run-all apply
こちらは成功します。output
もまとめて見てみましょう。
$ terragrunt run-all output INFO[0000] Stack at /path/to: => Module /path/to/database (excluded: false, dependencies: []) => Module /path/to/grants (excluded: false, dependencies: [/path/to/database, /path/to/users]) => Module /path/to/users (excluded: false, dependencies: []) administrator_allow_host = "%" administrator_password = <sensitive> administrator_username = "admin" application_user_allow_host = "%" application_user_password = <sensitive> application_user_username = "appuser" database_name = "my_database" WARN[0000] Could not parse remote_state block from target config /path/to/users/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants] WARN[0000] Could not parse remote_state block from target config /path/to/database/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants] administrator_allow_host = "%" administrator_privileges = toset([ "ALL", ]) administrator_username = "admin" application_user_allow_host = "%" application_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ]) application_user_username = "appuser"
こちらもplan
の時などと同じく警告が出ています。
よくよく見てみると、「Remote Stateが使えないからOutputにフォールバックしたよ」と言っていますね。
WARN[0000] Could not parse remote_state block from target config /path/to/users/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants] WARN[0000] Could not parse remote_state block from target config /path/to/database/terragrunt.hcl prefix=[/path/to/grants] WARN[0000] Falling back to terragrunt output. prefix=[/path/to/grants]
これは、Terragruntの依存関係の定義を使ってOutputを利用しようとすると、利用できるならRemote Stateを使い、そうでない場合は
Outputを利用する、ということを言っているようです。
とりあえずrun-all apply
はできたので、run-all destroy
します。依存関係を定義しなかった時とは違って、今度はうまくいきます。
$ terragrunt run-all destroy
これで、やりたいことは達成できました。
Remote Stateを導入する
最後に、警告されたままなのもなんなので、Remote Stateを導入してみましょう。
トップレベルのterragrunt.hcl
ファイルに、ConsulをBackendとする定義を追加します。
terragrunt.hcl
remote_state { backend = "consul" generate = { path = "backend.tf" if_exists = "overwrite_terragrunt" } config = { address = "172.17.0.3: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.2:3306" username = "root" password = "password" } EOF }
今回はRemote Stateの保存先として、Consul 1.9.3を選びました。Consulが動作するサーバーは、172.17.0.3です。
Backend Type: consul - Terraform by HashiCorp
Backendが変わったので、init -reconfigure
が必要になります。こちらも、run-all
で一気に実行できます。
$ terragrunt run-all init -reconfigure
run-all apply
。
$ terragrunt run-all apply
(先ほどの例ではapply
の方は載せていませんが…)今度は、警告が出なくなります。
INFO[0000] Stack at /path/to: => Module /path/to/database (excluded: false, dependencies: []) => Module /path/to/grants (excluded: false, dependencies: [/path/to/database, /path/to/users]) => Module /path/to/users (excluded: false, dependencies: []) Are you sure you want to run 'terragrunt apply' in each folder of the stack described above? (y/n) y mysql_database.app: Refreshing state... [id=my_database] mysql_user.application_user: Refreshing state... [id=appuser@%] mysql_user.administrator_user: Refreshing state... [id=admin@%] Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: database_name = "my_database" Apply complete! Resources: 0 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" mysql_grant.administrator_user: Creating... mysql_grant.application_user: Creating... mysql_grant.administrator_user: Creation complete after 0s [id=admin@%:`my_database`] mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`my_database`] Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: administrator_allow_host = "%" administrator_privileges = toset([ "ALL", ]) administrator_username = "admin" application_user_allow_host = "%" application_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ]) application_user_username = "appuser"
output
も見てみましょう。
$ terragrunt run-all output INFO[0000] Stack at /path/to: => Module /path/to/database (excluded: false, dependencies: []) => Module /path/to/grants (excluded: false, dependencies: [/path/to/database, /path/to/users]) => Module /path/to/users (excluded: false, dependencies: []) administrator_allow_host = "%" administrator_password = <sensitive> administrator_username = "admin" application_user_allow_host = "%" application_user_password = <sensitive> application_user_username = "appuser" database_name = "my_database" administrator_allow_host = "%" administrator_privileges = toset([ "ALL", ]) administrator_username = "admin" application_user_allow_host = "%" application_user_privileges = toset([ "DELETE", "INSERT", "SELECT", "UPDATE", ]) application_user_username = "appuser"
警告が出なくなったので、スッキリしましたね。
後片付け。
$ terragrunt run-all destroy
まとめ
Terragruntを使って、複数のモジュールに対して一括でapply
等の操作が行えることを試してみました。また、依存するモジュールの
Outputを使って、別のモジュールのVariableに利用できるのも良いですね。
ここまでで、Terragruntの基本的な使い方はわかったのかな、という気がします。