Terraform 코드 테스트
https://developer.hashicorp.com/terraform/tutorials/configuration-language/test 블로그를 기반으로 작성된 블로그입니다.
소개
Terraform 테스트를 사용하면 기존 상태 파일이나 리소스에 영향을 주지 않고도 모듈 구성을 검증할 수 있습니다. 테스트는 plan이나 apply 워크플로우와는 별개의 작업으로, 임시 인프라를 구축하고 이러한 단기 리소스의 인메모리 상태에 대해 테스트를 수행합니다. 이를 통해 인프라에 영향을 주지 않고도 모듈 변경사항을 안전하게 검증할 수 있습니다.
테스트와 검증의 차이
- 검증(Validation): 변수 검증, 사전/사후 조건, 체크 블록 등은 배포된 인프라의 유효성을 확인합니다. 검증이 실패하면 모듈 사용자가 해결해야 합니다.
- 테스트(Testing): 구성 자체의 동작과 로직을 검증합니다. 모듈 작성자가 구성의 동작을 확인하고 변경사항이 문제를 일으키지 않도록 보장하는 데 사용됩니다.
사전 준비 사항
테스트를 시작하기 위해서는 다음이 필요합니다:
- Terraform v1.7 이상
- 로컬에 구성된 AWS 계정 자격 증명
- GitHub 계정
- HCP Terraform 계정
프로젝트 구조 이해하기
일반적인 Terraform 테스트 프로젝트는 다음과 같은 구조를 가집니다:
.
├── LICENSE
├── README.md
├── main.tf
├── outputs.tf
├── terraform.tf
├── tests
│ ├── setup
│ │ ├── main.tf
│ └── website.tftest.hcl
├── variables.tf
└── www
├── error.html
└── index.html
Terraform 테스트는 크게 두 부분으로 구성됩니다:
.tftest.hcl
확장자를 가진 테스트 파일- 테스트별 리소스와 데이터 소스를 생성하는 선택적 헬퍼 모듈
기본적으로 terraform test
명령은 루트 디렉토리와 tests 디렉토리에서 .tftest.hcl
파일을 찾습니다. -test-directory
플래그를 사용하여 다른 디렉토리를 지정할 수 있습니다.
헬퍼 모듈 작성하기
예를 들어, 다음과 같은 헬퍼 모듈을 만들 수 있습니다:
# tests/setup/main.tf
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "3.5.1"
}
}
}
resource "random_pet" "bucket_prefix" {
length = 4
}
output "bucket_prefix" {
value = random_pet.bucket_prefix.id
}
테스트 파일 작성하기
테스트 파일은 순차적으로 실행되는 run
블록들로 구성됩니다:
# tests/website.tftest.hcl
run "setup_tests" {
module {
source = "./tests/setup"
}
}
run "create_bucket" {
command = apply
variables {
bucket_name = "${run.setup_tests.bucket_prefix}-aws-s3-website-test"
}
assert {
condition = aws_s3_bucket.s3_bucket.bucket == "${run.setup_tests.bucket_prefix}-aws-s3-website-test"
error_message = "Invalid bucket name"
}
assert {
condition = aws_s3_object.index.etag == filemd5("./www/index.html")
error_message = "Invalid eTag for index.html"
}
assert {
condition = aws_s3_object.error.etag == filemd5("./www/error.html")
error_message = "Invalid eTag for error.html"
}
}
각 run
블록은 고유한 이름을 가져야 하며, 여러 개의 assert
블록을 포함할 수 있습니다. 모든 assert
블록의 조건이 true로 평가되어야 해당 run 블록이 통과합니다.
HTTP 테스트 추가하기
웹사이트의 동작을 테스트하기 위해 HTTP 헬퍼 모듈을 만들 수 있습니다:
# tests/final/main.tf
terraform {
required_providers {
http = {
source = "hashicorp/http"
version = "3.4.0"
}
}
}
variable "endpoint" {
type = string
}
data "http" "index" {
url = var.endpoint
method = "GET"
}
그리고 이를 사용하는 테스트를 추가합니다:
run "website_is_running" {
command = plan
module {
source = "./tests/final"
}
variables {
endpoint = run.create_bucket.website_endpoint
}
assert {
condition = data.http.index.status_code == 200
error_message = "Website responded with HTTP status ${data.http.index.status_code}"
}
}
Mock 테스트란?
Terraform은 프로바이더, 리소스, 데이터 소스에 대한 Mock 테스트를 지원합니다. 이를 통해 실제로 임시 인프라를 생성하지 않고도 구성이 의존하는 모든 리소스와 속성을 시뮬레이션할 수 있습니다. Mock 테스트를 사용하면 Terraform이 리소스와 데이터 소스의 모든 계산된 필드에 대해 자동으로 값을 생성합니다.
Mock 테스트가 필요한 이유
- 테스트 시간 단축
- 비용 절감
- 복잡한 인프라 의존성 처리 단순화
- 테스트 안정성 향상
실제 예제로 알아보는 Mock 테스트
1. 백엔드 인프라 구성
먼저 테스트할 백엔드 인프라 구성을 main.tf
에 정의합니다:
# Backend API
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "backend_api" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
tags = {
Name = "backend"
}
}
resource "aws_db_instance" "backend_api" {
allocated_storage = 10
db_name = "backend_api"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t3.micro"
username = "foo"
password = "foobarbaz"
parameter_group_name = "default.mysql5.7"
skip_final_snapshot = true
}
이 구성은 애플리케이션의 백엔드 API를 지원하기 위한 EC2 인스턴스와 RDS 데이터베이스를 생성합니다. 이러한 리소스들은 프로비저닝 시간이 길어 테스트 속도를 늦출 수 있습니다.
2. Mock 설정
website.tftest.hcl
파일에 다음과 같은 override_resource
블록을 추가합니다:
override_resource {
target = aws_instance.backend_api
}
override_resource {
target = aws_db_instance.backend_api
}
이 override_resource
블록은 Terraform에게 target
속성에 정의된 주소의 리소스를 Mock하도록 지시합니다. 이 경우 EC2 인스턴스와 RDS 데이터베이스를 AWS에서 실제로 프로비저닝하지 않고 Mock 처리합니다.
3. 테스트 케이스 작성
다음으로 테스트 케이스를 추가합니다:
run "check_backend_api" {
assert {
condition = aws_instance.backend_api.tags.Name == "backend"
error_message = "Invalid name tag"
}
assert {
condition = aws_db_instance.backend_api.username == "foo"
error_message = "Invalid database username"
}
}
4. 테스트 실행
테스트를 실행하면 다음과 같은 결과를 얻을 수 있습니다:
$ terraform test
tests/website.tftest.hcl... in progress
run "setup_tests"... pass
run "create_bucket"... pass
run "website_is_running"... pass
run "check_backend_api"... pass
tests/website.tftest.hcl... tearing down
tests/website.tftest.hcl... pass
Success! 4 passed, 0 failed.
Mock을 사용하지 않았다면, AWS가 RDS 데이터베이스와 EC2 인스턴스를 생성하기를 기다려야 하므로 테스트 완료까지 수 분이 걸렸을 것입니다. override_resource
블록을 사용하여 Mock 처리했기 때문에 Terraform이 테스트를 빠르게 완료할 수 있었습니다.
데이터 소스 Mock 처리
데이터 소스도 Mock 처리가 가능합니다. 예를 들어, 조직에 네트워킹 팀과 애플리케이션 배포 팀이 별도로 있는 경우를 생각해보겠습니다. 이런 시나리오에서 애플리케이션 팀은 tfe_outputs
데이터 소스를 사용하여 네트워킹 팀이 생성한 인프라를 참조할 수 있습니다.
테스트를 위해 override_data
블록을 사용하여 tfe_outputs
데이터 소스의 값을 Mock 처리할 수 있습니다:
override_data {
target = data.tfe_outputs.network
values = {
vpc_id = "vpc-12345678"
subnet_ids = ["subnet-1", "subnet-2"]
}
}
Mock 테스트 모범 사례
- 선택적 Mock 처리: 모든 리소스를 Mock 처리할 필요는 없습니다. 테스트에 직접적으로 필요한 리소스만 실제로 생성하고 나머지는 Mock 처리하세요.
- 현실적인 값 사용: Mock 처리된 값이더라도 실제 환경에서 발생할 수 있는 값을 사용하세요.
- 예외 케이스 테스트: Mock을 통해 실제 환경에서는 테스트하기 어려운 예외 상황을 시뮬레이션할 수 있습니다.
- 문서화: 어떤 리소스가 Mock 처리되었는지 명확하게 문서화하여 다른 개발자들이 테스트 범위를 이해할 수 있게 하세요.
Terraform 테스트 모범 사례
- 모든 중요한 모듈에 대한 기본 테스트 작성
- CI/CD 파이프라인에 테스트 통합
- 시간이 오래 걸리는 리소스는 모의 테스트 사용
- 테스트 커버리지 점진적 확장
- 명확한 에러 메시지 작성