Skip to content
Published on

Terraform & Ansible 명령어 완벽 가이드: IaC와 Configuration Management의 모든 것

Authors
  • Name
    Twitter

1. 서론: Infrastructure as Code의 시대

1.1 왜 IaC와 Configuration Management인가?

클라우드 네이티브 시대에 인프라를 수동으로 관리하는 것은 더 이상 선택지가 아니다. 수백 대의 서버, 수십 개의 VPC, 복잡하게 얽힌 보안 그룹과 IAM 정책을 콘솔에서 클릭으로 관리한다면, 그것은 "Snowflake Server" — 눈송이처럼 모두 제각각인 서버 — 를 만드는 지름길이다. 재현 불가능하고, 감사 추적이 어렵고, 한 번의 실수가 전체 인프라를 무너뜨릴 수 있다.

Infrastructure as Code(IaC)와 Configuration Management(CM)는 이 문제에 대한 업계의 답이다.

  • IaC(Infrastructure as Code): 인프라 자체를 코드로 선언하고, 버전 관리하고, 리뷰하고, 자동으로 프로비저닝한다. 대표 도구: Terraform, Pulumi, AWS CloudFormation, OpenTofu
  • CM(Configuration Management): 프로비저닝된 인프라 위에 소프트웨어를 설치하고, 설정을 통일하고, 상태를 유지한다. 대표 도구: Ansible, Chef, Puppet, SaltStack

이 두 영역의 사실상 표준(de facto standard)이 바로 TerraformAnsible이다.

1.2 Terraform과 Ansible의 역할 분담

Terraform과 Ansible은 경쟁 도구가 아니라 상호 보완 도구다. 각각의 책임 영역이 명확히 다르다.

┌──────────────────────────────────────────────────────────────────┐
IaC + CM 워크플로우                            │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────────────┐       ┌─────────────────────────┐      │
│  │     Terraform        │       │       Ansible            │      │
  (Provisioning)      │──────▶│  (Configuration)         │      │
│  │                      │       │                          │      │
│  │  - VPC/Subnet 생성   │       │  - 패키지 설치            │      │
│  │  - EC2/RDS 생성      │       │  - Nginx/Apache 설정     │      │
│  │  - S3 버킷 생성      │       │  - 애플리케이션 배포      │      │
│  │  - IAM Role 생성     │       │  - 보안 하드닝            │      │
│  │  - Security Group    │       │  - 모니터링 에이전트 설치  │      │
│  └─────────────────────┘       └─────────────────────────┘      │
│                                                                  │
"인프라를 만든다"              "인프라를 구성한다"Declarative (선언적)          Procedural + DeclarativeState 기반                    Agentless (SSH/WinRM)└──────────────────────────────────────────────────────────────────┘

1.3 이 글의 구성

이 글은 크게 세 파트로 구성된다.

  1. Part 1 — Terraform: HCL 문법부터 핵심 워크플로우, State 관리, Workspace, 모듈, 고급 기능까지
  2. Part 2 — Ansible: 인벤토리, Ad-hoc 명령, Playbook, Role, Vault, Galaxy까지
  3. Part 3 — 통합과 치트시트: Terraform + Ansible 연동, 명령어 치트시트, 트러블슈팅

Part 1: Terraform 완벽 가이드


2. Terraform 소개와 아키텍처

2.1 Terraform이란?

Terraform은 HashiCorp가 2014년에 발표한 오픈소스 IaC 도구다. HCL(HashiCorp Configuration Language)이라는 선언적 언어로 인프라를 정의하면, Terraform이 현재 상태(State)와 원하는 상태(Configuration)를 비교하여 차이만큼만 변경을 적용한다.

2024년 8월, HashiCorp는 IBM에 인수되었고, Terraform의 라이선스는 BSL(Business Source License)로 변경되었다. 이에 대한 커뮤니티의 대응으로 OpenTofu(Linux Foundation 산하)가 탄생했다. 이 글에서 다루는 대부분의 명령어는 OpenTofu에서도 동일하게 동작한다.

2.2 Terraform 아키텍처

Terraform의 핵심 아키텍처는 Core + Providers + State 세 요소로 구성된다.

┌──────────────────────────────────────────────────────────────┐
Terraform Architecture├──────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌──────────────┐                                            │
│  │  .tf FilesHCL Configuration  (desired     │                                           │
│  │   state)      │                                           │
│  └──────┬───────┘                                            │
│         │                                                    │
│         ▼                                                    │
│  ┌──────────────────────────────────────────┐                │
│  │           Terraform Core                  │                │
│  │                                           │                │
│  │  ┌───────────┐    ┌──────────────┐       │                │
│  │  │ Resource   │    │  Dependency   │       │                │
│  │  │ Graph      │    │  Resolution   │       │                │
│  │  └───────────┘    └──────────────┘       │                │
│  │                                           │                │
│  │  ┌───────────┐    ┌──────────────┐       │                │
│  │  │ Plan      │    │  Apply        │       │                │
│  │  │ Engine    │    │  Engine       │       │                │
│  │  └───────────┘    └──────────────┘       │                │
│  └─────────┬───────────────┬────────────────┘                │
│            │               │                                  │
│     ┌──────▼──────┐ ┌─────▼──────────┐                      │
│     │  Providers   │ │  State File     │                      │
│     │              │ │                 │                      │
│     │ - AWS        │ │ terraform.tfstate││     │ - Azure (JSON)          │                      │
│     │ - GCP        │ │                 │                      │
│     │ - Kubernetes │ │ Local / Remote  │                      │
│     │ - 3000+ (S3, GCS, etc.) │                      │
│     └──────────────┘ └─────────────────┘                     │
│                                                               │
└──────────────────────────────────────────────────────────────┘
  • Terraform Core: HCL 파싱, 의존성 그래프 구성, Plan/Apply 엔진
  • Providers: 각 클라우드/서비스의 API를 추상화하는 플러그인 (AWS, Azure, GCP, Kubernetes 등 3,000개 이상)
  • State: 현재 인프라의 상태를 추적하는 JSON 파일

2.3 Terraform vs OpenTofu vs Pulumi

항목TerraformOpenTofuPulumi
라이선스BSL 1.1MPL 2.0 (OSS)Apache 2.0
언어HCLHCLPython/Go/TS/C#
상태 관리terraform.tfstateterraform.tfstatePulumi Cloud
Provider 생태계3,000+Terraform 호환100+
운영 주체HashiCorp(IBM)Linux FoundationPulumi Inc.
CLI 명령어terraformtofupulumi

3. Terraform 설치 및 환경 설정

3.1 tfenv를 이용한 버전 관리

프로젝트마다 다른 Terraform 버전을 사용해야 하는 경우가 많으므로, tfenv(Terraform Version Manager)를 사용하는 것을 강력히 권장한다.

# macOS (Homebrew)
brew install tfenv

# Linux (Git Clone)
git clone https://github.com/tfutils/tfenv.git ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc

# 사용 가능한 버전 목록 확인
tfenv list-remote

# 특정 버전 설치
tfenv install 1.9.8
tfenv install 1.10.3

# 글로벌 기본 버전 설정
tfenv use 1.10.3

# 프로젝트별 버전 고정 (.terraform-version 파일)
echo "1.9.8" > .terraform-version

# 설치된 버전 목록
tfenv list

# 현재 버전 확인
terraform version

3.2 직접 설치

# macOS (Homebrew)
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Linux (APT)
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /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

# 버전 확인
terraform version
# Terraform v1.10.3
# on darwin_arm64

3.3 자동완성 및 에디터 설정

# Bash/Zsh 자동완성
terraform -install-autocomplete

# VS Code 확장 프로그램
# - HashiCorp Terraform (공식)
# - Terraform Autocomplete

4. Terraform 핵심 워크플로우

Terraform의 핵심 워크플로우는 Write → Plan → Apply 세 단계다. 이 세 단계를 지원하는 명령어들을 상세히 살펴보자.

4.1 terraform init — 프로젝트 초기화

terraform init은 Terraform 프로젝트의 첫 번째 명령어다. Provider 플러그인 다운로드, 모듈 다운로드, Backend 초기화를 수행한다.

# 기본 초기화
terraform init

# 주요 플래그
terraform init -upgrade              # Provider/모듈을 최신 허용 버전으로 업그레이드
terraform init -reconfigure           # Backend 설정 재구성 (기존 state 무시)
terraform init -migrate-state         # Backend 변경 시 state 마이그레이션
terraform init -backend=false         # Backend 초기화 건너뛰기 (검증 용도)
terraform init -get=false             # 모듈 다운로드 건너뛰기
terraform init -input=false           # 대화형 입력 비활성화 (CI/CD용)
terraform init -no-color              # 컬러 출력 비활성화 (로그 파싱용)
terraform init -lockfile=readonly     # .terraform.lock.hcl 수정 금지 (CI용)

