IaC/Terraform

[IaC] Terraform(3) - HCL

vincent77 2025. 9. 25. 16:17

HCL 기본

HCL(HashiCorp Configuration Language)는 Terraform 뿐만 아니라 Vault, Consul 등Hashicorp의 다른 제품에서도 사용되는 언어로, 선언적 인프라 정의에 최적화 되어있음. JSON과 유사한 구조를 가지면서도 주석이나 더 유연한 구문을 지원

구문 이해

HCL의 기본 구성 요소는 블록, 인자, 표현식이다

블록 (Block)

  • Terraform 구성의 가장 기본적인 컨테이너
  • 중괄호로 둘러싸여 있으며 내부에 인자나 다른 중첩블록 포함가능
  • 각 블록은 특정 종류의 리소스, 프로바이더, 변수 등을 정의하는데 사용됨
  • 일반적으로 타입과 레이블을 가짐
#'resource'는 블록타입, 'aws_instance'는 첫 번째 레이블(리소스 타입), 'web_server'는 두 번째 레이블(리소스 이름)
resource "aws_instance" "web_server" {
	#인자 또는 중첩 블록
}

#'variable'은 블록 타입, 'instance_type'은 레이블 (변수 이름)
variable "instance_type" {
	#인자 또는 중첩 블록
}

인자 (Argument)

  • 블록 내부에 정의되는 이름-값 쌍
  • 특정 리소스의 속성이나 변수의 정의 등을 나타냄
#예시
resource "aws_instance" "web_server" {
	ami           = "ami-0eb26f391850125a1" #ami : Amazon Machine Image
	instance_type = "t2.micro"
	tags = {
	    Name = "MyWebServer"
	}
}

표현식 (Expressions)

  • 값이나 참조를 나타내는 방법
  • 리소스의 속성, 변수, 함수호출, 산술 연산 등을 포함할 수 있음
  • Terraform은 이 표현식들을 평가하여 실제 값을 결정
#변수 참조 (var.변수명)
instance_type = var.instance_type

#리소스 속성 참조 (리소스타입.리소스이름.속성)
value = aws_instance.web_server.public_ip

#함수 호출 (함수명(인자))
name = "${upper(var.server_name)}-DEV" #upper()함수로 문자열을 대문자로 변환

#리스트, 맵, 숫자, 불리언 등 다양한 데이터 타입 지원
list_example = ["item1", "item2"]
map_example = { key1 = "value1", key2 = "value2"}
number_example = 123
bool_example = true

주석

# 단일 라인 주석

/*
멀티
라인
주석
*/

 


주요 블록

리소스

Terraform 코드의 가장 핵심적인부분으로 실제 생성/수정/삭제 될 인프라 자원을 정의

  • 리소스 블록의 구조와 역할 : 리소스 블록은 두 개의 레이블을 가짐
    • 리소스 타입 (Type)
      • 어떤 종류의 리소스를 생성할 것인지 정의
      • 프로바이더가 제공하는 리소스 타입에 따라 달라짐 → 프로바이더 문서 참고 필요
      • ex : aws_instance, aws_s3_bucket, azurerm_virtual_machine, google_compute_instance
    • 리소스 로컬 이름 (Local Name)
      • Terraform 구성 내에서 해당 리소스를 참조할 때 사용되는 고유한 이름
      • Terraform 코드 내에서만 유효하며 실제 클라우드 환경에 배포되는 리소스 이름과 다를 수 있음
      • 실제 리소스 이름은 tags나 특정 인자를 통해 지정 가능
#예시: AWS에 EC2인스턴스 생성
#main.tf

#프로바이더 설정: AWS 리전 정의
provider "aws" {
	region = "ap-northeast-2"
}

#EC2 인스턴스 리소스 정의
resource "aws_instance" "web_server" {
	ami           = "ami-0eb26f391850125a1" # Amazon Linux 2023 AMI ID (서울 리전)
	instance_type = "t2.micro" #인스턴스 타입
	
	tags = {
		Name = "MyTerraformEC2"
	}
}

