목록으로 가기

Terraform으로 AWS 인프라 관리하기 — HCL 문법부터 Module까지

IaC가 뭔지, HCL로 코드를 어떻게 작성하는지, Provider/Resource/Variable/State/Module까지. Terraform을 처음 시작하는 사람을 위한 정리입니다.

Terraform으로 AWS 인프라 관리하기 — HCL 문법부터 Module까지 ko posts terraform IaC가 뭔지, HCL로 코드를 어떻게 작성하는지, Provider/Resource/Variable/State/Module까지. Terraform을 처음 시작하는 사람을 위한 정리입니다.

개요

Terraform은 인프라를 코드로 정의하고 관리하는 도구입니다. AWS 콘솔에서 클릭으로 EC2를 만드는 대신, 코드를 작성하면 Terraform이 API를 호출해서 리소스를 생성합니다.

콘솔 작업은 변경 내용이 코드로 남지 않습니다. 코드로 관리하면 Git에 변경 이력이 남고, 같은 정의로 동일한 인프라를 반복해서 만들 수 있습니다.

이 글은 Terraform을 처음 접하는 사람 기준으로 핵심 개념을 정리했습니다.

1. IaC (Infrastructure as Code)

정리 보기

IaC란

인프라(서버, 네트워크, DB 등)를 코드 파일로 정의하는 방식입니다. 수동 작업 대신 선언적으로 “이런 상태가 되어야 한다"고 적으면, 도구가 알아서 맞춰줍니다.

수동 관리 vs IaC

수동 관리:
  콘솔 로그인 → 클릭클릭 → EC2 생성 → 설정 변경 → 기록 없음

IaC:
  코드 작성 → Git 커밋 → terraform apply → 인프라 생성 → 변경 이력 추적 가능

항목별 비교

항목 수동 IaC
정의 위치 콘솔 조작 .tf 파일
변경 이력 클라우드 감사 로그(CloudTrail 등) Git 커밋 이력
적용 단위 리소스별 개별 작업 apply 한 번에 여러 리소스
되돌리기 이전 설정을 다시 입력 이전 커밋을 apply

IaC 도구 비교

도구 방식 언어 주요 용도
Terraform 선언적 HCL 클라우드 리소스 전반
Ansible 절차적 YAML 서버 설정/구성 관리
CloudFormation 선언적 JSON/YAML AWS 전용
Pulumi 선언적 TypeScript/Python 등 프로그래밍 언어로 인프라

Terraform은 provider 방식을 사용해 AWS, GCP, Azure 등 여러 플랫폼의 리소스를 같은 문법으로 관리할 수 있습니다.

2. Terraform 핵심 워크플로우

정리 보기

Terraform의 작업 흐름은 세 단계입니다.

Write → Plan → Apply

  ┌─────────┐      ┌─────────┐      ┌─────────┐
  │  Write  │ ──→  │  Plan   │ ──→  │  Apply  │
  │ .tf 작성 │      │ 변경 미리보기│      │ 실제 반영 │
  └─────────┘      └─────────┘      └─────────┘

Write: .tf 파일에 원하는 인프라 상태를 정의합니다.

Plan: terraform plan 명령으로 현재 상태와 코드를 비교해서, 뭐가 생기고 뭐가 바뀌고 뭐가 삭제될지 미리 보여줍니다.

Apply: terraform apply 명령으로 실제 클라우드에 반영합니다. plan 결과를 한 번 더 보여주고 승인(yes)을 받아야 실행됩니다.

전체 CLI 흐름

# 1. 프로젝트 초기화 (Provider 플러그인 다운로드)
terraform init

# 2. 변경 사항 미리 보기
terraform plan

# 3. 실제 인프라에 반영
terraform apply

# 4. Terraform이 관리하는 리소스 삭제
terraform destroy

Terraform은 AWS든 GCP든 각 클라우드별 플러그인(Provider)을 따로 다운로드받아야 합니다. init이 그 역할을 합니다.

3. HCL 문법

정리 보기

HCL (HashiCorp Configuration Language)은 Terraform이 사용하는 설정 언어입니다. JSON처럼 데이터를 표현하는데, 사람이 읽고 쓰기 편하게 만들어져 있습니다.

