본문 바로가기

[Terraform] 테라폼 반복문 본문

DevOps

[Terraform] 테라폼 반복문

겨울바람_ 2024. 8. 9. 21:34

Terraform 반복문

list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우 반복문을 통해 관리할 수 있다. 또한, 단순한 자료구조뿐만 아니라 리소스 내에 생성된 블록에 대한 것도 반복문을 통해 코드의 중복 없이 동적으로 생성이 가능하다.

count

리소스 또는 모듈 블록에 count값이 정수인 인수가 포함된 경우 선언된 정수 값만큼 리소스나 모듈을 생성하게 된다.

count에서 생성되는 참조값은 count.index이며, 반복하는 경우 0부터 1씩 증가해 인덱스가 부여된다. main.tf를 아래와 같이 작성해보자.

resource "local_file" "abc" {
    count = 5
    content = "abc"
    filename = "${path.module}/abc.txt"
}

 

위 코드를 실행시켰을 때 목표로 하는 결과는 다섯 개의 파일이 생성되는 것이지만, 파일명에는 변화가 없기 때문에 하나의 파일만 존재하게 된다. 본래의 의도대로 실행되도록 하려면 다음과 같이 count.index 값을 추가하면 된다.

resource "local_file" "abc" {
    count = 5
    content = "abc"
    filename = "${path.module}/abc${count.index}.txt"
}


count를 프로그래밍 언어의 반복문 인덱스처럼 활용하는 방법은 다음과 같다.

variable "names" {
    type = list(string)
    default = ["a", "b", "c"]
}

resource "local_file" "abc" {
    count = length(var.names)
    content = "abc"
    filename = "${path.module}/abc-${var.names[count.index]}.txt"
}

resource "local_file" "def" {
    count = length(var.names)
    content = local_file.abc[count.index].content
    filename = "${path.module}/def-${element(var.names, count.index)}.txt"
}

 

local_file.abclocal_file.defvar.names에 선언되는 값에 영향을 받아 동일한 개수만큼 생성하게 된다.

count로 생성되는 리소스의 경우 "<리소스 타입>.<이름>[<인덱스 번호>], 모듈의 경우 module.<모듈 이름>[<인덱스 번호>]로 해당 리소스의 값을 참조한다.

for_each

리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 map또는 set이면, 선언된 key 값 개수만큼 리소스를 생성하게 된다.

resource "local_file" "abc" {
    for_each {
        a = "content a"
        b = "content b"
    }
    content = each.value
    filename = "${path.module}/${each.key}.txt"
}

 

생성되는 리소스의 경우 <리소스 타입>.<이름>[<key>], 모듈의 경우 module.<모듈 이름>[<key>]로 해당 리소스의 값을 참조한다. 이 참조 방식을 통해 리소스 간 종속성을 정의하기도 하고 변수로 다른 리소스에서 사용하거나 출력을 위한 결과 값으로 사용한다.

for_each를 활용한 반복 참조 예시는 다음과 같다.

variable "names" {
    default = {
        a = "content a"
        b = "content b"
        c = "content c"
    }
}

resource "local_file" "abc" {
    for_each = var.names
    content = each.value
    filename = "${path.module}/abc-${each.key}.txt"
}

resource "local_file" "def" {
    for_each = local_file.abc
    content = each.value.content
    filename = "${path.module}/def-${each.key}.txt"
}

for

for문은 복합 형식 값의 형태를 변환하는데 사용된다. 예를 들어 list 값의 포맷을 변경하거나 특정 접두사를 추가할 수도 있고, output에 원하는 형태로 반복적인 결과를 표현할 수 있다.

  • list의 경우 값 또는 인덱스와 값을 반환한다.
  • map의 경우 키 또는 키와 값을 반환한다.
  • set의 경우 키 값을 반환한다.
variable "names" {
    default = ["a", "b", "c"]
}

resource "local_file" "abc" {
    content = jsonencode([for s in var.names: upper(s)]) # result: ["A", "B", "C"]
    filename = "${path.module}/abc.txt"
}

 

for문을 사용할 때 대상이 되는 타입에 따라 다음과 같은 규칙이 적용된다.

  • list 타입의 경우 반환 받는 값이 하나로 되어 있으면 값을, 두 개인 경우 앞의 인수가 인덱스를 반환하고 두 번째 인수가 값을 반환한다.
  • map 타입의 경우 반환 받는 값이 하나로 되어 있으면 키를, 두 개인 경우 앞의 인수가 키를 반환하고 두 번째 인수가 값을 반환한다.
  • for문의 결과값은 for문을 감싸고 있는 괄호의 종류에 따라 다르게 결정된다.
    • [ ] 형태로 감싸고 있을 경우 tuple로 반환된다.
    • { } 형태로 감싸고 있을 경우 object 형태로 반환된다.
      • object 형태의 경우 키와 값을 => 기호로 구분한다.

list 타입에 적용할 수 있는 규칙을 간단한 예제를 통해서 확인해보자.

variable "names" {
  type = list(string)
  default = ["a", "b"]
}

output "tuple_upper_value" {
  value = [for v in var.names: upper(v)]
} 

