如果您具有复杂程度适中的Terraform配置,那么如何围绕可作为持续集成/持续交付管道的一部分执行的配置编写测试?

例如可能具有指定以下所需状态的多云配置:


Azure容器服务以在Azure中托管Docker
Azure Blob存储
SQL Azure
> EC2容器服务将AWS托管在AWS中
Amazon S3存储服务
Amazon RDS SQL Server数据库

可能terraform apply可以从头开始创建上述内容,或者从部分部署状态过渡到上面期望的状态。

我知道Terraform将其工作分为执行计划阶段和应用程序阶段,这些阶段实际上对目标体系结构进行了更改。可以使用它来针对执行计划编写测试,如果有的话,是否有框架可以帮助编写这些测试?

评论

可能有用:github.com/hashicorp/terraform/issues/5059

确实很有趣,可能值得回答。

我自己并没有使用terraform,所以我会让有实际经验的人写一个答案:)

#1 楼

目前尚无将其集成到Terraform中的完整解决方案,但是有一些构建基块可以帮助以另一种编程语言编写测试。原则上,由外部程序用来提取有关Terraform创建的内容的某些数据。尽管此格式尚未被正式认为是稳定的,但实际上它的变化很少,以至于人们已经成功地集成了该格式,并接受了他们在升级Terraform时可能需要进行调整的做法。在很大程度上取决于您要测试的内容。例如:


在正在虚拟化服务器的环境中,可以使用Serverspec之类的工具从这些服务器的角度运行测试。可以使用某些带外过程将其与Terraform分开运行,也可以使用remote-exec预配器将其作为Terraform应用的一部分。这样可以验证诸如“服务器可以到达数据库吗?”之类的问题,但不适用于诸如“实例的安全组是否具有足够的限制性?”之类的问题,因为要进行稳健的检查需要从实例本身之外访问数据。 br />可以使用现有的测试框架(例如Ruby的RSpec,Python的unittest等)编写测试,这些框架从Terraform状态文件中收集相关的资源ID或地址,然后使用相关平台的SDK检索有关资源并声称它们已按预期设置。这是先前构想的一种更通用的形式,它从被测基础架构之外的主机的角度运行测试,从而可以收集更广泛的数据集来进行断言。对于更适度的需求,您可以选择相信Terraform状态是现实的准确表示(在许多情况下是有效的假设),并直接对此声明。这最适合简单的“棉绒状”情况,例如,验证是否出于成本分配目的而遵循正确的资源标记方案。 Github问题。

在最新版本的Terraform中,强烈建议对任何非玩具应用程序使用远程后端,但这意味着状态数据在本地磁盘上不直接可用。但是,可以使用terraform state pull命令从远程后端检索快照的快照,该命令将JSON格式的状态数据打印到stdout,以便可以由调用程序捕获和解析。

#2 楼

最近,我们开源了Terratest,这是我们用于测试基础结构代码的瑞士军刀。

今天,您可能正在通过部署,验证和取消部署来手动测试所有基础结构代码。 Terratest可帮助您自动化此过程:


在Go中编写测试。
在Terratest中使用帮助程序执行真正的IaC工具(例如Terraform,Packer等)进行部署真实环境(例如AWS)中的真实基础结构(例如服务器)。
通过发出HTTP请求,API调用,SSH连接等,在Terratest中使用帮助程序来验证基础结构在该环境中是否正常工作。
/>在测试结束时使用Terratest中的助手取消部署所有内容。

以下是一些Terraform代码的示例测试:

 terraformOptions := &terraform.Options {
  // The path to where your Terraform code is located
  TerraformDir: "../examples/terraform-basic-example",
}

// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
terraform.InitAndApply(t, terraformOptions)

// At the end of the test, run `terraform destroy` to clean up any resources that were created
defer terraform.Destroy(t, terraformOptions)

// Run `terraform output` to get the value of an output variable
instanceUrl := terraform.Output(t, terraformOptions, "instance_url")

// Verify that we get back a 200 OK with the expected text
// It can take a minute or so for the Instance to boot up, so retry a few times
expected := "Hello, World"
maxRetries := 15
timeBetweenRetries := 5 * time.Second
http_helper.HttpGetWithRetry(t, instanceUrl, 200, expected, maxRetries, timeBetweenRetries)
 


这些是集成测试,取决于您要测试的内容,可能需要5到50分钟。速度不是很快(尽管使用Docker和测试阶段,您可以加快一些速度),并且必须努力使测试可靠,但这是值得的。

签出用于文档的Terratest存储库以及各种类型的基础架构代码的示例以及针对它们的相应测试。

评论


我还写了一篇博客文章,其中详细介绍了如何使用Terratest测试我的示例项目之一:brightfame.co/blog/…。这对任何人都可能有价值。干杯,罗布!

–罗布·摩根(Rob Morgan)
18年5月24日在9:57

Terratest的忠实粉丝!

– jlucktay
19年8月5日在7:40

#3 楼

作为对此问题的更新,现在提供了Kitchen-Terraform,它可以在不破坏生产环境的情况下测试Terraform配置文件。该存储库还包括一些针对不同Terraform提供程序的示例。

#4 楼

除了提到的所有其他选项之外,我还要提到InSpec 2.0添加了对云提供商API的支持。基本上,您可以继续使用Terraform编写IaC,然后使用InSpec编写对云资源的兼容性检查。此外,InSpec支持在需要时为单个计算机编写测试。 lollyrock.com/articles/inspec-terraform/

#5 楼

在Aws侧上有https://github.com/k1LoW/awspec-应该可以输入terraform.state并进行测试,然后正确应用地形。

但是我认为,超越在较低级别上测试您所使用的工具,考虑如何测试整个基础架构可能是一个更好的主意。

我们在这里讨论这个想法:

https://github.com/DomainDrivenArchitecture/dda-cloudspec/blob/development/README.md

为了预先测试不变量,我不知道可以使用的解决方案...

我们使用terraform plan -out=plan.dumpgrep的混合物进行了一些实验,以查找缺少元素名称的情况。此处讨论了一种更易用的计划格式:github.com/hashicorp/terraform/issues/11883

但是目前,我们正在对基础架构的重要部分使用手动计划审查流程。

评论


目标是测试terraform配置中的更改不会破坏预期的需求,一旦部署为时已晚,充其量您无法看到已删除不应删除的DB,但您确实破坏了目标环境。 ..问题是关于测试terraform代码,而不是测试最终结果,单元测试还是集成测试。

–滕西拜
17年8月24日在12:41

好点...增加了测试不变性的部分。

– jerger
17年8月24日在15:08

#6 楼

我在GitHub问题线程中看到了一种明显的,低技术含量的方法来测试显然由martinmart建议的Terraform。这并不适合每种情况,但对于验证模块逻辑非常有用。

创建一个包含被测模块的根模块并验证被测输出。这是一个使用两个文件的简单示例:



main.tf将运行测试

simple_module/outputs.tf代表被测模块

./main.tf

terraform {
  required_version = ">= 0.12"
}

module "simple_module" {
  source = "./simple_module"
}

locals {
  expected = 1
  got      = module.simple_module.module-returns-1
}

# Test Output
output "expect-1" {
  value = upper(local.expected == local.got)
}

output "expect-other" {
  value = "other" == local.got ? upper(true) : "FALSE. Got ${local.got}"
}


./simple_module/outputs.tf

output "module-returns-1" {
  value = 1
}


运行测试

terraform init
terraform apply -auto-approve


Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

expect-1 = TRUE
expect-other = FALSE. Got 1