Language:Chinese VersionEnglish Version

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 角色所带来的后果,至少与生产代码中的错误一样严重。

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *

You missed