์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ ๋ณ€๊ฒฝ ์ž๋™ํ™”: Terraform๊ณผ CI/CD๋กœ ์™„์„ฑํ•˜๋Š” ์Šค๋งˆํŠธํ•œ ๋ฐฐํฌ ์ „๋žต

๐Ÿš€ ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ ๋ณ€๊ฒฝ ์ž๋™ํ™”: Terraform๊ณผ CI/CD๋กœ ์™„์„ฑํ•˜๋Š” ์Šค๋งˆํŠธํ•œ ๋ฐฐํฌ ์ „๋žต

์ฝ”๋“œ ํ•œ ์ค„๋กœ ์ธํ”„๋ผ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ์„ธ๊ณ„๋กœ ์ดˆ๋Œ€ํ•ฉ๋‹ˆ๋‹ค! โœจ

์•ˆ๋…•, ์นœ๊ตฌ! ๐Ÿ˜Š ํ˜น์‹œ ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ๋ฅผ ์ˆ˜๋™์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด์„œ "์ด๊ฑฐ ์‹ค์ˆ˜ํ•˜๋ฉด ์–ด์ฉŒ์ง€?" ํ•˜๊ณ  ์‹์€๋•€ ํ˜๋ฆฐ ์  ์žˆ์–ด? ์•„๋‹ˆ๋ฉด ์„œ๋ฒ„ ์„ค์ •์„ ๋ฐ”๊พธ๋ ค๊ณ  AWS ์ฝ˜์†”์„ ํด๋ฆญํด๋ฆญํ•˜๋‹ค๊ฐ€ "์•„, ์ด๊ฑฐ ์–ด๋””๊นŒ์ง€ ํ–ˆ๋”๋ผ?" ํ•˜๊ณ  ํ—ท๊ฐˆ๋ฆฐ ์ ์€? ๐Ÿค”

๋‚˜๋„ ๊ทธ๋žฌ์–ด. ๊ทธ๋Ÿฐ๋ฐ Terraform๊ณผ CI/CD๋ฅผ ๋งŒ๋‚˜๊ณ  ๋‚˜์„œ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด ์„ธ์ƒ์ด ์—ด๋ ธ์ง€! ์ด์ œ๋Š” ์ฝ”๋“œ ๋ช‡ ์ค„๋กœ ์ˆ˜๋ฐฑ ๊ฐœ์˜ ์„œ๋ฒ„๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ์‹ค์ˆ˜ ์—†์ด ์ž๋™์œผ๋กœ ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋๊ฑฐ๋“ . ๋งˆ์น˜ ๋งˆ๋ฒ• ๊ฐ™์€ ์ผ์ด์•ผ! โœจ

์˜ค๋Š˜์€ ์ด ๋†€๋ผ์šด ๊ธฐ์ˆ ๋“ค์„ ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•˜๋“ฏ์ด ์‰ฝ๊ณ  ์žฌ๋ฏธ์žˆ๊ฒŒ ํ’€์–ด๋ณผ๊ฒŒ. ์žฌ๋Šฅ๋„ท์—์„œ DevOps ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋ฐฐ์šฐ๊ณ  ์‹ถ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ๋„ ๋”ฑ ๋งž๋Š” ๋‚ด์šฉ์ด๋‹ˆ๊นŒ, ๋๊นŒ์ง€ ํ•จ๊ป˜ ๊ฐ€๋ณด์ž! ๐ŸŽฏ

๐ŸŽฏ ์™œ ์ธํ”„๋ผ ์ž๋™ํ™”๊ฐ€ ํ•„์š”ํ• ๊นŒ?

๋จผ์ € ์ด์•ผ๊ธฐ๋ฅผ ํ•˜๋‚˜ ํ•ด๋ณผ๊ฒŒ. ๋‚ด ์นœ๊ตฌ ๋ฏผ์ˆ˜(๊ฐ€๋ช…)๋Š” ์Šคํƒ€ํŠธ์—…์—์„œ ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž๋กœ ์ผํ•˜๊ณ  ์žˆ์–ด. ์–ด๋А ๋‚  ์„œ๋น„์Šค๊ฐ€ ๊ฐ‘์ž๊ธฐ ํ„ฐ์กŒ๋Š”๋ฐ, ์•Œ๊ณ  ๋ณด๋‹ˆ ๋ˆ„๊ตฐ๊ฐ€ AWS ์ฝ˜์†”์—์„œ ๋ณด์•ˆ ๊ทธ๋ฃน ์„ค์ •์„ ์ž˜๋ชป ๊ฑด๋“œ๋ฆฐ ๊ฑฐ์˜€์–ด. ๐Ÿ˜ฑ

๋ฌธ์ œ๋Š” ๋ˆ„๊ฐ€, ์–ธ์ œ, ๋ฌด์—‡์„ ๋ฐ”๊ฟจ๋Š”์ง€ ์ถ”์ ์ด ์•ˆ ๋œ๋‹ค๋Š” ๊ฑฐ์•ผ. ํŒ€์›๋“ค๋ผ๋ฆฌ "๋‚ด๊ฐ€ ์•„๋‹Œ๋ฐ?" ํ•˜๋ฉด์„œ ์„œ๋กœ ๋ˆˆ์น˜๋งŒ ๋ณด๋‹ค๊ฐ€ ๊ฒฐ๊ตญ ๋ช‡ ์‹œ๊ฐ„ ๋™์•ˆ ์„œ๋น„์Šค๊ฐ€ ๋‹ค์šด๋์ง€. ์ด๋Ÿฐ ์ƒํ™ฉ, ์ •๋ง ๋”์ฐํ•˜์ง€ ์•Š์•„?

๐Ÿ’ฅ ์ˆ˜๋™ ์ธํ”„๋ผ ๊ด€๋ฆฌ์˜ ๋ฌธ์ œ์ ๋“ค

1. ํœด๋จผ ์—๋Ÿฌ์˜ ์œ„ํ—˜ ๐Ÿ™ˆ
์‚ฌ๋žŒ์ด ์ง์ ‘ ํด๋ฆญํ•˜๊ณ  ํƒ€์ดํ•‘ํ•˜๋‹ค ๋ณด๋ฉด ์‹ค์ˆ˜๋Š” ํ•„์—ฐ์ ์ด์•ผ. ํŠนํžˆ ์ƒˆ๋ฒฝ 3์‹œ์— ๊ธด๊ธ‰ ๋ฐฐํฌํ•  ๋•Œ๋Š” ๋”๋”์šฑ!

2. ์ผ๊ด€์„ฑ ๋ถ€์กฑ ๐ŸŽญ
๊ฐœ๋ฐœ ํ™˜๊ฒฝ, ์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ, ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ... ๊ฐ๊ฐ ์„ค์ •์ด ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅด๋ฉด "๋‚ด ์ปดํ“จํ„ฐ์—์„  ๋˜๋Š”๋ฐ?"๋ผ๋Š” ์•…๋ชฝ์ด ์‹œ์ž‘๋ผ.

3. ์ถ”์  ๋ถˆ๊ฐ€๋Šฅ ๐Ÿ•ต๏ธ
๋ˆ„๊ฐ€ ์–ธ์ œ ๋ฌด์—‡์„ ๋ฐ”๊ฟจ๋Š”์ง€ ๊ธฐ๋ก์ด ์—†์œผ๋ฉด, ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ์›์ธ์„ ์ฐพ๊ธฐ๊ฐ€ ์ •๋ง ์–ด๋ ค์›Œ.

4. ํ™•์žฅ์„ฑ ํ•œ๊ณ„ ๐Ÿ“ˆ
์„œ๋ฒ„ 10๋Œ€๋Š” ์ˆ˜๋™์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด๋„, 100๋Œ€, 1000๋Œ€๊ฐ€ ๋˜๋ฉด? ์ƒ์ƒ๋งŒ ํ•ด๋„ ๋”์ฐํ•˜์ง€.

5. ์ง€์‹์˜ ์‚ฌ์ผ๋กœํ™” ๐Ÿ๏ธ
"์ด ์„ค์ •์€ ์ฒ ์ˆ˜๋งŒ ์•Œ์•„"๋ผ๋Š” ์ƒํ™ฉ์ด ๋˜๋ฉด, ์ฒ ์ˆ˜๊ฐ€ ํœด๊ฐ€ ๊ฐ€๋ฉด ํŒ€ ์ „์ฒด๊ฐ€ ๋ฉˆ์ถฐ๋ฒ„๋ ค.

์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ Infrastructure as Code (IaC)์•ผ! ์ธํ”„๋ผ๋ฅผ ์ฝ”๋“œ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฑฐ์ง€. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ค‘์‹ฌ์— Terraform์ด ์žˆ์–ด. ๐ŸŽช

์ˆ˜๋™ ๊ด€๋ฆฌ ๐Ÿ˜ฐ ํœด๋จผ ์—๋Ÿฌ ๐ŸŽญ ์ผ๊ด€์„ฑ ๋ถ€์กฑ ๐Ÿ•ต๏ธ ์ถ”์  ๋ถˆ๊ฐ€ ๐Ÿ“ˆ ํ™•์žฅ ํ•œ๊ณ„ ๐Ÿ๏ธ ์ง€์‹ ์‚ฌ์ผ๋กœ ์ž๋™ํ™” ๊ด€๋ฆฌ โœ… ์•ˆ์ •์„ฑ ๐ŸŽฏ ์ผ๊ด€์„ฑ ๐Ÿ“ ์ถ”์  ๊ฐ€๋Šฅ ๐Ÿš€ ๋ฌดํ•œ ํ™•์žฅ ๐Ÿค ์ง€์‹ ๊ณต์œ 

๐Ÿ—๏ธ Terraform์ด ๋ญ๊ธธ๋ž˜?

Terraform์€ HashiCorp๋ผ๋Š” ํšŒ์‚ฌ์—์„œ ๋งŒ๋“  ์˜คํ”ˆ์†Œ์Šค IaC ๋„๊ตฌ์•ผ. ๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด, ์ธํ”„๋ผ๋ฅผ ์ฝ”๋“œ๋กœ ์ •์˜ํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋งˆ๋ฒ• ๋„๊ตฌ์ง€! ๐Ÿช„

์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ. AWS์—์„œ EC2 ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๋ ค๋ฉด ๋ณดํ†ต ์ด๋ ‡๊ฒŒ ํ•˜์ž–์•„?

์ „ํ†ต์ ์ธ ๋ฐฉ๋ฒ•:
1. AWS ์ฝ˜์†”์— ๋กœ๊ทธ์ธ ๐Ÿ–ฑ๏ธ
2. EC2 ์„œ๋น„์Šค๋กœ ์ด๋™
3. "์ธ์Šคํ„ด์Šค ์‹œ์ž‘" ๋ฒ„ํŠผ ํด๋ฆญ
4. AMI ์„ ํƒ
5. ์ธ์Šคํ„ด์Šค ํƒ€์ž… ์„ ํƒ
6. ๋„คํŠธ์›Œํฌ ์„ค์ •
7. ์Šคํ† ๋ฆฌ์ง€ ์„ค์ •
8. ํƒœ๊ทธ ์ถ”๊ฐ€
9. ๋ณด์•ˆ ๊ทธ๋ฃน ์„ค์ •
10. ํ‚ค ํŽ˜์–ด ์„ ํƒ
11. ์ตœ์ข… ํ™•์ธ ๋ฐ ์‹œ์ž‘

์ด ์†Œ์š” ์‹œ๊ฐ„: ์•ฝ 10-15๋ถ„ (ํด๋ฆญ ํฌํ•จ) ๐Ÿ˜“

ํ•˜์ง€๋งŒ Terraform์„ ์‚ฌ์šฉํ•˜๋ฉด?

Terraform ๋ฐฉ๋ฒ•:

resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = {
    Name = "MyWebServer"
  }
}

๊ทธ๋ฆฌ๊ณ  ํ„ฐ๋ฏธ๋„์—์„œ:
terraform apply

์ด ์†Œ์š” ์‹œ๊ฐ„: ์•ฝ 2-3๋ถ„ (์ฝ”๋“œ ์ž‘์„ฑ + ์‹คํ–‰) ๐Ÿš€
๐ŸŽ‰ Terraform์˜ ํ•ต์‹ฌ ์žฅ์  1. ์„ ์–ธ์  ๋ฌธ๋ฒ• (Declarative)
"์–ด๋–ป๊ฒŒ" ๋งŒ๋“ค์ง€๊ฐ€ ์•„๋‹ˆ๋ผ "๋ฌด์—‡์„" ๋งŒ๋“ค์ง€๋งŒ ์ •์˜ํ•˜๋ฉด ๋ผ. Terraform์ด ์•Œ์•„์„œ ์ตœ์ ์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ๋งŒ๋“ค์–ด์ค˜!

2. ๋ฉ€ํ‹ฐ ํด๋ผ์šฐ๋“œ ์ง€์›
AWS, Azure, GCP, ์‹ฌ์ง€์–ด ์˜จํ”„๋ ˆ๋ฏธ์Šค๊นŒ์ง€! ํ•˜๋‚˜์˜ ๋„๊ตฌ๋กœ ๋ชจ๋“  ํด๋ผ์šฐ๋“œ๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ๐ŸŒ

3. ์ƒํƒœ ๊ด€๋ฆฌ (State Management)
ํ˜„์žฌ ์ธํ”„๋ผ์˜ ์ƒํƒœ๋ฅผ ์ถ”์ ํ•ด์„œ, ๋ณ€๊ฒฝ์‚ฌํ•ญ๋งŒ ์ •ํ™•ํ•˜๊ฒŒ ์ ์šฉํ•ด. ๋˜‘๋˜‘ํ•˜์ง€?

4. ์‹คํ–‰ ๊ณ„ํš (Plan)
์‹ค์ œ๋กœ ์ ์šฉํ•˜๊ธฐ ์ „์— "์ด๋ ‡๊ฒŒ ๋ฐ”๋€” ๊ฑฐ์˜ˆ์š”"๋ผ๊ณ  ๋ฏธ๋ฆฌ ๋ณด์—ฌ์ค˜. ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์ง€!

5. ๋ชจ๋“ˆํ™”
์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ ๋ธ”๋ก์„ ๋งŒ๋“ค์–ด์„œ, ๋งˆ์น˜ ๋ ˆ๊ณ  ๋ธ”๋ก์ฒ˜๋Ÿผ ์กฐ๋ฆฝํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿงฑ

๐Ÿ“š Terraform์˜ ํ•ต์‹ฌ ๊ฐœ๋…๋“ค

1. Provider (ํ”„๋กœ๋ฐ”์ด๋”)

Provider๋Š” Terraform์ด ์–ด๋–ค ํด๋ผ์šฐ๋“œ๋‚˜ ์„œ๋น„์Šค์™€ ํ†ต์‹ ํ• ์ง€ ์ •์˜ํ•˜๋Š” ๊ฑฐ์•ผ. AWS, Azure, GCP ๋“ฑ๋“ฑ ๋ง์ด์•ผ.

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

provider "aws" {
  region = "ap-northeast-2"  # ์„œ์šธ ๋ฆฌ์ „
}

