- 测试
- 开发工具
- DevOps
- 质量保障
【免费下载链接】terratest
Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.
本篇指南以仓库中的 terraform-ssh-example 示例模块 为主线,讲解如何用 Terratest 为部署了 EC2 实例(含公网/私网两种拓扑)的 Terraform 代码编写端到端自动化测试。读完本文,你将掌握:模块的资源构成与参数含义、手动terraform apply的运行流程,以及测试如何分阶段完成「部署 → SSH 验证 → 清理」,并理解ssh、aws、terraform、teststructure等核心模块在测试链路中的底层实现。
示例模块概览:它部署了什么
该模块是一个刻意保持精简的 AWS Terraform 模块,用于演示如何用 Terratest 为 AWS Terraform 代码编写自动化测试。它会在aws_region变量指定的区域部署两台 EC2 实例:
- 一台拥有公网 IP(
associate_public_ip_address = true),可直接从公网 SSH 访问; - 一台只有私网 IP(
associate_public_ip_address = false),只能从 VPC 内部访问,测试中通过公网实例作为跳板机(jump host / bastion host)访问它; - 两台实例都允许 SSH 请求,监听端口由
ssh_port变量控制(默认 22)。
模块对应的自动化测试位于 test/terraform_ssh_example_test.go,它是本文分析的核心。原文档也提示:该示例仍然相当简化,EC2 实例本身并不做什么实际工作;若需要更接近真实世界的完整示例(Terraform 模块 + Web 服务器),可参考 terraform-packer-example。
核心资源定义(main.tf)
在 main.tf 中可以看到完整的资源定义:
- provider 与版本约束(main.tf):要求 Terraform 版本
>= 1.0,AWS Provider 版本~> 5.0; - 公网实例
aws_instance.example_public(main.tf):使用最新 Ubuntu AMI,associate_public_ip_address = true,绑定安全组与 Key Pair; - 私网实例
aws_instance.example_private(main.tf):结构相同,但associate_public_ip_address = false,因此只能从 VPC 内部访问; - 安全组
aws_security_group.example(main.tf):开放 egress 全部流量,ingress 仅允许var.ssh_port端口的 TCP 请求。代码注释特别提醒:示例为了简单放行了0.0.0.0/0的全部来源 IP,真实场景应只允许可信来源(如堡垒机或 VPN); - Ubuntu AMI 数据源
data.aws_ami.ubuntu(main.tf):指定 Canonical(owner099720109477),并通过 virtualization-type、architecture、image-type 与 name 等过滤器锁定ubuntu-jammy-22.04-amd64-server-*系列镜像,most_recent = true取最新版本。
参数与输出
模块的输入参数定义在 variables.tf,如下表所示:
| 变量 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
key_pair_name | string | 是(必填) | — | 关联到 EC2 实例、用于 SSH 访问的 EC2 Key Pair 名称 |
aws_region | string | 否 | us-east-1 | 部署的 AWS 区域 |
instance_name | string | 否 | terratest-example | EC2 实例的 Name 标签(同时用作安全组名) |
ssh_port | number | 否 | 22 | EC2 实例监听 SSH 请求的端口 |
instance_type | string | 否 | t2.micro | 运行的 EC2 实例类型 |
其中key_pair_name是唯一必填项,其余都有合理默认值,方便快速上手。
模块的输出定义在 outputs.tf,共 4 项,供测试脚本通过terraform output读取:
public_instance_id/public_instance_ip:公网实例的 ID 与公网 IP;private_instance_id/private_instance_ip:私网实例的 ID 与私网 IP。
手动运行模块
不写测试、直接手动验证时,按以下步骤操作:
- 注册 AWS 账号;
- 按 AWS CLI 支持的任一方式配置凭证(如设置
AWS_ACCESS_KEY_ID与AWS_SECRET_ACCESS_KEY环境变量);如果使用~/.aws/config中的 profile,还需导出AWS_SDK_LOAD_CONFIG为"True"; - 安装 Terraform 并将其加入
PATH; - 在 examples/terraform-ssh-example 目录下执行
terraform init; - 执行
terraform apply(会提示输入key_pair_name,或通过-var传入); - 验证完成后执行
terraform destroy清理资源。
注意:由于key_pair_name是必填变量,手动 apply 时一般需要提前在 AWS 控制台或 CLI 中创建好 Key Pair,再通过-var key_pair_name=...传入。
编写自动化测试:TestTerraformSshExample 全流程
自动化测试的全部代码在 test/terraform_ssh_example_test.go。它演示了 Terratest 测试的三种典型能力:Terraform 生命周期管理、SSH/SCP 远程验证、按阶段组织测试以便本地快速迭代。
主测试:三个阶段
测试入口TestTerraformSshExample(test/terraform_ssh_example_test.go)以t.Parallel()开头(可与其他测试并行),整体被拆成三个阶段:
- teardown(清理):通过
defer注册,无论测试成功失败都会执行 —— 先运行terraform destroy销毁资源,再删除测试期间创建的 EC2 Key Pair; - setup(部署):构造 Terraform 配置并执行
terraform init+terraform apply; - validate(验证):对公网/私网实例执行 SSH、SSH Agent、SCP 六类验证。
阶段化的价值在于:只要设置SKIP_teardown=true这类环境变量,就能跳过某个阶段,避免本地反复运行时反复销毁/重建资源,从而大幅缩短迭代周期。这正是 Terratest 的 teststructure 模块 提供的RunTestStage能力(modules/teststructure/teststructure.go):它检查SKIP_<stageName>环境变量是否被设置,未设置才执行该阶段。
setup:构造 Terraform Options 与 Key Pair
configureTerraformOptions(test/terraform_ssh_example_test.go)集中展示了 Terratest 的测试习惯:
- 用
random.UniqueID()生成唯一 ID,拼进实例名与 Key Pair 名,避免与账号内已有资源、并行测试互相冲突; - 用
aws.GetRandomStableRegionContext(modules/aws/region.go)随机但稳定地挑选测试区域,提升代码在不同区域的兼容性验证; - 用
aws.GetRecommendedInstanceTypeContext(modules/aws/ec2.go)按所选区域推荐可用的实例类型(候选:t2.micro、t3.micro、t2.small、t3.small),规避某些区域缺少特定实例类型的问题; - 用
aws.CreateAndImportEC2KeyPairContext在测试区域内生成 2048 位 RSA 密钥对并导入 EC2(底层实现见 modules/aws/keypair.go,通过 EC2ImportKeyPairAPI 完成); - 用
terraform.WithDefaultRetryableErrors(modules/terraform/options.go)包装terraform.Options,为最常见的可重试错误自动重试; - 把
aws_region、instance_name、instance_type、key_pair_name通过Vars注入 Terraform。
随后 setup 阶段调用terraform.InitAndApplyContext(modules/terraform/apply.go)一次性完成init与apply,出错即测试失败;同时用teststructure.SaveTerraformOptions与aws.SaveEc2KeyPair把 Options 和 Key Pair 序列化保存(实现见 modules/teststructure/save_test_data.go 与 modules/aws/save_test_data.go),供后续阶段通过LoadTerraformOptions/LoadEc2KeyPair反序列化复用。
这里还有一个容易被忽视的细节:测试开头调用了teststructure.CopyTerraformFolderToTemp(t, "../", "examples/terraform-ssh-example")(test/terraform_ssh_example_test.go),把整个示例目录复制到随机临时目录后再运行 Terraform,避免并行测试互相覆盖.terraform工作目录和terraform.tfstate文件(modules/teststructure/teststructure.go)。
validate:对公网实例直接 SSH
testSSHToPublicHost(test/terraform_ssh_example_test.go)演示了最基础的远程验证:
- 通过
terraform.OutputContext(modules/terraform/output.go)读取public_instance_ip输出; - 构造
ssh.Host,指定主机名、SSH 用户名ubuntu(因为实例运行 Ubuntu AMI)与之前创建的 Key Pair; - 用
retry.DoWithRetryContext重试ssh.CheckSSHCommandContextE,执行echo -n 'Hello, World',并断言输出等于预期文本。由于实例启动需要一两分钟,测试配置了 30 次重试、每次间隔 5 秒; - 再执行一次
echo -n 'Hello, World' && exit 1,验证「命令非零退出」也能被正确捕获并视为错误。
Host结构体与命令执行底层实现在 modules/ssh/ssh.go:Host支持 Key Pair、SSH Agent、密码等多种认证方式(modules/ssh/ssh.go),CheckSSHCommandContextE内部会解析认证方法、建立 SSH 会话并执行命令(modules/ssh/ssh.go)。认证方法按「覆盖 Agent → 本地 Agent → Key Pair → 密码」的优先级拼接(modules/ssh/ssh.go),全部缺失时返回ErrNoAuthMethod。
validate:经跳板机 SSH 到私网实例
testSSHToPrivateHost(test/terraform_ssh_example_test.go)展示了 Terratest 的跳板机(bastion/jump host)能力:
- 私网实例的 IP 不通过 Terraform 输出获取,而是用
terraform.OutputContext取private_instance_id,再调用aws.GetPrivateIPOfEc2InstanceContext(modules/aws/ec2.go)按实例 ID 从 AWS 查询私网 IP,演示了「输出拿不到的元数据可以从 AWS API 补齐」; - 构造 publicHost 与 privateHost 两个
ssh.Host,调用ssh.CheckPrivateSSHConnectionContextE(modules/ssh/ssh.go)—— 该方法先连接公网主机,再通过该连接拨号到私网主机(JumpHost机制)执行命令并返回输出。
validate:SSH Agent 与 SCP 验证
testSSHAgentToPublicHost与testSSHAgentToPrivateHost(test/terraform_ssh_example_test.go)演示了在测试进程内模拟 SSH Agent:通过ssh.SSHAgentWithKeyPair(modules/ssh/agent.go)创建内存中的 Agent(监听 Unix socket,把密钥加入 keyring),并把OverrideSshAgent设进Host,从而验证「不直接使用私钥文件、改走 agent 转发」的场景。
testSCPToPublicHost(test/terraform_ssh_example_test.go)演示文件传输验证:先用ssh.SCPFileToContextE(modules/ssh/ssh.go)把内容Hello, World以0644权限上传到/tmp/test.txt,再用ssh.FetchContentsOfFileContextE(内部执行cat,modules/ssh/ssh.go)读取并比对内容。这组用例说明 Terratest 的 SSH 模块不只是「执行命令」,还覆盖了文件上传/下载类验证。
teardown:安全清理
teardown 阶段(test/terraform_ssh_example_test.go)执行terraform.DestroyContext(modules/terraform/destroy.go)销毁全部资源,再调用aws.DeleteEC2KeyPairContext(modules/aws/keypair.go)删除测试期间导入的 Key Pair,保证账号内不遗留测试残留。
运行自动化测试
README 给出了运行该测试的步骤:
- 注册 AWS 账号;
- 配置 AWS 凭证(
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY,profile 场景下导出AWS_SDK_LOAD_CONFIG为"True"); - 安装 Terraform 并加入
PATH; - 安装 Golang,并确保本仓库代码已 checkout 到本地;
cd test;dep ensure(README 沿用早期 dep 时代的写法;当前仓库已迁移到 Go Modules,仓库根目录维护有 go.mod 与 go.work,可直接在仓库根执行go mod download或go work sync等现代依赖管理命令);go test -v -run TestTerraformSshExample。
结合阶段化设计,本地迭代时还可以通过环境变量跳过阶段,例如SKIP_teardown=true go test -v -run TestTerraformSshExample,从而保留资源、只反复执行 setup 与 validate。
费用与注意事项
重要提醒(原文 WARNING):该模块及其自动化测试会在你的 AWS 账号中部署真实资源并产生费用。这些资源基本属于 AWS Free Tier 覆盖范围,如果免费额度尚未用完通常无需付费,但所有 AWS 费用完全由你自行负责。测试代码虽然会在 teardown 阶段自动销毁资源并删除 Key Pair,但建议运行前确认账号配置正确、区域选择合理,并在结束后检查是否有残留资源。
延伸阅读
- 本示例更复杂、更接近生产形态的姊妹示例:terraform-packer-example(Terraform + Packer + Web 服务器端到端);
- 测试中用到的基础设施校验与生命周期函数,可深入阅读 modules/terraform 与 modules/aws 对应源码;
- SSH/SCP/Agent 验证的完整 API 列表见 modules/ssh/ssh.go 与 modules/ssh/agent.go;
- 测试阶段化、临时目录复制与测试数据缓存的实现见 modules/teststructure。
- 测试
- 开发工具
- DevOps
- 质量保障
【免费下载链接】terratest
Terratest is a Go library that makes it easier to write automated tests for your infrastructure code.
相关推荐
Keep 集成 Okta 单点登录:从环境变量配置到 JWT/JWKS 校验的完整实战指南
Keep 集成 Okta 单点登录:从环境变量配置到 JWT/JWKS 校验的完整实战指南 本文以 Keep 开源 AIOps 告警管理平台为背景,系统讲解如何
测试开发工具DevOps质量保障用 Terratest 为 GCP Terraform 模块编写自动化测试:terraform-gcp-hello-world-example 实战解析
用 Terratest 为 GCP Terraform 模块编写自动化测试:terraform gcp hello world example 实战解析 本篇指
测试开发工具DevOps质量保障Terratest 入门实战:用 Go 为 Terraform 模块编写自动化测试(terraform-basic-example 全解析)
Terratest 入门实战:用 Go 为 Terraform 模块编写自动化测试(terraform basic example 全解析) Terratest
测试开发工具DevOps质量保障
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考