[setproduct] 테라폼에서 Cartesian Product 반복하기
- -
최근 T101 테라폼 스터디에서 테라폼을 공부하게 되면서 굉장히 빠르게 테라폼 실력이 늘었다.
이번에 Catesian Product 을 활용해서 리소스를 반복 생성해야되는 특이한 상황에 부딪혔는데 이를 해결한 내용을 포스팅해볼까 한다.
포스팅 순서는 다음과 같다.
- Cartesian Product 를 사용해야 했던 상황
- setproduct 내장 함수 알아보기
- setproduct 를 사용한 Cartesian Product 반복문 생성
포스팅에 사용된 테라폼 코드는 아래 깃허브 주소에 저장해두었다.
GitHub - AlarmKimKB/terraform_cartesian
Contribute to AlarmKimKB/terraform_cartesian development by creating an account on GitHub.
github.com
0. Case Study
부딪힌 상황은 다음과 같다.
- 기존에 Terraform 으로 만들어진 인프라 존재
- 타 계정에서 만들어진 TGW 를 공유받아 현 계정에 Attachment 됨
- 해당 TGW 로 향하는 CIDR 에 대한 라우팅 설정 테라폼 코드 필요
- 문제) 현재 Route Table 3개, 라우팅 설정이 필요한 CIDR 는 2개 (추후 그 이상이 필요할 가능성 존재)
- 간단한 상황 아키텍처

나의 VPC 가 10.0.0.0/16 대역이라고 할 때,
TGW 로 보내야되는 CIDR 는 10.100.0.0/16 , 192.168.10.0/24 의 2가지 대역이라고 가정한다.
최초, 문제를 해결할 수 있는 2가지 방법이 떠올랐다.
- 직접 route 리소스를 생성해주는 것
- 아래와 같이 기존에 생성되어 있던 Route Table 에 직접 변경을 가하는 것이다.
resource "aws_route_table" "private_routing_tables" { (생략) route { cidr_block = "10.100.0.0/16" transit_gateway_id = "TGW-ID" } route { cidr_block = "192.168.10.0/24" transit_gateway_id = "TGW-ID" } }
하지만 또 다른 문제는 다음이었다...
- 라우팅 설정이 계속 변경될 수 있다는 점
- 그에따라 모듈 부분을 계속 변경해야된다는 점
- 변경하기 위해 테라폼 코드를 알아야 한다는 점
1, 2번은 약간의 수고를 덜면 괜찮은 것이었지만 문제는 3번이었다.
해당 코드는 완성한 후 고객사 측으로 전달해줘야 했는데 고객사 측에서 테라폼 코드를 잘 모르고 있었기 때문에
이후 변경사항이 생기면 계속 연락이 올 수도 있다는 점이었다.
물론, 콘솔에서 해결할 수도 있겠지만...
아무튼 이런 저런 이유도 있고 테라폼 공부도 할겸 다른 방법을 찾아보고자 했다.
머릿속에 떠오른 두 번째 방법은 바로 반복문이었다.
- count 반복문을 통해서 라우팅 리소스를 반복 생성해주면 안될까??
그래서 바로 시도를 해보았다.
현재 나의 AWS 리소스는 이전의 그림과 동일하다.

Private Subnet 마다 Nat 와 연결된 각각의 라우팅 테이블 (3개) 가 존재하는데, '2개'의 라우팅 설정이 필요하다.
count 반복을 사용하면 다음과 같이 표현된다.
variable "add_route" { description = "Add TGW Routing" type = list default = ["10.100.0.0/16" , "192.168.10.0/24"] } resource "aws_route" "add_route" { count = length(var.add_route) route_table_id = aws_route_table.private_routing_tables[count.index].id destination_cidr_block = var.add_route[count.index] transit_gateway_id = data.aws_ec2_transit_gateway_attachment.tgw_attachment.id }
하지만 count 반복은 그 특성상 다음과 같은 문제가 발생한다.