#- aws_instance : AWS 프로바이더가 제공하는 EC2  인스턴스 리소스 타입
#- web_server : 이 Terraform 구성 내에서 이 EC2 인스턴스를 참조할 때 사용할 이름
  • 리소스 의존성 관리
    • Terraform은 기본적으로 암시적 의존성을 자동 파악함
    • 한 리소스가 다른 리소스의 속성 값을 참조하면 , 참조되는 리소스가 먼저 생성되어야 함을 자동으로 인지하고 순서를 결정한다는 의미
    • 때로는 Terraform이 의존성을 파악하기 어려운 경우에는 depends on  을 이용한 명시적 의존성을 사용
      • 자동 의존성 감지 실패 혹은 특정 순서가 논리적으로 필요한데 직접적인 참조가 없는 경우에만 사용
#S3 버킷 생성
resource "aws_s3_bucket" "my_website_bucket" {
	bucket = "my-unique-website-bucket-01012025"
	acl    = "private"
	
	tag.   = {
		Environment = "Dev"
		Project     = "Website"
	}
}

#S3 버킷 정책 생성
resource "aws_s3_bucket_policy" "bucket_policy" {
	bucket = aws_s3_bucket.my_website_bucket.id #암시적 의존성 : 버킷ID 참조
	policy = jsonencode({
		Version = "2012-10-17"
		Statement = [
			{
				Effect    = "Allow"
				Principal = "*"
				Action    = [
					"s3:GetObject"
				]
				Resource = [
					"${aws_s3_bucket.my_website_bucket_arm}/*" #암시적 의존성 : 버킷 ARN 참조
				]
			}
		]
	})
}

#aws_s3_bucket_policy 리소스는 
#aws_s3_bucket.my_website_bucket의 id와 arn 속성을 참조하고 있으므로
#Terraform은 자동으로 aws_s3_bucket이 aws_bucket_policy보다 먼저 생성해야한다고 판단

프로바이더

Terraform이 클라우드 서비스(AWS,Azure,GCP), SaaS(GitHub, DataDog), 온프레미스 솔루션(vSphere, Kubernetes)의 API와 상호작용할수 있도록 해주는 플러그인

  • 개념과 역할
    • Terraform은 자체적으로 인프라를 직접 제어하는 기능이 없음 대신 각 서비스의 API와 통신하기 위한 프로바이더를 사용
    • 프로바이더는 해당 서비스의 리소스 타입과 데이터 소스를 정의하고 Terraform 명령(plan, apply)이 실행될 때 실제 API 호출을 수행
  • 주요 클라우드 프로바이더 설정 예시
    • 일관성과 안정성 및 재현성을 위해서 버전 지정이 권장됨
    • ~> : 틸다와 함께 붙은 연산자, 지정된 최소 버전 이상이면서 다음 메이저 버전 미만 허용
#AWS
#main.tf or version.tf
#프로바이더 정의
terraform {
	requiered_providers {
		aws = {
			source = "hashicorp/aws" #프로바이더 소스(필수)
			version = "~> 5.0"       #프로바이더 버전 제약 (권장)
		}
	}
}

#프로바이더 상세 설정
provider "aws" {
	region = var.aws_region #프로바이더가 관리할 aws 리전/variables.tf 에 정의된 변수 사용
}

#Azure
terraform {
	required_providers {
		azurerm = {
			source = "hashicorp/azurerm"
			version = "~> 3.0"
		}
	}
}

provider "azurerm" {
	features {} #최신 버전에서는 불필요 할 수도 있음
}

#GCP
terraform {
	required_providers {
		google = {
			source = "hashicorp/google"
			version = "~> 5.0"
		}
	}
}

provider "google" {
	project = "gcp-project-id"
	region  = "asia-northeast3"
}