2. Resource (๋ฆฌ์†Œ์Šค)

์‹ค์ œ๋กœ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ธํ”„๋ผ ๊ตฌ์„ฑ ์š”์†Œ์•ผ. EC2, RDS, S3 ๋ฒ„ํ‚ท ๋“ฑ๋“ฑ!

resource "aws_s3_bucket" "my_bucket" {
  bucket = "my-awesome-bucket-2024"
  
  tags = {
    Environment = "Production"
    Team        = "DevOps"
  }
}

3. Variable (๋ณ€์ˆ˜)

์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋งˆ๋ฒ•! ํ™˜๊ฒฝ๋ณ„๋กœ ๋‹ค๋ฅธ ๊ฐ’์„ ์‰ฝ๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

variable "instance_type" {
  description = "EC2 ์ธ์Šคํ„ด์Šค ํƒ€์ž…"
  type        = string
  default     = "t2.micro"
}

resource "aws_instance" "app_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type
}

4. Output (์ถœ๋ ฅ)

์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค์˜ ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•ด์„œ ๋‹ค๋ฅธ ๊ณณ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜.

output "instance_public_ip" {
  description = "EC2 ์ธ์Šคํ„ด์Šค์˜ ๊ณต๊ฐœ IP"
  value       = aws_instance.app_server.public_ip
}

5. Module (๋ชจ๋“ˆ)

์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ Terraform ์ฝ”๋“œ ๋ฌถ์Œ! ๋งˆ์น˜ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

module "vpc" {
  source = "./modules/vpc"
  
  vpc_cidr = "10.0.0.0/16"
  environment = "production"
}
๐Ÿ’ก ์‹ค์ „ ํŒ: Terraform์„ ์ฒ˜์Œ ๋ฐฐ์šธ ๋•Œ๋Š” ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ด. ์˜ˆ๋ฅผ ๋“ค์–ด S3 ๋ฒ„ํ‚ท ํ•˜๋‚˜ ๋งŒ๋“ค๊ธฐ๋ถ€ํ„ฐ! ๊ทธ๋ฆฌ๊ณ  ์ ์  ๋ณต์žกํ•œ ์ธํ”„๋ผ๋กœ ํ™•์žฅํ•ด ๋‚˜๊ฐ€๋Š” ๊ฑฐ์•ผ. ์žฌ๋Šฅ๋„ท์—์„œ Terraform ๋ฉ˜ํ† ๋ฅผ ์ฐพ์•„์„œ 1:1๋กœ ๋ฐฐ์šฐ๋Š” ๊ฒƒ๋„ ์ •๋ง ํšจ๊ณผ์ ์ด์•ผ! ๐ŸŽ“
Terraform Provider AWS Azure Resource EC2 S3 Variable ํ™˜๊ฒฝ ์„ค์ • ์žฌ์‚ฌ์šฉ Module ์ฝ”๋“œ ๋ฌถ์Œ ์žฌ์‚ฌ์šฉ์„ฑ State ์ƒํƒœ ์ถ”์  Output ์ •๋ณด ์ถœ๋ ฅ

โš™๏ธ Terraform ์›Œํฌํ”Œ๋กœ์šฐ: ์‹ค์ „ ์‚ฌ์šฉ๋ฒ•

์ž, ์ด์ œ Terraform์„ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ๊นŒ? Terraform์˜ ์›Œํฌํ”Œ๋กœ์šฐ๋Š” ํฌ๊ฒŒ 4๋‹จ๊ณ„๋กœ ๋‚˜๋‰˜์–ด. ์ด๊ฑธ ์ดํ•ดํ•˜๋ฉด Terraform ๋งˆ์Šคํ„ฐ์˜ ์ ˆ๋ฐ˜์€ ์˜จ ๊ฑฐ์•ผ! ๐Ÿ’ช

๐Ÿ”„ Terraform 4๋‹จ๊ณ„ ์›Œํฌํ”Œ๋กœ์šฐ

1๏ธโƒฃ Write (์ž‘์„ฑ)

๋จผ์ € .tf ํŒŒ์ผ์— ์›ํ•˜๋Š” ์ธํ”„๋ผ๋ฅผ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•ด. HCL(HashiCorp Configuration Language)์ด๋ผ๋Š” ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ •๋ง ์ฝ๊ธฐ ์‰ฌ์›Œ!

# main.tf
terraform {
  required_version = ">= 1.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

# VPC ์ƒ์„ฑ
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  
  tags = {
    Name        = "main-vpc"
    Environment = var.environment
  }
}

# ์„œ๋ธŒ๋„ท ์ƒ์„ฑ
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-northeast-2a"
  map_public_ip_on_launch = true
  
  tags = {
    Name = "public-subnet"
  }
}
2๏ธโƒฃ Init (์ดˆ๊ธฐํ™”)

terraform init ๋ช…๋ น์–ด๋กœ Terraform์„ ์ดˆ๊ธฐํ™”ํ•ด. ์ด ๊ณผ์ •์—์„œ ํ•„์š”ํ•œ Provider ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์„ ๋‹ค์šด๋กœ๋“œ๋ฐ›์•„.

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.0"...
- Installing hashicorp/aws v4.67.0...
- Installed hashicorp/aws v4.67.0

Terraform has been successfully initialized! ๐ŸŽ‰

๐Ÿ’ก ์•Œ์•„๋‘๋ฉด ์ข‹์•„์š”: .terraform ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์ƒ์„ฑ๋˜๋Š”๋ฐ, ์—ฌ๊ธฐ์— Provider ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์ด ์ €์žฅ๋ผ. ์ด ํด๋”๋Š” .gitignore์— ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒŒ ์ข‹์•„!
3๏ธโƒฃ Plan (๊ณ„ํš)

terraform plan์œผ๋กœ ์‹คํ–‰ ๊ณ„ํš์„ ํ™•์ธํ•ด. ์‹ค์ œ๋กœ ์ ์šฉํ•˜๊ธฐ ์ „์— "์ด๋ ‡๊ฒŒ ๋ฐ”๋€” ๊ฑฐ์˜ˆ์š”"๋ผ๊ณ  ๋ฏธ๋ฆฌ ๋ณด์—ฌ์ฃผ๋Š” ๊ฑฐ์•ผ. ์ด๊ฒŒ ์ง„์งœ ์ค‘์š”ํ•ด! ๐Ÿ”

$ terraform plan

Terraform will perform the following actions:

  # aws_vpc.main will be created
  + resource "aws_vpc" "main" {
      + cidr_block           = "10.0.0.0/16"
      + enable_dns_hostnames = true
      + enable_dns_support   = true
      + id                   = (known after apply)
      + tags                 = {
          + "Environment" = "production"
          + "Name"        = "main-vpc"
        }
    }

  # aws_subnet.public will be created
  + resource "aws_subnet" "public" {
      + cidr_block              = "10.0.1.0/24"
      + vpc_id                  = (known after apply)
      + availability_zone       = "ap-northeast-2a"
      + map_public_ip_on_launch = true
      + tags                    = {
          + "Name" = "public-subnet"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ์ ! โœจ
- + (์ดˆ๋ก์ƒ‰): ์ƒˆ๋กœ ์ƒ์„ฑ๋  ๋ฆฌ์†Œ์Šค
- ~ (๋…ธ๋ž€์ƒ‰): ์ˆ˜์ •๋  ๋ฆฌ์†Œ์Šค
- - (๋นจ๊ฐ„์ƒ‰): ์‚ญ์ œ๋  ๋ฆฌ์†Œ์Šค

์ด๋ ‡๊ฒŒ ๋ฏธ๋ฆฌ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ๊นŒ ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์–ด!
4๏ธโƒฃ Apply (์ ์šฉ)

๋ชจ๋“  ๊ฒŒ ๊ดœ์ฐฎ์•„ ๋ณด์ด๋ฉด terraform apply๋กœ ์‹ค์ œ๋กœ ์ ์šฉํ•ด!

$ terraform apply

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 3s [id=vpc-0123456789abcdef]
aws_subnet.public: Creating...
aws_subnet.public: Creation complete after 2s [id=subnet-0123456789abcdef]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

์งœ์ž”! ๐ŸŽŠ ์ด์ œ AWS์— ์‹ค์ œ๋กœ VPC์™€ ์„œ๋ธŒ๋„ท์ด ์ƒ์„ฑ๋์–ด!
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ: terraform apply๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ํ•ญ์ƒ terraform plan์„ ๋จผ์ € ํ™•์ธํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์—ฌ! ํŠนํžˆ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋”๋”์šฑ! ํ•œ ๋ฒˆ์˜ ์‹ค์ˆ˜๊ฐ€ ์„œ๋น„์Šค ์ „์ฒด๋ฅผ ๋‹ค์šด์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐Ÿ˜ฑ

๐Ÿ—‘๏ธ ๋ณด๋„ˆ์Šค: Destroy (์‚ญ์ œ)

ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚˜๊ณ  ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š” terraform destroy๋ฅผ ์‚ฌ์šฉํ•ด. ์ƒ์„ฑํ•œ ๋ชจ๋“  ๋ฆฌ์†Œ์Šค๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์‚ญ์ œํ•ด์ค˜!

$ terraform destroy

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_subnet.public: Destroying... [id=subnet-0123456789abcdef]
aws_subnet.public: Destruction complete after 1s
aws_vpc.main: Destroying... [id=vpc-0123456789abcdef]
aws_vpc.main: Destruction complete after 2s

Destroy complete! Resources: 2 destroyed.
๐ŸŽฏ ํ”„๋กœ ํŒ: ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ์ž‘์—…์ด ๋๋‚˜๋ฉด destroy๋กœ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ญ์ œํ•ด์„œ ๋น„์šฉ์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์–ด. AWS๋Š” ์‚ฌ์šฉํ•œ ๋งŒํผ๋งŒ ๊ณผ๊ธˆ๋˜๋‹ˆ๊นŒ! ๐Ÿ’ฐ
Terraform ์›Œํฌํ”Œ๋กœ์šฐ 1. Write ์ฝ”๋“œ ์ž‘์„ฑ 2. Init ์ดˆ๊ธฐํ™” 3. Plan ์‹คํ–‰ ๊ณ„ํš ํ™•์ธ 4. Apply ์ธํ”„๋ผ ์ƒ์„ฑ! ๐Ÿ“ โš™๏ธ ๐Ÿ” ๐Ÿš€ ๐Ÿ’ป ๐Ÿ“ฆ โœ… ๐ŸŽ‰

๐Ÿ” State ๊ด€๋ฆฌ: Terraform์˜ ๋‘๋‡Œ

Terraform์˜ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฐœ๋… ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ State(์ƒํƒœ)์•ผ. ์ด๊ฒŒ ๋ญ๋ƒ๋ฉด, Terraform์ด ๊ด€๋ฆฌํ•˜๋Š” ์ธํ”„๋ผ์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ธฐ๋กํ•˜๋Š” ํŒŒ์ผ์ด์•ผ. ๋งˆ์น˜ ์ธํ”„๋ผ์˜ "์‚ฌ์ง„"์„ ์ฐ์–ด๋‘๋Š” ๊ฑฐ์ง€! ๐Ÿ“ธ

๐Ÿง  State ํŒŒ์ผ์ด ํ•˜๋Š” ์ผ

1. ํ˜„์žฌ ์ƒํƒœ ์ถ”์ 
์–ด๋–ค ๋ฆฌ์†Œ์Šค๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์žˆ๋Š”์ง€, ๊ฐ ๋ฆฌ์†Œ์Šค์˜ ID๋Š” ๋ฌด์—‡์ธ์ง€ ๋“ฑ์„ ๊ธฐ๋กํ•ด.

2. ๋ณ€๊ฒฝ์‚ฌํ•ญ ๊ณ„์‚ฐ
์ฝ”๋“œ์™€ ์‹ค์ œ ์ธํ”„๋ผ๋ฅผ ๋น„๊ตํ•ด์„œ ๋ฌด์—‡์„ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œํ•ด์•ผ ํ•˜๋Š”์ง€ ๊ณ„์‚ฐํ•ด.

3. ์„ฑ๋Šฅ ์ตœ์ ํ™”
๋งค๋ฒˆ ํด๋ผ์šฐ๋“œ API๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ , State ํŒŒ์ผ์„ ์ฐธ์กฐํ•ด์„œ ๋น ๋ฅด๊ฒŒ ๋™์ž‘ํ•ด.

4. ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ €์žฅ
๋ฆฌ์†Œ์Šค ๊ฐ„์˜ ์˜์กด์„ฑ ์ •๋ณด ๋“ฑ์„ ์ €์žฅํ•ด.

๐Ÿ“ Local State vs Remote State

State ํŒŒ์ผ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋กœ์ปฌ์— terraform.tfstate๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ €์žฅ๋ผ. ํ•˜์ง€๋งŒ ํŒ€์œผ๋กœ ์ผํ•  ๋•Œ๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ!

โš ๏ธ Local State์˜ ๋ฌธ์ œ์  1. ํ˜‘์—… ๋ถˆ๊ฐ€: ๋‚ด ์ปดํ“จํ„ฐ์—๋งŒ State ํŒŒ์ผ์ด ์žˆ์œผ๋ฉด, ํŒ€์›๋“ค์€ ์–ด๋–ป๊ฒŒ ํ•ด? ๐Ÿ˜ฐ
2. ๋™์‹œ ์ˆ˜์ • ์œ„ํ—˜: ๋‘ ๋ช…์ด ๋™์‹œ์— applyํ•˜๋ฉด ์ถฉ๋Œ ๋ฐœ์ƒ!
3. ๋ฐฑ์—… ๋ถ€์žฌ: ์ปดํ“จํ„ฐ๊ฐ€ ๊ณ ์žฅ๋‚˜๋ฉด? State ํŒŒ์ผ๋„ ๋‚ ์•„๊ฐ€!
4. ๋ณด์•ˆ ์œ„ํ—˜: State ํŒŒ์ผ์—๋Š” ๋ฏผ๊ฐํ•œ ์ •๋ณด(๋น„๋ฐ€๋ฒˆํ˜ธ, API ํ‚ค ๋“ฑ)๊ฐ€ ๋“ค์–ด์žˆ์–ด.

๊ทธ๋ž˜์„œ ์‹ค๋ฌด์—์„œ๋Š” Remote State๋ฅผ ์‚ฌ์šฉํ•ด! ์ฃผ๋กœ S3๋‚˜ Terraform Cloud๋ฅผ ์‚ฌ์šฉํ•˜์ง€.

โœ… Remote State ์„ค์ • ์˜ˆ์‹œ (S3 + DynamoDB)

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "production/terraform.tfstate"
    region         = "ap-northeast-2"
    encrypt        = true
    dynamodb_table = "terraform-lock"  # State Lock์šฉ
  }
}

# S3 ๋ฒ„ํ‚ท ์ƒ์„ฑ (๋ณ„๋„๋กœ ๋จผ์ € ์ƒ์„ฑ ํ•„์š”)
resource "aws_s3_bucket" "terraform_state" {
  bucket = "my-terraform-state-bucket"
  
  lifecycle {
    prevent_destroy = true  # ์‹ค์ˆ˜๋กœ ์‚ญ์ œ ๋ฐฉ์ง€!
  }
}