- 순서대로 값이 대입되므로 세 번째 라우팅 테이블에는 아무 값도 입력되지 않음
- 첫 번째, 두 번째 라우팅 테이블에는 값이 각각 하나 밖에 입력되지 않음
count 반복에 대한 특성은 아래 포스팅을 참고하길 바란다.
5주차(1)_Terraform 반복문_count
CloudNet@ 팀의 가시다님께서 Leading 하시는 Terraform T101 Study 내용 요약 해당 Terraform Study 는 Terraform Up and Running 책을 기반으로 진행 중입니다. 테라폼에는 반복되는 여러 리소스를 한 번에 배포할 수
kimalarm.tistory.com
때문에 다른 방법을 찾아보던 중 테라폼의 setproduct 내장 함수를 활용해 해당 문제를 해결할 수 있었다.
결과적으로 setproduct 를 사용하면 다음과 같이 문제를 해결할 수 있다.

2. setproduct
테라폼 내장함수인 setproduct 는 리스트 간 조합 가능한 모든 요소를 출력해준다.
다음의 예를 통해 setproduct 가 어떻게 동작하는 지 감이 올 것이다.
List A = [1, 2, 3] List B = [a, b] setproduct(a, b) = [ [1, a], [1, b], [2, a], [2, b], [3, a], [3, b], ]
자세한 내용은 테라폼 공식 문서를 확인해보자.
setproduct - Functions - Configuration Language | Terraform | HashiCorp Developer
The setproduct function finds all of the possible combinations of elements from all of the given sets by computing the cartesian product.
developer.hashicorp.com
3. Cartesian Product 반복
- aws_route 리소스를 생성하기 위해 내가 필요한 값은 아래와 같았다.
- route_table_id
- destination_cidr_block
- transit_gateway_id
내가 신경써야될 Route Table , Route 설정에서 원하는 값만 뽑아내어 새로운 리스트를 만들었다.
로컬 변수 사용.
// add_route 변수 내용 variable "add_route" { description = "Add TGW Routing" type = list(object({ dst_cidr = string create_route = bool })) default = [ { dst_cidr = "10.100.0.0/16" , create_route = true }, { dst_cidr = "192.168.10.0/24" , create_route = true } ] }
// add_route 변수와 aws_private_route_table 을 활용해 새로운 로컬 변수 생성 locals { add_route = [ for pair in setproduct(aws_route_table.private_routing_tables, var.add_route) : { route_table_id = pair[0].id destination_cidr_block = pair[1].dst_cidr create_route = pair[1].create_route route_table_name = pair[0].tags.Name } ] }
setproduct 와 for 반복문을 함께 돌려서 ' : ' 뒤에 내가 원하는 값을 넣어주었다.
route_table_id 는 aws_route_table.private_routing_tables.id 값
destination_cidr_block 은 var.add_route 의 dst_cidr 값
해당 원리를 이용하면 어떤 식으로든지 원하는 리스트를 생성해낼 수 있다.
해당 변수의 값은 다음과 같다.

딱 봐도 내가 원하는 대로 2*3 의 모든 경우의 수가 들어가있는 것을 확인할 수 있다.
또한, for_each 반복문을 돌리기 예쁘게 출력되어 있다.
다만 for_each 는 기본적으로 List 타입을 지원하지 않으니 해당 값을 map 형식으로 바꾼 후에 반복을 돌려야한다.
resource "aws_route" "add_route" { for_each = { for pair in local.add_route : "${pair.route_table_name}:${pair.destination_cidr_block}" => pair if pair.create_route == true } route_table_id = each.value.route_table_id destination_cidr_block = each.value.destination_cidr_block transit_gateway_id = data.aws_ec2_transit_gateway_attachment.tgw_attachment.id }
for_each 를 위해 생성한 로컬 변수를 for 반복문을 사용하여 map 형식으로 바꿔주었다.
유일한 map object 명을 지정하기 위해 "${pair.route_table_name}:${pair.destination_cidr_block}" 를 사용했다.
또한, 각 서브넷 마다 TGW 로 보내는 라우팅을 생성할지 말지 선택하기 위해 if 조건문을 부여했다.
for_each 를 돌리기 위해 map 형식으로 치환한 값이 궁금하면 아래의 사진을 참고해보자.

Cartesian Product 반복이 종료된 결과 화면이다.

주의 사항
route_table 내 dynamic route 와 aws_route 을 함께 사용하면, plan 및 apply 시 라우팅 누락이 발생
원인은 Dynamic Route 은 변경이 되면 route_table 자체를 재생성하게 되고,
aws_route 는 기존의 route_table 에 라우팅 설정을 추가하고 제거하는 것이기 때문.
때문에 실제 사용 시 하나만 사용할 것을 권장 !!