새소식

IaC/Terraform

[setproduct] 테라폼에서 Cartesian Product 반복하기

  • -

최근 T101 테라폼 스터디에서 테라폼을 공부하게 되면서 굉장히 빠르게 테라폼 실력이 늘었다.

이번에 Catesian Product 을 활용해서 리소스를 반복 생성해야되는 특이한 상황에 부딪혔는데 이를 해결한 내용을 포스팅해볼까 한다.



포스팅 순서는 다음과 같다.

  1. Cartesian Product 를 사용해야 했던 상황
  2. setproduct 내장 함수 알아보기
  3. setproduct 를 사용한 Cartesian Product 반복문 생성

 

 

포스팅에 사용된 테라폼 코드는 아래 깃허브 주소에 저장해두었다.

 

GitHub - AlarmKimKB/terraform_cartesian

Contribute to AlarmKimKB/terraform_cartesian development by creating an account on GitHub.

github.com

 

 

 

0. Case Study


부딪힌 상황은 다음과 같다.

  1. 기존에 Terraform 으로 만들어진 인프라 존재
  2. 타 계정에서 만들어진 TGW 를 공유받아 현 계정에 Attachment 됨
  3. 해당 TGW 로 향하는 CIDR 에 대한 라우팅 설정 테라폼 코드 필요
  4. 문제) 현재 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가지 방법이 떠올랐다.
  1. 직접 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. 변경하기 위해 테라폼 코드를 알아야 한다는 점

 

1, 2번은 약간의 수고를 덜면 괜찮은 것이었지만 문제는 3번이었다.
해당 코드는 완성한 후 고객사 측으로 전달해줘야 했는데 고객사 측에서 테라폼 코드를 잘 모르고 있었기 때문에

이후 변경사항이 생기면 계속 연락이 올 수도 있다는 점이었다.

물론, 콘솔에서 해결할 수도 있겠지만...

 

아무튼 이런 저런 이유도 있고 테라폼 공부도 할겸 다른 방법을 찾아보고자 했다.
머릿속에 떠오른 두 번째 방법은 바로 반복문이었다.

 

  1. 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 반복은 그 특성상 다음과 같은 문제가 발생한다.

  1. 순서대로 값이 대입되므로 세 번째 라우팅 테이블에는 아무 값도 입력되지 않음
  2. 첫 번째, 두 번째 라우팅 테이블에는 값이 각각 하나 밖에 입력되지 않음

 

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 리소스를 생성하기 위해 내가 필요한 값은 아래와 같았다.
  1. route_table_id
  2. destination_cidr_block
  3. 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_idaws_route_table.private_routing_tables.id 값
destination_cidr_blockvar.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 routeaws_route 을 함께 사용하면, plan 및 apply 시 라우팅 누락이 발생

 

원인은 Dynamic Route 은 변경이 되면 route_table 자체를 재생성하게 되고,

aws_route 는 기존의 route_table 에 라우팅 설정을 추가하고 제거하는 것이기 때문.

 

때문에 실제 사용 시 하나만 사용할 것을 권장 !!

Contents

포스팅 주소를 복사했습니다