Terraform Functions: 인프라가 초능력을 얻는 곳
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)
}