# ๋ฒ„ํ‚ท ์•”ํ˜ธํ™” ์„ค์ •
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# ๋ฒ„์ „ ๊ด€๋ฆฌ ํ™œ์„ฑํ™”
resource "aws_s3_bucket_versioning" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  versioning_configuration {
    status = "Enabled"
  }
}

# DynamoDB ํ…Œ์ด๋ธ” ์ƒ์„ฑ (State Lock์šฉ)
resource "aws_dynamodb_table" "terraform_lock" {
  name           = "terraform-lock"
  billing_mode   = "PAY_PER_REQUEST"
  hash_key       = "LockID"
  
  attribute {
    name = "LockID"
    type = "S"
  }
}
๐Ÿ’ก State Lock์ด ๋ญ์•ผ? ์—ฌ๋Ÿฌ ๋ช…์ด ๋™์‹œ์— Terraform์„ ์‹คํ–‰ํ•˜๋ฉด State ํŒŒ์ผ์ด ๊ผฌ์ผ ์ˆ˜ ์žˆ์–ด. State Lock์€ ํ•œ ๋ฒˆ์— ํ•œ ๋ช…๋งŒ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๊ฒŒ "์ž ๊ธˆ"์„ ๊ฑฐ๋Š” ๊ฑฐ์•ผ. DynamoDB๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ง€! ๐Ÿ”’

๐Ÿ› ๏ธ State ๊ด€๋ฆฌ ๋ช…๋ น์–ด๋“ค

State๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•ด์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ์–ด. ์กฐ์‹ฌํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜์ง€๋งŒ, ์•Œ์•„๋‘๋ฉด ์ •๋ง ์œ ์šฉํ•ด!

# State ํŒŒ์ผ ๋‚ด์šฉ ํ™•์ธ
$ terraform state list

# ํŠน์ • ๋ฆฌ์†Œ์Šค์˜ ์ƒ์„ธ ์ •๋ณด ํ™•์ธ
$ terraform state show aws_instance.web_server

# ๋ฆฌ์†Œ์Šค๋ฅผ State์—์„œ ์ œ๊ฑฐ (์‹ค์ œ ๋ฆฌ์†Œ์Šค๋Š” ์‚ญ์ œ ์•ˆ ๋จ)
$ terraform state rm aws_instance.old_server

# ๋ฆฌ์†Œ์Šค๋ฅผ ๋‹ค๋ฅธ ์ด๋ฆ„์œผ๋กœ ์ด๋™
$ terraform state mv aws_instance.old aws_instance.new

# State ํŒŒ์ผ์„ ์›๊ฒฉ์—์„œ ๋กœ์ปฌ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
$ terraform state pull > terraform.tfstate

# ๋กœ์ปฌ State๋ฅผ ์›๊ฒฉ์œผ๋กœ ํ‘ธ์‹œ
$ terraform state push terraform.tfstate
โš ๏ธ ๊ฒฝ๊ณ : State ํŒŒ์ผ์„ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋Š” ๊ฑด ์ •๋ง ์œ„ํ—˜ํ•ด! ๊ฐ€๋Šฅํ•˜๋ฉด Terraform ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์กฐ์ž‘ํ•˜๊ณ , ์ง์ ‘ ํŽธ์ง‘์€ ํ”ผํ•˜๋Š” ๊ฒŒ ์ข‹์•„. ๋ฐฑ์—…์€ ํ•„์ˆ˜! ๐Ÿšจ

๐Ÿ”„ CI/CD์™€ Terraform์˜ ๋งŒ๋‚จ

์ž, ์ด์ œ ์ง„์งœ ์žฌ๋ฏธ์žˆ๋Š” ๋ถ€๋ถ„์ด์•ผ! Terraform์„ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ํ†ตํ•ฉํ•˜๋ฉด, ์ธํ”„๋ผ ๋ณ€๊ฒฝ๋„ ์ฝ”๋“œ ๋ฐฐํฌ์ฒ˜๋Ÿผ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์–ด. ์ด๊ฒŒ ๋ฐ”๋กœ GitOps์˜ ํ•ต์‹ฌ์ด์ง€! ๐ŸŽฏ

๐ŸŽญ ์ „ํ†ต์ ์ธ ๋ฐฉ์‹ vs CI/CD ๋ฐฉ์‹

๊ตฌ๋ถ„ ์ „ํ†ต์ ์ธ ๋ฐฉ์‹ CI/CD ๋ฐฉ์‹
๋ฐฐํฌ ๋ฐฉ๋ฒ• ๊ฐœ๋ฐœ์ž๊ฐ€ ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰ Git push๋งŒ ํ•˜๋ฉด ์ž๋™ ๋ฐฐํฌ
๊ฒ€์ฆ ๊ฐœ๋ฐœ์ž์˜ ๋ˆˆ์œผ๋กœ๋งŒ ํ™•์ธ ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ์™€ ๊ฒ€์ฆ
์Šน์ธ ํ”„๋กœ์„ธ์Šค ๊ตฌ๋‘ ๋˜๋Š” ์ด๋ฉ”์ผ Pull Request ๋ฆฌ๋ทฐ
๋กค๋ฐฑ ์ˆ˜๋™์œผ๋กœ ์ด์ „ ์ƒํƒœ ๋ณต๊ตฌ Git revert๋กœ ์ž๋™ ๋กค๋ฐฑ
๊ฐ์‚ฌ ์ถ”์  ๋ณ„๋„ ๋ฌธ์„œ ์ž‘์„ฑ Git ํžˆ์Šคํ† ๋ฆฌ์— ์ž๋™ ๊ธฐ๋ก

๐Ÿ—๏ธ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์กฐ

Terraform์„ CI/CD์— ํ†ตํ•ฉํ•˜๋Š” ์ „ํ˜•์ ์ธ ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์‚ดํŽด๋ณผ๊นŒ?

1๋‹จ๊ณ„: ์ฝ”๋“œ ์ž‘์„ฑ ๋ฐ ์ปค๋ฐ‹ ๐Ÿ“

๊ฐœ๋ฐœ์ž๊ฐ€ Terraform ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  Git์— ์ปค๋ฐ‹ํ•ด.

$ git checkout -b feature/add-rds-database
$ vim main.tf  # RDS ๋ฆฌ์†Œ์Šค ์ถ”๊ฐ€
$ git add .
$ git commit -m "Add RDS database for production"
$ git push origin feature/add-rds-database
2๋‹จ๊ณ„: Pull Request ์ƒ์„ฑ ๐Ÿ”

GitHub/GitLab์—์„œ PR์„ ์ƒ์„ฑํ•˜๋ฉด, CI ํŒŒ์ดํ”„๋ผ์ธ์ด ์ž๋™์œผ๋กœ ์‹คํ–‰๋ผ.

์ž๋™ ์‹คํ–‰๋˜๋Š” ์ž‘์—…๋“ค:
- ์ฝ”๋“œ ํฌ๋งท ๊ฒ€์‚ฌ (terraform fmt -check)
- ๋ฌธ๋ฒ• ๊ฒ€์ฆ (terraform validate)
- ๋ณด์•ˆ ์Šค์บ” (tfsec, Checkov ๋“ฑ)
- ๋น„์šฉ ์˜ˆ์ธก (Infracost)
- Plan ์‹คํ–‰ ๋ฐ ๊ฒฐ๊ณผ๋ฅผ PR ์ฝ”๋ฉ˜ํŠธ๋กœ ์ž๋™ ๊ฒŒ์‹œ
3๋‹จ๊ณ„: ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋ฐ ์Šน์ธ ๐Ÿ‘ฅ

ํŒ€์›๋“ค์ด PR์„ ๋ฆฌ๋ทฐํ•˜๊ณ , Plan ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•œ ํ›„ ์Šน์ธํ•ด.

๐Ÿ’ก ๋ฆฌ๋ทฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ:
โœ… Plan ๊ฒฐ๊ณผ๊ฐ€ ์˜ˆ์ƒ๊ณผ ์ผ์น˜ํ•˜๋Š”๊ฐ€?
โœ… ๋ณด์•ˆ ์ด์Šˆ๋Š” ์—†๋Š”๊ฐ€?
โœ… ๋น„์šฉ ์ฆ๊ฐ€๊ฐ€ ํ•ฉ๋ฆฌ์ ์ธ๊ฐ€?
โœ… ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜์„ ๋”ฐ๋ฅด๋Š”๊ฐ€?
โœ… ๋ฌธ์„œํ™”๊ฐ€ ์ถฉ๋ถ„ํ•œ๊ฐ€?
4๋‹จ๊ณ„: ๋จธ์ง€ ๋ฐ ์ž๋™ ๋ฐฐํฌ ๐Ÿš€

PR์ด ๋จธ์ง€๋˜๋ฉด, CD ํŒŒ์ดํ”„๋ผ์ธ์ด ์ž๋™์œผ๋กœ terraform apply๋ฅผ ์‹คํ–‰ํ•ด!

๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค:
1. main ๋ธŒ๋žœ์น˜์— ๋จธ์ง€
2. CD ํŒŒ์ดํ”„๋ผ์ธ ํŠธ๋ฆฌ๊ฑฐ
3. ํ™˜๊ฒฝ๋ณ„๋กœ ์ˆœ์ฐจ ๋ฐฐํฌ (dev โ†’ staging โ†’ production)
4. ๊ฐ ๋‹จ๊ณ„๋งˆ๋‹ค ์Šน์ธ ๊ฒŒ์ดํŠธ ์„ค์ • ๊ฐ€๋Šฅ
5. ๋ฐฐํฌ ์™„๋ฃŒ ํ›„ Slack/์ด๋ฉ”์ผ ์•Œ๋ฆผ
5๋‹จ๊ณ„: ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋กค๋ฐฑ ๐Ÿ“Š

๋ฐฐํฌ ํ›„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ , ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋ฉด ์ฆ‰์‹œ ๋กค๋ฐฑ!

# ๋ฌธ์ œ ๋ฐœ๊ฒฌ ์‹œ Git revert
$ git revert HEAD
$ git push origin main

# CI/CD๊ฐ€ ์ž๋™์œผ๋กœ ์ด์ „ ์ƒํƒœ๋กœ ๋กค๋ฐฑ!
CI/CD ํŒŒ์ดํ”„๋ผ์ธ Git Push ์ฝ”๋“œ ์ปค๋ฐ‹ ๐Ÿ“ CI ๊ฒ€์ฆ ์ž๋™ ํ…Œ์ŠคํŠธ ๐Ÿ” ์ฝ”๋“œ ๋ฆฌ๋ทฐ ํŒ€ ์Šน์ธ ๐Ÿ‘ฅ ์ž๋™ ๋ฐฐํฌ Apply ๐Ÿš€ ์ž๋™ ์‹คํ–‰ ์ž‘์—…๋“ค โœ“ terraform fmt -check โœ“ terraform validate โœ“ ๋ณด์•ˆ ์Šค์บ” (tfsec) โœ“ ๋น„์šฉ ์˜ˆ์ธก (Infracost) Dev ํ™˜๊ฒฝ ์ž๋™ ๋ฐฐํฌ ๐Ÿ”ง Production ์Šน์ธ ํ›„ ๋ฐฐํฌ ๐ŸŽฏ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ์•Œ๋ฆผ ๐Ÿ“Š

๐Ÿ”ง GitHub Actions๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ

์‹ค์ œ๋กœ GitHub Actions๋ฅผ ์‚ฌ์šฉํ•ด์„œ Terraform CI/CD๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ๊นŒ? ์™„์ „ ์‹ค์ „ ์˜ˆ์ œ์•ผ! ๐Ÿ’ช

ํŒŒ์ผ ๊ตฌ์กฐ:
my-terraform-project/
โ”œโ”€โ”€ .github/
โ”‚   โ””โ”€โ”€ workflows/
โ”‚       โ”œโ”€โ”€ terraform-plan.yml    # PR ์ƒ์„ฑ ์‹œ ์‹คํ–‰
โ”‚       โ””โ”€โ”€ terraform-apply.yml   # ๋จธ์ง€ ์‹œ ์‹คํ–‰
โ”œโ”€โ”€ environments/
โ”‚   โ”œโ”€โ”€ dev/
โ”‚   โ”‚   โ”œโ”€โ”€ main.tf
โ”‚   โ”‚   โ””โ”€โ”€ variables.tf
โ”‚   โ”œโ”€โ”€ staging/
โ”‚   โ”‚   โ”œโ”€โ”€ main.tf
โ”‚   โ”‚   โ””โ”€โ”€ variables.tf
โ”‚   โ””โ”€โ”€ production/
โ”‚       โ”œโ”€โ”€ main.tf
โ”‚       โ””โ”€โ”€ variables.tf
โ””โ”€โ”€ modules/
    โ”œโ”€โ”€ vpc/
    โ”œโ”€โ”€ ec2/
    โ””โ”€โ”€ rds/
terraform-plan.yml (PR ์‹œ ์ž๋™ ์‹คํ–‰)

name: 'Terraform Plan'

on:
  pull_request:
    branches:
      - main
    paths:
      - 'environments/**'
      - 'modules/**'

env:
  TF_VERSION: '1.5.0'
  AWS_REGION: 'ap-northeast-2'

jobs:
  terraform-plan:
    name: 'Terraform Plan'
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        environment: [dev, staging, production]
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}
      
      - 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: ${{ env.AWS_REGION }}
      
      - name: Terraform Format Check
        id: fmt
        run: terraform fmt -check -recursive
        continue-on-error: true
      
      - name: Terraform Init
        id: init
        run: |
          cd environments/${{ matrix.environment }}
          terraform init
      
      - name: Terraform Validate
        id: validate
        run: |
          cd environments/${{ matrix.environment }}
          terraform validate -no-color
      
      - name: Terraform Plan
        id: plan
        run: |
          cd environments/${{ matrix.environment }}
          terraform plan -no-color -out=tfplan
        continue-on-error: true
      
      - name: Run tfsec
        uses: aquasecurity/tfsec-action@v1.0.0
        with:
          working_directory: environments/${{ matrix.environment }}
      
      - name: Comment PR
        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>
            
            *Environment: ${{ matrix.environment }}*
            *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
terraform-apply.yml (๋จธ์ง€ ์‹œ ์ž๋™ ์‹คํ–‰)

name: 'Terraform Apply'

on:
  push:
    branches:
      - main
    paths:
      - 'environments/**'
      - 'modules/**'

env:
  TF_VERSION: '1.5.0'
  AWS_REGION: 'ap-northeast-2'

