MOA 서비스 인프라 구축을 위한 Terraform 구성 - 100-hours-a-week/4-bull4zo-wiki GitHub Wiki

개요

본 문서는 MOA 서비스의 안정적이고 확장 가능한 인프라 구축과 자동화를 위해 작성된 Terraform 코드의 구조와 주요 리소스를 설명합니다.

image

Terraform

TerraformHashicorp에서 개발한 IaC, Infrastructure as Code 도구로 클라우드 인프라 리소스를 선언형 구성 파일로 정의하고 자동으로 배포, 변경, 관리할 수 있도록 합니다.

Terraform 사용에 따른 이점

  • 자동화 : 명령어(terraform apply)를 통해 복잡한 인프라 리소스를 자동 생성, 변경, 삭제 가능
  • 계획 기능 : terraform plan을 통해 생성 및 변경 내용을 사전 검토 가능
  • 일관성 & 재현성 : 모든 인프라를 코드로 정의하여 누구나 같은 환경을 반복 생성 가능
  • 멀티 클라우드 지원 : GCP 뿐 아니라 AWS, Azure 등 다양한 클라우드 환경 지원
  • 버전 관리 및 협업 : Git 과 같은 형상관리 도구를 함께 활용하여 코드 기반 인프라 변경 이력을 명확히 추적 가능

Terraform 인프라 구성 목표

  • IaC(Infrastructure as Code) 기반 자동화된 인프라 배포
  • GCP 리전 asia-northeast3(서울) 기반 서비스 안정성 확보
  • 도메인 기반 로드 밸런싱과 정적/동적 자원의 분리 운영
  • 보안과 확장성을 고려한 네트워크 설계
  • 환경 분리(dev, prod) 지원을 위한 변수 기반 설계

Terraform 코드 구조

main.tf

네트워크

## 네트워크 설정
# 서비스용 VPC 생성
resource "google_compute_network" "vpc" {
  name                    = "moa-main-vpc-${var.environment}"
  auto_create_subnetworks = false
}

# 백엔드 서브넷 생성
resource "google_compute_subnetwork" "subnet" {
  name          = "moa-backend-sub-${var.environment}-a"
  ip_cidr_range = "[VPC_CIDR]"
  region        = var.region
  network       = google_compute_network.vpc.id
}
  • 리소스 명의 경우, 사전 팀 내에서 협의한 리소스명 규칙을 따라서 구성
  • 환경에 따른 리소스명 변화는 변수화를 통해 처리 $(var.environment)

정적 배포용 Cloud Storage 설정

# 정적 배포용 Cloud Storage 버킷 생성
resource "google_storage_bucket" "static_website" {
  name          = "${var.project_id}-${var.environment}-frontend"
  location      = var.region
  force_destroy = true

# 모든 트래픽을 index.html로 리다이렉트
  website {
    main_page_suffix = "index.html"
    not_found_page   = "index.html"
  }

  uniform_bucket_level_access = true

  cors {
    origin          = ["*"]
    method          = ["GET", "HEAD", "OPTIONS"]
    response_header = ["Content-Type"]
    max_age_seconds = 3600
  }
}
# 버킷 공개 접근 설정
resource "google_storage_bucket_iam_member" "public_access" {
  bucket = google_storage_bucket.static_website.name
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}
  • 버킷명은 globally unique 해야하는 조건이 붙으므로, 고유한 프로젝트명 + 환경 변수 조합으로 생성
  • 정적 배포를 위한 allUser에 대한 접근 허용

방화벽 설정

## 방화벽
# SSH, MySQL, Redis 방화벽 규칙
resource "google_compute_firewall" "allow_ssh_mysql" {
  name    = "moa-ssh-sg-${var.environment}"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["22", "3306", "6379"]
  }

  source_ranges = ["[VPC_CIDR]"]
  target_tags   = ["ssh", "mysql", "redis"]
}

# 웹 서비스 및 사용 API 방화벽 규칙
resource "google_compute_firewall" "allow_web_services" {
  name    = "moa-lb-sg-${var.environment}"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["80", "443", "8000", "8080", "9090", "3000", "3100", "5601", "8200", "9200", "9300"]
  }

  source_ranges = ["[VPC_CIDR]"]
  target_tags   = ["web", "api"]
}

