如何测试Terraform配置?


37

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

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

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

可能terraform apply会从头开始创建上述内容,或从部分部署状态过渡到上述所需状态。

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



确实有趣,可能值得回答。
理查德·斯莱特

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

Answers:


20

目前尚没有将其集成到Terraform中的完整解决方案,但是有一些构建块可能有助于以单独的编程语言编写测试。

Terraform产生JSON格式的状态文件,原则上,外部程序可以使用它们来提取有关Terraform创建的内容的某些数据。尽管此格式尚未被正式认为是稳定的,但实际上它的变化很少,以至于人们已经成功地与之集成,并接受他们在升级Terraform时可能需要进行调整。

在这里合适的策略很大程度上取决于您要测试的内容。例如:

  • 在虚拟服务器分拆的环境中,可以使用Serverspec之类的工具从这些服务器的角度运行测试。这可以使用某些带外过程与Terraform分开运行,也可以使用remote-execProvisioninger作为Terraform应用的一部分。这样可以验证诸如“服务器可以到达数据库吗?”之类的问题,但不适用于诸如“实例的安全组是否具有足够的限制性?”之类的问题,因为要进行健壮的检查,需要从实例本身之外访问数据。

  • 可以使用现有的测试框架(例如Ruby的RSpec,unittestPython的RSpec)编写测试,该框架从Terraform状态文件中收集相关的资源ID或地址,然后使用相关平台的SDK检索有关资源的数据并断言它们按预期设置。这是先前构想的一种更一般的形式,它从被测基础架构之外的主机的角度运行测试,因此可以收集更广泛的数据集以进行断言。

  • 对于更适度的需求,您可以选择相信Terraform状态是现实的精确表示(在许多情况下是有效的假设),并直接对此声明。这最适合简单的“棉绒状”情况,例如验证出于成本分配目的而遵循了正确的资源标记方案。

有关的Terraform Github问题中对此有更多讨论。

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


12

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


12

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

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

  1. 在Go中编写测试。
  2. 使用Terratest中的助手来执行您的真实IaC工具(例如Terraform,Packer等),以在真实环境(例如AWS)中部署真实基础架构(例如服务器)。
  3. 通过发出HTTP请求,API调用,SSH连接等,在Terratest中使用帮助程序来验证基础架构在该环境中是否正常运行。
  4. 在测试结束时使用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存储库中的文档,以及各种类型的基础架构代码的示例以及针对它们的相应测试。


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

Terratest的忠实粉丝!
jlucktay

7

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

这是来自Christoph Hartmann(Inspec的共同创建者)的文章,内容涉及如何将Inspec与Terraform一起使用:https : //lollyrock.com/articles/inspec-terraform/


5

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

但是我认为,除了使用底层工具进行测试之外,考虑如何测试整个基础架构可能是一个更好的主意。

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

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

为了预先测试不变式,我不知道一个现成的解决方案...

我们没有使用的混合一些实验terraform plan -out=plan.dump,并grep为缺少的元素名称。这里有一个关于更易于访问的计划格式的讨论:github.com/hashicorp/terraform/issues/11883

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


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

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

0

我在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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.