jobs:
  terraform-apply-dev:
    name: 'Apply to Dev'
    runs-on: ubuntu-latest
    environment: dev
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}
      
      - 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: ${{ env.AWS_REGION }}
      
      - name: Terraform Init
        run: |
          cd environments/dev
          terraform init
      
      - name: Terraform Apply
        run: |
          cd environments/dev
          terraform apply -auto-approve
      
      - name: Notify Slack
        uses: 8398a7/action-slack@v3
        if: always()
        with:
          status: ${{ job.status }}
          text: 'Terraform Apply to Dev: ${{ job.status }}'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}
  
  terraform-apply-staging:
    name: 'Apply to Staging'
    runs-on: ubuntu-latest
    needs: terraform-apply-dev
    environment: staging
    
    steps:
      # Dev์™€ ๋™์ผํ•œ ์Šคํ…๋“ค...
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}
      
      - 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: ${{ env.AWS_REGION }}
      
      - name: Terraform Init
        run: |
          cd environments/staging
          terraform init
      
      - name: Terraform Apply
        run: |
          cd environments/staging
          terraform apply -auto-approve
  
  terraform-apply-production:
    name: 'Apply to Production'
    runs-on: ubuntu-latest
    needs: terraform-apply-staging
    environment: 
      name: production
      url: https://myapp.example.com
    
    steps:
      # Staging๊ณผ ๋™์ผํ•œ ์Šคํ…๋“ค...
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: ${{ env.TF_VERSION }}
      
      - 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: ${{ env.AWS_REGION }}
      
      - name: Terraform Init
        run: |
          cd environments/production
          terraform init
      
      - name: Terraform Apply
        run: |
          cd environments/production
          terraform apply -auto-approve
      
      - name: Notify Slack - Success
        uses: 8398a7/action-slack@v3
        if: success()
        with:
          status: custom
          custom_payload: |
            {
              text: '๐ŸŽ‰ Production ๋ฐฐํฌ ์™„๋ฃŒ!',
              attachments: [{
                color: 'good',
                text: `Commit: ${{ github.event.head_commit.message }}\nAuthor: ${{ github.actor }}`
              }]
            }
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}
๐Ÿ’ก GitHub Environment ์„ค์ •ํ•˜๊ธฐ GitHub Settings โ†’ Environments์—์„œ dev, staging, production ํ™˜๊ฒฝ์„ ๋งŒ๋“ค๊ณ :
- Production์—๋Š” ์Šน์ธ์ž(Reviewer) ์„ค์ •
- ๊ฐ ํ™˜๊ฒฝ๋ณ„๋กœ Secret ๋ณ€์ˆ˜ ์„ค์ •
- ๋ฐฐํฌ ๋ธŒ๋žœ์น˜ ์ œํ•œ ์„ค์ •

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ „์— ํŒ€์žฅ์˜ ์Šน์ธ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด! ๐Ÿ”

๐ŸŽจ ์‹ค์ „ ์˜ˆ์ œ: 3-Tier ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธํ”„๋ผ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ „ ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ์ „ํ˜•์ ์ธ 3-Tier ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธํ”„๋ผ๋ฅผ Terraform์œผ๋กœ ๊ตฌ์ถ•ํ•ด๋ณด์ž! ๐Ÿ—๏ธ

๐Ÿ›๏ธ ์•„ํ‚คํ…์ฒ˜ ๊ตฌ์„ฑ

Presentation Tier (ํ”„๋ก ํŠธ์—”๋“œ)
- CloudFront (CDN)
- S3 (์ •์  ํŒŒ์ผ ํ˜ธ์ŠคํŒ…)
- Route53 (DNS)

Application Tier (๋ฐฑ์—”๋“œ)
- Application Load Balancer
- Auto Scaling Group
- EC2 ์ธ์Šคํ„ด์Šค๋“ค

Data Tier (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)
- RDS (PostgreSQL)
- ElastiCache (Redis)
- S3 (๋ฐฑ์—… ๋ฐ ๋กœ๊ทธ)

๐Ÿ“ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

3tier-webapp/
โ”œโ”€โ”€ main.tf                 # ๋ฉ”์ธ ์„ค์ •
โ”œโ”€โ”€ variables.tf            # ๋ณ€์ˆ˜ ์ •์˜
โ”œโ”€โ”€ outputs.tf              # ์ถœ๋ ฅ ๊ฐ’
โ”œโ”€โ”€ backend.tf              # State ๋ฐฑ์—”๋“œ ์„ค์ •
โ”œโ”€โ”€ versions.tf             # Provider ๋ฒ„์ „
โ”‚
โ”œโ”€โ”€ modules/
โ”‚   โ”œโ”€โ”€ networking/         # VPC, ์„œ๋ธŒ๋„ท, ๋ผ์šฐํŒ…
โ”‚   โ”‚   โ”œโ”€โ”€ main.tf
โ”‚   โ”‚   โ”œโ”€โ”€ variables.tf
โ”‚   โ”‚   โ””โ”€โ”€ outputs.tf
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ compute/            # EC2, ASG, ALB
โ”‚   โ”‚   โ”œโ”€โ”€ main.tf
โ”‚   โ”‚   โ”œโ”€โ”€ variables.tf
โ”‚   โ”‚   โ””โ”€โ”€ outputs.tf
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ database/           # RDS, ElastiCache
โ”‚   โ”‚   โ”œโ”€โ”€ main.tf
โ”‚   โ”‚   โ”œโ”€โ”€ variables.tf
โ”‚   โ”‚   โ””โ”€โ”€ outputs.tf
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ cdn/                # CloudFront, S3
โ”‚       โ”œโ”€โ”€ main.tf
โ”‚       โ”œโ”€โ”€ variables.tf
โ”‚       โ””โ”€โ”€ outputs.tf
โ”‚
โ””โ”€โ”€ environments/
    โ”œโ”€โ”€ dev/
    โ”‚   โ”œโ”€โ”€ terraform.tfvars
    โ”‚   โ””โ”€โ”€ backend.hcl
    โ”œโ”€โ”€ staging/
    โ”‚   โ”œโ”€โ”€ terraform.tfvars
    โ”‚   โ””โ”€โ”€ backend.hcl
    โ””โ”€โ”€ production/
        โ”œโ”€โ”€ terraform.tfvars
        โ””โ”€โ”€ backend.hcl

๐ŸŒ 1. Networking ๋ชจ๋“ˆ

modules/networking/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
  }
}

# ์ธํ„ฐ๋„ท ๊ฒŒ์ดํŠธ์›จ์ด
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  
  tags = {
    Name = "${var.project_name}-igw"
  }
}

# Public ์„œ๋ธŒ๋„ท๋“ค (ALB์šฉ)
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]
  
  map_public_ip_on_launch = true
  
  tags = {
    Name = "${var.project_name}-public-${var.availability_zones[count.index]}"
    Type = "Public"
  }
}

# Private ์„œ๋ธŒ๋„ท๋“ค (EC2์šฉ)
resource "aws_subnet" "private" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
  availability_zone = var.availability_zones[count.index]
  
  tags = {
    Name = "${var.project_name}-private-${var.availability_zones[count.index]}"
    Type = "Private"
  }
}

# Database ์„œ๋ธŒ๋„ท๋“ค (RDS์šฉ)
resource "aws_subnet" "database" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 20)
  availability_zone = var.availability_zones[count.index]
  
  tags = {
    Name = "${var.project_name}-database-${var.availability_zones[count.index]}"
    Type = "Database"
  }
}

# NAT ๊ฒŒ์ดํŠธ์›จ์ด์šฉ EIP
resource "aws_eip" "nat" {
  count  = length(var.availability_zones)
  domain = "vpc"
  
  tags = {
    Name = "${var.project_name}-nat-eip-${count.index + 1}"
  }
  
  depends_on = [aws_internet_gateway.main]
}

# NAT ๊ฒŒ์ดํŠธ์›จ์ด
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}"
  }
}

# Public ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ”
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"
  }
}

# Private ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ”
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}"
  }
}

# ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ” ์—ฐ๊ฒฐ
resource "aws_route_table_association" "public" {
  count          = length(var.availability_zones)
  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.availability_zones)
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private[count.index].id
}

๐Ÿ’ป 2. Compute ๋ชจ๋“ˆ

modules/compute/main.tf

# Application Load Balancer
resource "aws_lb" "main" {
  name               = "${var.project_name}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = var.public_subnet_ids
  
  enable_deletion_protection = var.environment == "production" ? true : false
  
  tags = {
    Name        = "${var.project_name}-alb"
    Environment = var.environment
  }
}

# ALB ํƒ€๊ฒŸ ๊ทธ๋ฃน
resource "aws_lb_target_group" "app" {
  name     = "${var.project_name}-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = var.vpc_id
  
  health_check {
    enabled             = true
    healthy_threshold   = 2
    interval            = 30
    matcher             = "200"
    path                = "/health"
    port                = "traffic-port"
    protocol            = "HTTP"
    timeout             = 5
    unhealthy_threshold = 2
  }
  
  tags = {
    Name = "${var.project_name}-tg"
  }
}

# ALB ๋ฆฌ์Šค๋„ˆ
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"
  
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# HTTPS ๋ฆฌ์Šค๋„ˆ (์ธ์ฆ์„œ ์žˆ์„ ๊ฒฝ์šฐ)
resource "aws_lb_listener" "https" {
  count             = var.certificate_arn != "" ? 1 : 0
  load_balancer_arn = aws_lb.main.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS-1-2-2017-01"
  certificate_arn   = var.certificate_arn
  
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

# Launch Template
resource "aws_launch_template" "app" {
  name_prefix   = "${var.project_name}-lt-"
  image_id      = var.ami_id
  instance_type = var.instance_type
  
  vpc_security_group_ids = [aws_security_group.app.id]
  
  iam_instance_profile {
    name = aws_iam_instance_profile.app.name
  }
  
  user_data = base64encode(templatefile("${path.module}/user_data.sh", {
    environment = var.environment
    app_name    = var.project_name
  }))
  
  block_device_mappings {
    device_name = "/dev/xvda"
    
    ebs {
      volume_size           = 20
      volume_type           = "gp3"
      delete_on_termination = true
      encrypted             = true
    }
  }
  
  monitoring {
    enabled = true
  }
  
  tag_specifications {
    resource_type = "instance"
    
    tags = {
      Name        = "${var.project_name}-instance"
      Environment = var.environment
    }
  }
}

# Auto Scaling Group
resource "aws_autoscaling_group" "app" {
  name                = "${var.project_name}-asg"
  vpc_zone_identifier = var.private_subnet_ids
  target_group_arns   = [aws_lb_target_group.app.arn]
  health_check_type   = "ELB"
  health_check_grace_period = 300
  
  min_size         = var.asg_min_size
  max_size         = var.asg_max_size
  desired_capacity = var.asg_desired_capacity
  
  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
  
  tag {
    key                 = "Name"
    value               = "${var.project_name}-asg-instance"
    propagate_at_launch = true
  }
  
  tag {
    key                 = "Environment"
    value               = var.environment
    propagate_at_launch = true
  }
}

# Auto Scaling ์ •์ฑ… - CPU ๊ธฐ๋ฐ˜
resource "aws_autoscaling_policy" "cpu_scale_up" {
  name                   = "${var.project_name}-cpu-scale-up"
  scaling_adjustment     = 1
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300
  autoscaling_group_name = aws_autoscaling_group.app.name
}

resource "aws_autoscaling_policy" "cpu_scale_down" {
  name                   = "${var.project_name}-cpu-scale-down"
  scaling_adjustment     = -1
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300
  autoscaling_group_name = aws_autoscaling_group.app.name
}

# CloudWatch ์•Œ๋žŒ - CPU ๋†’์Œ
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
  alarm_name          = "${var.project_name}-cpu-high"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "80"
  
  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.app.name
  }
  
  alarm_actions = [aws_autoscaling_policy.cpu_scale_up.arn]
}

# CloudWatch ์•Œ๋žŒ - CPU ๋‚ฎ์Œ
resource "aws_cloudwatch_metric_alarm" "cpu_low" {
  alarm_name          = "${var.project_name}-cpu-low"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "2"
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = "120"
  statistic           = "Average"
  threshold           = "20"
  
  dimensions = {
    AutoScalingGroupName = aws_autoscaling_group.app.name
  }
  
  alarm_actions = [aws_autoscaling_policy.cpu_scale_down.arn]
}

# Security Group - ALB
resource "aws_security_group" "alb" {
  name        = "${var.project_name}-alb-sg"
  description = "Security group for ALB"
  vpc_id      = var.vpc_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}-alb-sg"
  }
}

# Security Group - Application
resource "aws_security_group" "app" {
  name        = "${var.project_name}-app-sg"
  description = "Security group for application instances"
  vpc_id      = var.vpc_id
  
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }
  
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  
  tags = {
    Name = "${var.project_name}-app-sg"
  }
}

๐Ÿ—„๏ธ 3. Database ๋ชจ๋“ˆ

modules/database/main.tf

# RDS ์„œ๋ธŒ๋„ท ๊ทธ๋ฃน
resource "aws_db_subnet_group" "main" {
  name       = "${var.project_name}-db-subnet-group"
  subnet_ids = var.database_subnet_ids
  
  tags = {
    Name = "${var.project_name}-db-subnet-group"
  }
}

# RDS ์ธ์Šคํ„ด์Šค
resource "aws_db_instance" "main" {
  identifier     = "${var.project_name}-db"
  engine         = "postgres"
  engine_version = "14.7"
  instance_class = var.db_instance_class
  
  allocated_storage     = var.db_allocated_storage
  max_allocated_storage = var.db_max_allocated_storage
  storage_type          = "gp3"
  storage_encrypted     = true
  
  db_name  = var.db_name
  username = var.db_username
  password = var.db_password  # ์‹ค์ œ๋กœ๋Š” AWS Secrets Manager ์‚ฌ์šฉ ๊ถŒ์žฅ!
  
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  
  multi_az               = var.environment == "production" ? true : false
  backup_retention_period = var.environment == "production" ? 7 : 1
  backup_window          = "03:00-04:00"
  maintenance_window     = "mon:04:00-mon:05:00"
  
  enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
  
  skip_final_snapshot       = var.environment != "production"
  final_snapshot_identifier = var.environment == "production" ? "${var.project_name}-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}" : null
  
  tags = {
    Name        = "${var.project_name}-db"
    Environment = var.environment
  }
}

# RDS ์ฝ๊ธฐ ์ „์šฉ ๋ณต์ œ๋ณธ (ํ”„๋กœ๋•์…˜๋งŒ)
resource "aws_db_instance" "read_replica" {
  count              = var.environment == "production" ? 1 : 0
  identifier         = "${var.project_name}-db-replica"
  replicate_source_db = aws_db_instance.main.identifier
  instance_class     = var.db_instance_class
  
  skip_final_snapshot = true
  
  tags = {
    Name        = "${var.project_name}-db-replica"
    Environment = var.environment
  }
}

# ElastiCache ์„œ๋ธŒ๋„ท ๊ทธ๋ฃน
resource "aws_elasticache_subnet_group" "main" {
  name       = "${var.project_name}-cache-subnet-group"
  subnet_ids = var.database_subnet_ids
}

