Terraform 이란?

Terraform 이란?

Terraform은 HashiCorp에서 개발한 오픈소스 Infrastructure as Code(IaC) 도구입니다. 선언적 구성 언어(HCL - HashiCorp Configuration Language)를 사용하여 클라우드 및 온프레미스 리소스를 안전하고 효율적으로 구축, 변경, 버전 관리할 수 있습니다.

주요 특징

1. 멀티 클라우드 지원

  • AWS, Azure, GCP, Kubernetes 등 다양한 프로바이더 지원
  • 700개 이상의 프로바이더 사용 가능
  • 단일 구성으로 여러 클라우드 관리 가능

2. 선언적 구성

  • 원하는 최종 상태를 선언하면 Terraform이 자동으로 실행 계획 수립
  • 명령형이 아닌 선언형 접근 방식

3. 실행 계획 (Plan)

  • 변경 사항을 미리 확인 가능
  • 예상치 못한 변경 방지
  • terraform plan 명령으로 변경 사항 시뮬레이션

4. 상태 관리

  • 인프라의 현재 상태를 추적
  • 원격 상태 저장 지원 (S3, Terraform Cloud 등)
  • 팀 협업 시 상태 잠금(lock) 기능 제공

5. 리소스 그래프

  • 리소스 간 종속성을 자동으로 파악
  • 병렬 실행으로 효율성 향상
  • 올바른 순서로 리소스 생성/삭제

설치 방법

macOS

# Homebrew 사용
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# 버전 확인
terraform version

Linux

# 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

# CentOS/RHEL
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraform

Windows

# Chocolatey 사용
choco install terraform

# 또는 Scoop 사용
scoop install terraform

기본 구조

프로젝트 구조

terraform-project/
├── main.tf # 주요 리소스 정의
├── variables.tf # 입력 변수 정의
├── outputs.tf # 출력 값 정의
├── terraform.tfvars # 변수 값 설정
├── providers.tf # 프로바이더 설정
└── modules/ # 재사용 가능한 모듈
├── vpc/
├── ec2/
└── rds/

HCL 기본 문법

1. 프로바이더 설정

providers.tf
terraform {
required_version = ">= 1.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}

backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-lock"
}
}

provider "aws" {
region = var.aws_region

default_tags {
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}
}

2. 변수 정의

variables.tf
variable "aws_region" {
description = "AWS 리전"
type = string
default = "ap-northeast-2"
}

variable "environment" {
description = "환경 (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "환경은 dev, staging, prod 중 하나여야 합니다."
}
}

variable "vpc_cidr" {
description = "VPC CIDR 블록"
type = string
default = "10.0.0.0/16"
}

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

variable "tags" {
description = "리소스 태그"
type = map(string)
default = {}
}

3. 리소스 정의

main.tf
# VPC 생성
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true

tags = merge(
var.tags,
{
Name = "${var.environment}-vpc"
}
)
}

# 서브넷 생성
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]

map_public_ip_on_launch = true

tags = {
Name = "${var.environment}-public-subnet-${count.index + 1}"
Type = "Public"
}
}

# 보안 그룹
resource "aws_security_group" "web" {
name = "${var.environment}-web-sg"
description = "Web server security group"
vpc_id = aws_vpc.main.id

ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
description = "All outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "${var.environment}-web-sg"
}
}

# EC2 인스턴스
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[0].id

vpc_security_group_ids = [aws_security_group.web.id]

user_data = <<-EOF
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from Terraform</h1>" > /var/www/html/index.html
EOF

tags = {
Name = "${var.environment}-web-server"
}
}

4. 데이터 소스

data.tf

data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]

filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}
}

data "aws_availability_zones" "available" {
state = "available"
}

5. 출력 값

outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}

output "public_subnet_ids" {
description = "Public 서브넷 ID 목록"
value = aws_subnet.public[*].id
}

output "web_server_public_ip" {
description = "웹 서버 공인 IP"
value = aws_instance.web.public_ip
}

output "web_server_url" {
description = "웹 서버 URL"
value = "http://${aws_instance.web.public_ip}"
}

주요 명령어

초기화

# Terraform 초기화 (프로바이더 다운로드)
terraform init

# 업그레이드 포함 초기화
terraform init -upgrade

포맷팅 및 검증

# 코드 포맷팅
terraform fmt

# 재귀적으로 포맷팅
terraform fmt -recursive

# 구성 파일 검증
terraform validate

계획 및 적용

# 실행 계획 확인
terraform plan

# 특정 변수 파일 사용
terraform plan -var-file="prod.tfvars"

# 특정 리소스만 계획
terraform plan -target=aws_instance.web

# 변경 사항 적용
terraform apply

# 자동 승인으로 적용
terraform apply -auto-approve

# 특정 리소스만 적용
terraform apply -target=aws_instance.web

상태 관리

# 상태 목록 확인
terraform state list

# 특정 리소스 상태 확인
terraform state show aws_instance.web

# 리소스 이름 변경
terraform state mv aws_instance.old aws_instance.new

# 리소스를 상태에서 제거 (실제 리소스는 유지)
terraform state rm aws_instance.web

