본문 바로가기
DevOps/Terraform

[Terraform] 테라폼의 팁과 요령: 반복문

by BenKangKang 2023. 3. 27.

테라폼은 선언적 언어로써 추론하기 쉬운 장점이 있다. 다만 선언적 언어는 보통 반복문, 조건문이 없어 논리적인 수행이 어려운데 테라폼은 이를 보완하기 위한 다양한 기능들을 제공한다.

 

테라폼을 관리하는 팁과 요령에 대해 알아보자.

 

1. 반복문

테라폼은 조금씩 다른 상황에 사용하도록 고안된 몇 가지 반복문 구성을 제공한다

  • count 매개 변수: 리소스를 반복
  • for_each 표현식: 리소스 내에서 리소스 및 인라인 블록을 반복
  • for 표현식: 리스트와 맵을 반복
  • for 문자열 지시어: 문자열 내에서 리스트와 맵을 반복

1.1 count 매개 변수를 이용한 반복

count?

  • 같은 리소스를 n개 생성할 때 사용하는 매개 변수
  • 가장 오래되고 단순한 반복 구조.

상황

  • 단순한 자원의 사본을 생성하는 경우 사용.

예시

resource "aws_iam_user" "example" {
  count = 3
  name  = "${var.user_name_prefix}.${count.index}"
}
  • count.index 사용해서 interation 인덱스를 얻을 수 있음.

특징

1. 리소스 배열이 생성되기 때문에 Array lookup 구문을 사용해야 한다.

resource "aws_iam_user" "example" {
  count = length(var.user_names)
  name  = var.user_names[count.index]
}

 

Array lookup 예시

output "neo_arn" {
  value       = aws_iam_user.example[0].arn
  description = "The ARN for user Neo"
}

스플랫 연산자로 전체 ARN을 출력하는 예시

output "all_arns" {
  value       = aws_iam_user.example[*].arn
  description = "The ARNs for all users"
}

2. count 를 사용하여 전체 리소스를 반복할 수는 있지만 인라인 블록을 반복할 수 없다

resource "aws_autoscaling_group" "example" {
  // 태그 같은 인라인 블록에 count 를 지정할 수 없다.
  tag {
    key                 = "Name"
    value               = "terraform-asg-example"
    propagate_at_launch = true
  }
}

3. count 매개변수 사용하는 경우 해당 배열의 index로 리소스를 식별하기 때문에 변경에 유의해야 한다.

아래와 같이 3개의 유저를 생성했다고 가정

variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["neo", "trinity", "morpheus"]
}

이때 index 1에 위치한 trinity 을 삭제해서 apply한다면? index 를 식별자로 보기 때문에 trinity 는 morpheus 로 변경되고, 기존의 morpheus 가 삭제된다.

실제 예시

  • count를 사용하는 경우는 거의 없었음.
  • 중간에 삭제되어도 상관 없는 리소스에만 사용하는 것으로 보임.
resource "aws_autoscaling_attachment" "asg_attachment_0" {
  count = length(var.autoscaling_group_names)

  alb_target_group_arn   = aws_lb_target_group.tg.arn
  autoscaling_group_name = var.autoscaling_group_names[count.index]
}
resource "kubernetes_config_map" "redis-failover-haproxy" {
  count = var.delete ? 0 : 1

1.2 for_each 표현식 사용한 반복문 처리

for_each?

  • 리스트, 집합, 맵을 사용해서 전체 리소스의 복사본을 만들 수 있는 구문.
    • 정확히 for_each 구문은 집합과 맵만 지원.
  • 리소스 내 인라인 블록의 복사본도 생성할 수 있음.

상황

  • count 를 사용하면 안되는 상황에 사용 → 자원 각각이 고유한 경우 (trinity, morpheus …)

예시

resource "aws_iam_user" "example" {
  for_each = toset(var.user_names)
  name     = each.value
}
  • 리스트를 집합으로 변환하여 사용하는 예시
  • each.key, each.value 를 사용하여 항목키와 값에 접근할 수 있음.

특징

1. 모든 ARN을 추출해야 할 경우 약간의 추가 작업이 필요하다.

output "all_arns" {
  value = values(aws_iam_user.example)[*].arn
}

2. 컬렉션 중간 항목도 안전하게 제거할 수 있다.

index를 식별자로 사용하는 count 와 달리 컬렉션 중간 항목을 삭제하더라도 정확히 목표한 리소스만 삭제할 수있다.

 

3. 동적 인라인 블록을 만들 수 있다.

resource "aws_autoscaling_group" "example" {
  ...

  tag {
    key                 = "Name"
    value               = var.cluster_name
    propagate_at_launch = true
  }

  dynamic "tag" {
    // 1. 이곳에서 그냥 배열을 넘기면 key는 index, value는 항목이 된다.
    // 2. 맵을 넘길 경우 key, value는 맵의 키-값 쌍 중 하나가 된다.
    for_each = {
      for key, value in var.custom_tags:
      key => upper(value)
      if key != "Name"
    }

    content {
      key                 = tag.key
      value               = tag.value
      propagate_at_launch = true
    }
  }

}

1.3 for 표현식을 이용한 반복문

for?

  • 단일 값을 생성하기 위해 반복이 필요한 경우 사용

상황

  • 리스트 값을 변경할 때
  • 리스트 값을 필터링할 때

예시

리스트

variable "names" {
  description = "A list of names"
  type        = list(string)
  default     = ["neo", "trinity", "morpheus"]
}

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

output "short_upper_names" {
  value = [for name in var.names : upper(name) if length(name) < 5]
}

variable "hero_thousand_faces" {
  description = "map"
  type        = map(string)
  default     = {
    neo      = "hero"
    trinity  = "love interest"
    morpheus = "mentor"
  }
}

# 배열로 출력
output "bios" {
  value = [for name, role in var.hero_thousand_faces : "${name} is the ${role}"]
}
# 출력 bios = ['neo is the hero', ...]

# 맵으로 출력
output "upper_roles" {
  value = {for name, role in var.hero_thousand_faces : upper(name) => upper(role)}
}
# 출력 { "NEO" = "HERO" ... }

실제 예시

리스트 출력 예시

resource "aws_lb" "alb" {
  tags = {
    Domain = join(" ", [for domain in var.domains: replace(domain, "*.", "")])
  }
}
	domains = [
    "*.naver.com",
    "naver.com"
  ]

맵 출력 예시

# Assume that you have the issued certificate
data "aws_acm_certificate" "cert" {
  domain   = var.cert_domain
  statuses = ["ISSUED"]
}
hosts = merge(
    {for domain in data.aws_acm_certificate.cert.*.domain : domain => domain}

1.4 문자열 지시자를 사용하는 반복문

  • 문자열 보간과 같이 for, if 를 제어할 수 있다.
  • 백분율 부호(%{…})를 사용한다.
output "for_directive" {
  value = <<EOF
%{ for name in var.names }
  ${name}
%{ endfor }
EOF
}
  • 물결표를 사용해 줄 바꿈 같은 공백을 없앨 수 있다.
output "for_directive_strip_marker" {
  value = <<EOF
%{~ for name in var.names }
  ${name}
%{~ endfor }
EOF
}

 

참고

- 테라폼 Up & Running

댓글