# ElastiCache Redis ํด๋Ÿฌ์Šคํ„ฐ
resource "aws_elasticache_replication_group" "main" {
  replication_group_id       = "${var.project_name}-redis"
  replication_group_description = "Redis cluster for ${var.project_name}"
  
  engine               = "redis"
  engine_version       = "7.0"
  node_type            = var.redis_node_type
  num_cache_clusters   = var.environment == "production" ? 2 : 1
  
  port                 = 6379
  parameter_group_name = "default.redis7"
  subnet_group_name    = aws_elasticache_subnet_group.main.name
  security_group_ids   = [aws_security_group.redis.id]
  
  at_rest_encryption_enabled = true
  transit_encryption_enabled = true
  auth_token                 = var.redis_auth_token
  
  automatic_failover_enabled = var.environment == "production" ? true : false
  multi_az_enabled          = var.environment == "production" ? true : false
  
  snapshot_retention_limit = var.environment == "production" ? 5 : 0
  snapshot_window         = "03:00-05:00"
  
  tags = {
    Name        = "${var.project_name}-redis"
    Environment = var.environment
  }
}

# Security Group - RDS
resource "aws_security_group" "rds" {
  name        = "${var.project_name}-rds-sg"
  description = "Security group for RDS"
  vpc_id      = var.vpc_id
  
  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }
  
  tags = {
    Name = "${var.project_name}-rds-sg"
  }
}

# Security Group - Redis
resource "aws_security_group" "redis" {
  name        = "${var.project_name}-redis-sg"
  description = "Security group for Redis"
  vpc_id      = var.vpc_id
  
  ingress {
    from_port       = 6379
    to_port         = 6379
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }
  
  tags = {
    Name = "${var.project_name}-redis-sg"
  }
}
๐Ÿ’ก ๋ณด์•ˆ ํŒ: ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ฝ”๋“œ์— ์ง์ ‘ ๋„ฃ์ง€ ๋ง๊ณ , AWS Secrets Manager๋‚˜ Parameter Store๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ด! ๊ทธ๋ฆฌ๊ณ  Terraform State ํŒŒ์ผ๋„ ์•”ํ˜ธํ™”๋œ S3์— ์ €์žฅํ•˜๋Š” ๊ฑฐ ์žŠ์ง€ ๋งˆ! ๐Ÿ”

๐ŸŒ 4. ๋ฉ”์ธ ์„ค์ • ํŒŒ์ผ

main.tf

terraform {
  required_version = ">= 1.5.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
  
  default_tags {
    tags = {
      Project     = var.project_name
      Environment = var.environment
      ManagedBy   = "Terraform"
    }
  }
}

# Networking ๋ชจ๋“ˆ
module "networking" {
  source = "./modules/networking"
  
  project_name       = var.project_name
  environment        = var.environment
  vpc_cidr           = var.vpc_cidr
  availability_zones = var.availability_zones
}

# Compute ๋ชจ๋“ˆ
module "compute" {
  source = "./modules/compute"
  
  project_name        = var.project_name
  environment         = var.environment
  vpc_id              = module.networking.vpc_id
  public_subnet_ids   = module.networking.public_subnet_ids
  private_subnet_ids  = module.networking.private_subnet_ids
  
  ami_id              = var.ami_id
  instance_type       = var.instance_type
  asg_min_size        = var.asg_min_size
  asg_max_size        = var.asg_max_size
  asg_desired_capacity = var.asg_desired_capacity
  certificate_arn     = var.certificate_arn
}

# Database ๋ชจ๋“ˆ
module "database" {
  source = "./modules/database"
  
  project_name           = var.project_name
  environment            = var.environment
  vpc_id                 = module.networking.vpc_id
  database_subnet_ids    = module.networking.database_subnet_ids
  app_security_group_id  = module.compute.app_security_group_id
  
  db_instance_class      = var.db_instance_class
  db_allocated_storage   = var.db_allocated_storage
  db_max_allocated_storage = var.db_max_allocated_storage
  db_name                = var.db_name
  db_username            = var.db_username
  db_password            = var.db_password
  
  redis_node_type        = var.redis_node_type
  redis_auth_token       = var.redis_auth_token
}

๐Ÿ“Š 5. ํ™˜๊ฒฝ๋ณ„ ๋ณ€์ˆ˜ ์„ค์ •

environments/production/terraform.tfvars

# ํ”„๋กœ์ ํŠธ ์„ค์ •
project_name = "myapp"
environment  = "production"
aws_region   = "ap-northeast-2"

# ๋„คํŠธ์›Œํฌ ์„ค์ •
vpc_cidr           = "10.0.0.0/16"
availability_zones = ["ap-northeast-2a", "ap-northeast-2c"]

# Compute ์„ค์ •
ami_id               = "ami-0c55b159cbfafe1f0"  # Amazon Linux 2
instance_type        = "t3.medium"
asg_min_size         = 2
asg_max_size         = 10
asg_desired_capacity = 4

# Database ์„ค์ •
db_instance_class        = "db.t3.medium"
db_allocated_storage     = 100
db_max_allocated_storage = 500
db_name                  = "myapp_prod"
db_username              = "admin"
# db_password๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜๋‚˜ Secrets Manager์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ!

# Redis ์„ค์ •
redis_node_type = "cache.t3.medium"

# SSL ์ธ์ฆ์„œ
certificate_arn = "arn:aws:acm:ap-northeast-2:123456789012:certificate/xxxxx"
environments/dev/terraform.tfvars

# ํ”„๋กœ์ ํŠธ ์„ค์ •
project_name = "myapp"
environment  = "dev"
aws_region   = "ap-northeast-2"

# ๋„คํŠธ์›Œํฌ ์„ค์ •
vpc_cidr           = "10.1.0.0/16"
availability_zones = ["ap-northeast-2a"]  # ๊ฐœ๋ฐœ์€ ๋‹จ์ผ AZ

# Compute ์„ค์ •
ami_id               = "ami-0c55b159cbfafe1f0"
instance_type        = "t3.small"  # ๊ฐœ๋ฐœ์€ ์ž‘์€ ์ธ์Šคํ„ด์Šค
asg_min_size         = 1
asg_max_size         = 2
asg_desired_capacity = 1

# Database ์„ค์ •
db_instance_class        = "db.t3.small"
db_allocated_storage     = 20
db_max_allocated_storage = 50
db_name                  = "myapp_dev"
db_username              = "admin"

# Redis ์„ค์ •
redis_node_type = "cache.t3.micro"

# SSL ์ธ์ฆ์„œ (๊ฐœ๋ฐœ์€ ์—†์„ ์ˆ˜๋„)
certificate_arn = ""
๐Ÿ’ก ๋น„์šฉ ์ตœ์ ํ™” ํŒ: - ๊ฐœ๋ฐœ/์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ์€ ์ž‘์€ ์ธ์Šคํ„ด์Šค ํƒ€์ž… ์‚ฌ์šฉ
- Multi-AZ๋Š” ํ”„๋กœ๋•์…˜์—๋งŒ ์ ์šฉ
- ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์€ ์—…๋ฌด ์‹œ๊ฐ„ ์™ธ์—๋Š” ์ž๋™์œผ๋กœ ์ค‘์ง€
- Reserved Instance๋‚˜ Savings Plan ํ™œ์šฉ
- ๋ถˆํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋Š” ์ฆ‰์‹œ ์‚ญ์ œ (terraform destroy)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํด๋ผ์šฐ๋“œ ๋น„์šฉ์„ 50% ์ด์ƒ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ’ฐ
3-Tier ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„ํ‚คํ…์ฒ˜ Presentation Tier CloudFront CDN S3 ์ •์  ํŒŒ์ผ Route53 DNS Application Tier ALB ๋กœ๋“œ๋ฐธ๋Ÿฐ์„œ Auto Scaling EC2 ๊ทธ๋ฃน EC2 ์ธ์Šคํ„ด์Šค Data Tier RDS PostgreSQL ElastiCache Redis S3 ๋ฐฑ์—…/๋กœ๊ทธ

๐Ÿ›ก๏ธ ๋ณด์•ˆ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

Terraform๊ณผ CI/CD๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ณด์•ˆ์€ ์ •๋ง ์ค‘์š”ํ•ด! ํ•œ ๋ฒˆ์˜ ์‹ค์ˆ˜๋กœ ํšŒ์‚ฌ์˜ ๋ชจ๋“  ์ธํ”„๋ผ๊ฐ€ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๐Ÿ˜ฑ ์‹ค์ „์—์„œ ๊ผญ ์ง€์ผœ์•ผ ํ•  ๋ณด์•ˆ ์›์น™๋“ค์„ ์•Œ์•„๋ณด์ž!

๐Ÿ” 1. ๋ฏผ๊ฐํ•œ ์ •๋ณด ๊ด€๋ฆฌ

โŒ ์ ˆ๋Œ€ ํ•˜์ง€ ๋ง์•„์•ผ ํ•  ๊ฒƒ๋“ค: - ๋น„๋ฐ€๋ฒˆํ˜ธ, API ํ‚ค๋ฅผ ์ฝ”๋“œ์— ์ง์ ‘ ์ž‘์„ฑ
- State ํŒŒ์ผ์„ Git์— ์ปค๋ฐ‹
- AWS Access Key๋ฅผ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉ
- ํ”„๋กœ๋•์…˜ ์ž๊ฒฉ์ฆ๋ช…์„ ๊ฐœ๋ฐœ์ž ๋กœ์ปฌ์— ์ €์žฅ
โœ… ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•:

AWS Secrets Manager ์‚ฌ์šฉ:
# Secrets Manager์—์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ€์ ธ์˜ค๊ธฐ
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "prod/database/password"
}

resource "aws_db_instance" "main" {
  # ... ๋‹ค๋ฅธ ์„ค์ •๋“ค ...
  password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ:
# GitHub Actions์—์„œ
env:
  TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

OIDC๋ฅผ ํ†ตํ•œ ์ธ์ฆ (์ถ”์ฒœ!):
# GitHub Actions์—์„œ OIDC ์‚ฌ์šฉ
- name: Configure AWS Credentials
  uses: aws-actions/configure-aws-credentials@v2
  with:
    role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
    aws-region: ap-northeast-2
    # Access Key ์—†์ด ์•ˆ์ „ํ•˜๊ฒŒ ์ธ์ฆ!

๐Ÿ” 2. ๋ณด์•ˆ ์Šค์บ” ์ž๋™ํ™”

์ฝ”๋“œ๋ฅผ ๋ฐฐํฌํ•˜๊ธฐ ์ „์— ์ž๋™์œผ๋กœ ๋ณด์•ˆ ์ทจ์•ฝ์ ์„ ๊ฒ€์‚ฌํ•ด์•ผ ํ•ด!

tfsec - Terraform ๋ณด์•ˆ ์Šค์บ๋„ˆ:
# GitHub Actions์— ์ถ”๊ฐ€
- name: Run tfsec
  uses: aquasecurity/tfsec-action@v1.0.0
  with:
    soft_fail: false  # ๋ณด์•ˆ ์ด์Šˆ ๋ฐœ๊ฒฌ ์‹œ ๋นŒ๋“œ ์‹คํŒจ

Checkov - ์ •์ฑ… ๊ธฐ๋ฐ˜ ์Šค์บ๋„ˆ:
- name: Run Checkov
  uses: bridgecrewio/checkov-action@master
  with:
    directory: .
    framework: terraform

์ฃผ์š” ๊ฒ€์‚ฌ ํ•ญ๋ชฉ๋“ค:
โœ… ์•”ํ˜ธํ™”๋˜์ง€ ์•Š์€ ์Šคํ† ๋ฆฌ์ง€
โœ… ๊ณต๊ฐœ๋œ ๋ณด์•ˆ ๊ทธ๋ฃน
โœ… ๋กœ๊น…์ด ๋น„ํ™œ์„ฑํ™”๋œ ๋ฆฌ์†Œ์Šค
โœ… ์ทจ์•ฝํ•œ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜
โœ… ๊ณผ๋„ํ•œ IAM ๊ถŒํ•œ

๐ŸŽฏ 3. ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ (Least Privilege)

IAM ์—ญํ• ๊ณผ ์ •์ฑ…์€ ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌํ•ด์•ผ ํ•ด!

โŒ ๋‚˜์œ ์˜ˆ:
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}
# ๋ชจ๋“  ๊ถŒํ•œ ๋ถ€์—ฌ - ์ ˆ๋Œ€ ๊ธˆ์ง€!
โœ… ์ข‹์€ ์˜ˆ:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeImages",
        "ec2:DescribeKeyPairs",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeSubnets",
        "ec2:DescribeVpcs"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:RunInstances",
        "ec2:TerminateInstances"
      ],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": "ap-northeast-2"
        }
      }
    }
  ]
}
# ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ๋ช…์‹œ์ ์œผ๋กœ ๋ถ€์—ฌ

๐Ÿ“ 4. ์ฝ”๋“œ ๋ฆฌ๋ทฐ ํ”„๋กœ์„ธ์Šค

๋ชจ๋“  ์ธํ”„๋ผ ๋ณ€๊ฒฝ์€ ๋ฐ˜๋“œ์‹œ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ๊ฑฐ์ณ์•ผ ํ•ด!

๋ฆฌ๋ทฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ:

ํ•ญ๋ชฉ ํ™•์ธ ์‚ฌํ•ญ
๋ณด์•ˆ ๋ฏผ๊ฐํ•œ ์ •๋ณด ๋…ธ์ถœ ์—ฌ๋ถ€, ๋ณด์•ˆ ๊ทธ๋ฃน ์„ค์ •, ์•”ํ˜ธํ™” ์ ์šฉ
๋น„์šฉ ์˜ˆ์ƒ ๋น„์šฉ ์ฆ๊ฐ€, ๋ถˆํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค, ์ธ์Šคํ„ด์Šค ํƒ€์ž… ์ ์ •์„ฑ
๊ฐ€์šฉ์„ฑ Multi-AZ ์„ค์ •, ๋ฐฑ์—… ์ •์ฑ…, ์žฅ์•  ๋ณต๊ตฌ ๊ณ„ํš
์„ฑ๋Šฅ ๋ฆฌ์†Œ์Šค ํฌ๊ธฐ ์ ์ •์„ฑ, Auto Scaling ์„ค์ •, ์บ์‹ฑ ์ „๋žต
๊ทœ์น™ ์ค€์ˆ˜ ๋„ค์ด๋ฐ ์ปจ๋ฒค์…˜, ํƒœ๊ทธ ์ •์ฑ…, ๋ฌธ์„œํ™”

๐Ÿ’ก ์ž๋™ํ™” ํŒ: GitHub์˜ CODEOWNERS ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • ๋””๋ ‰ํ† ๋ฆฌ์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์— ๋Œ€ํ•ด ์ž๋™์œผ๋กœ ๋ฆฌ๋ทฐ์–ด๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์–ด!

# CODEOWNERS
/environments/production/ @senior-devops @team-lead
/modules/database/ @database-admin @senior-devops
*.tf @devops-team

๐Ÿ”„ 5. State ํŒŒ์ผ ๋ณด์•ˆ

State ํŒŒ์ผ์—๋Š” ๋ชจ๋“  ์ธํ”„๋ผ ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ์–ด์„œ ์ •๋ง ์ค‘์š”ํ•ด!

ํ•„์ˆ˜ ๋ณด์•ˆ ์„ค์ •:

