1.8.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.8.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.8.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.8.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.8.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 ""