Terraform Functions: 인프라가 초능력을 얻는 곳

발행: (2025년 12월 8일 오전 08:31 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

Introduction

AWS 30‑Day Challenge 동안 저는 Terraform의 내장 함수를 탐구하는 데 이틀을 전념했습니다. 이 함수들은 길고 오류가 발생하기 쉬운 설정들을 간결하고 프로덕션에 바로 사용할 수 있는 코드로 바꾸어 주었습니다. 아래는 제가 발견한 가장 유용한 패턴들로, 각각 실용적인 예시와 함께 설명합니다.

Using the Terraform Console

main.tf를 수정하기 전에 인터랙티브 콘솔을 실행해 표현식을 테스트해 보세요:

terraform console
> lower("HELLO WORLD")
"hello world"

> max(5, 12, 9)
12

> split(",", "80,443,8080")
[
  "80",
  "443",
  "8080",
]

> reverse(["a", "b", "c"])
[
  "c",
  "b",
  "a",
]

콘솔을 사용하면 배포 없이도 로직을 검증할 수 있습니다.

1. Normalizing Resource Names

Problem – AWS 리소스는 종종 소문자와 하이픈(-)이 포함된 이름을 요구합니다.

Solution

locals {
  clean_name = lower(replace(var.project_name, " ", "-"))
}

resource "aws_resourcegroups_group" "project" {
  name = local.clean_name
}

lower()replace()는 일관된 이름을 만들 때 필수적입니다.

2. Sanitizing S3 Bucket Names

Problem – S3 버킷 이름은 소문자여야 하고, 언더스코어와 특수 문자를 포함할 수 없으며, 길이가 63자를 초과하면 안 됩니다.

Solution

locals {
  sanitized_bucket_name = substr(
    lower(
      replace(
        replace(var.bucket_name_input, "_", "-"),
        "/[^a-z0-9-]/", ""
      )
    ),
    0,
    63
  )
}

중첩된 함수를 사용하면 언더스코어를 하이픈으로 바꾸고, 잘못된 문자를 제거하고, 소문자로 변환한 뒤, 한 번에 잘라낼 수 있습니다.

3. Generating Security‑Group Rules from a CSV

Problem – 콤마로 구분된 포트 목록을 개별 인그레스 블록으로 변환하고 싶습니다.

Solution

variable "allowed_ports" {
  default = "80,443,8080"
}

locals {
  ports_list = split(",", var.allowed_ports)
}

resource "aws_security_group" "web" {
  name = "web-sg"

  dynamic "ingress" {
    for_each = local.ports_list
    content {
      from_port   = tonumber(ingress.value)
      to_port     = tonumber(ingress.value)
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

split()은 문자열을 리스트로 변환하고, 이 리스트가 dynamic 블록을 구동합니다.

4. Selecting Instance Types per Environment

Problem – 환경마다 서로 다른 인스턴스 크기가 필요합니다.

Solution

variable "environment" {
  default = "dev"
}

locals {
  instance_types = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.large"
  }

  selected_type = lookup(local.instance_types, var.environment, "t3.micro")
}

resource "aws_instance" "app" {
  ami           = var.ami_id
  instance_type = local.selected_type
}

lookup()은 맵 기반 조건 로직을 제공하며, 안전한 기본값을 지정할 수 있습니다.

5. Validating Instance‑Type Patterns

Problem – 인스턴스 타입이 회사 네이밍 규칙(예: t2.* 또는 t3.*)을 따르는지 강제하고 싶습니다.

Solution

variable "instance_type" {
  type = string

  validation {
    condition     = can(regex("^(t2|t3)\\.", var.instance_type))
    error_message = "Instance type must start with 't2.' or 't3.'"
  }
}

regex()는 패턴을 검사하고, can()은 오류를 발생시키지 않고 false를 반환합니다.

6. Naming and Secret Handling

Problem – 백업 이름이 규칙을 따르고, 비밀번호는 비밀로 유지해야 합니다.

Solution

variable "backup_name" {
  type = string

  validation {
    condition     = endswith(var.backup_name, "-backup")
    error_message = "Backup name must end with '-backup'"
  }
}

variable "db_password" {
  type      = string
  sensitive = true
}

output "backup_info" {
  value = {
    name     = var.backup_name
    password = sensitive(var.db_password)
  }
  sensitive = true
}

endswith()/startswith()는 문자열을 검증하고, sensitive()는 비밀이 누출되는 것을 방지합니다.

7. Cost Calculations

Problem – 월 총 비용을 계산하고, 크레딧을 적용한 뒤, 가장 큰 단일 비용을 찾아야 합니다.

Solution

variable "monthly_costs" {
  default = [120.50, 85.00, 200.75, 50.00]
}

variable "credit" {
  default = -50.00  # Negative credit
}

locals {
  total_cost    = sum(var.monthly_costs)
  actual_credit = abs(var.credit)
  final_cost    = max(local.total_cost - local.actual_credit, 0)
  highest_cost  = max(var.monthly_costs...)
}

output "cost_summary" {
  value = {
    total_before_credit = local.total_cost
    credit_applied      = local.actual_credit
    final_bill          = local.final_cost
    highest_single_cost = local.highest_cost
  }
}

sum(), abs(), max()와 스플랫 연산자(...)를 사용하면 산술 연산이 간단해집니다.

8. Timestamp Formatting

Problem – 태그와 리소스 이름에 일관된 타임스탬프를 생성하고 싶습니다.

Solution

locals {
  current_time = timestamp()

  formatted_tags = {
    CreatedAt  = formatdate("YYYY-MM-DD hh:mm:ss ZZZ", local.current_time)
    BackupDate = formatdate("YYYY-MM-DD", local.current_time)
    Year       = formatdate("YYYY", local.current_time)
  }
}

resource "aws_s3_bucket" "backups" {
  bucket = "backups-${formatdate("YYYY-MM-DD", timestamp())}"
  tags   = local.formatted_tags
}

timestamp()는 UTC 시간을 반환하고, formatdate()를 사용해 원하는 형태로 변형할 수 있습니다.

9. Reading JSON Configs and Storing Secrets

Problem – 데이터베이스 자격 증명이 들어 있는 JSON 파일을 읽어 AWS Secrets Manager에 저장하고 싶습니다.

Solution

locals {
  # Verify the file exists
  config_exists = fileexists("${path.module}/config/database.json")

  # Read and decode JSON (only if the file is present)
  db_config = jsondecode(
    file("${path.module}/config/database.json")
  )
}

resource "aws_secretsmanager_secret" "db" {
  name = "db-credentials"
}

resource "aws_secretsmanager_secret_version" "db_version" {
  secret_id     = aws_secretsmanager_secret.db.id
  secret_string = jsonencode(local.db_config)
}
Back to Blog

관련 글

더 보기 »

Day-12: AWS Terraform 함수

Terraform 고급 함수 Day 11에서 다룬 기본 함수들을 기반으로, 오늘은 Terraform을 더욱 강화하는 보다 전문화된 함수들을 살펴봅니다.