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/.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 5cb5a7e..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 @@ -91,26 +91,29 @@ 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/ + 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: | 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 + + # 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 # 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 @@ -124,11 +127,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 +139,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 +154,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 +176,23 @@ 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 + # 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=spring-app" + docker ps --filter "name=devnogi-batch-app-dev" EOF - name: Display deployment info @@ -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 c1c7e89..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 ] # Only main branch + branches: [ main, dev ] # Only main branch, 서비스 시작 후 dev 제거 permissions: contents: read @@ -83,35 +83,40 @@ 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 + 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 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 /opt/devnogi-batch/logs" - - name: Copy docker-compose-dev.yaml to server + - name: Copy docker-compose-prod.yml 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-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 ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' - cd /home/${{ secrets.PROD_SERVER_USER }}/app + ssh -i ~/.ssh/my-key.pem -p ${{ secrets.PROD_SERVER_PORT }} ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} << 'EOF' + cd /opt/devnogi-batch # Write .env.prod content to .env - echo "${{ secrets.ENV_FILE_PROD }}" > .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 # 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 --env-file .env.prod up -d echo "✅ Production deployment complete" EOF @@ -121,15 +126,15 @@ 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 - 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)" @@ -137,7 +142,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" @@ -152,7 +157,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 @@ -160,7 +165,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" @@ -174,14 +179,14 @@ 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 # 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 @@ -190,14 +195,14 @@ 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 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" @@ -211,4 +216,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" diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml deleted file mode 100644 index 9216a24..0000000 --- a/docker-compose-dev.yaml +++ /dev/null @@ -1,137 +0,0 @@ -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 - - 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 diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 0000000..88871b7 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,130 @@ +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: + context: . + dockerfile: Dockerfile + image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-latest} + container_name: devnogi-batch-app-dev + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + env_file: + - .env.dev # 개발 환경 변수 파일 (DB, JWT, API 키 등 민감 정보) + + labels: + autoheal: "true" + environment: + # === Application Configuration === + SPRING_PROFILES_ACTIVE: dev + 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 (Dev - 중간 리소스) === + # Heap: 256m~512m, Non-Heap: ~262m, Total: ~774m + JAVA_OPTS: >- + -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=${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=${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 + -Dspring.jmx.enabled=false + volumes: + - app-logs:/app/logs + restart: on-failure:5 + + # === Docker Resource Limits (Dev - 중간) === + deploy: + resources: + limits: + memory: 750M + reservations: + memory: 512M + + networks: + - app-network + + # === Health Check (Dev - 표준) === + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT}/actuator/health"] + interval: 30s + timeout: 15s + retries: 4 + start_period: 120s + + # === Logging (Dev - 표준) === + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + + # === Autoheal (Dev - 표준) === + autoheal: + image: willfarrell/autoheal:latest + container_name: devnogi-batch-autoheal-dev + restart: unless-stopped + environment: + AUTOHEAL_INTERVAL: 30 # 체크 주기 (초) + AUTOHEAL_START_PERIOD: 0 # 시작 유예 시간 + AUTOHEAL_DEFAULT_STOP_TIMEOUT: 15 # graceful shutdown 대기 시간 (초) + DOCKER_SOCK: /var/run/docker.sock + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - app-network + deploy: + resources: + limits: + memory: 50M + reservations: + memory: 20M + +volumes: + app-logs: + driver: local + +networks: + app-network: + driver: bridge \ No newline at end of file diff --git a/docker-compose-local.yml b/docker-compose-local.yml index b1b9104..4c99268 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: @@ -11,16 +14,16 @@ 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: - - .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,20 +47,21 @@ 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: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=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS:-200} - -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE:-1m} + -XX:G1HeapRegionSize=${JAVA_OPTS_G1_HEAP_REGION_SIZE:-2m} -XX:InitiatingHeapOccupancyPercent=${JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT:-45} -XX:+TieredCompilation - -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL:-1} + -XX:TieredStopAtLevel=${JAVA_OPTS_TIERED_STOP_AT_LEVEL:-2} -XX:CICompilerCount=${JAVA_OPTS_CI_COMPILER_COUNT:-2} -XX:+UseCompressedOops -XX:+UseCompressedClassPointers @@ -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: autoheal-local + 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 new file mode 100644 index 0000000..4b5eac7 --- /dev/null +++ b/docker-compose-prod.yml @@ -0,0 +1,130 @@ +# ============================================================================= +# 운영 서버 환경용 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} + pull_policy: always + container_name: devnogi-batch-app-prod + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + env_file: + - .env.prod # 운영 환경 변수 파일 (DB, JWT, API 키 등 민감 정보) + + labels: + autoheal: "true" + environment: + # === Application Configuration === + SPRING_PROFILES_ACTIVE: prod + 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 (Prod - 고성능, Dev의 3배) === + # Heap: 768m~1536m, Non-Heap: ~786m, Total: ~2322m + JAVA_OPTS: >- + -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=${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=${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 + -Dspring.jmx.enabled=false + volumes: + - app-logs:/app/logs + restart: on-failure:5 + + # === Docker Resource Limits (Prod - 고성능, Dev의 3배) === + deploy: + resources: + limits: + memory: 2250M + reservations: + memory: 1536M + + networks: + - app-network + - devnogi-network # MySQL, Redis 등 인프라 컨테이너와 통신 + + # === Health Check (Prod - 빈번한 체크, 빠른 복구) === + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT}/actuator/health"] + interval: 15s # 더 자주 체크 (운영 안정성) + timeout: 10s # 더 짧은 타임아웃 (빠른 감지) + retries: 5 # 더 많은 재시도 (일시적 장애 허용) + start_period: 180s # 더 긴 시작 유예 (충분한 워밍업) + + # === Logging (Prod - 대용량, 장기 보관) === + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "5" + + # === Autoheal (Prod - 빈번한 체크, 긴 graceful shutdown) === + autoheal: + image: willfarrell/autoheal:latest + container_name: devnogi-batch-autoheal-prod + restart: unless-stopped + environment: + AUTOHEAL_INTERVAL: 15 # 더 자주 체크 (초) + AUTOHEAL_START_PERIOD: 0 # 시작 유예 시간 + AUTOHEAL_DEFAULT_STOP_TIMEOUT: 30 # 더 긴 graceful shutdown (초) + DOCKER_SOCK: /var/run/docker.sock + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - app-network + deploy: + resources: + limits: + memory: 75M + reservations: + memory: 30M + +volumes: + app-logs: + driver: local + +networks: + app-network: + driver: bridge + devnogi-network: + external: true + name: infra_devnogi-network # 기존 인프라 네트워크 (MySQL, Redis) \ No newline at end of file