기본 구조

<블록 타입> "<블록 라벨>" "<블록 라벨>" {
  <인자> = <>
}

실제 예시

resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"

  tags = {
    Name = "web-server"
  }
}

위 코드의 의미:

  • resource: 블록 타입 (리소스를 만들겠다)
  • "aws_instance": 리소스 종류 (AWS EC2 인스턴스)
  • "web": 이 리소스의 이름 (코드 안에서 참조할 때 사용)
  • 중괄호 안: 설정값 (AMI, 인스턴스 타입 등)

데이터 타입

# 문자열
name = "web-server"

# 숫자
count = 3

# 불린
enable_monitoring = true

# 리스트
availability_zones = ["ap-northeast-2a", "ap-northeast-2c"]

# 맵
tags = {
  Name        = "web"
  Environment = "production"
}

주석

# 한 줄 주석
// 이것도   주석

/* 
  여러 줄 주석
*/

HCL은 프로그래밍 언어가 아니라 설정 언어입니다. 반복문이나 조건문도 있긴 하지만, 기본은 “원하는 상태를 선언하는 것"입니다.

4. Provider

정리 보기

Provider는 Terraform이 특정 클라우드나 서비스의 API와 통신할 수 있게 해주는 플러그인입니다.

Terraform Core ──→ Provider (AWS) ──→ AWS API
                ──→ Provider (GCP) ──→ GCP API
                ──→ Provider (K8s) ──→ Kubernetes API

Provider 설정 예시

# 사용할 Provider 선언
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Provider 설정
provider "aws" {
  region = "ap-northeast-2"  # 서울 리전
}

Provider

Provider 용도
aws AWS 리소스 관리
google GCP 리소스 관리
azurerm Azure 리소스 관리
kubernetes K8s 리소스 관리
helm Helm 차트 배포
github GitHub 레포/설정 관리

version 표기법

version = "5.0.0"    # 정확히 이 버전
version = "~> 5.0"   # 5.x 범위 (5.0 이상, 6.0 미만)
version = ">= 5.0"   # 5.0 이상 아무거나

~> 연산자는 마이너 업데이트는 허용하되 메이저 버전은 고정합니다. 예상치 못한 breaking change를 막으면서 패치를 받을 수 있습니다.

Provider는 Terraform Registry(registry.terraform.io)에서 찾을 수 있고, terraform init 하면 자동으로 다운로드됩니다.

5. Resource

정리 보기

Resource는 Terraform의 핵심입니다. 실제 인프라 객체(EC2, VPC, S3 등)를 정의하는 블록입니다.

기본 형태

resource "<provider>_<type>" "<name>" {
  # 설정
}

AWS 예시들

# VPC 생성
resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "main-vpc"
  }
}

# 서브넷 생성 (VPC 참조)
resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id  # 위에서 만든 VPC의 ID를 참조
  cidr_block = "10.0.1.0/24"

  tags = {
    Name = "public-subnet"
  }
}

# EC2 인스턴스 생성
resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public.id  # 위 서브넷에 배치

  tags = {
    Name = "web-server"
  }
}

리소스 참조

<리소스타입>.<리소스이름>.<속성>

예: aws_vpc.main.id
    aws_subnet.public.id

한 리소스가 다른 리소스를 참조하면, Terraform이 자동으로 의존 관계를 파악해서 생성 순서를 결정합니다. 위 예시에서는 VPC → 서브넷 → EC2 순서로 만들어집니다.

리소스 라이프사이클

terraform apply  → 리소스 생성 (Create)
코드 수정 후 apply → 리소스 수정 (Update) 또는 재생성 (Replace)
terraform destroy → 리소스 삭제 (Delete)

6. Variable과 Output

정리 보기

Variable (입력 변수)

코드에 값을 하드코딩하지 않고, 외부에서 주입할 수 있게 합니다.

# 변수 선언 (variables.tf)
variable "instance_type" {
  description = "EC2 인스턴스 타입"
  type        = string
  default     = "t3.micro"
}

variable "environment" {
  description = "배포 환경"
  type        = string
  # default 없으면 apply 시 입력 요구
}

