审校 | 孙淑娟
手动设置基础架构是费时又费力的过程。这时候我们可以利用基础架构即代码(IaC)工具来自动管理基础架构。IaC自动化可用于任何类型的基础架构:虚拟机和存储等。随着越来越多的基础架构变成代码,有必要为IaC进行单元测试和集成测试。
本文简要讨论了什么是IaC以及测试基础架构代码的意义,然后深入探讨了如何使用Terratest进行IaC测试。
一、基础架构即代码(IaC)
基础架构即代码是通过代码配置和设置环境的过程,而不是通过GUI手动创建所需的基础架构和支持系统。比如说,配置虚拟机、设置虚拟机并为其创建监控机制。Terraform、Packer和Ansible就是典型的IaC。借助基础架构即代码,您还可以将基础架构跟踪到Git等版本控制系统中,进行模块化和模板化,以便在多个环境和地区重用相同的代码。灾难恢复是从基础架构即代码获得的重要好处之一。有了IaC,您可以尽快在其他地区或环境复制基础架构。
二、测试基础架构代码
IaC测试可以分为多个阶段:
1.健全性或静态分析
2.单元测试
3.集成测试
- 健全性或静态分析
这是测试基础架构代码的初始阶段。在静态分析中,我们确保代码有正确的语法。它还有助于确保我们的代码符合行业标准,并遵循最佳实践。Linter属于这一类。几款典型的健全性测试工具包括面向Chef的foodcritic、面向Docker的hadolint和面向Terraform的tflint等。
- 单元测试
借助单元测试,我们不用实际配置基础架构即可评估代码。比如可以限制容器以便以非root用户身份运行,或者云网络安全组应该只有TCP协议。几个典型的单元测试是面向Terraform的Conftest和面向Chef Cookbooks的Chefspecs。
以非root用户身份执行的Conftest例子:
package main
deny[msg] {
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot
msg := "Containers must not run as root"
}
- 集成测试
在集成测试中,我们希望通过将IaC实际部署到所需的环境中对其进行测试。比如说,您部署了一个虚拟机,并在该机器的端口80上托管Nginx服务器。因此,您将在部署之后检查端口80是否在侦听。
以下是使用ServerSpec执行该操作的例子:
describe port(80) do
it { should be_listening }
end
我们在本文中介绍使用Terrratest对基础架构代码进行集成测试。
三、Terratest是什么?我们可以用它来做什么?
Terratest是由Gruntwork开发的Go库,可帮助您为使用Terraform或Packer编写的基础架构即代码创建和自动化测试。它为您提供了各种任务所需的函数和模式,比如:
测试Docker镜像、Helm图和Packer模板。
允许与各种云提供商API兼容,比如AWS和Azure。
Terratest为基础架构代码执行健全性和功能测试。有了Terratest,您可以轻松识别当前基础架构代码中的问题并尽快解决问题。我们还可以利用Terratest对基础架构进行合规测试,比如针对通过IaC创建的任何新S3存储桶启用版本控制和加密。
四、安装Terratest所需的二进制文件
Terratest主要需要Terraform和Go来执行。我们在这篇博文中使用了Terraform版本1.0.0 和Go版本1.17.6进行测试。
- 安装Terraform
按照Terraform网站的下载部分(https://www.terraform.io/downloads)在您的计算机上安装Terraform,您可以使用软件包管理器或下载二进制文件,并使其在PATH中可用。
安装后,通过运行以下命令验证是否已正确安装:
terraform version
Go & test依赖项安装可以通过以下步骤来完成:
- 安装Go
您可以使用Linux发行版的软件包管理器来安装Go,或者遵照Go的安装文档(https://go.dev/doc/install)。
- Go测试需要gcc来执行测试
go test命令可能需要gcc,您可以使用发行版的软件包管理器安装它。比如在CentOS/Amazon Linux 2上,您可以使用yum install -y gcc。
五、Terratest实战
现在,我们将使用Terratest执行一些集成测试。安装步骤完成后,克隆terratest-sample存储库,开始执行Terratest。我们将先使用Go编写测试并执行测试。
重要的事先说:
1.您的测试文件名称应包含_test,比如sample_test.go。这是Go查找测试文件的方式。
2.您的测试函数名称应以Test开头,其中T大写。比如说,TestFunction没有问题,但testFunction会给出错误“没有要运行的测试”。
- 设置AWS身份验证配置
我们需要AWS凭证在AWS中设置基础架构,可以使用环境变量或共享凭证文件进行配置。
基础架构的Terraform代码可以在组件的相应文件夹中找到。若是ec2,它位于ec2_instance下,若是API网关,它位于api_gateway文件夹下。Terratest将Terraform的output.tf的输出作为测试的输入。下面这个代码段用于测试我们是否在使用的ec2实例上有相同的ssh密钥。
package terratest
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/gruntwork-io/terratest/modules/terraform"
)
func TestEc2SshKey(t *testing.T) {
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../terraform",
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
ec2SshKey := terraform.Output(t, terraformOptions, "instance_ssh_key") assert.Equal(t, "terratest", ec2SshKey)
}
我们将把它分成不同的部分以便理解:第一步,我们定义一个名为Terratest的Go软件包,然后我们导入测试执行所需的不同软件包。
package terratest
import (
"testing"
"github.com/stretchr/testify/assert" "github.com/gruntwork-io/terratest/modules/terraform"
)
一旦我们满足了所有的先决条件,将创建一个函数来执行实际测试:
func TestEc2SshKey(t *testing.T) {
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../terraform",
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
ec2SshKey := terraform.Output(t, terraformOptions, "instance_ssh_key") assert.Equal(t, "terratest", ec2SshKey)
}
借助以下部分,我们定义了Terratest应该在其中查找Terraform清单文件(即main.tf和output.tf)的目录,以便创建基础架构。
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../terraform",
})
在Go中,我们使用defer方法来执行清理任务,它应该是terraform destroy。
我们使用下面的代码片段来定义它:
defer terraform.Destroy(t, terraformOptions)
现在我们可以继续实际执行了:
使用terraform.InitAndApply ,我们调用通常用于Terraform执行的Terraform函数terraform init和apply:
terraform.InitAndApply(t, terraformOptions)
如前所述,Terratest查找来自output.tf的输出,寻找变量定义。
在下面的代码片段中,我们从Terraform输出中获取ssh密钥,并与已定义的ssh密钥名称进行匹配:
ec2SshKey := terraform.Output(t, terraformOptions, "instance_ssh_key") assert.Equal(t, "terratest", ec2SshKey)
六、执行测试
将目录切换到已克隆存储库的位置。进入到测试文件所在的位置。
初始化Go模块,并下载依赖项。请查看Terratest文档的“设置项目”部分以获取更多详细信息。
go mod init ec2_instance
go mod tidy
最后执行测试:
$ go test –v
--- PASS: TestEc2SshKey (98.72s)
PASS
ok command-line-arguments 98.735s
七、不妨继续使用Terratest
在上一节中,我们使用Terratest执行了一些基本的测试。现在,我们将通过部署一个以Lambda和ALB作为后端的API网关来执行高级测试。
- 高级功能
API网关的GET请求将由ALB处理,任何方法将由Lambda通过API网关来处理。部署后,我们将对网关部署URL执行HTTP GET请求,并检查它是否返回成功码。
注意:我们在执行中没有使用任何API_KEY进行身份验证,但您应该使用它来再现API Gateway更实际的使用。
Terraform output.tf
output "lb_address" {
value = aws_lb.load-balancer.dns_name
description = "DNS of load balancer"
}
output "api_id" {
description = "REST API id"
value = aws_api_gateway_rest_api.api.id
}
output "deployment_invoke_url" {
description = "Deployment invoke url"
value = "${aws_api_gateway_stage.test.invoke_url}/resource"
}
- 测试执行的代码片段
在第一个场景中,我们已经解释了基本语法,因此将直接进入测试函数。
func TestApiGateway(t *testing.T) {
//awsRegion := "eu-west-2"
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../",
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
stageUrl := terraform.Output(t, terraformOptions,"deployment_invoke_url") time.Sleep(30 * time.Second)
statusCode := DoGetRequest(t, stageUrl)
assert.Equal(t, 200 , statusCode)
}
func DoGetRequest(t terra_test.TestingT, api string) int{
resp, err := http.Get(api)
if err != nil {
log.Fatalln(err)
}
//We Read the response status on the line below.
return resp.StatusCode
}
在上面的代码片段中,我们定义了函数DoGetRequest来运行HTTP GET测试。然后,我们将此函数的输出用作TestApiGateway函数的输入。
- 测试执行和输出
TestApiGateway 2022-03-01T06:56:18Z logger.go:66: deployment_invoke_url = "https://iuabeqgmj2.execute-api.eu-west-1.amazonaws.com/test/resource" TestApiGateway 2022-03-01T06:56:18Z logger.go:66: lb_address = "my-demo-load-balancer-
376285754.eu-west-1.elb.amazonaws.com"
TestApiGateway 2022-03-01T06:56:18Z retry.go:91: terraform [output -no-color -json deployment_invoke_url]
TestApiGateway 2022-03-01T06:56:18Z logger.go:66: Running command terraform with args [output –
no-color -json deployment_invoke_url]
TestApiGateway 2022-03-01T06:56:19Z logger.go:66: "https://iuabeqgmj2.execute-api.eu-west-
1.amazonaws.com/test/resource"
--- PASS: TestApiGateway (42.34s)
PASS
ok command-line-arguments 42.347s
如您所见,它执行了测试函数TestApiGateway,其中它对API网关的deployment_invoke_url执行了TTP GET测试,并返回了测试状态。
八、使用Terratest进行Terratest模块的 可扩展性和合规测试
我们还可以利用Terratest进行合规测试。一些例子包括:
- 检查是否在您的SQS队列或S3存储桶上启用了加密。
- 验证您是否为API网关设置了特定的限制。
我们为API网关开发了Terratest检查机制。在该例子中,我们验证是否为您的API网关添加了Authorizer。
目前,Terratest在其AWS模块中没有API网关模块。您可以在Terratest AWS模块目录中找到可用的AWS模块。Docker、Packer或Helm等其他Terratest模块可以在Terratest模块目录中找到。
我们使用Terratest和AWS Go SDK方法为Authorizer创建了自己的测试函数。
九、结语
企业及其客户希望产品更快速地交付。基础架构即代码加快了基础架构的配置,恰好满足了这个要求。随着越来越多的基础架构变成代码,用户对测试的需求也在增加。我们在本文中讨论了Terratest之类的工具如何帮助您在将代码部署到生产环境之前对其进行验证。我们介绍了Terratest的工作原理,甚至执行了测试用例来表明它是如何完成的。Terratest的优点之一是具有可扩展性,我们可以通过使用本文中提到的模块实现这种可扩展性。
原文链接:https://www.cncf.io/blog/2022/07/18/testing-your-infrastructure-as-code-using-terratest/