Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 마이크로서비스 운영
- 세션저장소
- rabbitmq
- devops
- 마이크로서비스 통신
- 컨테이너오케스트레이션
- 분산 시스템
- 분산 모니터링
- 서비스 설계
- 메시지 브로커
- 서비스 메시
- CI/CD
- ApacheBench
- 클러스터
- RabbitMQ Exchange
- 인메모리데이터베이스
- 이벤트 스트리밍
- docker
- 고가용성
- 클라우드
- 마이크로서비스
- kubernetes
- infrastructureascode
- Python
- 모니터링
- 모노리스 분해
- Kafka 클러스터
- 메시징 패턴
- 보안
- 프로덕션 운영
Archives
- Today
- Total
hobokai 님의 블로그
Terraform 완전 마스터 가이드: Infrastructure as Code로 클라우드 인프라 자동화 본문
목차
- Terraform이란 무엇인가?
- Terraform 아키텍처와 개념
- 설치 및 초기 설정
- HCL 언어 기초
- Provider와 Resource
- 변수와 출력값
- 상태 관리(State)
- 모듈 시스템
- 데이터 소스 활용
- 조건문과 반복문
- 함수와 표현식
- 워크스페이스 관리
- 클라우드별 실전 예제
- CI/CD 통합
- 보안과 베스트 프랙티스
- 문제 해결 가이드
- 결론
Terraform이란 무엇인가?
Terraform은 HashiCorp에서 개발한 오픈소스 Infrastructure as Code (IaC) 도구입니다. 선언적 구성 파일을 사용하여 클라우드 및 온프레미스 리소스를 정의, 배포, 관리할 수 있습니다.
Terraform의 핵심 가치
- 🏗️ Infrastructure as Code - 인프라를 코드로 관리
- 🌍 다중 클라우드 지원 - AWS, Azure, GCP 등 700+ 프로바이더
- 📋 선언적 구성 - 원하는 최종 상태를 정의
- 🔄 계획과 적용 - 변경사항을 미리 확인 후 적용
- 📈 버전 관리 - Git으로 인프라 변경 이력 추적
- 🤝 팀 협업 - 공유 상태와 잠금 메커니즘
Infrastructure as Code의 장점
| 전통적 방식 | Infrastructure as Code |
|---|---|
| 수동 구성 | 자동화된 프로비저닝 |
| 일회성 작업 | 재사용 가능한 템플릿 |
| 문서 별도 관리 | 코드가 곧 문서 |
| 환경별 차이 | 일관된 환경 구성 |
| 변경 추적 어려움 | Git을 통한 버전 관리 |
| 롤백 복잡 | 쉬운 롤백과 복구 |
Terraform vs 다른 IaC 도구
| 특성 | Terraform | CloudFormation | ARM Templates | Pulumi |
|---|---|---|---|---|
| 다중 클라우드 | ✅ | ❌ (AWS 전용) | ❌ (Azure 전용) | ✅ |
| 언어 | HCL | JSON/YAML | JSON | 범용 언어 |
| 상태 관리 | 별도 파일 | AWS 관리 | Azure 관리 | 별도 파일 |
| 커뮤니티 | 매우 큰 | 큰 (AWS) | 보통 (Azure) | 성장 중 |
| 학습 곡선 | 보통 | 보통 | 높음 | 낮음 (개발자) |
Terraform 아키텍처와 개념
핵심 구성 요소
┌─────────────────────────────────────────────────────────────┐
│ Terraform Core │
├─────────────────────────────────────────────────────────────┤
│ Configuration Files (.tf) │ State File (.tfstate) │
│ - Resources │ - Current Infrastructure │
│ - Providers │ - Resource Mapping │
│ - Variables │ - Metadata │
│ - Outputs │ │
└─────────────────────────────────────────────────────────────┘
│
│ (API 호출)
│
┌─────────────────────────────────────────────────────────────┐
│ Providers │
├─────────────────────────────────────────────────────────────┤
│ AWS Provider │ Azure Provider │ GCP Provider │ Others │
└─────────────────────────────────────────────────────────────┘
│
│
┌─────────────────────────────────────────────────────────────┐
│ Cloud Infrastructure │
├─────────────────────────────────────────────────────────────┤
│ EC2 │ VPC │ RDS │ VM │ Storage │ ... │
└─────────────────────────────────────────────────────────────┘Terraform 워크플로우
- Write - 인프라 구성 파일 작성 (.tf)
- Plan - 변경 계획 생성 및 검토 (
terraform plan) - Apply - 변경사항 실제 적용 (
terraform apply) - Manage - 상태 관리 및 업데이트
핵심 개념
- Provider: 클라우드 서비스와의 인터페이스
- Resource: 관리할 인프라 구성 요소
- Data Source: 기존 인프라 정보 조회
- Variable: 구성 값의 매개화
- Output: 다른 구성에서 사용할 값 출력
- Module: 재사용 가능한 구성 단위
설치 및 초기 설정
Terraform 설치
Windows
# Chocolatey 사용
choco install terraform
# Scoop 사용
scoop install terraform
# 수동 설치
# 1. https://www.terraform.io/downloads 에서 다운로드
# 2. PATH에 추가
macOS
# Homebrew 사용
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# 또는 기본 tap 사용
brew install terraform
Linux
# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /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 install terraform
# 바이너리 직접 설치
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install terraform
설치 확인 및 기본 설정
# 버전 확인
terraform version
# 자동 완성 설치
terraform -install-autocomplete
# 기본 디렉토리 구조 생성
mkdir terraform-project
cd terraform-project
# 첫 번째 구성 파일 생성
touch main.tf variables.tf outputs.tf
IDE 및 에디터 설정
# VS Code 확장
# - HashiCorp Terraform
# - Terraform doc snippets
# Vim 플러그인
# - vim-terraform
# - vim-terraform-completion
# IntelliJ/PyCharm 플러그인
# - Terraform and HCL plugin
HCL 언어 기초
HCL (HashiCorp Configuration Language) 구문
# 기본 블록 구조
block_type "block_label" "block_name" {
# 블록 내용
argument_name = argument_value
# 중첩 블록
nested_block {
nested_argument = "value"
}
}
# 예시: AWS EC2 인스턴스
resource "aws_instance" "web" {
ami = "ami-0c02fb55956c7d316"
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
데이터 타입
# 문자열
variable "region" {
type = string
default = "us-west-2"
}
# 숫자
variable "instance_count" {
type = number
default = 2
}
# 불린
variable "enable_monitoring" {
type = bool
default = true
}
# 리스트
variable "availability_zones" {
type = list(string)
default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}
# 맵
variable "instance_tags" {
type = map(string)
default = {
Environment = "dev"
Project = "web-app"
}
}
# 객체
variable "database_config" {
type = object({
engine = string
engine_version = string
instance_class = string
allocated_storage = number
})
default = {
engine = "postgres"
engine_version = "13.7"
instance_class = "db.t3.micro"
allocated_storage = 20
}
}
# 집합 (Set)
variable "security_group_ids" {
type = set(string)
default = ["sg-12345678", "sg-87654321"]
}
# 튜플
variable "mixed_list" {
type = tuple([string, number, bool])
default = ["example", 42, true]
}
주석과 문서화
# 한 줄 주석
/*
여러 줄
주석
*/
// C 스타일 주석도 지원
# 변수에 설명 추가
variable "instance_type" {
description = "EC2 인스턴스 타입을 지정합니다"
type = string
default = "t2.micro"
}
Provider와 Resource
Provider 구성
# 기본 AWS Provider
provider "aws" {
region = "us-west-2"
}
# 여러 리전을 위한 별칭 Provider
provider "aws" {
alias = "east"
region = "us-east-1"
}
# 프로파일 지정
provider "aws" {
region = "us-west-2"
profile = "production"
}
# 역할 가정 (Role Assumption)
provider "aws" {
region = "us-west-2"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/terraform-role"
}
}
# Azure Provider
provider "azurerm" {
features {}
subscription_id = var.subscription_id
tenant_id = var.tenant_id
}
# GCP Provider
provider "google" {
project = var.project_id
region = var.region
zone = var.zone
}
# 버전 제약
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
required_version = ">= 1.0"
}
기본 Resource 사용
# AWS VPC 생성
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
# 서브넷 생성
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-${count.index + 1}"
Type = "Public"
}
}
# 인터넷 게이트웨이
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-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 = "public-rt"
}
}
# 라우팅 테이블 연결
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
리소스 간 의존성
# 명시적 의존성
resource "aws_instance" "web" {
ami = "ami-0c02fb55956c7d316"
instance_type = "t2.micro"
subnet_id = aws_subnet.public[0].id
# 명시적 의존성 선언
depends_on = [
aws_internet_gateway.main,
aws_security_group.web
]
}
# 암시적 의존성 (참조를 통해 자동 생성)
resource "aws_security_group" "web" {
name_prefix = "web-sg"
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"]
}
}
변수와 출력값
변수 정의
# variables.tf
variable "project_name" {
description = "프로젝트 이름"
type = string
default = "my-project"
}
variable "environment" {
description = "배포 환경"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
description = "EC2 인스턴스 설정"
type = object({
type = string
count = number
})
default = {
type = "t2.micro"
count = 1
}
}
variable "allowed_cidr_blocks" {
description = "허용할 CIDR 블록 목록"
type = list(string)
default = ["0.0.0.0/0"]
}
# 민감한 변수
variable "db_password" {
description = "데이터베이스 비밀번호"
type = string
sensitive = true
}
변수 값 전달 방법
# 1. terraform.tfvars 파일
project_name = "web-application"
environment = "prod"
instance_config = {
type = "t3.medium"
count = 3
}
# 2. 환경별 변수 파일
# dev.tfvars
environment = "dev"
instance_config = {
type = "t2.micro"
count = 1
}
# prod.tfvars
environment = "prod"
instance_config = {
type = "t3.large"
count = 5
}
# 3. 환경 변수 (TF_VAR_prefix)
export TF_VAR_db_password="super-secret"
export TF_VAR_environment="prod"
# 4. 명령줄에서 직접 전달
terraform apply -var="environment=prod" -var="instance_count=3"
terraform apply -var-file="prod.tfvars"
출력값 정의
# outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "퍼블릭 서브넷 ID 목록"
value = aws_subnet.public[*].id
}
output "web_instance_public_ips" {
description = "웹 서버 퍼블릭 IP 목록"
value = aws_instance.web[*].public_ip
}
# 민감한 출력값
output "database_password" {
description = "데이터베이스 비밀번호"
value = var.db_password
sensitive = true
}
# 조건부 출력
output "load_balancer_dns" {
description = "로드 밸런서 DNS 이름"
value = var.create_load_balancer ? aws_lb.main[0].dns_name : null
}
# 복합 출력
output "connection_info" {
description = "연결 정보"
value = {
vpc_id = aws_vpc.main.id
region = var.region
endpoints = {
web = aws_instance.web[*].public_ip
db = aws_db_instance.main.endpoint
}
}
}
상태 관리(State)
로컬 상태 파일
# 기본적으로 terraform.tfstate 파일에 저장
ls -la terraform.tfstate*
# 상태 파일 내용 확인
terraform show
# 상태 목록 확인
terraform state list
# 특정 리소스 상태 확인
terraform state show aws_instance.web
원격 상태 저장소
# S3 백엔드 설정
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# Azure Storage 백엔드
terraform {
backend "azurerm" {
resource_group_name = "terraform-state"
storage_account_name = "terraformstate12345"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
# GCS 백엔드
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "prod"
}
}
# Terraform Cloud 백엔드
terraform {
backend "remote" {
organization = "my-org"
workspaces {
name = "production"
}
}
}
상태 잠금 설정
# S3 + DynamoDB 잠금 설정
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "global/s3/terraform.tfstate"
region = "us-west-2"
encrypt = true
# DynamoDB 테이블로 상태 잠금
dynamodb_table = "terraform-locks"
}
}
# DynamoDB 테이블 생성 (별도 구성)
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform State Lock Table"
}
}
상태 관리 명령어
# 상태 초기화 (백엔드 설정 후)
terraform init
# 원격 상태로 마이그레이션
terraform init -migrate-state
# 상태 파일 새로고침
terraform refresh
# 상태에서 리소스 제거 (실제 리소스는 유지)
terraform state rm aws_instance.web
# 리소스를 상태에 추가
terraform import aws_instance.web i-1234567890abcdef0
# 리소스 이름 변경
terraform state mv aws_instance.old_name aws_instance.new_name
# 상태 파일 백업
terraform state pull > backup.tfstate
# 상태 파일 복원
terraform state push backup.tfstate
# 상태 잠금 강제 해제 (주의!)
terraform force-unlock <LOCK_ID>
모듈 시스템
모듈 구조
modules/
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
├── ec2/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
└── rds/
├── main.tf
├── variables.tf
├── outputs.tf
└── README.mdVPC 모듈 예시
# modules/vpc/main.tf
resource "aws_vpc" "main" {
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_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 = merge(var.tags, {
Name = "${var.name}-public-${count.index + 1}"
Type = "Public"
})
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.name}-private-${count.index + 1}"
Type = "Private"
})
}
resource "aws_internet_gateway" "main" {
count = length(var.public_subnet_cidrs) > 0 ? 1 : 0
vpc_id = aws_vpc.main.id
tags = merge(var.tags, {
Name = "${var.name}-igw"
})
}
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? length(var.private_subnet_cidrs) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.tags, {
Name = "${var.name}-nat-${count.index + 1}"
})
depends_on = [aws_internet_gateway.main]
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.private_subnet_cidrs) : 0
domain = "vpc"
tags = merge(var.tags, {
Name = "${var.name}-nat-eip-${count.index + 1}"
})
}
# modules/vpc/variables.tf
variable "name" {
description = "VPC 이름"
type = string
}
variable "cidr_block" {
description = "VPC CIDR 블록"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "가용 영역 목록"
type = list(string)
}
variable "public_subnet_cidrs" {
description = "퍼블릭 서브넷 CIDR 목록"
type = list(string)
default = []
}
variable "private_subnet_cidrs" {
description = "프라이빗 서브넷 CIDR 목록"
type = list(string)
default = []
}
variable "enable_dns_hostnames" {
description = "DNS 호스트네임 활성화"
type = bool
default = true
}
variable "enable_dns_support" {
description = "DNS 지원 활성화"
type = bool
default = true
}
variable "enable_nat_gateway" {
description = "NAT 게이트웨이 생성 여부"
type = bool
default = true
}
variable "tags" {
description = "공통 태그"
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "vpc_cidr_block" {
description = "VPC CIDR 블록"
value = aws_vpc.main.cidr_block
}
output "public_subnet_ids" {
description = "퍼블릭 서브넷 ID 목록"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "프라이빗 서브넷 ID 목록"
value = aws_subnet.private[*].id
}
output "internet_gateway_id" {
description = "인터넷 게이트웨이 ID"
value = length(aws_internet_gateway.main) > 0 ? aws_internet_gateway.main[0].id : null
}
output "nat_gateway_ids" {
description = "NAT 게이트웨이 ID 목록"
value = aws_nat_gateway.main[*].id
}
모듈 사용
# main.tf
module "vpc" {
source = "./modules/vpc"
name = "my-app"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
enable_nat_gateway = true
tags = {
Environment = "production"
Project = "web-app"
}
}
module "web_servers" {
source = "./modules/ec2"
name = "web-server"
instance_type = "t3.medium"
instance_count = 3
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.public_subnet_ids
tags = {
Environment = "production"
Role = "web"
}
}
# 모듈 출력값 사용
output "vpc_info" {
value = {
vpc_id = module.vpc.vpc_id
public_subnets = module.vpc.public_subnet_ids
private_subnets = module.vpc.private_subnet_ids
}
}
외부 모듈 사용
# Terraform Registry에서 모듈 사용
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-west-2a", "us-west-2b", "us-west-2c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
enable_vpn_gateway = true
tags = {
Terraform = "true"
Environment = "dev"
}
}
# Git 저장소에서 모듈 사용
module "networking" {
source = "git::https://github.com/company/terraform-modules.git//networking?ref=v1.0.0"
vpc_cidr = "10.0.0.0/16"
region = "us-west-2"
}
데이터 소스 활용
기본 데이터 소스
# 기존 VPC 정보 조회
data "aws_vpc" "default" {
default = true
}
# AMI 정보 조회
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# 가용 영역 목록 조회
data "aws_availability_zones" "available" {
state = "available"
}
# 현재 AWS 계정 정보
data "aws_caller_identity" "current" {}
# 현재 AWS 리전 정보
data "aws_region" "current" {}
# 기존 보안 그룹 조회
data "aws_security_group" "web" {
filter {
name = "group-name"
values = ["web-sg"]
}
}
# 서브넷 정보 조회
data "aws_subnets" "private" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default.id]
}
filter {
name = "tag:Type"
values = ["Private"]
}
}
# Route 53 Hosted Zone 조회
data "aws_route53_zone" "main" {
name = "example.com"
private_zone = false
}
외부 데이터 소스
# HTTP 데이터 소스
data "http" "my_ip" {
url = "https://ipv4.icanhazip.com"
}
# 로컬 파일 읽기
data "local_file" "ssh_key" {
filename = "~/.ssh/id_rsa.pub"
}
# 템플릿 파일
data "template_file" "user_data" {
template = file("${path.module}/user-data.sh")
vars = {
server_name = "web-server"
environment = var.environment
}
}
# 외부 명령 실행
data "external" "git_info" {
program = ["bash", "-c", <<EOT
echo "{\"commit\": \"$(git rev-parse HEAD)\", \"branch\": \"$(git branch --show-current)\"}"
EOT
]
}
데이터 소스 활용 예시
# 데이터 소스를 활용한 인스턴스 생성
resource "aws_instance" "web" {
count = 2
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [data.aws_security_group.web.id]
subnet_id = data.aws_subnets.private.ids[count.index]
user_data = data.template_file.user_data.rendered
tags = {
Name = "web-${count.index + 1}"
AMI = data.aws_ami.ubuntu.name
AZ = data.aws_availability_zones.available.names[count.index]
Account = data.aws_caller_identity.current.account_id
Region = data.aws_region.current.name
Commit = jsondecode(data.external.git_info.result).commit
}
}
# 조건부 리소스 생성
resource "aws_route53_record" "web" {
count = var.create_dns_record ? 1 : 0
zone_id = data.aws_route53_zone.main.zone_id
name = "web.${data.aws_route53_zone.main.name}"
type = "A"
ttl = 300
records = aws_instance.web[*].public_ip
}
조건문과 반복문
조건문 (Conditional Expressions)
# 기본 조건문
variable "create_load_balancer" {
type = bool
default = true
}
resource "aws_lb" "main" {
count = var.create_load_balancer ? 1 : 0
name = "main-lb"
internal = false
load_balancer_type = "application"
subnets = var.public_subnet_ids
}
# 복잡한 조건문
locals {
instance_type = var.environment == "prod" ? "t3.large" : (
var.environment == "staging" ? "t3.medium" : "t2.micro"
)
storage_encrypted = var.environment == "prod" ? true : false
backup_retention = var.environment == "prod" ? 30 : (
var.environment == "staging" ? 7 : 1
)
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = local.instance_type
root_block_device {
volume_type = "gp3"
volume_size = var.environment == "prod" ? 50 : 20
encrypted = local.storage_encrypted
}
}
# null 값 처리
resource "aws_db_instance" "main" {
identifier = "myapp-db"
engine = "postgres"
engine_version = var.db_version != null ? var.db_version : "13.7"
instance_class = var.db_instance_class != "" ? var.db_instance_class : "db.t3.micro"
backup_retention_period = local.backup_retention
backup_window = var.environment == "prod" ? "03:00-04:00" : null
}
count를 이용한 반복
# 기본 count 사용
variable "instance_count" {
type = number
default = 3
}
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
subnet_id = var.subnet_ids[count.index % length(var.subnet_ids)]
tags = {
Name = "web-${count.index + 1}"
Index = count.index
}
}
# 조건부 count
resource "aws_instance" "worker" {
count = var.create_workers ? var.worker_count : 0
ami = data.aws_ami.ubuntu.id
instance_type = "t2.small"
tags = {
Name = "worker-${count.index + 1}"
Role = "worker"
}
}
# 리스트 기반 count
variable "environments" {
type = list(string)
default = ["dev", "staging", "prod"]
}
resource "aws_s3_bucket" "logs" {
count = length(var.environments)
bucket = "my-app-logs-${var.environments[count.index]}"
tags = {
Environment = var.environments[count.index]
}
}
for_each를 이용한 반복
# 맵을 이용한 for_each
variable "users" {
type = map(object({
role = string
team = string
}))
default = {
"alice" = {
role = "admin"
team = "platform"
}
"bob" = {
role = "developer"
team = "backend"
}
"charlie" = {
role = "developer"
team = "frontend"
}
}
}
resource "aws_iam_user" "users" {
for_each = var.users
name = each.key
tags = {
Role = each.value.role
Team = each.value.team
}
}
# 세트를 이용한 for_each
variable "bucket_names" {
type = set(string)
default = ["logs", "assets", "backups"]
}
resource "aws_s3_bucket" "buckets" {
for_each = var.bucket_names
bucket = "my-app-${each.value}"
tags = {
Purpose = each.value
}
}
# 복잡한 for_each 예시
variable "databases" {
type = map(object({
engine = string
engine_version = string
instance_class = string
allocated_storage = number
multi_az = bool
}))
default = {
"main" = {
engine = "postgres"
engine_version = "13.7"
instance_class = "db.t3.medium"
allocated_storage = 100
multi_az = true
}
"cache" = {
engine = "redis"
engine_version = "6.2"
instance_class = "cache.t3.micro"
allocated_storage = 0
multi_az = false
}
}
}
resource "aws_db_instance" "databases" {
for_each = {
for name, config in var.databases : name => config
if config.engine == "postgres" || config.engine == "mysql"
}
identifier = "myapp-${each.key}"
engine = each.value.engine
engine_version = each.value.engine_version
instance_class = each.value.instance_class
allocated_storage = each.value.allocated_storage
multi_az = each.value.multi_az
tags = {
Name = "myapp-${each.key}"
Engine = each.value.engine
}
}
동적 블록 (Dynamic Blocks)
variable "security_group_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"
},
{
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
description = "SSH"
}
]
}
resource "aws_security_group" "web" {
name_prefix = "web-sg"
vpc_id = var.vpc_id
# 동적 ingress 규칙 생성
dynamic "ingress" {
for_each = var.security_group_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
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# 복잡한 동적 블록 예시
variable "load_balancer_listeners" {
type = list(object({
port = number
protocol = string
ssl_policy = string
certificate_arn = string
default_action = object({
type = string
target_group_arn = string
})
}))
}
resource "aws_lb_listener" "listeners" {
for_each = {
for idx, listener in var.load_balancer_listeners : idx => listener
}
load_balancer_arn = aws_lb.main.arn
port = each.value.port
protocol = each.value.protocol
ssl_policy = each.value.protocol == "HTTPS" ? each.value.ssl_policy : null
certificate_arn = each.value.protocol == "HTTPS" ? each.value.certificate_arn : null
default_action {
type = each.value.default_action.type
target_group_arn = each.value.default_action.target_group_arn
}
}
함수와 표현식
문자열 함수
locals {
# 문자열 조작
project_name = "my-web-app"
environment = "production"
# 문자열 결합
resource_name = "${local.project_name}-${local.environment}"
# 대소문자 변환
upper_name = upper(local.project_name) # "MY-WEB-APP"
lower_name = lower(local.project_name) # "my-web-app"
title_name = title(local.project_name) # "My-Web-App"
# 문자열 치환
sanitized_name = replace(local.project_name, "-", "_") # "my_web_app"
# 문자열 분할
name_parts = split("-", local.project_name) # ["my", "web", "app"]
# 문자열 결합
joined_name = join("_", local.name_parts) # "my_web_app"
# 부분 문자열
short_name = substr(local.project_name, 0, 5) # "my-we"
# 문자열 길이
name_length = length(local.project_name) # 10
# 문자열 포함 확인
contains_web = strcontains(local.project_name, "web") # true
}
# 템플릿 문자열
locals {
user_data = templatestring(<<-EOT
#!/bin/bash
echo "Environment: ${environment}"
echo "Project: ${project_name}"
echo "Instance: ${instance_id}"
EOT
, {
environment = var.environment
project_name = var.project_name
instance_id = "i-1234567890"
})
}
숫자 함수
locals {
numbers = [10, 25, 5, 30, 15]
# 최대값, 최소값
max_number = max(local.numbers...) # 30
min_number = min(local.numbers...) # 5
# 절댓값, 올림, 내림
abs_value = abs(-10) # 10
ceil_value = ceil(4.3) # 5
floor_value = floor(4.8) # 4
# 로그 함수
log_value = log(100, 10) # 2 (log base 10 of 100)
}
# 계산 예시
resource "aws_instance" "web" {
count = ceil(var.expected_users / var.users_per_instance)
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
}
컬렉션 함수
locals {
# 리스트 관련
servers = ["web1", "web2", "db1", "cache1"]
numbers = [1, 2, 3, 4, 5]
# 리스트 길이
server_count = length(local.servers) # 4
# 리스트 인덱스
first_server = local.servers[0] # "web1"
last_server = local.servers[length(local.servers) - 1] # "cache1"
# 리스트 슬라이싱
web_servers = slice(local.servers, 0, 2) # ["web1", "web2"]
# 리스트 정렬
sorted_servers = sort(local.servers) # ["cache1", "db1", "web1", "web2"]
sorted_numbers = sort(local.numbers) # [1, 2, 3, 4, 5]
# 리스트 역순
reversed_servers = reverse(local.servers) # ["cache1", "db1", "web2", "web1"]
# 리스트 연결
all_servers = concat(local.servers, ["backup1", "backup2"])
# 중복 제거
unique_items = distinct(["a", "b", "a", "c", "b"]) # ["a", "b", "c"]
# 리스트 포함 확인
contains_web1 = contains(local.servers, "web1") # true
# 인덱스 찾기
web1_index = index(local.servers, "web1") # 0
}
# 맵 관련 함수
locals {
server_config = {
web1 = { type = "t3.medium", az = "us-west-2a" }
web2 = { type = "t3.medium", az = "us-west-2b" }
db1 = { type = "t3.large", az = "us-west-2a" }
}
# 키와 값 추출
server_names = keys(local.server_config) # ["web1", "web2", "db1"]
server_types = values(local.server_config) # [각 서버의 config 객체들]
# 맵 병합
additional_config = {
cache1 = { type = "t3.small", az = "us-west-2c" }
}
all_config = merge(local.server_config, local.additional_config)
# 맵 조회
web1_config = lookup(local.server_config, "web1", {})
web3_config = lookup(local.server_config, "web3", { type = "t2.micro", az = "us-west-2a" })
}
for 표현식
locals {
servers = ["web1", "web2", "db1", "cache1"]
# 리스트 변환
server_names = [for s in local.servers : upper(s)]
# 결과: ["WEB1", "WEB2", "DB1", "CACHE1"]
# 조건부 필터링
web_servers = [for s in local.servers : s if substr(s, 0, 3) == "web"]
# 결과: ["web1", "web2"]
# 인덱스 포함
indexed_servers = [for i, s in local.servers : "${i}-${s}"]
# 결과: ["0-web1", "1-web2", "2-db1", "3-cache1"]
}
# 맵 변환
locals {
instance_types = {
web1 = "t3.medium"
web2 = "t3.medium"
db1 = "t3.large"
}
# 맵 값 변환
instance_configs = {
for name, type in local.instance_types : name => {
instance_type = type
monitoring = type != "t3.large"
}
}
# 맵 필터링
large_instances = {
for name, type in local.instance_types : name => type
if type == "t3.large"
}
# 복잡한 변환
server_tags = {
for name, type in local.instance_types : name => {
Name = name
InstanceType = type
Environment = "prod"
Role = substr(name, 0, 3) == "web" ? "frontend" : "backend"
}
}
}
# 중첩 구조 처리
locals {
environments = {
dev = {
instances = ["web1", "web2"]
db_size = "small"
}
prod = {
instances = ["web1", "web2", "web3", "web4"]
db_size = "large"
}
}
# 중첩 for 표현식
all_instances = flatten([
for env_name, env_config in local.environments : [
for instance in env_config.instances : {
name = "${env_name}-${instance}"
environment = env_name
db_size = env_config.db_size
}
]
])
}
날짜/시간 함수
locals {
# 현재 타임스탬프
current_time = timestamp() # "2023-12-01T10:30:00Z"
# 날짜 포맷팅
formatted_date = formatdate("YYYY-MM-DD", timestamp()) # "2023-12-01"
formatted_time = formatdate("hh:mm:ss", timestamp()) # "10:30:00"
# 시간 계산
future_time = timeadd(timestamp(), "24h") # 24시간 후
past_time = timeadd(timestamp(), "-7d") # 7일 전
}
# 시간 기반 리소스 생성
resource "aws_s3_bucket" "daily_logs" {
bucket = "my-app-logs-${formatdate("YYYY-MM-DD", timestamp())}"
tags = {
CreatedAt = timestamp()
Purpose = "daily-logs"
}
}
인코딩 함수
locals {
# Base64 인코딩/디코딩
original_text = "Hello, World!"
encoded_text = base64encode(local.original_text) # "SGVsbG8sIFdvcmxkIQ=="
decoded_text = base64decode(local.encoded_text) # "Hello, World!"
# JSON 인코딩/디코딩
config_map = {
database_host = "db.example.com"
database_port = 5432
ssl_enabled = true
}
config_json = jsonencode(local.config_map)
parsed_json = jsondecode(local.config_json)
# URL 인코딩
query_param = "Hello World"
encoded_url = urlencode(local.query_param) # "Hello%20World"
}
# 사용자 데이터 스크립트 인코딩
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
user_data = base64encode(templatefile("user-data.sh", {
config = jsonencode(var.app_config)
}))
}
워크스페이스 관리
워크스페이스 개념
워크스페이스는 동일한 구성으로 여러 환경을 관리할 수 있게 해주는 기능입니다.
기본 워크스페이스 관리
# 현재 워크스페이스 확인
terraform workspace show
# 워크스페이스 목록 확인
terraform workspace list
# 새 워크스페이스 생성
terraform workspace new development
terraform workspace new staging
terraform workspace new production
# 워크스페이스 전환
terraform workspace select development
terraform workspace select production
# 워크스페이스 삭제
terraform workspace delete development
워크스페이스별 구성
# main.tf
locals {
# 워크스페이스별 설정
workspace_configs = {
default = {
instance_type = "t2.micro"
instance_count = 1
environment = "dev"
}
development = {
instance_type = "t2.micro"
instance_count = 1
environment = "dev"
}
staging = {
instance_type = "t3.small"
instance_count = 2
environment = "staging"
}
production = {
instance_type = "t3.medium"
instance_count = 3
environment = "prod"
}
}
# 현재 워크스페이스 설정
workspace_config = local.workspace_configs[terraform.workspace]
}
# 워크스페이스별 S3 백엔드
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "env:/terraform.tfstate" # workspace별로 자동 경로 생성
region = "us-west-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
# 워크스페이스 정보 활용
resource "aws_instance" "web" {
count = local.workspace_config.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = local.workspace_config.instance_type
tags = {
Name = "${terraform.workspace}-web-${count.index + 1}"
Environment = local.workspace_config.environment
Workspace = terraform.workspace
}
}
resource "aws_s3_bucket" "app_data" {
bucket = "my-app-data-${terraform.workspace}"
tags = {
Environment = local.workspace_config.environment
Workspace = terraform.workspace
}
}
환경별 변수 파일
# terraform.tfvars (기본값)
region = "us-west-2"
project_name = "my-app"
# development.tfvars
environment = "dev"
instance_type = "t2.micro"
instance_count = 1
db_instance_class = "db.t3.micro"
enable_monitoring = false
# staging.tfvars
environment = "staging"
instance_type = "t3.small"
instance_count = 2
db_instance_class = "db.t3.small"
enable_monitoring = true
# production.tfvars
environment = "prod"
instance_type = "t3.medium"
instance_count = 5
db_instance_class = "db.t3.medium"
enable_monitoring = true
backup_retention = 30
# 환경별 배포 스크립트
#!/bin/bash
ENVIRONMENT=$1
if [ -z "$ENVIRONMENT" ]; then
echo "Usage: $0 <environment>"
exit 1
fi
# 워크스페이스 선택
terraform workspace select $ENVIRONMENT || terraform workspace new $ENVIRONMENT
# 계획 실행
terraform plan -var-file="${ENVIRONMENT}.tfvars" -out="${ENVIRONMENT}.tfplan"
# 사용자 확인 후 적용
read -p "Apply changes for $ENVIRONMENT? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
terraform apply "${ENVIRONMENT}.tfplan"
fi
클라우드별 실전 예제
AWS 3-Tier 아키텍처
# AWS 3-Tier Architecture
# variables.tf
variable "project_name" {
description = "프로젝트 이름"
type = string
default = "webapp"
}
variable "environment" {
description = "환경 (dev/staging/prod)"
type = string
}
variable "vpc_cidr" {
description = "VPC CIDR 블록"
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
description = "가용 영역 목록"
type = list(string)
default = ["us-west-2a", "us-west-2b"]
}
# main.tf
# VPC 및 네트워킹
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
Environment = var.environment
}
}
# Public Subnets (Web Tier)
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-${count.index + 1}"
Tier = "Web"
}
}
# Private Subnets (App Tier)
resource "aws_subnet" "private_app" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 10}.0/24"
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-private-app-${count.index + 1}"
Tier = "Application"
}
}
# Private Subnets (DB Tier)
resource "aws_subnet" "private_db" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 20}.0/24"
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-private-db-${count.index + 1}"
Tier = "Database"
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
}
}
# NAT Gateway
resource "aws_eip" "nat" {
count = length(var.availability_zones)
domain = "vpc"
tags = {
Name = "${var.project_name}-nat-eip-${count.index + 1}"
}
}
resource "aws_nat_gateway" "main" {
count = length(var.availability_zones)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.project_name}-nat-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
# Route Tables
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_name}-public-rt"
}
}
resource "aws_route_table" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.project_name}-private-rt-${count.index + 1}"
}
}
# Route Table Associations
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private_app" {
count = length(aws_subnet.private_app)
subnet_id = aws_subnet.private_app[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
resource "aws_route_table_association" "private_db" {
count = length(aws_subnet.private_db)
subnet_id = aws_subnet.private_db[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
# Security Groups
resource "aws_security_group" "web" {
name_prefix = "${var.project_name}-web-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
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"]
}
tags = {
Name = "${var.project_name}-web-sg"
Tier = "Web"
}
}
resource "aws_security_group" "app" {
name_prefix = "${var.project_name}-app-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-app-sg"
Tier = "Application"
}
}
resource "aws_security_group" "db" {
name_prefix = "${var.project_name}-db-"
vpc_id = aws_vpc.main.id
ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
tags = {
Name = "${var.project_name}-db-sg"
Tier = "Database"
}
}
# Application Load Balancer
resource "aws_lb" "main" {
name = "${var.project_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.web.id]
subnets = aws_subnet.public[*].id
tags = {
Environment = var.environment
}
}
# Target Group
resource "aws_lb_target_group" "app" {
name = "${var.project_name}-tg"
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 5
interval = 30
path = "/health"
matcher = "200"
}
}
# ALB Listener
resource "aws_lb_listener" "main" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# Auto Scaling Group
resource "aws_launch_template" "app" {
name_prefix = "${var.project_name}-"
image_id = data.aws_ami.ubuntu.id
instance_type = var.environment == "prod" ? "t3.medium" : "t3.small"
vpc_security_group_ids = [aws_security_group.app.id]
user_data = base64encode(templatefile("user-data.sh", {
db_endpoint = aws_db_instance.main.endpoint
}))
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.project_name}-app"
Environment = var.environment
}
}
}
resource "aws_autoscaling_group" "app" {
name = "${var.project_name}-asg"
vpc_zone_identifier = aws_subnet.private_app[*].id
target_group_arns = [aws_lb_target_group.app.arn]
health_check_type = "ELB"
min_size = var.environment == "prod" ? 2 : 1
max_size = var.environment == "prod" ? 10 : 3
desired_capacity = var.environment == "prod" ? 3 : 1
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.project_name}-asg"
propagate_at_launch = false
}
}
# RDS Database
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db-subnet-group"
subnet_ids = aws_subnet.private_db[*].id
tags = {
Name = "${var.project_name} DB subnet group"
}
}
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-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
max_allocated_storage = var.environment == "prod" ? 1000 : 100
db_name = "webapp"
username = "admin"
password = var.db_password # 변수로 전달
vpc_security_group_ids = [aws_security_group.db.id]
db_subnet_group_name = aws_db_subnet_group.main.name
backup_retention_period = var.environment == "prod" ? 30 : 7
backup_window = "03:00-04:00"
maintenance_window = "sun:04:00-sun:05:00"
skip_final_snapshot = var.environment != "prod"
tags = {
Name = "${var.project_name}-database"
Environment = var.environment
}
}
Azure 웹 애플리케이션
# Azure Web Application
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}
# Resource Group
resource "azurerm_resource_group" "main" {
name = "rg-${var.project_name}-${var.environment}"
location = var.location
tags = {
Environment = var.environment
Project = var.project_name
}
}
# Virtual Network
resource "azurerm_virtual_network" "main" {
name = "vnet-${var.project_name}"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tags = {
Environment = var.environment
}
}
# Subnets
resource "azurerm_subnet" "web" {
name = "snet-web"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/24"]
}
resource "azurerm_subnet" "app" {
name = "snet-app"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.2.0/24"]
}
resource "azurerm_subnet" "db" {
name = "snet-db"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.3.0/24"]
delegation {
name = "Microsoft.DBforPostgreSQL/flexibleServers"
service_delegation {
name = "Microsoft.DBforPostgreSQL/flexibleServers"
}
}
}
# Network Security Groups
resource "azurerm_network_security_group" "web" {
name = "nsg-web"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "HTTP"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "HTTPS"
priority = 1002
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# App Service Plan
resource "azurerm_service_plan" "main" {
name = "asp-${var.project_name}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
os_type = "Linux"
sku_name = var.environment == "prod" ? "P1v2" : "B1"
}
# App Service
resource "azurerm_linux_web_app" "main" {
name = "app-${var.project_name}-${var.environment}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
service_plan_id = azurerm_service_plan.main.id
site_config {
application_stack {
node_version = "18-lts"
}
}
app_settings = {
"WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
"DATABASE_URL" = "postgresql://${var.db_username}:${var.db_password}@${azurerm_postgresql_flexible_server.main.fqdn}:5432/${var.db_name}"
}
tags = {
Environment = var.environment
}
}
# PostgreSQL Database
resource "azurerm_postgresql_flexible_server" "main" {
name = "psql-${var.project_name}-${var.environment}"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
version = "13"
delegated_subnet_id = azurerm_subnet.db.id
private_dns_zone_id = azurerm_private_dns_zone.db.id
administrator_login = var.db_username
administrator_password = var.db_password
zone = "1"
storage_mb = var.environment == "prod" ? 32768 : 32768
sku_name = var.environment == "prod" ? "GP_Standard_D2s_v3" : "B_Standard_B1ms"
depends_on = [azurerm_private_dns_zone_virtual_network_link.db]
tags = {
Environment = var.environment
}
}
resource "azurerm_postgresql_flexible_server_database" "main" {
name = var.db_name
server_id = azurerm_postgresql_flexible_server.main.id
collation = "en_US.utf8"
charset = "utf8"
}
# Private DNS Zone for Database
resource "azurerm_private_dns_zone" "db" {
name = "${var.project_name}.postgres.database.azure.com"
resource_group_name = azurerm_resource_group.main.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "db" {
name = "pdnsz-link-db"
private_dns_zone_name = azurerm_private_dns_zone.db.name
virtual_network_id = azurerm_virtual_network.main.id
resource_group_name = azurerm_resource_group.main.name
}
GCP 마이크로서비스 아키텍처
# GCP Microservices Architecture
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# VPC Network
resource "google_compute_network" "main" {
name = "${var.project_name}-vpc"
auto_create_subnetworks = false
routing_mode = "GLOBAL"
}
# Subnets
resource "google_compute_subnetwork" "gke" {
name = "${var.project_name}-gke-subnet"
ip_cidr_range = "10.0.0.0/16"
region = var.region
network = google_compute_network.main.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "192.168.0.0/18"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "192.168.64.0/18"
}
}
# GKE Cluster
resource "google_container_cluster" "primary" {
name = "${var.project_name}-gke"
location = var.region
# Remove default node pool
remove_default_node_pool = true
initial_node_count = 1
network = google_compute_network.main.name
subnetwork = google_compute_subnetwork.gke.name
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
# Enable Workload Identity
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
# Enable network policy
network_policy {
enabled = true
}
# Enable binary authorization
enable_binary_authorization = true
# Enable private nodes
private_cluster_config {
enable_private_nodes = true
enable_private_endpoint = false
master_ipv4_cidr_block = "172.16.0.0/28"
}
master_auth {
client_certificate_config {
issue_client_certificate = false
}
}
}
# GKE Node Pool
resource "google_container_node_pool" "primary_nodes" {
name = "${google_container_cluster.primary.name}-node-pool"
location = var.region
cluster = google_container_cluster.primary.name
node_count = 1
autoscaling {
min_node_count = 1
max_node_count = 5
}
node_config {
preemptible = var.environment == "dev"
machine_type = var.environment == "prod" ? "e2-standard-2" : "e2-medium"
service_account = google_service_account.gke.email
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform"
]
labels = {
environment = var.environment
}
tags = ["gke-node", "${var.project_name}-gke"]
}
management {
auto_repair = true
auto_upgrade = true
}
}
# Service Account for GKE
resource "google_service_account" "gke" {
account_id = "${var.project_name}-gke"
display_name = "GKE Service Account"
}
# Cloud SQL (PostgreSQL)
resource "google_sql_database_instance" "main" {
name = "${var.project_name}-db"
database_version = "POSTGRES_13"
region = var.region
settings {
tier = var.environment == "prod" ? "db-standard-2" : "db-f1-micro"
disk_size = var.environment == "prod" ? 100 : 20
disk_type = "PD_SSD"
backup_configuration {
enabled = true
start_time = "03:00"
location = var.region
point_in_time_recovery_enabled = var.environment == "prod"
}
ip_configuration {
ipv4_enabled = false
private_network = google_compute_network.main.id
}
database_flags {
name = "log_checkpoints"
value = "on"
}
}
deletion_protection = var.environment == "prod"
depends_on = [google_service_networking_connection.private_vpc_connection]
}
resource "google_sql_database" "database" {
name = var.db_name
instance = google_sql_database_instance.main.name
}
resource "google_sql_user" "user" {
name = var.db_username
instance = google_sql_database_instance.main.name
password = var.db_password
}
# Private Service Connection
resource "google_compute_global_address" "private_ip_address" {
name = "${var.project_name}-private-ip"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.main.id
}
resource "google_service_networking_connection" "private_vpc_connection" {
network = google_compute_network.main.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
}
# Cloud Pub/Sub Topic
resource "google_pubsub_topic" "events" {
name = "${var.project_name}-events"
labels = {
environment = var.environment
}
}
# Cloud Storage Bucket
resource "google_storage_bucket" "assets" {
name = "${var.project_name}-assets-${random_string.bucket_suffix.result}"
location = var.region
uniform_bucket_level_access = true
versioning {
enabled = var.environment == "prod"
}
lifecycle_rule {
condition {
age = 365
}
action {
type = "Delete"
}
}
labels = {
environment = var.environment
}
}
resource "random_string" "bucket_suffix" {
length = 8
special = false
upper = false
}
# IAM
resource "google_project_iam_member" "gke_sql" {
project = var.project_id
role = "roles/cloudsql.client"
member = "serviceAccount:${google_service_account.gke.email}"
}
resource "google_project_iam_member" "gke_storage" {
project = var.project_id
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.gke.email}"
}
CI/CD 통합
GitLab CI/CD 파이프라인
# .gitlab-ci.yml
variables:
TF_ROOT: ${CI_PROJECT_DIR}/infrastructure
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/production
cache:
key: production
paths:
- ${TF_ROOT}/.terraform
before_script:
- cd ${TF_ROOT}
- terraform --version
- terraform init -backend-config="address=${TF_ADDRESS}" -backend-config="lock_address=${TF_ADDRESS}/lock" -backend-config="unlock_address=${TF_ADDRESS}/lock" -backend-config="username=gitlab-ci-token" -backend-config="password=${CI_JOB_TOKEN}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5"
stages:
- validate
- plan
- apply
validate:
stage: validate
script:
- terraform fmt -check
- terraform validate
plan:
stage: plan
script:
- terraform plan -out="planfile"
dependencies:
- validate
artifacts:
name: plan
paths:
- ${TF_ROOT}/planfile
reports:
terraform: ${TF_ROOT}/planfile.json
only:
- merge_requests
- main
apply:
stage: apply
script:
- terraform apply -input=false "planfile"
dependencies:
- plan
when: manual
only:
- main
GitHub Actions 워크플로우
# .github/workflows/terraform.yml
name: 'Terraform'
on:
push:
branches: [ main ]
pull_request:
permissions:
contents: read
pull-requests: write
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
environment: production
defaults:
run:
shell: bash
working-directory: ./infrastructure
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.6.0
terraform_wrapper: false
- 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: us-west-2
- name: Terraform Format
id: fmt
run: terraform fmt -check
- name: Terraform Init
id: init
run: terraform init
- name: Terraform Validate
id: validate
run: terraform validate -no-color
- name: Terraform Plan
id: plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color -input=false
continue-on-error: true
- name: Update Pull Request
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve -input=false
Azure DevOps 파이프라인
# azure-pipelines.yml
trigger:
branches:
include:
- main
paths:
include:
- infrastructure/*
variables:
- group: terraform-variables
- name: terraformWorkingDirectory
value: '$(System.DefaultWorkingDirectory)/infrastructure'
stages:
- stage: Validate
displayName: 'Validate Terraform'
jobs:
- job: Validate
displayName: 'Validate'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: TerraformInstaller@0
displayName: 'Install Terraform'
inputs:
terraformVersion: '1.6.0'
- task: TerraformTaskV4@4
displayName: 'Terraform Init'
inputs:
provider: 'azurerm'
command: 'init'
workingDirectory: $(terraformWorkingDirectory)
backendServiceArm: 'Azure Service Connection'
backendAzureRmResourceGroupName: 'rg-terraform-state'
backendAzureRmStorageAccountName: 'stterraformstate12345'
backendAzureRmContainerName: 'tfstate'
backendAzureRmKey: 'prod.terraform.tfstate'
- task: TerraformTaskV4@4
displayName: 'Terraform Format Check'
inputs:
provider: 'azurerm'
command: 'custom'
workingDirectory: $(terraformWorkingDirectory)
customCommand: 'fmt'
commandOptions: '-check -diff'
- task: TerraformTaskV4@4
displayName: 'Terraform Validate'
inputs:
provider: 'azurerm'
command: 'validate'
workingDirectory: $(terraformWorkingDirectory)
- stage: Plan
displayName: 'Plan Terraform'
dependsOn: Validate
condition: succeeded()
jobs:
- job: Plan
displayName: 'Plan'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: TerraformTaskV4@4
displayName: 'Terraform Plan'
inputs:
provider: 'azurerm'
command: 'plan'
workingDirectory: $(terraformWorkingDirectory)
environmentServiceNameAzureRM: 'Azure Service Connection'
commandOptions: '-out=tfplan'
- task: PublishPipelineArtifact@1
displayName: 'Publish Plan'
inputs:
targetPath: '$(terraformWorkingDirectory)/tfplan'
artifact: 'tfplan'
- stage: Apply
displayName: 'Apply Terraform'
dependsOn: Plan
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: Apply
displayName: 'Apply'
environment: 'production'
pool:
vmImage: 'ubuntu-latest'
strategy:
runOnce:
deploy:
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download Plan'
inputs:
artifactName: 'tfplan'
targetPath: $(terraformWorkingDirectory)
- task: TerraformTaskV4@4
displayName: 'Terraform Apply'
inputs:
provider: 'azurerm'
command: 'apply'
workingDirectory: $(terraformWorkingDirectory)
environmentServiceNameAzureRM: 'Azure Service Connection'
commandOptions: 'tfplan'
Terraform Cloud 통합
# Terraform Cloud 설정
terraform {
cloud {
organization = "my-organization"
workspaces {
tags = ["production", "web-app"]
}
}
}
# 또는 특정 워크스페이스
terraform {
cloud {
organization = "my-organization"
workspaces {
name = "web-app-production"
}
}
}
보안과 베스트 프랙티스
시크릿 관리
# AWS Secrets Manager 사용
data "aws_secretsmanager_secret" "db_credentials" {
name = "prod/database/credentials"
}
data "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = data.aws_secretsmanager_secret.db_credentials.id
}
locals {
db_creds = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string)
}
resource "aws_db_instance" "main" {
identifier = "myapp-db"
engine = "postgres"
engine_version = "13.7"
instance_class = "db.t3.micro"
allocated_storage = 20
db_name = "myapp"
username = local.db_creds.username
password = local.db_creds.password
skip_final_snapshot = true
}
# Azure Key Vault 사용
data "azurerm_key_vault" "main" {
name = var.key_vault_name
resource_group_name = var.key_vault_resource_group
}
data "azurerm_key_vault_secret" "db_password" {
name = "database-password"
key_vault_id = data.azurerm_key_vault.main.id
}
# 환경 변수 사용 (민감한 값)
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
# terraform.tfvars에서 제외하고 환경 변수로 설정
# export TF_VAR_db_password="super-secret-password"
리소스 태깅 전략
# 공통 태그 정의
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
Owner = var.team_name
CostCenter = var.cost_center
CreatedBy = "terraform"
CreatedDate = formatdate("YYYY-MM-DD", timestamp())
}
# 보안 관련 태그
security_tags = var.environment == "prod" ? {
DataClassification = "confidential"
Compliance = "required"
BackupRequired = "yes"
} : {}
# 모든 태그 결합
all_tags = merge(local.common_tags, local.security_tags, var.additional_tags)
}
# 태그 적용
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = merge(local.all_tags, {
Name = "${var.project_name}-web-${count.index + 1}"
Role = "web-server"
})
}
# 태그 검증
resource "aws_instance" "web" {
# ... 다른 설정들
tags = local.all_tags
lifecycle {
# 태그가 없으면 생성 실패
precondition {
condition = length(local.all_tags) > 0
error_message = "All resources must have tags."
}
}
}
정책 기반 보안
# OPA (Open Policy Agent) 정책 검증
data "external" "policy_check" {
program = ["opa", "eval", "-d", "policies/", "-i", "terraform.json", "data.terraform.deny[x]"]
}
# Sentinel 정책 (Terraform Cloud/Enterprise)
# sentinel.hcl
policy "restrict-instance-type" {
source = "./restrict-instance-type.sentinel"
enforcement_level = "hard-mandatory"
}
# restrict-instance-type.sentinel
import "tfplan/v2" as tfplan
allowed_instance_types = ["t2.micro", "t2.small", "t3.micro", "t3.small"]
main = rule {
all tfplan.resource_changes as _, rc {
rc.type is "aws_instance" and
rc.mode is "managed" and
(rc.change.actions contains "create" or rc.change.actions contains "update") implies
rc.change.after.instance_type in allowed_instance_types
}
}
코드 품질 관리
# .terraform.lock.hcl 파일 버전 관리
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.0"
}
# 변수 검증
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be one of: dev, staging, prod."
}
}
variable "instance_type" {
description = "EC2 instance type"
type = string
validation {
condition = can(regex("^[tm][2-9]\\.", var.instance_type))
error_message = "Instance type must be t2/t3/m4/m5 family."
}
}
# 리소스 이름 규칙
locals {
# 이름 규칙: {project}-{environment}-{service}-{suffix}
name_prefix = "${var.project_name}-${var.environment}"
# 이름 검증
valid_name = can(regex("^[a-z][a-z0-9-]{2,62}[a-z0-9]$", local.name_prefix))
}
resource "aws_s3_bucket" "example" {
bucket = "${local.name_prefix}-data"
lifecycle {
precondition {
condition = local.valid_name
error_message = "Resource names must follow naming convention."
}
}
}
드리프트 감지
#!/bin/bash
# drift-detection.sh
# Terraform 상태와 실제 인프라 비교
terraform plan -detailed-exitcode
EXIT_CODE=$?
if [ $EXIT_CODE -eq 1 ]; then
echo "ERROR: Terraform plan failed"
exit 1
elif [ $EXIT_CODE -eq 2 ]; then
echo "WARNING: Infrastructure drift detected"
terraform show -no-color > drift-report.txt
# Slack 알림
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Infrastructure drift detected in '"$ENV"' environment"}' \
$SLACK_WEBHOOK_URL
exit 2
else
echo "INFO: No infrastructure drift detected"
exit 0
fi
문제 해결 가이드
일반적인 오류 해결
1. Provider 설정 오류
# 오류: Provider configuration not present
Error: Provider configuration not present
To work with aws_instance.example its original provider configuration
at provider["registry.terraform.io/hashicorp/aws"] is required, but it
has been removed. This occurs when a provider configuration is removed
while objects created by that provider still exist in the state.
# 해결책: Provider 재설정
terraform init -upgrade
terraform providers
2. 상태 파일 잠금 오류
# 오류: State locked
Error: Error locking state: Error acquiring the state lock
# 해결책: 잠금 강제 해제 (주의!)
terraform force-unlock <LOCK_ID>
# 더 안전한 해결책: 잠금 대기 시간 설정
terraform apply -lock-timeout=300s
3. 리소스 충돌 오류
# 오류: Resource already exists
Error: Error creating S3 bucket: BucketAlreadyExists
# 해결책 1: 기존 리소스 import
terraform import aws_s3_bucket.example existing-bucket-name
# 해결책 2: 리소스 이름 변경
resource "aws_s3_bucket" "example" {
bucket = "my-unique-bucket-${random_string.suffix.result}"
}
resource "random_string" "suffix" {
length = 8
special = false
upper = false
}
4. 버전 호환성 문제
# terraform versions.tf
terraform {
required_version = ">= 1.0, < 2.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# 버전 업그레이드
terraform init -upgrade
디버깅 도구
# 로그 레벨 설정
export TF_LOG=DEBUG
export TF_LOG_PATH="./terraform.log"
# 특정 리소스만 계획/적용
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.web
# 상태 파일 검사
terraform state list
terraform state show aws_instance.web
# 구성 검증
terraform validate
terraform fmt -check -diff
# 그래프 생성 (시각화)
terraform graph | dot -Tpng > graph.png
성능 최적화
# 병렬 처리 설정
terraform apply -parallelism=20
# 리소스 타겟팅
terraform apply -target=module.database
# 부분 새로고침
terraform apply -refresh=false
# 계획 파일 사용
terraform plan -out=tfplan
terraform apply tfplan
재해 복구
# 상태 파일 백업
terraform state pull > backup-$(date +%Y%m%d-%H%M%S).tfstate
# 상태 파일 복원
terraform state push backup.tfstate
# 리소스 재생성
terraform taint aws_instance.web
terraform apply
# 전체 상태 재구축
terraform refresh
결론
Terraform은 현대 인프라 관리의 필수 도구입니다. Infrastructure as Code의 개념을 통해 인프라의 버전 관리, 자동화, 일관성을 실현할 수 있습니다.
Terraform 마스터 로드맵
- 기초: HCL 언어, Provider/Resource 이해
- 중급: 모듈 시스템, 상태 관리, 워크스페이스
- 고급: 함수/표현식, 동적 블록, 복잡한 구성
- 전문가: CI/CD 통합, 정책 관리, 멀티 클라우드 전략
실무 적용 단계
- 학습 환경: 로컬에서 단순한 리소스 생성
- 개발 환경: 기본적인 3-tier 아키텍처 구축
- 스테이징: 프로덕션과 유사한 환경 자동화
- 프로덕션: 완전 자동화된 인프라 관리
성공을 위한 핵심 원칙
- 점진적 도입: 작은 단위부터 시작하여 확장
- 모듈화: 재사용 가능한 모듈 설계
- 상태 관리: 원격 상태 저장소 필수 사용
- 보안 우선: 시크릿 관리와 접근 제어
- 문서화: 코드 자체가 문서가 되도록 작성
지속적 학습 리소스
- 📚 공식 문서: https://terraform.io/docs
- 🎓 HashiCorp Learn: https://learn.hashicorp.com/terraform
- 📖 추천 도서: "Terraform: Up & Running"
- 🏆 인증: HashiCorp Certified: Terraform Associate
- 👥 커뮤니티: Terraform Community Forum
마지막 조언
- 실습 중심: 실제 클라우드 환경에서 실습
- 베스트 프랙티스: 처음부터 올바른 패턴 학습
- 팀 협업: 모듈과 표준 정립
- 지속적 개선: 정기적인 코드 리뷰와 리팩토링
- 보안 의식: 항상 보안을 최우선으로 고려
Terraform을 마스터하고 Infrastructure as Code의 힘을 경험해보세요! 🚀
'DevOps' 카테고리의 다른 글
| Kubernetes 완전 정복 가이드: 컨테이너 오케스트레이션부터 프로덕션까지 (0) | 2025.07.23 |
|---|---|
| Docker 완전 마스터 가이드: 초보자부터 프로덕션까지 컨테이너 기술 정복 (1) | 2025.07.23 |
| Git 완전 정복 가이드: 초보자부터 고급 사용자까지 버전 관리 마스터하기 (6) | 2025.07.23 |
| Linux OS 완전 가이드: 초보자부터 고급 사용자까지 (0) | 2025.07.23 |