Terraform 완벽 가이드 — Infrastructure as Code·멀티클라우드
이 글의 핵심
Terraform은 선언적 Infrastructure as Code(IaC) 도구로, 원하는 인프라 상태를 HCL로 기술하고 프로바이더를 통해 클라우드 리소스를 일관되게 생성·변경·삭제합니다. 이 글에서는 핵심 개념, HCL과 모듈, AWS·Azure·GCP 프로바이더, State·원격 백엔드, Workspace와 환경 분리, 보안 실무, 실전 구성 예제까지 한 흐름으로 정리합니다.
이 글의 핵심
Terraform은 HashiCorp가 만든 오픈소스 Infrastructure as Code(IaC) 도구입니다. 콘솔에서 클릭으로 만든 인프라는 재현이 어렵고 감사 추적이 부족합니다. Terraform은 원하는 최종 상태를 코드로 남기고, 현재 상태(State) 와의 차이를 계산한 뒤 안전하게 적용하는 흐름을 제공합니다.
이 글에서 다루는 범위는 다음과 같습니다.
- Terraform의 핵심 개념(프로바이더, 리소스, 의존성, 플랜/적용, State)
- HCL(HashiCorp Configuration Language) 문법과 모듈 설계
- AWS, Microsoft Azure, Google Cloud 프로바이더 사용 시 유의점
- State 관리, 원격 백엔드, 잠금(Lock) 과 협업
- Workspace와 환경(dev/stage/prod) 분리 전략
- 보안 베스트 프랙티스(시크릿, 최소 권한, 공급망)
- 실전 예제: VPC·서브넷·컴퓨트·로드밸런서 수준의 구성 골격
전제: Terraform CLI 설치, 대상 클라우드 계정 및 결제·권한이 있다고 가정합니다. 예제는 교육용이므로 실제 적용 전 비용·쿼터·조직 보안 정책을 반드시 확인하십시오.
1. Terraform과 Infrastructure as Code
1.1 선언적 모델과 멱등성
Terraform은 선언적(declarative) 입니다. “어떤 순서로 스크립트를 실행할지”가 아니라 “어떤 인프라가 존재해야 하는지”를 기술합니다. 동일한 구성을 다시 적용하면(리소스가 이미 목표 상태이면) 변경이 없거나 필요한 부분만 조정되는 멱등성(idempotency) 을 목표로 설계됩니다.
실무에서는 다음이 큰 이점으로 작용합니다.
- 재현성: Git에 코드를 두면 동일한 환경을 다시 만들 수 있습니다.
- 리뷰 가능: Pull Request로 인프라 변경을 코드 리뷰할 수 있습니다.
- 사전 검증:
terraform plan으로 적용 전 영향 범위를 확인할 수 있습니다.
1.2 핵심 구성 요소
| 개념 | 설명 |
|---|---|
| Provider | AWS, Azure, GCP 등 클라우드 API에 연결하는 플러그인 |
| Resource | 생성·관리할 인프라 객체(예: VPC, VM, 버킷) |
| Data source | 생성하지 않고 기존 리소스 정보를 읽기만 할 때 사용 |
| State | Terraform이 추적하는 현재 리소스와 속성의 스냅샷 |
| Module | 재사용 가능한 .tf 묶음(입력 변수·출력으로 캡슐화) |
Terraform은 리소스 간 참조를 분석해 의존성 그래프를 만들고, 병렬로 생성 가능한 작업은 동시에 수행합니다. 명시적 의존이 필요하면 depends_on을 사용할 수 있습니다.
1.3 기본 워크플로
- 작성:
.tf파일에resource등을 정의합니다. - 초기화:
terraform init으로 프로바이더와 백엔드 모듈을 내려받습니다. - 계획:
terraform plan으로 변경 예정 내용을 확인합니다. - 적용:
terraform apply로 실제 인프라에 반영합니다. - 파괴(선택): 학습·검증 환경에서는
terraform destroy로 정리합니다.
운영 환경에서는 plan 결과를 아티팩트로 저장하고, 승인 후 apply하는 CI/CD 파이프라인과 결합하는 경우가 일반적입니다.
2. HCL 문법과 모듈
2.1 terraform 블록과 required_providers
버전을 고정하면 팀 전체가 동일한 프로바이더 동작을 기대할 수 있습니다. ~> 같은 버전 제약은 Semantic Versioning 관례에 맞춥니다.
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
위 설정은 Terraform 코어 버전 하한과 AWS 프로바이더 5.x 대를 허용합니다. 조직 표준이 있다면 required_version을 CI에서 강제하는 것이 좋습니다.
2.2 변수·출력·지역 값
입력 변수(variable) 로 환경별 차이를 주고, 출력(output) 으로 다른 스택이나 모듈 소비자에게 값을 노출합니다. 지역 값(locals) 은 같은 디렉터리 내에서 반복 표현을 줄이는 데 씁니다.
variable "environment" {
type = string
description = "배포 환경 이름"
}
locals {
name_prefix = "app-${var.environment}"
}
output "name_prefix" {
value = local.name_prefix
description = "리소스 이름 접두사"
}
description과 type을 습관적으로 적어 두면 모듈 사용자가 실수로 잘못된 값을 넣는 것을 줄일 수 있습니다.
2.3 모듈 설계
모듈은 디렉터리 단위로 묶인 Terraform 구성입니다. 루트 모듈이 하위 디렉터리를 module 블록으로 호출합니다.
- 재사용: 동일한 네트워크 패턴을 여러 프로젝트에 복사하지 않고 모듈로 공유합니다.
- 인터페이스:
variables.tf와outputs.tf로 경계를 명확히 합니다. - 합성: 작은 모듈(VPC, 서브넷, 보안 그룹)을 조합해 상위 스택을 만듭니다.
모듈을 Git URL·Terraform Registry에서 가져오는 경우, 태그된 버전을 참조해 공급망을 고정하는 것이 안전합니다.
module "network" {
source = "./modules/network"
cidr_block = "10.0.0.0/16"
az_count = 3
}
모듈 내부에서는 가능한 한 환경 고유 문자열을 입력으로만 받고, 하드코딩된 계정 ID나 리전을 피하는 편이 이식성이 높습니다.
3. AWS, Azure, GCP 프로바이더
클라우드마다 리소스 이름·ID 규칙·태그·IAM 모델이 다릅니다. Terraform은 API를 추상화하지만, 각 프로바이더 문서를 함께 읽는 것이 필수입니다.
3.1 AWS (hashicorp/aws)
가장 널리 쓰이는 프로바이더 중 하나입니다. 리전, 자격 증명 체인(환경 변수, 공유 자격 증명 파일, IAM 역할 등)을 이해해야 합니다.
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_s3_bucket" "logs" {
bucket = "example-logs-unique-suffix"
}
실무 팁: S3 버킷 이름은 전역 유일해야 하고, 퍼블릭 액세스 차단·암호화·수명 주기 정책을 함께 정의하는 경우가 많습니다. 민감 로그는 별도 거버넌스 정책과 연동합니다.
3.2 Azure (hashicorp/azurerm)
구독(Subscription)·리소스 그룹·테넌트 개념이 중요합니다. 서비스 프린시펄이나 Managed Identity로 CI에서 비대화형 인증을 구성합니다.
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "main" {
name = "rg-example"
location = "Korea Central"
}
Azure는 리소스마다 이름 규칙과 제약이 다르므로, 모듈에서 유효성 검사(문자 길이 등)를 변수 제약으로 두면 배포 실패를 줄일 수 있습니다.
3.3 Google Cloud (hashicorp/google)
프로젝트 ID, 리전/존, 서비스 API 활성화가 선행되는 경우가 많습니다.
provider "google" {
project = var.gcp_project_id
region = "asia-northeast3"
}
resource "google_storage_bucket" "assets" {
name = "${var.gcp_project_id}-assets-unique"
location = "ASIA-NORTHEAST3"
force_destroy = false
}
GCP에서는 IAM을 최소 권한으로 쪼개고, VPC 서비스 컨트롤·조직 정책 같은 상위 거버넌스와 충돌하지 않는지 확인해야 합니다.
3.4 멀티 클라우드 운영 시 유의점
동일한 HCL “문법”을 쓰더라도 리소스 의미는 클라우드마다 다릅니다. “VPC에 해당한다”는 추상화를 팀 내 용어집으로 맞추고, 네트워크·아이덴티티·과금 단위를 문서화하는 것이 협업에 도움이 됩니다.
4. State 관리와 백엔드
4.1 State가 필요한 이유
Terraform은 선언 코드만으로는 클라우드에 존재하는 실제 ID를 모두 알 수 없습니다. State는 Terraform이 관리하는 리소스와 원격 ID 매핑, 일부 속성 캐시를 저장합니다. State가 없거나 오래되면 계획이 부정확해지고, 중복 생성·잘못된 삭제 위험이 커집니다.
4.2 원격 백엔드와 잠금
로컬 terraform.tfstate 파일은 개인 학습에는 편하지만, 팀에서는 원격 백엔드로 옮기는 것이 일반적입니다.
| 백엔드 예시 | 잠금(Locking) | 비고 |
|---|---|---|
| AWS S3 + DynamoDB | DynamoDB 테이블로 락 | 널리 쓰이는 패턴 |
| Azure Storage | Blob 잠금 기능 활용 | azurerm 백엔드 문서 참고 |
| GCS | GCS가 제공하는 잠금 | 프로젝트 IAM과 함께 설계 |
| Terraform Cloud / HCP Terraform | SaaS가 락·RBAC 제공 | 팀 협업·정책 강화에 유리 |
원격 백엔드 예시(S3, 개념 설명용):
terraform {
backend "s3" {
bucket = "my-org-terraform-state"
key = "prod/network/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
key는 스택별로 분리하는 것이 State 충돌을 줄입니다. 암호화, 버전 관리(Versioning), 접근 로그는 운영 필수에 가깝습니다.
4.3 State 분할과 terraform import
거대한 단일 State는 플랜 시간과 변경 폭을 키웁니다. 팀 단위·도메인 단위로 State를 쪼개고, terraform_remote_state 또는 공유 서비스(예: DNS, 허브 VPC)만 느슨하게 연결하는 패턴이 흔합니다. 이미 콘솔로 만든 리소스는 terraform import 로 State에 편입할 수 있으나, 코드와 실제 구성을 일치시키는 작업이 별도로 필요합니다.
5. Workspace와 환경 분리
5.1 Workspace
Workspace는 동일 구성에 대해 여러 State를 분리하는 내장 메커니즘입니다. terraform workspace new staging처럼 생성하고, terraform.workspace 값으로 이름을 참조할 수 있습니다.
장점은 설정이 단순할 때 빠르게 환경을 나눌 수 있다는 점입니다. 단점은 백엔드 key 분리 실수, 동일 브랜치에서 workspace만 바꿔 운영에 적용하는 인적 오류 위험입니다.
5.2 디렉터리·브랜치·파이프라인으로 분리
많은 조직은 env/dev, env/prod처럼 루트 모듈을 분리하고, 다른 백엔드·다른 변수 파일(terraform.tfvars)·다른 승인 게이트를 둡니다. 프로덕션은 main 브랜치 병합 후에만 apply되도록 제한하는 방식이 안전합니다.
5.3 변수 파일과 시크릿
비밀번호·API 키를 .tf에 직접 쓰지 마십시오. 환경 변수, CI 시크릿, 클라우드 시크릿 매니저(AWS Secrets Manager, Azure Key Vault, GCP Secret Manager)와 외부 데이터 소스를 조합합니다. State에 민감 값이 남지 않도록 민감 리소스 설계를 검토합니다.
6. 보안 베스트 프랙티스
6.1 최소 권한과 역할 분리
Terraform을 실행하는 CI 서비스 계정에는 스택에 필요한 최소 IAM 권한만 부여합니다. 개발자 개인 자격 증명으로 프로덕션을 적용하는 관행은 감사·추적 측면에서 불리합니다.
6.2 시크릿과 State
- State 파일은 민감 데이터가 포함될 수 있으므로 암호화 저장, 접근 제어, 감사 로그를 적용합니다.
.tfvars에 시크릿을 커밋하지 않습니다..gitignore와 pre-commit 훅으로 방지합니다.
6.3 공급망과 모듈 출처
- 공식 레지스트리와 검증된 모듈을 우선하고, Git 소스는 커밋 해시 또는 태그로 고정합니다.
- 프로바이더 버전을 제약해 재현 가능한 플랜을 유지합니다.
6.4 정책 as Code
조직 규모가 커지면 Sentinel(Terraform Enterprise/Cloud), OPA(Open Policy Agent) 로 “퍼블릭 S3 금지”, “필수 태그” 같은 규칙을 자동 검증합니다.
7. 실전 인프라 구축 예제
아래는 교육용으로 축약한 AWS 예시입니다. 실제 운영에서는 서브넷 CIDR, NAT 비용, 로드밸런서 헬스 체크, WAF, 백업 정책 등을 추가로 설계합니다.
7.1 목표 구조
- VPC와 퍼블릭·프라이빗 서브넷
- 애플리케이션은 프라이빗 서브넷에 두고, 로드밸런서로만 노출(개념)
- 보안 그룹으로 최소 포트만 허용
7.2 네트워크 및 ALB 골격 (예시)
# 예시: 개념 이해용. 실제 배포 전 CIDR·가용 영역·이름은 환경에 맞게 조정하십시오.
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "app-vpc"
}
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-${count.index}"
}
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index + 8)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "private-${count.index}"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
}
resource "aws_lb" "app" {
name = "app-alb"
load_balancer_type = "application"
subnets = aws_subnet.public[*].id
security_groups = [aws_security_group.alb.id]
}
resource "aws_security_group" "alb" {
name = "alb-sg"
description = "ALB ingress"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
위 코드에서 cidrsubnet으로 서브넷을 나누고, count로 가용 영역별 반복을 표현합니다. 인터넷 게이트웨이만으로는 프라이빗 서브넷에 아웃바운드가 필요할 때 NAT 게이트웨이·라우트 테이블을 추가해야 합니다(비용 발생). 운영 환경에서는 단일 AZ NAT vs 다중 AZ NAT 비용과 가용성을 트레이드오프합니다.
애플리케이션 계층은 aws_instance 또는 ECS/EKS 등 컨테이너 오케스트레이션으로 올리는 경우가 많습니다. Terraform으로 클러스터·노드 그룹·IRSA(IAM Roles for Service Accounts) 까지 한 스택에 넣을지, 플랫폼 팀과 앱 팀의 경계를 어디에 둘지는 조직 문맥에 따라 달라집니다.
7.3 Azure·GCP로 옮길 때
- Azure:
azurerm_virtual_network,azurerm_subnet,azurerm_lb등으로 대응하며, 리소스 그룹·위치를 일관되게 둡니다. - GCP:
google_compute_network,google_compute_subnetwork,google_compute_forwarding_rule또는 GLB 리소스로 유사 패턴을 구현합니다.
동일한 아키텍처라도 로드밸런서·헬스 체크·방화벽 모델이 다르므로, “클라우드별 모듈”을 두고 추상화 레이어를 억지로 통일하기보다 참조 아키텍처 문서를 맞추는 편이 현실적입니다.
8. 운영 체크리스트
- 원격 State, 잠금, 암호화, IAM 최소 권한이 준비되었는가.
- 프로덕션 apply는 CI와 승인 절차로만 수행되는가.
- 모듈·프로바이더 버전이 고정되어 재현 가능한가.
- 비용 알림, 태그 정책, 삭제 보호(S3 버전 관리, RDS 삭제 보호 등)가 정의되었는가.
- 장애 시
terraform plan이 예상과 다른 이유(수동 변경, drift)를 추적할 프로세스가 있는가.
마무리
Terraform은 인프라를 소프트웨어처럼 다루게 해 주는 강력한 도구입니다. HCL과 모듈로 구조를 명확히 하고, State와 백엔드로 협업과 안전성을 확보하며, Workspace나 디렉터리 전략으로 환경을 분리하는 패턴을 익히면 운영 성숙도가 빠르게 올라갑니다. AWS·Azure·GCP 중 어디를 쓰든 프로바이더 문서와 조직 보안 기준을 함께 보는 습관이 가장 중요합니다.
이미 동일 주제의 실무 요약 글이 있다면(terraform-practical-guide 등) 이 글은 멀티 클라우드·보안·백엔드·환경 분리를 한층 체계적으로 묶은 참고용으로 활용할 수 있습니다.