한 번 작성하고, 어디서든 배포하기: Terraform의 Expression Toolkit 마스터하기
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! 🚀