본문 바로가기
Terraform/Terraform 101 Study

3주차 1편 테라폼 기초 - 기본 사용 3/3

by 개발자 영만 2024. 6. 30.

3.9 반복문 (2/2)

for_each

  • for_each를 사용하면 주어진 map 또는 set 타입의 컬렉션에 있는 각 요소를 개별적으로 처리할 수 있습니다.
  • 각 요소에 대해 each.key와 each.value를 통해 해당 요소의 키와 값을 사용할 수 있습니다.
    • each.key: 이 인스턴스에 해당하는 map 타입의 키 값.
    • each.value: 이 인스턴스에 해당하는 map의 값.
  • map 또는 set 타입이 아닌 표현식은 toset 등의 함수를 사용해 이 타입으로 변환해야 합니다.
  • 예시 - map
resource "azurerm_resource_group" "rg" {
  for_each = tomap({
    a_group       = "eastus"
    another_group = "westus2"
  })
  name     = each.key
  location = each.value
}
  • 예시 - set
resource "aws_iam_user" "the-accounts" {
  for_each = toset(["Todd", "James", "Alice", "Dottie"])
  name     = each.key
}
  • main.tf 파일 생성
resource "local_file" "abc" {
  for_each = {
    a = "content a"
    b = "content b"
  }
  content  = each.value
  filename = "${path.module}/${each.key}.txt"
}
  • 실행 후 반복문 동작 확인
# Terraform 초기화, 계획 및 적용
terraform init && terraform plan && terraform apply -auto-approve

# Terraform 상태 목록 및 상태 조회
terraform state list
terraform state show 'local_file.abc["a"]'
terraform state show 'local_file.abc["b"]'

# 파일 확인
ls *.txt
cat a.txt ;echo
cat b.txt ;echo

# Terraform 콘솔에서 변수 값 확인
echo "local_file.abc" | terraform console
echo 'local_file.abc["a"]' | terraform console
echo 'local_file.abc["a"].content' | terraform console
echo 'local_file.abc["b"].content' | terraform console

  • 리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 map 또는 set이면, 선언된 키 값 개수만큼 리소스를 생성하게 됩니다.
  • 생성되는 리소스의 경우 <리소스 타입>.<이름>[<key>]로 해당 리소스의 값을 참조합니다.
  • 모듈의 경우 module.<모듈 이름>[<key>]로 해당 모듈의 값을 참조합니다.
  • 이 참조 방식을 통해 리소스 간 종속성을 정의할 수 있습니다. 다른 리소스에서 변수로 사용하거나 출력을 위한 결과 값으로 사용할 수 있습니다.
  • main.tf 파일 수정
    • local_file.abc는 map 형태의 변수 값을 참조합니다.
    • local_file.def는 local_file.abc의 결과를 참조하며, 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"
}
  • 실행 후 확인
# Terraform 적용
terraform apply -auto-approve

# Terraform 상태 목록 및 상태 조회
terraform state list
terraform state show 'local_file.abc["a"]'
terraform state show 'local_file.def["a"]'

# 파일 확인
ls *.txt

# Terraform 콘솔에서 변수 값 확인
echo "local_file.abc" | terraform console
echo 'local_file.abc["a"]' | terraform console
echo 'local_file.abc["a"].content' | terraform console

  • for_each의 key 값은 고유한 값으로 중간에 값을 삭제한 후 다시 적용해도 삭제된 값에 대해서만 리소스를 삭제합니다.
  • main.tf 파일 수정 - 중간 값 삭제
