Infrastructure as Code (Terraform)
Infrastructure as Code (Terraform)¶
1. IaC κ°μ¶
1.1 Infrastructure as Codeλ?¶
IaCλ μΈνλΌλ₯Ό μ½λλ‘ μ μνκ³ κ΄λ¦¬νλ λ°©μμ λλ€.
μ₯μ : - λ²μ κ΄λ¦¬ (Git) - μ¬ν κ°λ₯μ± - μλν - λ¬Έμν - νμ
1.2 IaC λꡬ λΉκ΅¶
| λꡬ | μ ν | μΈμ΄ | λ©ν° ν΄λΌμ°λ |
|---|---|---|---|
| Terraform | μ μΈμ | HCL | β |
| CloudFormation | μ μΈμ | JSON/YAML | AWSλ§ |
| Deployment Manager | μ μΈμ | YAML/Jinja | GCPλ§ |
| Pulumi | μ μΈμ | Python/TS λ± | β |
| Ansible | μ μ°¨μ | YAML | β |
2. Terraform κΈ°μ΄¶
2.1 μ€μΉ¶
# macOS
brew install terraform
# Linux
wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
unzip terraform_1.6.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
# λ²μ νμΈ
terraform version
2.2 κΈ°λ³Έ κ°λ ¶
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Terraform μν¬νλ‘μ° β
β β
β 1. Write β .tf νμΌ μμ± β
β 2. Init β terraform init (νλ‘λ°μ΄λ λ€μ΄λ‘λ) β
β 3. Plan β terraform plan (λ³κ²½ μ¬ν 미리보기) β
β 4. Apply β terraform apply (μΈνλΌ μ μ©) β
β 5. Destroy β terraform destroy (μΈνλΌ μμ ) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
2.3 HCL λ¬Έλ²¶
# νλ‘λ°μ΄λ μ€μ
provider "aws" {
region = "ap-northeast-2"
}
# 리μμ€ μ μ
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t3.micro"
tags = {
Name = "WebServer"
}
}
# λ³μ
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
# μΆλ ₯
output "public_ip" {
value = aws_instance.web.public_ip
}
# λ‘컬 κ°
locals {
environment = "production"
common_tags = {
Environment = local.environment
ManagedBy = "Terraform"
}
}
# λ°μ΄ν° μμ€ (κΈ°μ‘΄ 리μμ€ μ°Έμ‘°)
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
3. AWS μΈνλΌ κ΅¬μ±¶
3.1 VPC + EC2 μμ ¶
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = {
Name = "${var.project}-vpc"
}
}
# νΌλΈλ¦ μλΈλ·
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24"
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project}-public-${count.index + 1}"
}
}
# μΈν°λ· κ²μ΄νΈμ¨μ΄
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project}-igw"
}
}
# λΌμ°ν
ν
μ΄λΈ
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project}-public-rt"
}
}
resource "aws_route_table_association" "public" {
count = 2
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# 보μ κ·Έλ£Ή
resource "aws_security_group" "web" {
name = "${var.project}-web-sg"
description = "Web server security group"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.ssh_allowed_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# EC2 μΈμ€ν΄μ€
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[0].id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = var.key_name
user_data = <<-EOF
#!/bin/bash
dnf update -y
dnf install -y nginx
systemctl start nginx
systemctl enable nginx
echo "<h1>Hello from Terraform!</h1>" > /usr/share/nginx/html/index.html
EOF
tags = {
Name = "${var.project}-web"
}
}
# λ°μ΄ν° μμ€
data "aws_availability_zones" "available" {
state = "available"
}
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
}
# variables.tf
variable "region" {
description = "AWS region"
type = string
default = "ap-northeast-2"
}
variable "project" {
description = "Project name"
type = string
default = "myapp"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "key_name" {
description = "SSH key pair name"
type = string
}
variable "ssh_allowed_cidr" {
description = "CIDR block allowed for SSH"
type = string
default = "0.0.0.0/0"
}
# outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "public_ip" {
description = "Web server public IP"
value = aws_instance.web.public_ip
}
output "website_url" {
description = "Website URL"
value = "http://${aws_instance.web.public_ip}"
}
4. GCP μΈνλΌ κ΅¬μ±¶
4.1 VPC + Compute Engine μμ ¶
# main.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# VPC
resource "google_compute_network" "main" {
name = "${var.name_prefix}-vpc"
auto_create_subnetworks = false
}
# μλΈλ·
resource "google_compute_subnetwork" "public" {
name = "${var.name_prefix}-subnet"
ip_cidr_range = "10.0.1.0/24"
region = var.region
network = google_compute_network.main.id
}
# λ°©νλ²½ κ·μΉ - HTTP
resource "google_compute_firewall" "http" {
name = "${var.name_prefix}-allow-http"
network = google_compute_network.main.name
allow {
protocol = "tcp"
ports = ["80", "443"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["http-server"]
}
# λ°©νλ²½ κ·μΉ - SSH
resource "google_compute_firewall" "ssh" {
name = "${var.name_prefix}-allow-ssh"
network = google_compute_network.main.name
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = [var.ssh_allowed_cidr]
target_tags = ["ssh-server"]
}
# Compute Engine μΈμ€ν΄μ€
resource "google_compute_instance" "web" {
name = "${var.name_prefix}-web"
machine_type = var.machine_type
zone = "${var.region}-a"
boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-2204-lts"
size = 20
}
}
network_interface {
network = google_compute_network.main.name
subnetwork = google_compute_subnetwork.public.name
access_config {
// μΈλΆ IP ν λΉ
}
}
metadata_startup_script = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
echo "<h1>Hello from Terraform on GCP!</h1>" > /var/www/html/index.html
EOF
tags = ["http-server", "ssh-server"]
labels = {
environment = var.environment
}
}
# variables.tf
variable "project_id" {
description = "GCP project ID"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "asia-northeast3"
}
variable "name_prefix" {
description = "Resource name prefix"
type = string
default = "myapp"
}
variable "machine_type" {
description = "Compute Engine machine type"
type = string
default = "e2-micro"
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "ssh_allowed_cidr" {
description = "CIDR allowed for SSH"
type = string
default = "0.0.0.0/0"
}
# outputs.tf
output "instance_ip" {
description = "Instance external IP"
value = google_compute_instance.web.network_interface[0].access_config[0].nat_ip
}
output "website_url" {
description = "Website URL"
value = "http://${google_compute_instance.web.network_interface[0].access_config[0].nat_ip}"
}
5. μν κ΄λ¦¬¶
5.1 μ격 μν μ μ₯μ¶
AWS S3 λ°±μλ:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-locks" # μν μ κΈ
}
}
GCP Cloud Storage λ°±μλ:
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "prod/terraform.tfstate"
}
}
5.2 μν λͺ λ Ήμ΄¶
# μν λͺ©λ‘
terraform state list
# μν μ‘°ν
terraform state show aws_instance.web
# μνμμ 리μμ€ μ κ±° (μ€μ 리μμ€λ μ μ§)
terraform state rm aws_instance.web
# μν μ΄λ (리ν©ν λ§)
terraform state mv aws_instance.old aws_instance.new
# μν κ°μ Έμ€κΈ° (κΈ°μ‘΄ 리μμ€)
terraform import aws_instance.web i-1234567890abcdef0
6. λͺ¨λ¶
6.1 λͺ¨λ ꡬ쑰¶
modules/
βββ vpc/
β βββ main.tf
β βββ variables.tf
β βββ outputs.tf
βββ ec2/
βββ main.tf
βββ variables.tf
βββ outputs.tf
6.2 λͺ¨λ μ μ¶
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
enable_dns_hostnames = true
tags = merge(var.tags, {
Name = var.name
})
}
resource "aws_subnet" "public" {
count = length(var.public_subnets)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnets[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index + 1}"
})
}
# modules/vpc/variables.tf
variable "name" {
type = string
}
variable "cidr_block" {
type = string
default = "10.0.0.0/16"
}
variable "public_subnets" {
type = list(string)
}
variable "availability_zones" {
type = list(string)
}
variable "tags" {
type = map(string)
default = {}
}
# modules/vpc/outputs.tf
output "vpc_id" {
value = aws_vpc.this.id
}
output "public_subnet_ids" {
value = aws_subnet.public[*].id
}
6.3 λͺ¨λ μ¬μ©¶
# main.tf
module "vpc" {
source = "./modules/vpc"
name = "myapp"
cidr_block = "10.0.0.0/16"
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
availability_zones = ["ap-northeast-2a", "ap-northeast-2c"]
tags = {
Environment = "production"
}
}
module "ec2" {
source = "./modules/ec2"
name = "myapp-web"
subnet_id = module.vpc.public_subnet_ids[0]
instance_type = "t3.micro"
}
7. μν¬μ€νμ΄μ€¶
# μν¬μ€νμ΄μ€ λͺ©λ‘
terraform workspace list
# μ μν¬μ€νμ΄μ€ μμ±
terraform workspace new dev
terraform workspace new prod
# μν¬μ€νμ΄μ€ μ ν
terraform workspace select prod
# νμ¬ μν¬μ€νμ΄μ€
terraform workspace show
# μν¬μ€νμ΄μ€λ³ μ€μ
locals {
environment = terraform.workspace
instance_type = {
dev = "t3.micro"
prod = "t3.large"
}
}
resource "aws_instance" "web" {
instance_type = local.instance_type[local.environment]
# ...
}
8. λͺ¨λ² μ¬λ‘¶
8.1 λλ ν 리 ꡬ쑰¶
terraform/
βββ environments/
β βββ dev/
β β βββ main.tf
β β βββ variables.tf
β β βββ terraform.tfvars
β βββ prod/
β βββ main.tf
β βββ variables.tf
β βββ terraform.tfvars
βββ modules/
β βββ vpc/
β βββ ec2/
β βββ rds/
βββ global/
βββ iam/
8.2 μ½λ μ€νμΌ¶
# 리μμ€ μ΄λ¦ κ·μΉ
resource "aws_instance" "web" { } # λ¨μ
resource "aws_subnet" "public" { } # 볡μ μ¬μ© μ count/for_each
# λ³μ κΈ°λ³Έκ°
variable "instance_type" {
description = "EC2 instance type" # νμ μ€λͺ
ν¬ν¨
type = string
default = "t3.micro"
}
# νκ·Έ μΌκ΄μ±
locals {
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = "Terraform"
}
}
8.3 보μ¶
# λ―Όκ°ν λ³μ
variable "db_password" {
type = string
sensitive = true
}
# λ―Όκ°ν μΆλ ₯
output "db_password" {
value = var.db_password
sensitive = true
}
9. CI/CD ν΅ν©¶
9.1 GitHub Actions¶
# .github/workflows/terraform.yml
name: Terraform
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -no-color
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
10. λ€μ λ¨κ³¶
- 17_Monitoring_Logging_Cost.md - λͺ¨λν°λ§
- Docker/ - Kubernetes IaC