Terraform 测试策略 #
测试概述 #
测试是确保 Terraform 代码质量的重要环节。通过测试可以验证配置正确性、发现潜在问题、提高代码可靠性。
text
┌─────────────────────────────────────────────────────────────┐
│ 测试层次 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 静态分析 terraform validate, terraform fmt │
│ 计划测试 terraform plan 验证 │
│ 集成测试 实际部署并验证 │
│ 端到端测试 Terratest 等框架 │
│ │
└─────────────────────────────────────────────────────────────┘
静态分析 #
terraform validate #
bash
terraform init
terraform validate
输出:
text
Success! The configuration is valid.
terraform fmt #
bash
terraform fmt
terraform fmt -check
terraform fmt -recursive
terraform fmt -diff
pre-commit #
yaml
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.5
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
- id: terraform_tfsec
安装:
bash
pip install pre-commit
pre-commit install
TFLint #
bash
tflint
tflint --init
tflint --recursive
配置 .tflint.hcl:
hcl
plugin "terraform" {
enabled = true
preset = "recommended"
}
plugin "aws" {
enabled = true
version = "0.23.1"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_required_version" {
enabled = true
}
rule "terraform_required_providers" {
enabled = true
}
rule "terraform_naming_convention" {
enabled = true
}
tfsec #
bash
tfsec .
tfsec . --format json
tfsec . --format sarif > results.sarif
配置 .tfsec/config.yml:
yaml
minimum_severity: MEDIUM
exclude:
- AWS002
- AWS003
计划测试 #
基本计划测试 #
bash
terraform init
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
使用 OPA 验证计划 #
bash
opa exec --decision terraform/analysis/authz --bundle policies/ tfplan.json
策略文件 policies/terraform.rego:
rego
package terraform.analysis
import input as tfplan
deny[msg] {
some i
change := tfplan.resource_changes[i]
change.type == "aws_instance"
not change.change.after.associate_public_ip_address
msg := sprintf("Instance %s must have public IP", [change.address])
}
deny[msg] {
some i
change := tfplan.resource_changes[i]
change.type == "aws_s3_bucket"
not change.change.after.versioning[0].enabled
msg := sprintf("Bucket %s must have versioning enabled", [change.address])
}
使用 Conftest #
bash
conftest test tfplan.json
conftest test --policy ./policies tfplan.json
Terratest #
安装 #
bash
go mod init my-terraform-tests
go get github.com/gruntwork-io/terratest/modules/terraform
基本测试 #
go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVPCModule(t *testing.T) {
t.Parallel()
terraformOptions := &terraform.Options{
TerraformDir: "../examples/basic",
Vars: map[string]interface{}{
"vpc_cidr": "10.0.0.0/16",
"environment": "test",
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
subnetIds := terraform.OutputList(t, terraformOptions, "subnet_ids")
assert.Equal(t, 2, len(subnetIds))
}
验证 AWS 资源 #
go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestEC2Instance(t *testing.T) {
t.Parallel()
awsRegion := "us-east-1"
terraformOptions := &terraform.Options{
TerraformDir: "../examples/ec2",
Vars: map[string]interface{}{
"region": awsRegion,
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
instanceId := terraform.Output(t, terraformOptions, "instance_id")
instance := aws.GetEc2InstanceById(t, instanceId, awsRegion)
assert.Equal(t, "running", instance.State.Name)
assert.Equal(t, "t2.micro", *instance.InstanceType)
}
验证网络连接 #
go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/gruntwork-io/terratest/modules/http-helper"
"github.com/stretchr/testify/assert"
)
func TestWebServer(t *testing.T) {
t.Parallel()
terraformOptions := &terraform.Options{
TerraformDir: "../examples/web",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
url := terraform.Output(t, terraformOptions, "url")
http_helper.HttpGetWithRetry(
t,
url,
nil,
200,
"Hello, World!",
10,
30*time.Second,
)
}
测试组织 #
目录结构 #
text
project/
├── modules/
│ └── vpc/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── examples/
│ ├── basic/
│ │ └── main.tf
│ └── complete/
│ └── main.tf
└── test/
├── vpc_basic_test.go
└── vpc_complete_test.go
运行测试 #
bash
go test -v ./test/
go test -v -run TestVPCModule ./test/
go test -v -timeout 30m ./test/
CI/CD 集成 #
GitHub Actions #
yaml
name: Terraform Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Init
run: terraform init
- name: Terraform Validate
run: terraform validate
- name: Terraform Format
run: terraform fmt -check
- name: Run TFLint
uses: terraform-linters/setup-tflint@v3
with:
tflint_version: v0.47.0
- run: tflint --init
- run: tflint -f compact
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.0
terratest:
runs-on: ubuntu-latest
needs: validate
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Run Terratest
run: |
cd test
go mod init test
go get github.com/gruntwork-io/terratest/modules/terraform
go test -v -timeout 30m
最佳实践 #
1. 测试金字塔 #
text
┌─────────────────────────────────────────────────────────────┐
│ 测试金字塔 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 端到端测试(少量) │
│ └── 部署完整环境并验证 │
│ │
│ 集成测试(适量) │
│ └── 部署模块并验证资源 │
│ │
│ 单元测试(大量) │
│ └── 验证配置语法和计划 │
│ │
└─────────────────────────────────────────────────────────────┘
2. 测试隔离 #
go
func TestWithUniqueName(t *testing.T) {
t.Parallel()
uniqueID := random.UniqueId()
name := fmt.Sprintf("test-%s", uniqueID)
terraformOptions := &terraform.Options{
TerraformDir: "../examples/basic",
Vars: map[string]interface{}{
"name": name,
},
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
}
3. 清理资源 #
go
func TestWithCleanup(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/basic",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
}
下一步 #
掌握了测试策略后,接下来学习 CI/CD 集成,了解如何自动化 Terraform 工作流!
最后更新:2026-03-29