변수

  • Terrafomr 코드 유연성과 재사용성 부여하는 핵심 요소
  • 하드코딩 값 제거하고 환경,리전,인스턴스 타입 등 배포 시점에 변경될 수 있는 값들을 외부에서 주입할 수 있도록 함
  • 변수 타입
    • string : 일반적인 텍스트 문자열
    • number : 정수 또는 부동 소숫점 숫자
    • bool : true/false
    • list(<TYPE>) : 동일 타입 요소들을 순서대로 담는 컬렉션 (배열)
    • set(<TYPE>) : 순서 없고 중복 허용않는 동일한 타입 요소 컬렉션
    • map(<TYPE>): 문자열 키와 동일한 타입의 값을 가지는 컬렉션 (딕셔너리)
    • object({<KEY> = <TYPE>, …}) : 여러 타입의 값들을 가질수 있는 키-값쌍 컬렉션, 키 타입 명시 필수
#입력 변수 정의
variable "my_string_variable" {
	description = "설명"
	type        = string     #자료형
	default     = "기본값"
}

#입력 변수 사용 : var.변수명
resource "aws_instance" "example" {
	instance_type = var.my_string_variable
	...
}

#object type 변수
variable "server_config" {
	type = object({
		name          = string
		instance_type = string
		port          = number
		enabled       = bool
	})
	default = {
		name          = "default-server"
		instance_type = "t2.micro"
		port          = 80
		enabled       = true
	}
}
  • 기본값 설정
    • default 인자는 변수에 값이 주입되지 않았을 때 사요될 값을 지정
    • 항상 변수에 값을 주입하지 않아도 되는 편리함 제공
    • 기본 값 없는데 변수 값 주입되지 않으면 plan 이나 apply 실행 시 사용자에게 값을 입력하라는 프롬프트 출력
  • terraform.tfvars 파일
  • 환경 변수 및 CLI를 이용한 변수 전달
    • 환경 변수
      • TF_VAR_ 접두사를 사용해 환경변수로 변수값 설정 가능
      • 스크립트나 CI/CD파이프라인에서 주로 사용
      • terraform.tfvars 보다 높은 우선순위
    • CLI 옵션
      • terrafomr plan/apply 명령 실행시 -var,-var-file 옵션으로 변수 값 전달 가능
      • -var-file 이 여러개인 경우 마지막 지정 파일이 이전 파일의 동일 변수 덮어씀
      • terraform.tfvars 보다 높은 우선순위

출력

Terraform이 인프라 배포를 완료한 후, 특정 리소스의 속성 값이나 계산된 결과를 출력하도록 정의. 이 값들은 다른 Terraform 구성에서 사용되거나, 사용자에게 중요한 정보를 전달하는데 유용

  • 역할
    • 정보 전달 : 배포된 인프라의 IP 주소, DNS 이름, 연결 문자열, ID 등 필요 정보 출력
    • 모듈 간 연동: 한 모듈에서 생성된 인프라 정보를 다른 모듈의 입력변수로 전달할 때 사용
    • 스크립트 자동화 : Terraform 외부의 스크립트가 배포된 인프라 정보를 활용할 수 있도록 JSON 형식 등으로 출력 가능
  • terraform apply 완료 후, 또는 terraform output 명령을 통해 출력 값 확인 가능
#outputs.tf

#웹 서버 퍼블릭 IP 주소 출력
output "web_server_public_ip" {
	description = "The public IP address of the deployed web server"
	value.      = aws_instance.web_server.public_ip #리소스.로컬이름.속성 참조
}

#S3 버킷의 ARN(Amazon Resource Name)출력
output "s3_bucket_arn" {
	description = "The ARN of the created S3 bucket"
	value       = aws_s3_bucket.my_website_bucket.arn
}

#여러 값을 맵 형태로 출력
output "resource_details" {
	description = "Details of deployed resources"
	value = {
		ec2_id = aws_instance.web_server.id
		s3_name = aws_s3_bucket.my_website_bucket.bucket
		region = var.aws_region
	}
}

로컬 값

locals 블록은 Terrafomr 구성 내에서 재사용 가능한 명명된 표현식ㄷ을 정의할 때 사용. 변수와 비슷하지만 로컬 값은 외부에서 주입되는 입력이 아니라 Terraform 코드 내부에서 계산되거나 정의되는 값

  • 목적
    • 복잡한 표현식 단순화 : 반복적으로 사용되는 복잡한 표현식을 로컬값으로 정의해 가독성 향상
    • 계산된 값 재사용: 여러 리소스에서 공유될 필요가 있는 계산된 값(특정 패턴 이름 등)을 정의할 때 유용
    • 코드 추상화: 코드의 특정 부분을 더 명확하게 만들고, 의도를 드러내는데 도움 줌