variable "names" {
  default = {
    a = "content a"
    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"
}
  • 실행 후 확인
# Terraform 적용
terraform apply -auto-approve

# Terraform 상태 목록 및 상태 조회
terraform state list

# 파일 확인
ls *.txt

  • for_each는 인수로 map 또는 set of strings 형식만 허용합니다.
  • main.tf 파일 수정
resource "aws_iam_user" "the-accounts" {
  for_each = ["Todd", "James", "Alice", "Dottie"]
  name     = each.key
}
  • 확인
# Terraform 초기화, 업그레이드 및 계획
terraform init -upgrade && terraform plan

  • toset 함수를 사용하여 문자열들로 구성된 리스트를 set으로 변환합니다.
  • main.tf 파일 수정
resource "aws_iam_user" "the-accounts" {
  for_each = toset(["Todd", "James", "Alice", "Dottie"])
  name     = each.key
}
  • 확인
# Terraform 계획
terraform plan

Terraform 변수 및 데이터 유형 정리

https://www.youtube.com/watch?v=fhgGcC7wJoc&list=PL1mta2YyMpPXxFGBtSJyvoKnWTqZslm5p&index=2

  • 기본 유형
    • string: 문자열 유형
    • number: 숫자 유형
    • bool: true 또는 false
    • any: 모든 유형을 허용하는 일반적인 유형 표시
  • 집합 유형
    • list [<유형>]: 인덱스 기반 집합
      • 예: [ "apple", "orange", "banana" ]
      • 특징: 순서가 있고, 인덱스로 각 요소에 접근 가능합니다.
    • map (<유형>): 키-값 기반 집합이며 키에 따라 정렬됨
      • 예: { name = "John", age = 30, city = "New York" }
      • 특징: 키를 사용하여 값에 접근하며, 키는 정렬됩니다.
    • set (<유형>): 값 기반 집합이며 정렬되지 않음
      • 예: toSet(["apple", "orange", "banana"])
      • 특징: 중복을 허용하지 않으며, 정렬되지 않습니다.
    • object ({<인수 이름>=<유형>, …}): 속성과 해당 유형으로 구성된 복잡한 데이터 구조
      • 예: { name = string, age = number }
      • 특징: 명시적인 속성과 유형을 가지며, 객체의 속성은 순서가 없습니다.
    • tuple ([<유형>, …]): 고정된 크기와 타입을 가진 인덱스 기반 집합
      • 예: [ "apple", "orange", 42 ]
      • 특징: 고정된 순서를 가지며, 각 요소에는 순서대로 인덱스로 접근합니다.
  • main.tf 파일 수정
variable "string_a" {
  default     = "myString"
}

variable "string_b" {
  type        = string
  default     = "myString"
}

variable "string_c" {
  type        = string
  default     = myString
}


variable "number_a" {
  default = 123
}

variable "number_b" {
  type    = number
  default = 123
}

variable "number_c" {
  default = "123"
}


variable "boolean" {
  default = true
}


# (Array) list , set , tuple - value , [ ] 사용
variable "list_set_tuple_a" {
  default = ["aaa", "bbb", "ccc"]
}

variable "list_set_tuple_b" {
  type    = list(string)
  default = ["bbb", "ccc", "aaa"]
}

variable "list_set_tuple_c" {
  type    = set(string)
  default = ["bbb", "ccc", "aaa"]
}

variable "list_set_tuple_d" {
  default = ["aaa", 1, false]
}

variable "list_set_tuple_e" {
  type    = tuple([string, number, bool])
  default = ["aaa", 1, false]
}


# (Object) map , object - key : value , { } 사용
variable "map_object_a" {
  default = {"a" : "aaa", "b" : "bbb" , "c" : "ccc"}
}

variable "map_object_b" {
  type    = map(string)
  default = {"b" : "bbb" , "c" : "ccc", "a" : "aaa"}
}

variable "map_object_c" {
  default = {"name" : "gasida", "age" : 27 }
}

variable "map_object_d" {
  type = object({ name = string, age = number })
  default = {"name" : "gasida", "age" : 27 }
}
  • 확인
# Terraform 계획
terraform plan

# Terraform 콘솔에서 데이터 유형 확인
terraform console
-----------------
# string
var.string_a
var.string_b
type(var.string_a)
type(var.string_b)

# number
var.number_a
var.number_b
var.number_c
type(var.number_a)
type(var.number_b)
type(var.number_c)

# boolean
var.boolean
type(var.boolean)

# list , set , tuple - 'value'
var.list_set_tuple_a
var.list_set_tuple_b
var.list_set_tuple_c
var.list_set_tuple_d
var.list_set_tuple_e
type(var.list_set_tuple_a)
type(var.list_set_tuple_b)
type(var.list_set_tuple_c)
type(var.list_set_tuple_d)
type(var.list_set_tuple_e)

var.list_set_tuple_a[0]
type(var.list_set_tuple_a[0])

var.list_set_tuple_b[0]
type(var.list_set_tuple_b[0])

var.list_set_tuple_d[0]
var.list_set_tuple_d[1]
var.list_set_tuple_d[2]
type(var.list_set_tuple_d[0])
type(var.list_set_tuple_d[1])
type(var.list_set_tuple_d[2])

var.list_set_tuple_e[0]
type(var.list_set_tuple_e[0])

# map , object - 'key : value'
var.map_object_a
var.map_object_b
var.map_object_c
var.map_object_d
type(var.map_object_a)
type(var.map_object_b)
type(var.map_object_c)
type(var.map_object_d)

var.map_object_a["a"]
type(var.map_object_a["a"])

var.map_object_b["a"]
type(var.map_object_b["a"])

var.map_object_c["name"]
type(var.map_object_c["name"])

var.map_object_d["age"]
type(var.map_object_d["age"])


# tuple > list > set
type(["a","b"])
type(tolist(["a","b"]))
type(toset(["a","b"]))


# object > map
type({a="a", b="b"})
type({a="a", b=1})
type(tomap({a="a", b="b"}))

for_each 와 count 비교

  • main.tf 파일 수정
resource "local_file" "abc" {
  count    = 3
  content  = "This is filename abc${count.index}.txt"
  filename = "${path.module}/abc${count.index}.txt"
}
  • 확인
# Terraform 변경 사항을 적용
terraform apply -auto-approve

# 상태(state)에 있는 리소스의 목록을 출력
terraform state list

# 특정 리소스의 상세 정보를 출력
terraform state show 'local_file.abc[0]'

# Terraform 콘솔에서 리소스에 대한 정보 확인
echo "local_file.abc" | terraform console
echo 'local_file.abc[0]' | terraform console
echo 'local_file.abc[0].content' | terraform console

  • count를 사용하면 생성할 리소스의 수를 지정할 수 있지만, 각 리소스가 정확히 어떤 것을 나타내는지에 대한 정보는 제공하지 않습니다.
  • 중간에 인덱스를 추가하거나 제거할 때 기존의 인덱스와 충돌이 발생할 수 있습니다.
    • 예를 들어, 세 개의 리소스가 있는 상태에서 두 번째 리소스를 삭제하면, 세 번째 리소스는 두 번째로 이동하게 되어 인덱스가 변경될 수 있습니다.
    • 이는 의도하지 않은 인프라 변경으로 이어질 수 있습니다.
  • for_each 표현식을 사용하여 리소스의 여러 복사본을 만드는 구문은 다음과 같습니다.
resource "<PROVIDER>_<TYPE>" "<NAME>" {
  for_each = <COLLECTION>

  [CONFIG ...]
}
  • COLLECTION은 테라폼에서 루프를 처리할 때 사용되는 set과 map을 의미합니다.
  • CONFIG는 특정 리소스와 관련된 하나 이상의 인수로 구성됩니다. 이때 CONFIG 내에서 each.key와 each.value를 사용하여 COLLECTION에서 현재 항목의 키와 값을 참조할 수 있습니다. 이는 for_each 구문을 사용하여 리소스를 생성할 때 주로 활용됩니다.
  • 다음은 var.user_names 리스트를 기반으로 IAM 사용자를 생성하는 방법입니다.
    • toset을 사용하여 var.user_names 리스트를 set으로 변환합니다.
    • for_each 구문을 통해 각 사용자 이름에 대해 동적으로 리소스를 생성합니다. 각 사용자의 이름은 each.key 또는 each.value 로 접근할 수 있습니다.
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_iam_user" "myiam" {
  for_each = toset(var.user_names)
  name     = each.value
}

variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["gasida", "akbun", "ssoon"]
}

output "all_users" {
  value = aws_iam_user.myiam
}
  • 실행 및 확인
# Terraform 초기화, 계획 및 적용
terraform init -upgrade
terraform plan && terraform apply -auto-approve

# 상태 목록 리스트 및 출력 변수 확인
terraform state list
terraform output

# IAM 사용자 목록을 확인
aws iam list-users | jq

# 상태 파일에서 key, name, content와 관련된 정보를 필터링하여 확인
cat terraform.tfstate | grep -e key -e name -e "content:"

  • all_users 출력 변수가 각 사용자 이름을 키로 가지고, 값이 해당 IAM 사용자 리소스의 전체 출력을 맵으로 포함하게 됩니다.
  • for_each를 사용하면 리소스를 맵으로 처리할 수 있어서, 컬렉션의 중간 항목을 안전하게 제거하거나 추가할 수 있습니다. 이는 count를 사용하여 리소스를 배열 처리하는 것보다 매우 유연하고 이점이 큽니다.
  • var.user_names 리스트에서 중간에 값을 제거하고 terraform plan으로 변경 사항을 확인
...
variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["gasida", "ssoon"]
}
...
# Terraform 계획 및 적용
terraform plan
terraform apply -auto-approve