# backend.tf
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "ap-northeast-2"
    
    # ์•”ํ˜ธํ™” ํ•„์ˆ˜!
    encrypt        = true
    kms_key_id     = "arn:aws:kms:ap-northeast-2:123456789012:key/xxxxx"
    
    # State Lock์œผ๋กœ ๋™์‹œ ์ˆ˜์ • ๋ฐฉ์ง€
    dynamodb_table = "terraform-lock"
    
    # ๋ฒ„์ „ ๊ด€๋ฆฌ๋กœ ๋กค๋ฐฑ ๊ฐ€๋Šฅ
    versioning     = true
  }
}

# S3 ๋ฒ„ํ‚ท ๋ณด์•ˆ ์„ค์ •
resource "aws_s3_bucket_public_access_block" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

# ๋ฒ„ํ‚ท ์ •์ฑ… - ํŠน์ • ์—ญํ• ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
resource "aws_s3_bucket_policy" "terraform_state" {
  bucket = aws_s3_bucket.terraform_state.id
  
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Deny"
        Principal = "*"
        Action = "s3:*"
        Resource = [
          aws_s3_bucket.terraform_state.arn,
          "${aws_s3_bucket.terraform_state.arn}/*"
        ]
        Condition = {
          Bool = {
            "aws:SecureTransport" = "false"
          }
        }
      }
    ]
  })
}

๐Ÿ“Š 6. ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์•Œ๋ฆผ

๋ฐฐํฌ ํ›„์—๋„ ๊ณ„์† ๋ชจ๋‹ˆํ„ฐ๋งํ•ด์•ผ ํ•ด!

CloudWatch ์•Œ๋žŒ ์„ค์ •:
resource "aws_cloudwatch_metric_alarm" "unauthorized_api_calls" {
  alarm_name          = "UnauthorizedAPICalls"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name         = "UnauthorizedAPICalls"
  namespace           = "AWS/CloudTrail"
  period              = "300"
  statistic           = "Sum"
  threshold           = "5"
  alarm_description   = "๋ฌด๋‹จ API ํ˜ธ์ถœ ๊ฐ์ง€"
  
  alarm_actions = [aws_sns_topic.security_alerts.arn]
}

resource "aws_cloudwatch_metric_alarm" "root_account_usage" {
  alarm_name          = "RootAccountUsage"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name         = "RootAccountUsage"
  namespace           = "AWS/CloudTrail"
  period              = "60"
  statistic           = "Sum"
  threshold           = "0"
  alarm_description   = "๋ฃจํŠธ ๊ณ„์ • ์‚ฌ์šฉ ๊ฐ์ง€"
  
  alarm_actions = [aws_sns_topic.security_alerts.arn]
}

SNS ํ† ํ”ฝ์œผ๋กœ ์•Œ๋ฆผ:
resource "aws_sns_topic" "security_alerts" {
  name = "security-alerts"
}

resource "aws_sns_topic_subscription" "security_email" {
  topic_arn = aws_sns_topic.security_alerts.arn
  protocol  = "email"
  endpoint  = "security-team@company.com"
}

resource "aws_sns_topic_subscription" "security_slack" {
  topic_arn = aws_sns_topic.security_alerts.arn
  protocol  = "https"
  endpoint  = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
}

๐ŸŽ“ 7. ํŒ€ ๊ต์œก๊ณผ ๋ฌธ์„œํ™”

์•„๋ฌด๋ฆฌ ์ข‹์€ ๋„๊ตฌ์™€ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์žˆ์–ด๋„, ํŒ€์›๋“ค์ด ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๋ฉด ์†Œ์šฉ์—†์–ด!

ํ•„์ˆ˜ ๋ฌธ์„œ๋“ค:
โœ… ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ
โœ… Terraform ๋ชจ๋“ˆ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ
โœ… CI/CD ํŒŒ์ดํ”„๋ผ์ธ ์„ค๋ช…
โœ… ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ฐ€์ด๋“œ
โœ… ๊ธด๊ธ‰ ์ƒํ™ฉ ๋Œ€์‘ ๋งค๋‰ด์–ผ
โœ… ๋ณด์•ˆ ์ •์ฑ… ๋ฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๐Ÿ’ก ์žฌ๋Šฅ๋„ท ํ™œ์šฉ ํŒ: Terraform์ด๋‚˜ DevOps ๊ด€๋ จ ์ง€์‹์„ ํŒ€์›๋“ค๊ณผ ๊ณต์œ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€๋ฅผ ์ดˆ์ฒญํ•ด์„œ ์‚ฌ๋‚ด ๊ต์œก์„ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ! ์‹ค๋ฌด ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ์ „๋ฌธ๊ฐ€์—๊ฒŒ ์ง์ ‘ ๋ฐฐ์šฐ๋ฉด ํ›จ์”ฌ ํšจ๊ณผ์ ์ด๊ฑฐ๋“ . ๐ŸŽฏ

๐Ÿšจ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…: ์ž์ฃผ ๋งŒ๋‚˜๋Š” ๋ฌธ์ œ๋“ค

Terraform๊ณผ CI/CD๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ๋งŒ๋‚˜๊ฒŒ ๋ผ. ๋‚ด๊ฐ€ ์‹ค์ œ๋กœ ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๊ณต์œ ํ• ๊ฒŒ! ๐Ÿ˜Š

โŒ ๋ฌธ์ œ 1: State Lock ๊ฑธ๋ฆผ

์ฆ์ƒ:
Error: Error acquiring the state lock

Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
  ID:        a1b2c3d4-e5f6-7890-abcd-ef1234567890
  Path:      my-bucket/terraform.tfstate
  Operation: OperationTypeApply
  Who:       user@hostname
  Version:   1.5.0
  Created:   2024-01-15 10:30:00 UTC

์›์ธ:
- ์ด์ „ Terraform ์‹คํ–‰์ด ๋น„์ •์ƒ ์ข…๋ฃŒ๋จ
- ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋™์‹œ์— ์‹คํ–‰ ์ค‘
- CI/CD ํŒŒ์ดํ”„๋ผ์ธ์ด ์ค‘๋ณต ์‹คํ–‰๋จ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:
# 1. ๋จผ์ € ์ •๋ง๋กœ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์‹คํ–‰ ์ค‘์ธ์ง€ ํ™•์ธ!
# ํŒ€์›๋“ค์—๊ฒŒ ๋ฌผ์–ด๋ณด๊ธฐ

# 2. ํ™•์‹คํžˆ ์•„๋ฌด๋„ ์‹คํ–‰ ์ค‘์ด ์•„๋‹ˆ๋ผ๋ฉด Lock ๊ฐ•์ œ ํ•ด์ œ
$ terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890

# 3. DynamoDB์—์„œ ์ง์ ‘ ํ™•์ธ
$ aws dynamodb get-item \
    --table-name terraform-lock \
    --key '{"LockID": {"S": "my-bucket/terraform.tfstate"}}'
โš ๏ธ ์ฃผ์˜: force-unlock์€ ์ •๋ง ํ™•์‹คํ•  ๋•Œ๋งŒ ์‚ฌ์šฉํ•ด! ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ์‹คํ–‰ ์ค‘์ธ๋ฐ ๊ฐ•์ œ๋กœ ํ•ด์ œํ•˜๋ฉด State ํŒŒ์ผ์ด ๊ผฌ์ผ ์ˆ˜ ์žˆ์–ด!

โŒ ๋ฌธ์ œ 2: State Drift (์ƒํƒœ ๋ถˆ์ผ์น˜)

์ฆ์ƒ:
๋ˆ„๊ตฐ๊ฐ€ AWS ์ฝ˜์†”์—์„œ ์ง์ ‘ ๋ฆฌ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•ด์„œ, Terraform State์™€ ์‹ค์ œ ์ธํ”„๋ผ๊ฐ€ ๋‹ฌ๋ผ์ง.

๊ฐ์ง€ ๋ฐฉ๋ฒ•:
$ terraform plan

# ์ถœ๋ ฅ:
Note: Objects have changed outside of Terraform

Terraform detected the following changes made outside of Terraform since the
last "terraform apply":

  # aws_instance.web_server has changed
  ~ resource "aws_instance" "web_server" {
        id                    = "i-0123456789abcdef"
      ~ instance_type         = "t2.micro" -> "t2.small"
        # (10 unchanged attributes hidden)
    }

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:
# ๋ฐฉ๋ฒ• 1: Terraform ์ฝ”๋“œ๋ฅผ ์‹ค์ œ ์ƒํƒœ์— ๋งž์ถค
# main.tf๋ฅผ ์ˆ˜์ •ํ•ด์„œ instance_type = "t2.small"๋กœ ๋ณ€๊ฒฝ

# ๋ฐฉ๋ฒ• 2: ์‹ค์ œ ์ธํ”„๋ผ๋ฅผ Terraform ์ฝ”๋“œ์— ๋งž์ถค
$ terraform apply
# ๋‹ค์‹œ t2.micro๋กœ ๋ณ€๊ฒฝ๋จ

# ๋ฐฉ๋ฒ• 3: State๋ฅผ ์‹ค์ œ ์ƒํƒœ๋กœ ์—…๋ฐ์ดํŠธ (๊ถŒ์žฅํ•˜์ง€ ์•Š์Œ)
$ terraform refresh
๐Ÿ’ก ์˜ˆ๋ฐฉ ๋ฐฉ๋ฒ•: - AWS Config Rules๋กœ ์ˆ˜๋™ ๋ณ€๊ฒฝ ๊ฐ์ง€
- CloudTrail๋กœ ๋ชจ๋“  ๋ณ€๊ฒฝ์‚ฌํ•ญ ๋กœ๊น…
- ํŒ€ ๊ทœ์น™: ๋ชจ๋“  ๋ณ€๊ฒฝ์€ Terraform์„ ํ†ตํ•ด์„œ๋งŒ!
- ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์€ ์ฝ˜์†” ์ ‘๊ทผ ์ œํ•œ

โŒ ๋ฌธ์ œ 3: ์ˆœํ™˜ ์˜์กด์„ฑ (Circular Dependency)

์ฆ์ƒ:
Error: Cycle: aws_security_group.app, aws_security_group.db

์›์ธ:
๋‘ ๋ฆฌ์†Œ์Šค๊ฐ€ ์„œ๋กœ๋ฅผ ์ฐธ์กฐํ•˜๊ณ  ์žˆ์Œ.

# ์ž˜๋ชป๋œ ์˜ˆ
resource "aws_security_group" "app" {
  # ...
  egress {
    security_groups = [aws_security_group.db.id]
  }
}

resource "aws_security_group" "db" {
  # ...
  ingress {
    security_groups = [aws_security_group.app.id]
  }
}

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:
# ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•: Security Group Rule์„ ๋ณ„๋„๋กœ ๋ถ„๋ฆฌ
resource "aws_security_group" "app" {
  name = "app-sg"
  # egress ๊ทœ์น™ ์ œ๊ฑฐ
}

resource "aws_security_group" "db" {
  name = "db-sg"
  # ingress ๊ทœ์น™ ์ œ๊ฑฐ
}

# ๋ณ„๋„์˜ ๊ทœ์น™์œผ๋กœ ์ •์˜
resource "aws_security_group_rule" "app_to_db" {
  type                     = "egress"
  from_port                = 5432
  to_port                  = 5432
  protocol                 = "tcp"
  security_group_id        = aws_security_group.app.id
  source_security_group_id = aws_security_group.db.id
}

resource "aws_security_group_rule" "db_from_app" {
  type                     = "ingress"
  from_port                = 5432
  to_port                  = 5432
  protocol                 = "tcp"
  security_group_id        = aws_security_group.db.id
  source_security_group_id = aws_security_group.app.id
}

โŒ ๋ฌธ์ œ 4: Provider ๋ฒ„์ „ ์ถฉ๋Œ

์ฆ์ƒ:
Error: Failed to query available provider packages

Could not retrieve the list of available versions for provider
hashicorp/aws: locked provider registry.terraform.io/hashicorp/aws 4.0.0
does not match configured version constraint ~> 5.0

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:
# 1. .terraform.lock.hcl ํŒŒ์ผ ์‚ญ์ œ
$ rm .terraform.lock.hcl

# 2. .terraform ๋””๋ ‰ํ† ๋ฆฌ ์‚ญ์ œ
$ rm -rf .terraform

# 3. ๋‹ค์‹œ ์ดˆ๊ธฐํ™”
$ terraform init -upgrade

# 4. ์ƒˆ๋กœ์šด lock ํŒŒ์ผ ์ƒ์„ฑ๋จ
๐Ÿ’ก ๋ฒ„์ „ ๊ด€๋ฆฌ ํŒ: - versions.tf ํŒŒ์ผ์— ๋ช…์‹œ์ ์œผ๋กœ ๋ฒ„์ „ ์ง€์ •
- .terraform.lock.hcl์€ Git์— ์ปค๋ฐ‹
- ํŒ€ ์ „์ฒด๊ฐ€ ๊ฐ™์€ Terraform ๋ฒ„์ „ ์‚ฌ์šฉ
- ๋ฉ”์ด์ € ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ๋Š” ์‹ ์ค‘ํ•˜๊ฒŒ!

โŒ ๋ฌธ์ œ 5: ๋ฆฌ์†Œ์Šค ์‚ญ์ œ ์‹คํŒจ

์ฆ์ƒ:
Error: Error deleting VPC: DependencyViolation: The vpc 'vpc-xxxxx' has
dependencies and cannot be deleted.

์›์ธ:
VPC ์•ˆ์— ๋‹ค๋ฅธ ๋ฆฌ์†Œ์Šค๋“ค์ด ๋‚จ์•„์žˆ์Œ (ENI, NAT Gateway ๋“ฑ)

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•:
# 1. ์˜์กด์„ฑ ํ™•์ธ
$ terraform state list

# 2. ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„ ํ™•์ธ
$ terraform graph | dot -Tpng > graph.png

# 3. ์ˆ˜๋™์œผ๋กœ ๋ฆฌ์†Œ์Šค ์ •๋ฆฌ
$ aws ec2 describe-network-interfaces --filters "Name=vpc-id,Values=vpc-xxxxx"

# 4. Terraform์œผ๋กœ ๋‹ค์‹œ ์‹œ๋„
$ terraform destroy -target=aws_vpc.main

# 5. ๊ทธ๋ž˜๋„ ์•ˆ ๋˜๋ฉด State์—์„œ ์ œ๊ฑฐํ•˜๊ณ  ์ˆ˜๋™ ์‚ญ์ œ
$ terraform state rm aws_vpc.main
$ aws ec2 delete-vpc --vpc-id vpc-xxxxx
โš ๏ธ ์ฃผ์˜: terraform state rm์€ ์ตœํ›„์˜ ์ˆ˜๋‹จ! State์—์„œ๋งŒ ์ œ๊ฑฐ๋˜๊ณ  ์‹ค์ œ ๋ฆฌ์†Œ์Šค๋Š” ๋‚จ์•„์žˆ์–ด์„œ, ๋‚˜์ค‘์— ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด.

โŒ ๋ฌธ์ œ 6: CI/CD ํŒŒ์ดํ”„๋ผ์ธ ์‹คํŒจ

