2026年的基础设施即代码测试:Terratest、Checkov与策略即代码革命
基础设施即代码本应让基础设施变得可靠且可重复。它确实能做到——但前提是你需要测试它。然而多年来,”测试IaC”意味着手动运行terraform plan并人工检查输出。到2026年,基础设施代码测试工具已经发展成为一个完整的学科:静态分析、单元测试、集成测试和策略执行,每种都有适合自然融入CI/CD流程的专业工具。本指南涵盖了每个成熟的IaC测试策略所需的三层结构,并展示了如何在不降低开发速度的情况下实现它们。
为什么IaC测试与应用测试不同
测试应用代码相对容易理解。你使用已知输入调用函数,然后断言输出结果。测试基础设施代码则因几个结构性原因而更加困难。
基础设施测试通常需要配置真实的(或模拟的)云资源,这既耗时又花钱。针对AWS的完整端到端Terraform测试可能需要10-20分钟,并配置数十个资源。基础设施”输出”不是返回值——它们是云环境的状态,必须通过提供商API查询。关键的是,基础设施错误通常表现为功能测试无法发现的安全配置错误:一个工作正常但可公开读取的S3存储桶,一个按预期路由流量但向互联网暴露了意外端口的安全组。
这些特性推动IaC测试采用分层策略,每层以不同成本捕捉不同的故障模式。
第一层:使用Checkov进行静态分析
最便宜且最快的层是静态分析——在不执行IaC文件的情况下解析它们。来自Bridgecrew(现为Palo Alto Networks)的Checkov是Terraform、CloudFormation、Kubernetes清单、Helm图表、Dockerfile和ARM模板最全面的开源静态分析工具。
# 安装Checkov
pip install checkov
# 扫描Terraform目录
checkov -d ./terraform/modules/networking
# 启用/禁用特定检查进行扫描
checkov -d ./terraform
--check CKV_AWS_18,CKV_AWS_21 # 仅启用这些检查
--skip-check CKV_AWS_8 # 跳过此检查
# 输出SARIF格式以集成GitHub代码扫描
checkov -d ./terraform -o sarif > checkov-results.sarif
Checkov 提供了超过 1,000 个内置检查,覆盖多个云服务提供商。对于 AWS Terraform,它会标记常见错误配置,如未加密的 EBS 卷、具有公共 ACL 的 S3 存储桶、没有删除保护的 RDS 实例、没有保留并发的 Lambda 函数以及具有通配符操作的 IAM 策略。大多数检查在几秒钟内完成,使 Checkov 成为 pre-commit hook 的自然选择,可以在有问题的 IaC 到达 PR 之前就阻止它。
使用 Python 自定义检查
内置检查涵盖了公认的最佳实践,但每个组织都有内部标准,这些标准是 Checkov 的默认规则集无法覆盖的。自定义检查是扩展 Checkov 基础检查类型的 Python 类:
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories
class RequireTagOwner(BaseResourceCheck):
def __init__(self):
name = "确保所有 AWS 资源都有 'owner' 标签"
id = "CKV_CUSTOM_1"
supported_resources = ["aws_*"] # 匹配所有 AWS 资源
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
tags = conf.get("tags", [{}])
if isinstance(tags, list):
tags = tags[0] if tags else {}
if "owner" in tags:
return CheckResult.PASSED
return CheckResult.FAILED
scanner = RequireTagOwner()
# 从目录加载自定义检查
checkov -d ./terraform --external-checks-dir ./custom-checks
这种模式让平台团队能将组织策略编码为代码,并通过共享检查仓库分发它们。每个团队的 CI 管道都会自动加载共享检查——在不要求人工审查每个 PR 的情况下强制执行一致的标准。
第二层:使用 OPA 和 Sentinel 的策略即代码
静态分析检查是针对 IaC 文件语法的二进制通过/失败规则。策略即代码工具在更高层次上运行:它们评估基础设施变更的语义意图,通常可以访问完整的 Terraform 计划输出(包括静态分析无法看到的计算值和插值引用)。
使用 Conftest 的 Open Policy Agent (OPA)
OPA 的 Rego 语言允许你编写查询结构化数据的策略 — 而 Terraform plan JSON 就是结构化数据。conftest 工具可以在单个命令中将 Rego 策略应用于配置文件和计划输出。
# 将 Terraform 计划转换为 JSON 以进行策略评估
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
# 使用 conftest 应用 OPA 策略
conftest test tfplan.json --policy ./policies/
# policies/deny_public_s3.rego
package main
deny[msg] {
resource := input.planned_values.root_module.resources[_]
resource.type == "aws_s3_bucket"
resource.values.acl == "public-read"
msg := sprintf("S3 存储桶 '%v' 具有 public-read ACL — 生产环境中不允许",
[resource.address])
}
deny[msg] {
change := input.resource_changes[_]
change.type == "aws_security_group_rule"
change.change.after.cidr_blocks[_] == "0.0.0.0/0"
change.change.after.from_port == 22
msg := sprintf("安全组规则 '%v' 允许来自 0.0.0.0/0 的 SSH",
[change.address])
}
因为 Rego 评估完整的计划 JSON — 包括计算的资源 ID、插值资源和跨资源引用 — 它可以捕获静态分析无法表达的策略。例如,你可以编写一个策略,拒绝任何允许入站端口 22 到任何具有公共 IP 地址的实例的安全组规则,在单个策略中关联多个资源的数据。
HashiCorp Sentinel (Terraform Enterprise/Cloud)
使用 Terraform Cloud 或 Enterprise 的团队可以访问 Sentinel,这是 HashiCorp 的策略即代码框架。Sentinel 策略作为 Terraform 运行工作流中的一等关卡运行 — 在 plan 之后,apply 之前 — 并通过专用模块原生访问计划、状态和配置。
# Sentinel 策略:将实例类型限制为批准的大小
import "tfplan/v2" as tfplan
allowed_instance_types = ["t3.micro", "t3.small", "t3.medium", "t3.large"]
main = rule {
all tfplan.resource_changes as _, rc {
rc.type is not "aws_instance" or
rc.change.after.instance_type in allowed_instance_types
}
}
Sentinel 与 Terraform 的运行管道集成,无需对 Terraform 代码本身进行任何更改 — 策略在工作区或组织级别配置,并自动应用于每次运行。
第 3 层:使用 Terratest 进行集成测试
静态分析和策略检查可以在部署前捕获错误配置。但它们无法验证基础设施在部署后是否真正有效。VPC 是否允许预期的流量?RDS 集群是否接受来自应用程序子网的连接?ECS 服务是否健康启动并通过其负载均衡器健康检查?这些问题需要真实的基础设施,而 Terratest 可以回答这些问题。
Terratest 是一个 Go 测试库(来自 Gruntwork),它可以配置真实的基础设施,对其运行断言,然后进行清理——所有这些都作为标准的 Go 测试函数进行编排。
// vpc_test.go — 测试 VPC 模块是否创建预期的子网
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/stretchr/testify/assert"
)
func TestVPCModule(t *testing.T) {
t.Parallel()
terraformOptions := &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"vpc_cidr": "10.0.0.0/16",
"availability_zones": []string{"us-east-1a", "us-east-1b"},
"environment": "test",
},
}
// 确保即使测试失败也能清理
defer terraform.Destroy(t, terraformOptions)
// 应用模块
terraform.InitAndApply(t, terraformOptions)
// 对输出进行断言
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
// 直接查询 AWS 以验证子网数量
subnets := aws.GetSubnetsForVpc(t, vpcID, "us-east-1")
assert.Equal(t, 4, len(subnets)) // 2 个公共 + 2 个私有
// 验证没有子网自动分配公共 IP(安全要求)
for _, subnet := range subnets {
assert.False(t, aws.IsPublicSubnet(t, subnet.SubnetId, "us-east-1"),
"子网 %s 不应自动分配公共 IP", subnet.SubnetId)
}
}
# 运行 Terratest 测试(标准 Go 测试运行器)
cd test/
go test -v -run TestVPCModule -timeout 30m
管理 Terratest 的成本和速度
Terratest 的主要挑战在于成本和速度。针对复杂模块的完整端到端测试套件可能需要 30-60 分钟,每次运行成本高达数十美元。有几种策略可以控制这些成本:
使用 t.Parallel() 进行并行化:Terratest 测试使用唯一的资源后缀(通常来自随机十六进制字符串)来避免冲突,因此多个测试可以并行运行在同一个 AWS 账户上。
使用测试阶段:Terratest 的 stage 包允许你在开发过程中跳过配置或销毁阶段,这样你就可以对已配置的基础设施进行断言迭代,而不必每次都等待完整的应用周期。
分离快速和慢速测试:将类单元测试(验证输出、检查标签)与集成测试(验证网络连接、测试故障转移)分开标记。每次 PR 运行快速测试,仅在合并到主分支或按夜间计划运行慢速测试。
使用 LocalStack 进行 AWS 测试:LocalStack 提供了 AWS 服务的本地模拟环境。并非所有服务都完全支持,但对于 S3、SQS、DynamoDB 和 Lambda,LocalStack 让您可以在本地或 CI 中运行 Terratest 测试,无需 AWS 凭证或费用。
// 在测试配置中使用 LocalStack 端点
terraformOptions := &terraform.Options{
TerraformDir: "../modules/storage",
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": "us-east-1",
"AWS_ACCESS_KEY_ID": "test",
"AWS_SECRET_ACCESS_KEY": "test",
},
Vars: map[string]interface{}{
"localstack_endpoint": "http://localhost:4566",
},
}
将三层集成到 CI/CD 中
这三层在开发流程的不同阶段应用时效果最佳,每一层都充当一个逐渐深入且成本更高的检查点:
- 提交前检查:对变更的 Terraform 文件运行 Checkov。速度快(几秒钟),零云成本,在代码审查前捕获最常见的错误配置。
- PR 检查:运行
terraform plan,转换为 JSON,运行 Conftest/OPA 策略。中等成本(plan 是只读的),通过计算值的上下文捕获语义策略违规。 - 合并到主分支:在专用测试账户上运行 Terratest 集成测试。速度慢(几分钟到几小时),真实云成本,但验证实际行为。测试运行后自动销毁所有测试基础设施。
- 计划夜间运行:运行完整的合规测试套件,包括漂移检测(比较当前状态与预期状态)和安全态势检查。捕获通过手动控制台更改引入的配置漂移。
# .github/workflows/terraform-test.yml(节选)
jobs:
static-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
soft_fail: false
output_format: sarif
output_file_path: checkov.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: checkov.sarif
policy-check:
runs-on: ubuntu-latest
needs: static-analysis
steps:
- name: Terraform Plan
run: terraform plan -out=tfplan.binary && terraform show -json tfplan.binary > tfplan.json
- name: Conftest Policy Check
uses: instrumenta/conftest-action@v1
with:
files: tfplan.json
policy: policies/
合规即代码的前沿
除了捕获错误和配置问题外,代码即策略的方法还改变了组织处理合规性的方式。安全团队不再需要手动审查基础设施来进行周期性审计,而是将合规要求编码为机器可执行的策略,这些策略在每次变更时持续运行。
像 SOC 2、PCI-DSS 和 CIS 基准这样的框架已经被转换为 Checkov 检查集和 OPA 策略库。在 CI 中运行这些意味着每次基础设施变更都会验证您的合规状况,并且审计证据会作为测试工件自动生成——由 CI 系统签名,并在 git 中具有完整的历史记录。
结论
2026 年的基础设施即代码测试是一个具有明确工具层次结构的学科:Checkov 用于快速静态分析,OPA/Conftest 或 Sentinel 用于语义策略执行,Terratest 用于端到端集成验证。这些层中的任何一层单独都不够。静态分析可以捕获配置错误,但无法验证行为。策略检查可以强制执行组织标准,但无法测试基础设施是否真正正常工作。集成测试可以验证实际行为,但在每次提交时运行它们太慢且成本太高。正确实施这些方法的团队会在其管道的适当位置运行所有三层,将基础设施代码与应用程序代码同等严格对待——因为配置错误的 VPC 或权限过高的 IAM 角色所带来的后果,至少与生产代码中的错误一样严重。