# 상태 목록 리스트 확인
terraform state list

# IAM 사용자 목록을 확인
aws iam list-users | jq

# 상태 파일에서 key, name, content와 관련된 정보를 필터링하여 확인
cat terraform.tfstate | grep -e key -e name -e "content:"
cat terraform.tfstate.backup | grep -e key -e name -e "content:"

  • 경우에 따라 리소스의 개수나 구성이 동적으로 변할 수 있어야 하는 상황이 생길 수 있습니다. 이런 경우 count보다는 for_each를 사용하는 것이 더 적합할 수 있습니다.
  • for_each는 각 리소스 인스턴스에 대해 고유한 키-값 쌍을 정의하므로, 중간에 리소스를 추가하거나 제거할 때 더욱 유연하게 대응할 수 있습니다.

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"]
  }
}
  • 기존에 정적으로 정의되어 있던 블록 속성을 동적으로 생성할 dynamic 블록의 이름으로 선언합니다.
  • 각 동적 블록은 content 블록을 사용하여 실제 내용을 정의합니다. 이 content 블록은 각 요소의 속성을 정의하는 곳입니다.
  • dynamic 블록은 for_each 구문을 사용하여 동적으로 생성할 요소들을 정의합니다. for_each 구문에는 키-값 쌍이나 필요에 따라 단순 리스트 등을 사용할 수 있습니다.
  • dynamic 블록 활용 예
일반적인 블록 속성 반복 적용 시 dynamic 블록 적용 시
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
  }
}
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
    }
  }
}
  • main.tf 파일 수정
data "archive_file" "dotfiles" {
  type        = "zip"
  output_path = "${path.module}/dotfiles.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"
  }
}
  • 실행 후 확인
# Terraform 초기화 및 적용
terraform init -upgrade && terraform apply -auto-approve

# Terraform 상태 목록 및 상태 조회 
terraform state list
terraform state show data.archive_file.dotfiles

# 파일 확인
ls *.zip
unzip dotfiles.zip
ls *.txt
cat a.txt ; echo

  • main.tf 파일 수정 - 리소스 내의 반복 선언을 dynamic 블록으로 재구성하기
variable "names" {
  default = {
    a = "hello a"
    b = "hello b"
    c = "hello c"
  }
}

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

  dynamic "source" {
    for_each = var.names
    content {
      content  = source.value
      filename = "${path.module}/${source.key}.txt"
    }
  }
}
  • 실행 후 확인 - 변경 사항이 없다. 결과가 동일함
# Terraform 적용
terraform apply -auto-approve

# Terraform 상태 목록 및 상태 조회
terraform state list
terraform state show data.archive_file.dotfiles

# 파일 확인
ls *.zip

 