์ฆ์ƒ:
GitHub Actions์—์„œ Terraform apply๊ฐ€ ์‹คํŒจํ•จ.

์ฃผ์š” ์›์ธ๋“ค:

1. AWS ์ž๊ฒฉ์ฆ๋ช… ๋ฌธ์ œ
# ํ•ด๊ฒฐ: GitHub Secrets ํ™•์ธ
Settings โ†’ Secrets and variables โ†’ Actions
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_REGION

2. ๊ถŒํ•œ ๋ถ€์กฑ
# IAM ์ •์ฑ… ํ™•์ธ
$ aws iam get-role-policy \
    --role-name GitHubActionsRole \
    --policy-name TerraformPolicy

3. ํƒ€์ž„์•„์›ƒ
# GitHub Actions์—์„œ ํƒ€์ž„์•„์›ƒ ๋Š˜๋ฆฌ๊ธฐ
jobs:
  terraform:
    timeout-minutes: 60  # ๊ธฐ๋ณธ๊ฐ’์€ 360๋ถ„

4. ๋™์‹œ ์‹คํ–‰
# ๋™์‹œ ์‹คํ–‰ ๋ฐฉ์ง€
concurrency:
  group: terraform-${{ github.ref }}
  cancel-in-progress: false  # ์ง„ํ–‰ ์ค‘์ธ ์ž‘์—… ์ทจ์†Œํ•˜์ง€ ์•Š์Œ

๐Ÿ’ฐ ๋น„์šฉ ์ตœ์ ํ™” ์ „๋žต

ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ๋ฅผ ์ž๋™ํ™”ํ•˜๋ฉด ํŽธํ•˜์ง€๋งŒ, ๋ฐฉ์‹ฌํ•˜๋ฉด ๋น„์šฉ์ด ํญํƒ„์ฒ˜๋Ÿผ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์–ด! ๐Ÿ’ฃ ์‹ค์ „์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋น„์šฉ ์ตœ์ ํ™” ์ „๋žต๋“ค์„ ๊ณต์œ ํ• ๊ฒŒ.

๐Ÿ“Š 1. Infracost๋กœ ๋น„์šฉ ์˜ˆ์ธก

Terraform ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ์˜ˆ์ƒ ๋น„์šฉ์„ ๋ฏธ๋ฆฌ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด!

GitHub Actions์— ํ†ตํ•ฉ:
- name: Setup Infracost
  uses: infracost/actions/setup@v2
  with:
    api-key: ${{ secrets.INFRACOST_API_KEY }}

- name: Generate Infracost JSON
  run: |
    infracost breakdown --path . \
      --format json \
      --out-file /tmp/infracost.json

- name: Post Infracost comment
  uses: infracost/actions/comment@v1
  with:
    path: /tmp/infracost.json
    behavior: update

PR์— ์ž๋™์œผ๋กœ ๋น„์šฉ ์ •๋ณด ํ‘œ์‹œ:
๐Ÿ’ฐ Monthly cost estimate

  Name                     Monthly Qty  Unit   Monthly Cost
  
  aws_instance.web_server
  โ”œโ”€ Instance usage               730  hours        $8.47
  โ””โ”€ root_block_device
     โ””โ”€ Storage                    20  GB           $2.00
  
  aws_rds_instance.main
  โ”œโ”€ Database instance            730  hours       $73.00
  โ””โ”€ Storage                      100  GB          $11.50
  
  OVERALL TOTAL                                    $94.97
  
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
  
  ๐Ÿ’ธ Change from current: +$94.97 (+100%)
๐Ÿ’ก ๋น„์šฉ ์•Œ๋ฆผ ์„ค์ •: ์˜ˆ์ƒ ๋น„์šฉ์ด ์ผ์ • ๊ธˆ์•ก์„ ์ดˆ๊ณผํ•˜๋ฉด ์ž๋™์œผ๋กœ ์Šน์ธ์„ ์š”๊ตฌํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด!

โฐ 2. ์Šค์ผ€์ค„๋ง์œผ๋กœ ๋น„์šฉ ์ ˆ๊ฐ

๊ฐœ๋ฐœ/์Šคํ…Œ์ด์ง• ํ™˜๊ฒฝ์€ ์—…๋ฌด ์‹œ๊ฐ„์—๋งŒ ์ผœ๋‘๊ณ , ๋ฐค์—๋Š” ์ž๋™์œผ๋กœ ๊บผ๋ฒ„๋ฆฌ์ž!

Lambda + EventBridge๋กœ ์ž๋™ํ™”:
# lambda_scheduler.tf
resource "aws_lambda_function" "instance_scheduler" {
  filename      = "scheduler.zip"
  function_name = "instance-scheduler"
  role          = aws_iam_role.lambda_scheduler.arn
  handler       = "index.handler"
  runtime       = "python3.9"
  
  environment {
    variables = {
      ENVIRONMENT = var.environment
    }
  }
}

# ํ‰์ผ ์˜ค์ „ 9์‹œ์— ์‹œ์ž‘
resource "aws_cloudwatch_event_rule" "start_instances" {
  name                = "start-instances"
  schedule_expression = "cron(0 0 * * MON-FRI *)"  # UTC ๊ธฐ์ค€
}

resource "aws_cloudwatch_event_target" "start_instances" {
  rule      = aws_cloudwatch_event_rule.start_instances.name
  target_id = "StartInstances"
  arn       = aws_lambda_function.instance_scheduler.arn
  input     = jsonencode({
    action = "start"
  })
}

# ํ‰์ผ ์˜คํ›„ 7์‹œ์— ์ค‘์ง€
resource "aws_cloudwatch_event_rule" "stop_instances" {
  name                = "stop-instances"
  schedule_expression = "cron(0 10 * * MON-FRI *)"  # UTC ๊ธฐ์ค€
}

resource "aws_cloudwatch_event_target" "stop_instances" {
  rule      = aws_cloudwatch_event_rule.stop_instances.name
  target_id = "StopInstances"
  arn       = aws_lambda_function.instance_scheduler.arn
  input     = jsonencode({
    action = "stop"
  })
}

์˜ˆ์ƒ ์ ˆ๊ฐ์•ก:
- ํ•˜๋ฃจ 10์‹œ๊ฐ„๋งŒ ์šด์˜ (์—…๋ฌด ์‹œ๊ฐ„)
- ์ฃผ๋ง ์™„์ „ ์ค‘์ง€
- ์›” 730์‹œ๊ฐ„ โ†’ ์•ฝ 220์‹œ๊ฐ„ (70% ์ ˆ๊ฐ!)
- EC2 t3.medium ๊ธฐ์ค€: $30/์›” โ†’ $9/์›” ๐Ÿ’ฐ

๐ŸŽฏ 3. ์ ์ ˆํ•œ ์ธ์Šคํ„ด์Šค ํƒ€์ž… ์„ ํƒ

ํ™˜๊ฒฝ ์ถ”์ฒœ ์ธ์Šคํ„ด์Šค ์ด์œ 
๊ฐœ๋ฐœ t3.micro ~ t3.small ๋ฒ„์ŠคํŠธ ๊ฐ€๋Šฅ, ์ €๋ ด, ๊ฐœ๋ฐœ์— ์ถฉ๋ถ„
์Šคํ…Œ์ด์ง• t3.medium ํ”„๋กœ๋•์…˜๊ณผ ์œ ์‚ฌํ•œ ํ™˜๊ฒฝ ํ…Œ์ŠคํŠธ
ํ”„๋กœ๋•์…˜ c5.large ์ด์ƒ ์•ˆ์ •์ ์ธ ์„ฑ๋Šฅ, ์˜ˆ์ธก ๊ฐ€๋Šฅ
๋ฐฐ์น˜ ์ž‘์—… Spot Instance ์ตœ๋Œ€ 90% ํ• ์ธ!

Spot Instance ํ™œ์šฉ:
resource "aws_spot_instance_request" "batch_worker" {
  ami           = var.ami_id
  instance_type = "c5.xlarge"
  spot_price    = "0.05"  # ์ตœ๋Œ€ ์ง€๋ถˆ ๊ฐ€๊ฒฉ
  
  wait_for_fulfillment = true
  spot_type            = "one-time"
  
  tags = {
    Name = "batch-worker"
  }
}

# Spot Instance๋Š” ์ค‘๋‹จ๋  ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ค‘์š”ํ•œ ์ž‘์—…์€ ํ”ผํ•˜๊ธฐ!

๐Ÿ’พ 4. ์Šคํ† ๋ฆฌ์ง€ ์ตœ์ ํ™”

EBS ๋ณผ๋ฅจ ํƒ€์ž… ์„ ํƒ:
resource "aws_ebs_volume" "data" {
  availability_zone = "ap-northeast-2a"
  
  # gp3๊ฐ€ gp2๋ณด๋‹ค 20% ์ €๋ ดํ•˜๊ณ  ์„ฑ๋Šฅ๋„ ์ข‹์•„!
  type = "gp3"
  size = 100
  
  # IOPS์™€ ์ฒ˜๋ฆฌ๋Ÿ‰์„ ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ์„ค์ •
  iops       = 3000  # ๊ธฐ๋ณธ๊ฐ’
  throughput = 125   # ๊ธฐ๋ณธ๊ฐ’
  
  tags = {
    Name = "data-volume"
  }
}

S3 Lifecycle ์ •์ฑ…:
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id
  
  rule {
    id     = "archive-old-logs"
    status = "Enabled"
    
    # 30์ผ ํ›„ Glacier๋กœ ์ด๋™
    transition {
      days          = 30
      storage_class = "GLACIER"
    }
    
    # 90์ผ ํ›„ ์‚ญ์ œ
    expiration {
      days = 90
    }
  }
  
  rule {
    id     = "delete-incomplete-uploads"
    status = "Enabled"
    
    abort_incomplete_multipart_upload {
      days_after_initiation = 7
    }
  }
}

์˜ˆ์ƒ ์ ˆ๊ฐ์•ก:
- S3 Standard: $0.025/GB
- S3 Glacier: $0.004/GB (84% ์ ˆ๊ฐ!)
- 1TB ๋กœ๊ทธ ๋ฐ์ดํ„ฐ ๊ธฐ์ค€: $25/์›” โ†’ $4/์›” ๐Ÿ’ฐ

๐Ÿ” 5. ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋ฆฌ์†Œ์Šค ์ฐพ๊ธฐ

AWS Cost Explorer๋กœ ๋ถ„์„:
# Terraform์œผ๋กœ Cost Explorer ํ™œ์„ฑํ™”
resource "aws_ce_cost_category" "environment" {
  name         = "Environment"
  rule_version = "CostCategoryExpression.v1"
  
  rule {
    value = "Production"
    rule {
      dimension {
        key           = "TAG"
        values        = ["production"]
        match_options = ["EQUALS"]
      }
    }
  }
  
  rule {
    value = "Development"
    rule {
      dimension {
        key           = "TAG"
        values        = ["dev", "development"]
        match_options = ["EQUALS"]
      }
    }
  }
}

์ •๊ธฐ์ ์œผ๋กœ ํ™•์ธํ•  ๊ฒƒ๋“ค:
โœ… ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์€ EBS ๋ณผ๋ฅจ
โœ… ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” Elastic IP
โœ… ์˜ค๋ž˜๋œ ์Šค๋ƒ…์ƒท
โœ… ํŠธ๋ž˜ํ”ฝ ์—†๋Š” Load Balancer
โœ… ๋นˆ S3 ๋ฒ„ํ‚ท
โœ… ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” NAT Gateway

๐ŸŽ‰ ์‹ค์ œ ์‚ฌ๋ก€: ํ•œ ์Šคํƒ€ํŠธ์—…์—์„œ ์ด๋Ÿฐ ์ตœ์ ํ™”๋ฅผ ํ†ตํ•ด ์›” $3,000 โ†’ $1,200์œผ๋กœ 60% ์ ˆ๊ฐํ–ˆ์–ด! ํŠนํžˆ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์Šค์ผ€์ค„๋ง๊ณผ Spot Instance ํ™œ์šฉ์ด ํฐ ํšจ๊ณผ๋ฅผ ๋ดค์ง€. ๐Ÿ’ช

๐ŸŽ“ ํ•™์Šต ๋กœ๋“œ๋งต๊ณผ ๋‹ค์Œ ๋‹จ๊ณ„

์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐Ÿ‘ ์ด์ œ Terraform๊ณผ CI/CD์˜ ๊ธฐ๋ณธ์€ ๋งˆ์Šคํ„ฐํ–ˆ์–ด. ํ•˜์ง€๋งŒ DevOps์˜ ์„ธ๊ณ„๋Š” ์ •๋ง ๋„“๊ณ  ๊นŠ์–ด. ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ ์œ„ํ•œ ๋กœ๋“œ๋งต์„ ์•Œ๋ ค์ค„๊ฒŒ!

๐Ÿ“š ์ดˆ๊ธ‰ โ†’ ์ค‘๊ธ‰ (์ง€๊ธˆ ๋‹น์‹ ์˜ ์œ„์น˜!)

โœ… ์ด๋ฏธ ๋ฐฐ์šด ๊ฒƒ๋“ค:
- Terraform ๊ธฐ๋ณธ ๋ฌธ๋ฒ•๊ณผ ์›Œํฌํ”Œ๋กœ์šฐ
- ์ฃผ์š” ๋ฆฌ์†Œ์Šค ํƒ€์ž… (VPC, EC2, RDS ๋“ฑ)
- State ๊ด€๋ฆฌ์™€ Remote Backend
- GitHub Actions๋ฅผ ํ†ตํ•œ CI/CD
- ๊ธฐ๋ณธ์ ์ธ ๋ณด์•ˆ ์„ค์ •

๐ŸŽฏ ๋‹ค์Œ ํ•™์Šต ๋ชฉํ‘œ:
1. Terraform ๋ชจ๋“ˆ ๊ณ ๊ธ‰ ํ™œ์šฉ
- ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“ˆ ์„ค๊ณ„
- ๋ชจ๋“ˆ ๋ฒ„์ „ ๊ด€๋ฆฌ
- Terraform Registry ํ™œ์šฉ

2. ๋‹ค์–‘ํ•œ Provider ๊ฒฝํ—˜
- Kubernetes Provider
- Helm Provider
- Datadog, PagerDuty ๋“ฑ ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ

3. ๊ณ ๊ธ‰ State ๊ด€๋ฆฌ
- Workspace ํ™œ์šฉ
- State ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
- State ๋ฐฑ์—… ๋ฐ ๋ณต๊ตฌ

4. ํ…Œ์ŠคํŠธ ์ž๋™ํ™”
- Terratest๋กœ ์ธํ”„๋ผ ํ…Œ์ŠคํŠธ
- Kitchen-Terraform
- Compliance ํ…Œ์ŠคํŠธ

๐Ÿš€ ์ค‘๊ธ‰ โ†’ ๊ณ ๊ธ‰

1. GitOps ์™„์ „ ์ •๋ณต
# ArgoCD + Terraform
resource "kubernetes_namespace" "argocd" {
  metadata {
    name = "argocd"
  }
}

