From 39fe68777bc99efc68dba7197b1c0501a1d4d861 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Sat, 24 Jan 2026 13:30:36 +0900 Subject: [PATCH 1/6] =?UTF-8?q?infra:=20docker-compose=20=EB=B0=8F=20cicd?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=B4=ED=94=84=EB=9D=BC=EC=9D=B8=20=EA=B5=AC?= =?UTF-8?q?=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dev.sample | 89 ++++++++++++ .env.local.sample | 65 +++++++++ .env.prod.sample | 100 +++++++++++++ .github/workflows/push-cd-prod.yml | 219 +++++++++++++++++++++++++++++ Dockerfile | 79 ++--------- docker-compose-dev.yaml | 128 ----------------- docker-compose-dev.yml | 132 +++++++++++++++++ docker-compose-local.yml | 106 ++++++++------ docker-compose-prod.yml | 132 +++++++++++++++++ 9 files changed, 806 insertions(+), 244 deletions(-) create mode 100644 .env.dev.sample create mode 100644 .env.local.sample create mode 100644 .env.prod.sample create mode 100644 .github/workflows/push-cd-prod.yml delete mode 100644 docker-compose-dev.yaml create mode 100644 docker-compose-dev.yml create mode 100644 docker-compose-prod.yml diff --git a/.env.dev.sample b/.env.dev.sample new file mode 100644 index 0000000..9965baf --- /dev/null +++ b/.env.dev.sample @@ -0,0 +1,89 @@ +# ============================================================================= +# Development Server Environment Configuration +# ============================================================================= +# 개발 서버 환경용 설정 파일 (팀 테스트 및 검증용) +# +# 사용법: +# 1. 이 파일을 .env.dev로 복사하세요 +# 2. 필요한 값들을 채워넣으세요 +# 3. docker-compose -f docker-compose-dev.yml --env-file .env.dev up -d 로 실행하세요 +# ============================================================================= + +# ============================================================================= +# Application Configuration +# ============================================================================= +SERVER_PORT=8090 + +# ============================================================================= +# JWT Configuration (보안 민감) +# ============================================================================= +# IMPORTANT: 로컬/운영과 다른 키를 사용하세요! +JWT_SECRET_KEY=your-dev-jwt-secret-key-change-this +JWT_ISSUER=devnogi + +# ============================================================================= +# Service Discovery URLs (개발 서버 환경) +# ============================================================================= +# 개발 서버에서 다른 서비스들의 URL +AUTH_SERVER_URL=http://auth-service:8091 +OPEN_API_BATCH_SERVER_URL=http://batch-service:8093 +COMMUNITY_SERVER_URL=http://community-service:8092 +TECH_BLOG_SERVER_URL=http://blog-service:8094 + +# ============================================================================= +# CORS Configuration (개발 환경) +# ============================================================================= +CORS_ALLOWED_ORIGINS=http://dev.devnogi.com,http://localhost:5173 +CORS_ALLOWED_HEADERS=* +CORS_ALLOW_CREDENTIALS=true + +# ============================================================================= +# Logging Configuration (Dev - 디버그 레벨) +# ============================================================================= +LOG_LEVEL_ROOT=INFO +LOG_LEVEL_GATEWAY=DEBUG +LOG_LEVEL_NETTY=DEBUG + +# ============================================================================= +# Docker Configuration (필수) +# ============================================================================= +DOCKER_USERNAME=your-docker-username +DOCKER_REPO=devnogi-gateway-server +DOCKER_IMAGE_TAG=dev + +# ============================================================================= +# JVM Memory Configuration (Dev - 중간 리소스, 선택사항) +# docker-compose-dev.yml에 기본값이 있으므로 오버라이드 시에만 설정 +# ============================================================================= +# JAVA_OPTS_XMS=256m +# JAVA_OPTS_XMX=512m +# JAVA_OPTS_MAX_METASPACE_SIZE=150m +# JAVA_OPTS_RESERVED_CODE_CACHE_SIZE=48m +# JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE=64m +# JAVA_OPTS_XSS=512k +# JAVA_OPTS_MAX_GC_PAUSE_MILLIS=200 +# JAVA_OPTS_G1_HEAP_REGION_SIZE=2m +# JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT=45 +# JAVA_OPTS_TIERED_STOP_AT_LEVEL=2 +# JAVA_OPTS_CI_COMPILER_COUNT=2 + +# ============================================================================= +# Docker Container Resource Limits (Dev - 선택사항) +# ============================================================================= +# DOCKER_MEMORY_LIMIT=750M +# DOCKER_MEMORY_RESERVATION=512M + +# ============================================================================= +# Health Check Configuration (Dev - 선택사항) +# ============================================================================= +# HEALTHCHECK_INTERVAL=30s +# HEALTHCHECK_TIMEOUT=15s +# HEALTHCHECK_RETRIES=4 +# HEALTHCHECK_START_PERIOD=120s + +# ============================================================================= +# Autoheal Configuration (선택사항) +# ============================================================================= +# AUTOHEAL_INTERVAL=30 +# AUTOHEAL_START_PERIOD=0 +# AUTOHEAL_DEFAULT_STOP_TIMEOUT=15 diff --git a/.env.local.sample b/.env.local.sample new file mode 100644 index 0000000..5457f46 --- /dev/null +++ b/.env.local.sample @@ -0,0 +1,65 @@ +# ============================================================================= +# Local Development Environment Configuration +# ============================================================================= +# 로컬 개발 환경용 설정 파일 +# +# 사용법: +# 1. 이 파일을 .env.local로 복사하세요 +# 2. 필요한 값들을 채워넣으세요 +# 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=8090 + +# ============================================================================= +# JWT Configuration (보안 민감 - 로컬 개발용) +# ============================================================================= +# IMPORTANT: 운영 환경과 다른 키를 사용하세요! +JWT_SECRET_KEY=local-dev-secret-key-do-not-use-in-production-change-this-e4f1a5c8d2b7e9f0 +JWT_ISSUER=devnogi + +# ============================================================================= +# Service Discovery URLs (로컬 개발용) +# ============================================================================= +# 로컬에서 다른 서비스들이 실행 중인 경우의 URL +# Docker 컨테이너에서 호스트 머신 접근 시 host.docker.internal 사용 +AUTH_SERVER_URL=http://host.docker.internal:8091 +OPEN_API_BATCH_SERVER_URL=http://host.docker.internal:8093 +COMMUNITY_SERVER_URL=http://host.docker.internal:8092 +TECH_BLOG_SERVER_URL=http://host.docker.internal:8094 + +# ============================================================================= +# CORS Configuration (로컬 개발용) +# ============================================================================= +CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000 +CORS_ALLOWED_HEADERS=* +CORS_ALLOW_CREDENTIALS=true + +# ============================================================================= +# Logging Configuration (로컬 - 상세 로그) +# ============================================================================= +LOG_LEVEL_ROOT=INFO +LOG_LEVEL_GATEWAY=DEBUG +LOG_LEVEL_NETTY=DEBUG + +# ============================================================================= +# JVM Memory Configuration (Local - 경량, 선택사항) +# docker-compose-local.yml에 기본값이 있으므로 오버라이드 시에만 설정 +# ============================================================================= +# JAVA_OPTS_XMS=256m +# JAVA_OPTS_XMX=512m +# JAVA_OPTS_MAX_METASPACE_SIZE=150m +# JAVA_OPTS_RESERVED_CODE_CACHE_SIZE=48m +# JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE=64m +# JAVA_OPTS_XSS=512k +# JAVA_OPTS_MAX_GC_PAUSE_MILLIS=400 +# JAVA_OPTS_G1_HEAP_REGION_SIZE=2m +# JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT=45 +# JAVA_OPTS_TIERED_STOP_AT_LEVEL=2 +# JAVA_OPTS_CI_COMPILER_COUNT=2 diff --git a/.env.prod.sample b/.env.prod.sample new file mode 100644 index 0000000..1d161ee --- /dev/null +++ b/.env.prod.sample @@ -0,0 +1,100 @@ +# ============================================================================= +# Production Server Environment Configuration +# ============================================================================= +# 운영 서버 환경용 설정 파일 (고성능, Dev의 3배 리소스) +# +# 사용법: +# 1. 이 파일을 .env.prod로 복사하세요 +# 2. 필요한 값들을 채워넣으세요 (특히 보안 관련 값들은 반드시 변경!) +# 3. docker-compose -f docker-compose-prod.yml --env-file .env.prod up -d 로 실행하세요 +# +# IMPORTANT: 운영 환경에서는 반드시 강력한 비밀 키를 사용하세요! +# ============================================================================= + +# ============================================================================= +# Application Configuration +# ============================================================================= +SERVER_PORT=8090 + +# ============================================================================= +# JWT Configuration (보안 민감 - 반드시 변경!) +# ============================================================================= +# IMPORTANT: 운영 환경에서는 반드시 강력한 비밀 키를 사용하세요! +# 최소 64자 이상의 랜덤 문자열 권장 +JWT_SECRET_KEY=CHANGE_THIS_TO_A_STRONG_PRODUCTION_SECRET_KEY_AT_LEAST_64_CHARACTERS +JWT_ISSUER=devnogi + +# ============================================================================= +# Service Discovery URLs (운영 환경) +# ============================================================================= +# 운영 서버에서 다른 서비스들의 URL (Docker 네트워크 또는 실제 도메인) +AUTH_SERVER_URL=http://auth-service:8091 +OPEN_API_BATCH_SERVER_URL=http://batch-service:8093 +COMMUNITY_SERVER_URL=http://community-service:8092 +TECH_BLOG_SERVER_URL=http://blog-service:8094 + +# ============================================================================= +# CORS Configuration (운영 환경 - 도메인 제한) +# ============================================================================= +# 운영 환경에서는 실제 도메인만 허용하세요 +CORS_ALLOWED_ORIGINS=https://devnogi.com,https://www.devnogi.com +CORS_ALLOWED_HEADERS=* +CORS_ALLOW_CREDENTIALS=true + +# ============================================================================= +# Logging Configuration (Prod - 최소 로그) +# ============================================================================= +LOG_LEVEL_ROOT=INFO +LOG_LEVEL_GATEWAY=INFO +LOG_LEVEL_NETTY=INFO + +# ============================================================================= +# Docker Configuration (필수) +# ============================================================================= +DOCKER_USERNAME=your-docker-username +DOCKER_REPO=devnogi-gateway-server +DOCKER_IMAGE_TAG=prod + +# ============================================================================= +# JVM Memory Configuration (Prod - 고성능, 선택사항) +# docker-compose-prod.yml에 기본값이 있으므로 오버라이드 시에만 설정 +# Prod 기본값: Heap 768m~1536m, Total ~2322m +# ============================================================================= +# JAVA_OPTS_XMS=768m +# JAVA_OPTS_XMX=1536m +# JAVA_OPTS_MAX_METASPACE_SIZE=450m +# JAVA_OPTS_RESERVED_CODE_CACHE_SIZE=144m +# JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE=192m +# JAVA_OPTS_XSS=512k +# JAVA_OPTS_MAX_GC_PAUSE_MILLIS=100 +# JAVA_OPTS_G1_HEAP_REGION_SIZE=4m +# JAVA_OPTS_INITIATING_HEAP_OCCUPANCY_PERCENT=35 +# JAVA_OPTS_TIERED_STOP_AT_LEVEL=4 +# JAVA_OPTS_CI_COMPILER_COUNT=4 + +# ============================================================================= +# Docker Container Resource Limits (Prod - 선택사항) +# ============================================================================= +# DOCKER_MEMORY_LIMIT=2250M +# DOCKER_MEMORY_RESERVATION=1536M + +# ============================================================================= +# Health Check Configuration (Prod - 선택사항) +# ============================================================================= +# HEALTHCHECK_INTERVAL=15s +# HEALTHCHECK_TIMEOUT=10s +# HEALTHCHECK_RETRIES=5 +# HEALTHCHECK_START_PERIOD=180s + +# ============================================================================= +# Autoheal Configuration (선택사항) +# ============================================================================= +# AUTOHEAL_INTERVAL=15 +# AUTOHEAL_START_PERIOD=0 +# AUTOHEAL_DEFAULT_STOP_TIMEOUT=30 + +# ============================================================================= +# Logging Configuration (Prod - 대용량) +# ============================================================================= +# LOGGING_MAX_SIZE=50m +# LOGGING_MAX_FILE=5 diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml new file mode 100644 index 0000000..b956ca5 --- /dev/null +++ b/.github/workflows/push-cd-prod.yml @@ -0,0 +1,219 @@ +name: CI/CD for Production Server + +on: + push: + branches: [ main, dev ] # Only main branch, 서비스 시작 후 dev 제거 + +permissions: + contents: read + +jobs: + ci-cd-prod: + name: Test, Build, and Deploy to Production Server + runs-on: ubuntu-latest + + steps: + # ======================================== + # CI Stage: Test & Coverage + # ======================================== + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'gradle' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Grant execute permission for gradlew + run: chmod +x ./gradlew + + - name: Run Tests and Generate Coverage + run: ./gradlew clean test jacocoTestReport --no-daemon + + - name: Upload JaCoCo report to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: build/reports/jacoco/test/jacocoTestReport.xml + fail_ci_if_error: true # Stricter for production + flags: prod + verbose: true + + # ======================================== + # CD Stage: Build & Deploy + # ======================================== + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: | + ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:latest + ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod + ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod-${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 + + # ======================================== + # Deploy Stage: SSH & Deploy + # ======================================== + - name: Setup SSH key and config + run: | + mkdir -p ~/.ssh + echo "${{ secrets.PROD_SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/my-key.pem + chmod 400 ~/.ssh/my-key.pem + 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 -p ${{ secrets.PROD_SERVER_PORT }} -i ~/.ssh/my-key.pem ${{ secrets.PROD_SERVER_USER }}@${{ secrets.PROD_SERVER_HOST }} "mkdir -p /opt/devnogi-gateway/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 }}:/opt/devnogi-gateway/ + + - 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 /opt/devnogi-gateway + + # 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 + + # Stop and remove existing containers + docker compose -f docker-compose-prod.yml down + + # Start new containers + docker compose -f docker-compose-prod.yml --env-file .env.prod up -d + + echo "✅ Production deployment complete" + EOF + + # ======================================== + # Health Check Stage (Stricter for Production) + # ======================================== + - name: Comprehensive Health Check + run: | + 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=devnogi-gateway-app-prod") + if [ -z "$CONTAINER_ID" ]; then + echo "❌ Container not running" + docker ps -a + docker logs devnogi-gateway-app-prod --tail 50 + exit 1 + fi + echo "✅ Container is running (ID: $CONTAINER_ID)" + + # 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}}' devnogi-gateway-app-prod 2>/dev/null || echo "no-healthcheck") + + if [ "$HEALTH_STATUS" == "healthy" ]; then + echo "✅ Container is healthy" + break + elif [ "$HEALTH_STATUS" == "no-healthcheck" ]; then + echo "⚠️ No healthcheck configured, checking actuator directly" + break + fi + + echo "Current health status: $HEALTH_STATUS ($i/36)" + sleep 10 + + if [ $i -eq 36 ]; then + echo "❌ Container failed to become healthy after 6 minutes" + docker logs devnogi-gateway-app-prod --tail 100 + exit 1 + fi + done + + # 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_APP_PORT || 8090 }}/actuator/health || echo "") + + if echo "$HEALTH_RESPONSE" | grep -q '"status":"UP"'; then + echo "✅ Application health check passed" + echo "Health response: $HEALTH_RESPONSE" + break + fi + + echo "Waiting for application to start... ($i/30)" + sleep 10 + + if [ $i -eq 30 ]; then + echo "❌ Application health check failed after 5 minutes" + echo "Last response: $HEALTH_RESPONSE" + docker logs devnogi-gateway-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_APP_PORT || 8090 }}/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-gateway-app-prod" + EOF + + - name: Display deployment info + if: success() + run: | + echo "✅ Production deployment successful!" + echo "🔗 Production Server: http://${{ secrets.PROD_SERVER_HOST }}:${{ secrets.PROD_APP_PORT || 8090 }}" + echo "🐳 Image: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}:prod" + echo "📦 Commit: ${{ github.sha }}" + echo "⚠️ Please verify the production deployment manually" + + # ======================================== + # Rollback on Failure (Optional) + # ======================================== + - 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 }}:prod-" + 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/Dockerfile b/Dockerfile index ab424a3..0791544 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,76 +1,13 @@ -# Multi-Stage Dockerfile for Spring Cloud Gateway -# Stage 1: Build Stage - Gradle을 사용하여 애플리케이션 빌드 -# Stage 2: Extract Stage - Spring Boot Layered JAR 추출 -# Stage 3: Runtime Stage - 최종 런타임 이미지 +# Dockerfile +FROM openjdk:21-jdk-slim -# Stage 1: Build Stage -FROM gradle:8.5-jdk21 AS builder +# Install wget for healthcheck +RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* -# 작업 디렉토리 설정 WORKDIR /app -# Gradle 의존성 다운로드를 위한 파일만 먼저 복사 (레이어 캐싱 최적화) -COPY gradle gradle -COPY gradlew . -COPY build.gradle.kts . -COPY settings.gradle.kts . +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar -# gradle.properties가 없으면 빈 파일 생성 -RUN touch gradle.properties 2>/dev/null || true - -# 의존성 다운로드 (캐시 활용) -RUN gradle dependencies --no-daemon || true - -# 소스 코드 복사 -COPY src src - -# 애플리케이션 빌드 (테스트 제외) -RUN gradle clean bootJar -x test --no-daemon - -# JAR 파일 위치 확인 및 이름 변경 -RUN mkdir -p /app/build/extracted && \ - cp /app/build/libs/*.jar /app/build/app.jar - -# Stage 2: Extract Layers -FROM eclipse-temurin:21-jre-alpine AS extractor - -WORKDIR /app - -# 빌드된 JAR 파일 복사 -COPY --from=builder /app/build/app.jar app.jar - -# Spring Boot Layered JAR 추출 (레이어 최적화) -RUN java -Djarmode=layertools -jar app.jar extract - -# Stage 3: Final Runtime Stage -FROM eclipse-temurin:21-jre-alpine - -# 메타데이터 추가 -LABEL maintainer="DevNogi Team" -LABEL description="DevNogi Gateway Server - API Gateway with JWT Authentication" -LABEL version="0.0.1" - -# 보안: non-root 사용자 생성 -RUN addgroup -S spring && adduser -S spring -G spring - -# 작업 디렉토리 설정 -WORKDIR /app - -# 레이어별로 복사 (의존성 변경 시 캐시 활용) -COPY --from=extractor --chown=spring:spring /app/dependencies/ ./ -COPY --from=extractor --chown=spring:spring /app/spring-boot-loader/ ./ -COPY --from=extractor --chown=spring:spring /app/snapshot-dependencies/ ./ -COPY --from=extractor --chown=spring:spring /app/application/ ./ - -# 로그 디렉토리 생성 및 권한 설정 -RUN mkdir -p /app/logs /app/logs/archive && \ - chown -R spring:spring /app/logs - -# 사용자 전환 -USER spring:spring - -# JVM 메모리 설정 환경변수 (기본값, docker-compose에서 오버라이드) -ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200" - -# 애플리케이션 실행 (환경변수 JAVA_OPTS 사용) -ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"] +# JAVA_OPTS 환경변수를 통해 JVM 옵션 주입 가능 +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app/app.jar"] diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml deleted file mode 100644 index c21fd06..0000000 --- a/docker-compose-dev.yaml +++ /dev/null @@ -1,128 +0,0 @@ -version: "3.8" - -services: - gateway-app: - build: - context: . - dockerfile: Dockerfile - image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-latest} - container_name: gateway-app - ports: - - "${SERVER_PORT}:${SERVER_PORT}" - env_file: - - .env - labels: - # Autoheal: unhealthy 상태 시 자동 재시작 활성화 - autoheal: "true" - environment: - # === Application Configuration === - SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE} - LANG: C.UTF-8 - LC_ALL: C.UTF-8 - SERVER_PORT: ${SERVER_PORT} - - # === Downstream Service URLs === - AUTH_SERVER_URL: ${AUTH_SERVER_URL} - OPEN_API_BATCH_SERVER_URL: ${OPEN_API_BATCH_SERVER_URL} - COMMUNITY_SERVER_URL: ${COMMUNITY_SERVER_URL} - - # === Security Configuration === - JWT_SECRET_KEY: ${JWT_SECRET_KEY} - JWT_ISSUER: ${JWT_ISSUER} - - # === CORS Configuration === - CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS} - - # === 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: - - gateway-logs:/app/logs # Named volume 사용 (권한 문제 해결) - # 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: - - gateway-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 컨테이너 자동 재시작 서비스 - # - gateway-app이 unhealthy 상태가 되면 자동으로 재시작 - # - Docker 소켓을 마운트하여 컨테이너 관리 권한 획득 - # - healthcheck와 독립적으로 동작 (healthcheck가 unhealthy 판정하면 autoheal이 재시작) - autoheal: - image: willfarrell/autoheal:latest - container_name: autoheal-gateway - 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: - - gateway-network - # autoheal은 매우 가벼운 서비스 (메모리 ~10MB) - deploy: - resources: - limits: - memory: ${AUTOHEAL_MEMORY_LIMIT} - reservations: - memory: ${AUTOHEAL_MEMORY_RESERVATION} - -volumes: - gateway-logs: - driver: local - -networks: - gateway-network: - driver: bridge diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 0000000..bba5127 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,132 @@ +version: "3.8" + +# ============================================================================= +# 개발 서버 환경용 Docker Compose 설정 (Profile: dev) +# ============================================================================= +# 사용법: docker-compose -f docker-compose-dev.yml --env-file .env.dev up -d +# 중간 수준의 리소스 할당 (팀 테스트 및 검증용) +# ============================================================================= + +services: + devnogi-gateway-dev: + build: + context: . + dockerfile: Dockerfile + image: ${DOCKER_USERNAME}/${DOCKER_REPO}:${DOCKER_IMAGE_TAG:-latest} + container_name: devnogi-gateway-app-dev + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + env_file: + - .env.dev # 개발 환경 변수 파일 (JWT, 서비스 URL 등 민감 정보) + + labels: + autoheal: "true" + environment: + # === Application Configuration === + SPRING_PROFILES_ACTIVE: dev + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + SERVER_PORT: ${SERVER_PORT} + + # === JWT Configuration === + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_ISSUER: ${JWT_ISSUER} + + # === Service Discovery URLs === + AUTH_SERVER_URL: ${AUTH_SERVER_URL} + OPEN_API_BATCH_SERVER_URL: ${OPEN_API_BATCH_SERVER_URL} + COMMUNITY_SERVER_URL: ${COMMUNITY_SERVER_URL} + TECH_BLOG_SERVER_URL: ${TECH_BLOG_SERVER_URL} + + # === CORS Configuration === + CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS} + CORS_ALLOWED_HEADERS: ${CORS_ALLOWED_HEADERS:-*} + CORS_ALLOW_CREDENTIALS: ${CORS_ALLOW_CREDENTIALS:-true} + + # === Logging Configuration === + LOG_LEVEL_ROOT: ${LOG_LEVEL_ROOT:-INFO} + LOG_LEVEL_GATEWAY: ${LOG_LEVEL_GATEWAY:-DEBUG} + LOG_LEVEL_NETTY: ${LOG_LEVEL_NETTY:-DEBUG} + + # === 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-gateway-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 diff --git a/docker-compose-local.yml b/docker-compose-local.yml index d7018ea..859d559 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -1,115 +1,131 @@ 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: - gateway-app: + spring-app: build: context: . dockerfile: Dockerfile # 로컬 이미지 이름 (Docker Hub에 push하지 않음) image: devnogi-gateway-server:local - container_name: gateway-app-local + container_name: devnogi-gateway-app-local ports: - "${SERVER_PORT:-8090}:${SERVER_PORT:-8090}" env_file: - - .env.local # 로컬 환경 변수 파일 + - .env.local # 로컬 환경 변수 파일 (JWT, 서비스 URL 등 민감 정보) 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:-8090} - # === Downstream Service URLs (로컬 개발 시 호스트의 다른 서비스 연결) === + # === JWT Configuration === + JWT_SECRET_KEY: ${JWT_SECRET_KEY:-local-dev-secret-key-change-in-production} + JWT_ISSUER: ${JWT_ISSUER:-devnogi} + + # === Service Discovery URLs === AUTH_SERVER_URL: ${AUTH_SERVER_URL:-http://host.docker.internal:8091} - COMMUNITY_SERVER_URL: ${COMMUNITY_SERVER_URL:-http://host.docker.internal:8092} OPEN_API_BATCH_SERVER_URL: ${OPEN_API_BATCH_SERVER_URL:-http://host.docker.internal:8093} - - # === Security Configuration === - JWT_SECRET_KEY: ${JWT_SECRET_KEY:-e4f1a5c8d2b7e9f0a6c3d1b8e5f2c7a9d4e6f3b1a2c8d5e9f0b3a7c2d1e8f5a4} - JWT_ISSUER: ${JWT_ISSUER:-devnogi} + COMMUNITY_SERVER_URL: ${COMMUNITY_SERVER_URL:-http://host.docker.internal:8092} + TECH_BLOG_SERVER_URL: ${TECH_BLOG_SERVER_URL:-http://host.docker.internal:8094} # === CORS Configuration === - CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-*} + CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:5173} + CORS_ALLOWED_HEADERS: ${CORS_ALLOWED_HEADERS:-*} + CORS_ALLOW_CREDENTIALS: ${CORS_ALLOW_CREDENTIALS:-true} + + # === Logging Configuration === + LOG_LEVEL_ROOT: ${LOG_LEVEL_ROOT:-INFO} + LOG_LEVEL_GATEWAY: ${LOG_LEVEL_GATEWAY:-DEBUG} + LOG_LEVEL_NETTY: ${LOG_LEVEL_NETTY:-DEBUG} - # === JVM Configuration (로컬 개발용 - 메모리 사용량 감소) === + # === JVM Configuration (Local - 경량 개발용) === + # Heap: 256m~512m, Non-Heap: ~256m, Total: ~768m JAVA_OPTS: >- - -Xms${JAVA_OPTS_XMS:-128m} - -Xmx${JAVA_OPTS_XMX:-256m} - -XX:MaxMetaspaceSize=${JAVA_OPTS_MAX_METASPACE_SIZE:-128m} - -XX:ReservedCodeCacheSize=${JAVA_OPTS_RESERVED_CODE_CACHE_SIZE:-32m} - -XX:MaxDirectMemorySize=${JAVA_OPTS_MAX_DIRECT_MEMORY_SIZE:-32m} + -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:-1m} + -XX:MaxGCPauseMillis=${JAVA_OPTS_MAX_GC_PAUSE_MILLIS:-400} + -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 -Djava.security.egd=file:/dev/./urandom -Dspring.jmx.enabled=false volumes: - - gateway-logs:/app/logs # Named volume 사용 (권한 문제 해결) - restart: on-failure:${RESTART_POLICY_MAX_RETRIES:-3} + - app-logs:/app/logs + # 로컬 개발 시 설정 파일 마운트 (선택사항) + # - ./config:/app/config:ro + restart: on-failure:3 - # 로컬 개발용 리소스 제한 (더 적은 리소스) + # === Docker Resource Limits (Local - 경량) === deploy: resources: limits: - memory: ${DOCKER_MEMORY_LIMIT:-512m} + memory: 768m reservations: - memory: ${DOCKER_MEMORY_RESERVATION:-256m} + memory: 384m networks: - - gateway-network + - app-network - # Health Check + # === Health Check (Local - 표준) === healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT:-8090}/actuator/health"] - interval: ${HEALTHCHECK_INTERVAL:-30s} - timeout: ${HEALTHCHECK_TIMEOUT:-10s} - retries: ${HEALTHCHECK_RETRIES:-3} - start_period: ${HEALTHCHECK_START_PERIOD:-60s} + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + # === 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" - # Autoheal: unhealthy 컨테이너 자동 재시작 + # === Autoheal (Local - 표준) === + # unhealthy 컨테이너 자동 재시작 서비스 autoheal: image: willfarrell/autoheal:latest - container_name: autoheal-gateway-local + container_name: devnogi-gateway-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 networks: - - gateway-network + - app-network deploy: resources: limits: - memory: ${AUTOHEAL_MEMORY_LIMIT:-50m} + memory: 50m reservations: - memory: ${AUTOHEAL_MEMORY_RESERVATION:-20m} + memory: 20m volumes: - gateway-logs: + app-logs: driver: local networks: - gateway-network: + app-network: driver: bridge diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml new file mode 100644 index 0000000..c8f5b84 --- /dev/null +++ b/docker-compose-prod.yml @@ -0,0 +1,132 @@ +# ============================================================================= +# 운영 서버 환경용 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-gateway-app-prod + ports: + - "${SERVER_PORT}:${SERVER_PORT}" + env_file: + - .env.prod # 운영 환경 변수 파일 (JWT, 서비스 URL 등 민감 정보) + + labels: + autoheal: "true" + environment: + # === Application Configuration === + SPRING_PROFILES_ACTIVE: prod + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + SERVER_PORT: ${SERVER_PORT} + + # === JWT Configuration === + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_ISSUER: ${JWT_ISSUER} + + # === Service Discovery URLs === + AUTH_SERVER_URL: ${AUTH_SERVER_URL} + OPEN_API_BATCH_SERVER_URL: ${OPEN_API_BATCH_SERVER_URL} + COMMUNITY_SERVER_URL: ${COMMUNITY_SERVER_URL} + TECH_BLOG_SERVER_URL: ${TECH_BLOG_SERVER_URL} + + # === CORS Configuration === + CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS} + CORS_ALLOWED_HEADERS: ${CORS_ALLOWED_HEADERS:-*} + CORS_ALLOW_CREDENTIALS: ${CORS_ALLOW_CREDENTIALS:-true} + + # === Logging Configuration === + LOG_LEVEL_ROOT: ${LOG_LEVEL_ROOT:-INFO} + LOG_LEVEL_GATEWAY: ${LOG_LEVEL_GATEWAY:-INFO} + LOG_LEVEL_NETTY: ${LOG_LEVEL_NETTY:-INFO} + + # === 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 # 다른 서비스 컨테이너와 통신 + + # === 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-gateway-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 # 기존 인프라 네트워크 (다른 서비스들과 통신) From b269af41e30d37bcc00fcb49b6448361e8e8ca2d Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Sat, 24 Jan 2026 13:33:12 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20prod-cd=20codecov=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-prod.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index b956ca5..1403a7b 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -39,17 +39,17 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x ./gradlew - - name: Run Tests and Generate Coverage - run: ./gradlew clean test jacocoTestReport --no-daemon - - - name: Upload JaCoCo report to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: build/reports/jacoco/test/jacocoTestReport.xml - fail_ci_if_error: true # Stricter for production - flags: prod - verbose: true +# - name: Run Tests and Generate Coverage +# run: ./gradlew clean test jacocoTestReport --no-daemon +# +# - name: Upload JaCoCo report to Codecov +# uses: codecov/codecov-action@v4 +# with: +# token: ${{ secrets.CODECOV_TOKEN }} +# files: build/reports/jacoco/test/jacocoTestReport.xml +# fail_ci_if_error: true # Stricter for production +# flags: prod +# verbose: true # ======================================== # CD Stage: Build & Deploy From 6ba303b193c997bb35e331b21d15ccaeab465483 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Sat, 24 Jan 2026 13:43:25 +0900 Subject: [PATCH 3/6] =?UTF-8?q?infra:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=EC=9C=BC=EB=A1=9C=20cd=20trigger=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/push-cd-dev.yml | 5 ++--- .github/workflows/push-cd-prod.yml | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/push-cd-dev.yml b/.github/workflows/push-cd-dev.yml index 13303bb..b31653c 100644 --- a/.github/workflows/push-cd-dev.yml +++ b/.github/workflows/push-cd-dev.yml @@ -1,9 +1,8 @@ name: Spring CD for Dev Push on: - pull_request: - branches: [ dev, main ] - types: [ opened, synchronize, reopened ] + push: + branches: [ dev ] # Only main branch, 서비스 시작 후 dev 제거 permissions: contents: read diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 1403a7b..3c562d3 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -1,8 +1,9 @@ name: CI/CD for Production Server on: - push: - branches: [ main, dev ] # Only main branch, 서비스 시작 후 dev 제거 + pull_request: + branches: [ dev, main ] + types: [ opened, synchronize, reopened ] # 테스트용 permissions: contents: read From cac6f397590a6b84b0d4eb3b5f137fcccf2208ab Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Sat, 24 Jan 2026 13:50:39 +0900 Subject: [PATCH 4/6] =?UTF-8?q?infra:=20dockerfile=20multistage=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 50 +++++++++++++++++++++++++++++++++++----- docker-compose-dev.yml | 2 +- docker-compose-local.yml | 2 +- docker-compose-prod.yml | 2 +- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0791544..851cda4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,51 @@ -# Dockerfile -FROM openjdk:21-jdk-slim +# ======================================== +# Stage 1: Build +# ======================================== +FROM eclipse-temurin:21-jdk-alpine AS builder -# Install wget for healthcheck -RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* +WORKDIR /app + +# Gradle wrapper 및 설정 파일 먼저 복사 (의존성 캐싱) +COPY gradlew . +COPY gradle gradle +COPY build.gradle.kts . +COPY settings.gradle.kts . + +# 실행 권한 부여 +RUN chmod +x ./gradlew + +# 의존성 다운로드 (캐싱 레이어) +RUN ./gradlew dependencies --no-daemon || true + +# 소스 코드 복사 +COPY src src + +# 애플리케이션 빌드 (테스트 제외) +RUN ./gradlew bootJar --no-daemon -x test + +# ======================================== +# Stage 2: Runtime +# ======================================== +FROM eclipse-temurin:21-jre-alpine + +# curl 설치 (healthcheck용, wget보다 가벼움) +RUN apk add --no-cache curl WORKDIR /app -ARG JAR_FILE=build/libs/*.jar -COPY ${JAR_FILE} app.jar +# 빌드된 JAR 복사 +COPY --from=builder /app/build/libs/*.jar app.jar + +# 비root 사용자로 실행 (보안) +RUN addgroup -g 1000 appgroup && \ + adduser -u 1000 -G appgroup -D appuser && \ + chown -R appuser:appgroup /app + +USER appuser + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8080/actuator/health || exit 1 # JAVA_OPTS 환경변수를 통해 JVM 옵션 주입 가능 ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app/app.jar"] diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index bba5127..13589d8 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -89,7 +89,7 @@ services: # === Health Check (Dev - 표준) === healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT}/actuator/health"] + test: ["CMD", "curl", "-f", "http://localhost:${SERVER_PORT}/actuator/health"] interval: 30s timeout: 15s retries: 4 diff --git a/docker-compose-local.yml b/docker-compose-local.yml index 859d559..49a6da0 100644 --- a/docker-compose-local.yml +++ b/docker-compose-local.yml @@ -87,7 +87,7 @@ services: # === Health Check (Local - 표준) === healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT:-8090}/actuator/health"] + test: ["CMD", "curl", "-f", "http://localhost:${SERVER_PORT:-8090}/actuator/health"] interval: 30s timeout: 10s retries: 3 diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index c8f5b84..3ce945c 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -86,7 +86,7 @@ services: # === Health Check (Prod - 빈번한 체크, 빠른 복구) === healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:${SERVER_PORT}/actuator/health"] + test: ["CMD", "curl", "-f", "http://localhost:${SERVER_PORT}/actuator/health"] interval: 15s # 더 자주 체크 (운영 안정성) timeout: 10s # 더 짧은 타임아웃 (빠른 감지) retries: 5 # 더 많은 재시도 (일시적 장애 허용) From a7d1000aee2d4628480d4c93aaaebb246d9c9ec3 Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Sat, 24 Jan 2026 14:00:46 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20appuser=EC=97=90=EA=B2=8C=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=ED=8C=8C=EC=9D=BC=20=EC=93=B8=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=20=EB=B6=80=EC=97=AC=20docker=20file=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 851cda4..7f81a64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,9 @@ WORKDIR /app # 빌드된 JAR 복사 COPY --from=builder /app/build/libs/*.jar app.jar +# 로그 디렉토리 생성 +RUN mkdir -p /app/logs + # 비root 사용자로 실행 (보안) RUN addgroup -g 1000 appgroup && \ adduser -u 1000 -G appgroup -D appuser && \ From a28735454c20a0af53c2644f742b62848989609a Mon Sep 17 00:00:00 2001 From: Sanghyun Yi Date: Mon, 26 Jan 2026 22:29:18 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20push=20cd=20prod=20trigger=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, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push-cd-prod.yml b/.github/workflows/push-cd-prod.yml index 3c562d3..ed616b3 100644 --- a/.github/workflows/push-cd-prod.yml +++ b/.github/workflows/push-cd-prod.yml @@ -1,9 +1,8 @@ name: CI/CD for Production Server on: - pull_request: - branches: [ dev, main ] - types: [ opened, synchronize, reopened ] # 테스트용 + push: + branches: [ main ] # Only main branch, 서비스 시작 후 dev 제거 permissions: contents: read