Infrastructure as Code

Terraform 기초

meellon 2026. 7. 1. 14:20

학습 목표

Terraform Core, provider, resource의 역할을 구분할 수 있다.

HCL 파일 구조(terraform / provider / resource 블록)를 읽고 작성할 수 있다.

terraform init, plan, apply, destroy가 각각 무엇을 하는지 설명할 수 있다.

resource address(aws_s3_bucket.artifacts)와 속성 참조(aws_vpc.main.id)를 사용할 수 있다.

로컬 state로 최소 스택을 올리고 plan diff를 해석할 수 있다.

문제 상황

  • .tf를 작성했는데 terraform apply"Please run terraform init" — provider·plugin이 없다
  • provider "aws"만 쓰고 region을 안 적어서 다른 리전에 리소스가 생겼다
  • bucket = "my-bucket"처럼 문자열aws_s3_bucket.xxx.bucket 참조를 혼동 — plan에서 replace가 뜬다
  • 동료 PC에서는 되는데 내 PC에서는 provider version mismatch — required_providers가 없다
  • terraform apply -auto-approve로 prod에 destroy까지 한 번에 — plan을 안 봤다
  • state 파일(terraform.tfstate)을 Git에 커밋했다가 secret이 노출 — state가 뭔지 몰랐다
  • Platform IaC·GitOps에서 Terraform 개요는 봤지만, HCL·CLI는 처음 손대본다

앞 편에서 선언적·멱등·Git 원칙을 고정했다. 이번 편은 Terraform으로 첫 리소스를 올리기 위한 최소 문법·명령이다. remote state·module·workspace는 다음 편들에서 다룬다.

1. Terraform 아키텍처

Terraform은 Core(엔진) + Provider(플러그인) + State(상태 저장)로 동작한다.

구성 역할
Core HCL 파싱, graph 작성, plan·apply 오케스트레이션
Provider AWS·GCP·K8s 등 API 호출terraform init 시 다운로드
State resource address클라우드 ID 매핑 — 멱등·diff의 기준
Backend state 저장 위치 (기본: 로컬 terraform.tfstate)
HCL (.tf)  →  Core  →  Provider plugin  →  Cloud / K8s API
                ↕
            State file
  • 선언적 — Core가 desired(HCL) vs state vs refresh된 actual을 비교해 변경 그래프 생성
  • 멀티 클라우드 — provider만 바꾸면 같은 워크플로 유지 (AWS + Cloudflare DNS 등 한 root module에서 혼합 가능)
  • Platform IaC·GitOps에서 본 plan · apply · state — 여기서는 로컬 state로 손에 익힌다

2. HCL 구조

HashiCorp Configuration Language(HCL).tf 파일. JSON(*.tf.json)도 가능하지만 실무는 HCL 위주.

파일 나누기 (관례)

파일 내용
versions.tf terraform 블록 — required_version, required_providers
providers.tf provider 블록 — region, alias
main.tf resource, data주요 인프라
variables.tf variable 선언
outputs.tf output — plan 이후 값 노출
terraform.tfvars variable (env별, Gitignore 가능)

한 파일에 몰아도 동작하지만, 리뷰·재사용을 위해 나눈다.

terraform 블록

# code/minimal/versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
  • required_providers — provider 출처·버전 고정 → 팀·CI 재현성
  • backend — state 저장소 (S3 등) — 4편에서 remote state로 확장

provider 블록

# code/minimal/providers.tf
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      env     = var.environment
      managed = "terraform"
    }
  }
}
  • provider — "어떤 API에 연결할지" — aws, google, kubernetes, azurerm
  • alias — 같은 provider를 region·account별로 여러 개 (provider.aws.east)
  • 인증 — 환경 변수·shared credentials·IAM role (CI에서는 OIDC — Security 시리즈)

resource 블록

# code/minimal/main.tf
resource "aws_s3_bucket" "artifacts" {
  bucket = "${var.project}-artifacts-${var.environment}"

  tags = {
    Name = "ci-artifacts"
  }
}
  • resource "TYPE" "NAME"address: aws_s3_bucket.artifacts
  • TYPE — provider가 정의한 스키마 (aws_s3_bucket, aws_security_group)
  • NAME — 코드 안 로컬 식별자 — 같은 TYPE 여러 개 구분
  • 블록 속성 — API 필드에 대응; computed 속성은 apply 후에만 채워짐 (arn, id)

data 블록 (읽기 전용)

data "aws_availability_zones" "available" {
  state = "available"
}
  • 이미 존재하는 리소스·메타데이터 조회 — create/update 없음
  • data.aws_availability_zones.available.names처럼 참조

3. 참조와 의존성

속성 참조

resource "aws_security_group" "app" {
  name   = "app-sg"
  vpc_id = aws_vpc.main.id   # 다른 resource의 computed attribute

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.admin_cidr]
  }
}
표기 의미
aws_vpc.main.id resource vpc.mainid 속성
var.admin_cidr variable
local.common_tags local
data.aws_caller_identity.current.account_id data source
  • Core가 참조를 보고 의존성 그래프를 만든다 — VPC 먼저, SG 나중
  • 암시적 depends_on — 대부분 참조만으로 충분; API 순서가 보장 안 될 때만 depends_on 명시

문자열 보간

bucket = "${var.project}-logs-${var.environment}"
# Terraform 0.12+ — 단순 식별자는 ${} 생략 가능
name   = "app-${var.environment}"
  • 리터럴참조 혼동 주의 — bucket = "aws_s3_bucket.xxx.bucket" (X) vs bucket = aws_s3_bucket.xxx.bucket (O)

4. CLI 워크플로

명령 하는 일
terraform init provider·module 다운로드, backend 초기화, .terraform/ 생성
terraform fmt HCL 포맷 — PR 전 습관
terraform validate 문법·스키마 정적 검사 (API 호출 없음)
terraform plan desired + state + refresh변경 계획 출력
terraform apply plan 승인 후 API 실행, state 갱신
terraform destroy managed 리소스 삭제 (plan과 동일하게 diff)
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplan

plan 출력 읽기

Terraform will perform the following actions:

  # aws_s3_bucket.artifacts will be created
  + resource "aws_s3_bucket" "artifacts" {
      + bucket = "myapp-artifacts-staging"
      + id     = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
기호 의미
+ create
~ update in-place
-/+ replace (destroy + create)
- destroy
  • (known after apply) — API가 apply 시점에 채우는 computed
  • -replace=aws_s3_bucket.artifacts — 강제 재생성 (파괴적 — PR에서 반드시 눈여겨볼 것)

init이 필요한 이유

  • provider는 별도 바이너리 — Core 버전·OS별 plugin cache (.terraform/providers/)
  • module source clone·backend 설정도 init 단계
  • versions.tf 변경·provider upgrade → terraform init -upgrade

5. 최소 예제 따라하기

아래는 로컬 state 기준 S3 버킷 하나 — 실습용 AWS 계정·profile 가정.

디렉터리

3-terraform-basics/code/minimal/
├── versions.tf
├── providers.tf
├── variables.tf
├── main.tf
└── outputs.tf

variables.tf / outputs.tf는 저장소 code/minimal/ 참고.

cd code/minimal
export AWS_PROFILE=dev   # 또는 AWS_ACCESS_KEY_ID 등
terraform init
terraform plan
terraform apply   # yes 입력 — prod는 -auto-approve 지양
terraform output bucket_name
terraform plan    # 0 changes — 멱등 확인
terraform destroy # 실습 후 정리
단계 확인할 것
init .terraform/ 생성, provider 5.x lock
plan + create 1개, bucket 이름 변수 반영
apply state에 arn, id 기록
재 plan No changes — 앞 편 멱등성
destroy - destroy 1개
  • state (terraform.tfstate) — apply 후 로컬에 생성 — 민감 정보 가능, 4편에서 remote·lock
  • .gitignore*.tfstate, *.tfstate.backup, .terraform/, *.tfvars (secret 포함 시)

6. variable과 output (맛보기)

# code/minimal/variables.tf
variable "environment" {
  type        = string
  description = "dev | staging | prod"
}

variable "admin_cidr" {
  type    = string
  default = "10.0.0.0/8"
}
# code/minimal/outputs.tf
output "bucket_name" {
  value       = aws_s3_bucket.artifacts.bucket
  description = "CI artifact bucket"
}
  • variable — env별 분기 (terraform.tfvars, -var, TF_VAR_*)
  • output — 다른 stack·CI·사람에게 값 전달 — 4편 module output으로 확장
  • local — 파일 간 공통 표현식 (locals { common_tags = … })

7. 실무 습관 (초기)

습관 이유
plan 먼저 destroy·replace 사전 확인
fmt + validate in CI 스타일·문법 자동
required_providers pin "내 PC만 됨" 방지
default_tags 비용·소유자 태깅
state Git 커밋 금지 (팀) secret·충돌 — remote backend
작은 root module 한 apply 범위 좁게 — blast radius
  • Workspace·directory per env — 5편
  • Module 추출 — 4편
  • Plan in CI·승인 apply — 16·19편

정리

  • Core + provider + state — HCL → plan graph → API; state가 ID를 기억
  • HCLterraform / provider / resource / data / variable / output
  • init → plan → apply — init은 plugin, plan은 diff, apply는 수렴
  • resource address·속성 참조 — 의존성·replace 이해의 출발점
  • 로컬 state로 hands-on — remote·module은 다음 편

다음에 다룰 것

  • State와 Module
  • local vs remote state, locking, module·output·variable

해당 내용은 HashiCorp Terraform Documentation (CLI, Configuration Language), Brikman — Terraform: Up & Running, 3/E Ch 2 를 기반으로 합니다.

'Infrastructure as Code' 카테고리의 다른 글

AWS CDK 개요  (0) 2026.07.07
환경과 Workspace  (0) 2026.07.05
State와 Module  (0) 2026.07.03
Infrastructure as Code란  (0) 2026.06.30
IaC 오리엔테이션  (0) 2026.06.29