import-infrastructure-as-code

import-infrastructure-as-code

热门

使用 Azure CLI 发现和 Azure Verified Modules (AVM) 将现有 Azure 资源导入 Terraform。当需要逆向工程实时 Azure 基础设施、从现有订阅/资源组/资源 ID 生成基础设施即代码、映射依赖关系、从下载的模块源推导精确导入地址、防止配置漂移,并生成基于 AVM 的 Terraform 文件(可用于跨任何 Azure 资源类型的验证和规划)时使用。

3.7万Star
4572Fork
更新于 2026/7/14
SKILL.md
readonly只读
name
import-infrastructure-as-code
description

使用 Azure CLI 发现和 Azure Verified Modules (AVM) 将现有 Azure 资源导入 Terraform。当需要逆向工程实时 Azure 基础设施、从现有订阅/资源组/资源 ID 生成基础设施即代码、映射依赖关系、从下载的模块源推导精确导入地址、防止配置漂移,并生成基于 AVM 的 Terraform 文件(可用于跨任何 Azure 资源类型的验证和规划)时使用。

导入基础设施即代码(Azure -> 使用 AVM 的 Terraform)

使用发现数据和 Azure Verified Modules 将现有 Azure 基础设施转换为可维护的 Terraform 代码。

何时使用此技能

当用户要求以下操作时使用此技能:

  • 将现有 Azure 资源导入 Terraform
  • 从实时 Azure 环境生成 IaC
  • 处理 AVM 支持的任何 Azure 资源类型(并记录合理的非 AVM 回退方案)
  • 从订阅或资源组重建基础设施
  • 映射已发现 Azure 资源之间的依赖关系
  • 使用 AVM 模块而不是手写 azurerm_* 资源

先决条件

  • 已安装并认证 Azure CLI(az login
  • 可访问目标订阅或资源组
  • 已安装 Terraform CLI
  • 可访问 Terraform Registry 和 AVM 索引源

输入

参数 必需 默认值 描述
subscription-id 活动 CLI 上下文 用于订阅范围发现和上下文设置的 Azure 订阅
resource-group-name 用于资源组范围发现的 Azure 资源组
resource-id 用于特定资源范围发现的一个或多个 Azure ARM 资源 ID

至少需要 subscription-idresource-group-nameresource-id 中的一个。

分步工作流

1) 收集所需范围(必需)

在运行发现命令之前,请求以下范围之一:

  • 订阅范围:<subscription-id>
  • 资源组范围:<resource-group-name>
  • 特定资源范围:一个或多个 <resource-id>

范围处理规则:

  • 将 Azure ARM 资源 ID(例如 /subscriptions/.../providers/...)视为云资源标识符,而非本地文件系统路径。
  • 仅将资源 ID 用于 Azure CLI --ids 参数(例如 az resource show --ids <resource-id>)。
  • 除非用户明确说明是本地文件路径,否则切勿将资源 ID 传递给文件读取命令(catlsread_file、glob 搜索)。
  • 如果用户已提供一个有效范围,除非失败命令需要,否则不要请求额外的范围输入。
  • 不要询问可以从已提供的范围值回答的后续问题。

如果缺少范围,明确要求并提供,然后停止。

2) 认证并设置上下文

仅运行所选范围所需的命令。

对于订阅范围:

az login
az account set --subscription <subscription-id>
az account show --query "{subscriptionId:id, name:name, tenantId:tenantId}" -o json

预期输出:包含 subscriptionIdnametenantId 的 JSON 对象。

对于资源组或特定资源范围,仍需要 az login,但如果活动上下文已正确,则 az account set 可选。

使用特定资源范围时,优先直接使用基于 --ids 的命令,除非具体命令需要,否则避免额外的订阅或资源组发现提示。

3) 运行发现命令

使用所选范围发现资源。确保获取所有必要信息以准确生成 Terraform。

# 订阅范围
az resource list --subscription <subscription-id> -o json

# 资源组范围
az resource list --resource-group <resource-group-name> -o json

# 特定资源范围
az resource show --ids <resource-id-1> <resource-id-2> ... -o json

预期输出:包含 Azure 资源元数据(idtypenamelocationtagsproperties)的 JSON 对象或数组。

4) 在代码生成前解析依赖关系

解析导出的 JSON 并映射:

  • 父子关系(例如:NIC -> 子网 -> VNet)
  • properties 中的跨资源引用
  • Terraform 创建的顺序

重要:生成以下文档并保存到项目根目录的 docs 文件夹中。

  • exported-resources.json:包含所有发现的资源及其元数据,包括依赖关系和引用。
  • EXPORTED-ARCHITECTURE.MD:基于发现的资源及其关系的人类可读架构概述文件。

5) 选择 Azure Verified Modules(必需)

为每种资源类型使用最新的 AVM 版本。

Terraform Registry

  • 搜索 "avm" + 资源名称
  • 按 "Partner" 标签过滤以查找官方 AVM 模块
  • 示例:搜索 "avm storage account" → 按 Partner 过滤

官方 AVM 索引

注意: 以下链接始终指向主分支上 CSV 文件的最新版本。这意味着文件可能会随时间变化。如果需要特定时间点的版本,请考虑在 URL 中使用特定发布标签。

  • Terraform 资源模块https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformResourceModules.csv
  • Terraform 模式模块https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformPatternModules.csv
  • Terraform 实用模块https://raw.githubusercontent.com/Azure/Azure-Verified-Modules/refs/heads/main/docs/static/module-indexes/TerraformUtilityModules.csv

单个模块信息

如果 .terraform 文件夹中本地没有模块信息,请使用 web 工具或其他合适的 MCP 方法获取模块信息。

使用 AVM 源:

  • Registry:https://registry.terraform.io/modules/Azure/<module>/azurerm/latest
  • GitHub:https://github.com/Azure/terraform-azurerm-avm-res-<service>-<resource>

当 AVM 模块存在时,优先使用 AVM 模块而非手写 azurerm_* 资源。

从 GitHub 仓库获取模块信息时,仓库根目录中的 README.md 文件通常包含模块的所有详细信息,例如:https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res-<service>-<resource>/refs/heads/main/README.md

5a) 在编写任何代码之前阅读模块 README(必需)

此步骤不可省略。 在为模块编写一行 HCL 之前,获取并阅读该模块的完整 README。不要依赖对原始 azurerm 提供者的了解或之前使用其他 AVM 模块的经验。

对于每个选定的 AVM 模块,获取其 README:

https://raw.githubusercontent.com/Azure/terraform-azurerm-avm-res-<service>-<resource>/refs/heads/main/README.md

或者如果模块在 terraform init 后已下载:

cat .terraform/modules/<module_key>/README.md

从 README 中提取并记录 在编写代码之前

  1. 必需输入 — 模块所需的每个输入。此处列出的任何子资源(NIC、扩展、子网、公共 IP)都在模块 内部 管理。不要为这些资源创建独立的模块块。
  2. 可选输入 — 确切的 Terraform 变量名称及其声明的 type。不要假设它们与原始 azurerm 提供者的参数名称或块形状匹配。
  3. 使用示例 — 检查使用的资源组标识符(parent_idresource_group_name),子资源如何表示(内联映射与单独模块),以及每个输入期望的语法。
将模块规则作为模式应用,而非假设

使用以下经验教训作为导致导入失败的 类型 不匹配的示例。不要假设这些确切名称适用于每个 AVM 模块。始终验证每个选定模块的 README 和 variables.tf

avm-res-compute-virtualmachine(任何版本)

  • network_interfaces必需输入。NIC 由 VM 模块拥有。切勿在 VM 模块旁边创建独立的 avm-res-network-networkinterface 模块 — 在 network_interfaces 下内联定义每个 NIC。
  • TrustedLaunch 通过顶层布尔值 secure_boot_enabled = truevtpm_enabled = true 表示。security_type 参数仅存在于 os_disk 下,用于机密 VM 磁盘加密,不得用于 TrustedLaunch。
  • boot_diagnosticsbool 类型,而非对象。使用 boot_diagnostics = true;如果需要存储 URI,则使用单独的 boot_diagnostics_storage_account_uri 变量。
  • 扩展通过 extensions 映射在模块内部管理。不要创建独立的扩展资源。

avm-res-network-virtualnetwork(任何版本)

  • 此模块由 AzAPI 提供者支持,而非 azurerm。使用 parent_id(完整的资源组资源 ID 字符串)指定资源组,而非 resource_group_name
  • README 中的每个示例都显示 parent_id;没有示例显示 resource_group_name