#locals.tf or main.tf
locals {
	#프로젝트 이름과 환경을 조합해 리소스 접두사 정의
	#var.project_name과 var.environment 변수가 있다고 가정
	resource_prefix = "${var.project_name}-${var.envrionment}"
	
	#웹서버 인스턴스 이름 생성
	web_server_instance_name = "${local.resource_prefix}-web-server"
	
	#복잡한 계산 결과 (ex: 네트워크 CIDR 불록)
	vpc_cidr_block = "10.0.0.0/16"
	subnet_cidr_blokcs = [
		"10.0.1.0/24",
		"10.0.2.0/24",
		"10.0.3.0/24"
	]
	
	#조건부 값 설정
	enable_monitoring = var.environment == "prod" ? true : false
}

resource "aws_instance" "webserver" {
	ami           = var.ami_id
	instance_type = var.instance_type
	tags = {
		Name        = local.web_server_instnace_name
		Environment = var.environment
	}
}

데이터 소스

data 블록은 Terraform 외부의 정보(클라우드에 이미 존재하는 리소스 , 파일시스템 파일 등)를 조회하고 그 정보를 Terraform 구성 내에서 활용할 수 있도록 해줌. 기존 인프라를 읽어오는 역할

  • 기존 인프라 정보 조회 및 활용
    • Terraform이 관리하지 않거나, 이미 존재하는 리소스를 활용해야할 때 사용
    • ex : 특정 ami 코드를 하드코딩하는 대신 최신 AMI ID를 조회하는게 좋음
  • 사용법
    • 두 개의 레이블 사용
    • 데이터 소스 타입 : 조회할 데이터의 종류를 정의 (ex: aws_ami, aws_vpc 등)
    • 데이터 소스 로컬 이름 : Terraform 구성 내에서 이 데이터 소스를 참조할 때 사용되는 고유한 이름
  • 예시 : 기존 VPC ID, AMD ID 조회
#최신 Amazion Linux AMI Id 조회
data "aws_ami" "amazone_linux_latest" {
	most_recent = true
	owners      = ["amazon"]
	filter {
		name = "name"
		values = ["al2023-ami-*-kernel-6.1-x86_64"] #AMI 이름 패턴
	}
	filter {
		name = "virtualization-ty[e"
		values = ["hvm"]
	}
}

#데이터 소스에서 얻은 AMI ID로 EC2 인스턴스 생성
resource "aws_instance" "web_server_with_latest_ami" {
	ami           = data.aws_ami.amazon_linux_latest.id #데이터 소스 참조
	instance_type = "t2.micro"
	
	tags = {
		Name = "WebServerWithLatestAMI"
	}
}

#이름 태그를 통해 기존 VPC 조회
data "aws_vpc" "existing_vpc" {
	filter {
		name   = "tag:Name"
		values = ["my-production-vpc"] #기존 VPC Name 태그
	}
	#또는 ID로 직접 지정 id = "vpc-xxxxxxxxxxxxxxxxx"
}

#조회된 vpc id를 사용해 서브넷 생성
resource "aws_subnet" "public_subnet" {
	vpc_id            = data.aws_vpc.existing_vpc.id
	cidr_block        = "10.0.10.0/24"
	availability_zone = "ap-northeast-2a"
	
	tags = {
		Name = "MyPublicSubnet"
	}
}

'IaC > Terraform' 카테고리의 다른 글

[IaC] Terraform(6) - 워크스페이스  (0) 2025.09.25
[IaC] Terraform(5) - 상태관리  (0) 2025.09.25
[IaC] Terraform(4) - 모듈  (0) 2025.09.25
[IaC] Terraform(2) - 설치 및 기본 사용법  (1) 2025.09.15
[IaC] Terraform(1) - 개요  (0) 2025.09.15