# Backend 설정을 CLI에서 전달 (CI/CD에서 유용)
terraform init \
  -backend-config="bucket=my-tf-state" \
  -backend-config="key=prod/terraform.tfstate" \
  -backend-config="region=ap-northeast-2" \
  -backend-config="dynamodb_table=tf-lock"

terraform init 실행 후 생성되는 파일/디렉토리:

.terraform/              # Provider 플러그인, 모듈 캐시
.terraform.lock.hcl      # Provider 버전 잠금 파일 (커밋 대상)

4.2 terraform validate — 구문 검증

# 구문 유효성 검사 (init 후 사용 가능)
terraform validate

# JSON 출력 (CI/CD 파이프라인용)
terraform validate -json

# 출력 예시 (성공)
# Success! The configuration is valid.

# 출력 예시 (실패, JSON)
# {
#   "valid": false,
#   "error_count": 1,
#   "diagnostics": [
#     {
#       "severity": "error",
#       "summary": "Unsupported argument",
#       "detail": "An argument named \"vps_id\" is not expected here. Did you mean \"vpc_id\"?"
#     }
#   ]
# }

4.3 terraform fmt — 코드 포매팅

# 현재 디렉토리의 .tf 파일 포매팅
terraform fmt

# 재귀적으로 모든 하위 디렉토리 포매팅
terraform fmt -recursive

# 변경이 필요한 파일만 표시 (CI에서 체크용)
terraform fmt -check

# diff 출력
terraform fmt -diff

# CI/CD 파이프라인에서의 활용
terraform fmt -check -recursive -diff
# 종료 코드 0: 포매팅 변경 없음
# 종료 코드 3: 포매팅 변경 필요

4.4 terraform plan — 실행 계획

terraform plan은 Terraform의 가장 중요한 명령어 중 하나다. 현재 State와 Configuration을 비교하여 어떤 변경이 일어날지 미리 보여준다. 실제 인프라에는 아무런 변경도 가하지 않는다.

# 기본 Plan
terraform plan

# 주요 플래그
terraform plan -out=tfplan              # Plan을 파일로 저장 (apply에서 사용)
terraform plan -destroy                 # 삭제 계획 확인
terraform plan -target=aws_instance.web # 특정 리소스만 Plan
terraform plan -var="instance_type=t3.large"  # 변수 전달
terraform plan -var-file="prod.tfvars"  # 변수 파일 지정
terraform plan -refresh=false           # State 새로고침 건너뛰기 (속도 향상)
terraform plan -parallelism=20          # 동시 작업 수 (기본값: 10)
terraform plan -compact-warnings        # 경고 메시지 축약
terraform plan -no-color                # 컬러 출력 비활성화
terraform plan -input=false             # 대화형 입력 비활성화
terraform plan -json                    # JSON 출력 (자동화용)
terraform plan -detailed-exitcode       # 상세 종료 코드
# 종료 코드 0: 변경 없음
# 종료 코드 1: 오류 발생
# 종료 코드 2: 변경 존재

# CI/CD 파이프라인 권장 패턴
terraform plan -out=tfplan -input=false -no-color -detailed-exitcode

Plan 출력의 기호 해석:

# + create    (리소스 생성)
# - destroy   (리소스 삭제)
# ~ update    (리소스 수정, in-place)
# -/+ replace (리소스 삭제 후 재생성)
# <= read     (데이터 소스 읽기)

4.5 terraform apply — 변경 적용

# 기본 Apply (Plan 후 확인 프롬프트)
terraform apply

# 저장된 Plan 파일로 Apply (확인 프롬프트 없음)
terraform apply tfplan

# 자동 승인 (CI/CD용, 주의 필요!)
terraform apply -auto-approve

# 주요 플래그
terraform apply -target=aws_instance.web      # 특정 리소스만 적용
terraform apply -var="instance_type=t3.large"  # 변수 전달
terraform apply -var-file="prod.tfvars"        # 변수 파일 지정
terraform apply -parallelism=20                # 동시 작업 수
terraform apply -refresh=false                 # State 새로고침 건너뛰기
terraform apply -replace=aws_instance.web      # 리소스 강제 재생성 (taint 대체)
terraform apply -lock=false                    # State 잠금 비활성화 (비권장)
terraform apply -lock-timeout=5m               # State 잠금 대기 시간

# 안전한 CI/CD 패턴 (Plan → Save → Apply)
terraform plan -out=tfplan -input=false
# ... 리뷰 ...
terraform apply tfplan

4.6 terraform destroy — 인프라 삭제

# 모든 리소스 삭제 (확인 프롬프트)
terraform destroy

# 자동 승인
terraform destroy -auto-approve

# 특정 리소스만 삭제
terraform destroy -target=aws_instance.web

# 변수 파일 지정
terraform destroy -var-file="prod.tfvars"

# 삭제 Plan 미리 확인
terraform plan -destroy

4.7 terraform output — 출력 값 조회

# 모든 Output 값 표시
terraform output

# 특정 Output 값
terraform output vpc_id

# Raw 값 (따옴표 없이, 스크립트용)
terraform output -raw vpc_id

# JSON 출력
terraform output -json

# 다른 Terraform 프로젝트나 Ansible에서 활용
VPC_ID=$(terraform output -raw vpc_id)
echo "VPC ID: $VPC_ID"

5. Terraform State 관리

5.1 State란 무엇인가?

Terraform State(terraform.tfstate)는 Terraform이 관리하는 인프라의 현재 상태를 기록한 JSON 파일이다. Terraform은 이 파일을 통해 Configuration(원하는 상태)과 실제 인프라(현재 상태)의 차이를 계산한다.

State 파일에는 리소스 ID, 속성 값, 메타데이터가 포함되며, 민감한 정보(비밀번호, 액세스 키 등)도 평문으로 저장될 수 있으므로 반드시 암호화된 원격 Backend(S3 + KMS 등)를 사용해야 한다.

5.2 Remote Backend 설정

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/network/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-lock"    # State 잠금용 DynamoDB 테이블
    kms_key_id     = "alias/terraform"   # KMS 암호화 키
  }
}

5.3 terraform state 명령어

# ── 리소스 목록 조회 ──
terraform state list
# aws_vpc.main
# aws_subnet.public[0]
# aws_subnet.public[1]
# aws_instance.web
# aws_db_instance.main

# 필터링
terraform state list aws_subnet.*
terraform state list module.network

# ── 리소스 상세 조회 ──
terraform state show aws_instance.web
# resource "aws_instance" "web" {
#     ami                    = "ami-0c55b159cbfafe1f0"
#     arn                    = "arn:aws:ec2:ap-northeast-2:123456789:instance/i-0abc123def456"
#     instance_type          = "t3.medium"
#     private_ip             = "10.0.1.50"
#     public_ip              = "54.180.xxx.xxx"
#     ...
# }

# ── 리소스 이동 (이름 변경/모듈 이동) ──
# 리소스 이름 변경 (코드에서도 함께 변경 필요)
terraform state mv aws_instance.web aws_instance.app_server

# 모듈로 이동
terraform state mv aws_vpc.main module.network.aws_vpc.main

# 다른 State 파일로 이동
terraform state mv -state-out=other.tfstate aws_instance.web aws_instance.web

# Dry-run (실제 변경 없이 확인)
terraform state mv -dry-run aws_instance.web aws_instance.app

# ── State에서 리소스 제거 (실제 인프라는 유지) ──
terraform state rm aws_instance.web
# Terraform이 더 이상 이 리소스를 관리하지 않음
# 실제 EC2 인스턴스는 삭제되지 않음

# ── Remote State 다운로드 ──
terraform state pull > terraform.tfstate.backup

# ── Local State를 Remote에 업로드 ──
terraform state push terraform.tfstate

# 강제 업로드 (serial 번호 충돌 무시, 위험!)
terraform state push -force terraform.tfstate

# ── State 잠금 해제 ──
# 비정상 종료로 잠금이 풀리지 않았을 때
terraform force-unlock LOCK_ID
# LOCK_ID는 에러 메시지에 표시됨

# 확인 프롬프트 없이 강제 해제
terraform force-unlock -force LOCK_ID

5.4 terraform import — 기존 리소스 가져오기

기존에 수동으로 생성한 리소스를 Terraform 관리 하에 두려면 import를 사용한다.

# 전통적 CLI import (Terraform 1.5 이전)
terraform import aws_instance.web i-0abc123def456
terraform import aws_vpc.main vpc-0abc123def
terraform import 'aws_subnet.public[0]' subnet-0abc123def
terraform import module.network.aws_vpc.main vpc-0abc123def

Terraform 1.5+의 import block (선언적 Import, 권장):

