关于 import-infrastructure-as-code
此 Skill 将发现的 Azure 基础设施转换为基于 Azure Verified Modules 的 Terraform 配置,面向云工程师,涵盖资源发现、依赖梳理、模块选择和差异检查。
主要功能
- 01按订阅、资源组或 ID 发现 Azure 资源
- 02梳理依赖并生成架构文档
- 03选择并检查 Azure Verified Modules
- 04生成 Terraform 配置和导入块
- 05将实时属性与模块默认值比较
适用场景
- 01将现有 Azure 环境重建为 Terraform
- 02记录已发现 Azure 资源之间的关系
- 03导入资源并优先采用 Azure Verified Modules
- 04检查生成的 Terraform 是否存在配置漂移
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ github/ awesome-copilot --skill import-infrastructure-as-code安装后在 Claude Code 中输入/import-infrastructure-as-code确认已加载。
详细介绍
导入基础设施即代码(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-id、resource-group-name 或 resource-id 中至少需要提供一个。
分步工作流
1) 收集所需范围(强制)
在运行发现命令之前,请求以下范围之一:
- 订阅范围:
<subscription-id> - 资源组范围:
<resource-group-name> - 特定资源范围:一个或多个
<resource-id>值
范围处理规则:
- 将 Azure ARM 资源 ID(例如
/subscriptions/.../providers/...)视为云资源标识符,而非本地文件系统路径。 - 仅在 Azure CLI 的
--ids参数中使用资源 ID(例如az resource show --ids <resource-id>)。 - 切勿将资源 ID 传递给文件读取命令(
cat、ls、read_file、glob 搜索),除非用户明确说明它们是本地文件路径。 - 如果用户已提供一个有效范围,则不要请求额外的范围输入,除非失败的命令需要。
- 不要提出可根据已提供范围值回答的后续问题。
如果缺少范围,请明确请求并停止。
2) 身份验证并设置上下文
仅运行所选范围所需的命令。
对于订阅范围:
az login
az account set --subscription <subscription-id>
az account show --query "{subscriptionId:id, name:name, tenantId:tenantId}" -o json
预期输出:包含 subscriptionId、name 和 tenantId 的 JSON 对象。
对于资源组或特定资源范围,仍需要 az login,但如果当前上下文已正确,则 az account set 是可选的。
使用特定资源范围时,优先使用基于 --ids 的直接命令,并避免为订阅或资源组进行额外的发现提示,除非具体命令需要。
3) 运行发现命令
使用所选范围发现资源。确保获取所有必要信息以准确生成 Terraform。
# Subscription scope
az resource list --subscription <subscription-id> -o json
# Resource group scope
az resource list --resource-group <resource-group-name> -o json
# Specific resource scope
az resource show --ids <resource-id-1> <resource-id-2> ... -o json
预期输出:包含 Azure 资源元数据(id、type、name、location、tags、properties)的 JSON 对象或数组。
4) 在代码生成前解析依赖关系
解析导出的 JSON 并映射:
- 父子关系(例如:NIC -> Subnet -> 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 索引
注意: 以下链接始终指向 main 分支上 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--/refs/heads/main/README.md
5a) 在编写任何代码之前阅读模块 README(强制)
此步骤不是可选的。 在为某个模块编写任何一行 HCL 之前,请获取并
阅读该模块的完整 README。不要依赖对原始 azurerm provider
的了解,也不要依赖使用其他 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 中,在编写代码之前提取并记录:
- 必需输入 — 模块所需的每一个输入。这里列出的任何子资源 (NIC、扩展、子网、公共 IP)都由模块内部管理。不要 为这些资源创建独立的模块块。
- 可选输入 — 确切的 Terraform 变量名称及其声明的
type。 不要假设它们与原始azurermprovider 的参数名称或块形状相匹配。 - 用法示例 — 检查使用了哪个资源组标识符(
parent_id还是resource_group_name),子资源如何表达(内联 map 还是独立模块), 以及每个输入期望什么语法。
将模块规则作为模式应用,而非假设
将下面的经验教训视为经常导致导入失败的那类不匹配的示例。
不要假设这些确切的名称适用于每一个 AVM 模块。始终核实每个所选模块的
README 和 variables.tf。
avm-res-compute-virtualmachine(任何版本)
network_interfaces是必需输入。NIC 归 VM 模块所有。切勿在 VM 模块之外 创建独立的avm-res-network-networkinterface模块 —— 在network_interfaces下内联定义每一个 NIC。- TrustedLaunch 通过顶层布尔值
secure_boot_enabled = true和vtpm_enabled = true表达。security_type参数仅存在于os_disk下,用于 机密 VM 磁盘加密,不得用于 TrustedLaunch。 boot_diagnostics是bool,不是对象。使用boot_diagnostics = true;如果需要 存储 URI,请使用单独的boot_diagnostics_storage_account_uri变量。- 扩展通过
extensions映射在模块内部管理。不要创建 独立的扩展资源。
avm-res-network-virtualnetwork(任何版本)
- 此模块由 AzAPI provider 支持,而非
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 —— 确定 provider 和资源标签
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 —— 检查 count 与 for_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,包含azurermprovider 及所需的版本约束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
sku、allocation_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,验证订阅上下文,确认所需权限 |
| 发现输出为空 | 范围不正确或范围内没有资源 | 重新检查范围输入,并再次运行限定范围的 list/show 命令 |
| 未找到适用于某资源类型的 AVM 模块 | 该资源类型尚未被 AVM 覆盖 | 对该类型使用原生 azurerm_* 资源,并记录此缺口 |
terraform validate 失败 | 缺少变量或依赖未解析 | 添加所需变量和显式依赖,然后重新运行验证 |
| 模块中出现未知参数或找不到变量 | AVM 变量名与 azurerm 提供程序参数名不同 | 阅读模块 README 的 variables.tf 或 Optional Inputs 部分以获取正确的名称 |
| 导入块失败 — 在地址处找不到资源 | 提供程序标签错误(azurerm_ 与 azapi_)、缺少子模块路径或缺少 [0] 索引 | 运行 grep "^resource" .terraform/modules/<key>/main*.tf 和 grep "^module" 以查找准确的地址 |
terraform plan 显示对已导入资源出现意外的 ~ update | 实际值与 AVM 模块默认值不同 | 使用 az <resource> show 获取实际属性,与模块默认值比较,添加显式值 |
| 子资源模块给出 "provider configuration not present" | 子资源被声明为独立模块,尽管父模块拥有它们 | 检查 README 中的 Required Inputs,移除错误的独立模块,并使用父模块记录的输入结构来建模子资源 |
| 嵌套子资源导入失败并提示 "resource not found" | 缺少中间模块路径、映射键错误或缺少索引 | 检查源码中的模块块以及 count/for_each;构建包含所有模块段和所需键/索引的完整嵌套导入地址 |
| 工具尝试将 ARM 资源 ID 读取为文件路径,或反复询问范围问题 | 资源 ID 未被当作 --ids 输入处理,或 Agent 未信任已提供的范围 | 严格将 ARM ID 视为云标识符,使用 az ... --ids ...,并在出现一个有效范围后停止重复提示 |
响应约定
返回结果时,请提供:
- 使用的范围(订阅、资源组或资源 ID)
- 创建的发现文件
- 检测到的资源类型
- 选定的 AVM 模块及其版本
- 生成或更新的 Terraform 文件
- 验证命令结果
- 需要用户输入的未解决缺口(如有)
Agent 的执行规则
- 如果缺少范围,则不要继续。
- 在没有列出已发现文件和验证输出的情况下,不得声称导入成功。
- 在生成 Terraform 之前,不得跳过依赖关系映射。
- 优先使用 AVM 模块;对每个非 AVM 回退方案明确说明理由。
- 在编写代码之前,阅读每个 AVM 模块的 README。 必需输入(Required Inputs)指明
该模块拥有哪些子资源。可选输入(Optional Inputs)记录确切的变量名称和
类型。用法示例展示特定于提供程序的约定(
parent_id与resource_group_name)。跳过 README 是基于 AVM 的导入中出现 代码错误的最常见原因。 - 绝不要假设 NIC、扩展或公共 IP 资源是独立的。 对于 任何 AVM 模块,除非 README 明确表明 需要单独的模块,否则将子资源视为父级拥有。在创建同级模块之前,检查必需输入(Required Inputs)。
- 绝不要凭记忆编写导入地址。 在
terraform init之后,grep 已下载的 模块源码,以在编写任何import {}块之前发现实际的提供程序(azurerm与azapi)、资源标签、 子模块嵌套以及count与for_each的用法。 - 绝不要把 ARM 资源 ID 当作文件路径。 资源 ID 属于 Azure CLI
--ids参数和 API 查询,而不是文件 IO 工具。仅当提供了真实的工作区 路径时,才读取本地文件。 - 当范围已知时,尽量减少提示。 如果已提供订阅、资源组或 特定资源 ID,则直接执行命令,仅当命令因缺少必需上下文而失败时 才提出后续问题。
- 在
terraform plan显示 0 个销毁和 0 个 非预期更改之前,不要宣布导入完成。 遥测+ create资源是可以接受的。对真实基础设施资源的任何~ update或- destroy都必须解决。