통합 테스트
여러 모듈이 함께 동작하는지 확인하는 과정
개별 모듈은 유닛 테스트로 검증을 진행하고, 실제 인프라에 배포했을 때 정상적으로 연동되는지 확인하기 위해 통합테스트가 필요하다.
테스트 단계
- 테라폼 코드 검증
terraform fmt / terraform validate - 테라폼 계획 검증 : plan 결과를 토대로 예상대로 동작하는지 검증
terraform plan - 실제 배포 후 테스트 : 리소스가 기대한 상태인지 체크
terraform apply - 정리 테스트 : 실행 후 리소스가 깨끗이 삭제되는지 확인
terraform destroy
재시도
테라폼 실행 중 일시적인 네트워크 문제나 API Rate Limit 초과로 테스트가 실패할 수 있음 / 이런 경우를 고려하여 재시도 로직을 추가하여 안정성을 올릴 수 있다.
API Rate Limit 초과 : 특정 API에 짧은 시간내에 너무 많은 요청을 보내, API 요청 허용 한도를 넘어선 경우
AWS나 GCP 같은 provider들은 계정당 또는 특정 서비스별로 초당 요청 수(QPS) 혹은 분당 요청 수(RPM) 제한이 있음
- 테스트 프레임워크에서 재시도 (예: Terratest에서 require.Eventually() 활용)
package test import ( "testing" "time" "github.com/stretchr/testify/require" "github.com/gruntwork-io/terratest/modules/aws" ) func TestRetryEC2DescribeInstances(t *testing.T) { awsRegion := "us-east-1" // 3분동안 10초 간격으로 err == nil 일때까지 EC2 인스턴스 조회 재시도 require.Eventually(t, func() bool { _, err := aws.GetEc2InstanceIdsByTag(t, awsRegion, "Environment", "dev") return err == nil }, 3*time.Minute, 10*time.Second) }
- 프로바이더 SDK에서 제공되는 retry 기능 - 지수 backoff 전략 적용 (재시도 간격을 점진적으로 늘리는 방식)
종단 간 테스트
인프라 전체가 의도한 대로 동작하는지 확인하는 과정
리소스 생성 ~ 어플리케이션이 정상적으로 실행되는지까지 포함해서 검증
예시
EC2 + RDS + ALB를 배포한 경우
- EC2가 정상적으로 실행되는지 체크
- RDS에 견결 가능한지 확인
- ALB 통해 HTTP 요청이 정상적으로 처리되는지 검
다른 테스트 접근 방식
- Mocking -> 클라우드 리소스를 실제 배포하지 않고 모의(Mock) 객체로 테스트
- Sandbox 환경 테스트 -> 프로덕션과 비슷한 환경을 임시로 배포하여 검증
정적 분석
테라폼 코드를 실행하기 전에 코드 품질과 보인 이슈를 찾아내는 방법
CI/CD 파이프라인에 정적 분석을 추가하여 해당 코드가 배포되기 전에 문제를 잡아낼 수 있음
- terraform fmt → 코드 스타일 정리
- terraform validate → 기본적인 문법 검사
- tflint → 모범 사례(Best Practices) 및 잠재적인 버그 감지
- checkov → 보안 검증 (예: 공개된 S3 버킷 감지)
속성 테스트
특정 입력값이 아닌 속성을 기반으로 테스트하는 형식
예시
- 생성된 S3 버킷이 항상 암호화 상태여야 한다.
- RDS 인스턴스는 특정 보안 그룹을 가져야 한다.
- 모든 EC2 인스턴스는 특정 태그를 가져야 한다.
Mocks
테스트를 위해 provider 및 resource, datasource 등을 모의할 수 있다.
mock provider
mock provider는 원래의 provider와 동일한 스키마를 반환하며, 모의 provider가 제공하여 검색된 모든 리소스와 datasource는 구성에서 관련 값을 설정하고 계산된 모든 속성에 대한 가짜 데이터를 생성한다.
mock provider는 기존 provider 블록을 대체할 수 있으며, 동일한 글로벌 네임스페이스를 공유한다.
terraform test
명령을 실행하는 동안, terraform은 실제 provider와 mock provider를 구별하지 않는다.
AWS S3 버킷 생성 후에 mock provider 이용하여 AWS 계정없이 구성을 테스트하는 예시
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
variable "bucket_name" {
type = string
}
resource "aws_s3_bucket" "my_bucket" {
bucket = var.bucket_name
}
terraform 코드가 bucket_name을 "my-bucket-name"으로 설정하는지 검증하기 위한 테스트
terraform test 명령어를 실행하여 테스트한다.
# bucket_name.tftest.hcl
mock_provider "aws" {}
run "sets_correct_name" {
variables {
bucket_name = "my-bucket-name"
}
assert {
condition = aws_s3_bucket.my_bucket.bucket == "my-bucket-name"
error_message = "incorrect bucket name"
}
}
-> mock provider를 이용하여 실제 리소스를 생성하지 않아도, 테라폼 코드가 원하는 상태로 리소스를 구성하는지 테스트할 수 있다.
- mock provider 이용하여 terraform test 명령어를 실행한다.
- (GCP, AWS 등) 실제 리소스를 배포하지 않고 mock 리소스를 생성한다.
- 테라폼에선 실제 리소스가 만들어진 것처럼 state를 메모리에 저장
- assert 블록을 이용하여 리소스의 설정을 검증함
- condition 만족하면 테스트 통과 / 값이 다르면 설정한 에러메시지 출력
- 테스트 종료 후, 메모리에 저장된 모든 state를 삭제
# mocked_providers.tftest.hcl
provider "aws" {}
mock_provider "aws" {
alias = "fake"
}
run "use_real_provider" {
providers = {
aws = aws
}
}
run "use_mocked_provider" {
providers = {
aws = aws.fake
}
}
실제 provider와 aws의 mock provider 는 aws provider 의 golbal namespace 를공유하기에, 둘 다 정의해서 사용하려면, alias를 사용해야 한다.
generated data
mock provider를 사용하는 경우, 필수 리소스 설정을 지정해야 하며, 선택적인 속성에 대한 값을 제공하지 않으면, 자동으로 값을 생성하게 된다.
생성 규칙
- 숫자는 0
- bool은 false
- 문자열은 8자리의 무작위 영문,숫자 문자열
- set, list, map을 포함한 collection은 empty collection
- 객체는 이와 동일한 규칙을 재귀적으로 사용하여, 생성된 모든 필수 하위 속성을 생성
mock provider data
mock_resource와 mock_data를 이용할 수 있다. 다만 defaults를 이용하여 특정 속성 값을 지정할 수 있다.
mock_provider "aws" {
# resource mocking
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
# data source mocking
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
}
해당 예시에서 resource와 data 모두 arn:aws:s3:::name 을 반환하게 된다.
source 를 이용해서 테스트 간에 mock provider 데이터 공유가 가능하다. mock data 파일은 .tfmock.hcl 또는 .tfmock.json 확장자를 가지며, mock_resource, mock_data, mock_provider 블록을 가질 수 있다.
# ./testing/aws/data.tfmock.hcl
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_provider "aws" {
source = "./testing/aws"
}
이렇게 하면 여러 파일에 동일한 정의를 복사하지 않고, 동일한 mock provider data를 공유할 수 있다.
직접 선언한 mock_resource와 mock_data와 source의 우선순위
mock_provider를 사용할 때, source로 mock_data를 불러오면서, 직접 mock_data나 mock_resource를 선언할 수 있다.
이 경우, source와 직접 선언한 mock_resource나 mock_data와 충돌이 일어나는 경우, 직접 선언한 값이 우선 적용된다.
overrides
mock provider외에도 override 블록을 이용하여 특정 리소스, datasource 및 모듈을 재정의 할 수 있다.
(테스트 환경에서 특정 값만 변경하고 싶거나, 모든 리소스에 공통적인 설정을 적용하고 싶은 경우)
- override_resource : 리소스의 값을 재정의 / terraform은 기본 provider를 호출하지 않음
- override_data : 데이터 소스의 값을 재정의 / terraform은 기본 provider를 호출하지 않음
- override_module: 모듈의 출력을 재정의 / terraform은 모듈에 어떤 리소스도 생성하지 않음
-> 위 세 블록은 terraform test 파일의 루트레벨과 run 블록에 배치할 수 있으며 override는 실제 provider와 mock provider 모두 함께 사용할 수 있으며, 기본 공급자 대신 계산된 값을 제공한다.
기본 구성 예시
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
module "credentials" {
source = "./modules/s3_data"
data_bucket_name = "my_company_bucket_name"
}
resource "local_file" "credentials_json" {
filename = "credentials.json"
content = jsonencode(module.credentials.data)
}
# ./modules/s3_data/main.tf
variable "data_bucket_name" {
type = string
}
# 기존 리소스 참조
data "aws_s3_object" "data_bucket" {
bucket = var.data_bucket_name
key = "credentials.json"
}
output "data" {
value = jsondecode(data.aws_s3_object.data_bucket.body)
}
mock provider에서 모듈의 aws_s3_object.data_bucket을 재정의
이 경우 terraform은 mock provider가 생성하는 경우에만 대상 data source를 재정의한다.
# main.tftest.hcl
mock_provider "aws" {
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
파일 또는 run 블록에서의 오버라이드
# main.tftest.hcl
mock_provider "aws" {}
# 파일 수준에서 정의된 오버라이드 블록
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
# 위에 정의된 파일 수준에서 정의된 오버라이드 블록이 해당 run 블록에서 사용된다.
run "test_file_override" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
# 로컬 오버라이드가 파일 수준의 오버라이드보다 우선된다.
run "test_run_override" {
# The value in this local override block takes precedence over the
# alternate defined in the file.
# 로컬 오버라이드
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"a_different_username\",\"password\":\"password\"}"
}
}
assert {
condition = jsondecode(local_file.credentials_json.content).username == "a_different_username"
error_message = "incorrect username"
}
}
전체 모듈을 오버라이드
# main.tftest.hcl
mock_provider "aws" {}
override_module {
target = module.credentials
# 기존 모듈의 출력을 덮어쓰기
outputs = {
data = { username = "username", password = "password" }
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
반복되는(repeated) 블록과 중첩된(nested) 속성
DynamoDB table 리소스는 replica 블록을 제공하여, 복제본을 쉽게 생성할 수 있다.
# main.tf
resource "aws_dynamodb_table" "my_table" {
name = "my_table"
hash_key = "key"
attribute {
name = "key"
type = "S"
}
replica {
region_name = "eu-west-2"
}
replica {
region_name = "us-east-1"
}
}
aws_dynamo_table 리소스는 루트 수준의 계산된 arn속성과 계산된 arn속성이 포함되는 replica 라는 집합 반복 블록이 포함되어 있다.
반면 mock_resource는 어떨까?
mock_resource "aws_dynamodb_table" {
defaults = {
arn = "aws:dynamodb:::my_table"
replica = {
arn = "aws:dynamodb:::my_replica"
}
}
}
이 경우에는 replica "aws_dynamo_table"들에 반환된 arn 값은 모든 인스턴스가 공유된다.
mock_resource에서의 replica는 모든 인스턴스들이 같은 arn을 공유하는 것이고, 일반 resource는 각각의 replica마다 고유한 arn이 존재한다.
'IaC' 카테고리의 다른 글
테라폼 스터디 - 10주차 (0) | 2025.02.15 |
---|---|
테라폼 스터디 9주차 - 추가 내용 (0) | 2025.02.09 |
테라폼 스터디 8주차 - 테라폼 코드 테스트 (0) | 2025.02.01 |
테라폼 스터디 - 7주차 (프로덕션 수준의 인프라 구성) (0) | 2025.01.18 |
테라폼 스터디 6주차 (0) | 2025.01.12 |