别再靠运气部署了:Terraform + Terratest 让基础设施也能自动化测试

简介: 别再靠运气部署了:Terraform + Terratest 让基础设施也能自动化测试

别再靠运气部署了:Terraform + Terratest 让基础设施也能自动化测试

一、基础设施代码,真的可以不测试吗?

不知道你有没有遇到过这样的情况。

运维人员写了一份 Terraform 配置,在本地执行 terraform plan,看了一眼,没有明显问题,于是直接执行 terraform apply。

几分钟后,云服务器创建成功,安全组也配置完成,看起来一切正常。

但真正的问题可能才刚刚开始。

  • ECS 创建了,但是安全组意外开放了不该开放的端口。
  • 数据库部署成功了,但是网络访问规则配置错误。
  • Terraform 代码修改了一个变量,结果影响了其他环境。
  • 模块本身没有问题,但是不同环境组合起来之后,出现了资源依赖冲突。

这些问题有一个共同点:Terraform 执行成功,不代表基础设施部署正确。

这其实和我们开发应用程序是一个道理。

写完 Python 代码,需要单元测试;开发完接口,需要接口测试;发布之前,还需要集成测试。

但很多团队在基础设施管理上,却经常采用另一套标准:

代码写完了,执行成功了,就认为没有问题。

我一直觉得,这是 IaC(Infrastructure as Code,基础设施即代码)落地过程中一个比较容易被忽略的问题。

Terraform 解决的是基础设施如何通过代码进行管理,而 Terratest 解决的是这些基础设施代码是否真正符合预期。

两者结合起来,才能让基础设施管理从简单的自动化部署,进一步走向自动化验证。

今天我们就从零开始,搭建一套 Terraform + Terratest 的基础设施测试工程。

二、先搞清楚:Terraform 和 Terratest 分别负责什么?

在正式动手之前,先把两个工具的职责分清楚。

Terraform:负责定义和创建基础设施。

比如我们需要创建一台云服务器、安全组、VPC 网络,可以使用 Terraform 描述资源,而不是每次都登录云平台手动操作。

一个简单的 Terraform 配置:

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type

  tags = {
    Name = "echo-wish-web"
    Env  = "test"
  }
}

这段代码表达的意思很简单:创建一台 AWS EC2 实例,并设置实例类型、镜像以及资源标签。

Terraform 会根据配置文件生成执行计划,然后按照计划创建资源。

但这里有个问题。

Terraform 能够检查配置语法、资源参数和依赖关系,却不能仅凭一次成功的 apply 就证明业务层面的要求全部满足。

例如:

  • 服务器是否真的处于运行状态?
  • 安全组是否只开放了指定端口?
  • 创建出来的资源是否符合团队的命名规范?
  • 部署之后,应用端口是否真的能够访问?

这时候就需要 Terratest 了。

Terratest:负责验证基础设施是否符合预期。

它是一个使用 Go 语言编写的基础设施测试框架,可以通过代码调用 Terraform,部署真实资源,然后检查部署结果。

例如:

func TestTerraformEC2(t *testing.T) {
    terraformOptions := &terraform.Options{
        TerraformDir: "../terraform",
    }

    defer terraform.Destroy(t, terraformOptions)

    terraform.InitAndApply(t, terraformOptions)

    instanceID := terraform.Output(
        t,
        terraformOptions,
        "instance_id",
    )

    assert.NotEmpty(t, instanceID)
}

这段代码做了几件事:

  1. 指定 Terraform 项目目录。
  2. 初始化 Terraform。
  3. 执行部署。
  4. 获取实例 ID。
  5. 验证实例 ID 不为空。
  6. 测试结束后销毁资源。

这样一来,我们就不需要每次部署完成后,再手动登录云平台检查资源了。

不过需要注意:Terratest 并不是只测试 Terraform 文件是否能够执行,它更重要的价值在于验证最终创建出来的基础设施。