所有 AVM 模块的通用要点:

  • 在创建兄弟模块之前,从 必需输入 确定子资源所有权。
  • 可选输入variables.tf 确定接受的变量名称和类型。
  • 从 README 使用示例确定标识符样式和输入形状。
  • 不要从原始 azurerm_* 资源推断参数名称。

6) 生成 Terraform 文件

在编写导入块之前 — 检查模块源(必需)

terraform init 下载模块后,在编写任何 import {} 块之前,检查每个模块的源文件以确定确切的 Terraform 资源地址。切勿凭记忆编写导入地址。

步骤 A — 识别提供者和资源标签
grep "^resource" .terraform/modules/<module_key>/main*.tf

这将揭示模块使用的是 azurerm_* 还是 azapi_resource 标签。例如,avm-res-network-virtualnetwork 暴露的是 azapi_resource "vnet",而非 azurerm_virtual_network "this"

步骤 B — 识别子模块和嵌套路径
grep "^module" .terraform/modules/<module_key>/main*.tf

如果子资源在子模块中管理(子网、扩展等),导入地址必须包含每个中间模块标签:

module.<root_module_key>.module.<child_module_key>["<map_key>"].<resource_type>.<label>[<index>]
步骤 C — 检查 countfor_each
grep -n "count\|for_each" .terraform/modules/<module_key>/main*.tf

任何使用 count 的资源都需要在导入地址中包含索引。当 count = 1 时(例如,条件性 Linux 与 Windows 选择),地址必须以 [0] 结尾。使用 for_each 的资源使用字符串键,而非数字索引。

已知导入地址模式(来自经验教训的示例)

这些仅是示例。将它们用作推理模板,然后从当前导入中模块的下载源代码推导出确切地址。

资源 正确的导入 to 地址模式
基于 AzAPI 的 VNet module.<vnet_key>.azapi_resource.vnet
子网(嵌套,基于 count) module.<vnet_key>.module.subnet["<subnet_name>"].azapi_resource.subnet[0]
Linux VM(基于 count) module.<vm_key>.azurerm_linux_virtual_machine.this[0]
VM NIC module.<vm_key>.azurerm_network_interface.virtualmachine_network_interfaces["<nic_key>"]
VM 扩展(默认 deploy_sequence=5) module.<vm_key>.module.extension["<ext_name>"].azurerm_virtual_machine_extension.this
VM 扩展(deploy_sequence=1–4) module.<vm_key>.module.extension_<n>["<ext_name>"].azurerm_virtual_machine_extension.this
NSG-NIC 关联 module.<vm_key>.azurerm_network_interface_security_group_association.this["<nic_key>-<nsg_key>"]

生成:

  • providers.tf:包含 azurerm 提供者和所需版本约束
  • main.tf:包含 AVM 模块块和显式依赖关系
  • variables.tf:用于环境特定值
  • outputs.tf:用于关键 ID 和端点
  • terraform.tfvars.example:包含占位符值

将实时属性与模块默认值进行比较(必需)

编写初始配置后,将每个发现的实时资源的每个非零属性与相应 AVM 模块的 variables.tf 中声明的默认值进行比较。任何实时值与模块默认值不同的属性都必须在 Terraform 配置中显式设置。