# HTTP/HTTPS Health Check 방화벽 규칙
resource "google_compute_firewall" "allow_http_https_health" {
  name    = "moa-http-health-${var.environment}"
  network = google_compute_network.vpc.name

  allow {
    protocol = "tcp"
    ports    = ["80", "443", "8080"]  # Common health check port
  }

  # 구글 클라우드 로드 밸런서 헬스 체크를 위한 IP 범위
  source_ranges = [
    "[VPC_CIDR]",
    "130.211.0.0/22",    # Google Cloud Load Balancer health checks
    "35.191.0.0/16"      # Google Cloud health checks
  ]
}
  • 외부 접근은 차단, 내부 통신만 허용 (외부 접속은 LB를 통해 접속)
  • 방화벽 정책은 tag 를 통해 인스턴스 별로 적용

Google Compute Engine 인스턴스 생성

# 백엔드 서버 인스턴스
resource "google_compute_instance" "vm_instance" {
  name         = "moa-be-ec2-${var.environment}"
  machine_type = "e2-medium"
  zone         = "asia-northeast3-c"
  tags         = ["ssh", "web", "api", "mysql", "http-server", "https-server"]

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2204-lts"
      size  = 60
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.subnet.id
    network_ip = "[내부 IP]"
  }
}

# AI 서버 인스턴스
resource "google_compute_instance" "ai_instance" {
  name         = "moa-ai-ec2-${var.environment}"
  machine_type = "n1-standard-4"
  zone         = "asia-northeast3-c"
  tags         = ["ssh", "web", "api", "mysql", "http-server", "https-server"]

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2204-lts"
      size  = 130
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.subnet.id
    network_ip = "[내부 IP]"
  }

  guest_accelerator {
    type  = "nvidia-tesla-t4"
    count = 1
  }

  # GPU 사용을 위해 필요 - 유지보수 시에 인스턴스 종료(정지)
  scheduling {
    on_host_maintenance = "TERMINATE"
  }
}

# DB 서버 인스턴스
resource "google_compute_instance" "db_instance" {
  name         = "moa-db-ec2-${var.environment}"
  machine_type = "e2-small"
  zone         = "asia-northeast3-c"
  tags         = ["mysql", "db"]

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2204-lts"
      size  = 30
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.subnet.id
    network_ip = "[내부 IP]"
  }
}

# Redis 서버 인스턴스
resource "google_compute_instance" "redis_instance" {
  name         = "moa-redis-ec2-${var.environment}"
  machine_type = "e2-small"
  zone         = "asia-northeast3-c"
  tags         = ["redis", "cache"]

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2204-lts"
      size  = 30
    }
  }

  network_interface {
    subnetwork = google_compute_subnetwork.subnet.id
    network_ip = "[내부 IP]"
  }
}

# 백엔드 인스턴스 그룹 생성
resource "google_compute_instance_group" "backend_group" {
  name        = "moa-be-ec2-group-${var.environment}"
  description = "Backend instance group"
  zone        = "asia-northeast3-c"
  
  instances = [
    google_compute_instance.vm_instance.id
  ]

  named_port {
    name = "http"
    port = 80
  }
  
  named_port {
    name = "api"
    port = 8080
  }
}
  • BE, AI, Redis, DB 총 4대의 인스턴스 생성
  • 외부 IP 없이 내부 IP만 부여
  • 각 인스턴스에 tag 작성하여 보안 규칙 적용 등 리소스 호출 시 사용
  • 외부에서 접속을 위한 bastion 서버 추후 생성

로드 밸런서 설정

## 프론트엔드 로드 밸런서 설정
# FE 로드 밸런서 고정 IP 주소
resource "google_compute_global_address" "frontend_lb_ip" {
  name = "moa-fe-lb-ip-${var.environment}"
}

# 정적 배포용 백엔드 버킷 설정
resource "google_compute_backend_bucket" "frontend_bucket" {
  name        = "moa-fe-frontend-backend-${var.environment}"
  bucket_name = google_storage_bucket.static_website.name
  enable_cdn  = true
}