# import.tf
import {
  to = aws_instance.web
  id = "i-0abc123def456"
}

import {
  to = aws_vpc.main
  id = "vpc-0abc123def"
}
# import block 기반 Plan (코드 자동 생성)
terraform plan -generate-config-out=generated.tf

# 생성된 코드 확인 후 Apply
terraform apply

6. Terraform Workspace

Workspace는 동일한 Configuration으로 여러 환경(dev/staging/prod)을 관리할 때 유용하다. 각 Workspace는 독립된 State 파일을 가진다.

# ── Workspace 목록 ──
terraform workspace list
# * default
#   dev
#   staging
#   prod

# ── 새 Workspace 생성 ──
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# ── Workspace 전환 ──
terraform workspace select prod

# ── 현재 Workspace 확인 ──
terraform workspace show
# prod

# ── Workspace 삭제 (빈 State만 가능) ──
terraform workspace delete dev

# 강제 삭제 (State가 남아있어도 삭제)
terraform workspace delete -force dev

Workspace를 HCL에서 활용하는 패턴:

# 환경별 인스턴스 타입 분기
locals {
  instance_type = {
    dev     = "t3.micro"
    staging = "t3.small"
    prod    = "t3.large"
  }
}

resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.instance_type[terraform.workspace]

  tags = {
    Name        = "app-${terraform.workspace}"
    Environment = terraform.workspace
  }
}

7. Terraform 모듈 관리

7.1 모듈 구조

modules/
├── network/
│   ├── main.tf       # VPC, Subnet, IGW, NAT
│   ├── variables.tf  # 입력 변수
│   ├── outputs.tf    # 출력 값
│   └── README.md
├── compute/
│   ├── main.tf       # EC2, ASG, ALB
│   ├── variables.tf
│   └── outputs.tf
└── database/
    ├── main.tf       # RDS, ElastiCache
    ├── variables.tf
    └── outputs.tf

7.2 모듈 소스 유형

# 로컬 모듈
module "network" {
  source = "./modules/network"
}

# Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.16.0"
}

# GitHub
module "vpc" {
  source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.16.0"
}

# S3 Bucket
module "vpc" {
  source = "s3::https://s3-ap-northeast-2.amazonaws.com/my-modules/vpc.zip"
}

# Git (SSH)
module "vpc" {
  source = "git::ssh://git@github.com/myorg/modules.git//network?ref=v1.0.0"
}

7.3 모듈 관련 명령어

# 모듈 다운로드/업데이트
terraform init -upgrade

# 모듈에서 사용하는 Provider 확인
terraform providers

# Provider 잠금 파일 업데이트 (여러 플랫폼 지원)
terraform providers lock \
  -platform=linux_amd64 \
  -platform=darwin_arm64

# Provider 미러 (에어갭 환경)
terraform providers mirror /path/to/mirror

8. Terraform 고급 기능

8.1 terraform console — 인터랙티브 콘솔

# 대화형 표현식 평가
terraform console

# 사용 예시
> var.instance_type
"t3.medium"

> length(var.subnet_ids)
3

> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"

> formatdate("YYYY-MM-DD", timestamp())
"2026-03-01"

> [for s in var.subnet_ids : upper(s)]
["SUBNET-AAA", "SUBNET-BBB", "SUBNET-CCC"]

# 종료: Ctrl+D 또는 exit

8.2 terraform graph — 의존성 그래프

# DOT 형식으로 의존성 그래프 출력
terraform graph

# Graphviz로 이미지 생성
terraform graph | dot -Tpng > graph.png
terraform graph | dot -Tsvg > graph.svg

# Plan 기반 그래프
terraform graph -type=plan

# 특정 리소스 중심 그래프
terraform graph -draw-cycles

8.3 기타 유틸리티 명령어

# 현재 설정에서 사용하는 Provider 트리 표시
terraform providers

# Provider 스키마를 JSON으로 출력
terraform providers schema -json

# Terraform 버전 확인
terraform version

# JSON 출력
terraform version -json

# Terraform 설정 파일 위치 표시
terraform -help

# 특정 명령어 도움말
terraform plan -help

9. HCL 문법 치트시트

9.1 Variables (입력 변수)

# variables.tf

# 기본 타입
variable "region" {
  description = "AWS Region"
  type        = string
  default     = "ap-northeast-2"
}

variable "instance_count" {
  description = "Number of instances"
  type        = number
  default     = 2
}

variable "enable_monitoring" {
  description = "Enable CloudWatch monitoring"
  type        = bool
  default     = true
}

# 복합 타입 - List
variable "availability_zones" {
  type    = list(string)
  default = ["ap-northeast-2a", "ap-northeast-2c"]
}

# 복합 타입 - Map
variable "instance_types" {
  type = map(string)
  default = {
    dev  = "t3.micro"
    prod = "t3.large"
  }
}

# 복합 타입 - Object
variable "database_config" {
  type = object({
    engine         = string
    engine_version = string
    instance_class = string
    multi_az       = bool
    storage_gb     = number
  })
  default = {
    engine         = "mysql"
    engine_version = "8.0"
    instance_class = "db.t3.medium"
    multi_az       = true
    storage_gb     = 100
  }
}

# 민감한 변수
variable "db_password" {
  description = "Database master password"
  type        = string
  sensitive   = true  # Plan/Apply 출력에서 마스킹
}

# Validation Rule
variable "instance_type" {
  type = string
  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Instance type must start with t3."
  }
}

# Nullable
variable "override_name" {
  type     = string
  default  = null
  nullable = true
}

변수 값 전달 방법 (우선순위 높은 순):

# 1. CLI -var 플래그 (최우선)
terraform apply -var="region=us-east-1"

# 2. -var-file 플래그
terraform apply -var-file="prod.tfvars"

# 3. *.auto.tfvars (자동 로드)
# terraform.tfvars, *.auto.tfvars

# 4. 환경 변수 (TF_VAR_ 접두사)
export TF_VAR_region="us-east-1"
export TF_VAR_db_password="SuperSecret123!"

# 5. default 값

9.2 Locals (로컬 변수)

locals {
  project_name = "my-app"
  environment  = terraform.workspace

  common_tags = {
    Project     = local.project_name
    Environment = local.environment
    ManagedBy   = "terraform"
    Team        = "platform"
  }

  # 조건부 값
  is_prod = local.environment == "prod"

  # 계산된 값
  name_prefix = "${local.project_name}-${local.environment}"
}

resource "aws_instance" "app" {
  # ...
  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-app"
  })
}

9.3 Outputs (출력 값)

# outputs.tf

output "vpc_id" {
  description = "The ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "List of public subnet IDs"
  value       = aws_subnet.public[*].id
}

output "db_endpoint" {
  description = "RDS endpoint"
  value       = aws_db_instance.main.endpoint
  sensitive   = true  # 민감한 출력값 마스킹
}

# 다른 모듈에서 참조
# module.network.vpc_id

9.4 count와 for_each

# count — 같은 리소스를 N개 생성
resource "aws_subnet" "public" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "public-subnet-${count.index}"
  }
}

# 참조: aws_subnet.public[0], aws_subnet.public[1]

# for_each — Map 또는 Set 기반 반복 (권장)
resource "aws_iam_user" "users" {
  for_each = toset(["alice", "bob", "charlie"])
  name     = each.value
}

# Map 기반 for_each
variable "instances" {
  default = {
    web = { type = "t3.small", az = "ap-northeast-2a" }
    api = { type = "t3.medium", az = "ap-northeast-2c" }
    worker = { type = "t3.large", az = "ap-northeast-2a" }
  }
}

resource "aws_instance" "servers" {
  for_each      = var.instances
  ami           = data.aws_ami.ubuntu.id
  instance_type = each.value.type
  availability_zone = each.value.az

  tags = {
    Name = "${each.key}-server"
  }
}

# 참조: aws_instance.servers["web"], aws_instance.servers["api"]

9.5 dynamic Block

# 보안 그룹 규칙을 동적으로 생성
variable "ingress_rules" {
  default = [
    { port = 80,  cidr = "0.0.0.0/0",   description = "HTTP" },
    { port = 443, cidr = "0.0.0.0/0",   description = "HTTPS" },
    { port = 22,  cidr = "10.0.0.0/8",  description = "SSH (internal)" },
  ]
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Web server security group"
  vpc_id      = aws_vpc.main.id

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = [ingress.value.cidr]
      description = ingress.value.description
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

9.6 lifecycle 메타 인수

resource "aws_instance" "app" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  lifecycle {
    # 삭제 전에 새 리소스 먼저 생성 (다운타임 최소화)
    create_before_destroy = true

    # 특정 속성 변경 무시 (외부에서 수정되는 태그 등)
    ignore_changes = [
      tags["LastModified"],
      ami,
    ]

    # 삭제 방지 (실수로 destroy 못하게)
    prevent_destroy = true

    # 사전/사후 조건
    precondition {
      condition     = var.instance_type != "t3.nano"
      error_message = "t3.nano is too small for this application."
    }

    postcondition {
      condition     = self.public_ip != ""
      error_message = "Instance must have a public IP."
    }

    # 리소스 교체 트리거 (값이 바뀌면 리소스 재생성)
    replace_triggered_by = [
      aws_ami.app_ami.id
    ]
  }
}

10. Terraform 실전 예제 — AWS VPC + EC2 + RDS

10.1 프로젝트 구조

terraform-aws-project/
├── main.tf           # Provider, Backend 설정
├── variables.tf      # 입력 변수 정의
├── outputs.tf        # 출력 값 정의
├── terraform.tfvars  # 변수  (gitignore 대상)
├── network.tf        # VPC, Subnet, IGW, NAT, Route Table
├── compute.tf        # EC2, Security Group, Key Pair
├── database.tf       # RDS, Subnet Group
├── data.tf           # Data Sources (AMI 조회 등)
└── versions.tf       # Provider/Terraform 버전 제약

10.2 전체 코드

# versions.tf
terraform {
  required_version = ">= 1.9.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.80"
    }
  }

  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-lock"
  }
}

# main.tf
provider "aws" {
  region = var.region

  default_tags {
    tags = {
      Project     = var.project_name
      Environment = terraform.workspace
      ManagedBy   = "terraform"
    }
  }
}

# variables.tf
variable "region" {
  type    = string
  default = "ap-northeast-2"
}

variable "project_name" {
  type    = string
  default = "myapp"
}

variable "vpc_cidr" {
  type    = string
  default = "10.0.0.0/16"
}

variable "azs" {
  type    = list(string)
  default = ["ap-northeast-2a", "ap-northeast-2c"]
}

variable "db_password" {
  type      = string
  sensitive = true
}

# data.tf
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"] # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }
}

# network.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = { Name = "${var.project_name}-vpc" }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "${var.project_name}-igw" }
}

resource "aws_subnet" "public" {
  count                   = length(var.azs)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone       = var.azs[count.index]
  map_public_ip_on_launch = true

  tags = { Name = "${var.project_name}-public-${count.index}" }
}

resource "aws_subnet" "private" {
  count             = length(var.azs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
  availability_zone = var.azs[count.index]

  tags = { Name = "${var.project_name}-private-${count.index}" }
}

resource "aws_eip" "nat" {
  domain = "vpc"
  tags   = { Name = "${var.project_name}-nat-eip" }
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = { Name = "${var.project_name}-nat" }
  depends_on = [aws_internet_gateway.main]
}

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" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main.id
  }

  tags = { Name = "${var.project_name}-private-rt" }
}

resource "aws_route_table_association" "public" {
  count          = length(var.azs)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
  count          = length(var.azs)
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}

# compute.tf
resource "aws_security_group" "web" {
  name        = "${var.project_name}-web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTP"
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTPS"
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/8"]
    description = "SSH internal"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = { Name = "${var.project_name}-web-sg" }
}

resource "aws_instance" "web" {
  count                  = 2
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = "t3.small"
  subnet_id              = aws_subnet.public[count.index % length(var.azs)].id
  vpc_security_group_ids = [aws_security_group.web.id]

  root_block_device {
    volume_size = 30
    volume_type = "gp3"
    encrypted   = true
  }

  tags = { Name = "${var.project_name}-web-${count.index}" }

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [ami]
  }
}

# database.tf
resource "aws_security_group" "db" {
  name        = "${var.project_name}-db-sg"
  description = "Security group for RDS"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.web.id]
    description     = "MySQL from web servers"
  }

  tags = { Name = "${var.project_name}-db-sg" }
}

resource "aws_db_subnet_group" "main" {
  name       = "${var.project_name}-db-subnet-group"
  subnet_ids = aws_subnet.private[*].id

  tags = { Name = "${var.project_name}-db-subnet-group" }
}

resource "aws_db_instance" "main" {
  identifier           = "${var.project_name}-mysql"
  engine               = "mysql"
  engine_version       = "8.0"
  instance_class       = "db.t3.medium"
  allocated_storage    = 100
  storage_type         = "gp3"
  storage_encrypted    = true
  db_name              = "myapp"
  username             = "admin"
  password             = var.db_password
  multi_az             = true
  db_subnet_group_name = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.db.id]
  skip_final_snapshot  = false
  final_snapshot_identifier = "${var.project_name}-final-snapshot"
  backup_retention_period   = 7

  tags = { Name = "${var.project_name}-mysql" }

  lifecycle {
    prevent_destroy = true
  }
}

# outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_ids" {
  value = aws_subnet.public[*].id
}

output "web_instance_ips" {
  value = aws_instance.web[*].public_ip
}

output "rds_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

Part 2: Ansible 완벽 가이드


11. Ansible 소개와 아키텍처

11.1 Ansible이란?

Ansible은 Red Hat이 관리하는 오픈소스 자동화 도구로, 2012년 Michael DeHaan이 만들었다. Configuration Management, Application Deployment, Task Automation을 하나의 도구로 수행할 수 있다.

Ansible의 핵심 철학:

  • Agentless: 대상 서버에 에이전트 설치가 필요 없다 (SSH/WinRM 기반)
  • Idempotent: 같은 Playbook을 여러 번 실행해도 결과가 동일하다
  • YAML 기반: 프로그래밍 언어가 아닌 YAML로 작성하여 진입 장벽이 낮다
  • Push 모델: Control Node에서 Managed Node로 작업을 밀어넣는다

11.2 Ansible 아키텍처

┌──────────────────────────────────────────────────────────────────┐
Ansible Architecture├──────────────────────────────────────────────────────────────────┤
│                                                                   │
│  ┌─────────────────────────────────────────────────┐             │
│  │              Control Node                        │             │
│  │                                                  │             │
│  │  ┌────────────┐  ┌──────────┐  ┌────────────┐  │             │
│  │  │ Playbook   │  │ Inventory │  │  Modules    │  │             │
│  │   (.yml) (hosts)  (2,500+)   │  │             │
│  │  └─────┬──────┘  └────┬─────┘  └──────┬─────┘  │             │
│  │        │              │               │         │             │
│  │        ▼              ▼               ▼         │             │
│  │  ┌──────────────────────────────────────────┐   │             │
│  │  │            Ansible Engine                 │   │             │
│  │  │                                           │   │             │
│  │  │  - Task Execution                         │   │             │
│  │  │  - Variable Resolution                    │   │             │
│  │  │  - Connection Management (SSH/WinRM)      │   │             │
│  │  │  - Fact Gathering                         │   │             │
│  │  └─────────────┬─────────────────────────────┘   │             │
│  └────────────────┼─────────────────────────────────┘             │
│                   │                                               │
SSH / WinRM (No Agent!)│                   │                                               │
│     ┌─────────────┼──────────────────────────────┐               │
│     │             ▼                              │               │
│     │  ┌──────┐  ┌──────┐  ┌──────┐  ┌──────┐  │               │
│     │  │ Web1 │  │ Web2 │  │ DB1  │  │ DB2  │  │               │
│     │  └──────┘  └──────┘  └──────┘  └──────┘  │               │
│     │            Managed Nodes                   │               │
│     └────────────────────────────────────────────┘               │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘
  • Control Node: Ansible이 설치되어 실행되는 노드 (Linux/macOS, Windows는 WSL 필요)
  • Managed Node: Ansible이 관리하는 대상 서버 (SSH 접속 가능하면 됨)
  • Inventory: 관리 대상 호스트 목록
  • Module: 실제 작업을 수행하는 코드 단위 (2,500+ 내장 모듈)
  • Playbook: 작업 순서를 정의한 YAML 파일

12. Ansible 설치

# ── pip로 설치 (권장) ──
pip install ansible

# 특정 버전 설치
pip install ansible==10.6.0

# ansible-core만 설치 (최소 설치)
pip install ansible-core

# ── macOS (Homebrew) ──
brew install ansible

# ── Ubuntu/Debian ──
sudo apt update
sudo apt install ansible

# ── RHEL/CentOS/Fedora ──
sudo dnf install ansible

# ── 버전 확인 ──
ansible --version
# ansible [core 2.17.7]
#   config file = /etc/ansible/ansible.cfg
#   configured module search path = ['~/.ansible/plugins/modules']
#   ansible python module location = /usr/lib/python3.12/site-packages/ansible
#   python version = 3.12.8

# ── ansible-navigator 설치 (TUI, Execution Environment 지원) ──
pip install ansible-navigator

13. Ansible 인벤토리

13.1 정적 인벤토리 (INI 형식)

# inventory/hosts.ini

