Terraform:无法使用 azurerm_lb_backend_address_pool 在 lb 后面创建 vm

Terraform: Can't create vm behind lb with azurerm_lb_backend_address_pool

当我尝试同时在后端池中创建一个带有 vm 的 azure lb 时,我有一个奇怪的行为。

我有一个管理 vm 的模块和一个管理 lb 的模块。 如果我先创建 lb,一切正常,但如果我同时创建两者,它就不起作用。

这是我的配置(terraform 0.12.9,azure 1.33.1):

resource "azurerm_network_interface_backend_address_pool_association" "vm-if-lb-public-association" {
  count                   = var.azure_lb_public_backend_id != "" ? var.countvm : 0
  network_interface_id    = element(azurerm_network_interface.vm-if.*.id, count.index)
  ip_configuration_name   = "${var.workspace_config.prefix}-${var.profile}-${count.index + 1}"
  backend_address_pool_id = var.azure_lb_public_backend_id
}

var.azure_lb_public_backend_id 来自我的 lb 模块

output "lb_id" {
  value = var.enable ? azurerm_lb_backend_address_pool.lb-backend[0].id : ""
}

resource "azurerm_lb_backend_address_pool" "lb-backend" {
  name                = "pool-1"
  count               = var.enable ? 1 : 0
  resource_group_name = azurerm_resource_group.lb-rg[0].name
  loadbalancer_id     = azurerm_lb.lb[0].id
}

当我 运行 一个计划时,我得到以下内容:

Error: Invalid count argument

  on modules/vm/network.tf line 46, in resource "azurerm_network_interface_backend_address_pool_association" "vm-if-lb-public-association":
  46:   count                   = var.azure_lb_public_backend_id != "" ? var.countvm : 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.
It's like the plan command doesn't understand vm creation depends on azurerm_lb_backend_address_pool

我自愿不post所有代码以避免太大post,但如果需要我会毫不犹豫地询问更多。

你知道我为什么会有这种行为吗?

如错误消息所述,count 值不得依赖于 Terraform 在应用完成之前不会知道的任何值。在这种情况下,看起来 var.azure_lb_public_backend_id 是一个对象的 ID,在创建该对象之前不会分配该 ID,因此 Terraform 尚不知道它将具有什么值,因此它不能说确定它是否等于 "".

要完成这项工作,您需要根据 Terraform 在计划时知道的事情做出决定。一种方法是将您的负载均衡器 ID 值包装在一个对象中,以便可以根据是否设置了该对象来做出决定:

variable "load_balancer" {
  type = object({
    backend_address_pool_id = string
  })
  default = null
}

resource "azurerm_network_interface_backend_address_pool_association" "vm-if-lb-public-association" {
  count                   = var.load_balancer != null ? var.countvm : 0
  network_interface_id    = element(azurerm_network_interface.vm-if.*.id, count.index)
  ip_configuration_name   = "${var.workspace_config.prefix}-${var.profile}-${count.index + 1}"
  backend_address_pool_id = var.load_balancer.backend_address_pool_id
}

现在决定是基于 var.load_balancer 对象是否为 null,而不是它里面的 backend_address_pool_id 属性的值。然后你的调用模块可以根据它用来决定如何在另一个模块上设置 var.enable 的相同测试来设置它:

  load_balancer = var.load_balancer_enabled ? {
    backend_address_pool_id = module.load_balancer.lb_id
  } : null

假设 var.load_balancer_enabled 在计划时是已知的,这现在应该可以工作,因为 Terraform 可以决定 load_balancer 是否为空,从而在所有情况下确定 count 的值.


在上文中,我尝试尽可能贴近您的安排方式,以便更容易看到我提出的更改建议,但是有一些不同的方式来安排上述原则可能会这些模块更易于调用者使用。下面是一些与您共享的内容截然不同的示例,展示了我们如何在模块接口本身中隐藏此切换的细节,以实现更清晰的 module composition.

在你的根模块中:

variable "load_balancer_enabled" {
  type    = bool
  default = false
}

resource "azurerm_resource_group" "example" {
  name     = "example"
  location = "West US"
}

module "load_balancer" {
  source = "./modules/load-balancer"

  resource_group = azurerm_resource_group.example
  enabled        = var.load_balancer_enabled
}

module "virtual_machines" {
  source = "./modules/virtual_machines"

  resource_group = azurerm_resource_group.example
  vm_count       = 4
  load_balancer  = module.load_balancer
}

load-balancer模块中:

variable "resource_group" {
  type = object({
    name     = string
    location = string
  })
}

variable "enabled" {
  type    = bool
  default = true
}

resource "azurerm_lb" "example" {
  count = var.enabled ? 1 : 0

  name = "example"

  resource_group_name = var.resource_group.name
  location            = var.resource_group.location

  # (and probably a frontend IP allocation)
}

resource "azurerm_lb_backend_address_pool" "example" {
  count = length(azurerm_lb.example)

  name                = "example"
  resource_group_name = var.resource_group.name
  loadbalancer_id     = azurerm_lb.lb[count.index].id
}

output "backend_address_pool" {
  # Set only if the load balancer is enabled. Null otherwise.
  value = var.enabled ? azurerm_lb_backend_address_pool.example[0] : null
}

virtual-machine模块中:

variable "resource_group" {
  type = object({
    name     = string
    location = string
  })
}

variable "vm_count" {
  type = number
}

variable "load_balancer" {
  type = object({
    # We only need to specify the subset of the module outputs
    # that we need here.
    backend_address_pool = object({
      id = string
    })
  })
}

resource "azurerm_network_interface" "example" {
  count = var.vm_count

  # (and whatever other settings you need here)
}

resource "azurerm_network_interface_backend_address_pool_association" "vm-if-lb-public-association" {
  count = var.load_balancer.backend_address_pool != null ? var.vm_count : 0

  network_interface_id    = azurerm_network_interface.example[count.index].id
  backend_address_pool_id = var.load_balancer.backend_address_pool.id
}

在此变体中,load-balancer 模块生成一个表示后端地址池的对象,并在模块禁用时将其设置为 null。然后我们可以将整个模块结果传递给 virtual-machine 模块,让它根据该对象的 null-ness 做出决定,调用模块只是简单地将模块连接在一起而不需要任何特殊逻辑。

同样,重要的细节是最终仅基于 var.load_balancer_enabled 变量(间接)做出决定,而不是 Terraform 在应用期间学习的任何值。