output "tuple_index_value" {
  value = [for i, v in var.names: "${i}: ${v}"]
}

output "object_upper_value" {
  value = {for v in var.names: v => upper(v)}
}

output "tuple_with_if" {
  value = [for v in var.names: upper(v) if v != "a"]
}

 

위의 HCL을 terraform apply 명령어를 통해 실행할 경우 아래와 같은 output 결과가 나오게 된다. 작성된 순서와 무관하게 결과가 표시되기 때문에 output의 이름을 잘 확인해보자.

이번에는 좀 더 복잡한 예제를 통해 확인해보자.

variable "members" {
  type = map(object({
    role = string
    group = string
  }))

  default = {
    "memberA" = {
      role = "member",
      group = "dev"
    }
    "adminB" = {
      role = "admin",
      group = "dev"
    }
    "devopsC" = {
      role = "member",
      group = "devops"
    }
  }
}

output "get_all_group" {
  value = [for k, v in var.members: "${k} is a ${v.group}"]
} 

output "get_only_admin" {
  value = {
    for name, user in var.members: name => user.role
    if user.role == "admin"
  }
}

output "C" {
  value = {
    for name, user in var.members: user.role => name...
  }
}

 

위의 HCL을 terraform apply 명령어를 통해 실행할 경우 아래와 같은 output 결과가 나오게 된다. 작성된 순서와 무관하게 결과가 표시되기 때문에 output의 이름을 잘 확인해보자.

특히 주목해서 살펴봐야 할 outputget_member_group_by_role인데, name 뒤에 ...이 붙은 것을 확인할 수 있다. 이는 object 타입으로 결과를 반환하는 경우 키 값은 고유해야 하기 때문에 값 뒤에 그룹화 모드 심볼인 ...를 붙여 키의 중복을 방지하는 것이다.

 

...는 SQL의 group by문과 동일한 역할을 한다.

dynamic

테라폼을 통해 리소스를 구성하다 보면 countfor_each를 통해 리소스 전체를 여러 개 생성하는 것 이외에도 리소스 내에 선언되는 구성 블록들을 여러 개 작성해야 할 때가 있다.

 

동일한 요소가 리소스 선언 내부에서 블록 형태로 여러 개 정의되는 상황에 dynamic을 통해 동적으로 생성할 수 있다.

 

dynamic 블록을 작성하려면, 기존 블록의 속성 이름을 dynamic 블록의 이름으로 선언하고 기존 블록 속성에 정의되는 내용을 content 블록에 작성한다. 반복 선언에 사용되는 반복문 구문은 for_each를 사용한다.

 

기존 for_each문 사용시 각 속성에 key, value가 할당되었다면, dynamic에서는 dynamic에 지정한 이름에 대한 속성이 할당된다.

 

간단한 예시를 통해 dynamic의 사용법에 대해 알아보자.

data "archive_file" "dotfiles" {
  type = "zip"
  output_path = "${path.module}/dotfile.zip"

  source {
    content = "hello a"
    filename = "${path.module}/a.txt"
  }

  source {
    content = "hello b"
    filename = "${path.module}/b.txt"
  }

  source {
    content = "hello c"
    filename = "${path.module}/c.txt"
  }
}

 

위의 HCLterraform apply를 통해 실행시키게 되면 경로에 dotfiles.zip 이라는 압축 파일이 생성되고 해당 압축 파일을 풀어보면 리소스 내에 정의된 source 블록에 맞게 텍스트 파일이 생성된 것을 확인할 수 있다

참고로 처음 archive 프로바이더를 사용하는 경우에는 잊지 말고 terraform init을 실행하자.

이제 data 리소스 내에 반복적으로 정의되어 있는 source 블록을 dynamic을 활용하여 동적으로 설정하는 코드로 변경해보자.

 

우선 각 리소스 블록 내에서 매번 변동되는 값인 contentfilename 부분을 추출하여 variable 리소스로 만든다.

variable "names" {
    default = {
        a = "hello a"
        b = "hello b"
        c = "hello c"
    }
}

data "archive_file" "dotfiles" {
  type = "zip"
  output_path = "${path.module}/dotfile.zip"

  source {
    content = [ ]
    filename = "${path.module}/[ ]"
  }
}

 

이제 반복적으로 생성되는 source 리소스에 dynamic을 붙이고 for_eachvariable 리소스에서 반복 구문을 추출한다.

variable "names" {
    default = {
        a = "hello a"
        b = "hello b"
        c = "hello c"
    }
}

data "archive_file" "dotfiles" {
  type = "zip"
  output_path = "${path.module}/dotfile.zip"

  dynamic "source" {
    for_each = var.names
    content = {
        content = source.value
        filename = "${path.module}/${source.key}.txt"        
    }
  }
}

 

해당 코드를 실행시켜보면 이전에 dynamic없이 작성했던 코드와 동일하게 동작하는 것을 확인할 수 있다. 이러한 반복문을 적절히 사용하는 것으로 테라폼 사용 시 중복되는 코드를 줄여 유지 보수 및 확장에 용이한 코드 작성이 가능해진다.

Comments