これは、なにをしたくて書いたもの?
Terraformでcountを使っていた時にこんなエラーに当たったので、「これはなんだろう?」と思い。
The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.
ちょっと調べてみることにしました。
結論を書くと、countやfor_eachで指定する値の中に、作成前のリソースの情報(属性)が含まれているとダメみたいです。
ドキュメントにも書いてありました。
Resources / Using Expressions in count
The count meta-argument accepts numeric expressions. However, unlike most resource arguments, the count value must be known before Terraform performs any remote resource actions. This means count can't refer to any resource attributes that aren't known until after a configuration is applied (such as a unique ID generated by the remote API when an object is created).
Resources / Using Expressions in for_each
The for_each meta-argument accepts map or set expressions. However, unlike most resource arguments, the for_each value must be known before Terraform performs any remote resource actions. This means for_each can't refer to any resource attributes that aren't known until after a configuration is applied (such as a unique ID generated by the remote API when an object is created).
これは、その他のリソースに関する引数とは異なる、countやfor_eachに関する性質です。
環境
今回の環境は、こちらです。
$ terraform version Terraform v0.12.28 + provider.mysql v1.9.0
Providerは、MySQLを使用します。MySQLは8.0.20を使い、172.17.0.2のサーバーで動作しているものとします。
サンプル
では、サンプルを書いていきます。
お題はこうしましょう。
- MySQLデータベースのユーザーおよび権限を作成するモジュールを作る
- 権限などで利用するデータベースは、モジュールのInput Variablesで指定する
- データベースの情報が指定されなかった場合は、モジュール側でデフォルトのデータベースを作成する
このお題だと、データベースが条件によって作成されたり、作成されなかったりします。
では、最初にモジュールの呼び出し側となるリソース定義ファイルを載せます。
main.tf
terraform { required_version = "0.12.28" } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" version = "1.9.0" } module "user" { source = "./modules/user" }
この段階では、単純にモジュールを呼び出しているだけです。
次に、モジュール側の定義ファイル。
modules/user/main.tf
variable "database_id" { type = string default = null } variable "database_name" { type = string default = null } resource "mysql_database" "default" { count = var.database_id == null ? 1 : 0 name = "default_database" default_character_set = "utf8mb4" default_collation = "utf8mb4_ja_0900_as_cs_ks" } resource "mysql_user" "user" { user = "user" host = "%" plaintext_password = "password" } resource "mysql_grant" "grant" { user = mysql_user.user.user host = "%" database = var.database_name != null ? var.database_name : mysql_database.default[0].id privileges = ["ALL"] }
Input Variableに、mysql_databaseリソースのidとnameを指定しています。
variable "database_id" { type = string default = null } variable "database_name" { type = string default = null }
特に、idはリソース作成後でないと得られない値です。
MySQL: mysql_database - Terraform by HashiCorp
ここで、idが指定されていなければデフォルトのデータベースを作成し、指定された場合はデータベースを作成しない、という
条件でmysql_databaseリソースの定義を行います。
resource "mysql_database" "default" { count = var.database_id == null ? 1 : 0 name = "default_database" default_character_set = "utf8mb4" default_collation = "utf8mb4_ja_0900_as_cs_ks" }
ちなみに、mysql_databaseリソースの情報はmysql_grantで使用します。
resource "mysql_grant" "grant" { user = mysql_user.user.user host = "%" database = var.database_name != null ? var.database_name : mysql_database.default[0].id privileges = ["ALL"] }
これを実行します。
$ terraform apply -auto-approve
問題なく、うまくいきます。
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
1度、リソースは破棄しておきます。
$ terraform destroy -force
モジュールの外側でmysql_databaseを定義した場合
次に、モジュールの外側でデータベースを事前に作成するように変更してみましょう。 main.tf
terraform { required_version = "0.12.28" } provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" version = "1.9.0" } resource "mysql_database" "predefined" { name = "my_database" default_character_set = "utf8mb4" default_collation = "utf8mb4_ja_0900_as_cs_ks" } module "user" { source = "./modules/user" database_id = mysql_database.predefined.id database_name = mysql_database.predefined.name }
mysql_databaseリソース定義を追加して
resource "mysql_database" "predefined" { name = "my_database" default_character_set = "utf8mb4" default_collation = "utf8mb4_ja_0900_as_cs_ks" }
モジュールのid、nameには、このリソースの属性を指定します。
module "user" { source = "./modules/user" database_id = mysql_database.predefined.id database_name = mysql_database.predefined.name }
これがどういう結果になるか、ですが、Terraformが実行できなくなります。
$ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ Error: Invalid count argument on modules/user/main.tf line 13, in resource "mysql_database" "default": 13: count = var.database_id == null ? 1 : 0 The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.
エラーメッセージを読むと、「countは、applyを実行するまで決定できない値には依存できない」と言っています。
インスタンスをいくつ作っていいか予測できないから、だと。
これを回避するには、エラーメッセージにあるように事前にモジュールが依存している(countを使うリソースが依存している)
リソースを作成しておきます。
$ terraform apply -target=mysql_database.predefined -auto-approve
まあ、完全には終わってないよ、って言われますけど。
mysql_database.predefined: Creating... mysql_database.predefined: Creation complete after 0s [id=my_database] Warning: Resource targeting is in effect You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration. The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message. Warning: Applied changes may be incomplete The plan was created with the -target option in effect, so some changes requested in the configuration may have been ignored and the output values may not be fully updated. Run the following command to verify that no other changes are pending: terraform plan Note that the -target option is not suitable for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message. Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
このあとは、「terraform apply」すれば残りのリソースを作成してくれます。
$ terraform apply -auto-approve mysql_database.predefined: Refreshing state... [id=my_database] module.user.mysql_user.user: Creating... module.user.mysql_user.user: Creation complete after 0s [id=user@%] module.user.mysql_grant.grant: Creating... module.user.mysql_grant.grant: Creation complete after 0s [id=user@%:`my_database`] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
ちなみに、この事象はcountではなく、for_eachを使っても発生します。
resource "mysql_database" "default" { for_each = var.database_id == null ? toset([var.database_id]) : toset([]) #count = var.database_id == null ? 1 : 0 name = "default_database" default_character_set = "utf8mb4" default_collation = "utf8mb4_ja_0900_as_cs_ks" }
こんな感じのエラーになります。
$ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ Error: Invalid for_each argument on modules/user/main.tf line 12, in resource "mysql_database" "default": 12: for_each = var.database_id == null ? toset([var.database_id]) : toset([]) The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.
というわけで、countやfor_eachを使う値に、terraform applyなどが同じタイミングで適用、生成される値が使用されている場合は、
こういう結果になるので事前に作成しておきましょう、と。
ちょっと惜しい感じもするのですが、今回のような単一の値ではなく、リソースの作成結果がリストのような複数の値を返し、
それを元に別のリソースを作成する…といったようなケースを考えると、確かに作成するリソースの数が定まらなくなるので
仕方ないですね、と。
参考
https://github.com/hashicorp/terraform/blob/v0.12.28/terraform/eval_count.go#L39
https://github.com/hashicorp/terraform/blob/v0.12.28/terraform/eval_for_each.go#L23