3.10 조건문

  • 테라폼에서는 조건식을 사용할 때 일반적으로 3항 연산자의 형태를 취하며, 조건은 true 또는 false로 평가될 수 있는 모든 표현식을 사용할 수 있습니다.
  • 테라폼에서는 다음과 같은 형식으로 조건식을 작성합니다.
    • <조건 정의> ? <옳은 경우> : <틀린 경우>
    • 조건 정의 : true 또는 false로 평가될 수 있는 표현식입니다. 예를 들어, 변수의 비교, 함수 호출 결과, 논리 연산 등이 포함될 수 있습니다.
    • 옳은 경우 : 조건 정의가 true로 평가될 경우 반환할 값 또는 표현식입니다.
    • 틀린 경우 : 조건 정의가 false로 평가될 경우 반환할 값 또는 표현식입니다.
  • 조건식에서는 비교 대상의 형태가 다를 경우 테라폼이 자동으로 형 변환을 수행할 수 있지만, 가독성과 예측 가능성을 위해 명시적인 형태 작성을 권장합니다.
# 명시적 형태 작성 권장
var.example ? 12 : "hello"            # 비권장
var.example ? "12" : "hello"          # 권장
var.example ? tostring(12) : "hello"  # 권장
  • 조건식을 사용할 때 특정 속성의 정의, 로컬 변수의 재정의, 출력 값의 조건적 정의 외에도 리소스의 생성 여부를 조절하는 데 활용할 수 있습니다.
  • count와 조건식을 결합하여 특정 조건에 따라 리소스를 동적으로 생성하거나 생략할 수 있습니다.
  • main.tf 파일 생성
variable "enable_file" {
  default = true
}

resource "local_file" "foo" {
  count    = var.enable_file ? 1 : 0
  content  = "foo!"
  filename = "${path.module}/foo.bar"
}

output "content" {
  value = var.enable_file ? local_file.foo[0].content : ""
}
  • 실행 및 확인
# 환경 변수 (TF_VAR_*) - 변수 우선순위 : 명령줄 인수(-var) > 변수 파일 (.tfvars) > 환경 변수 (TF_VAR_*)
export TF_VAR_enable_file=false
export | grep TF_VAR_enable_file

# Terraform 초기화 및 실행
terraform init && terraform plan && terraform apply -auto-approve

# Terraform 상태 목록 조회
terraform state list

# Terraform 콘솔에서 변수 조회
echo "var.enable_file ? 1 : 0" | terraform console

# 환경 변수 삭제
unset TF_VAR_enable_file
export | grep TF_VAR_enable_file

# 재실행 및 상태 목록 조회
terraform plan && terraform apply -auto-approve
terraform state list

# Terraform 콘솔에서 조회
echo "local_file.foo[0]" | terraform console
echo "local_file.foo[0].content" | terraform console
echo "var.enable_file ? 1 : 0" | terraform console

 

 

 

3.11 함수

  • 테라폼은 많은 내장 함수를 제공하여 변수, 리소스 속성, 데이터 소스 속성, 출력 값 등을 동적이고 효과적으로 처리할 수 있습니다.
  • 하지만 테라폼은 사용자가 직접 정의한 사용자 정의 함수를 지원하지 않습니다.
  • 이러한 함수들은 다양한 작업을 지원하며, 주요 카테고리로는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환 등이 있습니다.
  • main.tf 파일 생성
resource "local_file" "foo" {
  content  = upper("foo! bar!")
  filename = "${path.module}/foo.bar"
}
  • 실행 및 확인
# Terraform 초기화, 계획 및 적용
terraform init && terraform plan && terraform apply -auto-approve

# 파일 확인
cat foo.bar ; echo

# 내장 함수의 간단한 사용 예시
terraform console
-----------------
upper("foo!")

max(5, 12, 9)

lower(local_file.foo.content)
upper(local_file.foo.content)

cidrnetmask("172.16.0.0/12")

cidrsubnet("1.1.1.0/24", 1, 0)
cidrsubnet("1.1.1.0/24", 1, 1)

cidrsubnet("1.1.1.0/24", 2, 0)
cidrsubnet("1.1.1.0/24", 2, 1)
cidrsubnet("1.1.1.0/24", 2, 2)
cidrsubnet("1.1.1.0/24", 2, 3)

cidrsubnets("10.1.0.0/16", 4, 4, 8, 4)

exit
-----------------