更新azurerm_linux_virtual_machine的SSH公钥为何触发VM替换?
核心结论
这确实是你使用的AzureRM Provider 2.50.0版本的预期行为。在该版本中,azurerm_linux_virtual_machine的admin_ssh_key块被标记为ForceNew属性——也就是说,一旦该块内的任何值(比如公钥)发生变更,Terraform会强制销毁并重建整个虚拟机,这和Azure门户直接修改VM配置的逻辑不同。
原因分析
早期版本的AzureRM Provider对VM的SSH密钥配置采用了强制替换的设计,因为当时Terraform还未实现对Azure VM SSH密钥的原地更新逻辑。而Azure门户或CLI是直接调用Azure API修改VM的操作系统配置,不需要重建实例,所以不会触发VM替换和IP地址变更。
解决方案
根据你的需求(避免VM替换、后续用密钥保管库管理密钥),可以选择以下几种方案:
1. 升级AzureRM Provider版本(推荐)
在AzureRM Provider 3.x及以上的版本中,已经修复了这个限制,支持对admin_ssh_key进行原地更新,不需要替换VM。升级后,修改公钥只会触发VM的配置更新,不会重建实例。
注意:升级Provider前请仔细阅读官方版本变更日志,处理可能的配置兼容性问题(比如资源属性重命名、参数变更等),建议在测试环境验证后再应用到生产环境。
2. 临时绕过Terraform同步密钥(应急方案)
如果暂时无法升级Provider,可以通过Azure CLI或门户手动更新VM的SSH公钥,然后执行以下命令让Terraform同步本地状态:
terraform refresh
这样后续的terraform plan就不会再触发VM替换,但这种方式会导致Terraform配置文件与实际资源状态不一致,仅适合临时场景。
3. 使用Key Vault+VM扩展管理密钥(长期最优方案)
既然你计划将公钥存储到密钥保管库,可以通过azurerm_virtual_machine_extension实现密钥的动态同步,完全避免VM替换:
# 从Key Vault获取最新的SSH公钥 data "azurerm_key_vault_secret" "ssh_public_key" { name = "your-ssh-public-key-secret-name" key_vault_id = azurerm_key_vault.your_key_vault.id } # 通过自定义脚本扩展将公钥注入VM的authorized_keys resource "azurerm_virtual_machine_extension" "ssh_key_sync" { for_each = azurerm_linux_virtual_machine.linux_vms name = "SSHKeySync" virtual_machine_id = each.value.id publisher = "Microsoft.Azure.Extensions" type = "CustomScript" type_handler_version = "2.1" # 给VM分配访问Key Vault的托管身份权限(需提前配置Managed Identity) identity { type = "SystemAssigned" } settings = jsonencode({ commandToExecute = <<-EOT # 清空旧密钥并写入新密钥(或追加,根据需求调整) echo "${data.azurerm_key_vault_secret.ssh_public_key.value}" > /home/username/.ssh/authorized_keys chmod 600 /home/username/.ssh/authorized_keys chown username:username /home/username/.ssh/authorized_keys EOT }) }
使用该方案时,只需更新Key Vault中的密钥,再重新应用Terraform即可触发扩展同步密钥到VM,全程不需要重建VM。
额外说明
你标记tls_private_key为tainted会强制Terraform重新生成密钥,由于VM的admin_ssh_key依赖该资源的输出值,所以会触发VM的替换逻辑,这是Terraform依赖链的正常行为。
内容的提问来源于stack exchange,提问作者thetechguy

