Terraform Variables

Variable이 하는 일: 모듈의 입력 파라미터

Terraform에서 Input Variable은 모듈의 입력 파라미터다. 하드코딩된 값을 변수로 추출하면 같은 모듈을 다른 환경(dev/staging/prod)에서 다른 설정으로 재사용할 수 있다. Terraform 공식 문서는 Variable을 “모듈의 사용자가 소스 코드를 수정하지 않고 Terraform 구성을 커스터마이즈할 수 있게 하는 것”으로 정의한다.

이 글에서는 Variable의 타입 시스템, validation 블록, sensitive 마킹, 값 할당 우선순위, locals와의 차이, 그리고 모듈 인터페이스 설계 패턴까지 공식 문서 기반으로 정리한다.

Variable 선언: 기본 구조와 속성

variable "instance_type" {
  description = "EC2 인스턴스 타입"
  type        = string
  default     = "t3.micro"
  nullable    = false
  sensitive   = false

  validation {
    condition     = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
    error_message = "instance_type은 t3.micro, t3.small, t3.medium 중 하나여야 합니다."
  }
}
속성 필수 역할 기본값
type 타입 제약 (string, number, list 등) any
default 값 미지정 시 기본값 없음 (필수 입력)
description 문서화 (terraform plan, Registry) “”
sensitive plan/apply 출력에서 마스킹 false
nullable null 값 허용 여부 (1.1+) true
validation 커스텀 검증 규칙 없음

타입 시스템: Primitive, Collection, Structural

Primitive 타입

variable "name" {
  type = string    # "hello"
}

variable "count" {
  type = number    # 42, 3.14
}

variable "enabled" {
  type = bool      # true, false
}

Collection 타입: list, map, set

variable "availability_zones" {
  type    = list(string)
  default = ["ap-northeast-2a", "ap-northeast-2c"]
}

variable "tags" {
  type = map(string)
  default = {
    Environment = "production"
    Team        = "platform"
  }
}

variable "allowed_ports" {
  type    = set(number)
  default = [80, 443, 8080]   # set은 중복 제거, 순서 없음
}

Structural 타입: object, tuple

# object: 이름이 있는 속성들의 집합
variable "database_config" {
  type = object({
    host     = string
    port     = number
    name     = string
    ssl      = optional(bool, true)     # optional + 기본값 (1.3+)
    replicas = optional(number, 0)
  })
  default = {
    host = "localhost"
    port = 3306
    name = "mydb"
  }
}

# tuple: 고정 길이, 각 요소 타입 다름
variable "cidr_and_count" {
  type = tuple([string, number])
  # ["10.0.0.0/16", 3]
}
타입 특징 사용 시점
list(T) 순서 있음, 인덱스 접근 서브넷 CIDR 목록, AZ 목록
set(T) 순서 없음, 중복 없음 허용 포트, 태그 키 집합
map(T) 키-값, 모든 값이 같은 타입 태그, 환경 변수
object({...}) 각 속성이 다른 타입 가능 DB 설정, 서비스 구성 등 복합 설정
tuple([...]) 고정 길이, 각 요소 다른 타입 드물게 사용

validation: 커스텀 검증 규칙 (0.13+)

# 여러 validation 블록 가능 (1.9+)
variable "environment" {
  type = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment는 dev, staging, prod 중 하나여야 합니다."
  }
}

variable "vpc_cidr" {
  type = string

  validation {
    condition     = can(cidrhost(var.vpc_cidr, 0))
    error_message = "vpc_cidr은 유효한 CIDR 블록이어야 합니다 (예: 10.0.0.0/16)."
  }

  validation {
    condition     = tonumber(split("/", var.vpc_cidr)[1]) <= 24
    error_message = "vpc_cidr의 prefix는 /24 이하여야 합니다."
  }
}

variable "db_port" {
  type = number

  validation {
    condition     = var.db_port >= 1024 && var.db_port <= 65535
    error_message = "db_port는 1024~65535 범위여야 합니다."
  }
}

# object 내부 속성 검증
variable "autoscaling" {
  type = object({
    min_size     = number
    max_size     = number
    desired_size = number
  })

  validation {
    condition     = var.autoscaling.min_size <= var.autoscaling.desired_size
    error_message = "min_size는 desired_size 이하여야 합니다."
  }

  validation {
    condition     = var.autoscaling.desired_size <= var.autoscaling.max_size
    error_message = "desired_size는 max_size 이하여야 합니다."
  }
}

값 할당 방법과 우선순위

공식 문서에 따르면 Variable 값은 다음 순서로 로드되며, 나중 것이 이전 것을 덮어쓴다.

# 1. 환경 변수 (TF_VAR_ 접두사)
export TF_VAR_instance_type="t3.large"
export TF_VAR_environment="prod"

# 2. terraform.tfvars (자동 로드)
# terraform.tfvars
instance_type = "t3.medium"
environment   = "staging"

# 3. *.auto.tfvars (자동 로드, 알파벳 순)
# prod.auto.tfvars
instance_type = "t3.large"

# 4. -var-file 플래그
terraform apply -var-file="prod.tfvars"

