인프라 as Code (IaC): Terraform 중심 실전 가이드

1. IaC란 무엇인가

**Infrastructure as Code(IaC)**는 인프라를 수동으로 관리하는 대신, 코드로 선언하고 자동화하는 접근 방식이다.

전통적으로 서버를 구성할 때는 관리자가 콘솔에 접속하여 수동으로 설정했다. 이 방식은 다음과 같은 문제가 있다:

수동 관리의 문제점:

개발자 A가 서버 구성  ──▶  "내 서버에서는 잘 되는데?"
    │
    ├── 어떤 패키지를 설치했는지 기억 안 남
    ├── 설정 파일을 어디서 수정했는지 모름
    ├── 같은 환경을 다시 만들 수 없음
    └── 다른 사람이 인수인계 받기 어려움

        ↓  IaC 도입  ↓

코드로 인프라 정의  ──▶  "코드가 곧 문서이자 실행 가능한 인프라"
    │
    ├── Git으로 변경 이력 추적
    ├── PR 리뷰로 인프라 변경 검증
    ├── 동일한 환경을 언제든 재생성
    └── 누구나 코드를 읽고 이해 가능

2. IaC의 장점

버전 관리 (Version Control)

commit abc1234 - "VPC 서브넷 추가"
commit def5678 - "보안그룹 규칙 수정"
commit ghi9012 - "오토스케일링 설정 변경"
    │
    └── 누가, 언제, 왜 인프라를 변경했는지 추적 가능
        문제 발생 시 이전 커밋으로 롤백 가능

재현성 (Reproducibility)

동일한 Terraform 코드로:

Production 환경  ─┐
Staging 환경     ─┤── 동일한 인프라 구조
Development 환경 ─┤   (변수만 다름)
DR 환경          ─┘

자동화 (Automation)

PR 머지 → CI/CD 파이프라인 → terraform plan → 승인 → terraform apply
    │
    └── 사람의 개입 최소화, 실수 방지, 속도 향상

추가 장점

장점설명
협업코드 리뷰를 통한 인프라 변경 검토
테스트인프라 코드에 대한 자동화 테스트 가능
문서화코드 자체가 인프라의 현재 상태를 문서화
비용 최적화리소스를 코드로 관리하여 불필요한 리소스 파악
컴플라이언스정책을 코드로 강제 (Policy as Code)

3. 선언적 vs 명령적 접근

IaC 도구는 크게 두 가지 접근 방식으로 나뉜다:

선언적 (Declarative)

**“무엇(What)“**을 원하는지 정의한다. 도구가 현재 상태와 원하는 상태의 차이를 계산하여 적용한다.

# Terraform (선언적)
# "이런 상태가 되어야 한다"
 
resource "aws_instance" "web" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.medium"
 
  tags = {
    Name = "web-server"
  }
}
 
# 이미 존재하면 변경사항만 적용
# 없으면 새로 생성
# 코드에 없는 리소스는 삭제 대상

명령적 (Imperative)

“어떻게(How)” 수행할지 단계별로 지시한다.

# Ansible (명령적 성격이 강함)
# "이 단계들을 순서대로 수행하라"
 
- name: Install nginx
  apt:
    name: nginx
    state: present
 
- name: Start nginx
  service:
    name: nginx
    state: started
 
- name: Copy config file
  copy:
    src: nginx.conf
    dest: /etc/nginx/nginx.conf

비교

구분선언적명령적
정의원하는 최종 상태수행할 단계
멱등성도구가 자동 보장개발자가 직접 관리
학습 곡선중간낮음 (절차적)
대표 도구Terraform, CloudFormationAnsible, Chef
적합한 대상인프라 프로비저닝서버 내부 설정 관리

실무에서는 **Terraform(인프라 프로비저닝) + Ansible(서버 구성 관리)**를 함께 사용하는 패턴이 일반적이다.


4. Terraform 소개

Terraform은 HashiCorp에서 개발한 오픈소스 IaC 도구로, **HCL(HashiCorp Configuration Language)**을 사용하여 인프라를 정의한다.

주요 특징

  • 멀티 클라우드: AWS, GCP, Azure 등 다양한 클라우드 프로바이더 지원
  • 선언적 언어: 원하는 상태를 정의하면 자동으로 실행 계획 수립
  • 상태 관리: 인프라의 현재 상태를 추적하고 관리
  • 모듈 시스템: 재사용 가능한 인프라 컴포넌트 구성
  • 대규모 에코시스템: 수천 개의 프로바이더와 모듈 지원

5. Terraform 핵심 개념

Provider

클라우드 서비스나 외부 API와 통신하는 플러그인이다:

# AWS 프로바이더 설정
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = ">= 1.7.0"
}
 
provider "aws" {
  region = var.aws_region
 
  default_tags {
    tags = {
      Project     = "my-project"
      Environment = var.environment
      ManagedBy   = "terraform"
    }
  }
}

Resource

관리할 인프라 리소스를 정의한다:

# VPC 생성
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
 
  tags = {
    Name = "${var.project_name}-vpc"
  }
}
 
# 서브넷 생성
resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]
 
  map_public_ip_on_launch = true
 
  tags = {
    Name = "${var.project_name}-public-subnet-${count.index + 1}"
    Tier = "public"
  }
}
 
# EC2 인스턴스
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  subnet_id     = aws_subnet.public[0].id
 
  vpc_security_group_ids = [aws_security_group.web.id]
 
  user_data = templatefile("${path.module}/scripts/init.sh", {
    app_port = var.app_port
  })
 
  tags = {
    Name = "${var.project_name}-web"
  }
}

Data Source

기존 리소스의 정보를 조회한다:

# 최신 Ubuntu AMI 조회
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical 공식 계정
 
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-*-22.04-amd64-server-*"]
  }
}
 
# 현재 AWS 계정 정보
data "aws_caller_identity" "current" {}
 
# 사용 예시
output "account_id" {
  value = data.aws_caller_identity.current.account_id
}

Variable & Output

# variables.tf
variable "environment" {
  description = "배포 환경 (dev, staging, prod)"
  type        = string
  default     = "dev"
 
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "environment는 dev, staging, prod 중 하나여야 합니다."
  }
}
 
variable "instance_type" {
  description = "EC2 인스턴스 타입"
  type        = string
  default     = "t3.medium"
}
 
variable "public_subnet_cidrs" {
  description = "퍼블릭 서브넷 CIDR 목록"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}
 
variable "db_password" {
  description = "데이터베이스 비밀번호"
  type        = string
  sensitive   = true                # 출력에서 마스킹
}
 
# outputs.tf
output "vpc_id" {
  description = "생성된 VPC의 ID"
  value       = aws_vpc.main.id
}
 
output "web_instance_public_ip" {
  description = "웹 서버 퍼블릭 IP"
  value       = aws_instance.web.public_ip
}

6. Terraform 워크플로우

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│  init    │───▶│  plan    │───▶│  apply   │───▶│ destroy  │
│          │    │          │    │          │    │ (선택)    │
│ 초기화    │    │ 실행계획  │    │ 적용     │    │ 삭제     │
└──────────┘    └──────────┘    └──────────┘    └──────────┘

각 단계 상세

# 1. Init: 프로바이더 다운로드, 백엔드 초기화
terraform init
 
# 2. Validate: 구문 검증
terraform validate
 
# 3. Format: 코드 포맷팅
terraform fmt -recursive
 
# 4. Plan: 변경 사항 미리 확인
terraform plan -out=tfplan
# + 생성, ~ 수정, - 삭제 표시
 
# 5. Apply: 실제 인프라에 적용
terraform apply tfplan
# 또는 terraform apply -auto-approve (주의: 프로덕션에서는 사용 자제)
 
# 6. Destroy: 모든 리소스 삭제
terraform destroy

Plan 출력 예시

Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + ami                    = "ami-0abcdef1234567890"
      + instance_type          = "t3.medium"
      + public_ip              = (known after apply)
      + tags                   = {
          + "Name" = "my-project-web"
        }
    }

  # aws_security_group.web will be modified
  ~ resource "aws_security_group" "web" {
      ~ ingress = [
          + {
              + from_port   = 443
              + to_port     = 443
              + protocol    = "tcp"
              + cidr_blocks = ["0.0.0.0/0"]
            },
        ]
    }

Plan: 1 to add, 1 to change, 0 to destroy.

7. 상태 관리

Terraform은 terraform.tfstate 파일로 인프라의 현재 상태를 추적한다.

로컬 상태의 문제점

개발자 A의 로컬          개발자 B의 로컬
┌─────────────────┐    ┌─────────────────┐
│ terraform.tfstate│    │ terraform.tfstate│
│ (서로 다른 상태) │    │ (서로 다른 상태) │
└─────────────────┘    └─────────────────┘
        │                      │
        └──── 충돌 발생! ──────┘

리모트 백엔드 설정

팀 작업 시 반드시 리모트 백엔드를 사용해야 한다:

# S3 + DynamoDB 백엔드 (AWS)
terraform {
  backend "s3" {
    bucket         = "my-project-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-lock"     # 동시 실행 방지 (잠금)
  }
}
리모트 백엔드 구조:

개발자 A ──┐                     ┌── S3 Bucket
           ├──▶ DynamoDB Lock ──▶│   terraform.tfstate
개발자 B ──┘    (동시 실행 방지)   └── (암호화 저장)

상태 관리 명령어

# 상태에 있는 리소스 목록 조회
terraform state list
 
# 특정 리소스 상세 정보
terraform state show aws_instance.web
 
# 상태에서 리소스 제거 (인프라는 유지, 관리만 해제)
terraform state rm aws_instance.web
 
# 리소스를 다른 이름으로 이동 (리팩토링 시)
terraform state mv aws_instance.web aws_instance.web_server
 
# 외부에서 생성된 리소스를 상태에 추가
terraform import aws_instance.web i-0abc123def456789

8. 모듈화 패턴

디렉토리 구조

infrastructure/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── terraform.tfvars
│   ├── staging/
│   │   ├── main.tf
│   │   └── terraform.tfvars
│   └── prod/
│       ├── main.tf
│       └── terraform.tfvars
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── ec2/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── rds/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── global/
    ├── iam/
    └── dns/

모듈 정의

# modules/vpc/main.tf
resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
 
  tags = {
    Name = "${var.project_name}-${var.environment}-vpc"
  }
}
 
resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.this.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]
 
  tags = {
    Name = "${var.project_name}-public-${count.index + 1}"
  }
}
 
# modules/vpc/variables.tf
variable "project_name" {
  type = string
}
 
variable "environment" {
  type = string
}
 
variable "vpc_cidr" {
  type    = string
  default = "10.0.0.0/16"
}
 
variable "public_subnet_cidrs" {
  type = list(string)
}
 
variable "availability_zones" {
  type = list(string)
}
 
# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.this.id
}
 
output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

모듈 사용

# environments/prod/main.tf
module "vpc" {
  source = "../../modules/vpc"
 
  project_name        = "my-project"
  environment         = "prod"
  vpc_cidr            = "10.0.0.0/16"
  public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
  availability_zones  = ["ap-northeast-2a", "ap-northeast-2c"]
}
 
module "web" {
  source = "../../modules/ec2"
 
  project_name  = "my-project"
  environment   = "prod"
  instance_type = "t3.large"
  subnet_id     = module.vpc.public_subnet_ids[0]  # 모듈 출력 참조
}

9. Terraform vs CloudFormation vs Pulumi 비교표

항목TerraformCloudFormationPulumi
개발사HashiCorpAWSPulumi Inc.
언어HCLJSON/YAMLPython, TypeScript, Go 등
멀티 클라우드지원AWS 전용지원
상태 관리tfstate (로컬/리모트)AWS 자동 관리Pulumi Cloud / 자체
학습 곡선중간 (HCL 학습)낮음 (AWS 사용자)낮음 (기존 언어 사용)
에코시스템매우 넓음 (3000+ providers)AWS 완벽 지원성장 중
드리프트 감지plan 시 확인드리프트 감지 기능preview 시 확인
가격오픈소스 / Cloud 유료무료 (AWS 내)오픈소스 / Cloud 유료
테스트Terratest, tftestTaskCat일반 테스트 프레임워크
적합한 경우멀티 클라우드, 범용AWS 올인프로그래밍 선호

선택 가이드

Q1. AWS만 사용하는가?
    └── YES → CloudFormation (네이티브 통합)
    └── NO  → Q2

Q2. 프로그래밍 언어로 인프라를 정의하고 싶은가?
    └── YES → Pulumi (TypeScript, Python 등)
    └── NO  → Terraform (HCL, 업계 표준)

10. IaC 베스트 프랙티스

코드 관리

  1. 모든 인프라를 코드로 관리: 콘솔에서의 수동 변경을 금지한다
  2. 작은 단위로 변경: 한 번에 큰 변경보다 작은 변경을 자주 적용한다
  3. 코드 리뷰 필수: 인프라 변경도 PR 리뷰를 거친다
  4. 환경별 변수 분리: terraform.tfvars로 환경별 설정을 관리한다

보안

# 민감 정보는 변수로 분리하고 sensitive 표시
variable "db_password" {
  type      = string
  sensitive = true
}
 
# terraform.tfvars는 .gitignore에 추가
# 시크릿은 외부 시크릿 매니저 연동
data "aws_secretsmanager_secret_version" "db" {
  secret_id = "my-project/db-password"
}

CI/CD 통합

# GitHub Actions에서 Terraform 실행
name: Terraform
 
on:
  pull_request:
    paths: ['infrastructure/**']
  push:
    branches: [main]
    paths: ['infrastructure/**']
 
jobs:
  plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.9.0
 
      - name: Terraform Init
        run: terraform init
        working-directory: infrastructure/environments/prod
 
      - name: Terraform Plan
        run: terraform plan -no-color
        working-directory: infrastructure/environments/prod
 
  apply:
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    needs: plan
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: hashicorp/setup-terraform@v3
      - run: terraform init && terraform apply -auto-approve
        working-directory: infrastructure/environments/prod

기타 베스트 프랙티스

항목설명
상태 파일 암호화리모트 백엔드에서 암호화 활성화
잠금(Lock) 사용DynamoDB 등으로 동시 실행 방지
태깅 표준화모든 리소스에 일관된 태그 적용
모듈 버전 관리모듈에 버전을 부여하고 변경 추적
destroy 보호중요 리소스에 prevent_destroy 설정
정기적 Plan드리프트를 감지하기 위해 주기적으로 plan 실행
# 실수로 삭제 방지
resource "aws_rds_instance" "main" {
  # ...
 
  lifecycle {
    prevent_destroy = true    # destroy 시 에러 발생
  }
}

마무리

IaC는 현대 인프라 관리의 핵심 패러다임이다. 핵심 포인트를 정리하면:

  • IaC를 도입하면 재현성, 버전 관리, 자동화라는 세 가지 핵심 가치를 얻는다
  • Terraform은 멀티 클라우드 환경에서 가장 널리 사용되는 IaC 도구다
  • 모듈화를 통해 재사용성과 유지보수성을 높인다
  • 리모트 상태 관리잠금으로 팀 협업 시 충돌을 방지한다
  • CI/CD 파이프라인에 통합하여 인프라 변경도 자동화된 검증을 거치게 한다

다음 글에서는 인프라를 안정적으로 운영하기 위한 모니터링과 옵저버빌리티 전략을 다룰 예정이다.