# 로드밸런서 URL 맵 설정
resource "google_compute_url_map" "frontend_url_map" {
  name            = "moa-fe-url-map-${var.environment}"
  default_service = google_compute_backend_bucket.frontend_bucket.id
}

# 프론트엔드 HTTP proxy 
resource "google_compute_target_http_proxy" "frontend_http_proxy" {
  name    = "moa-fe-http-proxy-${var.environment}"
  url_map = google_compute_url_map.frontend_url_map.id
}

# 프론트엔드 HTTP 포워딩 규칙
resource "google_compute_global_forwarding_rule" "frontend_http_forwarding_rule" {
  name       = "moa-fe-http-rule-${var.environment}"
  target     = google_compute_target_http_proxy.frontend_http_proxy.id
  port_range = "80"
  ip_address = google_compute_global_address.frontend_lb_ip.address
}

# 프론트엔드 SSL 인증서
resource "google_compute_managed_ssl_certificate" "frontend_certificate" {
  name = "moa-fe-ssl-cert-${var.environment}"
  
  managed {
    domains = [var.frontend_domain]
  }
}

# 프론트엔드 HTTPS proxy
resource "google_compute_target_https_proxy" "frontend_https_proxy" {
  name             = "moa-fe-https-proxy-${var.environment}"
  url_map          = google_compute_url_map.frontend_url_map.id
  ssl_certificates = [google_compute_managed_ssl_certificate.frontend_certificate.id]
}

# 프론트엔드 HTTPS 포워딩 규칙
resource "google_compute_global_forwarding_rule" "frontend_https_forwarding_rule" {
  name       = "moa-fe-https-rule-${var.environment}"
  target     = google_compute_target_https_proxy.frontend_https_proxy.id
  port_range = "443"
  ip_address = google_compute_global_address.frontend_lb_ip.address
}




## 백엔드 로드 밸런서 설정
# 백엔드 고정 IP 주소
resource "google_compute_global_address" "backend_lb_ip" {
  name = "moa-be-lb-ip-${var.environment}"
}

# 백엔드 서비스 헬스체크
resource "google_compute_health_check" "backend_health_check" {
  name               = "moa-be-ec2-healthcheck-${var.environment}"
  timeout_sec        = 5
  check_interval_sec = 10

  http_health_check {
    port         = 8080
    request_path = "/health"
  }
}

# 백엔드 서비스 설정
resource "google_compute_backend_service" "backend_service" {
  name                  = "moa-be-ec2-service-${var.environment}"
  protocol              = "HTTP"
  port_name             = "api"
  timeout_sec           = 30
  health_checks         = [google_compute_health_check.backend_health_check.id]
  load_balancing_scheme = "EXTERNAL"

  backend {
    group = google_compute_instance_group.backend_group.id
  }
}

# 백엔드 URL 맵 설정
resource "google_compute_url_map" "backend_url_map" {
  name            = "moa-be-url-map-${var.environment}"
  default_service = google_compute_backend_service.backend_service.id
  
  host_rule {
    hosts        = [var.backend_domain]
    path_matcher = "backend-paths"
  }
  
  path_matcher {
    name            = "backend-paths"
    default_service = google_compute_backend_service.backend_service.id
    
    path_rule {
      paths   = ["/api/*", "/api/v1/*", "/*"]
      service = google_compute_backend_service.backend_service.id
    }
  }
}

# 백엔드 HTTP 프록시
resource "google_compute_target_http_proxy" "backend_http_proxy" {
  name    = "moa-be-http-proxy-${var.environment}"
  url_map = google_compute_url_map.backend_url_map.id
}

# 백엔드 HTTP 포워딩 규칙
resource "google_compute_global_forwarding_rule" "backend_http_forwarding_rule" {
  name       = "moa-be-http-rule-${var.environment}"
  target     = google_compute_target_http_proxy.backend_http_proxy.id
  port_range = "80"
  ip_address = google_compute_global_address.backend_lb_ip.address
}

# 백엔드 HTTPS SSL 인증서
resource "google_compute_managed_ssl_certificate" "backend_certificate" {
  name = "moa-be-ssl-cert-${var.environment}"
  managed {
    domains = [var.backend_domain]
  }
}