variable "allowed_ports" {
  description = "허용할 포트 목록"
  type        = list(number)
  default     = [80, 443]
}
# 변수 사용 (main.tf)
resource "aws_instance" "web" {
  instance_type = var.instance_type

  tags = {
    Environment = var.environment
  }
}

변수 값 전달 방법

# 1. CLI에서 직접
terraform apply -var="environment=production"

# 2. 파일로 (terraform.tfvars → 자동 로드)
# terraform.tfvars
environment   = "production"
instance_type = "t3.small"

# 3. 환경 변수 (TF_VAR_ 접두사)
export TF_VAR_environment="production"

Output (출력 값)

apply 후 결과를 확인하거나, 다른 모듈에서 참조할 때 사용합니다.

# 출력 선언 (outputs.tf)
output "instance_ip" {
  description = "EC2 퍼블릭 IP"
  value       = aws_instance.web.public_ip
}

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}
# apply 후 출력 확인
terraform output
# instance_ip = "13.125.xxx.xxx"
# vpc_id = "vpc-0abc123def456"

HashiCorp 스타일 가이드는 변수 선언을 variables.tf, 출력 선언을 outputs.tf에 두는 구성을 제시합니다.

7. State (상태 관리)

정리 보기

State는 Terraform이 “현재 인프라가 어떤 상태인지” 기록하는 파일입니다. 코드와 실제 인프라 사이의 매핑 정보를 저장합니다.

왜 필요한가

코드 (.tf 파일):  "EC2 인스턴스가 있어야 한다"
State 파일:       "EC2 인스턴스 i-0abc123이 이미 있다"
실제 인프라:       EC2 인스턴스 i-0abc123 실행 중

→ Terraform은 State를 보고 "이미 있으니 변경할 것만 반영하면 되겠다" 판단

State가 없으면 Terraform은 매번 리소스를 새로 만들려고 합니다.

State 파일

# 기본: 로컬 파일로 저장
terraform.tfstate        # 현재 상태
terraform.tfstate.backup # 이전 상태 백업

원격 State (Backend)

기본 backend는 State를 작업 디렉터리의 로컬 파일에 저장합니다. S3 같은 원격 backend를 설정하면 State가 원격 저장소에 저장되고 여러 사용자가 같은 State를 참조합니다.

# 원격 Backend 설정
terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/terraform.tfstate"
    region = "ap-northeast-2"

    # State 동시 수정 방지 (S3 네이티브 Lock)
    use_lockfile = true
  }
}

S3 backend에서 bucket, key, region은 필수 설정이고 잠금은 선택(opt-in)입니다. use_lockfile = true로 켜면 State 파일과 같은 위치에 .tflock 확장자를 가진 잠금 파일이 생깁니다. 이 잠금 파일에는 s3:GetObject, s3:PutObject, s3:DeleteObject 권한이 필요합니다.

잠금 방식이 최근에 바뀐 부분입니다. 예전에는 dynamodb_table로 DynamoDB 테이블을 지정하는 방법만 있었는데, S3 네이티브 잠금이 Terraform 1.10에서 실험적 기능으로 들어왔고 1.11에서 정식 기능이 되면서 DynamoDB 관련 인자들은 deprecated로 바뀌었습니다. 공식 문서는 DynamoDB 기반 잠금이 향후 마이너 버전에서 제거될 예정이라고 안내하고, 마이그레이션 목적이라면 두 방식을 동시에 설정할 수 있다고 설명합니다.

# 오래된 Terraform과 State를 공유하는 중이라면 마이그레이션 기간에만 병행
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-2"
    use_lockfile   = true
    dynamodb_table = "terraform-locks"  # deprecated
  }
}
  • use_lockfile은 Terraform 1.10 이상에서 사용할 수 있고, 기본값은 false입니다.
  • 두 방식을 함께 설정하면 양쪽에서 잠금을 모두 획득해야 작업이 진행됩니다.
  • 공식 문서는 State 복구를 위해 S3 버킷의 Bucket Versioning을 켜는 것을 강하게 권장합니다.

State 관련 제약

  • State 파일에는 리소스 속성이 그대로 저장되므로 비밀번호나 키 같은 민감 정보가 포함될 수 있습니다
  • State는 구성과 실제 인프라의 매핑이므로 직접 편집하면 매핑이 깨집니다
  • 원격 backend는 잠금을 지원해 동시 apply로 인한 충돌을 막습니다

