Terraform (Infrastructure as Code)
Terraform 핵심 개념, HCL 문법, 주요 명령어, AWS 튜토리얼까지 정리
- Terraform
Terraform
이 문서는 HashiCorp Terraform의 핵심 개념부터 실습 튜토리얼까지 한 곳에 정리한 문서다. 공식 문서(Docs, Tutorials)를 기반으로 작성했다.
1) Terraform이란
Terraform은 인프라를 코드로 정의하고, 버전 관리하며, 안전하게 프로비저닝하는 도구다.
수동으로 클라우드 콘솔을 클릭하는 대신, 선언형 설정 파일로 원하는 상태를 기술하면 Terraform이 실제 인프라를 그 상태에 맞춘다.
Infrastructure as Code
- 인프라를 소프트웨어처럼 다룬다 - 버전 관리, 코드 리뷰, 재사용
- 수동 프로비저닝 제거로 휴먼 에러 감소
- 팀 간 표준화된 구성 공유 가능
핵심 워크플로우
flowchart LR
W["Write\n(HCL로 리소스 정의)"]
P["Plan\n(변경 사항 미리 확인)"]
A["Apply\n(실제 인프라에 반영)"]
W --> P --> A
| 단계 | 설명 |
|---|---|
| Write | HCL로 리소스를 정의한다 |
| Plan | 변경 사항을 미리 확인한다 (terraform plan) |
| Apply | 승인 후 실제 인프라에 반영한다 (terraform apply) |
왜 Terraform인가
- 선언형 - 원하는 최종 상태를 기술하면, Terraform이 도달 방법을 알아서 계산한다
- Provider 생태계 - AWS, Azure, GCP, Kubernetes, GitHub, Datadog 등 수천 개의 Provider 지원
- State 기반 - 현재 인프라 상태를 추적하여 변경 사항만 정확히 적용
- 모듈화 - 재사용 가능한 모듈로 인프라를 표준화
- 병렬 처리 - 의존성 없는 리소스를 동시에 프로비저닝
2) HCL (HashiCorp Configuration Language)
Terraform 설정 파일의 언어다. 블록, 인자, 표현식으로 구성된다.
기본 문법
<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
<IDENTIFIER> = <EXPRESSION>
}
실제 예시:
resource "aws_vpc" "main" {
cidr_block = var.base_cidr_block
}
- 블록(Block) - 설정 객체의 컨테이너. 블록 타입, 레이블, 본문으로 구성
- 인자(Argument) - 블록 안에서 이름에 값을 할당
- 표현식(Expression) - 값을 직접 쓰거나 다른 값을 참조/조합
핵심 특성
- 선언형이므로 블록/파일의 순서가 중요하지 않다
- Terraform이 리소스 간 암시적/명시적 관계를 파악하여 실행 순서를 결정한다
- 하나의 설정은 여러 파일과 디렉토리로 구성할 수 있다
3) Provider
Provider는 Terraform이 클라우드/SaaS/API와 통신하기 위한 플러그인이다. Provider 없이는 어떤 인프라도 관리할 수 없다.
flowchart TB
TF["Terraform Core"]
TF --> AWS["AWS Provider"]
TF --> AZ["Azure Provider"]
TF --> GCP["GCP Provider"]
TF --> K8S["Kubernetes Provider"]
AWS --> A1["EC2, S3, RDS ..."]
AZ --> A2["VM, Blob, SQL ..."]
GCP --> A3["GCE, GCS, Cloud SQL ..."]
K8S --> A4["Pod, Service, Deployment ..."]
Provider 설정
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.92"
}
}
}
provider "aws" {
region = "us-west-2"
}
required_providers블록에서 사용할 Provider를 선언한다provider블록에서 리전, 인증 등 세부 설정을 한다terraform init실행 시 자동으로 다운로드 및 설치된다
Provider 티어
| 티어 | 설명 |
|---|---|
| Official | HashiCorp이 직접 관리 |
| Partner | 서드파티 기업이 자체 API에 맞게 작성/검증 |
| Community | 개인/그룹 메인테이너가 배포 |
Dependency Lock File
terraform init 시 .terraform.lock.hcl 파일이 생성된다.
Provider 버전을 고정하여 팀 전체가 동일한 버전을 사용하도록 보장한다. 반드시 버전 관리에 커밋해야 한다.
4) Resource
Resource는 Terraform에서 가장 중요한 요소다. 실제로 생성하고 관리할 인프라 객체를 나타낸다.
resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
tags = {
Name = "learn-terraform"
}
}
Resource 동작 방식
terraform apply 실행 시 Terraform은 다음을 수행한다:
flowchart TD
APPLY["terraform apply"]
APPLY --> CMP["설정 vs State 비교"]
CMP --> C["설정에만 존재"]
CMP --> D["State에만 존재"]
CMP --> U["양쪽 모두 존재\n(인자 변경됨)"]
C --> CREATE["리소스 생성"]
D --> DESTROY["리소스 삭제"]
U --> IP{"in-place\n업데이트 가능?"}
IP -- "Yes" --> UPDATE["리소스 업데이트"]
IP -- "No" --> RECREATE["삭제 후 재생성"]
CREATE --> SYNC["State 파일 동기화"]
DESTROY --> SYNC
UPDATE --> SYNC
RECREATE --> SYNC
Resource 주소
<RESOURCE TYPE>.<NAME> 형식으로 참조한다.
aws_instance.app_server.id
aws_instance.app_server.private_dns
5) Data Source
Data Source는 외부 데이터를 읽어오는 읽기 전용 작업이다. 리소스를 생성하지 않는다.
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
owners = ["099720109477"] # Canonical
}
참조 방법
data.<TYPE>.<LABEL>.<ATTRIBUTE> 형식으로 참조한다.
resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
}
동작 시점
- Plan 단계에서 쿼리를 시도한다
- 인자가 아직 생성되지 않은 리소스에 의존하면 Apply 단계로 지연된다
6) Variable (입력 변수)
설정을 파라미터화하여 재사용성을 높인다.
선언
variable "instance_name" {
description = "EC2 인스턴스의 Name 태그 값"
type = string
default = "learn-terraform"
}
variable "instance_type" {
description = "EC2 인스턴스 타입"
type = string
default = "t2.micro"
}
사용
var.<NAME> 으로 참조한다.
resource "aws_instance" "app_server" {
instance_type = var.instance_type
tags = {
Name = var.instance_name
}
}
주요 속성
| 속성 | 설명 |
|---|---|
type |
변수 타입 (string, number, bool, list, map, object 등) |
description |
변수 설명 |
default |
기본값 |
sensitive |
true면 CLI 출력에서 마스킹 |
validation |
커스텀 유효성 검사 규칙 |
값 할당 우선순위 (높은 순)
flowchart TD
A["1. CLI 플래그 (-var, -var-file)"] --> B["2. *.auto.tfvars (사전순)"]
B --> C["3. terraform.tfvars.json"]
C --> D["4. terraform.tfvars"]
D --> E["5. 환경 변수 (TF_VAR_ 접두사)"]
E --> F["6. default 값"]
style A fill:#4a7a46,color:#fff
style F fill:#8a8a8a,color:#fff
- CLI 플래그 (
-var,-var-file) *.auto.tfvars파일 (사전순)terraform.tfvars.jsonterraform.tfvars- 환경 변수 (
TF_VAR_접두사) default값
7) Output (출력 값)
인프라의 속성을 외부로 노출한다.
output "instance_hostname" {
description = "EC2 인스턴스의 Private DNS"
value = aws_instance.app_server.private_dns
}
용도
- CLI에서
terraform output으로 확인 - 자식 모듈의 속성을 부모 모듈에 노출
- 다른 Terraform 설정에서
terraform_remote_state데이터 소스로 참조 - 외부 자동화 도구에 정보 전달
자식 모듈 출력 참조
module.<CHILD_MODULE_NAME>.<OUTPUT_NAME>
8) State
Terraform은 관리 중인 인프라의 상태를 State 파일에 저장한다. 설정 파일의 리소스와 실제 인프라 객체 간의 매핑을 추적한다.
flowchart LR
CONFIG["설정 파일\n(.tf)"]
STATE["State 파일\n(.tfstate)"]
INFRA["실제 인프라\n(AWS, GCP ...)"]
CONFIG <-- "비교" --> STATE
STATE <-- "매핑" --> INFRA
핵심 역할
- 설정의 리소스 인스턴스와 실제 원격 객체 간 바인딩 저장
- 메타데이터 추적 (의존성 정보 등)
- 대규모 인프라에서 성능 최적화 (캐싱)
저장 위치
- 기본값: 로컬 파일
terraform.tfstate - 팀 환경에서는 HCP Terraform 등 원격 백엔드를 사용하여 버전 관리, 암호화, 공유
State 관리 명령어
| 명령어 | 설명 |
|---|---|
terraform state list |
관리 중인 리소스 목록 |
terraform state show <addr> |
특정 리소스의 상세 상태 |
terraform state rm <addr> |
State에서 리소스 제거 (실제 인프라는 유지) |
terraform import <addr> <id> |
기존 인프라를 State에 추가 |
State 파일은 JSON 형식이지만 직접 편집하지 말 것. 반드시 CLI 명령어를 사용한다.
9) Module
Module은 함께 관리되는 리소스들의 모음이다. 인프라를 표준화하고 재사용할 수 있게 한다.
Root Module vs Child Module
flowchart TB
ROOT["Root Module\n(작업 디렉토리)"]
ROOT --> VPC["Child Module\n(VPC)"]
ROOT --> EC2["Child Module\n(Compute)"]
ROOT --> RDS["Child Module\n(Database)"]
VPC --> V1["aws_vpc"]
VPC --> V2["aws_subnet"]
EC2 --> E1["aws_instance"]
EC2 --> E2["aws_security_group"]
RDS --> R1["aws_db_instance"]
- Root Module - 작업 디렉토리의
.tf파일들. 모든 Terraform 워크스페이스가 가진다. - Child Module -
module블록으로 호출되는 외부 모듈
Module 사용
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.19.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
}
Module 소스
| 소스 | 예시 |
|---|---|
| Terraform Registry | "terraform-aws-modules/vpc/aws" |
| 로컬 경로 | "./modules/vpc" |
| GitHub | "github.com/hashicorp/example" |
| S3 | "s3::https://s3.amazonaws.com/bucket/module.zip" |
Module 워크플로우
- Develop - 리소스를 모듈 구조로 설계
- Distribute - Registry에 배포하거나 VCS로 공유
- Provision -
module블록으로 호출하여 사용
10) CLI 주요 명령어
flowchart LR
INIT["terraform init\n(초기화)"]
PLAN["terraform plan\n(계획)"]
APPLY["terraform apply\n(적용)"]
DESTROY["terraform destroy\n(삭제)"]
INIT --> PLAN --> APPLY
APPLY -.-> DESTROY
terraform init
작업 디렉토리를 초기화한다. 새 설정을 작성하거나 클론한 후 가장 먼저 실행해야 하는 명령어.
수행하는 작업:
- 백엔드 초기화 - State 저장소 설정
- 모듈 설치 -
module블록의 소스 코드 다운로드 - Provider 설치 - 필요한 Provider 플러그인 다운로드, Lock 파일 생성
terraform init
terraform init -upgrade # 모듈/Provider를 최신 버전으로 업그레이드
terraform plan
실행 계획을 생성한다. 실제 변경을 수행하지 않는다.
terraform plan # 기본 - 변경 사항 미리보기
terraform plan -destroy # 삭제 계획 미리보기
terraform plan -out=tfplan # 계획을 파일로 저장 (자동화 시 권장)
terraform plan -var 'key=val' # 변수를 지정하며 계획
계획 모드:
| 모드 | 설명 |
|---|---|
| Normal (기본) | 설정에 맞게 인프라 변경 |
Destroy (-destroy) |
모든 리소스 삭제 계획 |
Refresh-only (-refresh-only) |
State만 실제 인프라에 동기화 |
terraform apply
Plan의 변경 사항을 실제로 적용한다.
terraform apply # 계획 생성 -> 승인 -> 적용
terraform apply tfplan # 저장된 계획 파일로 적용 (승인 없이)
terraform apply -auto-approve # 승인 없이 바로 적용 (주의)
terraform destroy
관리 중인 모든 리소스를 삭제한다. terraform apply -destroy의 단축 명령.
terraform destroy # 전체 삭제
terraform destroy -target aws_instance.app # 특정 리소스만 삭제
기타 유용한 명령어
terraform fmt # 설정 파일 포맷팅
terraform validate # 문법 검증
terraform output # 출력 값 확인
terraform show # 현재 State 상세 출력
11) 튜토리얼: AWS 인프라 구축
공식 AWS Get Started 튜토리얼을 기반으로 정리한다.
Step 1 - Terraform 설치
macOS (Homebrew):
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Ubuntu/Debian:
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
설치 확인:
terraform -help
terraform -install-autocomplete # 탭 자동완성 설정
Step 2 - 프로젝트 구성
mkdir learn-terraform-aws && cd learn-terraform-aws
terraform.tf - Terraform과 Provider 버전을 선언한다:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.92"
}
}
required_version = ">= 1.2"
}
main.tf - Provider 설정과 리소스를 정의한다:
provider "aws" {
region = "us-west-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = var.instance_name
}
}
variables.tf - 입력 변수를 분리한다:
variable "instance_name" {
description = "EC2 인스턴스의 Name 태그 값"
type = string
default = "learn-terraform"
}
variable "instance_type" {
description = "EC2 인스턴스 타입"
type = string
default = "t2.micro"
}
outputs.tf - 출력 값을 정의한다:
output "instance_hostname" {
description = "EC2 인스턴스의 Private DNS"
value = aws_instance.app_server.private_dns
}
Step 3 - 인프라 생성
# AWS 인증 설정
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
# 초기화 - Provider 다운로드
terraform init
# 포맷팅 및 검증
terraform fmt
terraform validate
# 실행 계획 확인
terraform plan
# 인프라 생성 (yes 입력으로 승인)
terraform apply
Apply 실행 시 + 기호로 생성될 리소스를 보여준다:
# aws_instance.app_server will be created
+ resource "aws_instance" "app_server" {
+ ami = "ami-0c55b159cbfafe1f0"
+ instance_type = "t2.micro"
+ tags = {
+ "Name" = "learn-terraform"
}
...
}
Plan: 1 to add, 0 to change, 0 to destroy.
Step 4 - 인프라 변경
변수를 통해 인스턴스 타입을 변경할 수 있다:
terraform plan -var instance_type=t2.large
또는 variables.tf의 default 값을 수정한 뒤:
terraform apply
Terraform은 변경 가능한 속성은 in-place 업데이트하고, 불가능한 속성(예: AMI 변경)은 삭제 후 재생성한다.
Step 5 - 상태 확인
terraform state list # 관리 중인 리소스 목록
terraform state show aws_instance.app_server # 상세 정보
terraform output # 출력 값 확인
terraform show # 전체 State 출력
Step 6 - 인프라 삭제
특정 리소스만 삭제 - 설정 파일에서 해당 리소스 블록을 제거한 뒤:
terraform apply
전체 삭제:
terraform destroy
리소스는 의존성의 역순으로 삭제된다.
12) 모듈 활용 튜토리얼
Registry 모듈 사용
공개 Registry의 VPC 모듈로 네트워크를 구성하는 예시:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.19.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
}
모듈 추가 후 반드시 terraform init 을 다시 실행해야 한다.
로컬 모듈 작성
디렉토리 구조:
.
├── main.tf
├── modules/
│ └── s3-static-website/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
로컬 모듈 호출:
module "website" {
source = "./modules/s3-static-website"
bucket_name = "my-static-site"
}
모듈 리팩토링
기존 리소스를 모듈로 이동할 때 moved 블록을 사용하면 삭제/재생성 없이 State를 업데이트할 수 있다:
moved {
from = aws_instance.app_server
to = module.compute.aws_instance.app_server
}
13) 튜토리얼: Docker 컨테이너 관리
AWS 계정 없이 로컬에서 바로 실습할 수 있는 Docker 튜토리얼이다. 공식 Docker Get Started 튜토리얼을 기반으로 정리한다.
사전 준비
- Terraform CLI (0.15+)
- Docker 설치 및 실행 중
Step 1 - 프로젝트 생성
mkdir learn-terraform-docker-container
cd learn-terraform-docker-container
Step 2 - 설정 파일 작성
main.tf:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 3.0.1"
}
}
}
provider "docker" {}
resource "docker_image" "nginx" {
name = "nginx:latest"
keep_locally = false
}
resource "docker_container" "nginx" {
image = docker_image.nginx.image_id
name = var.container_name
ports {
internal = 80
external = 8000
}
}
Windows의 경우 provider 블록에
host = "npipe:////.//pipe//docker_engine"을 추가한다.
variables.tf:
variable "container_name" {
description = "Docker 컨테이너 이름"
type = string
default = "tutorial"
}
outputs.tf:
output "container_id" {
description = "Docker 컨테이너 ID"
value = docker_container.nginx.id
}
output "image_id" {
description = "Docker 이미지 ID"
value = docker_image.nginx.id
}
Step 3 - 컨테이너 생성
terraform init
terraform fmt
terraform validate
terraform apply
완료 후 localhost:8000 으로 접속하면 Nginx 기본 페이지가 보인다.
flowchart LR
TF["terraform apply"]
TF --> IMG["docker_image.nginx\n(nginx:latest 풀)"]
IMG --> CTR["docker_container.nginx\n(포트 80:8000)"]
CTR --> WEB["localhost:8000"]
Step 4 - 상태 확인
terraform state list
# docker_container.nginx
# docker_image.nginx
terraform show # 전체 State 상세 출력
terraform output # 출력 값 확인
Step 5 - 인프라 변경
포트를 8000에서 8080으로 변경한다. main.tf에서:
ports {
internal = 80
external = 8080
}
terraform apply
Docker는 실행 중인 컨테이너의 포트를 변경할 수 없으므로, Terraform은 기존 컨테이너를 삭제 후 재생성한다.
실행 계획에서 -/+ 접두사와 # forces replacement 표시로 확인할 수 있다.
변수로도 변경할 수 있다:
terraform apply -var "container_name=my-nginx"
Step 6 - 인프라 삭제
terraform destroy
Terraform이 의존성 순서대로 리소스를 삭제한다:
docker_container.nginx: Destroying...
docker_container.nginx: Destruction complete after 2s
docker_image.nginx: Destroying...
docker_image.nginx: Destruction complete after 0s
Destroy complete! Resources: 2 destroyed.
Docker 튜토리얼 최종 파일 구조
learn-terraform-docker-container/
├── main.tf # Provider + docker_image + docker_container
├── variables.tf # container_name 변수
└── outputs.tf # container_id, image_id 출력
14) 파일 구조 권장 패턴
project/
├── terraform.tf # required_providers, backend 설정
├── main.tf # 리소스 정의
├── variables.tf # 입력 변수
├── outputs.tf # 출력 값
├── terraform.tfvars # 변수 값 (gitignore 권장)
└── modules/ # 로컬 모듈
└── <module>/
├── main.tf
├── variables.tf
└── outputs.tf