# 백엔드 HTTPS 프록시
resource "google_compute_target_https_proxy" "backend_https_proxy" {
  name             = "moa-be-https-proxy-${var.environment}"
  url_map          = google_compute_url_map.backend_url_map.id
  ssl_certificates = [google_compute_managed_ssl_certificate.backend_certificate.id]
}

# 백엔드 HTTPS 포워딩 규칙
resource "google_compute_global_forwarding_rule" "backend_https_forwarding_rule" {
  name       = "moa-be-https-rule-${var.environment}"
  target     = google_compute_target_https_proxy.backend_https_proxy.id
  port_range = "443"
  ip_address = google_compute_global_address.backend_lb_ip.address
}
  • LB 구성을 위해 HTTPS용 SSL 인증서 생성
  • 인증서 발급용 domaintfvars파일을 통해 변수 처리

variables.tf

variable "project_id" {
  description = "The GCP project ID"
  type        = string
}

variable "region" {
  description = "The GCP region for resources"
  type        = string
}

variable "zone" {
  description = "The GCP zone for zonal resources"
  type        = string
}

variable "environment" {
  description = "Environment name (dev, staging, prod)"
  type        = string
}

variable "frontend_domain" {
  description = "Domain name for the frontend SSL certificate"
  type        = string
}

variable "backend_domain" {
  description = "Domain name for the backend SSL certificate"
  type        = string
}

variable "credentials_file" {
  description = "Path to the GCP credentials JSON file"
  type        = string
  }
  • 변수는 .tfvars 파일을 통해 환경변수 주입, 이를 통해 다른 운영 환경에서 직접적 코드 수정 없이 동작 가능

outputs.tf

output "vpc_id" {
  description = "ID of the VPC"
  value       = google_compute_network.vpc.id
}

output "subnet_id" {
  description = "ID of the subnet"
  value       = google_compute_subnetwork.subnet.id
}

output "be_instance_name" {
  description = "Name of the VM instance"
  value       = google_compute_instance.vm_instance.name
}

output "instance_group_name" {
  description = "Name of the backend instance group"
  value       = google_compute_instance_group.backend_group.name
}

output "ai_instance_name" {
  description = "Name of the AI VM instance"
  value       = google_compute_instance.ai_instance.name
}

output "db_instance_name" {
  description = "Name of the database instance"
  value       = google_compute_instance.db_instance.name
}

output "redis_instance_name" {
  description = "Name of the Redis instance"
  value       = google_compute_instance.redis_instance.name
}

output "frontend_bucket_name" {
  description = "Name of the frontend static website bucket"
  value       = google_storage_bucket.static_website.name
}

output "frontend_bucket_url" {
  description = "URL of the frontend static website"
  value       = "https://storage.googleapis.com/${google_storage_bucket.static_website.name}"
}

output "frontend_load_balancer_ip" {
  description = "IP address of the frontend load balancer"
  value       = google_compute_global_address.frontend_lb_ip.address
}

output "backend_load_balancer_ip" {
  description = "IP address of the backend load balancer"
  value       = google_compute_global_address.backend_lb_ip.address
}

output "backend_service_name" {
  description = "Name of the backend service"
  value       = google_compute_backend_service.backend_service.name
}
  • 주요 리소스 명과 접속 경로를 outputs로 생성

terraform.tfvars

project_id      = "[Project ID]"
region          = "[Region]"
zone            = "[Zone]"
environment     = "[environment]"
frontend_domain = "[frontend domain]"
backend_domain  = "[backend domain]"
credentials_file = "[Credential Key Path]"
  • 환경에 맞는 변수 주입 후 적용

Terraform 실행 과정

# Terraform 프로젝트 폴더로 이동 **(GCP Credential 을 위한 Key 파일 경로 확인 필수)**

cd [Terraform Project]

# 초기 실행
terraform init

# 리소스 생성 계획 점검
terraform plan -var-file=terraform.tfvars

# 리소스 적용
terraform apply -var-file=terraform.tfvars

# 리소스 삭제
terraform destroy

시연 화면 (Environment = test로 적용)

terraform planterraform apply

실제 GCP 콘솔 상 생성 화면

Instance 생성

Load Balancer 생성

CDN 생성


terraform destroy 실행