한 번 작성하고, 어디서든 배포하기: Terraform의 Expression Toolkit 마스터하기

발행: (2025년 12월 8일 오전 07:56 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

AWS 챌린지 10일 차에 오신 것을 환영합니다. 오늘은 Terraform을 단순 선언형 도구에서 유연하고 지능적인 구성 파워하우스로 변모시키는 기능들을 살펴봅니다. 어제 다룬 lifecycle 메타‑인수는 외과적 제어를 제공했으며, 오늘의 표현식은 최대 재사용성을 갖춘 외과적 정밀성을 제공합니다.

우리는 조건식, 동적 블록, 그리고 스플랫 표현식을 살펴볼 것입니다—코드 중복을 없애고 인프라를 환경 전반에 걸쳐 진정으로 적응 가능하게 만드는 세 가지 요소입니다.

Conditional Expressions

Syntax

condition ? true_value : false_value

Terraform은 조건을 평가합니다. true이면 첫 번째 값을 사용하고, 그렇지 않으면 두 번째 값을 사용합니다.

Real‑World Example

resource "aws_instance" "app_server" {
  ami           = var.ami
  instance_type = var.environment == "production" ? "t3.large" : "t3.micro"

  monitoring = var.environment == "production" ? true : false

  tags = {
    Name        = "${var.environment}-app-server"
    Environment = var.environment
    CostCenter  = var.environment == "production" ? "prod-ops" : "dev-team"
  }
}

프로덕션에서는 더 큰 인스턴스와 모니터링이 활성화되고, 개발 환경은 가볍게 유지됩니다—모두 하나의 구성 파일에서 구현됩니다.

Use Cases

  • 환경에 따라 인스턴스 크기 선택
  • 비용이 많이 드는 기능(예: 향상된 모니터링)을 프로덕션에서만 활성화
  • 지역별로 다른 AMI 선택
  • 리소스 개수 설정(프로덕션은 더 많이, 개발은 적게)
  • 환경별 보안 정책 적용

Pro Tip: 여러 개의 삼항 연산자를 연속해서 사용하게 된다면, 가독성을 위해 locals 블록으로 로직을 분리하세요.

Dynamic Blocks

Syntax

dynamic "block_name" {
  for_each = var.collection
  content {
    # Configuration using block_name.value
  }
}

Note: for_each를 리소스에 사용하면 여러 리소스가 생성됩니다. 동적 블록은 하나의 리소스 안에 여러 중첩 블록을 생성합니다.

Real‑World Example: Security Group Rules

variable "ingress_rules" {
  type = list(object({
    port        = number
    protocol    = string
    cidr_blocks = list(string)
    description = string
  }))
  default = [
    {
      port        = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTP access"
    },
    {
      port        = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
      description = "HTTPS access"
    },
    {
      port        = 22
      protocol    = "tcp"
      cidr_blocks = ["10.0.0.0/8"]
      description = "SSH access"
    }
  ]
}

resource "aws_security_group" "app_sg" {
  name = "app-security-group"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
      description = ingress.value.description
    }
  }
}

Why This Is Brilliant

  • 변수를 업데이트하면 새로운 규칙이 추가되며, 코드 수정이 필요 없습니다.
  • 서로 다른 리스트를 전달해 환경별 규칙 집합을 제공할 수 있습니다.
  • 빈 리스트를 사용하면 외부 접근을 모두 차단할 수 있습니다.
  • 단일 변수를 검사함으로써 열린 포트를 감사할 수 있습니다.

Other Powerful Use Cases

  • EC2 인스턴스에 여러 EBS 볼륨 연결
  • IAM 정책 문서 생성
  • 라우트 테이블에 경로 채우기
  • CloudWatch 알람 액션 정의
  • 변형이 있는 모든 중첩 블록 반복

Important: 동적 블록은 리소스 내부의 중첩 블록에만 적용됩니다. 여러 리소스를 만들려면 리소스 자체에 count 또는 for_each를 사용하세요.

Splat Expressions

Syntax

resource_list[*].attribute_name

Real‑World Example

resource "aws_instance" "web_servers" {
  count         = 3
  ami           = var.ami_id
  instance_type = "t3.micro"

  tags = {
    Name = "web-server-${count.index}"
  }
}

# Output all instance IDs in one line
output "instance_ids" {
  value = aws_instance.web_servers[*].id
}

# Output all private IPs
output "private_ips" {
  value = aws_instance.web_servers[*].private_ip
}

# Use those IPs in another resource
resource "aws_lb_target_group_attachment" "web" {
  count            = length(aws_instance.web_servers)
  target_group_arn = aws_lb_target_group.web.arn
  target_id        = aws_instance.web_servers[*].id[count.index]
}

스플랫 표현식이 없었다면 루프나 반복 코드를 작성해야 했겠지만, 한 줄의 깔끔한 코드로 해결됩니다.

Common Use Cases

  • 하위 리소스에서 여러 EC2 인스턴스 ID를 추출해 다른 리소스에 전달
  • VPC 데이터 소스에서 서브넷 ID 수집
  • 보안 그룹 ID를 가져와 리소스에 연결
  • IAM 정책에 사용할 ARN 수집
  • 출력값 및 의존성을 위한 리스트 생성

Pro Tip: join() 같은 함수와 결합하면 더욱 강력해집니다:

output "all_ips_comma_separated" {
  value = join(",", aws_instance.web_servers[*].private_ip)
}

Putting It All Together

아래는 조건식, 동적 블록, 스플랫 표현식을 조합한 프로덕션 급 구성 예시입니다.

variable "environment" {
  type = string
}

variable "ingress_rules" {
  type = map(object({
    port        = number
    cidr_blocks = list(string)
  }))
}

# Conditional: Different instance counts and types per environment
resource "aws_instance" "app" {
  count         = var.environment == "production" ? 3 : 1
  ami           = var.ami_id
  instance_type = var.environment == "production" ? "t3.large" : "t3.micro"

  vpc_security_group_ids = [aws_security_group.app.id]

  tags = {
    Name        = "${var.environment}-app-${count.index}"
    Environment = var.environment
  }
}

# Dynamic: Generate security rules from a variable
resource "aws_security_group" "app" {
  name = "${var.environment}-app-sg"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

# Splat: Extract all instance IDs for output
output "instance_ids" {
  description = "IDs of all app instances"
  value       = aws_instance.app[*].id
}

# Splat: Get all private IPs for load balancer configuration
output "private_ips" {
  description = "Private IPs for load balancer config"
  value       = aws_instance.app[*].private_ip
}
  • 조건식은 파일 중복 없이 환경에 맞는 구성을 가능하게 합니다.
  • 동적 블록은 반복되는 중첩 블록을 없애고, 보안 그룹 및 정책을 데이터 기반으로 유지보수하기 쉽게 합니다.
  • 스플랫 표현식은 데이터를 손쉽게 추출해 수동 루프 없이도 필요한 정보를 얻을 수 있게 해줍니다.

Happy Terraforming! 🚀

Back to Blog

관련 글

더 보기 »

Terraform 데이터 소스 (AWS)

Terraform 데이터 소스란 무엇인가요? 데이터 소스는 Terraform에서 기존 리소스를 읽기 전용으로 조회하는 기능입니다. 새로운 리소스를 생성하는 대신, Terraform은 ...