# 상태 파일 가져오기 (기존 리소스를 Terraform 관리로)
terraform import aws_instance.web i-1234567890abcdef0

출력 및 조회

# 모든 출력 값 표시
terraform output

# 특정 출력 값 표시
terraform output vpc_id

# JSON 형식으로 출력
terraform output -json

리소스 삭제

# 모든 리소스 삭제
terraform destroy

# 특정 리소스만 삭제
terraform destroy -target=aws_instance.web

# 자동 승인으로 삭제
terraform destroy -auto-approve

워크스페이스 관리

# 워크스페이스 목록
terraform workspace list

# 새 워크스페이스 생성
terraform workspace new dev

# 워크스페이스 전환
terraform workspace select prod

# 현재 워크스페이스 확인
terraform workspace show

# 워크스페이스 삭제
terraform workspace delete dev

모듈 활용

모듈 구조

modules/
└── vpc/
├── main.tf
├── variables.tf
├── outputs.tf
└── README.md

모듈 정의

modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support

tags = merge(
var.tags,
{
Name = var.name
}
)
}

resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id

tags = merge(
var.tags,
{
Name = "${var.name}-igw"
}
)
}
modules/vpc/variables.tf
variable "name" {
description = "VPC 이름"
type = string
}

variable "cidr_block" {
description = "VPC CIDR 블록"
type = string
}

variable "enable_dns_hostnames" {
description = "DNS 호스트명 활성화"
type = bool
default = true
}

variable "enable_dns_support" {
description = "DNS 지원 활성화"
type = bool
default = true
}

variable "tags" {
description = "태그"
type = map(string)
default = {}
}
modules/vpc/outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.this.id
}

output "igw_id" {
description = "Internet Gateway ID"
value = aws_internet_gateway.this.id
}

모듈 사용

main.tf
module "vpc" {
source = "./modules/vpc"

name = "${var.environment}-vpc"
cidr_block = var.vpc_cidr

tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}

# 모듈 출력 값 사용
resource "aws_subnet" "public" {
vpc_id = module.vpc.vpc_id
cidr_block = "10.0.1.0/24"

tags = {
Name = "${var.environment}-public-subnet"
}
}

고급 기능

1. 반복문 (Loop)

count 사용

resource "aws_instance" "server" {
count = 3

ami = data.aws_ami.amazon_linux_2.id
instance_type = "t3.micro"

tags = {
Name = "server-${count.index + 1}"
}
}

for_each 사용

variable "instances" {
type = map(object({
instance_type = string
ami = string
}))
default = {
web = {
instance_type = "t3.micro"
ami = "ami-0c55b159cbfafe1f0"
}
app = {
instance_type = "t3.small"
ami = "ami-0c55b159cbfafe1f0"
}
}
}

resource "aws_instance" "server" {
for_each = var.instances

ami = each.value.ami
instance_type = each.value.instance_type

tags = {
Name = "${each.key}-server"
}
}

2. 조건문

resource "aws_instance" "server" {
ami = data.aws_ami.amazon_linux_2.id
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"

tags = {
Name = var.environment == "prod" ? "prod-server" : "dev-server"
}
}

# 조건부 리소스 생성
resource "aws_eip" "server" {
count = var.create_eip ? 1 : 0

instance = aws_instance.server.id
domain = "vpc"
}

3. 동적 블록

variable "ingress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
description = string
}))
default = [
{
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP"
},
{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS"
}
]
}

resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id

dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
description = ingress.value.description
}
}
}

4. 로컬 변수

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

vpc_name = "${var.environment}-${var.project_name}-vpc"

availability_zones = slice(data.aws_availability_zones.available.names, 0, 3)
}

resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr

tags = merge(
local.common_tags,
{
Name = local.vpc_name
}
)
}

5. 함수 활용

# 문자열 함수
output "upper_name" {
value = upper(var.name)
}

output "lower_name" {
value = lower(var.name)
}

# 숫자 함수
output "max_value" {
value = max(5, 12, 9)
}

# 컬렉션 함수
output "subnet_ids" {
value = join(",", aws_subnet.public[*].id)
}

output "first_subnet" {
value = element(aws_subnet.public[*].id, 0)
}

# IP 네트워크 함수
output "subnets" {
value = [
for i in range(3) :
cidrsubnet(var.vpc_cidr, 8, i)
]
}

# 날짜/시간 함수
output "timestamp" {
value = timestamp()
}

# 파일시스템 함수
resource "aws_instance" "web" {
user_data = file("${path.module}/user-data.sh")
}

# 인코딩 함수
output "encoded" {
value = base64encode("Hello Terraform")
}

상태 관리 Best Practices

원격 백엔드 설정 (S3)

terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-lock"

# 상태 파일 버전 관리
versioning = true
}
}

DynamoDB 잠금 테이블 생성

resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"

attribute {
name = "LockID"
type = "S"
}

tags = {
Name = "Terraform Lock Table"
}
}

워크플로우 예제

환경별 관리

# 디렉토리 구조
terraform/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ ├── main.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── prod/
│ ├── main.tf
│ ├── terraform.tfvars
│ └── backend.tf
└── modules/
├── vpc/
├── ec2/
└── rds/

