1.9.6.1. 利用 null_resource 的 triggers 触发其他资源更新

社区有人提了一个 Terraform 问题,他写了这样一段 Terraform 代码:

resource "azurerm_key_vault_secret" "service_bus_connection_string" {
  name = "service-bus-connection-string"

  value        = azurerm_servicebus_topic_authorization_rule.mysb.primary_connection_string
  key_vault_id = azurerm_key_vault.main.id
}

resource "azurerm_function_app" "main" {
  name                = "myfn"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

  app_service_plan_id = azurerm_app_service_plan.main.id

  enable_builtin_logging = true
  https_only             = true
  os_type                = "linux"

  storage_account_name       = azurerm_storage_account.main.name
  storage_account_access_key = azurerm_storage_account.main.primary_access_key

  version = "~3"

  app_settings = {
    AzureWebJobsServiceBus      = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.service_bus_connection_string.id})"
  }
}

意思大概是他把一段含有机密信息的连接字符串保存在 Azure KeyVault 服务中,然后创建了一个 Azure Faas 函数,通过 KeyVault 机密引用地址传递该机密。

1.9.6.1.1. 问题描述

这位老兄发现,如果他修改了机密的内容,也就是 azurerm_key_vault_secret 声明里的 value = azurerm_servicebus_topic_authorization_rule.mysb.primary_connection_string 这一段的值的时候,KeyVault 保存的机密内容的确会正确更新,但 Azure Function 读取到的还是旧的机密引用地址,也就是这段代码中得到的 KeyVault 机密引用地址没有更新:

app_settings = {
  AzureWebJobsServiceBus      = "@Microsoft.KeyVault(SecretUri=${azurerm_key_vault_secret.service_bus_connection_string.id})"
}

更加奇怪的是,这之后他什么都没有做,只是重新再执行一次 terraform apply,该引用地址又被正确更新了?!

1.9.6.1.2. 问题原因

因为 KeyVault Secret 被设计成是不可变的,所以更新 azurerm_key_vault_secretvalue 会导致资源被重新创建。Terraform 官网上的相关文档中对该参数的定义如下:

value - (Required) Specifies the value of the Key Vault Secret.

在 Terraform 中 ,一个参数如果被标记为 Required,那么它不但是必填项,同时类似数据库记录的主键的概念,主键不同的记录被认定是两条不同的记录,修改记录的主键值可以看作是删除重建之。Terraform 资源的 Required 参数如果发生变化会触发重新创建资源,这就导致了修改 value 后,该 azurerm_key_vault_secretid 也会发生变化。

那么为什么在 azurerm_key_vault_secret 被重新创建之后,我们会发现 azurerm_function_app 中引用的 id 没有变化呢?

Terraform 的工作流含有 Plan 和 Apply 两个主要阶段,首先会分析 Terraform 代码,调用 terraform refresh(可以用参数跳过该步骤)读取资源在云端目前的最新状态,再加上 State 文件中记录的状态,三个状态对比出一个执行计划,使得最终产生的云端状态能够符合当前代码描述的状态。

就这个场景而言,Terraform 能够意识到 azurerm_key_vault_secret 的参数发生了变化,这会导致某种程度的更新,但它无法意识到这个更新会导致 azurerm_key_vault_secretid 发生变化,进而导致 azurerm_function_app 也必须进行更新,所以就发生了他第一次执行 terraform apply 后看到的情况。

当他第二次执行 terraform apply 时,Terraform 记录的 State 文件里,azurerm_key_vault_secretidazurerm_function_app 里使用的 id 已经对不上了,这时 Terraform 会再生成一个更新 azurerm_function_app 的 Plan,执行后一切恢复正常。

有没有办法让 azurerm_function_app 能在第一次生成 Plan 时就感知到这个变更?

1.9.6.1.3. 巧用 null_resource 的 triggers

HashiCorp 提供了一个非常常用的内建 Provider —— null。其中最知名的资源就是 null_resource 了,一般它都是和 provisioner 搭配出现,可以用来在某些资源创建完成后执行一些自定义脚本等等。但是它还有一个很有用的参数:

The triggers argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced.

triggers 参数可以用来指向一些值,只要这些值的内容发生了变动,会导致 null_resource 资源被重新创建,从而生成一个新的 id

1.9.6.1.4. 一个小实验

我们尝试构建一个简单的实验环境来验证一下,首先是这样一段代码:

resource "azurerm_key_vault_secret" "example" {
  name         = "secret-sauce"
  value        = "szechuan"
  key_vault_id = azurerm_key_vault.example.id
}

resource "local_file" "output" {
  filename = "${path.module}/output.txt"
  content = azurerm_key_vault_secret.example.id
}

我们创建一个 azurerm_key_vault_secret,然后把它的 id 输出到一个文件里。随后我们复制一下该文件,比如叫 output.bak 好了。随后我们修改 azurerm_key_vault_secretvalue 到一个新的值,执行 terraform apply 以后,我们会发现 output.txtoutput.bak 的内容完全一样,说明 value 的更新并没有触发 local_file 的更新。

随后我们把代码改成这样:

resource "azurerm_key_vault_secret" "example" {
  name         = "secret-sauce"
  value        = "szechuan2"
  key_vault_id = azurerm_key_vault.example.id
}

resource "null_resource" "example" {
  triggers = {
    trigger = azurerm_key_vault_secret.example.value
  }
}

resource "local_file" "output" {
  filename = "${path.module}/output.txt"
  content = null_resource.example.id == null_resource.example.id ? azurerm_key_vault_secret.example.id : ""
}

我们在代码中插入了一个 null_resource,并设置 triggers 的内容,盯住 azurerm_key_vault_secret.example.value。在 value 发生变化时,null_resourceid 也会发生变化。

然后我们在 local_file 的代码中,content 的赋值改成了这样一个三目表达式:null_resource.example.id == null_resource.example.id ? azurerm_key_vault_secret.example.id : ""。这个表达式里实际上 null_resource.example.id 是不起作用的,自己等于自己的永真条件会导致仍然使用 azurerm_key_vault_secret.example.id 作为值,但是由于掺入了 null_resource.example.id,使得 Terraform 在第一次计算 Plan 时就感知到 local_file 的内容发生了变化,从而使得我们可以一次 terraform apply 搞定。

results matching ""

    No results matching ""