特别注意以下属性类别,这些是静默配置漂移的常见来源:

  • 超时值(例如,公共 IP idle_timeout_in_minutes 默认为 4;实际部署通常使用 30
  • 网络策略标志(例如,子网 private_endpoint_network_policies 默认为 "Enabled";现有子网通常为 "Disabled"
  • SKU 和分配(例如,公共 IP skuallocation_method
  • 可用区(例如,VM 区域、公共 IP 区域)
  • 存储和数据库资源的冗余和复制设置

使用显式 az 命令检索完整的实时属性,例如:

az network public-ip show --ids <resource_id> --query "{idleTimeout:idleTimeoutInMinutes, sku:sku.name, zones:zones}" -o json
az network vnet subnet show --ids <resource_id> --query "{privateEndpointPolicies:privateEndpointNetworkPolicies, delegation:delegations}" -o json

不要仅依赖 az resource list 输出,它可能省略嵌套或计算属性。

显式固定模块版本:

module "example" {
	source  = "Azure/<module>/azurerm"
	version = "<latest-compatible-version>"
}

7) 验证生成的代码

运行:

terraform init
terraform fmt -recursive
terraform validate
terraform plan

预期输出:无语法错误、无验证错误,且计划与发现的基础设施意图匹配。

故障排除

问题 可能原因 操作
az 命令因授权错误失败 错误的租户/订阅或缺少 RBAC 角色 重新运行 az login,验证订阅上下文,确认所需权限
发现输出为空 范围不正确或范围内无资源 重新检查范围输入并再次运行范围列表/显示命令
未找到资源类型的 AVM 模块 资源类型尚未被 AVM 覆盖 对该类型使用原生 azurerm_* 资源并记录差距
terraform validate 失败 缺少变量或未解析的依赖关系 添加所需变量和显式依赖关系,然后重新运行验证
模块中找不到未知参数或变量 AVM 变量名称与 azurerm 提供者参数名称不同 阅读模块 README 的 variables.tf 或可选输入部分以获取正确名称
导入块失败 — 在地址处未找到资源 错误的提供者标签(azurerm_azapi_)、缺少子模块路径或缺少 [0] 索引 运行 grep "^resource" .terraform/modules/<key>/main*.tfgrep "^module" 以找到确切地址
terraform plan 显示导入资源上意外的 ~ update 实时值与 AVM 模块默认值不同 使用 az <resource> show 获取实时属性,与模块默认值比较,添加显式值
子资源模块给出 "provider configuration not present" 子资源声明为独立模块,即使父模块拥有它们 检查 README 中的必需输入,删除不正确的独立模块,并使用父模块文档化的输入结构建模子资源
嵌套子资源导入失败,提示 "resource not found" 缺少中间模块路径、错误的映射键或缺少索引 检查源中的模块块和 count/for_each;构建完整的嵌套导入地址,包括所有模块段和所需键/索引
工具尝试将 ARM 资源 ID 作为文件路径读取或重复询问范围问题 资源 ID 未被视为 --ids 输入,或代理未信任已提供的范围 严格将 ARM ID 视为云标识符,使用 az ... --ids ...,并在存在一个有效范围后停止重新提示

响应契约

返回结果时,提供:

  1. 使用的范围(订阅、资源组或资源 ID)
  2. 创建的文件
  3. 检测到的资源类型
  4. 选定的 AVM 模块及其版本
  5. 生成或更新的 Terraform 文件
  6. 验证命令结果
  7. 需要用户输入的未解决问题(如有)

代理执行规则

  • 如果缺少范围,不要继续。
  • 不要在没有列出发现文件和验证输出的情况下声称导入成功。
  • 在生成 Terraform 之前不要跳过依赖关系映射。
  • 优先使用 AVM 模块;明确证明每个非 AVM 回退的合理性。
  • 在编写代码之前阅读每个 AVM 模块的 README。 必需输入标识模块拥有哪些子资源。可选输入记录确切的变量名称和类型。使用示例显示提供者特定的约定(parent_idresource_group_name)。跳过 README 是基于 AVM 的导入中代码错误的最常见原因。
  • 切勿假设 NIC、扩展或公共 IP 资源是独立的。 对于任何 AVM 模块,除非 README 明确指示需要单独的模块,否则将子资源视为父模块拥有。在创建兄弟模块之前检查必需输入。
  • 切勿凭记忆编写导入地址。terraform init 之后,使用 grep 检查下载的模块源以发现实际的提供者(azurermazapi)、资源标签、子模块嵌套以及 countfor_each 的使用,然后再编写任何 import {} 块。
  • 切勿将 ARM 资源 ID 视为文件路径。 资源 ID 属于 Azure CLI --ids 参数和 API 查询,而非文件 IO 工具。仅在提供实际工作空间路径时读取本地文件。
  • 当范围已知时最小化提示。 如果已提供订阅、资源组或特定资源 ID,直接继续执行命令,仅当命令因缺少所需上下文而失败时才询问后续问题。
  • terraform plan 显示 0 个销毁和 0 个不需要的更改之前,不要声明导入完成。 + create 资源是可接受的。任何 ~ update- destroy 对实际基础设施资源的更改都必须解决。

参考