State 명령어

# 현재 State에 있는 리소스 목록
terraform state list

# 특정 리소스 상세 정보
terraform state show aws_instance.web

# 리소스를 State에서 제거 (실제 삭제 아님)
terraform state rm aws_instance.web

# 기존 리소스를 State에 가져오기
terraform import aws_instance.web i-0abc123def456

8. Module (모듈)

정리 보기

Module은 여러 리소스를 하나로 묶어서 재사용할 수 있게 만든 것입니다.

왜 쓰는가

같은 구조의 VPC를 dev, staging, prod 마다 만들어야 한다면, 매번 복붙하는 대신 모듈로 만들어서 호출합니다.

디렉터리 구조

project/
├── main.tf          # 루트 모듈 (여기서 자식 모듈 호출)
├── variables.tf
├── outputs.tf
├── terraform.tfvars
└── modules/
    └── vpc/         # 자식 모듈
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

모듈 정의 (modules/vpc/main.tf)

variable "cidr_block" {
  type = string
}

variable "name" {
  type = string
}

resource "aws_vpc" "this" {
  cidr_block = var.cidr_block

  tags = {
    Name = var.name
  }
}

output "vpc_id" {
  value = aws_vpc.this.id
}

모듈 호출 (main.tf)

module "vpc_prod" {
  source     = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  name       = "prod-vpc"
}

module "vpc_dev" {
  source     = "./modules/vpc"
  cidr_block = "10.1.0.0/16"
  name       = "dev-vpc"
}

# 모듈 출력값 참조
output "prod_vpc_id" {
  value = module.vpc_prod.vpc_id
}

공개 모듈 사용

Terraform Registry에 공개된 모듈을 가져다 쓸 수도 있습니다.

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-2a", "ap-northeast-2c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
}

9. 프로젝트 구조

정리 보기

소규모 프로젝트와 환경별로 분리하는 구조를 비교합니다.

소규모 (단일 환경)

project/
├── main.tf           # 리소스 정의
├── variables.tf      # 변수 선언
├── outputs.tf        # 출력값
├── terraform.tfvars  # 변수 값
└── provider.tf       # Provider 설정

중규모 (환경 분리)

infra/
├── modules/
│   ├── vpc/
│   ├── ec2/
│   └── rds/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   ├── staging/
│   │   ├── main.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   └── prod/
│       ├── main.tf
│       ├── terraform.tfvars
│       └── backend.tf

파일 역할 정리

파일 역할
main.tf 리소스 및 모듈 호출
variables.tf 입력 변수 선언
outputs.tf 출력값 선언
terraform.tfvars 변수에 넣을 실제 값
provider.tf Provider 및 버전 설정
backend.tf State 저장 위치 설정

환경(dev/staging/prod)마다 디렉터리를 분리하면 디렉터리마다 별도 State와 backend를 갖습니다.

10. 명령어 정리

정리 보기
# 초기화 (Provider 다운로드, Backend 설정)
terraform init

# 코드 포맷 정리
terraform fmt

# 문법 검증
terraform validate

# 변경 사항 미리보기
terraform plan

# 인프라 반영
terraform apply

# 자동 승인 (CI/CD에서 사용)
terraform apply -auto-approve

# 인프라 삭제
terraform destroy

# 현재 State 확인
terraform state list
terraform state show <resource>

# 기존 리소스를 Terraform 관리 하에 가져오기
terraform import <resource> <id>

# 출력값 확인
terraform output

plan 읽는 법

# terraform plan 결과 예시:

  + aws_instance.web          ← + 생성될 리소스
  ~ aws_vpc.main              ← ~ 수정될 리소스
  - aws_subnet.old            ← - 삭제될 리소스
-/+ aws_instance.api          ← 삭제 후 재생성

plan 출력에서 -는 삭제, -/+는 재생성을 뜻합니다.

관련 포스트

Terraform 반복문, Lifecycle, Import, CI/CD 자동화 — 심화 패턴 for_each로 리소스 반복 생성, lifecycle로 삭제 방지, 기존 인프라 import, GitHub …