학습 목표
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.main의 id 속성 |
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) vsbucket = 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를 기억
- HCL —
terraform/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 |