如何使用Terratest測試基礎(chǔ)架構(gòu)即代碼
譯文譯者 | 布加迪
審校 | 孫淑娟
手動設(shè)置基礎(chǔ)架構(gòu)是費時又費力的過程。這時候我們可以利用基礎(chǔ)架構(gòu)即代碼(IaC)工具來自動管理基礎(chǔ)架構(gòu)。IaC自動化可用于任何類型的基礎(chǔ)架構(gòu):虛擬機和存儲等。隨著越來越多的基礎(chǔ)架構(gòu)變成代碼,有必要為IaC進行單元測試和集成測試。
本文簡要討論了什么是IaC以及測試基礎(chǔ)架構(gòu)代碼的意義,然后深入探討了如何使用Terratest進行IaC測試。
一、基礎(chǔ)架構(gòu)即代碼(IaC)
基礎(chǔ)架構(gòu)即代碼是通過代碼配置和設(shè)置環(huán)境的過程,而不是通過GUI手動創(chuàng)建所需的基礎(chǔ)架構(gòu)和支持系統(tǒng)。比如說,配置虛擬機、設(shè)置虛擬機并為其創(chuàng)建監(jiān)控機制。Terraform、Packer和Ansible就是典型的IaC。借助基礎(chǔ)架構(gòu)即代碼,您還可以將基礎(chǔ)架構(gòu)跟蹤到Git等版本控制系統(tǒng)中,進行模塊化和模板化,以便在多個環(huán)境和地區(qū)重用相同的代碼。災(zāi)難恢復(fù)是從基礎(chǔ)架構(gòu)即代碼獲得的重要好處之一。有了IaC,您可以盡快在其他地區(qū)或環(huán)境復(fù)制基礎(chǔ)架構(gòu)。
二、測試基礎(chǔ)架構(gòu)代碼
IaC測試可以分為多個階段:
1.健全性或靜態(tài)分析
2.單元測試
3.集成測試
- 健全性或靜態(tài)分析?
這是測試基礎(chǔ)架構(gòu)代碼的初始階段。在靜態(tài)分析中,我們確保代碼有正確的語法。它還有助于確保我們的代碼符合行業(yè)標(biāo)準(zhǔn),并遵循最佳實踐。Linter屬于這一類。幾款典型的健全性測試工具包括面向Chef的foodcritic、面向Docker的hadolint和面向Terraform的tflint等。
- 單元測試?
借助單元測試,我們不用實際配置基礎(chǔ)架構(gòu)即可評估代碼。比如可以限制容器以便以非root用戶身份運行,或者云網(wǎng)絡(luò)安全組應(yīng)該只有TCP協(xié)議。幾個典型的單元測試是面向Terraform的Conftest和面向Chef Cookbooks的Chefspecs。
以非root用戶身份執(zhí)行的Conftest例子:
package main
deny[msg] {
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot
msg := "Containers must not run as root"
}
- 集成測試?
在集成測試中,我們希望通過將IaC實際部署到所需的環(huán)境中對其進行測試。比如說,您部署了一個虛擬機,并在該機器的端口80上托管Nginx服務(wù)器。因此,您將在部署之后檢查端口80是否在偵聽。
以下是使用ServerSpec執(zhí)行該操作的例子:
describe port(80) do
it { should be_listening }
end
我們在本文中介紹使用Terrratest對基礎(chǔ)架構(gòu)代碼進行集成測試。
三、Terratest是什么?我們可以用它來做什么?
Terratest是由Gruntwork開發(fā)的Go庫,可幫助您為使用Terraform或Packer編寫的基礎(chǔ)架構(gòu)即代碼創(chuàng)建和自動化測試。它為您提供了各種任務(wù)所需的函數(shù)和模式,比如:
測試Docker鏡像、Helm圖和Packer模板。
允許與各種云提供商API兼容,比如AWS和Azure。
Terratest為基礎(chǔ)架構(gòu)代碼執(zhí)行健全性和功能測試。有了Terratest,您可以輕松識別當(dāng)前基礎(chǔ)架構(gòu)代碼中的問題并盡快解決問題。我們還可以利用Terratest對基礎(chǔ)架構(gòu)進行合規(guī)測試,比如針對通過IaC創(chuàng)建的任何新S3存儲桶啟用版本控制和加密。
四、安裝Terratest所需的二進制文件
Terratest主要需要Terraform和Go來執(zhí)行。我們在這篇博文中使用了Terraform版本1.0.0 和Go版本1.17.6進行測試。
- 安裝Terraform?
按照Terraform網(wǎng)站的下載部分(https://www.terraform.io/downloads)在您的計算機上安裝Terraform,您可以使用軟件包管理器或下載二進制文件,并使其在PATH中可用。
安裝后,通過運行以下命令驗證是否已正確安裝:
terraform version
Go & test依賴項安裝可以通過以下步驟來完成:
- 安裝Go?
您可以使用Linux發(fā)行版的軟件包管理器來安裝Go,或者遵照Go的安裝文檔(https://go.dev/doc/install)。
- Go測試需要gcc來執(zhí)行測試?
go test命令可能需要gcc,您可以使用發(fā)行版的軟件包管理器安裝它。比如在CentOS/Amazon Linux 2上,您可以使用yum install -y gcc。
五、Terratest實戰(zhàn)
現(xiàn)在,我們將使用Terratest執(zhí)行一些集成測試。安裝步驟完成后,克隆terratest-sample存儲庫,開始執(zhí)行Terratest。我們將先使用Go編寫測試并執(zhí)行測試。
重要的事先說:
1.您的測試文件名稱應(yīng)包含_test,比如sample_test.go。這是Go查找測試文件的方式。
2.您的測試函數(shù)名稱應(yīng)以Test開頭,其中T大寫。比如說,TestFunction沒有問題,但testFunction會給出錯誤“沒有要運行的測試”。
- 設(shè)置AWS身份驗證配置?
我們需要AWS憑證在AWS中設(shè)置基礎(chǔ)架構(gòu),可以使用環(huán)境變量或共享憑證文件進行配置。
基礎(chǔ)架構(gòu)的Terraform代碼可以在組件的相應(yīng)文件夾中找到。若是ec2,它位于ec2_instance下,若是API網(wǎng)關(guān),它位于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軟件包,然后我們導(dǎo)入測試執(zhí)行所需的不同軟件包。
package terratest
import (
"testing"
"github.com/stretchr/testify/assert" "github.com/gruntwork-io/terratest/modules/terraform"
)
一旦我們滿足了所有的先決條件,將創(chuàng)建一個函數(shù)來執(zhí)行實際測試:
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應(yīng)該在其中查找Terraform清單文件(即main.tf和output.tf)的目錄,以便創(chuàng)建基礎(chǔ)架構(gòu)。
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../terraform",
})
在Go中,我們使用defer方法來執(zhí)行清理任務(wù),它應(yīng)該是terraform destroy。
我們使用下面的代碼片段來定義它:
defer terraform.Destroy(t, terraformOptions)
現(xiàn)在我們可以繼續(xù)實際執(zhí)行了:
使用terraform.InitAndApply ,我們調(diào)用通常用于Terraform執(zhí)行的Terraform函數(shù)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)
六、執(zhí)行測試
將目錄切換到已克隆存儲庫的位置。進入到測試文件所在的位置。
初始化Go模塊,并下載依賴項。請查看Terratest文檔的“設(shè)置項目”部分以獲取更多詳細信息。
go mod init ec2_instance
go mod tidy
最后執(zhí)行測試:
$ go test –v
--- PASS: TestEc2SshKey (98.72s)
PASS
ok command-line-arguments 98.735s
七、不妨繼續(xù)使用Terratest
在上一節(jié)中,我們使用Terratest執(zhí)行了一些基本的測試?,F(xiàn)在,我們將通過部署一個以Lambda和ALB作為后端的API網(wǎng)關(guān)來執(zhí)行高級測試。
- 高級功能
API網(wǎng)關(guān)的GET請求將由ALB處理,任何方法將由Lambda通過API網(wǎng)關(guān)來處理。部署后,我們將對網(wǎng)關(guān)部署URL執(zhí)行HTTP GET請求,并檢查它是否返回成功碼。
注意:我們在執(zhí)行中沒有使用任何API_KEY進行身份驗證,但您應(yīng)該使用它來再現(xiàn)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"
}
- 測試執(zhí)行的代碼片段
在第一個場景中,我們已經(jīng)解釋了基本語法,因此將直接進入測試函數(shù)。
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
}
在上面的代碼片段中,我們定義了函數(shù)DoGetRequest來運行HTTP GET測試。然后,我們將此函數(shù)的輸出用作TestApiGateway函數(shù)的輸入。
- 測試執(zhí)行和輸出
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
如您所見,它執(zhí)行了測試函數(shù)TestApiGateway,其中它對API網(wǎng)關(guān)的deployment_invoke_url執(zhí)行了TTP GET測試,并返回了測試狀態(tài)。
八、使用Terratest進行Terratest模塊的 可擴展性和合規(guī)測試
我們還可以利用Terratest進行合規(guī)測試。一些例子包括:
- 檢查是否在您的SQS隊列或S3存儲桶上啟用了加密。
- 驗證您是否為API網(wǎng)關(guān)設(shè)置了特定的限制。
我們?yōu)锳PI網(wǎng)關(guān)開發(fā)了Terratest檢查機制。在該例子中,我們驗證是否為您的API網(wǎng)關(guān)添加了Authorizer。
目前,Terratest在其AWS模塊中沒有API網(wǎng)關(guān)模塊。您可以在Terratest AWS模塊目錄中找到可用的AWS模塊。Docker、Packer或Helm等其他Terratest模塊可以在Terratest模塊目錄中找到。
我們使用Terratest和AWS Go SDK方法為Authorizer創(chuàng)建了自己的測試函數(shù)。
九、結(jié)語
企業(yè)及其客戶希望產(chǎn)品更快速地交付?;A(chǔ)架構(gòu)即代碼加快了基礎(chǔ)架構(gòu)的配置,恰好滿足了這個要求。隨著越來越多的基礎(chǔ)架構(gòu)變成代碼,用戶對測試的需求也在增加。我們在本文中討論了Terratest之類的工具如何幫助您在將代碼部署到生產(chǎn)環(huán)境之前對其進行驗證。我們介紹了Terratest的工作原理,甚至執(zhí)行了測試用例來表明它是如何完成的。Terratest的優(yōu)點之一是具有可擴展性,我們可以通過使用本文中提到的模塊實現(xiàn)這種可擴展性。
原文鏈接:https://www.cncf.io/blog/2022/07/18/testing-your-infrastructure-as-code-using-terratest/