# 개별 호스트
web1.example.com
web2.example.com

# 그룹 정의
[webservers]
web1.example.com ansible_host=10.0.1.10
web2.example.com ansible_host=10.0.1.11

[dbservers]
db1.example.com ansible_host=10.0.2.10 ansible_port=2222
db2.example.com ansible_host=10.0.2.11

[monitoring]
grafana.example.com

# 그룹의 변수
[webservers:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/web_key.pem
http_port=80

[dbservers:vars]
ansible_user=ec2-user
ansible_ssh_private_key_file=~/.ssh/db_key.pem

# 그룹의 그룹 (Children)
[production:children]
webservers
dbservers
monitoring

[production:vars]
env=production

# 호스트 범위 패턴
[loadbalancers]
lb[01:03].example.com  # lb01, lb02, lb03

13.2 정적 인벤토리 (YAML 형식)

# inventory/hosts.yml
all:
  children:
    production:
      children:
        webservers:
          hosts:
            web1.example.com:
              ansible_host: 10.0.1.10
            web2.example.com:
              ansible_host: 10.0.1.11
          vars:
            ansible_user: ubuntu
            ansible_ssh_private_key_file: ~/.ssh/web_key.pem
            http_port: 80

        dbservers:
          hosts:
            db1.example.com:
              ansible_host: 10.0.2.10
            db2.example.com:
              ansible_host: 10.0.2.11
          vars:
            ansible_user: ec2-user
      vars:
        env: production

    staging:
      children:
        webservers_stg:
          hosts:
            stg-web1.example.com:
              ansible_host: 10.1.1.10

13.3 동적 인벤토리

동적 인벤토리는 클라우드 API에서 실시간으로 호스트 목록을 가져온다.

# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - ap-northeast-2

keyed_groups:
  - key: tags.Environment
    prefix: env
  - key: instance_type
    prefix: type
  - key: placement.availability_zone
    prefix: az

filters:
  tag:ManagedBy: ansible
  instance-state-name: running

compose:
  ansible_host: private_ip_address
# 동적 인벤토리 테스트
ansible-inventory -i inventory/aws_ec2.yml --list
ansible-inventory -i inventory/aws_ec2.yml --graph

13.4 인벤토리 관련 명령어

# 인벤토리 호스트 목록 확인
ansible-inventory -i inventory/hosts.yml --list
ansible-inventory -i inventory/hosts.yml --graph

# 특정 그룹만 표시
ansible-inventory -i inventory/hosts.yml --graph webservers

# 호스트 변수 확인
ansible-inventory -i inventory/hosts.yml --host web1.example.com

# JSON 출력
ansible-inventory -i inventory/hosts.yml --list --yaml

14. Ansible Ad-hoc 명령어

Ad-hoc 명령은 Playbook 없이 한 줄로 실행하는 일회성 명령이다. 빠른 확인이나 간단한 작업에 유용하다.

14.1 기본 문법

ansible [호스트패턴] -i [인벤토리] -m [모듈] -a "[인수]" [옵션]

14.2 주요 모듈별 예제

# ── ping: 연결 확인 ──
ansible all -i inventory/hosts.yml -m ping
ansible webservers -i inventory/hosts.yml -m ping

# ── command: 명령 실행 (기본 모듈, 셸 기능 미지원) ──
ansible webservers -m command -a "uptime"
ansible webservers -m command -a "df -h"
ansible webservers -m command -a "free -m"

# ── shell: 셸 명령 실행 (파이프, 리다이렉트 지원) ──
ansible webservers -m shell -a "ps aux | grep nginx | wc -l"
ansible webservers -m shell -a "cat /etc/os-release | head -5"
ansible dbservers -m shell -a "mysql -e 'SHOW DATABASES;'"

# ── copy: 파일 복사 ──
ansible webservers -m copy -a "src=./app.conf dest=/etc/nginx/conf.d/app.conf owner=root group=root mode=0644"

# ── file: 파일/디렉토리 관리 ──
ansible webservers -m file -a "path=/opt/app state=directory owner=www-data mode=0755"
ansible webservers -m file -a "path=/tmp/old_file state=absent"
ansible webservers -m file -a "src=/etc/nginx/sites-available/app dest=/etc/nginx/sites-enabled/app state=link"

# ── apt: 패키지 관리 (Debian/Ubuntu) ──
ansible webservers -m apt -a "name=nginx state=present update_cache=yes" --become
ansible webservers -m apt -a "name=nginx state=latest" --become
ansible webservers -m apt -a "name=nginx state=absent" --become
ansible webservers -m apt -a "upgrade=dist" --become

# ── yum/dnf: 패키지 관리 (RHEL/CentOS) ──
ansible dbservers -m dnf -a "name=mysql-server state=present" --become

# ── service/systemd: 서비스 관리 ──
ansible webservers -m service -a "name=nginx state=started enabled=yes" --become
ansible webservers -m service -a "name=nginx state=restarted" --become
ansible webservers -m service -a "name=nginx state=stopped" --become
ansible webservers -m systemd -a "name=nginx state=reloaded daemon_reload=yes" --become

# ── user: 사용자 관리 ──
ansible all -m user -a "name=deploy state=present groups=sudo shell=/bin/bash" --become
ansible all -m user -a "name=olduser state=absent remove=yes" --become

# ── setup: 팩트(시스템 정보) 수집 ──
ansible webservers -m setup
ansible webservers -m setup -a "filter=ansible_os_family"
ansible webservers -m setup -a "filter=ansible_memtotal_mb"
ansible webservers -m setup -a "filter=ansible_distribution*"

# ── lineinfile: 파일 내 라인 관리 ──
ansible webservers -m lineinfile -a "path=/etc/ssh/sshd_config regexp='^PermitRootLogin' line='PermitRootLogin no'" --become

# ── cron: 크론잡 관리 ──
ansible webservers -m cron -a "name='log cleanup' minute='0' hour='3' job='find /var/log -name \"*.gz\" -mtime +30 -delete'"

# ── get_url: URL에서 파일 다운로드 ──
ansible webservers -m get_url -a "url=https://example.com/app.tar.gz dest=/tmp/app.tar.gz"

# ── git: Git 저장소 클론/풀 ──
ansible webservers -m git -a "repo=https://github.com/myorg/myapp.git dest=/opt/myapp version=main"

14.3 Ad-hoc 주요 옵션

# 주요 옵션
-i inventory/hosts.yml  # 인벤토리 파일 지정
-m module_name           # 모듈 지정 (기본: command)
-a "arguments"           # 모듈 인수
--become (-b)            # sudo 권한 상승
--become-user root       # 권한 상승 대상 사용자
--become-method sudo     # 권한 상승 방법
-u ubuntu                # SSH 접속 사용자
--private-key ~/.ssh/key # SSH 키
-f 10                    # 병렬 실행 수 (기본: 5)
--limit web1             # 특정 호스트만 실행
-v / -vv / -vvv / -vvvv  # 상세 출력 레벨
--check                  # Dry-run (실제 변경 없음)
--diff                   # 변경 내용 diff 표시
-o                       # 한 줄 출력 (요약)
--ask-pass (-k)          # SSH 비밀번호 프롬프트
--ask-become-pass (-K)   # sudo 비밀번호 프롬프트

15. Ansible Playbook

15.1 ansible-playbook 명령어 옵션

# 기본 실행
ansible-playbook -i inventory/hosts.yml playbook.yml

# 주요 옵션
ansible-playbook playbook.yml \
  -i inventory/hosts.yml \       # 인벤토리
  --limit webservers \           # 대상 호스트 제한
  --tags "nginx,ssl" \           # 특정 태그만 실행
  --skip-tags "debug" \          # 특정 태그 건너뛰기
  -e "env=prod version=2.1" \    # 추가 변수 전달
  -e @vars/prod.yml \            # 변수 파일 전달
  --check \                      # Dry-run
  --diff \                       # 변경 내용 diff
  --start-at-task "Install Nginx" \ # 특정 태스크부터 시작
  --step \                       # 태스크별 확인 프롬프트
  --list-tasks \                 # 태스크 목록만 표시
  --list-tags \                  # 태그 목록만 표시
  --list-hosts \                 # 대상 호스트 목록만 표시
  -f 20 \                        # 병렬 실행 수
  --become \                     # sudo
  --vault-password-file .vault_pass \ # Vault 비밀번호 파일
  --ask-vault-pass \             # Vault 비밀번호 프롬프트
  -v                             # 상세 출력

# 문법 검사
ansible-playbook playbook.yml --syntax-check

15.2 Playbook 기본 구조

# site.yml
---
- name: Configure web servers
  hosts: webservers
  become: yes
  gather_facts: yes
  vars:
    http_port: 80
    app_version: '2.1.0'

  pre_tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
      tags: [nginx]

    - name: Deploy Nginx config
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
      notify: Restart Nginx
      tags: [nginx, config]

    - name: Deploy application
      git:
        repo: 'https://github.com/myorg/myapp.git'
        dest: /opt/myapp
        version: '{{ app_version }}'
      tags: [deploy]

    - name: Ensure Nginx is running
      service:
        name: nginx
        state: started
        enabled: yes
      tags: [nginx]

  post_tasks:
    - name: Verify HTTP response
      uri:
        url: 'http://localhost:{{ http_port }}'
        status_code: 200
      register: result
      retries: 3
      delay: 5
      until: result.status == 200

  handlers:
    - name: Restart Nginx
      service:
        name: nginx
        state: restarted

15.3 조건문 (when)

tasks:
  # 기본 조건
  - name: Install Apache (Debian)
    apt:
      name: apache2
      state: present
    when: ansible_os_family == "Debian"

  - name: Install httpd (RedHat)
    dnf:
      name: httpd
      state: present
    when: ansible_os_family == "RedHat"

  # 복합 조건
  - name: Configure for production
    template:
      src: prod.conf.j2
      dest: /etc/app/app.conf
    when:
      - env == "prod"
      - ansible_memtotal_mb >= 4096

  # OR 조건
  - name: Install on Debian or Ubuntu
    apt:
      name: curl
      state: present
    when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"

  # 변수 존재 여부
  - name: Configure custom DNS
    template:
      src: resolv.conf.j2
      dest: /etc/resolv.conf
    when: custom_dns is defined

  # 이전 태스크 결과 기반
  - name: Check if config exists
    stat:
      path: /etc/app/app.conf
    register: config_file

  - name: Create default config
    template:
      src: default.conf.j2
      dest: /etc/app/app.conf
    when: not config_file.stat.exists

15.4 반복문 (loop)

tasks:
  # 기본 loop
  - name: Install packages
    apt:
      name: '{{ item }}'
      state: present
    loop:
      - nginx
      - python3
      - git
      - curl
      - htop

  # 더 효율적인 방법 (한 번에 설치)
  - name: Install packages (optimized)
    apt:
      name:
        - nginx
        - python3
        - git
      state: present

  # Dict loop
  - name: Create users
    user:
      name: '{{ item.name }}'
      groups: '{{ item.groups }}'
      shell: '{{ item.shell }}'
      state: present
    loop:
      - { name: 'alice', groups: 'sudo', shell: '/bin/bash' }
      - { name: 'bob', groups: 'developers', shell: '/bin/zsh' }
      - { name: 'charlie', groups: 'developers', shell: '/bin/bash' }

  # with_fileglob (파일 글로브)
  - name: Copy all config files
    copy:
      src: '{{ item }}'
      dest: /etc/app/conf.d/
    with_fileglob:
      - 'files/configs/*.conf'

  # loop_control
  - name: Create directories with index
    file:
      path: '/opt/app/data-{{ idx }}'
      state: directory
    loop:
      - logs
      - cache
      - uploads
    loop_control:
      index_var: idx
      label: '{{ item }}' # 출력에서 표시할 라벨 (민감 데이터 숨김)

15.5 Handlers

tasks:
  - name: Update Nginx config
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify:
      - Validate Nginx config
      - Restart Nginx

  - name: Update SSL certificate
    copy:
      src: ssl/cert.pem
      dest: /etc/ssl/certs/app.pem
    notify: Restart Nginx

handlers:
  # Handler는 notified된 경우에만 실행, 여러 번 notify되어도 한 번만 실행
  - name: Validate Nginx config
    command: nginx -t
    listen: 'Validate Nginx config'

  - name: Restart Nginx
    service:
      name: nginx
      state: restarted
    listen: 'Restart Nginx'

15.6 Block (에러 처리)

tasks:
  - name: Application deployment with rollback
    block:
      - name: Pull latest code
        git:
          repo: 'https://github.com/myorg/myapp.git'
          dest: /opt/myapp
          version: '{{ app_version }}'

      - name: Install dependencies
        pip:
          requirements: /opt/myapp/requirements.txt
          virtualenv: /opt/myapp/venv

      - name: Run database migrations
        command: /opt/myapp/venv/bin/python manage.py migrate
        args:
          chdir: /opt/myapp

      - name: Restart application
        systemd:
          name: myapp
          state: restarted

    rescue:
      - name: Rollback to previous version
        git:
          repo: 'https://github.com/myorg/myapp.git'
          dest: /opt/myapp
          version: '{{ previous_version }}'

      - name: Restart application (rollback)
        systemd:
          name: myapp
          state: restarted

      - name: Send failure notification
        slack:
          token: '{{ slack_token }}'
          channel: '#deploy'
          msg: 'Deployment of {{ app_version }} FAILED. Rolled back to {{ previous_version }}.'

    always:
      - name: Clean up temp files
        file:
          path: /tmp/deploy_artifacts
          state: absent

      - name: Log deployment result
        lineinfile:
          path: /var/log/deployments.log
          line: "{{ ansible_date_time.iso8601 }} - {{ app_version }} - {{ ansible_failed_task.name | default('SUCCESS') }}"
          create: yes

16. Ansible Role

16.1 Role 구조

roles/
└── nginx/
    ├── tasks/
    │   ├── main.yml         # 메인 태스크
    │   ├── install.yml      # 설치 태스크
    │   └── configure.yml    # 설정 태스크
    ├── handlers/
    │   └── main.yml         # 핸들러
    ├── templates/
    │   └── nginx.conf.j2    # Jinja2 템플릿
    ├── files/
    │   └── index.html       # 정적 파일
    ├── vars/
    │   └── main.yml         # 변수 (높은 우선순위)
    ├── defaults/
    │   └── main.yml         # 기본값 (낮은 우선순위)
    ├── meta/
    │   └── main.yml         # 메타데이터, 의존성
    ├── tests/
    │   ├── inventory
    │   └── test.yml
    └── README.md

16.2 ansible-galaxy 명령어

# ── Role 관리 ──

# Role 초기화 (디렉토리 구조 생성)
ansible-galaxy role init roles/nginx
ansible-galaxy role init --init-path=./roles nginx

# Galaxy에서 Role 설치
ansible-galaxy role install geerlingguy.nginx
ansible-galaxy role install geerlingguy.docker -p roles/

# 특정 버전 설치
ansible-galaxy role install geerlingguy.nginx,3.1.0

# requirements.yml에서 일괄 설치
ansible-galaxy role install -r requirements.yml

# 설치된 Role 목록
ansible-galaxy role list

# Role 삭제
ansible-galaxy role remove geerlingguy.nginx

# Role 검색
ansible-galaxy role search nginx --author geerlingguy

# Role 정보 확인
ansible-galaxy role info geerlingguy.nginx

# ── Collection 관리 ──

# Collection 설치
ansible-galaxy collection install amazon.aws
ansible-galaxy collection install community.general

# requirements.yml에서 일괄 설치
ansible-galaxy collection install -r requirements.yml

# 설치된 Collection 목록
ansible-galaxy collection list

# Collection 빌드
ansible-galaxy collection build

# Collection 발행
ansible-galaxy collection publish ./myns-mycoll-1.0.0.tar.gz

requirements.yml 예시:

# requirements.yml
---
roles:
  - name: geerlingguy.nginx
    version: '3.1.0'
  - name: geerlingguy.docker
    version: '7.4.1'
  - name: geerlingguy.certbot
  - src: https://github.com/myorg/ansible-role-custom.git
    scm: git
    version: main
    name: custom_role

collections:
  - name: amazon.aws
    version: '>=8.0.0'
  - name: community.general
  - name: ansible.posix

16.3 Role 사용 예시

# site.yml
---
- name: Configure web servers
  hosts: webservers
  become: yes

  roles:
    # 기본 사용
    - nginx

    # 변수 전달
    - role: nginx
      vars:
        nginx_worker_processes: 4
        nginx_worker_connections: 2048

    # 조건부 실행
    - role: certbot
      when: enable_ssl | default(false)

    # 태그 지정
    - role: monitoring
      tags: [monitoring, observability]

17. Ansible Vault

Ansible Vault는 민감한 데이터(비밀번호, API 키, 인증서 등)를 암호화하는 기능이다.

17.1 Vault 명령어

# ── 암호화된 파일 생성 ──
ansible-vault create secrets.yml
# 에디터가 열리고, 저장 시 자동 암호화

# 지정된 에디터로 생성
EDITOR=nano ansible-vault create secrets.yml

# ── 암호화된 파일 편집 ──
ansible-vault edit secrets.yml

# ── 기존 파일 암호화 ──
ansible-vault encrypt vars/prod_secrets.yml

# 여러 파일 동시 암호화
ansible-vault encrypt vars/secret1.yml vars/secret2.yml

# ── 암호화된 파일 복호화 ──
ansible-vault decrypt vars/prod_secrets.yml

# ── 암호화된 파일 내용 보기 (복호화 없이) ──
ansible-vault view secrets.yml

# ── 비밀번호 변경 ──
ansible-vault rekey secrets.yml

# ── 문자열 암호화 (인라인) ──
ansible-vault encrypt_string 'SuperSecretPassword123!' --name 'db_password'
# 출력:
# db_password: !vault |
#   $ANSIBLE_VAULT;1.1;AES256
#   61636530396131653661313936353764...

ansible-vault encrypt_string --vault-password-file .vault_pass 'my_api_key_value' --name 'api_key'

# ── Vault 비밀번호 전달 방법 ──
# 1. 프롬프트
ansible-playbook site.yml --ask-vault-pass

# 2. 파일
ansible-playbook site.yml --vault-password-file .vault_pass

# 3. 환경 변수
export ANSIBLE_VAULT_PASSWORD_FILE=.vault_pass
ansible-playbook site.yml

# 4. 스크립트 (비밀번호 관리 도구 연동)
ansible-playbook site.yml --vault-password-file ./get_vault_pass.sh

# ── 여러 Vault ID 사용 (환경별 다른 비밀번호) ──
ansible-vault create --vault-id prod@prompt secrets_prod.yml
ansible-vault create --vault-id dev@.vault_pass_dev secrets_dev.yml

ansible-playbook site.yml \
  --vault-id prod@prompt \
  --vault-id dev@.vault_pass_dev

17.2 Vault 사용 예시

# vars/secrets.yml (암호화됨)
---
db_password: 'SuperSecretPassword123!'
api_key: 'sk-1234567890abcdef'
ssl_private_key: |
  -----BEGIN PRIVATE KEY-----
  MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...
  -----END PRIVATE KEY-----
# playbook에서 사용
- name: Deploy application
  hosts: webservers
  become: yes
  vars_files:
    - vars/defaults.yml
    - vars/secrets.yml # Vault로 암호화된 파일

  tasks:
    - name: Configure database connection
      template:
        src: db_config.j2
        dest: /etc/app/database.yml
        mode: '0600'

18. Ansible 고급 기능

18.1 ansible-doc — 모듈 문서

# 모듈 도움말 보기
ansible-doc apt
ansible-doc copy
ansible-doc template
ansible-doc amazon.aws.ec2_instance

# 모듈 목록 보기
ansible-doc --list
ansible-doc --list | grep aws

# 짧은 도움말 (사용 예시)
ansible-doc -s apt
ansible-doc -s copy

# 플러그인 유형별 문서
ansible-doc -t callback -l          # Callback 플러그인 목록
ansible-doc -t connection -l        # Connection 플러그인 목록
ansible-doc -t inventory -l         # Inventory 플러그인 목록
ansible-doc -t lookup -l            # Lookup 플러그인 목록

18.2 ansible-navigator — TUI 기반 도구

# TUI 모드로 Playbook 실행
ansible-navigator run site.yml -i inventory/hosts.yml

# stdout 모드 (기존 ansible-playbook과 유사한 출력)
ansible-navigator run site.yml -i inventory/hosts.yml -m stdout

# 인벤토리 탐색
ansible-navigator inventory -i inventory/hosts.yml

# 모듈 문서 탐색
ansible-navigator doc apt

# Collection 탐색
ansible-navigator collections

# 설정 확인
ansible-navigator config

18.3 ansible-lint — 코드 품질

# 설치
pip install ansible-lint

# Playbook 린팅
ansible-lint site.yml

# 디렉토리 전체 린팅
ansible-lint

# 특정 규칙 무시
ansible-lint -x yaml[truthy]

# 자동 수정
ansible-lint --fix

18.4 ansible.cfg 설정

# ansible.cfg
[defaults]
inventory = inventory/hosts.yml
remote_user = ubuntu
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
retry_files_enabled = False
stdout_callback = yaml
callback_whitelist = timer, profile_tasks
forks = 20
timeout = 30
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 86400

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
control_path_dir = ~/.ansible/cp

Part 3: 통합, 치트시트, 트러블슈팅


19. Terraform + Ansible 통합

19.1 통합 아키텍처

Terraform과 Ansible을 연동하는 가장 일반적인 패턴은 다음과 같다.

┌─────────────────────────────────────────────────────────────┐
Terraform + Ansible Integration├─────────────────────────────────────────────────────────────┤
│                                                              │
1. Terraform으로 인프라 프로비저닝                           │
│     terraform apply                                          │
│         │                                                    │
│         ├── VPC, Subnet, SG 생성                             │
│         ├── EC2 인스턴스 생성                                 │
│         └── terraform output -json                           │
│                    │                                         │
│                    ▼                                         │
2. Terraform OutputAnsible Dynamic Inventory│     terraform output -json > tf_output.json│         │                                                    │
│         ▼                                                    │
3. Ansible로 Configuration Management│     ansible-playbook -i dynamic_inventory.py site.yml│         │                                                    │
│         ├── 패키지 설치                                      │
│         ├── 애플리케이션 설정                                 │
│         └── 서비스 시작                                      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

19.2 Terraform Output을 Ansible에서 활용

# Terraform outputs.tf
output "web_server_ips" {
  value = aws_instance.web[*].public_ip
}

output "db_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

output "ssh_key_name" {
  value = aws_key_pair.deployer.key_name
}
#!/usr/bin/env python3
# dynamic_inventory.py — Terraform Output 기반 동적 인벤토리
import json
import subprocess
import sys

def get_terraform_output():
    result = subprocess.run(
        ["terraform", "output", "-json"],
        capture_output=True, text=True
    )
    return json.loads(result.stdout)

def main():
    tf_output = get_terraform_output()
    web_ips = tf_output["web_server_ips"]["value"]

    inventory = {
        "webservers": {
            "hosts": web_ips,
            "vars": {
                "ansible_user": "ubuntu",
                "ansible_ssh_private_key_file": "~/.ssh/deployer.pem",
                "db_endpoint": tf_output["db_endpoint"]["value"]
            }
        },
        "_meta": {
            "hostvars": {}
        }
    }

    print(json.dumps(inventory, indent=2))

if __name__ == "__main__":
    main()
# 실행
chmod +x dynamic_inventory.py
ansible-playbook -i dynamic_inventory.py site.yml

19.3 Terraform local-exec Provisioner로 Ansible 호출

resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.small"
  key_name      = aws_key_pair.deployer.key_name

  # Ansible 실행 (Provisioner는 최후의 수단으로만 사용)
  provisioner "local-exec" {
    command = <<-EOT
      sleep 30  # SSH 준비 대기
      ANSIBLE_HOST_KEY_CHECKING=False \
      ansible-playbook \
        -i '${self.public_ip},' \
        -u ubuntu \
        --private-key ~/.ssh/deployer.pem \
        -e "db_endpoint=${aws_db_instance.main.endpoint}" \
        ansible/site.yml
    EOT
  }

  depends_on = [aws_db_instance.main]
}

19.4 Terraform + Ansible 자동화 스크립트

#!/bin/bash
# deploy.sh — 전체 인프라 + 설정 배포
set -euo pipefail

echo "=== Step 1: Terraform Init ==="
terraform init -input=false

echo "=== Step 2: Terraform Plan ==="
terraform plan -out=tfplan -input=false

echo "=== Step 3: Terraform Apply ==="
terraform apply tfplan

echo "=== Step 4: Generate Ansible Inventory ==="
terraform output -json > tf_output.json

echo "=== Step 5: Wait for instances to be ready ==="
WEB_IPS=$(terraform output -json web_server_ips | jq -r '.[]')
for ip in $WEB_IPS; do
  echo "Waiting for $ip..."
  until ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 ubuntu@"$ip" true 2>/dev/null; do
    sleep 5
  done
  echo "$ip is ready!"
done

echo "=== Step 6: Run Ansible Playbook ==="
ANSIBLE_HOST_KEY_CHECKING=False \
ansible-playbook \
  -i ./dynamic_inventory.py \
  --vault-password-file .vault_pass \
  site.yml

echo "=== Deployment Complete ==="
terraform output

20. Terraform 명령어 치트시트 Top 20

# ── 초기화 및 검증 ──
terraform init                           # 1. 프로젝트 초기화
terraform init -upgrade                  # 2. Provider/모듈 업그레이드
terraform validate                       # 3. 구문 검증
terraform fmt -recursive -check          # 4. 포매팅 체크

