这里只有部分配置可见,我猜测了一下,但让我们看看。您提到您想在本练习中了解更多有关 Terraform 的信息,因此我将在此处详细介绍有关链的内容,以解释 为什么我推荐我'我会推荐,但如果你觉得这个额外的细节无趣,你可以跳到最后。
我们将从that first module's definition of its project_id output value开始:
output "project_id" {
value = module.project-factory.project_id
}
module.project-factory这里指的是a nested module call,所以我们需要再深入一层in the nested module terraform-google-modules/project-factory/google//modules/core_project_factory:
output "project_id" {
value = module.project_services.project_id
depends_on = [
module.project_services,
google_project.main,
google_compute_shared_vpc_service_project.shared_vpc_attachment,
google_compute_shared_vpc_host_project.shared_vpc_host,
]
}
另一个嵌套模块调用! ? 那个人像这样声明its project_id:
output "project_id" {
description = "The GCP project you want to enable APIs on"
value = element(concat([for v in google_project_service.project_services : v.project], [var.project_id]), 0)
}
呸! ? 最后是一个实际的资源。在这种情况下,此表达式似乎采用了 google_project_service 资源实例的 project 属性,或者如果在此模块实例中禁用了该资源,则可能从 var.project_id 中获取它。一起来看看the google_project_service.project_services definition:
resource "google_project_service" "project_services" {
for_each = local.services
project = var.project_id
service = each.value
disable_on_destroy = var.disable_services_on_destroy
disable_dependent_services = var.disable_dependent_services
}
project 这里设置为var.project_id,所以看起来像either way 这个最里面的project_id 输出只是反映了project_id 输入变量的值,所以我们需要跳回上一级并查看 the module call 到此模块以查看设置的内容:
module "project_services" {
source = "../project_services"
project_id = google_project.main.project_id
activate_apis = local.activate_apis
activate_api_identities = var.activate_api_identities
disable_services_on_destroy = var.disable_services_on_destroy
disable_dependent_services = var.disable_dependent_services
}
project_id 设置为google_project.main 的project_id 属性:
resource "google_project" "main" {
name = var.name
project_id = local.temp_project_id
org_id = local.project_org_id
folder_id = local.project_folder_id
billing_account = var.billing_account
auto_create_network = var.auto_create_network
labels = var.labels
}
project_id 这里设置为local.temp_project_id,在同一个文件中进一步声明:
temp_project_id = var.random_project_id ? format(
"%s-%s",
local.base_project_id,
random_id.random_project_id_suffix.hex,
) : local.base_project_id
此表达式包含对random_id.random_project_id_suffix.hex 的引用,而.hex 是来自random_id 的result 属性,因此由于@ 987654359@ 资源类型已实现。 (它会在应用步骤中生成一个随机值并将其保存在状态中,以便在以后的运行中保持一致。)
这意味着(在所有这些间接之后)你的模块中的module.project-factory.project_id 不是在配置中静态定义的值,而是可能在应用步骤期间动态决定。这意味着它不适合用作资源实例键的一部分,因此不适合用作 for_each 映射中的键。
不幸的是,这里for_each 的使用隐藏在另一个模块terraform-google-modules/service-accounts/google 中,所以我们也需要看看那个模块,看看它是如何使用project_roles 输入变量的。先来看看the specific resource block the error message was talking about:
resource "google_project_iam_member" "project-roles" {
for_each = local.project_roles_map_data
project = element(
split(
"=>",
each.value.role
),
0,
)
role = element(
split(
"=>",
each.value.role
),
1,
)
member = "serviceAccount:${google_service_account.service_accounts[each.value.name].email}"
}
这里发生了一些有点复杂的事情,但与我们在这里看到的最相关的是,这个资源配置正在根据local.project_roles_map_data 的内容创建多个实例。现在让我们看看local.project_roles_map_data:
project_roles_map_data = zipmap(
[for pair in local.name_role_pairs : "${pair[0]}-${pair[1]}"],
[for pair in local.name_role_pairs : {
name = pair[0]
role = pair[1]
}]
)
这里稍微复杂一点,这对我们正在寻找的东西并不重要;这里要考虑的主要事情是,这是构造一个映射,其键是从元素零和 local.name_role_pairs 的元素一构建的,它直接在上面声明,以及它引用的 local.names:
names = toset(var.names)
name_role_pairs = setproduct(local.names, toset(var.project_roles))
所以我们在这里了解到的是var.names 中的值和var.project_roles 中的值都有助于该资源上for_each 的键,这意味着这些变量值都不应该包含任何内容在应用步骤中动态决定。
但是,我们还了解到(以上)google_project_iam_member.project-roles 的 project 和 role 参数源自您在您的自己的模块调用。
让我们回到我们开始的地方,记住所有这些额外的信息:
module "service_accounts" {
source = "terraform-google-modules/service-accounts/google"
version = "4.0.3"
project_id = module.project-factory.project_id
generate_keys = "true"
names = ["backend-runner"]
project_roles = [
"${module.project-factory.project_id}=>roles/cloudsql.client",
"${module.project-factory.project_id}=>roles/pubsub.publisher"
]
}
我们了解到names 和project_roles 都必须只包含配置中决定的静态值,因此使用module.project-factory.project_id 是不合适的,因为直到随机项目ID 才能知道它已在应用步骤中生成。
然而,我们也知道这个模块期望project_roles中每个项目的前缀(=>之前的部分)是一个有效的项目ID,所以没有任何其他可以合理使用的值。
因此我们陷入了困境:第二个模块有一个相当尴尬的设计决策,它试图从相同的值,而这两种情况有相互冲突的要求。但这不是您创建的模块,因此您无法轻松修改它以解决该设计怪癖。
鉴于此,我看到了两种可能的前进方法,虽然都不理想,但都有一些注意事项是可行的:
-
您可以采用提供的错误消息作为解决方法,要求 Terraform 首先单独计划和应用第一个模块中的资源,然后在项目 ID 已经确定并在后续运行中计划和应用其余部分记录状态:
terraform apply -target=module.factory
terraform apply
虽然必须分两步进行初始创建很烦人,但它至少只对这个基础架构的初始创建 很重要。如果您稍后对其进行更新,则无需重复此两步过程,除非您以需要生成新项目 ID 的方式更改了配置。
-
在完成上述操作时,我们看到根据第一个模块的 var.random_project_id(您在配置中将其设置为 "true"),这种生成和返回随机项目 ID 的方法是可选的。否则,project_id 输出将只是您给定 name 参数的副本,该参数似乎是通过引用根模块变量来静态定义的。
除非您特别需要在您的项目 ID 上使用随机后缀,否则您可以不设置 random_project_id,从而将项目 ID 设置为与您的 var.project_name 相同的静态值,这应该然后是一个可接受的值,可用作for_each 键。
理想情况下,第二个模块将被设计为将它使用的值(例如键)与它用于引用真实远程对象的值分开,因此可以使用远程对象的随机后缀名称,但是本地对象的静态定义名称。如果这是您控制下的模块,那么我会建议进行类似的设计更改,但我认为该第三方模块当前不寻常的设计(将多个值打包到带有分隔符的单个字符串中)是一种折衷方案希望保持与模块早期迭代的向后兼容性。