# 5. -var 플래그 (최우선)
terraform apply -var="instance_type=t3.xlarge"
우선순위 방법 자동 로드 사용 시점
1 (최저) default 안전한 기본값
2 TF_VAR_* 환경 변수 CI/CD 파이프라인
3 terraform.tfvars 로컬 기본 설정
4 *.auto.tfvars ✅ (알파벳순) 환경별 자동 오버라이드
5 -var-file ❌ 명시적 환경별 명시적 설정 파일
6 (최고) -var ❌ 명시적 일회성 오버라이드, 디버깅

sensitive: 민감한 변수 마스킹

variable "db_password" {
  type      = string
  sensitive = true     # plan/apply 출력에서 (sensitive value)로 표시
}

# terraform plan 출력:
#   + resource "aws_db_instance" "main" {
#       + password = (sensitive value)
#     }

한계: Output의 sensitive와 마찬가지로, state 파일에는 평문 저장된다. 또한 sensitive 변수를 참조하는 모든 표현식도 자동으로 sensitive로 전파된다.

locals vs variables: 언제 무엇을 쓸까

# variables: 모듈 외부에서 값을 받음
variable "environment" {
  type = string
}

variable "project_name" {
  type = string
}

# locals: 모듈 내부에서 계산된 값
locals {
  name_prefix = "${var.project_name}-${var.environment}"

  common_tags = {
    Project     = var.project_name
    Environment = var.environment
    ManagedBy   = "terraform"
  }

  is_production = var.environment == "prod"
  instance_type = local.is_production ? "t3.large" : "t3.micro"
}
항목 variable local
값의 출처 외부 (사용자, tfvars, CLI) 내부 (표현식으로 계산)
참조 문법 var.name local.name
validation ✅ 지원 ❌ 없음
사용 목적 모듈의 공개 인터페이스 중복 제거, 중간 계산값
모듈 외부 노출 ✅ (입력) ❌ (내부 전용)

optional() 속성: object 타입의 유연한 기본값 (1.3+)

variable "service_config" {
  type = object({
    name           = string
    port           = number
    replicas       = optional(number, 1)        # 기본값 1
    health_path    = optional(string, "/health") # 기본값 "/health"
    enable_logging = optional(bool, true)        # 기본값 true
    extra_env      = optional(map(string), {})   # 기본값 빈 맵
  })
}

# 사용: replicas, health_path 등 생략 가능
module "api" {
  source = "./modules/service"

  service_config = {
    name = "api-server"
    port = 8080
    # replicas = 1 (기본값 자동 적용)
    # health_path = "/health" (기본값 자동 적용)
  }
}

실전 체크리스트: Variable 설계 7단계

  1. 타입을 항상 명시any를 피하고, 가능한 한 구체적인 타입을 지정해 오류를 조기 발견
  2. description 필수 작성 — 특히 공개 모듈에서는 사실상 필수, terraform plan 시 사용자에게 안내
  3. default 유무로 필수/선택 구분 — default가 없으면 필수 입력, 있으면 선택 입력
  4. validation으로 잘못된 값 차단 — CIDR 형식, 포트 범위, enum 값 등을 plan 시점에 검증
  5. sensitive 표시 — 비밀번호, API 키, 토큰에는 반드시 sensitive = true
  6. tfvars를 .gitignore에 추가 — 민감 값이 포함된 tfvars 파일은 버전 관리에서 제외
  7. locals로 중복 제거 — 같은 표현식이 3번 이상 반복되면 local로 추출

흔한 실수 4가지와 방지법

실수 1: 타입을 지정하지 않아 예상치 못한 값 통과

증상: variable "port" {}로 선언했더니 문자열 “8080”이 들어와 number를 기대하는 리소스에서 에러.

방지: type = number를 명시한다. Terraform이 자동 변환을 시도하지만, 명시적 타입이 안전하다.

실수 2: terraform.tfvars와 -var-file의 우선순위 혼동

증상: terraform.tfvars에 dev 설정을 넣고 -var-file=prod.tfvars로 실행했는데 일부 값이 dev로 남아있다. prod.tfvars에 해당 변수가 없었다.

방지: -var-file은 terraform.tfvars를 완전히 대체하지 않고 오버라이드한다. 환경별 tfvars에 모든 변수를 명시하거나, terraform.tfvars를 환경 공통 값으로만 사용한다.

실수 3: sensitive 변수를 로컬에서 조합해 노출

증상: local.connection_string = "mysql://${var.db_user}:${var.db_password}@..."을 output하면 sensitive 전파로 에러, 또는 로그에 노출.

방지: sensitive 변수는 리소스에 직접 전달하고, 조합 문자열은 output하지 않는다. 필요하면 output에도 sensitive = true를 명시한다.

실수 4: validation 없이 잘못된 CIDR/포트가 apply까지 도달

증상: 잘못된 CIDR 블록이 apply 시점에 AWS API 에러를 발생시킨다. 이미 일부 리소스가 생성된 후라 롤백이 복잡하다.

방지: validation 블록으로 plan 시점에 값을 검증한다. can() 함수로 형식 검증, contains()로 enum 검증을 추가한다.

마무리

Terraform Variable은 모듈의 공개 입력 인터페이스다. 타입을 명시하고, validation으로 잘못된 값을 조기 차단하며, sensitive로 민감 정보를 마스킹하고, optional()로 유연한 기본값을 제공하면 안전하고 재사용 가능한 모듈을 만들 수 있다. 반복되는 표현식은 locals로 추출해 DRY를 유지한다. 이 글의 모든 내용은 Terraform 공식 문서(Input Variables, Local Values)를 근거로 한다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux