From f18beeda93ffee204c357a2c234f03a37e7fdaa1 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Sun, 18 Jan 2026 22:18:57 +0900 Subject: [PATCH 01/21] =?UTF-8?q?test:=20prod-cicd-pipeline=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=9A=A9=EC=9C=BC=EB=A1=9C=20workflows=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index c1c7e89..8eb42af 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -2,7 +2,10 @@ name: CI/CD for Production Server on: push: - branches: [ main ] # Only main branch + branches: [ main, dev ] # Only main branch, 테스트 이후에 dev 지울 것 + pull_request: + branches: [ dev, main ] # 테스트 이후에 지울 것 + types: [ opened, synchronize, reopened ] permissions: contents: read From 3c2427101052ad9fe4b934a6c349a89193c3a567 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Sun, 18 Jan 2026 23:34:14 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20docker-compose-prod=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 2 +- docker-compose-prod.yaml | 137 +++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 docker-compose-prod.yaml diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 8eb42af..657a67b 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -101,7 +101,7 @@ jobs: - name: Deploy to Production Server run: | - ssh -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' + ssh -i ~/.ssh/my-key.pem -p ${{ secrets.PROD_SERVER_PORT }} ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' cd /home/${{ secrets.PROD_SERVER_USER }}/app # Write .env.prod content to .env diff --git a/docker-compose-prod.yaml b/docker-compose-prod.yaml new file mode 100644 index 0000000..8b71b8f --- /dev/null +++ b/docker-compose-prod.yaml @@ -0,0 +1,137 @@ +version: "3.8" + +services: + spring-app: + build: + context: . + dockerfile: Dockerfile + image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-latest} + container_name: spring-app + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + env_file: + - .env.prod + + labels: + # Autoheal: unhealthy 상태 시 자동 재시작 활성화 + autoheal: "true" + environment: + # === Application Configuration === + # SPRING_CONFIG_LOCATION: file:/app/config/application.yaml # Optional: use external config + SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + SERVER_PORT: ${SERVER_PORT} + + # === Database Configuration === + DB_IP: ${DB_IP} + DB_PORT: ${DB_PORT} + DB_SCHEMA: ${DB_SCHEMA} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + + # === Security Configuration === + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_ACCESS_TOKEN_VALIDITY: ${JWT_ACCESS_TOKEN_VALIDITY} + JWT_REFRESH_TOKEN_VALIDITY: ${JWT_REFRESH_TOKEN_VALIDITY} + + # === External API Configuration === + NEXON_OPEN_API_KEY: ${NEXON_OPEN_API_KEY} + AUCTION_HISTORY_DELAY_MS: ${AUCTION_HISTORY_DELAY_MS} + AUCTION_HISTORY_CRON: "${AUCTION_HISTORY_CRON}" + STATISTICS_PREVIOUS_DAY_CRON: "${STATISTICS_PREVIOUS_DAY_CRON:-0 0 * * * *}" + + # === Docker Configuration === + DOCKER_USERNAME: ${DOCKER_USERNAME} + DOCKER_REPO: ${DOCKER_REPO} + + # === JVM Configuration === + # All JVM options are now configurable via .env file + JAVA_OPTS: >- + -Xms${JAVA_OPTS_XMS} + -Xmx${JAVA_OPTS_XMX} + -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE} + -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE} + -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE} + -Xss${JAVA_OPTS_XSS} + -XX:+UseG1GC + -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS} + -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE} + -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT} + -XX:+TieredCompilation + -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL} + -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT} + -XX:+UseCompressedOops + -XX:+UseCompressedClassPointers + -Djava.security.egd=file:/dev/./urandom + -Dspring.jmx.enabled=false + volumes: + - app-logs:/app/logs # Named volume 사용 (권한 문제 해결) + # - ./config:/app/config:ro # Optional: mount external config directory + # Restart Policy: + # - always: 항상 재시작 (수동 stop 포함) + # - unless-stopped: 수동 stop 제외하고 재시작 + # - on-failure:N: 실패 시 최대 N번만 재시작 (무한 재시작 루프 방지) + restart: on-failure:${RESTART_POLICY_MAX_RETRIES} + + # Docker Resource Limits (cgroup을 통한 강제 메모리 제한) + deploy: + resources: + limits: + memory: ${DOCKER_MEMORY_LIMIT} # 컨테이너 최대 메모리 (hard limit, OOM killer threshold) + reservations: + memory: ${DOCKER_MEMORY_RESERVATION} # 예약 메모리 (soft limit, guaranteed minimum) + + networks: + - app-network + # Health Check: 컨테이너 상태 감지 (autoheal과 연동) + # wget 사용 (Alpine Linux에 기본 설치되어 있음) + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT}/actuator/health"] + interval: ${HEALTHCHECK_INTERVAL} # 체크 주기 + timeout: ${HEALTHCHECK_TIMEOUT} # 응답 타임아웃 + retries: ${HEALTHCHECK_RETRIES} # 연속 실패 횟수 + start_period: ${HEALTHCHECK_START_PERIOD} # 시작 유예 기간 + logging: + driver: "json-file" + options: + max-size: ${LOGGING_MAX_SIZE} # 로그 파일 최대 크기 + max-file: "${LOGGING_MAX_FILE}" # 로그 파일 보관 개수 + + # Autoheal: unhealthy 컨테이너 자동 재시작 서비스 + # - spring-app이 unhealthy 상태가 되면 자동으로 재시작 + # - Docker 소켓을 마운트하여 컨테이너 관리 권한 획득 + # - healthcheck와 독립적으로 동작 (healthcheck가 unhealthy 판정하면 autoheal이 재시작) + autoheal: + image: willfarrell/autoheal:latest + container_name: autoheal + restart: unless-stopped + environment: + # AUTOHEAL_INTERVAL: 체크 주기 (초 단위) + AUTOHEAL_INTERVAL: ${AUTOHEAL_INTERVAL} # unhealthy 컨테이너 체크 주기 (healthcheck interval과 동기화 권장) + # AUTOHEAL_START_PERIOD: 컨테이너 시작 후 체크 시작까지 유예 시간 (초) + AUTOHEAL_START_PERIOD: ${AUTOHEAL_START_PERIOD} # healthcheck의 start_period를 따르므로 0으로 설정 + # AUTOHEAL_DEFAULT_STOP_TIMEOUT: 재시작 시 강제 종료까지 대기 시간 (초) + AUTOHEAL_DEFAULT_STOP_TIMEOUT: ${AUTOHEAL_DEFAULT_STOP_TIMEOUT} # graceful shutdown 대기 시간 + # DOCKER_SOCK: Docker 소켓 경로 (컨테이너 제어용) + DOCKER_SOCK: /var/run/docker.sock + volumes: + # Docker 소켓 마운트 (컨테이너 재시작 권한 획득) + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - app-network + # autoheal은 매우 가벼운 서비스 (메모리 ~10MB) + deploy: + resources: + limits: + memory: ${AUTOHEAL_MEMORY_LIMIT} + reservations: + memory: ${AUTOHEAL_MEMORY_RESERVATION} + +volumes: + app-logs: + driver: local + +networks: + app-network: + driver: bridge \ No newline at end of file From 3e89f363f0d8f02a9d6e738fa015795eb6faa4bd Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:01:11 +0900 Subject: [PATCH 03/21] =?UTF-8?q?feat:=20github=20actions=20workflow=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 657a67b..fa6ef8a 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -86,18 +86,18 @@ jobs: - name: Setup SSH key and config run: | mkdir -p ~/.ssh - echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" > ~/.ssh/my-key.pem + echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/my-key.pem chmod 400 ~/.ssh/my-key.pem ssh-keyscan -H ${{ secrets.PROD_SERVER_HOST }} >> ~/.ssh/known_hosts echo -e "Host *\n ServerAliveInterval 60\n ServerAliveCountMax 3" >> ~/.ssh/config - name: Create app directory on server run: | - ssh -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} "mkdir -p /home/${{ secrets.PROD_SERVER_USER }}/app/logs" + ssh -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} "mkdir -p /home/${{ secrets.PROD_SERVER_USER }}/app/logs" - name: Copy docker-compose-dev.yaml to server run: | - scp -i ~/.ssh/my-key.pem docker-compose.yaml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ + scp -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose.yaml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ - name: Deploy to Production Server run: | From 7e88db2f5eb3d77d53272771e809717777df6ccb Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:04:23 +0900 Subject: [PATCH 04/21] =?UTF-8?q?feat:=20workflow=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=EC=9A=A9=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index fa6ef8a..3314ef7 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -85,6 +85,12 @@ jobs: # ======================================== - name: Setup SSH key and config run: | + set -x + + echo "HOST=${{ secrets.PROD_SERVER_HOST }}" + echo "USER=${{ secrets.PROD_SERVER_USER }}" + echo "PORT=${{ secrets.PROD_SERVER_PORT }}" + mkdir -p ~/.ssh echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/my-key.pem chmod 400 ~/.ssh/my-key.pem From d225e62f0efca06fdaefe9da16be5d1931e52b86 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:12:16 +0900 Subject: [PATCH 05/21] =?UTF-8?q?fix:=20cd-pipeline=20workflows=20ssh=20ke?= =?UTF-8?q?y-gen=20=EC=8B=9C=20=ED=8F=AC=ED=8A=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 3314ef7..a7e007b 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -94,7 +94,9 @@ jobs: mkdir -p ~/.ssh echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/my-key.pem chmod 400 ~/.ssh/my-key.pem - ssh-keyscan -H ${{ secrets.PROD_SERVER_HOST }} >> ~/.ssh/known_hosts + ssh-keyscan -p ${{ secrets.PROD_SERVER_PORT }} \ + -H ${{ secrets.PROD_SERVER_HOST }} >> ~/.ssh/known_hosts + echo -e "Host *\n ServerAliveInterval 60\n ServerAliveCountMax 3" >> ~/.ssh/config - name: Create app directory on server From 1fa5310400d902a53df5808603ad6056838e8d53 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:17:32 +0900 Subject: [PATCH 06/21] =?UTF-8?q?feat:ixod-cd=20workflows=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-dev.yml | 2 +- .github/workflows/push-cd-prod.yml | 4 ++-- docker-compose-dev.yaml => docker-compose-dev.yml | 0 docker-compose-prod.yaml => docker-compose-prod.yml | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename docker-compose-dev.yaml => docker-compose-dev.yml (100%) rename docker-compose-prod.yaml => docker-compose-prod.yml (100%) diff --git a/.github/workflows/push-cd-dev.yml b/.github/workflows/push-cd-dev.yml index 5cb5a7e..bf61650 100644 --- a/.github/workflows/push-cd-dev.yml +++ b/.github/workflows/push-cd-dev.yml @@ -91,7 +91,7 @@ jobs: run: | ssh -i ~/.ssh/my-key.pem ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} "mkdir -p /home/${{ secrets.SERVER_USER }}/app/logs" - - name: Copy docker-compose-dev.yaml to server + - name: Copy docker-compose-dev.yml to server run: | scp -i ~/.ssh/my-key.pem docker-compose-dev.yaml ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/home/${{ secrets.SERVER_USER }}/app/ diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index a7e007b..8b315c5 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -103,9 +103,9 @@ jobs: run: | ssh -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} "mkdir -p /home/${{ secrets.PROD_SERVER_USER }}/app/logs" - - name: Copy docker-compose-dev.yaml to server + - name: Copy docker-compose-prod.yml to server run: | - scp -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose.yaml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ + scp -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose-prod.yaml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ - name: Deploy to Production Server run: | diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yml similarity index 100% rename from docker-compose-dev.yaml rename to docker-compose-dev.yml diff --git a/docker-compose-prod.yaml b/docker-compose-prod.yml similarity index 100% rename from docker-compose-prod.yaml rename to docker-compose-prod.yml From c16ddaf32304f6b7d0b93066dfbb99397d93892d Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:21:45 +0900 Subject: [PATCH 07/21] =?UTF-8?q?fix:=20worflows=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 8b315c5..5be0bde 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -105,7 +105,7 @@ jobs: - name: Copy docker-compose-prod.yml to server run: | - scp -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose-prod.yaml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ + scp -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose-prod.yml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ - name: Deploy to Production Server run: | From 41353be53ad990b9d925dba339a9117a344cfe9a Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:27:46 +0900 Subject: [PATCH 08/21] =?UTF-8?q?fix:=20workflows=20scp=20-p=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 5be0bde..f5a4fea 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -105,7 +105,7 @@ jobs: - name: Copy docker-compose-prod.yml to server run: | - scp -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose-prod.yml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ + scp -P ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose-prod.yml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ - name: Deploy to Production Server run: | From b6570f280051691d516a884fc3e6bf569f255f29 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:47:25 +0900 Subject: [PATCH 09/21] =?UTF-8?q?feat:=20cd=20workflows=20app=20port,=20se?= =?UTF-8?q?rver=20port=20=ED=98=BC=EB=8F=88=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index f5a4fea..9d02606 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -119,10 +119,10 @@ jobs: docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod # Stop and remove existing containers - docker compose down + docker compose -f docker-compose-prod.yml down # Start new containers - docker compose up -d + docker compose -f docker-compose-prod.yml up -d echo "✅ Production deployment complete" EOF @@ -132,7 +132,7 @@ jobs: # ======================================== - name: Comprehensive Health Check run: | - ssh -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' + ssh -i ~/.ssh/my-key.pem -p ${{ secrets.PROD_SERVER_PORT }} ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' echo "=== Starting Production Health Check ===" # 1. Check if container is running @@ -171,7 +171,7 @@ jobs: # 3. Check Spring Boot actuator health endpoint echo "Checking actuator health endpoint..." for i in {1..30}; do - HEALTH_RESPONSE=$(curl -s http://localhost:${{ secrets.PROD_SERVER_PORT || 8080 }}/actuator/health || echo "") + HEALTH_RESPONSE=$(curl -s http://localhost:${{ secrets.PROD_APP_PORT || 8080 }}/actuator/health || echo "") if echo "$HEALTH_RESPONSE" | grep -q '"status":"UP"'; then echo "✅ Application health check passed" @@ -192,7 +192,7 @@ jobs: # 4. Smoke test: Check if API responds echo "Running smoke test..." - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${{ secrets.PROD_SERVER_PORT || 8080 }}/actuator/health) + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${{ secrets.PROD_APP_PORT || 8080 }}/actuator/health) if [ "$HTTP_CODE" == "200" ]; then echo "✅ Smoke test passed (HTTP $HTTP_CODE)" else @@ -208,7 +208,7 @@ jobs: if: success() run: | echo "✅ Production deployment successful!" - echo "🔗 Production Server: http://${{ secrets.PROD_SERVER_HOST }}:${{ secrets.PROD_SERVER_PORT || 8080 }}" + echo "🔗 Production Server: http://${{ secrets.PROD_SERVER_HOST }}:${{ secrets.PROD_APP_PORT || 8080 }}" echo "🐳 Image: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod" echo "📦 Commit: ${{ github.sha }}" echo "⚠️ Please verify the production deployment manually" From 799edec6713c3ac462ae1043df72ae421950c841 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 00:51:18 +0900 Subject: [PATCH 10/21] =?UTF-8?q?fix:=20cd=20workflows=20env=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 9d02606..458624a 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -113,7 +113,7 @@ jobs: cd /home/${{ secrets.PROD_SERVER_USER }}/app # Write .env.prod content to .env - echo "${{ secrets.ENV_FILE_PROD }}" > .env + echo "${{ secrets.ENV_FILE_PROD }}" > .env.prod # Pull latest production image docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod From a2866cccb3b43aaf2b4aff3872bcdc425358af84 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 01:03:21 +0900 Subject: [PATCH 11/21] =?UTF-8?q?fix:=20cd=20workflows=20docker=20hub=20lo?= =?UTF-8?q?gin=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 458624a..d9c92f9 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -115,6 +115,9 @@ jobs: # Write .env.prod content to .env echo "${{ secrets.ENV_FILE_PROD }}" > .env.prod + # Docker Hub 로그인 + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + # Pull latest production image docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod From 28a98692b1d1e11c912cc1f2c806c64776bfb437 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 01:21:31 +0900 Subject: [PATCH 12/21] =?UTF-8?q?feat:=20docker-compose-prod=20network=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-prod.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 8b71b8f..7ecb5a3 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -1,11 +1,7 @@ -version: "3.8" - services: spring-app: - build: - context: . - dockerfile: Dockerfile - image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-latest} + image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-prod} + pull_policy: always container_name: spring-app ports: - "${SERVER_PORT}:${SERVER_PORT}" @@ -84,6 +80,7 @@ services: networks: - app-network + - devnogi-network # MySQL, Redis 등 인프라 컨테이너와 통신 # Health Check: 컨테이너 상태 감지 (autoheal과 연동) # wget 사용 (Alpine Linux에 기본 설치되어 있음) healthcheck: @@ -134,4 +131,7 @@ volumes: networks: app-network: - driver: bridge \ No newline at end of file + driver: bridge + devnogi-network: + external: true + name: infra_devnogi-network # 기존 인프라 네트워크 (MySQL, Redis) \ No newline at end of file From 6c2fd32d9fccc342234235f0dd74161fa1ab2efc Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 19 Jan 2026 23:49:28 +0900 Subject: [PATCH 13/21] =?UTF-8?q?fix:=20docker=20compose=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-dev.yml | 4 ++-- docker-compose-dev.yml | 8 ++++---- docker-compose-local.yml | 4 ++-- docker-compose-prod.yml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/push-cd-dev.yml b/.github/workflows/push-cd-dev.yml index bf61650..003d22d 100644 --- a/.github/workflows/push-cd-dev.yml +++ b/.github/workflows/push-cd-dev.yml @@ -100,8 +100,8 @@ jobs: ssh -i ~/.ssh/my-key.pem ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }} << 'EOF' cd /home/${{ secrets.SERVER_USER }}/app - # Write .env.dev content to .env - echo "${{ secrets.ENV_FILE_DEV }}" > .env + # Write .env.dev content to .env.dev + echo "${{ secrets.ENV_FILE_DEV }}" > .env.dev # Pull latest dev image docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:dev diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 9216a24..1547d5d 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,16 +1,16 @@ version: "3.8" services: - spring-app: + devnogi-batch-dev: build: context: . dockerfile: Dockerfile image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-latest} - container_name: spring-app + container_name: devnogi-batch-app-dev ports: - "${SERVER_PORT}:${SERVER_PORT}" env_file: - - .env + - .env.dev labels: # Autoheal: unhealthy 상태 시 자동 재시작 활성화 @@ -104,7 +104,7 @@ services: # - healthcheck와 독립적으로 동작 (healthcheck가 unhealthy 판정하면 autoheal이 재시작) autoheal: image: willfarrell/autoheal:latest - container_name: autoheal + container_name: devnogi-batch-autoheal-dev restart: unless-stopped environment: # AUTOHEAL_INTERVAL: 체크 주기 (초 단위) diff --git a/docker-compose-local.yml b/docker-compose-local.yml index b1b9104..f372408 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -11,7 +11,7 @@ services: dockerfile: Dockerfile # 로컬 이미지 이름 (Docker Hub에 push하지 않음) image: open-api-batch-server:local - container_name: spring-app-local + container_name: devnogi-batch-app-local ports: - "${SERVER_PORT:-8092}:${SERVER_PORT:-8092}" env_file: @@ -142,7 +142,7 @@ services: # Autoheal: unhealthy 컨테이너 자동 재시작 autoheal: image: willfarrell/autoheal:latest - container_name: autoheal-local + container_name: devnogi-batch-autoheal-local restart: unless-stopped environment: AUTOHEAL_INTERVAL: ${AUTOHEAL_INTERVAL:-30} diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 7ecb5a3..a1dfd3b 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -2,7 +2,7 @@ services: spring-app: image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-prod} pull_policy: always - container_name: spring-app + container_name: devnogi-batch-app-prod ports: - "${SERVER_PORT}:${SERVER_PORT}" env_file: @@ -101,7 +101,7 @@ services: # - healthcheck와 독립적으로 동작 (healthcheck가 unhealthy 판정하면 autoheal이 재시작) autoheal: image: willfarrell/autoheal:latest - container_name: autoheal + container_name: devnogi-batch-autoheal-prod restart: unless-stopped environment: # AUTOHEAL_INTERVAL: 체크 주기 (초 단위) From 27212a8641ed64432a2fd0e82d652ca3d4ee14e4 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Tue, 20 Jan 2026 00:02:29 +0900 Subject: [PATCH 14/21] =?UTF-8?q?feat:=20cd=20workflow=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.sample | 82 ------------------------------ .github/workflows/push-cd-dev.yml | 6 +-- .github/workflows/push-cd-prod.yml | 4 +- 3 files changed, 5 insertions(+), 87 deletions(-) delete mode 100644 .env.sample diff --git a/.env.sample b/.env.sample deleted file mode 100644 index dd9df1b..0000000 --- a/.env.sample +++ /dev/null @@ -1,82 +0,0 @@ -# ============================================================================= -# Environment Configuration Template for Open API Batch Server -# ============================================================================= -# Copy this file to .env and fill in the values for your environment -# - Local: Development on local machine -# - Dev: Development server -# - Prod: Production server -# ============================================================================= - -# Application Configuration -SERVER_PORT=${SERVER_PORT} - -# Database Configuration -DB_IP=${DB_IP} -DB_PORT=${DB_PORT} -DB_SCHEMA=${DB_SCHEMA} -DB_USER=${DB_USER} -DB_PASSWORD=${DB_PASSWORD} - -# Security Configuration -JWT_SECRET_KEY=${JWT_SECRET_KEY} -JWT_ACCESS_TOKEN_VALIDITY=${JWT_ACCESS_TOKEN_VALIDITY} -JWT_REFRESH_TOKEN_VALIDITY=${JWT_REFRESH_TOKEN_VALIDITY} - -# External API Configuration -NEXON_OPEN_API_KEY=${NEXON_OPEN_API_KEY} -AUCTION_HISTORY_DELAY_MS=${AUCTION_HISTORY_DELAY_MS} -AUCTION_HISTORY_CRON=${AUCTION_HISTORY_CRON} -AUCTION_HISTORY_MIN_PRICE_CRON=${AUCTION_HISTORY_MIN_PRICE_CRON} - -# Docker Configuration -DOCKER_USERNAME=${DOCKER_USERNAME} -DOCKER_PASSWORD=${DOCKER_PASSWORD} -DOCKER_REPO=${DOCKER_REPO} -DOCKER_IMAGE_TAG=${DOCKER_IMAGE_TAG:-latest} - -# Spring Profile -SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-default} - -# JVM Memory Configuration -# Heap Memory -JAVA_OPTS_XMS=${JAVA_OPTS_XMS:-256m} -JAVA_OPTS_XMX=${JAVA_OPTS_XMX:-512m} - -# Non-Heap Memory -JAVA_OPTS_MAX_METASPACE_SIZE=${JAVA_OPTS_MAX_METASPACE_SIZE:-150m} -JAVA_OPTS_RESERVED_CODE_CACHE_SIZE=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE:-48m} -JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE:-64m} -JAVA_OPTS_XSS=${JAVA_OPTS_XSS:-512k} - -# JVM GC Configuration (G1GC) -JAVA_OPTS_MAX_GC_PAUSE_MILLIS=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS:-200} -JAVA_OPTS_G1_HEAP_REGION_SIZE=${JAVA_OPTS_G1_HEAP_REGION_SIZE:-2m} -JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT:-45} - -# JVM Compiler Configuration -JAVA_OPTS_TIERED_STOP_AT_LEVEL=${JAVA_OPTS_TIERED_STOP_AT_LEVEL:-2} -JAVA_OPTS_CI_COMPILER_COUNT=${JAVA_OPTS_CI_COMPILER_COUNT:-2} - -# Docker Container Resource Limits -DOCKER_MEMORY_LIMIT=${DOCKER_MEMORY_LIMIT:-750M} -DOCKER_MEMORY_RESERVATION=${DOCKER_MEMORY_RESERVATION:-512M} - -# Container Restart Policy -RESTART_POLICY_MAX_RETRIES=${RESTART_POLICY_MAX_RETRIES:-5} - -# Health Check Configuration -HEALTHCHECK_INTERVAL=${HEALTHCHECK_INTERVAL:-30s} -HEALTHCHECK_TIMEOUT=${HEALTHCHECK_TIMEOUT:-15s} -HEALTHCHECK_RETRIES=${HEALTHCHECK_RETRIES:-4} -HEALTHCHECK_START_PERIOD=${HEALTHCHECK_START_PERIOD:-120s} - -# Autoheal Configuration -AUTOHEAL_INTERVAL=${AUTOHEAL_INTERVAL:-30} -AUTOHEAL_START_PERIOD=${AUTOHEAL_START_PERIOD:-0} -AUTOHEAL_DEFAULT_STOP_TIMEOUT=${AUTOHEAL_DEFAULT_STOP_TIMEOUT:-15} -AUTOHEAL_MEMORY_LIMIT=${AUTOHEAL_MEMORY_LIMIT:-50M} -AUTOHEAL_MEMORY_RESERVATION=${AUTOHEAL_MEMORY_RESERVATION:-20M} - -# Logging Configuration -LOGGING_MAX_SIZE=${LOGGING_MAX_SIZE:-10m} -LOGGING_MAX_FILE=${LOGGING_MAX_FILE:-3} \ No newline at end of file diff --git a/.github/workflows/push-cd-dev.yml b/.github/workflows/push-cd-dev.yml index 003d22d..cf9fbf2 100644 --- a/.github/workflows/push-cd-dev.yml +++ b/.github/workflows/push-cd-dev.yml @@ -93,7 +93,7 @@ jobs: - name: Copy docker-compose-dev.yml to server run: | - scp -i ~/.ssh/my-key.pem docker-compose-dev.yaml ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/home/${{ secrets.SERVER_USER }}/app/ + scp -i ~/.ssh/my-key.pem docker-compose-dev.yml ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_HOST }}:/home/${{ secrets.SERVER_USER }}/app/ - name: Deploy to Dev Server run: | @@ -107,10 +107,10 @@ jobs: docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:dev # Stop and remove existing containers - docker compose down + docker compose -f docker-compose-dev.yml down # Start new containers - docker compose up -d + docker compose -f docker-compose-dev.yml --env-file .env.dev up -d echo "✅ Dev deployment complete" EOF diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index d9c92f9..eced656 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -125,7 +125,7 @@ jobs: docker compose -f docker-compose-prod.yml down # Start new containers - docker compose -f docker-compose-prod.yml up -d + docker compose -f docker-compose-prod.yml --env-file .env.prod up -d echo "✅ Production deployment complete" EOF @@ -225,4 +225,4 @@ jobs: echo "❌ Deployment failed! Consider manual rollback if needed." echo "To rollback, SSH to server and run:" echo " docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod-" - echo " docker compose down && docker compose up -d" + echo " docker compose -f docker-compose-prod.yml down && docker compose -f docker-compose-prod.yml --env-file .env.prod up -d" From c96370c0bc5d3f80b4ec9ab95493b6495efcaf1a Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Tue, 20 Jan 2026 00:22:40 +0900 Subject: [PATCH 15/21] =?UTF-8?q?fix:=20env=5Ffile=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=9C=20=EA=B0=92=20docker-compose=EB=A1=9C=20=EC=9D=B4?= =?UTF-8?q?=EC=8B=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.local.sample | 99 ++++++++-------------------------------- docker-compose-dev.yml | 93 +++++++++++++++++-------------------- docker-compose-local.yml | 72 +++++++++++++++-------------- docker-compose-prod.yml | 93 +++++++++++++++++-------------------- 4 files changed, 144 insertions(+), 213 deletions(-) diff --git a/.env.local.sample b/.env.local.sample index 4e2fd45..43e27c9 100644 --- a/.env.local.sample +++ b/.env.local.sample @@ -6,32 +6,37 @@ # 사용법: # 1. 이 파일을 .env.local로 복사하세요 # 2. 필요한 값들을 채워넣으세요 -# 3. docker-compose -f docker-compose.local.yml up --build 로 실행하세요 +# 3. docker-compose -f docker-compose-local.yml --env-file .env.local up -d --build 로 실행하세요 +# +# 참고: JVM, Docker 리소스, Healthcheck, Autoheal, Logging 설정은 +# docker-compose-local.yml에 하드코딩되어 있습니다. # ============================================================================= # ============================================================================= # Application Configuration # ============================================================================= -SERVER_PORT=8080 -SPRING_PROFILES_ACTIVE=local +SERVER_PORT=8092 # ============================================================================= # Database Configuration # ============================================================================= -# 로컬 MySQL 사용 시 (Docker로 MySQL 실행하는 경우) -DB_IP=host.docker.internal # Docker 컨테이너에서 호스트 머신 접근 -# DB_IP=localhost # 호스트 머신에서 직접 실행 시 -DB_PORT=3306 -DB_SCHEMA=devnogi -DB_USER=root +# Docker Compose 내부 MySQL 사용 시 (docker-compose-local.yml에 MySQL 포함) +DB_IP=mysql # Docker 서비스 이름 +DB_PORT=3318 # Docker 내부 포트 +DB_SCHEMA=local_oab +DB_USER=local_oab DB_PASSWORD=your_local_password +DB_ROOT_PASSWORD=your_root_password + +# 외부 MySQL 접속용 포트 (호스트에서 MySQL 컨테이너 접근 시) +MYSQL_EXTERNAL_PORT=3318 # ============================================================================= # Security Configuration (로컬 개발용 - 운영 환경과 다른 값 사용) # ============================================================================= JWT_SECRET_KEY=local-development-secret-key-do-not-use-in-production-change-this JWT_ACCESS_TOKEN_VALIDITY=3600000 # 1시간 (밀리초) -JWT_REFRESH_TOKEN_VALIDITY=86400000 # 24시간 (밀리초) +JWT_REFRESH_TOKEN_VALIDITY=86400000 # 24시간 (밀리초) # ============================================================================= # External API Configuration @@ -41,74 +46,8 @@ NEXON_OPEN_API_KEY=your_nexon_api_key_here # 경매 데이터 수집 설정 AUCTION_HISTORY_DELAY_MS=1000 # API 호출 간 딜레이 (1초) -AUCTION_HISTORY_CRON=0 0 * * * * # 매시간 정각에 실행 (초 분 시 일 월 요일) -AUCTION_HISTORY_MIN_PRICE_CRON=0 30 * * * * # 매시간 30분에 실행 - -# ============================================================================= -# Docker Configuration (로컬 개발에서는 불필요) -# ============================================================================= -# DOCKER_USERNAME=your_dockerhub_username -# DOCKER_PASSWORD=your_dockerhub_password -# DOCKER_REPO=open-api-batch-server -# DOCKER_IMAGE_TAG=local - -# ============================================================================= -# JVM Memory Configuration (로컬 개발용 - 메모리 사용량 감소) -# ============================================================================= -# Heap Memory - 로컬에서는 적은 메모리로 실행 -JAVA_OPTS_XMS=256m # 초기 힙 메모리 -JAVA_OPTS_XMX=512m # 최대 힙 메모리 - -# Non-Heap Memory -JAVA_OPTS_MAX_METASPACE_SIZE=128m # Metaspace 최대 크기 -JAVA_OPTS_RESERVED_CODE_CACHE_SIZE=64m # JIT 컴파일된 코드 캐시 -JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE=64m # Direct Buffer 최대 크기 -JAVA_OPTS_XSS=512k # 스레드 스택 크기 - -# ============================================================================= -# JVM GC Configuration (G1GC) -# ============================================================================= -JAVA_OPTS_MAX_GC_PAUSE_MILLIS=200 # GC 일시정지 목표 시간 -JAVA_OPTS_G1_HEAP_REGION_SIZE=1m # G1 힙 영역 크기 -JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT=45 # GC 시작 힙 점유율 - -# ============================================================================= -# JVM Compiler Configuration -# ============================================================================= -# 로컬 개발에서는 빠른 시작을 위해 TieredStopAtLevel=1 (C1 컴파일러만 사용) -JAVA_OPTS_TIERED_STOP_AT_LEVEL=1 # 1: 빠른 시작, 4: 최적 성능 -JAVA_OPTS_CI_COMPILER_COUNT=2 # 컴파일러 스레드 수 +AUCTION_HISTORY_CRON=0 0 * * * * # 매시간 정각에 실행 (초 분 시 일 월 요일) -# ============================================================================= -# Docker Container Resource Limits (로컬 개발용) -# ============================================================================= -DOCKER_MEMORY_LIMIT=1g # 컨테이너 최대 메모리 -DOCKER_MEMORY_RESERVATION=512m # 예약 메모리 - -# ============================================================================= -# Container Restart Policy -# ============================================================================= -RESTART_POLICY_MAX_RETRIES=3 # 실패 시 최대 재시작 횟수 - -# ============================================================================= -# Health Check Configuration (로컬 개발용 - 더 짧은 간격) -# ============================================================================= -HEALTHCHECK_INTERVAL=30s # 헬스 체크 주기 -HEALTHCHECK_TIMEOUT=10s # 헬스 체크 타임아웃 -HEALTHCHECK_RETRIES=3 # 연속 실패 횟수 -HEALTHCHECK_START_PERIOD=60s # 시작 유예 기간 - -# ============================================================================= -# Autoheal Configuration -# ============================================================================= -AUTOHEAL_INTERVAL=30 # unhealthy 체크 주기 (초) -AUTOHEAL_START_PERIOD=0 # 체크 시작 유예 시간 -AUTOHEAL_DEFAULT_STOP_TIMEOUT=10 # 재시작 시 강제 종료 대기 시간 -AUTOHEAL_MEMORY_LIMIT=50M # autoheal 최대 메모리 -AUTOHEAL_MEMORY_RESERVATION=20M # autoheal 예약 메모리 - -# ============================================================================= -# Logging Configuration -# ============================================================================= -LOGGING_MAX_SIZE=10m # 로그 파일 최대 크기 -LOGGING_MAX_FILE=3 # 로그 파일 보관 개수 +# 통계 스케줄러 설정 +STATISTICS_DAILY_CRON=0 0 3 * * * # 매일 오전 3시 +STATISTICS_WEEKLY_CRON=0 0 4 * * MON # 매주 월요일 오전 4시 diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 1547d5d..240cb64 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,5 +1,12 @@ version: "3.8" +# ============================================================================= +# 개발 서버 환경용 Docker Compose 설정 (Profile: dev) +# ============================================================================= +# 사용법: docker-compose -f docker-compose-dev.yml --env-file .env.dev up -d +# 중간 수준의 리소스 할당 (팀 테스트 및 검증용) +# ============================================================================= + services: devnogi-batch-dev: build: @@ -10,15 +17,13 @@ services: ports: - "${SERVER_PORT}:${SERVER_PORT}" env_file: - - .env.dev + - .env.dev # 개발 환경 변수 파일 (DB, JWT, API 키 등 민감 정보) labels: - # Autoheal: unhealthy 상태 시 자동 재시작 활성화 autoheal: "true" environment: # === Application Configuration === - # SPRING_CONFIG_LOCATION: file:/app/config/application.yaml # Optional: use external config - SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + SPRING_PROFILES_ACTIVE: dev LANG: C.UTF-8 LC_ALL: C.UTF-8 SERVER_PORT: ${SERVER_PORT} @@ -45,88 +50,76 @@ services: DOCKER_USERNAME: ${DOCKER_USERNAME} DOCKER_REPO: ${DOCKER_REPO} - # === JVM Configuration === - # All JVM options are now configurable via .env file + # === JVM Configuration (Dev - 중간 리소스) === + # Heap: 256m~512m, Non-Heap: ~262m, Total: ~774m JAVA_OPTS: >- - -Xms${JAVA_OPTS_XMS} - -Xmx${JAVA_OPTS_XMX} - -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE} - -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE} - -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE} - -Xss${JAVA_OPTS_XSS} + -Xms256m + -Xmx512m + -XX:MaxMetaspaceSize=150m + -XX:ReservedCodeCacheSize=48m + -XX:MaxDirectMemorySize=64m + -Xss512k -XX:+UseG1GC - -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS} - -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE} - -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT} + -XX:MaxGCPauseMillis=200 + -XX:G1HeapRegionSize=2m + -XX:InitiatingHeapOccupancyPercent=45 -XX:+TieredCompilation - -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL} - -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT} + -XX:TieredStopAtLevel=2 + -XX:CICompilerCount=2 -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -Djava.security.egd=file:/dev/./urandom -Dspring.jmx.enabled=false volumes: - - app-logs:/app/logs # Named volume 사용 (권한 문제 해결) - # - ./config:/app/config:ro # Optional: mount external config directory - # Restart Policy: - # - always: 항상 재시작 (수동 stop 포함) - # - unless-stopped: 수동 stop 제외하고 재시작 - # - on-failure:N: 실패 시 최대 N번만 재시작 (무한 재시작 루프 방지) - restart: on-failure:${RESTART_POLICY_MAX_RETRIES} + - app-logs:/app/logs + restart: on-failure:5 - # Docker Resource Limits (cgroup을 통한 강제 메모리 제한) + # === Docker Resource Limits (Dev - 중간) === deploy: resources: limits: - memory: ${DOCKER_MEMORY_LIMIT} # 컨테이너 최대 메모리 (hard limit, OOM killer threshold) + memory: 750M reservations: - memory: ${DOCKER_MEMORY_RESERVATION} # 예약 메모리 (soft limit, guaranteed minimum) + memory: 512M networks: - app-network - # Health Check: 컨테이너 상태 감지 (autoheal과 연동) - # wget 사용 (Alpine Linux에 기본 설치되어 있음) + + # === Health Check (Dev - 표준) === healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT}/actuator/health"] - interval: ${HEALTHCHECK_INTERVAL} # 체크 주기 - timeout: ${HEALTHCHECK_TIMEOUT} # 응답 타임아웃 - retries: ${HEALTHCHECK_RETRIES} # 연속 실패 횟수 - start_period: ${HEALTHCHECK_START_PERIOD} # 시작 유예 기간 + interval: 30s + timeout: 15s + retries: 4 + start_period: 120s + + # === Logging (Dev - 표준) === logging: driver: "json-file" options: - max-size: ${LOGGING_MAX_SIZE} # 로그 파일 최대 크기 - max-file: "${LOGGING_MAX_FILE}" # 로그 파일 보관 개수 + max-size: "10m" + max-file: "3" - # Autoheal: unhealthy 컨테이너 자동 재시작 서비스 - # - spring-app이 unhealthy 상태가 되면 자동으로 재시작 - # - Docker 소켓을 마운트하여 컨테이너 관리 권한 획득 - # - healthcheck와 독립적으로 동작 (healthcheck가 unhealthy 판정하면 autoheal이 재시작) + # === Autoheal (Dev - 표준) === autoheal: image: willfarrell/autoheal:latest container_name: devnogi-batch-autoheal-dev restart: unless-stopped environment: - # AUTOHEAL_INTERVAL: 체크 주기 (초 단위) - AUTOHEAL_INTERVAL: ${AUTOHEAL_INTERVAL} # unhealthy 컨테이너 체크 주기 (healthcheck interval과 동기화 권장) - # AUTOHEAL_START_PERIOD: 컨테이너 시작 후 체크 시작까지 유예 시간 (초) - AUTOHEAL_START_PERIOD: ${AUTOHEAL_START_PERIOD} # healthcheck의 start_period를 따르므로 0으로 설정 - # AUTOHEAL_DEFAULT_STOP_TIMEOUT: 재시작 시 강제 종료까지 대기 시간 (초) - AUTOHEAL_DEFAULT_STOP_TIMEOUT: ${AUTOHEAL_DEFAULT_STOP_TIMEOUT} # graceful shutdown 대기 시간 - # DOCKER_SOCK: Docker 소켓 경로 (컨테이너 제어용) + AUTOHEAL_INTERVAL: 30 # 체크 주기 (초) + AUTOHEAL_START_PERIOD: 0 # 시작 유예 시간 + AUTOHEAL_DEFAULT_STOP_TIMEOUT: 15 # graceful shutdown 대기 시간 (초) DOCKER_SOCK: /var/run/docker.sock volumes: - # Docker 소켓 마운트 (컨테이너 재시작 권한 획득) - /var/run/docker.sock:/var/run/docker.sock:ro networks: - app-network - # autoheal은 매우 가벼운 서비스 (메모리 ~10MB) deploy: resources: limits: - memory: ${AUTOHEAL_MEMORY_LIMIT} + memory: 50M reservations: - memory: ${AUTOHEAL_MEMORY_RESERVATION} + memory: 20M volumes: app-logs: diff --git a/docker-compose-local.yml b/docker-compose-local.yml index f372408..f61af38 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -1,8 +1,11 @@ version: "3.8" -# 로컬 개발 환경용 Docker Compose 설정 -# 사용법: docker-compose -f docker-compose-local.yml --env-file .env.local up --build +# ============================================================================= +# 로컬 개발 환경용 Docker Compose 설정 (Profile: local) +# ============================================================================= +# 사용법: docker-compose -f docker-compose-local.yml --env-file .env.local up -d --build # 참고: 쉘 환경 변수가 .env.local 파일의 값을 오버라이드할 수 있으므로 --env-file 옵션을 명시하는 것이 좋습니다 +# ============================================================================= services: spring-app: @@ -15,12 +18,12 @@ services: ports: - "${SERVER_PORT:-8092}:${SERVER_PORT:-8092}" env_file: - - .env.local # 로컬 환경 변수 파일 + - .env.local # 로컬 환경 변수 파일 (DB, JWT, API 키 등 민감 정보) labels: autoheal: "true" environment: # === Application Configuration === - SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-local} + SPRING_PROFILES_ACTIVE: local LANG: C.UTF-8 LC_ALL: C.UTF-8 SERVER_PORT: ${SERVER_PORT:-8092} @@ -44,21 +47,22 @@ services: AUCTION_HISTORY_CRON: "${AUCTION_HISTORY_CRON:-0 0 * * * *}" STATISTICS_PREVIOUS_DAY_CRON: "${STATISTICS_PREVIOUS_DAY_CRON:-0 0 * * * *}" - # === JVM Configuration (로컬 개발용 - 메모리 사용량 감소) === + # === JVM Configuration (Local - 경량 개발용) === + # Heap: 256m~512m, Non-Heap: 256m, Total: ~768m JAVA_OPTS: >- - -Xms${JAVA_OPTS_XMS:-256m} - -Xmx${JAVA_OPTS_XMX:-512m} - -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE:-128m} - -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE:-64m} - -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE:-64m} - -Xss${JAVA_OPTS_XSS:-512k} + -Xms256m + -Xmx512m + -XX:MaxMetaspaceSize=128m + -XX:ReservedCodeCacheSize=64m + -XX:MaxDirectMemorySize=64m + -Xss512k -XX:+UseG1GC - -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS:-200} - -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE:-1m} - -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT:-45} + -XX:MaxGCPauseMillis=200 + -XX:G1HeapRegionSize=1m + -XX:InitiatingHeapOccupancyPercent=45 -XX:+TieredCompilation - -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL:-1} - -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT:-2} + -XX:TieredStopAtLevel=1 + -XX:CICompilerCount=2 -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -Djava.security.egd=file:/dev/./urandom @@ -67,15 +71,15 @@ services: - app-logs:/app/logs # Named volume 사용 (권한 문제 해결) # 로컬 개발 시 설정 파일 마운트 (선택사항) # - ./config:/app/config:ro - restart: on-failure:${RESTART_POLICY_MAX_RETRIES:-3} + restart: on-failure:3 - # 로컬 개발용 리소스 제한 (더 적은 리소스) + # === Docker Resource Limits (Local - 경량) === deploy: resources: limits: - memory: ${DOCKER_MEMORY_LIMIT:-1g} + memory: 1g reservations: - memory: ${DOCKER_MEMORY_RESERVATION:-512m} + memory: 512m networks: - app-network @@ -86,19 +90,20 @@ services: mysql: condition: service_healthy - # Health Check + # === Health Check (Local - 표준) === healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT:-8092}/actuator/health"] - interval: ${HEALTHCHECK_INTERVAL:-30s} - timeout: ${HEALTHCHECK_TIMEOUT:-10s} - retries: ${HEALTHCHECK_RETRIES:-3} - start_period: ${HEALTHCHECK_START_PERIOD:-90s} # MySQL 초기화 시간 고려하여 증가 + interval: 30s + timeout: 10s + retries: 3 + start_period: 90s # MySQL 초기화 시간 고려 + # === Logging (Local - 최소) === logging: driver: "json-file" options: - max-size: ${LOGGING_MAX_SIZE:-10m} - max-file: "${LOGGING_MAX_FILE:-3}" + max-size: "10m" + max-file: "3" # MySQL Database mysql: @@ -139,15 +144,16 @@ services: - --default-time-zone=+09:00 # MySQL 레벨 타임존 설정 - --explicit_defaults_for_timestamp=1 # TIMESTAMP 기본값 명시 허용 - # Autoheal: unhealthy 컨테이너 자동 재시작 + # === Autoheal (Local - 표준) === + # unhealthy 컨테이너 자동 재시작 서비스 autoheal: image: willfarrell/autoheal:latest container_name: devnogi-batch-autoheal-local restart: unless-stopped environment: - AUTOHEAL_INTERVAL: ${AUTOHEAL_INTERVAL:-30} - AUTOHEAL_START_PERIOD: ${AUTOHEAL_START_PERIOD:-0} - AUTOHEAL_DEFAULT_STOP_TIMEOUT: ${AUTOHEAL_DEFAULT_STOP_TIMEOUT:-10} + AUTOHEAL_INTERVAL: 30 # 체크 주기 (초) + AUTOHEAL_START_PERIOD: 0 # 시작 유예 시간 (healthcheck start_period를 따름) + AUTOHEAL_DEFAULT_STOP_TIMEOUT: 10 # graceful shutdown 대기 시간 (초) DOCKER_SOCK: /var/run/docker.sock volumes: - /var/run/docker.sock:/var/run/docker.sock:ro @@ -156,9 +162,9 @@ services: deploy: resources: limits: - memory: ${AUTOHEAL_MEMORY_LIMIT:-50m} + memory: 50m reservations: - memory: ${AUTOHEAL_MEMORY_RESERVATION:-20m} + memory: 20m volumes: mysql_data: diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index a1dfd3b..77d56f0 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -1,3 +1,10 @@ +# ============================================================================= +# 운영 서버 환경용 Docker Compose 설정 (Profile: prod) +# ============================================================================= +# 사용법: docker-compose -f docker-compose-prod.yml --env-file .env.prod up -d +# 고성능 리소스 할당 (Dev의 3배 메모리) +# ============================================================================= + services: spring-app: image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-prod} @@ -6,15 +13,13 @@ services: ports: - "${SERVER_PORT}:${SERVER_PORT}" env_file: - - .env.prod + - .env.prod # 운영 환경 변수 파일 (DB, JWT, API 키 등 민감 정보) labels: - # Autoheal: unhealthy 상태 시 자동 재시작 활성화 autoheal: "true" environment: # === Application Configuration === - # SPRING_CONFIG_LOCATION: file:/app/config/application.yaml # Optional: use external config - SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} + SPRING_PROFILES_ACTIVE: prod LANG: C.UTF-8 LC_ALL: C.UTF-8 SERVER_PORT: ${SERVER_PORT} @@ -41,89 +46,77 @@ services: DOCKER_USERNAME: ${DOCKER_USERNAME} DOCKER_REPO: ${DOCKER_REPO} - # === JVM Configuration === - # All JVM options are now configurable via .env file + # === JVM Configuration (Prod - 고성능, Dev의 3배) === + # Heap: 768m~1536m, Non-Heap: ~786m, Total: ~2322m JAVA_OPTS: >- - -Xms${JAVA_OPTS_XMS} - -Xmx${JAVA_OPTS_XMX} - -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE} - -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE} - -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE} - -Xss${JAVA_OPTS_XSS} + -Xms768m + -Xmx1536m + -XX:MaxMetaspaceSize=450m + -XX:ReservedCodeCacheSize=144m + -XX:MaxDirectMemorySize=192m + -Xss512k -XX:+UseG1GC - -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS} - -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE} - -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT} + -XX:MaxGCPauseMillis=100 + -XX:G1HeapRegionSize=4m + -XX:InitiatingHeapOccupancyPercent=35 -XX:+TieredCompilation - -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL} - -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT} + -XX:TieredStopAtLevel=4 + -XX:CICompilerCount=4 -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -Djava.security.egd=file:/dev/./urandom -Dspring.jmx.enabled=false volumes: - - app-logs:/app/logs # Named volume 사용 (권한 문제 해결) - # - ./config:/app/config:ro # Optional: mount external config directory - # Restart Policy: - # - always: 항상 재시작 (수동 stop 포함) - # - unless-stopped: 수동 stop 제외하고 재시작 - # - on-failure:N: 실패 시 최대 N번만 재시작 (무한 재시작 루프 방지) - restart: on-failure:${RESTART_POLICY_MAX_RETRIES} + - app-logs:/app/logs + restart: on-failure:5 - # Docker Resource Limits (cgroup을 통한 강제 메모리 제한) + # === Docker Resource Limits (Prod - 고성능, Dev의 3배) === deploy: resources: limits: - memory: ${DOCKER_MEMORY_LIMIT} # 컨테이너 최대 메모리 (hard limit, OOM killer threshold) + memory: 2250M reservations: - memory: ${DOCKER_MEMORY_RESERVATION} # 예약 메모리 (soft limit, guaranteed minimum) + memory: 1536M networks: - app-network - devnogi-network # MySQL, Redis 등 인프라 컨테이너와 통신 - # Health Check: 컨테이너 상태 감지 (autoheal과 연동) - # wget 사용 (Alpine Linux에 기본 설치되어 있음) + + # === Health Check (Prod - 빈번한 체크, 빠른 복구) === healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT}/actuator/health"] - interval: ${HEALTHCHECK_INTERVAL} # 체크 주기 - timeout: ${HEALTHCHECK_TIMEOUT} # 응답 타임아웃 - retries: ${HEALTHCHECK_RETRIES} # 연속 실패 횟수 - start_period: ${HEALTHCHECK_START_PERIOD} # 시작 유예 기간 + interval: 15s # 더 자주 체크 (운영 안정성) + timeout: 10s # 더 짧은 타임아웃 (빠른 감지) + retries: 5 # 더 많은 재시도 (일시적 장애 허용) + start_period: 180s # 더 긴 시작 유예 (충분한 워밍업) + + # === Logging (Prod - 대용량, 장기 보관) === logging: driver: "json-file" options: - max-size: ${LOGGING_MAX_SIZE} # 로그 파일 최대 크기 - max-file: "${LOGGING_MAX_FILE}" # 로그 파일 보관 개수 + max-size: "50m" + max-file: "5" - # Autoheal: unhealthy 컨테이너 자동 재시작 서비스 - # - spring-app이 unhealthy 상태가 되면 자동으로 재시작 - # - Docker 소켓을 마운트하여 컨테이너 관리 권한 획득 - # - healthcheck와 독립적으로 동작 (healthcheck가 unhealthy 판정하면 autoheal이 재시작) + # === Autoheal (Prod - 빈번한 체크, 긴 graceful shutdown) === autoheal: image: willfarrell/autoheal:latest container_name: devnogi-batch-autoheal-prod restart: unless-stopped environment: - # AUTOHEAL_INTERVAL: 체크 주기 (초 단위) - AUTOHEAL_INTERVAL: ${AUTOHEAL_INTERVAL} # unhealthy 컨테이너 체크 주기 (healthcheck interval과 동기화 권장) - # AUTOHEAL_START_PERIOD: 컨테이너 시작 후 체크 시작까지 유예 시간 (초) - AUTOHEAL_START_PERIOD: ${AUTOHEAL_START_PERIOD} # healthcheck의 start_period를 따르므로 0으로 설정 - # AUTOHEAL_DEFAULT_STOP_TIMEOUT: 재시작 시 강제 종료까지 대기 시간 (초) - AUTOHEAL_DEFAULT_STOP_TIMEOUT: ${AUTOHEAL_DEFAULT_STOP_TIMEOUT} # graceful shutdown 대기 시간 - # DOCKER_SOCK: Docker 소켓 경로 (컨테이너 제어용) + AUTOHEAL_INTERVAL: 15 # 더 자주 체크 (초) + AUTOHEAL_START_PERIOD: 0 # 시작 유예 시간 + AUTOHEAL_DEFAULT_STOP_TIMEOUT: 30 # 더 긴 graceful shutdown (초) DOCKER_SOCK: /var/run/docker.sock volumes: - # Docker 소켓 마운트 (컨테이너 재시작 권한 획득) - /var/run/docker.sock:/var/run/docker.sock:ro networks: - app-network - # autoheal은 매우 가벼운 서비스 (메모리 ~10MB) deploy: resources: limits: - memory: ${AUTOHEAL_MEMORY_LIMIT} + memory: 75M reservations: - memory: ${AUTOHEAL_MEMORY_RESERVATION} + memory: 30M volumes: app-logs: From 484224954311d54562bf9b832f6fb5315e49cfae Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Tue, 20 Jan 2026 00:29:40 +0900 Subject: [PATCH 16/21] =?UTF-8?q?fix:=20cd=20workflows=EC=97=90=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EB=90=9C=20=EB=8F=84=EC=BB=A4=20=EC=BB=A8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=84=88=EB=AA=85=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-dev.yml | 12 ++++++------ .github/workflows/push-cd-prod.yml | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/push-cd-dev.yml b/.github/workflows/push-cd-dev.yml index cf9fbf2..568e3ad 100644 --- a/.github/workflows/push-cd-dev.yml +++ b/.github/workflows/push-cd-dev.yml @@ -124,11 +124,11 @@ jobs: echo "=== Starting Health Check ===" # 1. Check if container is running - CONTAINER_ID=$(docker ps -q --filter "name=spring-app") + CONTAINER_ID=$(docker ps -q --filter "name=devnogi-batch-app-dev") if [ -z "$CONTAINER_ID" ]; then echo "❌ Container not running" docker ps -a - docker logs spring-app --tail 50 + docker logs devnogi-batch-app-dev --tail 50 exit 1 fi echo "✅ Container is running (ID: $CONTAINER_ID)" @@ -136,7 +136,7 @@ jobs: # 2. Wait for Docker health check echo "Waiting for container to become healthy..." for i in {1..30}; do - HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' spring-app 2>/dev/null || echo "no-healthcheck") + HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' devnogi-batch-app-dev 2>/dev/null || echo "no-healthcheck") if [ "$HEALTH_STATUS" == "healthy" ]; then echo "✅ Container is healthy" @@ -151,7 +151,7 @@ jobs: if [ $i -eq 30 ]; then echo "❌ Container failed to become healthy after 5 minutes" - docker logs spring-app --tail 100 + docker logs devnogi-batch-app-dev --tail 100 exit 1 fi done @@ -173,13 +173,13 @@ jobs: if [ $i -eq 20 ]; then echo "❌ Application health check failed after 3+ minutes" echo "Last response: $HEALTH_RESPONSE" - docker logs spring-app --tail 100 + docker logs devnogi-batch-app-dev --tail 100 exit 1 fi done echo "=== Health Check Complete ===" - docker ps --filter "name=spring-app" + docker ps --filter "name=devnogi-batch-app-dev" EOF - name: Display deployment info diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index eced656..1bc20c0 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -139,11 +139,11 @@ jobs: echo "=== Starting Production Health Check ===" # 1. Check if container is running - CONTAINER_ID=$(docker ps -q --filter "name=spring-app") + CONTAINER_ID=$(docker ps -q --filter "name=devnogi-batch-app-prod") if [ -z "$CONTAINER_ID" ]; then echo "❌ Container not running" docker ps -a - docker logs spring-app --tail 50 + docker logs devnogi-batch-app-prod --tail 50 exit 1 fi echo "✅ Container is running (ID: $CONTAINER_ID)" @@ -151,7 +151,7 @@ jobs: # 2. Wait for Docker health check echo "Waiting for container to become healthy..." for i in {1..36}; do - HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' spring-app 2>/dev/null || echo "no-healthcheck") + HEALTH_STATUS=$(docker inspect --format='{{.State.Health.Status}}' devnogi-batch-app-prod 2>/dev/null || echo "no-healthcheck") if [ "$HEALTH_STATUS" == "healthy" ]; then echo "✅ Container is healthy" @@ -166,7 +166,7 @@ jobs: if [ $i -eq 36 ]; then echo "❌ Container failed to become healthy after 6 minutes" - docker logs spring-app --tail 100 + docker logs devnogi-batch-app-prod --tail 100 exit 1 fi done @@ -188,7 +188,7 @@ jobs: if [ $i -eq 30 ]; then echo "❌ Application health check failed after 5 minutes" echo "Last response: $HEALTH_RESPONSE" - docker logs spring-app --tail 100 + docker logs devnogi-batch-app-prod --tail 100 exit 1 fi done From 8056d8a13799340278ac968539bd13a4953ba298 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Tue, 20 Jan 2026 00:33:26 +0900 Subject: [PATCH 17/21] =?UTF-8?q?fix:=20push-cd-prod.yml=20=EC=98=A4?= =?UTF-8?q?=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 1bc20c0..eb7054c 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -204,7 +204,7 @@ jobs: fi echo "=== Health Check Complete ===" - docker ps --filter "name=spring-app" + docker ps --filter "name=devnogi-batch-app-prod" EOF - name: Display deployment info From 5c9aeab6c2e3161d53132558c5fa46093732f4db Mon Sep 17 00:00:00 2001 From: dev-ant Date: Tue, 20 Jan 2026 12:41:22 +0900 Subject: [PATCH 18/21] =?UTF-8?q?feat:=20docker-compose=20java=20opt=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=EA=B0=92=20=EB=B3=B4=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-dev.yml | 22 +++++++++++----------- docker-compose-local.yml | 22 +++++++++++----------- docker-compose-prod.yml | 22 +++++++++++----------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 240cb64..88871b7 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -53,19 +53,19 @@ services: # === JVM Configuration (Dev - 중간 리소스) === # Heap: 256m~512m, Non-Heap: ~262m, Total: ~774m JAVA_OPTS: >- - -Xms256m - -Xmx512m - -XX:MaxMetaspaceSize=150m - -XX:ReservedCodeCacheSize=48m - -XX:MaxDirectMemorySize=64m - -Xss512k + -Xms${JAVA_OPTS_XMS:-256m} + -Xmx${JAVA_OPTS_XMX:-512m} + -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE:-150m} + -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE:-48m} + -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE:-64m} + -Xss${JAVA_OPTS_XSS:-512k} -XX:+UseG1GC - -XX:MaxGCPauseMillis=200 - -XX:G1HeapRegionSize=2m - -XX:InitiatingHeapOccupancyPercent=45 + -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS:-200} + -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE:-2m} + -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT:-45} -XX:+TieredCompilation - -XX:TieredStopAtLevel=2 - -XX:CICompilerCount=2 + -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL:-2} + -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT:-2} -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -Djava.security.egd=file:/dev/./urandom diff --git a/docker-compose-local.yml b/docker-compose-local.yml index f61af38..4c99268 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -50,19 +50,19 @@ services: # === JVM Configuration (Local - 경량 개발용) === # Heap: 256m~512m, Non-Heap: 256m, Total: ~768m JAVA_OPTS: >- - -Xms256m - -Xmx512m - -XX:MaxMetaspaceSize=128m - -XX:ReservedCodeCacheSize=64m - -XX:MaxDirectMemorySize=64m - -Xss512k + -Xms${JAVA_OPTS_XMS:-256m} + -Xmx${JAVA_OPTS_XMX:-512m} + -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE:-150m} + -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE:-48m} + -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE:-64m} + -Xss${JAVA_OPTS_XSS:-512k} -XX:+UseG1GC - -XX:MaxGCPauseMillis=200 - -XX:G1HeapRegionSize=1m - -XX:InitiatingHeapOccupancyPercent=45 + -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS:-200} + -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE:-2m} + -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT:-45} -XX:+TieredCompilation - -XX:TieredStopAtLevel=1 - -XX:CICompilerCount=2 + -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL:-2} + -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT:-2} -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -Djava.security.egd=file:/dev/./urandom diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index 77d56f0..4b5eac7 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -49,19 +49,19 @@ services: # === JVM Configuration (Prod - 고성능, Dev의 3배) === # Heap: 768m~1536m, Non-Heap: ~786m, Total: ~2322m JAVA_OPTS: >- - -Xms768m - -Xmx1536m - -XX:MaxMetaspaceSize=450m - -XX:ReservedCodeCacheSize=144m - -XX:MaxDirectMemorySize=192m - -Xss512k + -Xms${JAVA_OPTS_XMS:-768m} + -Xmx${JAVA_OPTS_XMX:-1536m} + -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE:-450m} + -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE:-144m} + -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE:-192m} + -Xss${JAVA_OPTS_XSS:-512k} -XX:+UseG1GC - -XX:MaxGCPauseMillis=100 - -XX:G1HeapRegionSize=4m - -XX:InitiatingHeapOccupancyPercent=35 + -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS:-100} + -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE:-4m} + -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT:-35} -XX:+TieredCompilation - -XX:TieredStopAtLevel=4 - -XX:CICompilerCount=4 + -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL:-4} + -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT:-4} -XX:+UseCompressedOops -XX:+UseCompressedClassPointers -Djava.security.egd=file:/dev/./urandom From 4cdcbf37726ff0d9fe043fd6a771569822cd7a3b Mon Sep 17 00:00:00 2001 From: dev-ant Date: Tue, 20 Jan 2026 12:55:58 +0900 Subject: [PATCH 19/21] =?UTF-8?q?fix:=20push-cd-prod=20=EB=B0=B0=ED=8F=AC?= =?UTF-8?q?=20=EB=94=94=EB=A0=89=ED=84=B0=EB=A6=AC=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index eb7054c..73d1ee2 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -101,16 +101,16 @@ jobs: - name: Create app directory on server run: | - ssh -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} "mkdir -p /home/${{ secrets.PROD_SERVER_USER }}/app/logs" + ssh -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} "mkdir -p /opt/devnogi-batch/logs" - name: Copy docker-compose-prod.yml to server run: | - scp -P ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose-prod.yml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/home/${{ secrets.PROD_SERVER_USER }}/app/ + scp -P ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem docker-compose-prod.yml ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }}:/opt/devnogi-batch/ - name: Deploy to Production Server run: | ssh -i ~/.ssh/my-key.pem -p ${{ secrets.PROD_SERVER_PORT }} ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' - cd /home/${{ secrets.PROD_SERVER_USER }}/app + cd /opt/devnogi-batch # Write .env.prod content to .env echo "${{ secrets.ENV_FILE_PROD }}" > .env.prod From 6a605fbfa22a2e23c4b000cf68b0dad8ecdf7d31 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Tue, 20 Jan 2026 23:26:51 +0900 Subject: [PATCH 20/21] =?UTF-8?q?fix:=20push-cd-prod=20pull=20request=20tr?= =?UTF-8?q?igger=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 73d1ee2..0de8b8a 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -2,10 +2,7 @@ name: CI/CD for Production Server on: push: - branches: [ main, dev ] # Only main branch, 테스트 이후에 dev 지울 것 - pull_request: - branches: [ dev, main ] # 테스트 이후에 지울 것 - types: [ opened, synchronize, reopened ] + branches: [ main, dev ] # Only main branch, 서비스 시작 이후 dev 지울 것 permissions: contents: read From be42177e26fa32c171b933faa3482e30e50541dc Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Wed, 21 Jan 2026 00:47:26 +0900 Subject: [PATCH 21/21] =?UTF-8?q?feat:=20prod=20dev=20cd=20workflows=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-dev.yml | 26 +++++++++++++++++++++++++- .github/workflows/push-cd-prod.yml | 8 +------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.github/workflows/push-cd-dev.yml b/.github/workflows/push-cd-dev.yml index 568e3ad..fc80c01 100644 --- a/.github/workflows/push-cd-dev.yml +++ b/.github/workflows/push-cd-dev.yml @@ -82,7 +82,7 @@ jobs: - name: Setup SSH key and config run: | mkdir -p ~/.ssh - echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/my-key.pem + echo "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/my-key.pem chmod 400 ~/.ssh/my-key.pem ssh-keyscan -H ${{ secrets.SERVER_HOST }} >> ~/.ssh/known_hosts echo -e "Host *\n ServerAliveInterval 60\n ServerAliveCountMax 3" >> ~/.ssh/config @@ -103,6 +103,9 @@ jobs: # Write .env.dev content to .env.dev echo "${{ secrets.ENV_FILE_DEV }}" > .env.dev + # Docker Hub 로그인 + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + # Pull latest dev image docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:dev @@ -178,6 +181,16 @@ jobs: fi done + # 4. Smoke test: Check if API responds + echo "Running smoke test..." + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:${{ secrets.SERVER_PORT || 8080 }}/actuator/health) + if [ "$HTTP_CODE" == "200" ]; then + echo "✅ Smoke test passed (HTTP $HTTP_CODE)" + else + echo "❌ Smoke test failed (HTTP $HTTP_CODE)" + exit 1 + fi + echo "=== Health Check Complete ===" docker ps --filter "name=devnogi-batch-app-dev" EOF @@ -189,3 +202,14 @@ jobs: echo "🔗 Dev Server: http://${{ secrets.SERVER_HOST }}:${{ secrets.SERVER_PORT || 8080 }}" echo "🐳 Image: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:dev" echo "📦 Commit: ${{ github.sha }}" + + # ======================================== + # Rollback on Failure + # ======================================== + - name: Rollback on failure + if: failure() + run: | + echo "❌ Deployment failed! Consider manual rollback if needed." + echo "To rollback, SSH to server and run:" + echo " docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:dev-" + echo " docker compose -f docker-compose-dev.yml down && docker compose -f docker-compose-dev.yml --env-file .env.dev up -d" diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 0de8b8a..d5c3b31 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -2,7 +2,7 @@ name: CI/CD for Production Server on: push: - branches: [ main, dev ] # Only main branch, 서비스 시작 이후 dev 지울 것 + branches: [ main, dev ] # Only main branch, 서비스 시작 후 dev 제거 permissions: contents: read @@ -82,12 +82,6 @@ jobs: # ======================================== - name: Setup SSH key and config run: | - set -x - - echo "HOST=${{ secrets.PROD_SERVER_HOST }}" - echo "USER=${{ secrets.PROD_SERVER_USER }}" - echo "PORT=${{ secrets.PROD_SERVER_PORT }}" - mkdir -p ~/.ssh echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/my-key.pem chmod 400 ~/.ssh/my-key.pem