# ── 핵심 워크플로우 ──
terraform plan -out=tfplan               # 5. 실행 계획 저장
terraform apply tfplan                   # 6. 계획 적용
terraform apply -auto-approve            # 7. 자동 승인 적용 (CI/CD)
terraform destroy -auto-approve          # 8. 전체 삭제

# ── State 관리 ──
terraform state list                     # 9. 리소스 목록
terraform state show aws_instance.web    # 10. 리소스 상세
terraform state mv OLD NEW              # 11. 리소스 이동/이름변경
terraform state rm RESOURCE             # 12. State에서 제거
terraform state pull > backup.tfstate    # 13. State 백업
terraform force-unlock LOCK_ID          # 14. 잠금 해제

# ── Import ──
terraform import RESOURCE ID            # 15. 기존 리소스 가져오기
terraform plan -generate-config-out=g.tf # 16. Import 코드 자동생성

# ── Workspace ──
terraform workspace new ENV             # 17. Workspace 생성
terraform workspace select ENV          # 18. Workspace 전환

# ── 유틸리티 ──
terraform output -json                   # 19. 출력 값 (JSON)
terraform console                        # 20. 대화형 콘솔

21. Ansible 명령어 치트시트 Top 20

# ── 연결 확인 및 정보 수집 ──
ansible all -m ping                              # 1. 전체 호스트 Ping
ansible all -m setup -a "filter=ansible_os*"     # 2. 시스템 정보 수집

# ── Ad-hoc 명령 ──
ansible web -m shell -a "uptime"                 # 3. 셸 명령 실행
ansible web -m apt -a "name=nginx state=present" -b  # 4. 패키지 설치
ansible web -m service -a "name=nginx state=started" -b # 5. 서비스 시작
ansible web -m copy -a "src=f dest=/etc/app/" -b # 6. 파일 복사
ansible web -m user -a "name=deploy state=present" -b   # 7. 사용자 생성

# ── Playbook 실행 ──
ansible-playbook site.yml                        # 8. Playbook 실행
ansible-playbook site.yml --check --diff         # 9. Dry-run + diff
ansible-playbook site.yml --limit web1           # 10. 특정 호스트만
ansible-playbook site.yml --tags deploy          # 11. 특정 태그만
ansible-playbook site.yml -e "env=prod"          # 12. 변수 전달
ansible-playbook site.yml --syntax-check         # 13. 문법 검사

# ── Vault ──
ansible-vault create secrets.yml                 # 14. 암호화 파일 생성
ansible-vault edit secrets.yml                   # 15. 암호화 파일 편집
ansible-vault encrypt file.yml                   # 16. 파일 암호화
ansible-vault decrypt file.yml                   # 17. 파일 복호화
ansible-vault encrypt_string 'secret' --name key # 18. 문자열 암호화

# ── Galaxy 및 유틸리티 ──
ansible-galaxy role install geerlingguy.nginx    # 19. Role 설치
ansible-doc -s apt                               # 20. 모듈 도움말

22. 트러블슈팅

22.1 Terraform 트러블슈팅

# ── 1. State Lock 문제 ──
# 에러: Error acquiring the state lock
# 원인: 이전 terraform apply가 비정상 종료
# 해결:
terraform force-unlock LOCK_ID

# ── 2. Provider 인증 실패 ──
# 에러: NoCredentialProviders
# 해결: AWS 자격 증명 확인
aws sts get-caller-identity
export AWS_PROFILE=myprofile

# ── 3. State와 실제 인프라 불일치 ──
# 해결: State 새로고침
terraform apply -refresh-only

# 특정 리소스를 State에서 제거 후 다시 Import
terraform state rm aws_instance.web
terraform import aws_instance.web i-0abc123def

# ── 4. Provider 버전 충돌 ──
# 해결: 잠금 파일 재생성
rm .terraform.lock.hcl
terraform init -upgrade

# ── 5. 순환 의존성 ──
# 에러: Cycle detected
# 해결: depends_on 확인, 리소스 분리
terraform graph | dot -Tpng > graph.png  # 의존성 시각화

# ── 6. 디버그 로그 활성화 ──
export TF_LOG=DEBUG        # TRACE, DEBUG, INFO, WARN, ERROR
export TF_LOG_PATH=terraform.log
terraform apply

# ── 7. Plan은 성공하지만 Apply 실패 ──
# 원인: API 권한 부족, 리소스 제한, 네트워크 문제
# 해결: -parallelism=1로 순차 실행하여 정확한 에러 파악
terraform apply -parallelism=1

# ── 8. 대규모 State 성능 문제 ──
# 해결: State 분리 (여러 Terraform 프로젝트로)
# network/ compute/ database/ 등으로 분리하고
# terraform_remote_state data source로 연결

22.2 Ansible 트러블슈팅

# ── 1. SSH 연결 실패 ──
# 에러: UNREACHABLE!
# 디버그:
ansible webservers -m ping -vvvv  # 최대 상세 로그

# SSH 직접 테스트
ssh -i ~/.ssh/key.pem -o StrictHostKeyChecking=no ubuntu@10.0.1.10

# ── 2. sudo 권한 문제 ──
# 에러: Missing sudo password
# 해결:
ansible-playbook site.yml --ask-become-pass
# 또는 ansible.cfg에 설정:
# [privilege_escalation]
# become_ask_pass = True

# ── 3. 모듈을 찾을 수 없음 ──
# 에러: MODULE FAILURE
# 해결: Collection 설치
ansible-galaxy collection install amazon.aws

# ── 4. Jinja2 템플릿 오류 ──
# 에러: AnsibleUndefinedVariable
# 해결: 변수 정의 확인
ansible-playbook site.yml -e "@vars/defaults.yml" --check
# 또는 default 필터 사용: "{{ my_var | default('fallback') }}"

# ── 5. 성능 최적화 ──
# ansible.cfg 설정:
# [defaults]
# forks = 20                    # 병렬 실행 수 증가
# gathering = smart             # 팩트 캐싱
# [ssh_connection]
# pipelining = True             # SSH 파이프라이닝 활성화
# ssh_args = -o ControlMaster=auto -o ControlPersist=60s

# ── 6. Vault 비밀번호 분실 ──
# 해결: 복구 불가. 새 비밀번호로 재암호화 필요
# 비밀번호를 기억하는 동안:
ansible-vault rekey secrets.yml

# ── 7. 인벤토리 파싱 오류 ──
# 디버그: 인벤토리 유효성 검사
ansible-inventory -i inventory/hosts.yml --list --export

# ── 8. 멱등성 깨짐 (changed 반복) ──
# command/shell 모듈에 creates/removes 조건 추가
- name: Initialize database
  command: /opt/app/init_db.sh
  args:
    creates: /opt/app/.db_initialized

23. 베스트 프랙티스 요약

23.1 Terraform 베스트 프랙티스

항목권장 사항
State 관리Remote Backend(S3 + DynamoDB) 사용, 절대 Git에 커밋하지 않음
디렉토리 구조환경별 분리 (envs/dev, envs/prod) 또는 Workspace 사용
코드 리뷰terraform plan 출력을 PR에 첨부
모듈화재사용 가능한 모듈로 분리, 버전 태깅
변수 관리민감 변수는 sensitive = true, 환경 변수 또는 Vault 활용
잠금 파일.terraform.lock.hcl은 반드시 Git에 커밋
CI/CDPlan은 PR에서, Apply는 merge 후 자동 실행
명명 규칙project-env-resource 패턴 일관되게 사용

23.2 Ansible 베스트 프랙티스

항목권장 사항
인벤토리클라우드 환경은 동적 인벤토리 사용
민감 데이터Ansible Vault로 반드시 암호화
Role 사용태스크를 Role로 구조화, Galaxy Role 적극 활용
멱등성command/shell 대신 전용 모듈 사용, creates/removes 활용
테스트--check --diff로 사전 검증, Molecule로 Role 테스트
변수 우선순위변수 우선순위를 이해하고, 적절한 위치에 변수 정의
태그 활용모든 태스크에 태그를 붙여 선택적 실행 가능하게
로그callback_whitelist = timer, profile_tasks로 성능 측정

24. 참고 자료

24.1 공식 문서

24.2 추천 학습 리소스

24.3 관련 인증

  • HashiCorp Certified: Terraform Associate (003): Terraform 기초 인증
  • Red Hat Certified Specialist in Ansible Automation (EX374): Ansible 전문가 인증

24.4 커뮤니티


Terraform과 Ansible은 현대 인프라 자동화의 양대 축이다. Terraform으로 인프라를 선언적으로 프로비저닝하고, Ansible로 그 위에 소프트웨어를 구성하는 패턴은 업계의 사실상 표준으로 자리 잡았다. 이 글에서 다룬 명령어와 패턴들을 실무에 적용하면서, 반복적인 수작업을 코드로 대체하고, 인프라를 안전하고 예측 가능하게 관리하는 문화를 팀에 정착시키길 바란다.