三、从零搭建项目:先把目录结构设计好

一个能够长期维护的 IaC 项目,不能所有配置和测试代码都堆在一起。

建议采用下面这种目录结构:

terraform-terratest/
│
├── terraform/
│   ├── main.tf
│   ├── variables.tf
│   ├── outputs.tf
│   └── versions.tf
│
├── test/
│   ├── terraform_test.go
│   └── go.mod
│
├── environments/
│   ├── dev.tfvars
│   └── prod.tfvars
│
└── README.md

各目录的职责:

目录 主要作用
terraform Terraform 基础设施定义
test Terratest 自动化测试代码
environments 不同环境的参数配置
README.md 项目说明和执行方法

这里有一个建议:

不要把开发环境和生产环境的配置完全复制两份。

例如,开发环境使用较小规格的实例,生产环境使用更高规格的实例,这种差异完全可以通过变量控制。

variables.tf:

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"
}

variable "ami_id" {
  description = "EC2 AMI ID"
  type        = string
}

开发环境:

dev.tfvars

instance_type = "t3.micro"
ami_id        = "ami-xxxxxxxx"

生产环境:

prod.tfvars

instance_type = "t3.medium"
ami_id        = "ami-xxxxxxxx"

这样做的好处是,基础设施逻辑保持一致,环境差异通过配置管理。

当然,实际项目还需要考虑不同环境的账号、网络、权限以及状态文件隔离,不能简单地认为换一个 tfvars 就完成了生产环境隔离。

四、编写 Terraform:让基础设施配置具备可验证性

接下来创建 Terraform 资源。

这里以 AWS EC2 为例。

main.tf:

resource "aws_instance" "web" {
  ami           = var.ami_id
  instance_type = var.instance_type

  vpc_security_group_ids = [
    aws_security_group.web.id
  ]

  tags = {
    Name = "echo-wish-web"
    Env  = "test"
  }
}

