본문 바로가기

DevOps/Terraform

[T102 2주차] (6) 테라폼 반복문

cloudNet@ 팀의 가시다 님이 진행하는 테라폼 102 스터디 2주차 정리입니다.

2주차 내용: 

테라폼 반복문

  • count
  • for_each
  • for
  • dynamic

1. count

  • list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우 동일한 내용에 대해 테라폼 구성 정의를 반복적으로 하지 않고 관리할 수 있다.
  • 리소스나 모듈을 반복적으로 만든다.
  • 5개의 파일이 생성되어야 하지만 파일명이 동일하여 결과적으로 하나의 파일만 존재 ← count 사용 시 주의
resource "local_file" "abc" {
  count    = 5
  content  = "abc"
  filename = "${path.module}/abc.txt"
}

output "filecontent" {
  value = local_file.abc.*.content
}

output "fileid" {
  value = local_file.abc.*.id
}

output "filename" {
  value = local_file.abc.*.filename
}
  • 파일명이 바뀌면?
resource "local_file" "abc" {
  count    = 5
  content  = "abc${count.index}"
  filename = "${path.module}/abc${count.index}.txt"
}

output "fileid" {
  value = local_file.abc.*.id
}

output "filename" {
  value = local_file.abc.*.filename
}

output "filecontent" {
  value = local_file.abc.*.content
}
  • 파일명과 content가 변경되었다.

  • 때때로 여러 리소스나 모듈의 count로 지정되는 수량이 동일해야 하는 상황이 있다. 이 경우 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
  # element function 활용
  filename = "${path.module}/def-${element(var.names, count.index)}.txt"
}
  • 실행후 확인: var.name에 선언되는 값에 영향을 받아 동일한 갯수만큼 생성하게 된다.
  • local_file.def의 경우 local_file.abc와 개수가 같아야 content에 선언되는 인수 값에 오류가 없을 것이므로 서로 참조되는 리소스와 모듈의 반복정의에 대한 공통의 영향을 주는 변수로 관리할 수 있다.
  • 외부 변수가 list 타입인 경우 중간에 값이 삭제되면 인덱스가 줄어들어 의도했던 중간 값에 대한 리소스만 삭제되는 것이 아니라 이후의 정의된 리소스들도 삭제되고 재생성된다. → 아래 실습으로 확인
# 외부 변수 b를 제거
variable "names" {
  type    = list(string)
  default = ["a", "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
  # element function 활용
  filename = "${path.module}/def-${element(var.names, count.index)}.txt"
}
  • 의도한 상황과 다른 결과

  • 배열의 중간에 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨짐
  • 테라폼이 인덱스 번호를 리소스 식별자로 보기 떄문에, 인덱스 1에서는 계정 생성, 인덱스 2에서는 계정 삭제로 해석
  • 즉, count 사용시 목록 중간 항목을 제거하면 테라폼은 해당 항목 뒤에 있는 모든 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만듬


2. for_each

  • for_each는 count 보다 사용 가능성이 높다
  • 반복문, 선언된 key 값 개수만큼 리소스를 생성
resource "local_file" "abc" {
  for_each = {
    a = "content a"
    b = "content b"
  }
  content  = each.value
  filename = "${path.module}/${each.key}.txt"
}
  • list일 경우: 중복 허가 ? set일 경우 : 중복 불허, 고유값 ? type 설정은 어디에? 
  • 기본값은 list로 연산됨
resource "local_file" "abc" {
  for_each = {
    a = [4,5,6]
    b = [1,1,2,3] # b = [1,1,2,3] 인 경우는? 리스트가 default임으로 
  }
  content  = each.value[1] # 리스트가 default임으로 each.value[1]은 1임 
  filename = "${path.module}/${each.key}.txt"
}
  • for_each가 설정된 블록에서는 each 속성을 사용해 구성을 수정할 수 있다
    • each.key : 이 인스턴스에 해당하는 map 타입의 key 값
    • each.value: 이 인스턴스에 해당하는 map의 value 값
#local_file.abc는 변수의 map 형태의 값을 참조, local_file.def의 경우 local_file.abc 
# 의도한 결과가 map으로 반환되므로 다시 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"
}
  • key 값은 count의 인덱스와 달리 고유하므로 중간에 값이 삭제 되더라도 해당 리소스만 삭제한다.
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # local_file.abc["b"] will be destroyed
  # (because key ["b"] is not in for_each map)

3. for

  • 예를 들어 list 값의 포맷을 변경하거나 특정 접두사 prefix를 추가할 수도 있고, output에 원하는 형태로 반복적인 결과를 표현할 수 도 있다.
    • list 타입의 경우 값 또는 인덱스와 값을 반환
    • map 타입의 경우 키 또는 키와 값에 대해 반환
    • set 타입의 경우 키 값에 대해 반환
variable "names" {
  default = ["a", "b", "c"]
}

resource "local_file" "abc" {
  content  = jsonencode(var.names) # 결과 : ["a", "b", "c"]
  filename = "${path.module}/abc.txt"
  }
  • map  유형의 경우 반환 받는 값이 하나로 되어 있으면 를, 두 개의 경우 앞의 인수가 를 반환하고 뒤의 인수가을 반환
variable "members" {
  type = map(object({
    role = string
  }))
  default = {
    ab = { role = "member", group = "dev" }
    cd = { role = "admin", group = "dev" }
    ef = { role = "member", group = "ops" }
  }
}

output "A_to_tupple" {
  value = [for k, v in var.members : "${k} is ${v.role}"]
}

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

output "C_group" {
  value = {
    for name, user in var.members : user.role => name...
  }
}
  • 실행 후 terraform console을 통한 확인
# 
terraform console
>
-----------------
var.members
[for k, v in var.members : "${k} is ${v.role}"]
{for name, user in var.members : name => user.role}
{for name, user in var.members : name => user.role  if user.role == "admin"}
{for name, user in var.members : user.role => name...}
exit
-----------------

4. dynamic

리소스 내부 속성 블록 동적인 블록으로 생성

  • count나 for_each 구문은 리소스 전체를 여러개 생성 하는 것 
  • dynamic은 리소스 내에 선언되는 블록을 다중으로 생성 하는 것
  • 예를 들면 AWS Security Group 리소스 구성 ( ingress, egress )
resource "aws_security_group" "example" {
  name        = "example-security-group"
  description = "Example security group"
  vpc_id.     = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    ipv6_cidr_blocks = ["::/0"]
  }
}
  • 일반적인 블록 속성 반복 적용 시 
resource "provider_resource" "name" {
  name = "some_resource"

  some_setting {
    key = a_value
  }

  some_setting {
    key = b_value
  }

  some_setting {
    key = c_value
  }

  some_setting {
    key = d_value
  }
}
  • dynamic 블록 적용 시
resource "provider_resource" "name" {
  name = "some_resource"

  dynamic "some_setting" {
    for_each = {
      a_key = a_value
      b_key = b_value
      c_key = c_value
      d_key = d_value
    }

    content {
      key = some_setting.value
    }
  }
}

반복문의 개념정의를 했습니다.

다음 포스팅에서는 스터디 전용 / 실습 부분을 함께 풀어보도록 하겠습니다.