diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 071c993..ea10f1a 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1,12 +1,13 @@ name: Build and Deploy +# 조건 on: - workflow_dispatch: + workflow_dispatch: # 수동 실행 가능 push: - branches: ["main", "develop"] + branches: ["main", "develop"] # main, develop 브랜치 푸시 permissions: - contents: read + contents: read # 코드 읽기 jobs: build: @@ -27,7 +28,7 @@ jobs: java-version: '21' distribution: 'temurin' - # Gradle wrapper 실행 권한 부여 + # Gradle Wrapper 실행 권한 부여 - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -72,7 +73,7 @@ jobs: echo "REDIS_PASSWORD=${{ secrets.REDIS_DEV_PASSWORD }}" >> $GITHUB_OUTPUT fi - # docker-compose.server.yml 체크아웃 + # 배포 파일 체크아웃 - name: Checkout uses: actions/checkout@v4 with: @@ -94,7 +95,7 @@ jobs: fi chmod 600 ~/.ssh/deploy_key.pem - # EC2로 docker-compose.server.yml, server-deploy.sh 복사 + # EC2로 배포 파일 전송 - name: Prepare remote directories and copy files run: | ssh -o StrictHostKeyChecking=no -i ~/.ssh/deploy_key.pem ubuntu@${{ steps.deploy-env.outputs.EC2_HOST }} \ @@ -127,16 +128,16 @@ jobs: # SSH Key 정리 - name: Cleanup SSH Key - if: always() + if: always() # 항상 실행 run: | rm -f ~/.ssh/deploy_key.pem # 결과 알림 - name: Notify Discord - if: always() + if: always() # 항상 실행 uses: Ilshidur/action-discord@master env: - DISCORD_WEBHOOK: ${{ github.ref_name == 'main' && secrets.DISCORD_MAIN_WEBHOOK || secrets.DISCORD_DEV_WEBHOOK }} + DISCORD_WEBHOOK: ${{ github.ref_name == 'main' && secrets.DISCORD_PROD_WEBHOOK || secrets.DISCORD_DEV_WEBHOOK }} with: args: | 배포 결과: ${{ job.status == 'success' && '✅ 서버 배포 성공(Blue-Green 전환 완료)' || '❌ 서버 배포 실패 (기존 컨테이너 유지)' }} diff --git a/.github/workflows/TEST.yml b/.github/workflows/TEST.yml index 7e14059..84522e0 100644 --- a/.github/workflows/TEST.yml +++ b/.github/workflows/TEST.yml @@ -1,22 +1,25 @@ name: Test +# 조건 on: - workflow_dispatch: + workflow_dispatch: # 수동 실행 가능 push: - branches-ignore: + branches-ignore: # main, develop 제외한 모든 브랜치 푸시 - main - develop - pull_request: + pull_request: # main, develop으로 PR 생성 branches: ["main", "develop"] permissions: - contents: read - checks: write - pull-requests: write + contents: read # 코드 읽기 + checks: write # 테스트 결과 작성 + pull-requests: write # PR 코멘트 작성 jobs: build: runs-on: ubuntu-latest + # 브랜치 생성 직후 실행 방지 + if: github.event_name != 'push' || github.event.created == false steps: # 코드 체크아웃 (서브모듈 포함) @@ -34,13 +37,13 @@ jobs: java-version: '21' distribution: 'temurin' - # Redis 서비스 시작 + # 테스트용 Redis 실행 - name: Start Redis uses: supercharge/redis-github-action@1.7.0 with: redis-version: 7 - # Gradle 캐시 설정 + # Gradle 의존성 캐싱 - name: Cache Gradle uses: actions/cache@v3 with: @@ -51,25 +54,25 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - # Gradle wrapper 실행 권한 부여 + # Gradle Wrapper 실행 권한 부여 - name: Grant execute permission for gradlew run: chmod +x gradlew # 빌드 및 테스트 실행 - name: Build and Test run: ./gradlew clean build -Dspring.profiles.active=test - continue-on-error: true + continue-on-error: true # 테스트 실패해도 리포트 생성 위해 - # 테스트 결과 보고 + # 테스트 결과 리포트 생성 - name: Report Test Results uses: EnricoMi/publish-unit-test-result-action@v2 - if: always() + if: always() # 항상 실행 with: files: '**/build/test-results/test/TEST-*.xml' - # PR에 테스트 결과 코멘트 추가 + # PR에 테스트 결과 코멘트 - name: Comment Test Summary on PR uses: mikepenz/action-junit-report@v3 - if: always() + if: always() # 항상 실행 with: report_paths: '**/build/test-results/test/TEST-*.xml' \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4ebbd93..f09c2ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,19 @@ +# Java 21 런타임 이미지 FROM eclipse-temurin:21-jre-jammy +# 작업 디렉토리 WORKDIR /app +# 빌드된 JAR 파일 복사 COPY build/libs/*.jar app.jar +# 보안을 위한 전용 사용자 생성 RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser RUN chown -R appuser:appgroup /app USER appuser +# 애플리케이션 포트 설정 EXPOSE 8080 +# 애플리케이션 실행 ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1c8660f..fe5eb6f 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,7 @@ tasks.named('test') { useJUnitPlatform() } +// config 폴더의 .properties 파일을 src/main/resources로 복사 processResources.dependsOn('copyPrivateProperties') tasks.register("copyPrivateProperties", Copy) { from './config' diff --git a/deploy/docker-compose.infra.yml b/deploy/docker-compose.infra.yml index d5bd810..82d12a8 100644 --- a/deploy/docker-compose.infra.yml +++ b/deploy/docker-compose.infra.yml @@ -5,9 +5,9 @@ services: restart: unless-stopped command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes ports: - - "6379:6379" + - "6379:6379" # 호스트:컨테이너 volumes: - - redis-data:/data + - redis-data:/data # 데이터 저장 networks: - devops-study @@ -16,16 +16,18 @@ services: container_name: nginx_container restart: unless-stopped volumes: - - /home/ubuntu/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - - /home/ubuntu/nginx/logs:/var/log/nginx + - /home/ubuntu/nginx/nginx.conf:/etc/nginx/nginx.conf:ro # 설정 파일 (읽기 전용) + - /home/ubuntu/nginx/logs:/var/log/nginx # 로그 저장 ports: - - "80:80" + - "80:80" # 호스트:컨테이너 networks: - devops-study +# 외부 네트워크 (server.yml과 공유) networks: devops-study: external: true +# 볼륨 volumes: redis-data: \ No newline at end of file diff --git a/deploy/docker-compose.server.yml b/deploy/docker-compose.server.yml index de06560..adacade 100644 --- a/deploy/docker-compose.server.yml +++ b/deploy/docker-compose.server.yml @@ -13,7 +13,7 @@ services: - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD} ports: - - "8081:8080" + - "8081:8080" # 호스트:컨테이너 networks: - devops-study @@ -31,10 +31,11 @@ services: - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD} ports: - - "8082:8080" + - "8082:8080" # 호스트:컨테이너 networks: - devops-study +# 외부 네트워크 (infra.yml과 공유) networks: devops-study: external: true \ No newline at end of file diff --git a/deploy/nginx.conf b/deploy/nginx.conf index d7f1325..553aa2d 100644 --- a/deploy/nginx.conf +++ b/deploy/nginx.conf @@ -1,34 +1,38 @@ +# 워커 프로세스 수 (CPU 코어 수에 맞춰 자동 설정) worker_processes auto; events { + # 워커당 최대 동시 연결 수 worker_connections 1024; } http { - # 활성 서버 설정 + # 활성 서버 설정 (Blue-Green 배포 시 변경) upstream backend { server springboot_blue_container:8080; } server { + # HTTP 포트 listen 80; - # 메인 트래픽 + # 메인 트래픽 (모든 요청을 백엔드로 프록시) location / { - proxy_pass http://backend; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://backend; # upstream backend로 요청 전달 + + proxy_set_header Host $host; # 원본 도메인 전달 + proxy_set_header X-Real-IP $remote_addr; # 클라이언트 실제 IP 전달 + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 프록시 체인 전체 IP 기록 + proxy_set_header X-Forwarded-Proto $scheme; # 원본 프로토콜 전달 (http/https) } # 로그 설정 - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log warn; + access_log /var/log/nginx/access.log; # 접속 기록 + error_log /var/log/nginx/error.log warn; # 에러 기록 # 타임아웃 설정 - proxy_connect_timeout 5s; - proxy_read_timeout 30s; - proxy_send_timeout 30s; + proxy_connect_timeout 5s; # 백엔드 연결 타임아웃 + proxy_read_timeout 30s; # 응답 읽기 타임아웃 + proxy_send_timeout 30s; # 요청 전송 타임아웃 } } diff --git a/deploy/server-deploy.sh b/deploy/server-deploy.sh index 9f48e07..d2c5ae8 100644 --- a/deploy/server-deploy.sh +++ b/deploy/server-deploy.sh @@ -1,13 +1,13 @@ #!/bin/bash set -e -# 0. devops-study 네트워크 없으면 생성 +# Docker 네트워크(devops-study) 확인 및 생성 if ! docker network ls | grep -q devops-study; then echo "📦 Docker 네트워크 생성" docker network create devops-study fi -# 1. Redis, Nginx 미실행 시 실행 +# Redis, Nginx 실행 확인 및 시작 for SERVICE in redis nginx; do CONTAINER=$(docker ps --filter "name=${SERVICE}_container" --format "{{.Names}}") if [ -z "$CONTAINER" ]; then @@ -16,9 +16,10 @@ for SERVICE in redis nginx; do fi done -# 2. 실행 중인 컨테이너 확인 +# 현재 실행 중인 Spring Boot 컨테이너 확인 (Blue or Green) SPRINGBOOT_CONTAINER=$(docker ps --filter "name=springboot_" --format "{{.Names}}") +# Blue 실행 중 -> Green으로 배포 if [ "$SPRINGBOOT_CONTAINER" == "springboot_blue_container" ]; then NEW_PORT=8082 NEW_SERVICE="app_green" @@ -26,6 +27,7 @@ if [ "$SPRINGBOOT_CONTAINER" == "springboot_blue_container" ]; then OLD_SERVICE="app_blue" OLD_CONTAINER="springboot_blue_container" echo "🟢 Green(8082) 배포 시작" +# Green 실행 중 또는 없음 -> Blue로 배포 else NEW_PORT=8081 NEW_SERVICE="app_blue" @@ -35,10 +37,10 @@ else echo "🔵 Blue(8081) 배포 시작" fi -# 3. 이미지 Pull +# 이미지 Pull docker pull ${DOCKER_IMAGE} -# 4. 새 컨테이너 시작 +# 새 컨테이너 시작 docker compose -f /home/ubuntu/deploy/docker-compose.server.yml up -d --no-deps ${NEW_SERVICE} # 헬스체크 (최대 45초) @@ -58,20 +60,20 @@ for i in {1..9}; do sleep 5 done - -# 새로운 컨테이너 헬스체크 성공한 경우만 이후 단계 진행 -# 5. Nginx 설정 변경 +# 새로운 컨테이너 헬스체크 성공 시에만 진행 +# Nginx upstream 변경 (Blue <-> Green) if [ -n "$SPRINGBOOT_CONTAINER" ]; then + # 임시파일에 새로운 설정 내용 작성한 뒤 기존 파일에 덮어씌우기 (i-node 유지) sed "s/${OLD_CONTAINER}:8080/${NEW_CONTAINER}:8080/" /home/ubuntu/nginx/nginx.conf > /tmp/nginx_temp.conf cat /tmp/nginx_temp.conf > /home/ubuntu/nginx/nginx.conf rm /tmp/nginx_temp.conf - # 6. Nginx 리로드 + # Nginx 설정 리로드 docker exec nginx_container nginx -s reload - # 7. 이전 컨테이너 중지 + # 이전 컨테이너 중지 docker compose -f /home/ubuntu/deploy/docker-compose.server.yml stop ${OLD_SERVICE} fi -# 8. 리소스 정리 +# 미사용 리소스 정리 docker system prune -f > /dev/null 2>&1 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d7ef454..02c7d02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,10 +10,10 @@ services: MYSQL_PASSWORD: studentpw TZ: Asia/Seoul ports: - - "3306:3306" + - "3306:3306" # 호스트:컨테이너 volumes: - - ./db/mysql/data:/var/lib/mysql - - ./db/mysql/init:/docker-entrypoint-initdb.d + - ./db/mysql/data:/var/lib/mysql # 데이터 저장 +# - ./db/mysql/init:/docker-entrypoint-initdb.d # 초기화 스크립트 networks: - devops-study @@ -21,14 +21,15 @@ services: image: redis:7.0 container_name: redis_container restart: always - command: redis-server --requirepass studentpw + command: redis-server --requirepass studentpw --appendonly yes ports: - - "6379:6379" + - "6379:6379" # 호스트:컨테이너 volumes: - - ./db/redis/data:/data + - ./db/redis/data:/data # 데이터 저장 networks: - devops-study +# 네트워크 설정 networks: devops-study: driver: bridge \ No newline at end of file