From c4f14793f61f6883229008b532d70f626f08cfa7 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Wed, 31 Dec 2025 20:41:21 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20AWS=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=85=8C=EB=9D=BC=ED=8F=BC=20=EC=BD=94=EB=93=9C=20aws=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=95=84=EB=9E=98=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/{ => aws}/.terraform.lock.hcl | 0 infra/{ => aws}/cloudflare.tf | 0 infra/{ => aws}/main.tf | 0 infra/{ => aws}/outputs.tf | 0 infra/{ => aws}/provider.tf | 0 infra/{ => aws}/variables.tf | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename infra/{ => aws}/.terraform.lock.hcl (100%) rename infra/{ => aws}/cloudflare.tf (100%) rename infra/{ => aws}/main.tf (100%) rename infra/{ => aws}/outputs.tf (100%) rename infra/{ => aws}/provider.tf (100%) rename infra/{ => aws}/variables.tf (100%) diff --git a/infra/.terraform.lock.hcl b/infra/aws/.terraform.lock.hcl similarity index 100% rename from infra/.terraform.lock.hcl rename to infra/aws/.terraform.lock.hcl diff --git a/infra/cloudflare.tf b/infra/aws/cloudflare.tf similarity index 100% rename from infra/cloudflare.tf rename to infra/aws/cloudflare.tf diff --git a/infra/main.tf b/infra/aws/main.tf similarity index 100% rename from infra/main.tf rename to infra/aws/main.tf diff --git a/infra/outputs.tf b/infra/aws/outputs.tf similarity index 100% rename from infra/outputs.tf rename to infra/aws/outputs.tf diff --git a/infra/provider.tf b/infra/aws/provider.tf similarity index 100% rename from infra/provider.tf rename to infra/aws/provider.tf diff --git a/infra/variables.tf b/infra/aws/variables.tf similarity index 100% rename from infra/variables.tf rename to infra/aws/variables.tf From 6eab2f86e1f533aceae144d91518af014b57fa69 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Thu, 1 Jan 2026 11:21:39 +0900 Subject: [PATCH 2/5] =?UTF-8?q?deploy:=20=EC=98=A4=EB=9D=BC=ED=81=B4=20?= =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9A=B0=EB=93=9C=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/oracle/.terraform.lock.hcl | 48 +++++ infra/oracle/cloud-init.sh | 318 +++++++++++++++++++++++++++++++ infra/oracle/cloudflare.tf | 39 ++++ infra/oracle/main.tf | 211 ++++++++++++++++++++ infra/oracle/outputs.tf | 89 +++++++++ infra/oracle/provider.tf | 28 +++ infra/oracle/variables.tf | 145 ++++++++++++++ 7 files changed, 878 insertions(+) create mode 100644 infra/oracle/.terraform.lock.hcl create mode 100644 infra/oracle/cloud-init.sh create mode 100644 infra/oracle/cloudflare.tf create mode 100644 infra/oracle/main.tf create mode 100644 infra/oracle/outputs.tf create mode 100644 infra/oracle/provider.tf create mode 100644 infra/oracle/variables.tf diff --git a/infra/oracle/.terraform.lock.hcl b/infra/oracle/.terraform.lock.hcl new file mode 100644 index 0000000..d4110f9 --- /dev/null +++ b/infra/oracle/.terraform.lock.hcl @@ -0,0 +1,48 @@ +# 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/oracle/oci" { + version = "5.47.0" + constraints = "~> 5.0" + hashes = [ + "h1:XuD7rQMenwvCdmbRA20FcROuRux+5c3s6+eX3FX8ZsA=", + "zh:1989a7d68753329c25e1175cdae0ecfb917e86a1b7687366888a69727cfdf383", + "zh:4b497dd634896c78f05c6000f51cc295adffa4d77edaf4e8cec889ee24751754", + "zh:4f50ed77d982eac118bf1e00b5cf90f12eb09acb1aa3be5b3f71601935093675", + "zh:50dc6e13843ab8b13439cb9029d9893f55debeb20541a63aef62e20bb85ece91", + "zh:6332a8ec1a5e80030b80dd0d37f20e6755bd1225a308fe512c083e65ca66ce83", + "zh:83bbb9f06ff4d7a09a13dcb145db9b8298b5c679ea681a753d6fbaf2fdc40d20", + "zh:9173587be16322072947be951834ce4bd7be1fd0f846b527ff478b6dd4e8a91b", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a4331ca1d66848414857b4c5c0f9b2fe49aa4f72e283fb1df6edefbac1f76474", + "zh:a7d7bd1bd421ee94d72772f0c71f012ce538cbe48f8a646baf00c07755738122", + "zh:d0738787cae2842d6f13ac1d5d9cea6d5ec6acfe78ca25f1f992c1f2ed55eb5f", + "zh:d6e47352cc460bab26a04b45bf00f9ffd3fb01bdc254df2209e3af8c640e28d2", + "zh:e12ec25cba44489dac966ecb5ea89b63e88aefad6c0d28b54bde3af79f1389d7", + "zh:fe6aa72e82f9912b6c9893025bd58d967fad0f5864ea1151acc2d804ddbf7d70", + "zh:ff703d90bb3afab257c660732c23269cc7f58167fe85d163b3fa29cd70010d3a", + ] +} diff --git a/infra/oracle/cloud-init.sh b/infra/oracle/cloud-init.sh new file mode 100644 index 0000000..ec1c6ab --- /dev/null +++ b/infra/oracle/cloud-init.sh @@ -0,0 +1,318 @@ +#!/bin/bash +set -e + +# 로그 설정 +exec > >(tee /var/log/cloud-init-custom.log) 2>&1 +echo "===== Cloud Init Started: $(date) =====" + +# =========================================== +# 시스템 업데이트 +# =========================================== +echo "===== System Update =====" +apt-get update -y +apt-get upgrade -y + +# =========================================== +# Swap 메모리 설정 (4GB - ES 안정성을 위해) +# =========================================== +echo "===== Swap Memory Setup (4GB) =====" +if [ ! -f /swapfile ]; then + fallocate -l 4G /swapfile + chmod 600 /swapfile + mkswap /swapfile + swapon /swapfile + echo '/swapfile none swap sw 0 0' >> /etc/fstab + + # Swappiness 설정 (ES 권장: 낮은 값) + echo 'vm.swappiness=1' >> /etc/sysctl.conf + sysctl -p + + echo "Swap setup complete!" + free -h +fi + +# =========================================== +# Docker 설치 (ARM64) +# =========================================== +echo "===== Install Docker =====" +apt-get install -y ca-certificates curl gnupg lsb-release + +# Docker GPG 키 추가 +install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg +chmod a+r /etc/apt/keyrings/docker.gpg + +# Docker 저장소 추가 +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null + +apt-get update -y +apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +# Docker 서비스 시작 +systemctl start docker +systemctl enable docker + +# ubuntu 사용자를 docker 그룹에 추가 +usermod -aG docker ubuntu + +echo "Docker version: $(docker --version)" + +# =========================================== +# Nginx 설치 +# =========================================== +echo "===== Install Nginx =====" +apt-get install -y nginx + +# =========================================== +# 애플리케이션 디렉토리 생성 +# =========================================== +echo "===== Create Application Directories =====" +mkdir -p /opt/tech-fork +mkdir -p /var/log/tech-fork +chown -R ubuntu:ubuntu /opt/tech-fork +chown -R ubuntu:ubuntu /var/log/tech-fork + +# =========================================== +# 시스템 튜닝 (Elasticsearch 권장 설정) +# =========================================== +echo "===== System Tuning for Elasticsearch =====" + +# vm.max_map_count (ES 필수) +echo 'vm.max_map_count=262144' >> /etc/sysctl.conf +sysctl -w vm.max_map_count=262144 + +# 파일 디스크립터 제한 증가 +cat >> /etc/security/limits.conf < /etc/nginx/sites-available/tech-fork <<'NGINX' +upstream springapp { + server 127.0.0.1:8080 fail_timeout=0; +} + +server { + listen 80; + server_name ${domain_name} www.${domain_name} api.${domain_name}; + + client_max_body_size 10M; + + access_log /var/log/nginx/tech-fork-access.log; + error_log /var/log/nginx/tech-fork-error.log; + + # Cloudflare Real IP 설정 + set_real_ip_from 173.245.48.0/20; + set_real_ip_from 103.21.244.0/22; + set_real_ip_from 103.22.200.0/22; + set_real_ip_from 103.31.4.0/22; + set_real_ip_from 141.101.64.0/18; + set_real_ip_from 108.162.192.0/18; + set_real_ip_from 190.93.240.0/20; + set_real_ip_from 188.114.96.0/20; + set_real_ip_from 197.234.240.0/22; + set_real_ip_from 198.41.128.0/17; + set_real_ip_from 162.158.0.0/15; + set_real_ip_from 104.16.0.0/13; + set_real_ip_from 104.24.0.0/14; + set_real_ip_from 172.64.0.0/13; + set_real_ip_from 131.0.72.0/22; + real_ip_header CF-Connecting-IP; + + location / { + proxy_pass http://springapp; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 타임아웃 설정 (검색 응답 시간 고려) + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Health Check 엔드포인트 + location /health { + proxy_pass http://springapp/actuator/health; + proxy_set_header Host $host; + } +} +NGINX + +# 사이트 활성화 +ln -sf /etc/nginx/sites-available/tech-fork /etc/nginx/sites-enabled/ +rm -f /etc/nginx/sites-enabled/default + +# Nginx 테스트 및 재시작 +nginx -t +systemctl restart nginx +systemctl enable nginx + +# =========================================== +# Docker Compose 파일 생성 (Oracle 최적화) +# =========================================== +echo "===== Create Docker Compose File =====" +cat > /opt/tech-fork/docker-compose.yml <<'COMPOSE' +version: '3.8' + +services: + app: + image: $${DOCKER_IMAGE}:$${BRANCH} + container_name: tech-fork-app + restart: always + ports: + - "8080:8080" + environment: + - SPRING_PROFILES_ACTIVE=$${SPRING_PROFILES_ACTIVE} + - DB_URL=$${DB_URL} + - DB_USERNAME=$${DB_USERNAME} + - DB_PASSWORD=$${DB_PASSWORD} + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_PASSWORD=$${REDIS_PASSWORD} + - ELASTICSEARCH_HOST=elasticsearch + - ELASTICSEARCH_PORT=9200 + - ANTHROPIC_API_KEY=$${ANTHROPIC_API_KEY} + - OPENAI_API_KEY=$${OPENAI_API_KEY} + networks: + - app-network + depends_on: + mysql: + condition: service_healthy + redis: + condition: service_started + elasticsearch: + condition: service_healthy + + mysql: + image: mysql:8.0 + container_name: tech-fork-mysql + restart: always + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD=$${DB_PASSWORD} + - MYSQL_DATABASE=techblog + - MYSQL_USER=techfork + - MYSQL_PASSWORD=$${DB_PASSWORD} + - TZ=Asia/Seoul + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --innodb-buffer-pool-size=2G + volumes: + - mysql-data:/var/lib/mysql + networks: + - app-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + container_name: tech-fork-redis + restart: always + ports: + - "6379:6379" + command: > + redis-server + --requirepass $${REDIS_PASSWORD} + --maxmemory 1gb + --maxmemory-policy allkeys-lru + volumes: + - redis-data:/data + networks: + - app-network + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:8.18.0 + container_name: tech-fork-elasticsearch + restart: always + ports: + - "9200:9200" + - "9300:9300" + environment: + - discovery.type=single-node + - xpack.security.enabled=false + - "ES_JAVA_OPTS=-Xms8g -Xmx8g" + - cluster.routing.allocation.disk.threshold_enabled=true + - cluster.routing.allocation.disk.watermark.low=85% + - cluster.routing.allocation.disk.watermark.high=90% + volumes: + - elasticsearch-data:/usr/share/elasticsearch/data + networks: + - app-network + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"] + interval: 10s + timeout: 5s + retries: 30 + start_period: 120s + +networks: + app-network: + driver: bridge + +volumes: + mysql-data: + redis-data: + elasticsearch-data: +COMPOSE + +chown -R ubuntu:ubuntu /opt/tech-fork + +# =========================================== +# 환경 변수 템플릿 생성 +# =========================================== +echo "===== Create Environment Template =====" +cat > /opt/tech-fork/.env.template <" +echo "2. Copy .env.template to .env and fill in the values" +echo "3. Run: cd /opt/tech-fork && docker compose up -d" diff --git a/infra/oracle/cloudflare.tf b/infra/oracle/cloudflare.tf new file mode 100644 index 0000000..f0546de --- /dev/null +++ b/infra/oracle/cloudflare.tf @@ -0,0 +1,39 @@ +# =========================================== +# Cloudflare DNS +# =========================================== + +# resource "cloudflare_record" "main" { +# zone_id = var.cloudflare_zone_id +# name = "@" +# content = oci_core_instance.app.public_ip +# type = "A" +# ttl = 1 # Auto +# proxied = true +# } +# +# resource "cloudflare_record" "www" { +# zone_id = var.cloudflare_zone_id +# name = "www" +# content = var.domain_name +# type = "CNAME" +# ttl = 1 +# proxied = true +# } +# +# resource "cloudflare_record" "api" { +# zone_id = var.cloudflare_zone_id +# name = "api" +# content = oci_core_instance.app.public_ip +# type = "A" +# ttl = 1 +# proxied = true +# } +# +# resource "cloudflare_record" "ssh" { +# zone_id = var.cloudflare_zone_id +# name = "ssh" +# content = oci_core_instance.app.public_ip +# type = "A" +# ttl = 300 +# proxied = false # SSH 직접 연결! +# } diff --git a/infra/oracle/main.tf b/infra/oracle/main.tf new file mode 100644 index 0000000..88bf784 --- /dev/null +++ b/infra/oracle/main.tf @@ -0,0 +1,211 @@ +# =========================================== +# Data Sources +# =========================================== + +# 가용 도메인 조회 +data "oci_identity_availability_domains" "ads" { + compartment_id = var.tenancy_ocid +} + +# Ubuntu 22.04 ARM 이미지 조회 (Always Free용) +data "oci_core_images" "ubuntu" { + compartment_id = var.compartment_ocid + operating_system = "Canonical Ubuntu" + operating_system_version = "22.04" + shape = var.instance_shape + sort_by = "TIMECREATED" + sort_order = "DESC" + + filter { + name = "display_name" + values = ["^Canonical-Ubuntu-22.04-aarch64-.*"] + regex = true + } +} + +# =========================================== +# Networking - VCN +# =========================================== + +resource "oci_core_vcn" "main" { + compartment_id = var.compartment_ocid + cidr_blocks = [var.vcn_cidr] + display_name = "${var.project_name}-${var.environment}-vcn" + dns_label = "techfork" + + freeform_tags = { + Project = var.project_name + Environment = var.environment + } +} + +# Internet Gateway +resource "oci_core_internet_gateway" "main" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.main.id + display_name = "${var.project_name}-${var.environment}-igw" + enabled = true + + freeform_tags = { + Project = var.project_name + Environment = var.environment + } +} + +# Route Table +resource "oci_core_route_table" "public" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.main.id + display_name = "${var.project_name}-${var.environment}-public-rt" + + route_rules { + destination = "0.0.0.0/0" + destination_type = "CIDR_BLOCK" + network_entity_id = oci_core_internet_gateway.main.id + } + + freeform_tags = { + Project = var.project_name + Environment = var.environment + } +} + +# Security List +resource "oci_core_security_list" "public" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.main.id + display_name = "${var.project_name}-${var.environment}-public-sl" + + # Egress: 모든 트래픽 허용 + egress_security_rules { + destination = "0.0.0.0/0" + protocol = "all" + stateless = false + } + + # Ingress: SSH (22) + ingress_security_rules { + protocol = "6" # TCP + source = "0.0.0.0/0" + stateless = false + description = "SSH" + + tcp_options { + min = 22 + max = 22 + } + } + + # Ingress: HTTP (80) + ingress_security_rules { + protocol = "6" + source = "0.0.0.0/0" + stateless = false + description = "HTTP" + + tcp_options { + min = 80 + max = 80 + } + } + + # Ingress: HTTPS (443) + ingress_security_rules { + protocol = "6" + source = "0.0.0.0/0" + stateless = false + description = "HTTPS" + + tcp_options { + min = 443 + max = 443 + } + } + + # Ingress: Spring Boot (8080) - Cloudflare Only 권장 + ingress_security_rules { + protocol = "6" + source = "0.0.0.0/0" + stateless = false + description = "Spring Boot" + + tcp_options { + min = 8080 + max = 8080 + } + } + + freeform_tags = { + Project = var.project_name + Environment = var.environment + } +} + +# Public Subnet +resource "oci_core_subnet" "public" { + compartment_id = var.compartment_ocid + vcn_id = oci_core_vcn.main.id + cidr_block = var.public_subnet_cidr + display_name = "${var.project_name}-${var.environment}-public-subnet" + dns_label = "public" + route_table_id = oci_core_route_table.public.id + security_list_ids = [oci_core_security_list.public.id] + prohibit_public_ip_on_vnic = false + + freeform_tags = { + Project = var.project_name + Environment = var.environment + } +} + +# =========================================== +# Compute Instance (Always Free ARM) +# =========================================== + +resource "oci_core_instance" "app" { + compartment_id = var.compartment_ocid + availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name + display_name = "${var.project_name}-${var.environment}-app" + shape = var.instance_shape + + # ARM Flex Shape 설정 + shape_config { + ocpus = var.instance_ocpus + memory_in_gbs = var.instance_memory_gb + } + + source_details { + source_type = "image" + source_id = data.oci_core_images.ubuntu.images[0].id + boot_volume_size_in_gbs = var.boot_volume_size_gb + } + + create_vnic_details { + subnet_id = oci_core_subnet.public.id + display_name = "${var.project_name}-${var.environment}-vnic" + assign_public_ip = true + hostname_label = "techfork" + } + + metadata = { + ssh_authorized_keys = var.ssh_public_key + user_data = base64encode(templatefile("${path.module}/cloud-init.sh", { + db_password = var.db_password + redis_password = var.redis_password + domain_name = var.domain_name + })) + } + + freeform_tags = { + Project = var.project_name + Environment = var.environment + } + + # Always Free 인스턴스는 생성 경쟁이 치열함 + # 실패 시 재시도 필요 + lifecycle { + ignore_changes = [ + source_details[0].source_id # 이미지 업데이트 무시 + ] + } +} \ No newline at end of file diff --git a/infra/oracle/outputs.tf b/infra/oracle/outputs.tf new file mode 100644 index 0000000..a166e1b --- /dev/null +++ b/infra/oracle/outputs.tf @@ -0,0 +1,89 @@ +# =========================================== +# Networking Outputs +# =========================================== + +output "vcn_id" { + description = "VCN OCID" + value = oci_core_vcn.main.id +} + +output "public_subnet_id" { + description = "Public Subnet OCID" + value = oci_core_subnet.public.id +} + +# =========================================== +# Compute Outputs +# =========================================== + +output "instance_id" { + description = "Compute Instance OCID" + value = oci_core_instance.app.id +} + +output "instance_public_ip" { + description = "인스턴스 Public IP (SSH 접속용)" + value = oci_core_instance.app.public_ip +} + +output "instance_private_ip" { + description = "인스턴스 Private IP" + value = oci_core_instance.app.private_ip +} + +output "instance_shape" { + description = "인스턴스 Shape 정보" + value = "${var.instance_shape} (${var.instance_ocpus} OCPU, ${var.instance_memory_gb}GB RAM)" +} + +# =========================================== +# Connection Info +# =========================================== + +output "ssh_command" { + description = "SSH 접속 명령어" + value = "ssh ubuntu@${oci_core_instance.app.public_ip}" +} + +output "application_url" { + description = "애플리케이션 URL" + value = "https://${var.domain_name}" +} + +output "api_url" { + description = "API URL" + value = "https://api.${var.domain_name}" +} + +# =========================================== +# Resource Summary +# =========================================== + +output "resource_summary" { + description = "생성된 리소스 요약" + value = <<-EOT + + ╔══════════════════════════════════════════════════════════════╗ + ║ TechFork Oracle Cloud 배포 완료 ║ + ╠══════════════════════════════════════════════════════════════╣ + ║ ║ + ║ 🖥️ Instance: ${var.instance_shape} + ║ - OCPU: ${var.instance_ocpus} + ║ - Memory: ${var.instance_memory_gb}GB + ║ - Boot Volume: ${var.boot_volume_size_gb}GB + ║ ║ + ║ 🌐 Public IP: ${oci_core_instance.app.public_ip} + ║ ║ + ║ 🔗 URLs: ║ + ║ - Web: https://${var.domain_name} + ║ - API: https://api.${var.domain_name} + ║ ║ + ║ 📋 다음 단계: ║ + ║ 1. SSH 접속: ssh ubuntu@${oci_core_instance.app.public_ip} + ║ 2. 환경 설정: cd /opt/tech-fork ║ + ║ 3. .env 파일 생성 및 설정 ║ + ║ 4. docker compose up -d ║ + ║ ║ + ╚══════════════════════════════════════════════════════════════╝ + EOT +} diff --git a/infra/oracle/provider.tf b/infra/oracle/provider.tf new file mode 100644 index 0000000..5a9b298 --- /dev/null +++ b/infra/oracle/provider.tf @@ -0,0 +1,28 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + oci = { + source = "oracle/oci" + version = "~> 5.0" + } + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4.0" + } + } +} + +# OCI Provider 설정 +# 인증 방식: API Key (권장) 또는 Instance Principal +provider "oci" { + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + region = var.region +} + +provider "cloudflare" { + api_token = var.cloudflare_api_token +} diff --git a/infra/oracle/variables.tf b/infra/oracle/variables.tf new file mode 100644 index 0000000..75995cb --- /dev/null +++ b/infra/oracle/variables.tf @@ -0,0 +1,145 @@ +# =========================================== +# OCI 인증 관련 변수 +# =========================================== +variable "tenancy_ocid" { + description = "OCI Tenancy OCID" + type = string + sensitive = true +} + +variable "user_ocid" { + description = "OCI User OCID" + type = string + sensitive = true +} + +variable "fingerprint" { + description = "OCI API Key Fingerprint" + type = string + sensitive = true +} + +variable "private_key_path" { + description = "OCI API Private Key 경로" + type = string + default = "~/.oci/oci_api_key.pem" +} + +variable "region" { + description = "OCI 리전 (서울: ap-seoul-1, 춘천: ap-chuncheon-1)" + type = string + default = "ap-chuncheon-1" # 춘천 리전 (Always Free 가용) +} + +variable "compartment_ocid" { + description = "OCI Compartment OCID (리소스를 생성할 구획)" + type = string +} + +# =========================================== +# 프로젝트 설정 +# =========================================== +variable "project_name" { + description = "프로젝트 이름" + type = string + default = "tech-fork" +} + +variable "environment" { + description = "환경 (dev, prod)" + type = string + default = "prod" +} + +# =========================================== +# 컴퓨트 인스턴스 설정 +# =========================================== +variable "instance_shape" { + description = "인스턴스 Shape (Always Free: VM.Standard.A1.Flex)" + type = string + default = "VM.Standard.A1.Flex" # ARM 기반 Ampere +} + +variable "instance_ocpus" { + description = "OCPU 수 (Always Free 최대: 4)" + type = number + default = 4 +} + +variable "instance_memory_gb" { + description = "메모리 GB (Always Free 최대: 24)" + type = number + default = 24 +} + +variable "boot_volume_size_gb" { + description = "부트 볼륨 크기 GB (Always Free 최대: 200)" + type = number + default = 100 +} + +# =========================================== +# 네트워크 설정 +# =========================================== +variable "vcn_cidr" { + description = "VCN CIDR 블록" + type = string + default = "10.0.0.0/16" +} + +variable "public_subnet_cidr" { + description = "Public Subnet CIDR" + type = string + default = "10.0.1.0/24" +} + +# =========================================== +# SSH 설정 +# =========================================== +variable "ssh_public_key" { + description = "SSH Public Key (인스턴스 접속용)" + type = string +} + +# =========================================== +# 애플리케이션 설정 +# =========================================== +variable "domain_name" { + description = "도메인 이름" + type = string +} + +variable "db_password" { + description = "MySQL Root 비밀번호" + type = string + sensitive = true +} + +variable "redis_password" { + description = "Redis 비밀번호" + type = string + sensitive = true +} + +# =========================================== +# Cloudflare 설정 +# =========================================== +variable "cloudflare_api_token" { + description = "Cloudflare API Token" + type = string + sensitive = true +} + +variable "cloudflare_zone_id" { + description = "Cloudflare Zone ID" + type = string +} + +# =========================================== +# Docker 설정 +# =========================================== +variable "docker_image" { + description = "Docker 이미지 이름" + type = string + default = "ghcr.io/your-org/tech-fork" +} From 0a04c592f3aed6d5e42501ff912eb6fbdeede58e Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Thu, 1 Jan 2026 11:29:21 +0900 Subject: [PATCH 3/5] =?UTF-8?q?deploy:=20arm=20=ED=99=98=EA=B2=BD=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=ED=95=A8=EC=97=90=20=EB=94=B0=EB=9D=BC=20?= =?UTF-8?q?cd=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ebc5541..5ed4256 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -26,6 +26,12 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to DockerHub uses: docker/login-action@v3 with: @@ -36,7 +42,7 @@ jobs: uses: docker/build-push-action@v5 with: context: . - platforms: linux/amd64 + platforms: linux/arm64 push: true tags: | ${{ env.DOCKER_IMAGE }}:${{ github.event.workflow_run.head_branch }} From 2ff651650b1d14a81b830f229601d0c7e51e474f Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Thu, 1 Jan 2026 11:29:39 +0900 Subject: [PATCH 4/5] =?UTF-8?q?deploy:=20RDS=EB=8C=80=EC=8B=A0=20docker=20?= =?UTF-8?q?MySQL=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 7029be1..61d91cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,11 +18,39 @@ services: networks: - app-network depends_on: + mysql: + condition: service_healthy redis: condition: service_started elasticsearch: condition: service_healthy + mysql: + image: mysql:8.0 + container_name: tech-fork-mysql + restart: always + ports: + - "3306:3306" + environment: + - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} + - MYSQL_DATABASE=techblog + - MYSQL_USER=${DB_USERNAME} + - MYSQL_PASSWORD=${DB_PASSWORD} + - TZ=Asia/Seoul + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --innodb-buffer-pool-size=2G + volumes: + - mysql-data:/var/lib/mysql + networks: + - app-network + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] + interval: 10s + timeout: 5s + retries: 5 + redis: image: redis:7-alpine container_name: tech-fork-redis From 4644dfdb4d486c671910e7e45d3a3eb6c4321538 Mon Sep 17 00:00:00 2001 From: Dimo-2562 Date: Thu, 1 Jan 2026 11:32:13 +0900 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20Spring,=20MySQL,=20ES=20=EB=A9=94?= =?UTF-8?q?=EB=AA=A8=EB=A6=AC=20=EC=A0=9C=ED=95=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 61d91cb..3c72546 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,7 @@ services: ports: - "8080:8080" environment: + - JAVA_OPTS=-Xms2g -Xmx2g - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE} - DB_URL=${DB_URL} - DB_USERNAME=${DB_USERNAME} @@ -60,7 +61,7 @@ services: command: redis-server --requirepass ${REDIS_PASSWORD} - --maxmemory 256mb + --maxmemory 1gb --maxmemory-policy allkeys-lru networks: - app-network @@ -77,7 +78,7 @@ services: environment: - discovery.type=single-node - xpack.security.enabled=false - - "ES_JAVA_OPTS=-Xms2g -Xmx2g" + - "ES_JAVA_OPTS=-Xms8g -Xmx8g" networks: - app-network volumes: