
Terraform 입문: 인프라를 코드로 관리하는 시대
AWS 콘솔에서 클릭 100번 vs HCL 코드 한 파일. Infrastructure as Code의 핵심 도구 Terraform의 개념부터 실전 예제, State 관리, 모듈화, 그리고 BSL 라이선스 논란까지 — 클라우드 입문자를 위한 완전 가이드.

AWS 콘솔에서 클릭 100번 vs HCL 코드 한 파일. Infrastructure as Code의 핵심 도구 Terraform의 개념부터 실전 예제, State 관리, 모듈화, 그리고 BSL 라이선스 논란까지 — 클라우드 입문자를 위한 완전 가이드.
AWS를 처음 써본 날을 떠올려보자.
EC2 인스턴스 하나 만드는 데 VPC를 만들고, 서브넷을 만들고, 인터넷 게이트웨이를 연결하고, 라우팅 테이블을 설정하고, 보안 그룹을 만들고, 키 페어를 생성하고... 콘솔 화면을 수십 번 클릭한다. 마침내 인스턴스가 뜨면 뿌듯하다.
그런데 이제 같은 환경을 10개 더 만들어야 한다면? 아니면 3개월 뒤에 "그때 설정 어떻게 했더라?" 싶은 순간이 온다면?
이 글은 IaC의 대표 도구 Terraform을 처음 접하는 사람을 위한 가이드다. HCL 문법 기초부터 실전 예제, State 관리, 모듈화, 그리고 라이선스 논란까지 한 번에 정리한다.

인프라를 관리하는 방식은 크게 두 가지다.
| 비교 항목 | 수동 관리 (ClickOps) | 코드 관리 (IaC) |
|---|---|---|
| 환경 복제 | 처음부터 수동 반복 | 같은 코드 실행 |
| 변경 추적 | CloudTrail 로그 뒤지기 | Git 커밋 히스토리 |
| 코드 리뷰 | 불가능 | PR에서 diff 확인 |
| 롤백 | 수동 복원 (가능하면) | 이전 커밋으로 apply |
| 일관성 | 사람마다 다른 결과 | 항상 동일한 결과 |
| 문서화 | 별도로 작성해야 함 | 코드 자체가 문서 |
IaC 도구들은 크게 선언형(Declarative)과 명령형(Imperative)으로 나뉜다.
선언형의 장점: "원하는 최종 상태"만 기술하면 도구가 알아서 현재 상태와의 차이를 계산한다. 이미 3대가 있으면 아무것도 하지 않고, 2대만 있으면 1대를 추가한다. 이것을 멱등성(idempotency)이라 한다.
Mitchell Hashimoto는 대학 시절부터 인프라 자동화에 관심이 많았다. 2010년에 Vagrant(로컬 개발 환경 자동화 도구)를 만들었고, 이 경험을 바탕으로 2012년에 HashiCorp를 공동 창업한다.
HashiCorp의 비전은 명확했다 — "클라우드 인프라 자동화의 모든 단계를 도구로 만든다."
Terraform이 출시될 당시(2014년), AWS의 CloudFormation은 이미 존재했다. 하지만 CloudFormation은 AWS에서만 작동했다. Terraform은 처음부터 멀티 클라우드를 목표로 설계되었다. AWS, Azure, GCP를 하나의 도구로 관리한다는 것 — 이것이 Terraform의 킬러 피처였다.
IaC 도구는 여러 가지가 있다. 각각의 특징을 비교해보자.
| 비교 항목 | Terraform | CloudFormation | Pulumi | CDK |
|---|---|---|---|---|
| 개발사 | HashiCorp (IBM) | AWS | Pulumi Inc. | AWS |
| 언어 | HCL (전용 DSL) | JSON / YAML | Python, TS, Go 등 | Python, TS 등 |
| 멀티 클라우드 | 지원 (3,000+ Provider) | AWS 전용 | 지원 | AWS 전용 |
| State 관리 | 자체 tfstate 파일 | AWS가 자동 관리 | Pulumi Cloud 또는 자체 | CloudFormation 위임 |
| 접근 방식 | 선언형 | 선언형 | 명령형 + 선언형 | 명령형 → CFn 변환 |
| 학습 곡선 | 중간 (HCL 학습 필요) | 높음 (JSON/YAML 장황) | 낮음 (기존 언어 사용) | 중간 |
| 커뮤니티 | 가장 큼 | AWS 생태계 내 | 성장 중 | AWS 생태계 내 |
| 라이선스 | BSL 1.1 (2023~) | 프로프리어터리 | Apache 2.0 | Apache 2.0 |
Terraform을 선택하는 이유: 멀티 클라우드 지원, 방대한 Provider 생태계(AWS, Azure, GCP, Kubernetes, Datadog, GitHub 등 3,000개 이상), 그리고 업계에서 가장 큰 커뮤니티와 레퍼런스. 한국 채용 공고에서 IaC를 언급하면 대부분 Terraform이다.
CloudFormation을 선택하는 이유: AWS 올인(all-in) 환경에서는 State 관리를 AWS가 대신 해주므로 운영 부담이 적다. AWS 신규 서비스를 가장 빨리 지원한다.
Pulumi를 선택하는 이유: HCL을 새로 배우기 싫고, Python이나 TypeScript 같은 기존 프로그래밍 언어로 인프라를 정의하고 싶을 때. 조건문, 반복문, 테스트를 자연스럽게 쓸 수 있다.
Terraform은 HCL(HashiCorp Configuration Language)이라는 전용 언어를 사용한다. JSON보다 읽기 쉽고, 프로그래밍 언어보다 단순하다. 핵심 블록 5가지를 알면 기본은 끝이다.
Provider는 Terraform이 어떤 API와 통신할지 정의한다. AWS, Azure, GCP, Kubernetes 등 각 서비스마다 Provider가 있다.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.7.0"
}
provider "aws" {
region = "ap-northeast-2" # 서울 리전
}
~> 5.0은 "5.x 범위 내에서 최신 버전"이라는 의미다. 5.1, 5.23은 허용하지만, 6.0은 허용하지 않는다.
Resource는 실제로 생성할 인프라 리소스를 정의한다. Terraform의 핵심 블록이다.
resource "aws_instance" "web" {
ami = "ami-0c9c942bd7bf113a2"
instance_type = "t3.micro"
tags = {
Name = "my-first-terraform-server"
}
}
"aws_instance"는 리소스 타입(AWS EC2 인스턴스), "web"은 Terraform 내부에서 사용하는 이름이다. 이 이름으로 다른 블록에서 aws_instance.web.id처럼 참조한다.
하드코딩을 피하기 위해 변수를 사용한다.
# variables.tf
variable "instance_type" {
description = "EC2 인스턴스 타입"
type = string
default = "t3.micro"
}
variable "environment" {
description = "환경 (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment는 dev, staging, prod 중 하나여야 합니다."
}
}
# 사용
resource "aws_instance" "web" {
instance_type = var.instance_type
# ...
}
변수 값은 terraform.tfvars 파일, 환경 변수(TF_VAR_instance_type), 또는 CLI 플래그(-var="instance_type=t3.small")로 전달한다.
생성된 리소스의 정보를 외부로 노출한다.
# outputs.tf
output "instance_public_ip" {
description = "EC2 인스턴스의 퍼블릭 IP"
value = aws_instance.web.public_ip
}
output "instance_id" {
description = "EC2 인스턴스 ID"
value = aws_instance.web.id
}
terraform apply 후 콘솔에 출력되고, 다른 모듈에서 참조할 수도 있다.
Data 블록은 리소스를 만드는 것이 아니라, 이미 존재하는 리소스를 읽어오는 블록이다.
# 최신 Amazon Linux 2023 AMI를 자동으로 조회
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id # 조회 결과 사용
instance_type = var.instance_type
}
AMI ID를 하드코딩하지 않고, 항상 최신 AMI를 자동으로 사용할 수 있다.

Terraform의 작업 흐름은 단 4개의 명령어로 돌아간다. 이 흐름을 이해하면 Terraform의 절반을 이해한 것이다.
terraform init — 초기화프로젝트 디렉토리에서 가장 먼저 실행하는 명령어다. Provider 플러그인을 다운로드하고, Backend를 설정하고, 모듈을 가져온다.
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.82.2...
- Installed hashicorp/aws v5.82.2 (signed by HashiCorp)
Terraform has been successfully initialized!
.terraform/ 디렉토리가 생기는데, 이 디렉토리는 .gitignore에 넣어야 한다. Provider 바이너리가 수백 MB에 달하기 때문이다.
terraform plan — 실행 계획 미리보기실제로 아무것도 변경하지 않고, 현재 상태와 코드의 차이를 보여준다. "이걸 적용하면 무슨 일이 벌어질까?"를 미리 확인하는 단계다.
$ terraform plan
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0c9c942bd7bf113a2"
+ instance_type = "t3.micro"
+ id = (known after apply)
+ public_ip = (known after apply)
+ tags = {
+ "Name" = "my-first-terraform-server"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
+는 생성, ~는 수정, -는 삭제를 의미한다. PR을 올릴 때 terraform plan 결과를 같이 첨부하면 코드 리뷰어가 "이 코드가 실제로 무슨 변경을 일으키는지" 바로 확인할 수 있다.
terraform apply — 실제 적용plan에서 보여준 변경을 실제로 실행한다. yes를 입력하면 클라우드에 리소스가 생성된다.
$ terraform apply
# ... plan 결과 출력 ...
Do you want to perform these actions?
Enter a value: yes
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Creation complete after 32s [id=i-0abc123def456]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
instance_public_ip = "3.34.123.45"
terraform destroy — 전체 삭제코드로 만든 모든 리소스를 삭제한다. 테스트 환경을 깔끔하게 정리할 때 유용하다.
$ terraform destroy
# 삭제될 리소스 목록 출력
Do you want to destroy all resources?
Enter a value: yes
aws_instance.web: Destroying... [id=i-0abc123def456]
aws_instance.web: Destruction complete after 40s
Destroy complete! Resources: 1 destroyed.
주의: 프로덕션 환경에서
terraform destroy를 실행하면 서비스가 전부 내려간다. 실수를 방지하려면prevent_destroy = true라이프사이클 설정을 사용한다.

Terraform의 가장 중요한 개념 중 하나가 State다. terraform apply를 실행하면 Terraform은 terraform.tfstate라는 JSON 파일을 생성한다. 이 파일은 "내가 지금까지 만든 인프라의 현재 상태"를 기록한다.
{
"version": 4,
"terraform_version": "1.9.5",
"resources": [
{
"type": "aws_instance",
"name": "web",
"instances": [
{
"attributes": {
"id": "i-0abc123def456",
"ami": "ami-0c9c942bd7bf113a2",
"instance_type": "t3.micro",
"public_ip": "3.34.123.45"
}
}
]
}
]
}
Terraform은 plan이나 apply를 실행할 때 이 State 파일과 실제 클라우드 상태를 비교한다.
terraform import로 하나하나 복구해야 하는데, 리소스가 수백 개면 악몽이다.팀에서 Terraform을 사용한다면, State 파일을 S3 버킷에 저장하고 DynamoDB로 잠금(Lock)을 관리하는 것이 표준 패턴이다.
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/vpc/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
apply 중이면 B는 기다려야 한다. 동시 수정으로 인한 State 충돌을 방지한다.State 파일에는 비밀번호, 키 같은 민감 정보가 평문으로 저장될 수 있다. 예를 들어
aws_db_instance의password값이 State에 그대로 기록된다. S3 암호화는 반드시 켜고, State 파일을 Git에 커밋하면 절대 안 된다..gitignore에*.tfstate와*.tfstate.backup을 추가한다.
프로젝트가 커지면 main.tf 파일 하나에 수백 줄의 코드가 쌓인다. VPC 설정, EC2 설정, RDS 설정, S3 설정이 뒤섞여서 읽기도, 관리하기도 어려워진다.
모듈(Module)은 리소스의 논리적 그룹을 재사용 가능한 패키지로 만드는 것이다. React의 컴포넌트, Python의 패키지와 같은 개념이다.
modules/
├── vpc/
│ ├── main.tf # VPC, 서브넷, IGW 리소스
│ ├── variables.tf # 입력 변수 (CIDR, 서브넷 수 등)
│ └── outputs.tf # 출력 (VPC ID, 서브넷 ID 등)
├── ec2/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── rds/
├── main.tf
├── variables.tf
└── outputs.tf
# 루트 main.tf
module "vpc" {
source = "./modules/vpc"
vpc_cidr = "10.0.0.0/16"
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.20.0/24"]
environment = "prod"
}
module "web_server" {
source = "./modules/ec2"
instance_type = "t3.medium"
subnet_id = module.vpc.public_subnet_ids[0] # VPC 모듈의 출력 참조
environment = "prod"
}
module "database" {
source = "./modules/rds"
instance_class = "db.t3.medium"
subnet_ids = module.vpc.private_subnet_ids # 프라이빗 서브넷에 배치
vpc_id = module.vpc.vpc_id
environment = "prod"
}
모듈의 장점을 정리하면 다음과 같다.
직접 모듈을 만들 필요 없이, Terraform Registry(registry.terraform.io)에서 커뮤니티가 만든 모듈을 가져다 쓸 수 있다. 예를 들어 AWS VPC 모듈은 GitHub 스타가 5,000개가 넘는다.
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.16.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-2a", "ap-northeast-2c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.20.0/24"]
enable_nat_gateway = true
single_nat_gateway = true # 비용 절약 (dev 환경)
tags = {
Environment = "dev"
Terraform = "true"
}
}
이론은 충분하다. 실제로 웹 서비스 인프라 한 세트를 Terraform으로 만들어보자. 구성은 다음과 같다.
my-web-infra/
├── main.tf # 메인 설정
├── variables.tf # 변수 정의
├── outputs.tf # 출력 정의
├── terraform.tfvars # 변수 값 (환경별)
└── .gitignore # tfstate, .terraform 제외
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.7.0"
}
provider "aws" {
region = var.aws_region
}
# ─── VPC ───
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = { Name = "${var.project}-vpc" }
}
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = { Name = "${var.project}-public-${count.index + 1}" }
}
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 10}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = { Name = "${var.project}-private-${count.index + 1}" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "${var.project}-igw" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "${var.project}-public-rt" }
}
resource "aws_route_table_association" "public" {
count = 2
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
data "aws_availability_zones" "available" {
state = "available"
}
# ─── Security Groups ───
resource "aws_security_group" "alb" {
name_prefix = "${var.project}-alb-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
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"]
}
}
resource "aws_security_group" "web" {
name_prefix = "${var.project}-web-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "db" {
name_prefix = "${var.project}-db-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
}
# ─── EC2 ───
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.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
dnf install -y httpd
systemctl enable --now httpd
echo "<h1>Hello from Terraform!</h1>" > /var/www/html/index.html
EOF
tags = { Name = "${var.project}-web" }
}
# ─── RDS ───
resource "aws_db_subnet_group" "main" {
name = "${var.project}-db-subnet"
subnet_ids = aws_subnet.private[*].id
}
resource "aws_db_instance" "main" {
identifier = "${var.project}-db"
engine = "mysql"
engine_version = "8.0"
instance_class = "db.t3.micro"
allocated_storage = 20
db_name = "appdb"
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
skip_final_snapshot = true
tags = { Name = "${var.project}-db" }
}
variable "aws_region" {
description = "AWS 리전"
type = string
default = "ap-northeast-2"
}
variable "project" {
description = "프로젝트 이름"
type = string
default = "my-web"
}
variable "instance_type" {
description = "EC2 인스턴스 타입"
type = string
default = "t3.micro"
}
variable "db_username" {
description = "RDS 관리자 계정명"
type = string
sensitive = true
}
variable "db_password" {
description = "RDS 관리자 비밀번호"
type = string
sensitive = true
}
# 1. 초기화
$ terraform init
# 2. 계획 확인
$ terraform plan -var="db_username=admin" -var="db_password=MySecureP@ss123"
# Plan: 12 to add, 0 to change, 0 to destroy.
# VPC, 서브넷 4개, IGW, 라우팅 테이블, SG 3개, EC2, RDS = 12개 리소스
# 3. 적용
$ terraform apply -var="db_username=admin" -var="db_password=MySecureP@ss123"
# 4. 결과 확인
$ terraform output
# instance_public_ip = "3.34.xxx.xxx"
# rds_endpoint = "my-web-db.xxxx.ap-northeast-2.rds.amazonaws.com:3306"
코드 한 파일로 VPC, 서브넷, 인터넷 게이트웨이, 보안 그룹, EC2 인스턴스, RDS 데이터베이스가 모두 만들어진다. 같은 코드를 다른 리전이나 다른 AWS 계정에 적용하면 동일한 환경이 복제된다.
지금까지의 예제는 모두 로컬 머신에서 terraform CLI를 실행하는 방식이었다. 혼자 작업할 때는 괜찮지만, 팀으로 일하면 문제가 생긴다.
| 문제 | 로컬 실행 | Terraform Cloud |
|---|---|---|
| State 공유 | S3 Backend 직접 설정 | 자동 관리 + 암호화 |
| 시크릿 관리 | tfvars 파일 공유 (위험) | Workspace 변수 (암호화) |
| 코드 리뷰 | Plan 결과 수동 복붙 | PR에 Plan 자동 코멘트 |
| 권한 제어 | AWS 자격증명 공유 | RBAC + Team 기능 |
| 실행 이력 | 터미널 히스토리뿐 | 모든 Run 기록 보존 |
| 정책 적용 | 사람이 직접 확인 | Sentinel/OPA 자동 정책 검사 |
Terraform Cloud(app.terraform.io)는 HashiCorp가 운영하는 SaaS 플랫폼이다. 무료 플랜으로 최대 500개 리소스를 관리할 수 있다.
plan을 실행하고, PR에 결과를 코멘트로 남긴다
apply 실행 — 모든 기록이 보존된다
# Terraform Cloud 설정
terraform {
cloud {
organization = "my-company"
workspaces {
name = "my-web-prod"
}
}
}
이렇게 설정하면 terraform plan과 terraform apply가 로컬이 아닌 Terraform Cloud의 서버에서 실행된다. AWS 자격증명은 Terraform Cloud의 Workspace 변수에 저장되므로, 개발자 로컬에 AWS 키가 없어도 된다.
2023년 8월, HashiCorp는 Terraform을 포함한 모든 제품의 라이선스를 MPL 2.0(Mozilla Public License)에서 BSL 1.1(Business Source License)으로 변경했다. 이 결정은 IaC 커뮤니티를 뒤흔들었다.
| 비교 항목 | Terraform | OpenTofu |
|---|---|---|
| 라이선스 | BSL 1.1 | MPL 2.0 (진정한 오픈소스) |
| 관리 주체 | HashiCorp (IBM) | Linux Foundation |
| CLI 명령어 | terraform | tofu |
| HCL 호환 | 원본 | 완전 호환 |
| Provider 호환 | Terraform Registry | 동일 Provider 사용 가능 |
| State 암호화 | 미지원 (Cloud에서만) | 클라이언트 사이드 암호화 지원 |
| 생태계 성숙도 | 10년+ 레퍼런스 | 아직 성장 중 |
| 마이그레이션 | — | tofu init만 실행하면 전환 |
결론: 일반 사용자 입장에서 BSL 라이선스는 실질적 영향이 거의 없다. Terraform을 사용하여 자사 인프라를 관리하는 것은 완전히 허용된다. 하지만 "진정한 오픈소스"를 중시하거나, Terraform을 기반으로 상업 서비스를 구축하는 경우에는 OpenTofu가 대안이 된다.
입문 단계에서는 Terraform으로 시작하는 것을 권장한다. 레퍼런스와 커뮤니티 자료가 압도적으로 많다. OpenTofu로의 전환은 나중에 필요할 때 해도
tofu init한 번이면 된다.
version = "~> 5.0"처럼 범위를 지정하고, terraform.lock.hcl을 Git에 커밋한다.main.tf는 아무도 읽고 싶어하지 않는다.terraform plan을 반드시 확인한 후 apply한다. CI에서 자동으로 plan 결과를 PR에 코멘트하는 것이 이상적이다.sensitive = true 변수 + Terraform Cloud 변수 또는 AWS Secrets Manager를 사용한다.${project}-${environment}-${component} 패턴이 일반적이다.마지막으로, Terraform을 체계적으로 학습하기 위한 로드맵을 정리한다.
추천 자료:
콘솔에서 클릭 100번 하는 것과 코드 한 파일을 apply하는 것. 결과물은 같지만, 관리 가능성은 완전히 다르다.
코드로 작성된 인프라는 재현 가능하고, 버전 관리되고, 코드 리뷰를 받고, 자동화할 수 있다. 소프트웨어 개발에서 이미 입증된 모든 좋은 관행(Git, CI/CD, 코드 리뷰, 테스트)을 인프라에도 적용할 수 있다는 것 — 그것이 IaC의 본질이고, Terraform이 지난 10년간 이끌어온 변화다.
Terraform의 진입 장벽은 높지 않다. AWS 계정 하나, terraform init, 그리고 HCL 파일 하나면 시작할 수 있다. 오늘 EC2 인스턴스 하나를 코드로 만들어보자. 그 작은 경험이 인프라를 바라보는 관점을 완전히 바꿔줄 것이다.