CI/CD 파이프라인 (GitHub Actions)

name: Terraform

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

env:
TF_VERSION: 1.6.0
AWS_REGION: ap-northeast-2

jobs:
terraform:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v3

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}

- name: Terraform Format
run: terraform fmt -check -recursive

- name: Terraform Init
run: terraform init

- name: Terraform Validate
run: terraform validate

- name: Terraform Plan
run: terraform plan -out=tfplan

- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve tfplan

Best Practices

1. 코드 구조화

  • 리소스를 논리적으로 그룹화
  • 재사용 가능한 모듈 작성
  • 환경별로 분리 (dev, staging, prod)

2. 명명 규칙

# 일관된 명명 규칙 사용
resource "aws_instance" "web_server" { # snake_case
tags = {
Name = "${var.environment}-web-server" # kebab-case
}
}

3. 변수 사용

  • 하드코딩 대신 변수 사용
  • 민감한 정보는 환경 변수나 Vault 사용
  • 기본값 제공

4. 상태 관리

  • 원격 백엔드 사용 (S3 + DynamoDB)
  • 상태 파일 암호화
  • 버전 관리 활성화

5. 버전 관리

terraform {
required_version = ">= 1.0, < 2.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # 5.x 버전 사용
}
}
}

6. 태그 전략

locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
Owner = var.owner
CostCenter = var.cost_center
}
}

# 모든 리소스에 공통 태그 적용
resource "aws_instance" "web" {
tags = merge(
local.common_tags,
{
Name = "web-server"
Role = "web"
}
)
}

7. 문서화

# 주석으로 의도 명확히 표현
variable "instance_type" {
description = "EC2 인스턴스 타입. 프로덕션 환경에서는 최소 t3.small 권장"
type = string
default = "t3.micro"
}

8. 보안

# 민감한 정보는 sensitive 표시
variable "db_password" {
description = "데이터베이스 비밀번호"
type = string
sensitive = true
}

output "db_password" {
value = var.db_password
sensitive = true
}

실전 예제: 3-Tier 아키텍처

# VPC
module "vpc" {
source = "./modules/vpc"

name = "${var.environment}-vpc"
cidr_block = "10.0.0.0/16"

availability_zones = ["ap-northeast-2a", "ap-northeast-2c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.11.0/24", "10.0.12.0/24"]
database_subnets = ["10.0.21.0/24", "10.0.22.0/24"]

enable_nat_gateway = true
single_nat_gateway = var.environment != "prod"

tags = local.common_tags
}

# ALB
module "alb" {
source = "./modules/alb"

name = "${var.environment}-alb"
vpc_id = module.vpc.vpc_id
public_subnets = module.vpc.public_subnet_ids
security_groups = [aws_security_group.alb.id]

tags = local.common_tags
}

# EC2 Auto Scaling
module "asg" {
source = "./modules/asg"

name = "${var.environment}-web-asg"
vpc_id = module.vpc.vpc_id
private_subnets = module.vpc.private_subnet_ids
target_group_arns = [module.alb.target_group_arn]

min_size = var.environment == "prod" ? 2 : 1
max_size = var.environment == "prod" ? 10 : 3
desired_capacity = var.environment == "prod" ? 2 : 1

instance_type = var.instance_type
ami_id = data.aws_ami.amazon_linux_2.id

tags = local.common_tags
}

# RDS
module "rds" {
source = "./modules/rds"

identifier = "${var.environment}-db"
engine = "mysql"
engine_version = "8.0"
instance_class = var.environment == "prod" ? "db.t3.medium" : "db.t3.micro"
allocated_storage = var.environment == "prod" ? 100 : 20

db_name = var.db_name
username = var.db_username
password = var.db_password

vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.database_subnet_ids
security_group_ids = [aws_security_group.rds.id]

multi_az = var.environment == "prod"
backup_retention_period = var.environment == "prod" ? 7 : 1

tags = local.common_tags
}

트러블슈팅

일반적인 문제 해결

1. 상태 파일 충돌

# 잠금 해제
terraform force-unlock <LOCK_ID>

# 상태 파일 새로고침
terraform refresh

2. 리소스 import

# 기존 AWS 리소스를 Terraform 관리로 가져오기
terraform import aws_instance.web i-1234567890abcdef0

3. 상태 파일 복구

# 백업에서 복구 (S3)
aws s3 cp s3://my-terraform-state/prod/terraform.tfstate.backup terraform.tfstate

4. 디버깅

# 디버그 로그 활성화
export TF_LOG=DEBUG
export TF_LOG_PATH=./terraform.log

terraform plan

유용한 도구

1. Terragrunt

  • Terraform 코드를 DRY하게 관리
  • 여러 환경 구성 간소화

2. tflint

# 설치
brew install tflint

# 실행
tflint

3. terraform-docs

# 설치
brew install terraform-docs

# 문서 생성
terraform-docs markdown table . > README.md

4. Checkov

# 설치
pip install checkov

# 보안 스캔
checkov -d .

5. Infracost

# 설치
brew install infracost

# 비용 예측
infracost breakdown --path .
Share