关于 azure-verified-modules
这份指南面向希望让 Terraform 模块符合 Azure Verified Modules 认证要求的作者,涵盖模块结构、Provider 约束、测试、文档和变更管理。
主要功能
- 01固定版本的 AVM 模块引用
- 02Azure Provider 版本规则
- 03Terraform 命名与区块排序
- 04变量和输出要求
- 05测试与文档规范
适用场景
- 01核对 Terraform 模块的 AVM 要求
- 02指导 Azure 模块变更
- 03检查 Provider、测试和文档配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ hashicorp/ agent-skills --skill azure-verified-modules安装后在 Claude Code 中输入/azure-verified-modules确认已加载。
详细介绍
Azure Verified Modules(AVM)要求
本指南介绍 Azure Verified Modules 认证的强制要求。这些要求旨在确保 Azure Terraform 模块的一致性、质量和可维护性。
参考资料:
目录
模块交叉引用
严重性: 必须 | 要求: TFFR1
构建资源模块或模式模块时,模块所有者可以交叉引用其他模块。但是:
- 必须使用 HashiCorp Terraform Registry 引用指定固定版本的模块
- 示例:
source = "Azure/xxx/azurerm",并设置version = "1.2.3"
- 示例:
- 不得使用 git 引用(例如,
git::https://xxx.yyy/xxx.git或github.com/xxx/yyy) - 不得引用非 AVM 模块
Azure Provider 要求
严重性: 必须 | 要求: TFFR3
作者必须仅使用以下 Azure Provider:
| Provider | 最低版本 | 最高版本 |
|---|---|---|
| azapi | >= 2.0 | < 3.0 |
| azurerm | >= 4.0 | < 5.0 |
要求:
- 作者可以选择 Azurerm、Azapi,或同时使用这两种 Provider
- 必须使用
required_providers块来限定 Provider 版本 - 建议使用悲观版本约束运算符(
~>)
示例:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
azapi = {
source = "Azure/azapi"
version = "~> 2.0"
}
}
}
代码风格标准
小写蛇形命名
严重性: 必须 | 要求: TFNFR4
以下名称必须使用小写蛇形命名:
- 本地值
- 变量
- 输出
- 资源(符号名称)
- 模块(符号名称)
示例:snake_casing_example
资源与数据源排序
严重性: 建议 | 要求: TFNFR6
- 被其他资源依赖的资源建议排在前面
- 有依赖关系的资源建议彼此靠近定义
count 与 for_each 的用法
严重性: 必须 | 要求: TFNFR7
- 使用
count有条件地创建资源 - 资源的
for_each集合必须使用map(xxx)或set(xxx) - map 的键或 set 的元素必须是静态字面值
示例:
resource "azurerm_subnet" "pair" {
for_each = var.subnet_map # map(string)
name = "${each.value}-pair"
resource_group_name = azurerm_resource_group.example.name
virtual_network_name = azurerm_virtual_network.example.name
address_prefixes = ["10.0.1.0/24"]
}
资源与数据块内部排序
严重性: 建议 | 要求: TFNFR8
资源块/数据块中的顺序:
-
元参数(顶部):
providercountfor_each
-
参数/块(中间,按字母顺序):
- 必需参数
- 可选参数
- 必需嵌套块
- 可选嵌套块
-
元参数(底部):
depends_onlifecycle(子项顺序:create_before_destroy、ignore_changes、prevent_destroy)
各部分之间用空行分隔。
模块块排序
严重性: 建议 | 要求: TFNFR9
模块块中的顺序:
-
顶部元参数:
sourceversioncountfor_each
-
参数(按字母顺序):
- 必需参数
- 可选参数
-
底部元参数:
depends_onproviders
Lifecycle ignore_changes 语法
严重性: MUST | 要求: TFNFR10
ignore_changes 属性不得用双引号括起来。
正确:
lifecycle {
ignore_changes = [tags]
}
错误:
lifecycle {
ignore_changes = ["tags"]
}
条件创建的 Null 比较
严重性: SHOULD | 要求: TFNFR11
对于需要条件创建资源的参数,请用 object 类型包装,以避免在计划阶段出现“应用后已知”的问题。
建议:
variable "security_group" {
type = object({
id = string
})
default = null
}
可选嵌套对象的 Dynamic 块
严重性: MUST | 要求: TFNFR12
条件下的嵌套块必须使用以下模式:
dynamic "identity" {
for_each = <condition> ? [<some_item>] : []
content {
# block content
}
}
使用 coalesce/try 设置默认值
严重性: SHOULD | 要求: TFNFR13
正确:
coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg")
错误:
var.new_network_security_group_name == null ? "${var.subnet_name}-nsg" : var.new_network_security_group_name
模块中的 Provider 声明
严重性: MUST | 要求: TFNFR27
- 模块中不得声明
provider(configuration_aliases除外) - 模块中的
provider块只能使用alias - 建议由模块用户传入 Provider 配置
变量要求
不允许的变量
严重性: MUST | 要求: TFNFR14
模块所有者不得添加 enabled 或 module_depends_on 之类的变量来控制整个模块的运行。针对特定资源的布尔功能开关可以使用。
变量定义顺序
严重性: SHOULD | 要求: TFNFR15
变量建议按以下顺序排列:
- 所有必填字段(按字母顺序)
- 所有可选字段(按字母顺序)
变量命名规则
严重性: SHOULD | 要求: TFNFR16
- 遵循 HashiCorp 的命名规则
- 功能开关建议使用肯定表述:使用
xxx_enabled,而不是xxx_disabled
带说明的变量
严重性: SHOULD | 要求: TFNFR17
description建议准确描述参数的用途和预期数据类型- 目标受众是模块用户,而非开发者
- 对于
object类型,请使用 HEREDOC 格式
带类型的变量
严重性: MUST | 要求: TFNFR18
- 必须为每个变量定义
type type建议尽可能精确- 只有在理由充分时才可以使用
any - 对于 true/false 值,使用
bool而非string/number - 使用具体的
object,而非map(any)
敏感数据变量
严重性: SHOULD | 要求: TFNFR19
如果变量类型为 object 且包含敏感字段,则整个变量建议设置为 sensitive = true,或将敏感字段提取为单独的变量。
集合类型使用非空默认值
严重性: SHOULD | 要求: TFNFR20
在循环中使用集合值(set、map、list)时,nullable 建议设置为 false。对于标量值,null 可能具有语义含义。
默认情况下避免可空
严重性: MUST | 要求: TFNFR21
除非 null 值有特定的语义需求,否则必须避免使用 nullable = true。
避免 sensitive = false
严重性: MUST | 要求: TFNFR22
必须避免使用 sensitive = false(这是默认值)。
敏感默认值条件
严重性: MUST | 要求: TFNFR23
不得为敏感输入设置默认值(例如默认密码)。
处理已弃用的变量
严重性: MUST | 要求: TFNFR24
- 将已弃用的变量移至
deprecated_variables.tf - 在说明开头标注
DEPRECATED - 声明替代变量的名称
- 在主要版本发布期间清理
输出要求
其他 Terraform 输出
严重性: SHOULD | 要求: TFFR2
作者 SHOULD NOT 输出整个资源对象,因为其中可能包含敏感数据,且架构可能随 API 或提供程序版本而变化。
最佳实践:
- 将资源的计算得出的属性作为独立输出输出(防腐层模式)
- SHOULD NOT 输出已经作为输入的值(
name除外) - 对敏感属性使用
sensitive = true - 对使用
for_each部署的资源,以映射结构输出计算得出的属性
示例:
# Single resource computed attribute
output "foo" {
description = "MyResource foo attribute"
value = azurerm_resource_myresource.foo
}
# for_each resources
output "childresource_foos" {
description = "MyResource children's foo attributes"
value = {
for key, value in azurerm_resource_mychildresource : key => value.foo
}
}
# Sensitive output
output "bar" {
description = "MyResource bar attribute"
value = azurerm_resource_myresource.bar
sensitive = true
}
敏感数据输出
严重性: MUST | 要求: TFNFR29
包含机密数据的输出 MUST 声明为 sensitive = true。
处理已弃用的输出
严重性: MUST | 要求: TFNFR30
- 将已弃用的输出移至
deprecated_outputs.tf - 在
outputs.tf中定义新输出 - 在主版本发布期间进行清理
本地值规范
locals.tf 组织
严重性: MAY | 要求: TFNFR31
locals.tfSHOULD 仅包含locals块- 在高级场景中,MAY 在资源旁声明
locals块
本地值按字母顺序排列
严重性: MUST | 要求: TFNFR32
locals 块中的表达式 MUST 按字母顺序排列。
精确的本地值类型
严重性: SHOULD | 要求: TFNFR33
使用精确的类型(例如,年龄使用 number 而非 string)。
Terraform 配置要求
Terraform 版本要求
严重性: MUST | 要求: TFNFR25
terraform.tf 要求:
- MUST 仅包含一个
terraform块 - 第一行 MUST 定义
required_version - MUST 包含最低版本约束
- MUST 包含最高主版本约束
- SHOULD 使用
~> #.#或>= #.#.#, < #.#.#格式
示例:
terraform {
required_version = "~> 1.6"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
required_providers 中的提供程序
严重性: MUST | 要求: TFNFR26
terraform块 MUST 包含required_providers块- 每个提供程序 MUST 指定
source和version - 提供程序 SHOULD 按字母顺序排列
- 仅包含直接需要的提供程序
sourceMUST 采用namespace/name格式versionMUST 包含最低和最高主版本约束- SHOULD 使用
~> #.#或>= #.#.#, < #.#.#格式
测试要求
测试工具
严重性: MUST | 要求: TFNFR5
AVM 必需的测试工具:
- Terraform(
terraform validate/fmt/test) - terrafmt
- Checkov
- tflint(使用 azurerm 规则集)
- Go(可选,用于自定义测试)
测试提供程序配置
严重性: SHOULD | 要求: TFNFR36
为确保稳健测试,测试提供程序配置中 SHOULD 显式将 prevent_deletion_if_contains_resources 设为 false。
文档要求
模块文档生成
严重性: MUST | 要求: TFNFR2
- 文档 MUST 通过 Terraform Docs 自动生成
- 模块根目录 MUST 包含
.terraform-docs.yml文件
破坏性变更与功能管理
使用功能开关
严重性: MUST | 要求: TFNFR34
在次版本或补丁版本中新增的资源 MUST 配备一个开关变量,以避免默认创建:
variable "create_route_table" {
type = bool
default = false
nullable = false
}
resource "azurerm_route_table" "this" {
count = var.create_route_table ? 1 : 0
# ...
}
审查潜在的破坏性变更
严重性: MUST | 要求: TFNFR35
需要谨慎处理的破坏性变更:
资源块:
- 添加资源时未进行条件创建
- 添加非默认值的参数
- 添加未使用
dynamic的嵌套块 - 重命名资源时未添加
moved块 - 将
count改为for_each,或反之
变量/输出块:
- 删除/重命名变量
- 更改变量
type - 更改变量
default值 - 将
nullable改为 false - 将
sensitive从 false 改为 true - 添加未设置
default的变量 - 删除输出
- 更改输出
value - 更改输出
sensitive值
贡献标准
GitHub 仓库分支保护
严重性: MUST | 要求: TFNFR3
模块所有者 必须 在默认分支(通常为 main)上设置分支保护策略:
- 合并前必须提交 Pull Request
- 必须批准最近一次可审查的推送
- 推送新提交时,取消过时的 PR 批准
- 要求线性历史
- 禁止强制推送
- 不允许删除分支
- 要求 CODEOWNERS 审查
- 不允许绕过设置
- 对管理员强制执行
合规性检查清单
开发或审查 Azure Verified Modules 时,请使用此检查清单:
模块结构
- 模块交叉引用使用带固定版本的注册表源
- Azure 提供程序(azurerm/azapi)版本符合 AVM 要求
- 模块根目录中存在
.terraform-docs.yml - 存在 CODEOWNERS 文件
代码风格
- 所有名称均使用小写蛇形命名法
- 资源按依赖项优先的顺序排列
-
for_each使用具有静态键的map()或set() - resource/data/module 块遵循正确的内部排序
-
ignore_changes不加引号 - 条件嵌套对象使用动态块
- 默认值使用
coalesce()或try()
变量
- 不含
enabled或module_depends_on变量 - 变量排序:必需变量(按字母顺序),然后是可选变量(按字母顺序)
- 所有变量均具有精确类型(避免使用
any) - 所有变量均有说明
- 集合设置了
nullable = false - 不声明
sensitive = false - 敏感输入不设置默认值
- 弃用的变量移至
deprecated_variables.tf
输出
- 输出采用防腐层模式(离散属性)
- 敏感输出标记为
sensitive = true - 弃用的输出移至
deprecated_outputs.tf
Terraform 配置
-
terraform.tf中包含版本约束(~>格式) - 存在包含所有提供程序的
required_providers块 - 模块中不声明
provider(别名除外) - Locals 按字母顺序排列
测试与质量
- 配置了必需的测试工具
- 新资源具有功能开关
- 已审查并记录破坏性变更
汇总统计
- 功能性要求: 3
- 非功能性要求: 34
- 要求总数: 37
按严重性分类
- MUST: 21 项要求
- SHOULD: 14 项要求
- MAY: 2 项要求
依据:Azure Verified Modules - Terraform Requirements