resource "aws_security_group" "web" {
  name = "echo-wish-web-sg"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

这里创建了一台 EC2,并且配置了一个安全组。

需要特别注意,入站规则只允许指定内网网段访问 80 端口。

这就是基础设施代码的一个重要价值:安全策略不再依赖运维人员手动配置,而是直接体现在代码里。

然后定义输出:

outputs.tf

output "instance_id" {
  value = aws_instance.web.id
}

output "security_group_id" {
  value = aws_security_group.web.id
}

output "instance_type" {
  value = aws_instance.web.instance_type
}

为什么要定义这些输出?

因为 Terratest 后续需要读取 Terraform 部署结果。

如果没有合理设计输出,测试代码就很难方便地获取资源信息。

我的建议是:

Terraform 不仅要考虑如何创建资源,还要考虑后续如何验证资源。

这两件事情最好在项目设计阶段就一起考虑。

五、重点来了:使用 Terratest 编写自动化测试

前面的工作完成之后,就可以开始编写测试代码了。

首先初始化 Go 项目:

cd test

go mod init terraform-terratest

go get github.com/gruntwork-io/terratest/modules/terraform
go get github.com/stretchr/testify/assert

接下来创建 terraform_test.go。

1. 验证基础设施是否成功创建

package test

import (
    "testing"

    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestTerraformEC2(t *testing.T) {

    terraformOptions := &terraform.Options{
        TerraformDir: "../terraform",

        Vars: map[string]interface{}{
            "ami_id":        "ami-xxxxxxxx",
            "instance_type": "t3.micro",
        },
    }

    defer terraform.Destroy(t, terraformOptions)

    terraform.InitAndApply(t, terraformOptions)

    instanceID := terraform.Output(
        t,
        terraformOptions,
        "instance_id",
    )

    assert.NotEmpty(t, instanceID)
}

运行测试:

go test -v

执行之后,Terratest 会自动完成初始化、部署和验证。

测试结束时,通过 defer 执行资源销毁。

这里有一个容易忽略的细节:

defer 只有在测试正常返回或者发生常规测试失败时才会按预期执行,进程被强制终止、机器断电等情况仍可能留下云资源。

所以正式项目还需要配合资源标签、成本监控以及定期清理机制。

2. 验证实例规格是否正确

资源创建成功只是第一步。

假设我们要求开发环境必须使用 t3.micro,那么就应该把这个要求写进测试代码。

instanceType := terraform.Output(
    t,
    terraformOptions,
    "instance_type",
)

assert.Equal(
    t,
    "t3.micro",
    instanceType,
)

这样一来,即使有人误将开发环境实例规格改成 t3.large,测试也能够发现问题。

这就是自动化测试的意义。

不是等到资源部署之后再发现配置错误,而是在发布流程中提前拦截。

3. 验证安全组配置

对于运维来说,安全组往往比服务器规格更加重要。

比如我们要求 Web 服务器只允许指定内网网段访问 80 端口。

如果有人误操作,将入站规则改成:

cidr_blocks = ["0.0.0.0/0"]

那么就可能造成服务暴露到公网。

我们可以使用 Terratest 获取安全组 ID,再调用 AWS API 验证入站规则。

示例:

import (
    "testing"

    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/gruntwork-io/terratest/modules/aws"
    "github.com/stretchr/testify/assert"
)

func TestSecurityGroup(t *testing.T) {

    options := &terraform.Options{
        TerraformDir: "../terraform",
    }

    defer terraform.Destroy(t, options)

    terraform.InitAndApply(t, options)

    sgID := terraform.Output(
        t,
        options,
        "security_group_id",
    )

    region := "ap-southeast-1"

    rules := aws.GetSecurityGroupIngressRules(
        t,
        region,
        sgID,
    )

    assert.NotEmpty(t, rules)
}

这里先获取安全组入站规则,再进行断言。

需要说明的是,这只是获取规则的基础示例,真正的安全测试还需要遍历规则,逐项检查协议、端口范围和 CIDR,不能只判断规则数量不为空。

例如,可以把以下要求作为测试标准:

  • 80 端口只能允许指定网段访问。
  • 不允许出现 0.0.0.0/0 的管理端口规则。
  • 不允许意外开放 SSH 或 RDP。
  • 不允许出现超出业务需求的入站规则。

这一步非常重要。

因为很多基础设施故障,并不是资源没有创建成功,而是创建出来的资源不符合安全要求。

六、不要只测资源创建,还要测试真实运行结果

这里我想强调一个观点:

基础设施测试不能停留在 Terraform Output 不为空这个层面。

例如,EC2 实例创建成功,实例 ID 也正常返回,但这并不代表应用真的能够正常访问。

真正有价值的测试,应该尽可能接近真实业务场景。

假设我们在 EC2 上部署了一个 Web 服务,那么可以进一步验证 HTTP 接口是否正常。

Terratest 提供了 HTTP 请求相关模块。

示例:

import (
    "testing"

    "github.com/gruntwork-io/terratest/modules/http-helper"
)

func TestWebServer(t *testing.T) {

    url := "http://10.0.1.10/health"

    http_helper.HttpGetWithRetry(
        t,
        url,
        nil,
        200,
        "OK",
        10,
        5,
    )
}

这段代码会多次请求健康检查接口,直到满足预期或者重试次数耗尽。

实际项目中,URL 应该从 Terraform Output 或其他部署信息中动态获取,而不是像示例这样固定写死。

这样我们就形成了三个层次的测试:

测试层次 验证内容
配置验证 Terraform 配置是否合法
资源验证 云资源是否按预期创建
业务验证 服务是否真正能够正常运行

这三个层次缺一不可。

七、把测试接入 CI/CD,让每次变更都经过验证

前面已经实现了 Terraform 和 Terratest 的基本测试。

但如果每次都需要运维人员手动执行 go test,自动化程度依然不够。

更好的方式是将测试集成到 CI/CD 流水线。

例如使用 GitHub Actions:

name: Terraform Test

on:
  pull_request:
    branches:
      - main

jobs:
  terraform-test:

    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "1.23"

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Download Dependencies
        working-directory: test
        run: go mod download

      - name: Run Tests
        working-directory: test
        run: go test -v -timeout 30m

这样,当开发人员提交 Terraform 代码并创建 Pull Request 时,就可以自动触发测试。

不过,这里还需要注意几个生产实践问题:

  1. AWS 凭证应该通过 GitHub Secrets 或 OIDC 等安全方式提供,不要直接写进代码。
  2. 测试环境应该使用独立账号或者隔离的测试网络。
  3. 测试资源应该统一添加项目、环境、负责人等标签。
  4. 应当设置测试超时和资源清理策略。
  5. 对于生产环境,应该先执行 Plan、审查变更,再通过受控流程实施 Apply。

尤其是生产环境,不建议在普通 Pull Request 测试中直接创建或修改生产资源。

测试环境负责验证,生产环境负责受控发布,这个边界必须明确。

八、真正落地时,我建议建立四道防线

如果只是搭建一个 Demo,前面的内容已经足够入门。

但如果准备把 Terraform + Terratest 用到企业运维体系中,我认为还需要进一步完善。

可以将基础设施测试分成四道防线。

第一道:静态检查。

不需要真正创建云资源,就能提前发现很多问题。

例如:

terraform fmt -check
terraform validate

还可以增加 TFLint 和 Checkov,分别辅助检查 Terraform 配置问题以及基础设施安全策略。

第二道:Plan 检查。

terraform plan \
  -var-file="dev.tfvars" \
  -out=tfplan

重点关注资源新增、修改和销毁情况。

尤其要注意一些看似普通的代码修改,是否意外导致数据库、网络或者存储资源被替换。

第三道:Terratest 集成测试。

真正创建测试资源,验证:

  • 资源规格是否正确。
  • 网络和安全组是否符合要求。
  • 资源之间的依赖关系是否正常。
  • 关键输出是否符合预期。

第四道:业务可用性测试。

基础设施创建完成之后,再验证服务是否能够正常访问。

例如 HTTP 健康检查、数据库连接、端口连通性以及关键业务接口。

四道防线组合起来,才算形成比较完整的 IaC 质量保障体系。

当然,不是每次代码提交都必须执行全部测试。静态检查可以高频执行,真实云资源集成测试可以根据变更范围、环境和成本安排执行频率。

九、最后聊聊:基础设施代码化之后,为什么测试反而更重要?

写到这里,我想聊一个实际问题。

很多团队刚开始使用 Terraform,最大的兴奋点就是部署速度变快了。

以前创建一套测试环境,可能需要运维人员手动配置网络、安全组、服务器和数据库。

现在只需要执行几条命令,就能完成整个过程。

效率确实提高了。

但我们也应该看到另外一面:

以前手动操作,一个人可能只会影响一台服务器;现在一段 Terraform 代码,可能同时影响几十台服务器、多个网络环境,甚至整个云账号。

自动化提高了效率,也放大了错误的影响范围。

如果代码没有经过充分验证,那么部署越快,错误扩散得可能也越快。

这也是我为什么认为 Terratest 值得引入的原因。

它不是为了让运维人员多写几百行 Go 代码,也不是为了追求测试覆盖率这个数字。

它真正解决的是一个问题:

我们能不能在基础设施变更真正影响业务之前,就知道这次修改是否安全?

举个例子。

某位同事修改了 Terraform 安全组配置,本来只是想开放一个新的业务端口,结果不小心把 SSH 管理端口也开放给了所有公网 IP。

如果没有测试,这个问题可能要等到安全审计甚至事故发生之后才能发现。

如果提前编写了安全规则测试,那么代码提交时就能够发现异常,直接阻止变更进入后续发布流程。

这才是自动化测试真正的价值。

另外,我也不建议一开始就追求复杂的测试体系。

如果团队之前没有使用过 Terratest,可以从最简单的几个场景开始:

  • 验证服务器是否正常创建。
  • 验证实例规格是否符合环境要求。
  • 验证安全组是否符合预期。
  • 验证服务健康检查是否正常。

先把这些最容易出问题的地方覆盖起来,再逐步增加网络、数据库、负载均衡、自动扩缩容等测试场景。

不要为了测试而测试,更不要让测试体系本身变成新的维护负担。

Terraform 让基础设施变得可以复制,Terratest 让基础设施变得可以验证。

在我看来,真正成熟的 IaC 工程,不应该只是做到一键部署,而应该做到每一次变更都有依据、每一次部署都能验证、每一次失败都能追溯。

毕竟,运维工作的目标从来不是把资源创建出来就结束了。

让系统稳定运行,让变更风险可控,才是真正值得投入精力的地方。

作者:Echo_Wish

专注 Python、AI、DevOps、云原生与自动化运维实践。

目录
相关文章
|
4天前
|
JavaScript API iOS开发
DeepSeek Harness下载、安装、配置一篇搞定(2026整理,送常用插件)
DeepSeek Harness(DSH)是DeepSeek于2026年开源的MIT协议AI Agent框架,支持插件化组装模型、工具、沙箱与UI,兼容主流大模型。本文提供免Node.js的桌面端安装包(Win/macOS),含详细图文教程及热门插件推荐。
|
云安全 安全 网络安全
|
5天前
|
iOS开发 Docker MacOS
【2026实测】Docker Desktop下载、安装、汉化、配置教程(附官网安装包)
Docker Desktop 是 Docker 官方推出的桌面容器管理工具,集成引擎、CLI、图形界面与 Kubernetes,支持 Win/macOS/Linux。2026 最新版 4.88.1 强化 ARM 原生运行、镜像加速及资源可视化,附中文汉化与国内镜像配置指南。
861 1
|
17天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
2979 49
|
11天前
|
JavaScript 安全 前端开发
Nodejs Brotli decoder 代码执行漏洞 (CVE-2026-111497)10629
Node.js发布安全更新修复Brotli解码器高危RCE漏洞(CVE-2026-111497),建议受影响版本用户立即升级。
2100 2
|
5天前
|
安全 算法 定位技术
《高性价比人生指南》完整原版:普通人用最少的资源,过好这一生的底层逻辑~How To Live Better
📖《高性价比人生指南》是一份基于实证、拒绝焦虑的生存手册,倡导以“人生时薪”为视角,重新计算时间、金钱与精力的成本与收益。它不承诺逆袭,只教人算清账:省下的钱若耗掉时间便亏;买体验比买物品更值;睡眠是最高杠杆的健康投资;关系贵在情绪价值而非表面热络;学习重在改变行为而非自我安慰。核心理念:用低成本、高回报的微小行动,换取长期的选择权——不是成为谁,而是掌控自己人生的主动权。
|
10天前
|
人工智能 JSON Linux
【保姆级】ComfyUI部署教程(下载+安装+配置+工作流,2026最新)
ComfyUI 是一款开源节点式AI生成工具,支持图像、视频、音频及3D内容创作。通过拖拽连线组合模型加载、提示词、采样等节点,构建可复用、易修改的工作流,兼容SD1.5、SDXL、Flux、Qwen Image等主流模型。
|
4天前
|
Linux 开发工具 iOS开发
Codex保姆级教程(下载+安装+登录+使用,含所有版本,2026最新)
Codex是OpenAI推出的开源AI编程智能体,本地运行,可理解整个代码库,支持自然语言指令改代码、执行命令、跑测试、修Bug,兼容Win/macOS/Linux。(239字)