IaC/Infrastructure Provisioning

5. Terraform의 다양한 Expression

Somaz 2023. 4. 13. 20:00
728x90
반응형

 

Overview

오늘은 Terraform에서 사용되는 다양한 Expressions(표현식) 에 대해 알아본다.
Terraform의 표현식은 구성 파일 내에서 값들을 변형하고 조건을 적용하는 데 사용되며,
이를 통해 보다 동적이고 유연한 인프라 구성 이 가능하다.

 

 

이번 글에서는 Terraform의 주요 표현식인 Types and Values, Conditional Expressions, for Expressions, Dynamic Blocks 를 다룬다.

 


이를 활용하여 리스트와 맵을 조작하고 조건문을 적용하며, 동적인 리소스 구성을 생성하는 방법 을 학습할 것이다.

 

 

 

 

📅 관련 글

2023.03.30 - [IaC/Infrastructure Provisioning] - 1. Terraform 기초 : 설치 및 .tf 파일 설명

2023.03.31 - [IaC/Infrastructure Provisioning] - 2. Terraform 변수 사용법(use-variable) - AWS

2023.04.03 - [IaC/Infrastructure Provisioning] - 3. Terraform 다양한 변수(variable, local, data...output, input) - AWS

2023.04.03 - [IaC/Infrastructure Provisioning] - 4. Terraform의 다양한 Function(함수)

2023.04.04 - [IaC/Infrastructure Provisioning] - 5. Terraform의 다양한 Expression

 

 

 

 

 


 

 

1. Types and Values(유형 및 값)

 

모든 값에는 해당 값을 사용할 수 있는 위치와 적용할 수 있는 변환을 나타내는 유형이 있다.

 

 

 

Type

  • string: 와 같은 일부 텍스트를 나타내는 일련의 유니코드 문자이다 "hello".
  • number: 숫자 값이다. 유형 은 number와 같은 정수 (15)및 와 같은 소수 값을 모두 나타낼 수 있다 (6.283185.)
  • bool: 부울 값, true또는 false. bool값은 조건 논리에서 사용할 수 있다.
  • list(또는 tuple): 와 같은 일련의 값 ["us-west-1a", "us-west-1c"]. 목록 또는 튜플의 요소는 0부터 시작하여 연속된 정수로 식별된다.
  • map(또는 object): 와 같이 명명된 레이블로 식별되는 값 그룹이다 {name = "Mabel", age = 52}.
  • null : 부재 또는 생략을 나타내는 값이다. 리소스의 인수를 로 설정하면 null Terraform은 인수를 완전히 생략한 것처럼 동작한다. 인수가 있는 경우 인수의 기본값을 사용하고 인수가 필수인 경우 오류를 발생한다.
  • any :  'any' 유형은 변수가 string, number, bool, list, map, object, 등과 같은 모든 유형의 값을 허용할 수 있는 특수 유형 제약 조건이다.

 

 

 

 

리스트(list)와 튜플(tuple)의 차이

 

 

List

  • list는 동일한 데이터 유형을 가진 값의 모음이다.
  • list 내의 모든 요소가 동일한 데이터 유형을 공유해야 함을 의미하는 동종 데이터 구조이다.
  • 문자열 목록, 숫자 목록, 지도 목록 등을 가질 수 있지만 단일 목록 내에서 다른 데이터 유형을 혼합할 수는 없습다.
  • Terraform에서 list(<type>) 함수를 사용하여 목록을 정의할 수 있다. 
variable "example_list" {
  type    = list(string)
  default = ["value1", "value2", "value3"]
}

 

 

 

Tuple

  • 튜플은 다른 데이터 유형을 가질 수 있는 값의 모음이다.
  • 이기종 데이터 구조이다. 즉, 단일 튜플 내에서 데이터 유형이 다른 요소를 가질 수 있다.
  • 튜플의 각 요소에는 특정 유형 제약이 있으며 튜플의 유형은 해당 요소 유형의 정렬된 조합에 의해 결정된다.
  • Terraform에서 tuple(<types>) 함수를 사용하여 튜플을 정의할 수 있다. 
variable "example_tuple" {
  type    = tuple([string, number, bool])
  default = ("value1", 42, true)
}

 

 

 

 

 

 

인덱스 및 속성(Indices and Attributes)

`elements of lists, tuples, maps, and objects` 에 액세스하는 방법을 설명한다.

 

 

 

리스트 / 튜플 인덱스

  • 리스트와 튜플의 요소는 인덱스가 정수인 대괄호 인덱스 표기법을 사용하여 액세스할 수 있다.
  • 인덱싱은 0부터 시작한다
locals {
  example_list = ["apple", "banana", "cherry"]
  example_tuple = ["apple", 42, true]
}

output "list_element" {
  value = local.example_list[1] # Output: "banana"
}

output "tuple_element" {
  value = local.example_tuple[2] # Output: true
}

 

 

 

리스트 / 튜플 사용법

locals {
  example_map = {
    apple  = "fruit"
    number = 42
    valid  = true
  }
  example_object = {
    apple  = "fruit"
    number = 42
    valid  = true
  }
}

output "map_element_bracket" {
  value = local.example_map["apple"] # Output: "fruit"
}

output "map_element_dot" {
  value = local.example_map.apple # Output: "fruit"
}

output "object_element_bracket" {
  value = local.example_object["number"] # Output: 42
}

output "object_element_dot" {
  value = local.example_object.valid # Output: true
}

 

 

 

대괄호 사용법

 

값의 type이나 `variables.tf` 파일에 정의된 방식에 관계없이 값을 대괄호 '[]'로 묶어 단일 요소 목록을 만들 수 있다.

  • 보통 variable 사용시 `variable.tf` 파일에 type을 지정한다.
  • 리스트나 튜플 타입이 아니더라도 []를 붙이면 리스트나 튜플이 된다.

 

 

`variable.tf`

variable "example_string" {
  type    = string
  default = "hello"
}

variable "example_map" {
  type = map(string)
  default = {
    key1 = "value1"
    key2 = "value2"
  }
}

 

 

`main.tf`

locals {
  single_element_string_list = [var.example_string] # ["hello"]
  single_element_map_list    = [var.example_map]    # [{"key1"="value1", "key2"="value2"}]
}

output "single_element_string_list" {
  value = local.single_element_string_list
}

output "single_element_map_list" {
  value = local.single_element_map_list
}

 

 

 

 

 


 

 

 

2. Conditional Expressions(조건식)

 

조건식 은 bool 식의 값을 사용하여 두 값 중 하나를 선택한다.

 

 

 

Syntax

condition ? true_val : false_val
  • `condition` 이 `true` 이면 값은 `true_val` 이고 `false` 이면 `false_val` 이다.

 

var.a != "" ? var.a : "default-a"
  • `var.a` 가 비어있지않으면 `true` 이고 결과는 `var.a` 가 나온다. 비어있으면 결과는 `"default-a"` 이다.

 

 

두 결과 값은 모든 유형일 수 있지만 Terraform이 조건 값을 모르고 전체 조건식이 반환할 유형을 결정할 수 있도록 둘 다 동일한 유형이여야 한다.

var.example ? tostring(12) : "hello"

 

 

 

 

 

`Practice .tf` code

 

 

`variable.tf`

variable "create" {
  description = "Determines whether to create Fargate profile or not"
  type        = bool
  default     = true
}

variable "create_iam_role" {
  description = "Determines whether an IAM role is created or to use an existing IAM role"
  type        = bool
  default     = true
}

 

  • `variable "create"`
    • 해당 변수는 `bool` 데이터 유형이며 기본값은 `true` 이다. 해당 설명은 Fargate 프로필 생성 여부를 결정하는 데 사용됨을 나타낸다.
  • `variable "create_iam_role"`: 해당 변수에는 `bool` 데이터 유형이며 기본값은 `true` 이다. 해당 설명은 새 IAM 역할을 생성할지 또는 기존 역할을 사용할지 결정하는 데 사용된다.

 

 

`main.tf`

data "aws_iam_policy_document" "assume_role_policy" {
  count = var.create && var.create_iam_role ? 1 : 0

  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["eks-fargate-pods.amazonaws.com"]
    }
  }
}
  • `data "aws_iam_policy_document" "assume_role_policy"`: 이 데이터 블록은 역할 가정 정책에 대한 AWS IAM 정책 문서를 생성한다.
  • `count` 매개변수를 사용하여 `create` 및 `create_iam_role` 변수의 값을 기반으로 정책 문서 생성을 제어한다.
  • `var.create` 와 `var.create_iam_role` 이 모두 `true` 이면 `count`  값이 1이 되고 정책 문서가 생성된다.
  • `var.create` 또는 `var.create_iam_role` 이 `false` 인 경우 `count` 값은 0이 되고 정책 문서가 생성되지 않는다.

 

 

 

결과

Fargate 프로필과 IAM 역할의 생성을 제어하는 ​​두 가지 변수를 정의한다. 또한 Amazon EKS Fargate 서비스가 IAM 역할을 맡도록 허용하는 역할 가정 정책에 대한 AWS IAM 정책 문서를 생성한다.

 

 

 


 

 

3. for Expressions

'for' 표현식을 사용하면 입력 컬렉션의 각 요소에 표현식을 적용하여 하나의 컬렉션(목록, 집합 또는 맵)을 다른 컬렉션으로 변환할 수 있다. 이는 새 컬렉션을 만들기 위해 값 컬렉션을 조작하거나 필터링해야 할 때 유용하다.

 

 

Syntax

[for <item> in <collection>: <result_expression>]

ex.)
[for s in var.list : upper(s)]
  • <item>: 입력 <collection>의 각 요소를 나타내는 임시 변수이다.
  • <collection>: 반복하려는 input list, set 또는 map이다.
  • <result_expression>: 새 컬렉션의 요소를 생성하기 위해 각 <item>에 적용되는 표현식이다.

 

 

if 절을 추가하여 입력 컬렉션을 필터링할 수도 있다.

[for <item> in <collection>: <result_expression> if <condition>]

ex.) 
[for s in var.list : upper(s) if s != ""]

 

 

 

`Practice .tf` code

 

`variable.tf`

variable "users" {
  type = map(object({
    is_admin = bool
  }))
}
  • `type`: 이 속성은 변수의 데이터 유형을 지정한다.
  • 이 경우 `bool` 값을 보유하는 단일 키 `"is_admin"` 이 있는 객체 `map` 이다.
  • 즉, `"users"` 변수는 각각 고유한 키(이름)와 사용자가 관리자인지 여부를 나타내는 `bool` 값이 있는 사용자 개체 모음이다.

 

`terraform.tvars`

users = {
  "user1" = {
    is_admin = true
  }
  "user2" = {
    is_admin = false
  }
  "user3" = {
    is_admin = true
  }
}

 

 

`main.tf`

locals {
  admin_users = {
    for name, user in var.users : name => user
    if user.is_admin
  }
  regular_users = {
    for name, user in var.users : name => user
    if !user.is_admin
  }
}
  • `locals`: 이 키워드는 하나 이상의 로컬 값을 정의하는 데 사용된다. `"admin_users"` 및 `"regular_users"` 라는 두 개의 로컬 값이 정의된다.
  • `admin_users`: 이 로컬 값은 `"users"` 변수를 필터링하여  `"is_admin"`  값이 `true` 로 설정된 사용자만 포함하는 `map` 을 생성한다. 이는 `"for"` 루프를 사용하여 수행되며 `"users"` 변수의 각 키-값 쌍(이름, 사용자)을 반복하고 `"is_admin"` 값이 `true`  인 경우 새 map에 포함한다
  • `regular_users`: 마찬가지로 이 로컬 값은 `"users"` 변수를 필터링하여 `"is_admin"` 값이 `false` 로 설정된 사용자만 포함하는 새  `map` 을 만든다. `"for"` 루프가 다시 사용되지만 이번에는 `"is_admin"` 이 `false` 로 설정된 사용자를 확인한다.

 

결과

즉, `bool` 속성이 `"is_admin"` 인 사용자 개체 컬렉션을 보유하는 `"users"` 변수를 정의한다. 그런 다음 `"is_admin"` 특성을 기반으로 `"users"` 변수를 필터링하여 `"admin_users"` 및 `"regular_users"` 라는 두 개의 로컬 값을 만들고 관리 사용자와 비관리 사용자에 대한 별도의 `map` 을 만든다.

 

 

`admin_users` 에는 `"이름 : user1", "이름 : user3"` 인 `map` 이 생성되고,

admin_users = {
  "user1" = {
    is_admin = true
  }
  "user3" = {
    is_admin = true
  }
}

 

 

`regular_users` 에는 `"이름 : user2"` 인 `map` 이 생성된다.

regular_users = {
  "user2" = {
    is_admin = false
  }
}

 

 

 

 


 

 

4. dynamic Blocks

 

dynamic 블록은 collection(list, set 또는 map)을 기반으로 리소스 또는 모듈 내에 중첩된 블록을 동적으로 생성하는 데 사용된다.

 

생성할 중첩 블록의 수가 입력 데이터에 따라 달라지는 경우 특히 유용하며 유연하고 재사용 가능한 구성을 작성할 수 있다.

 

또한 특정 리소스 또는 모듈에 유효한 블록 유형을 제공해야 한다. 예를 들어 `aws_security_group` 리소스를 사용하는 경우 ingress, egress 또는 tags와 같은 유효한 블록 유형과 함께 dynamic 블록을 사용할 수 있다.

 

 

 

Syntax

resource "aws_elastic_beanstalk_environment" "tfenvtest" {
  name = "tf-test-name" # can use expressions here

  setting {
    # but the "setting" block is always a literal block
  }
}

 

 

 

 

Practice .tf code

AWS 보안 그룹 리소스가 있고 CIDR 블록 목록이 포함된 변수를 기반으로 여러 수신 규칙을 추가하려고 한다고 가정한다.

 

 

 

`variable.tf`

variable "cidr_blocks" {
  type = list(string)
  default = ["10.0.0.0/16", "192.168.1.0/24"]
}

 

 

`main.tf`

resource "aws_security_group" "example" {
  name = "example"

  dynamic "ingress" {
    for_each = var.cidr_blocks
    content {
      from_port   = 0
      to_port     = 0
      protocol    = "tcp"
      cidr_blocks = [ingress.value]
    }
  }
}

 

 

 

 

Result

 

 `dynmaic` 블록은 `var.cidr_blocks` 목록을 반복하고 목록의 각 CIDR 블록에 대해 `ingress` 블록을 생성한다. `content` 블록은 중첩된 `ingress` 블록의 구조를 정의하고 `ingress.value` 는 반복의 현재 CIDR 블록을 나타낸다.

 

결과적으로 `cidr_blocks` 변수의 CIDR 블록 `"10.0.0.0/16" 및 "192.168.1.0/24"` 를 사용하여 `aws_security_group.example` 리소스에 대해 두 개의 수신 규칙이 생성된다.

 

마지막으로 Terraform의 `dynamic` 블록을 사용하면 컬렉션을 기반으로 리소스 또는 모듈 내에 중첩된 블록을 생성하여 구성을 보다 유연하고 재사용 가능하게 만들 수 있다.

 

 

 


 

 

 

 

 

마무리

Terraform의 표현식을 활용하면 구성의 가독성을 높이고, 반복되는 패턴을 줄이며, 동적인 인프라 설정 이 가능해진다.
특히 조건문(Conditional Expressions) 을 활용한 값 선택, for 문을 사용한 데이터 변형,
dynamic 블록을 통한 동적 리소스 생성 은 실전에서 매우 유용하다.

 

이제 Terraform의 표현식을 이해하고 적용할 수 있으므로,
더 나아가 Terraform의 모듈화(Module) 및 고급 기능 을 활용하여 보다 효율적인 인프라를 구축할 수 있을 것이다.

 

 

 

 

 


Reference

 

terraform expressions

 

terraform type

 

 

 

728x90
반응형