diff --git a/.gitignore b/.gitignore index 21ca485b..61319394 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,10 @@ src/main/resources/application-dev.yml .env -application-dev.yml \ No newline at end of file +application-dev.yml + +### Terraform ### +terraform/.terraform/ +terraform/terraform.tfstate +terraform/terraform.tfstate.backup +terraform/terraform.tfvars \ No newline at end of file diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 00000000..f5fc3276 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,45 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/cloudflare/cloudflare" { + version = "4.52.5" + constraints = "~> 4.0" + hashes = [ + "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", + "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", + "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", + "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", + "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", + "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", + "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", + "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", + "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", + "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", + "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", + "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", + "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", + "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", + ] +} + +provider "registry.terraform.io/hashicorp/google" { + version = "5.45.2" + constraints = "~> 5.0" + hashes = [ + "h1:k8taQAdfHrv2F/AiGV5BZBZfI+1uaq8g6O8dWzjx42c=", + "zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a", + "zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804", + "zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e", + "zh:3e555cf22a570a4bd247964671f421ed7517970cd9765ceb46f335edc2c6f392", + "zh:688bd5b05a75124da7ae6e885b2b92bd29f4261808b2b78bd5f51f525c1052ca", + "zh:6db3ef37a05010d82900bfffb3261c59a0c247e0692049cb3eb8c2ef16c9d7bf", + "zh:70316fde75f6a15d72749f66d994ccbdde5f5ed4311b6d06b99850f698c9bbf9", + "zh:84b8e583771a4f2bd514e519d98ed7fd28dce5efe0634e973170e1cfb5556fb4", + "zh:9d4b8ef0a9b6677935c604d94495042e68ff5489932cfd1ec41052e094a279d3", + "zh:a2089dd9bd825c107b148dd12d6b286f71aa37dfd4ca9c35157f2dcba7bc19d8", + "zh:f03d795c0fd9721e59839255ee7ba7414173017dc530b4ce566daf3802a0d6dd", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} diff --git a/terraform/compute.tf b/terraform/compute.tf new file mode 100644 index 00000000..7cace9f5 --- /dev/null +++ b/terraform/compute.tf @@ -0,0 +1,93 @@ +resource "google_compute_address" "prod" { + name = "cockple-prod-ip" + region = "asia-northeast3" +} + +resource "google_compute_address" "staging" { + name = "cockple-staging-ip" + region = "us-central1" +} + +resource "google_compute_instance" "prod" { + name = "cockple-prod" + machine_type = "e2-medium" # 4GB RAM + zone = "asia-northeast3-b" + tags = ["cockple-prod"] + allow_stopping_for_update = true + + boot_disk { + initialize_params { + image = "ubuntu-os-cloud/ubuntu-2204-lts" + size = 20 + } + } + + network_interface { + subnetwork = google_compute_subnetwork.prod.id + access_config { + nat_ip = google_compute_address.prod.address + } + } + + metadata = { + ssh-keys = "ubuntu:${var.ssh_public_key}" + } + + service_account { + email = google_service_account.cockple_app.email + scopes = ["cloud-platform"] # GCS 등 GCP 서비스 접근 + } + + metadata_startup_script = <<-EOF + #!/bin/bash + apt-get update -y + apt-get install -y docker.io + curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + systemctl enable docker + systemctl start docker + usermod -aG docker ubuntu + EOF +} + +resource "google_compute_instance" "staging" { + name = "cockple-staging" + machine_type = "e2-micro" # 1GB RAM, 무료 티어 + zone = "us-central1-a" + tags = ["cockple-staging"] + allow_stopping_for_update = true + + boot_disk { + initialize_params { + image = "ubuntu-os-cloud/ubuntu-2204-lts" + size = 30 # 무료 티어 최대 + } + } + + network_interface { + subnetwork = google_compute_subnetwork.staging.id + access_config { + nat_ip = google_compute_address.staging.address + } + } + + metadata = { + ssh-keys = "ubuntu:${var.ssh_public_key}" + } + + service_account { + email = google_service_account.cockple_app.email + scopes = ["cloud-platform"] + } + + metadata_startup_script = <<-EOF + #!/bin/bash + apt-get update -y + apt-get install -y docker.io + curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + systemctl enable docker + systemctl start docker + usermod -aG docker ubuntu + EOF +} diff --git a/terraform/dns.tf b/terraform/dns.tf new file mode 100644 index 00000000..95d6a4f7 --- /dev/null +++ b/terraform/dns.tf @@ -0,0 +1,31 @@ +resource "cloudflare_record" "prod" { + zone_id = var.cloudflare_zone_id + name = "@" + content = google_compute_address.prod.address + type = "A" + proxied = true +} + +resource "cloudflare_record" "prod_ssh" { + zone_id = var.cloudflare_zone_id + name = "ssh" + content = google_compute_address.prod.address + type = "A" + proxied = false +} + +resource "cloudflare_record" "staging" { + zone_id = var.cloudflare_zone_id + name = "staging" + content = google_compute_address.staging.address + type = "A" + proxied = true +} + +resource "cloudflare_record" "staging_ssh" { + zone_id = var.cloudflare_zone_id + name = "ssh-staging" + content = google_compute_address.staging.address + type = "A" + proxied = false +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 00000000..9fa928de --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,23 @@ +terraform { + required_version = ">= 1.6.0" + + required_providers { + google = { + source = "hashicorp/google" + version = "~> 5.0" + } + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4.0" + } + } +} + +provider "google" { + project = var.gcp_project_id + region = var.gcp_region +} + +provider "cloudflare" { + api_token = var.cloudflare_api_token +} \ No newline at end of file diff --git a/terraform/network.tf b/terraform/network.tf new file mode 100644 index 00000000..d65214f4 --- /dev/null +++ b/terraform/network.tf @@ -0,0 +1,62 @@ +resource "google_compute_network" "cockple_vpc" { + name = "cockple-vpc" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "prod" { + name = "cockple-subnet-prod" + ip_cidr_range = "10.0.1.0/24" + region = "asia-northeast3" + network = google_compute_network.cockple_vpc.id +} + +resource "google_compute_subnetwork" "staging" { + name = "cockple-subnet-staging" + ip_cidr_range = "10.0.2.0/24" + region = "us-central1" + network = google_compute_network.cockple_vpc.id +} + +# Cloudflare IP 대역에서만 80 포트 허용 (origin IP 보호) +resource "google_compute_firewall" "allow_http_cloudflare" { + name = "cockple-allow-http-cloudflare" + network = google_compute_network.cockple_vpc.name + + allow { + protocol = "tcp" + ports = ["80"] + } + + source_ranges = [ + "173.245.48.0/20", + "103.21.244.0/22", + "103.22.200.0/22", + "103.31.4.0/22", + "141.101.64.0/18", + "108.162.192.0/18", + "190.93.240.0/20", + "188.114.96.0/20", + "197.234.240.0/22", + "198.41.128.0/17", + "162.158.0.0/15", + "104.16.0.0/13", + "104.24.0.0/14", + "172.64.0.0/13", + "131.0.72.0/22", + ] + + target_tags = ["cockple-prod", "cockple-staging"] +} + +resource "google_compute_firewall" "allow_ssh" { + name = "cockple-allow-ssh" + network = google_compute_network.cockple_vpc.name + + allow { + protocol = "tcp" + ports = ["22"] + } + + source_ranges = ["0.0.0.0/0"] + target_tags = ["cockple-prod", "cockple-staging"] +} \ No newline at end of file diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 00000000..aeb7a1a9 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,19 @@ +output "prod_ip" { + description = "Prod 서버 공인 IP" + value = google_compute_address.prod.address +} + +output "staging_ip" { + description = "Staging 서버 공인 IP" + value = google_compute_address.staging.address +} + +output "gcs_bucket_name" { + description = "GCS 버킷 이름" + value = google_storage_bucket.cockple_assets.name +} + +output "app_service_account_email" { + description = "앱 서비스 계정 이메일 (GCS 인증에 사용)" + value = google_service_account.cockple_app.email +} \ No newline at end of file diff --git a/terraform/storage.tf b/terraform/storage.tf new file mode 100644 index 00000000..63e9e4a4 --- /dev/null +++ b/terraform/storage.tf @@ -0,0 +1,32 @@ +resource "google_project_service" "storage" { + service = "storage.googleapis.com" + disable_on_destroy = false +} + +# 앱 인스턴스용 서비스 계정 (GCS 접근) +resource "google_service_account" "cockple_app" { + account_id = "cockple-app" + display_name = "Cockple App Service Account" +} + +resource "google_storage_bucket" "cockple_assets" { + name = "cockple-assets-${var.gcp_project_id}" + location = "ASIA-NORTHEAST3" + + uniform_bucket_level_access = true + + cors { + origin = ["https://cockple.shop", "https://staging.cockple.shop"] + method = ["GET", "PUT", "POST", "DELETE"] + response_header = ["Content-Type"] + max_age_seconds = 3600 + } + + depends_on = [google_project_service.storage] +} + +resource "google_storage_bucket_iam_member" "app_storage_admin" { + bucket = google_storage_bucket.cockple_assets.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_service_account.cockple_app.email}" +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 00000000..2f96e69b --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,26 @@ +variable "gcp_project_id" { + description = "GCP 프로젝트 ID" + type = string +} + +variable "gcp_region" { + description = "GCP 기본 리전 (prod)" + type = string + default = "asia-northeast3" +} + +variable "cloudflare_api_token" { + description = "Cloudflare API 토큰" + type = string + sensitive = true +} + +variable "cloudflare_zone_id" { + description = "cockple.shop Cloudflare Zone ID" + type = string +} + +variable "ssh_public_key" { + description = "인스턴스 접속용 SSH 공개키" + type = string +}