resource "helm_release" "argocd" {
  name       = "argocd"
  repository = "https://argoproj.github.io/argo-helm"
  chart      = "argo-cd"
  namespace  = kubernetes_namespace.argocd.metadata[0].name
  
  set {
    name  = "server.service.type"
    value = "LoadBalancer"
  }
}

2. Multi-Cloud ์ „๋žต
- AWS + GCP ๋™์‹œ ๊ด€๋ฆฌ
- Cloud-agnostic ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„
- ํด๋ผ์šฐ๋“œ ๊ฐ„ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”

3. ๋Œ€๊ทœ๋ชจ ์ธํ”„๋ผ ๊ด€๋ฆฌ
- Terraform Cloud/Enterprise
- Sentinel ์ •์ฑ… ๊ด€๋ฆฌ
- Cost Estimation & Governance
- Private Module Registry

4. ๊ณ ๊ธ‰ ๋ณด์•ˆ
- Vault๋ฅผ ํ†ตํ•œ ์‹œํฌ๋ฆฟ ๊ด€๋ฆฌ
- OPA (Open Policy Agent) ํ†ตํ•ฉ
- Compliance as Code
- Zero Trust ์•„ํ‚คํ…์ฒ˜

๐Ÿ› ๏ธ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด

ํ”„๋กœ์ ํŠธ 1: ๊ฐœ์ธ ๋ธ”๋กœ๊ทธ ์ธํ”„๋ผ
- CloudFront + S3๋กœ ์ •์  ์‚ฌ์ดํŠธ
- Route53์œผ๋กœ ์ปค์Šคํ…€ ๋„๋ฉ”์ธ
- ACM์œผ๋กœ SSL ์ธ์ฆ์„œ
- GitHub Actions๋กœ ์ž๋™ ๋ฐฐํฌ
- ์˜ˆ์ƒ ๋น„์šฉ: ์›” $5 ์ดํ•˜

ํ”„๋กœ์ ํŠธ 2: ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ”Œ๋žซํผ
- EKS ํด๋Ÿฌ์Šคํ„ฐ ๊ตฌ์ถ•
- Service Mesh (Istio)
- ์ค‘์•™ํ™”๋œ ๋กœ๊น… (ELK Stack)
- ๋ถ„์‚ฐ ์ถ”์  (Jaeger)
- ์˜ˆ์ƒ ๋น„์šฉ: ์›” $200-500

ํ”„๋กœ์ ํŠธ 3: ๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ
- Airflow on ECS
- Redshift ๋ฐ์ดํ„ฐ ์›จ์–ดํ•˜์šฐ์Šค
- Glue ETL ์ž‘์—…
- QuickSight ๋Œ€์‹œ๋ณด๋“œ
- ์˜ˆ์ƒ ๋น„์šฉ: ์›” $300-800

๐Ÿ’ก ํ”„๋กœ์ ํŠธ ํŒ: ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๊ฐ€๋Š” ๊ฒŒ ์ข‹์•„. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ GitHub์— ์˜ฌ๋ ค์„œ ํฌํŠธํด๋ฆฌ์˜ค๋กœ ํ™œ์šฉํ•˜์ž! ์žฌ๋Šฅ๋„ท์—์„œ DevOps ๋ฉ˜ํ† ๋ง์„ ์ œ๊ณตํ•  ๋•Œ๋„ ์ด๋Ÿฐ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜์ด ํฐ ๋„์›€์ด ๋ผ. ๐ŸŽฏ

๐Ÿ“– ์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ

๊ณต์‹ ๋ฌธ์„œ:
- Terraform Documentation (terraform.io/docs)
- AWS Well-Architected Framework
- Kubernetes Documentation

์˜จ๋ผ์ธ ๊ฐ•์˜:
- HashiCorp Learn (learn.hashicorp.com)
- A Cloud Guru
- Udemy์˜ Terraform ๊ฐ•์˜๋“ค

์ปค๋ฎค๋‹ˆํ‹ฐ:
- Terraform Community Forum
- Reddit r/Terraform
- DevOps Korea ์ปค๋ฎค๋‹ˆํ‹ฐ
- ์žฌ๋Šฅ๋„ท์˜ DevOps ์ „๋ฌธ๊ฐ€๋“ค

์ฑ…:
- "Terraform: Up & Running" by Yevgeniy Brikman
- "Infrastructure as Code" by Kief Morris
- "The DevOps Handbook"

๐ŸŽ“ ์ž๊ฒฉ์ฆ ์ถ”์ฒœ: - HashiCorp Certified: Terraform Associate
- AWS Certified DevOps Engineer
- Certified Kubernetes Administrator (CKA)

์ด๋Ÿฐ ์ž๊ฒฉ์ฆ๋“ค์€ ์ทจ์—…์ด๋‚˜ ์ด์งํ•  ๋•Œ ์ •๋ง ๋„์›€์ด ๋ผ!

๐Ÿค ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฐธ์—ฌํ•˜๊ธฐ

ํ˜ผ์ž ๊ณต๋ถ€ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์ง€๋งŒ, ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์ฐธ์—ฌํ•˜๋ฉด ํ›จ์”ฌ ๋น ๋ฅด๊ฒŒ ์„ฑ์žฅํ•  ์ˆ˜ ์žˆ์–ด!

์˜คํ”ˆ์†Œ์Šค ๊ธฐ์—ฌ:
- Terraform Provider ๋ฒ„๊ทธ ๋ฆฌํฌํŠธ
- ๋ฌธ์„œ ๊ฐœ์„  PR
- ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ชจ๋“ˆ ๊ฐœ๋ฐœ

์ง€์‹ ๊ณต์œ :
- ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ ์ž‘์„ฑ
- ์ปจํผ๋Ÿฐ์Šค ๋ฐœํ‘œ
- ์žฌ๋Šฅ๋„ท์—์„œ ๋ฉ˜ํ† ๋ง ์ œ๊ณต
- YouTube ํŠœํ† ๋ฆฌ์–ผ ์ œ์ž‘

๐Ÿ’ก ์žฌ๋Šฅ๋„ท ํ™œ์šฉํ•˜๊ธฐ: Terraform์ด๋‚˜ DevOps ๊ด€๋ จ ์ง€์‹์„ ์žฌ๋Šฅ๋„ท์—์„œ ๊ณต์œ ํ•ด๋ณด๋Š” ๊ฑด ์–ด๋•Œ? ๋‹ค๋ฅธ ์‚ฌ๋žŒ์„ ๊ฐ€๋ฅด์น˜๋‹ค ๋ณด๋ฉด ์ž์‹ ์˜ ์ง€์‹๋„ ๋” ๋‹จ๋‹จํ•ด์ง€๊ณ , ๋ถ€์ˆ˜์ž…๋„ ์ƒ๊ธฐ๊ณ , ๋„คํŠธ์›Œํ‚น๋„ ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ผ์„์‚ผ์กฐ์•ผ! ๐ŸŽฏ

๐ŸŽฌ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด. ๐ŸŽ‰ Terraform๊ณผ CI/CD๋ฅผ ํ™œ์šฉํ•œ ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ ์ž๋™ํ™”์— ๋Œ€ํ•ด ์ •๋ง ๋งŽ์€ ๊ฑธ ๋ฐฐ์› ์ง€?

โœจ ํ•ต์‹ฌ ์š”์•ฝ

1. Infrastructure as Code์˜ ํž˜
- ์ฝ”๋“œ๋กœ ์ธํ”„๋ผ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ์ผ๊ด€์„ฑ, ์žฌํ˜„์„ฑ, ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด
- Terraform์€ ์„ ์–ธ์  ๋ฌธ๋ฒ•์œผ๋กœ ์‰ฝ๊ฒŒ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด
- ๋ฉ€ํ‹ฐ ํด๋ผ์šฐ๋“œ ์ง€์›์œผ๋กœ ๋ฒค๋” ์ข…์†์„ฑ ์—†์Œ

2. CI/CD ํ†ตํ•ฉ์˜ ์ค‘์š”์„ฑ
- ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ์™€ ๋ฐฐํฌ๋กœ ํœด๋จผ ์—๋Ÿฌ ๋ฐฉ์ง€
- Pull Request ๊ธฐ๋ฐ˜ ์›Œํฌํ”Œ๋กœ์šฐ๋กœ ํ˜‘์—… ๊ฐ•ํ™”
- GitOps๋กœ ๋ชจ๋“  ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ถ”์  ๊ฐ€๋Šฅ

3. ๋ณด์•ˆ์€ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜
- ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ์ ˆ๋Œ€ ์ฝ”๋“œ์— ๋„ฃ์ง€ ๋ง๊ธฐ
- State ํŒŒ์ผ ์•”ํ˜ธํ™”์™€ ์ ‘๊ทผ ์ œ์–ด
- ์ž๋™ํ™”๋œ ๋ณด์•ˆ ์Šค์บ” ๋„๊ตฌ ํ™œ์šฉ

4. ๋น„์šฉ ์ตœ์ ํ™”
- ํ™˜๊ฒฝ๋ณ„๋กœ ์ ์ ˆํ•œ ๋ฆฌ์†Œ์Šค ํฌ๊ธฐ ์„ ํƒ
- ์Šค์ผ€์ค„๋ง์œผ๋กœ ๋ถˆํ•„์š”ํ•œ ๋น„์šฉ ์ ˆ๊ฐ
- Infracost๋กœ ์‚ฌ์ „์— ๋น„์šฉ ์˜ˆ์ธก

5. ์ง€์†์ ์ธ ํ•™์Šต
- DevOps๋Š” ๋Š์ž„์—†์ด ์ง„ํ™”ํ•˜๋Š” ๋ถ„์•ผ
- ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฐธ์—ฌ์™€ ์ง€์‹ ๊ณต์œ 
- ์‹ค์ „ ํ”„๋กœ์ ํŠธ๋กœ ๊ฒฝํ—˜ ์Œ“๊ธฐ

๐Ÿš€ ๋‹น์‹ ์˜ ๋‹ค์Œ ์•ก์…˜ ํ”Œ๋žœ

์˜ค๋Š˜ ๋‹น์žฅ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค:

1๋‹จ๊ณ„: ํ™˜๊ฒฝ ์„ค์ • (30๋ถ„)
- Terraform ์„ค์น˜
- AWS CLI ์„ค์ •
- GitHub ์ €์žฅ์†Œ ์ƒ์„ฑ

2๋‹จ๊ณ„: ์ฒซ ํ”„๋กœ์ ํŠธ (2-3์‹œ๊ฐ„)
- S3 ๋ฒ„ํ‚ท ํ•˜๋‚˜ ๋งŒ๋“ค์–ด๋ณด๊ธฐ
- Terraform ์›Œํฌํ”Œ๋กœ์šฐ ์ตํžˆ๊ธฐ
- GitHub Actions ์—ฐ๋™

3๋‹จ๊ณ„: ์‹ค์ „ ์ ์šฉ (1์ฃผ์ผ)
- ํšŒ์‚ฌ/๊ฐœ์ธ ํ”„๋กœ์ ํŠธ์— ์ ์šฉ
- ํŒ€์›๋“ค๊ณผ ์ง€์‹ ๊ณต์œ 
- ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฒฝํ—˜ ์Œ“๊ธฐ

4๋‹จ๊ณ„: ์‹ฌํ™” ํ•™์Šต (์ง€์†์ )
- ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ํ•™์Šต
- ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฐธ์—ฌ
- ์ž๊ฒฉ์ฆ ์ทจ๋“ ๊ณ ๋ ค

๐Ÿ’ฌ ๋งˆ์ง€๋ง‰ ํ•œ๋งˆ๋””

DevOps์™€ ์ธํ”„๋ผ ์ž๋™ํ™”๋Š” ์ฒ˜์Œ์—๋Š” ์–ด๋ ต๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ํ•œ ๋ฒˆ ์ต์ˆ™ํ•ด์ง€๋ฉด, ์ด์ „์œผ๋กœ๋Š” ์ ˆ๋Œ€ ๋Œ์•„๊ฐˆ ์ˆ˜ ์—†์„ ๊ฑฐ์•ผ! ๐Ÿ˜Š

์ˆ˜๋™์œผ๋กœ ์„œ๋ฒ„๋ฅผ ์„ค์ •ํ•˜๋˜ ์‹œ์ ˆ์ด ์–ผ๋งˆ๋‚˜ ๋น„ํšจ์œจ์ ์ด์—ˆ๋Š”์ง€ ๊นจ๋‹ซ๊ฒŒ ๋  ๊ฑฐ๊ณ , ์ฝ”๋“œ ํ•œ ์ค„๋กœ ์ˆ˜๋ฐฑ ๊ฐœ์˜ ์„œ๋ฒ„๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ž์‹ ์„ ๋ฐœ๊ฒฌํ•˜๊ฒŒ ๋  ๊ฑฐ์•ผ.

์‹ค์ˆ˜๋ฅผ ๋‘๋ ค์›Œํ•˜์ง€ ๋งˆ! ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ๋งˆ์Œ๊ป ์‹คํ—˜ํ•˜๊ณ , ์‹คํŒจํ•˜๊ณ , ๋ฐฐ์›Œ. ๊ทธ๊ฒŒ ์„ฑ์žฅํ•˜๋Š” ๊ฐ€์žฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ•์ด์•ผ. ๐Ÿ’ช

๊ทธ๋ฆฌ๊ณ  ํ˜ผ์ž ๊ณ ๋ฏผํ•˜์ง€ ๋ง๊ณ , ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์ „๋ฌธ๊ฐ€๋“ค์˜ ๋„์›€์„ ๋ฐ›๊ฑฐ๋‚˜, ๋ฐ˜๋Œ€๋กœ ๋‹น์‹ ์˜ ์ง€์‹์„ ๊ณต์œ ํ•˜๋ฉด์„œ ํ•จ๊ป˜ ์„ฑ์žฅํ•˜์ž!

๋‹น์‹ ์˜ DevOps ์—ฌ์ •์„ ์‘์›ํ•ด! ํ™”์ดํŒ…! ๐Ÿš€โœจ

๐Ÿ“ข ์งˆ๋ฌธ์ด๋‚˜ ํ”ผ๋“œ๋ฐฑ์ด ์žˆ๋‹ค๋ฉด: - ์žฌ๋Šฅ๋„ท ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ DevOps ์ „๋ฌธ๊ฐ€๋“ค๊ณผ ์†Œํ†ตํ•˜์„ธ์š”
- GitHub์—์„œ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์— ์ฐธ์—ฌํ•ด๋ณด์„ธ์š”
- ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ๋ฅผ ์‹œ์ž‘ํ•ด์„œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด์„ธ์š”
- ์ปจํผ๋Ÿฐ์Šค๋‚˜ ๋ฐ‹์—…์— ์ฐธ์„ํ•ด์„œ ๋„คํŠธ์›Œํ‚นํ•˜์„ธ์š”

ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” DevOps ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€์š”! ๐Ÿค
๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰ Terraform + CI/CD๋กœ ๋” ๋‚˜์€ ์ธํ”„๋ผ๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€์„ธ์š”! ๐Ÿš€ ๐Ÿ’ป โš™๏ธ ๐Ÿ”ง โœจ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.