diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 39598c3fb..5898fdcb0 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -1,6 +1,7 @@ name: Backend CI on: + workflow_call: pull_request: branches: [ backend ] workflow_dispatch: diff --git a/.github/workflows/backend-dev-cd.yml b/.github/workflows/backend-dev-cd.yml index f2e3ec2eb..d5f26a8b7 100644 --- a/.github/workflows/backend-dev-cd.yml +++ b/.github/workflows/backend-dev-cd.yml @@ -6,8 +6,14 @@ on: workflow_dispatch: jobs: + ci: + name: CI 실행 + uses: ./.github/workflows/backend-ci.yml + secrets: inherit + build: name: 도커 이미지 빌드 & 푸시 + needs: ci runs-on: ubuntu-latest defaults: run: @@ -24,16 +30,11 @@ jobs: distribution: 'corretto' cache: 'gradle' - - name: 프로젝트 gradlew 빌드 - run: ./gradlew build + - name: gradlew 실행 권한 부여 + run: chmod +x gradlew - - name: 테스트 결과 보고서 작성 - uses: dorny/test-reporter@v1 - if: success() || failure() - with: - name: 테스트 결과 - path: backend/build/test-results/test/*.xml - reporter: java-junit + - name: 프로젝트 gradlew 빌드 + run: ./gradlew clean build -x test - name: Docker 로그인 uses: docker/login-action@v3 @@ -52,49 +53,47 @@ jobs: deploy: name: 개발서버 배포 needs: build - runs-on: [ self-hosted, dev-app ] - defaults: - run: - working-directory: /home/ssm-user/coursepick + runs-on: ubuntu-latest steps: - - name: Docker 로그인 - uses: docker/login-action@v3 + - name: SSH로 배포 스크립트 실행 + uses: appleboy/ssh-action@v1.0.0 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: .env 파일 생성 - run: | - cat > .env <<'EOF' - ${{ secrets.DEV_ENV_FILE }} - EOF - chmod 600 .env - - - name: 새로운 서버 시작 - run: | - sudo docker compose --env-file .env up -d --pull always backend - - - name: 사용하지 않는 도커 이미지 정리 - run: | - sudo docker image prune -a -f - - - name: 시작된 서버 헬스체크 - run: | - for i in $(seq 1 10) - do - SERVER_STATUS=$(curl -o /dev/null -w "%{http_code}" http://localhost:80/actuator/health || true) - - if [ "$SERVER_STATUS" -eq 200 ]; then - echo "서버 정상적으로 실행됨 status=$SERVER_STATUS" - break - else - echo "헬스체크에 실패함. 5초 후 다시 시도" - sleep 5 - fi - - if [ $i -eq 10 ]; then - echo "서버가 정상적으로 실행되지 않아 종료함" - exit 1 - fi - done + host: ${{ secrets.DEV_SERVER_HOST }} + username: ubuntu + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /home/ubuntu/coursepick + + echo "🚀 개발 서버 배포 시작..." + + echo "📦 최신 이미지 Pull..." + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/coursepick-backend:dev + + echo "🌳 .env 파일 생성..." + cat > .env <<'EOF' + ${{ secrets.DEV_ENV_FILE }} + EOF + chmod 600 .env + + echo "▶️ 새 서버 시작..." + sudo docker compose --env-file .env up -d --pull always backend + + echo "🧹 사용하지 않는 이미지 정리..." + sudo docker image prune -a -f + + echo "⛑️ 헬스 체크..." + for i in $(seq 1 10); do + SERVER_STATUS=$(curl -o /dev/null -w "%{http_code}" http://localhost:80/actuator/health || echo "000") + if [ "$SERVER_STATUS" -eq 200 ]; then + echo "✅ 배포 성공! 서버 동작 중..." + exit 0 + else + echo "Attempt $i/10: 헬스 체크 실패 (status=$SERVER_STATUS). 5초 후 재시도..." + sleep 5 + fi + done + + echo "❌ 배포 실패! 서버가 동작하지 않습니다." + sudo docker logs coursepick-backend --tail 50 + exit 1 diff --git a/.github/workflows/backend-prod-app-cd.yml b/.github/workflows/backend-prod-app-cd.yml deleted file mode 100644 index 3d728e7bd..000000000 --- a/.github/workflows/backend-prod-app-cd.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Backend Prod App CD - -on: - workflow_call: - inputs: - run_id: - required: true - type: string - main_runner: - required: true - type: string - sub_runner: - required: true - type: string - -jobs: - deploy-prod-main: - name: main runner에 서버 배포 - runs-on: [ self-hosted, "${{ inputs.main_runner }}" ] - defaults: - run: - working-directory: /home/ssm-user/coursepick - steps: - - name: Docker 로그인 - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: .env 파일 생성 - working-directory: /home/ssm-user/coursepick - run: | - cat > .env <<'EOF' - ${{ secrets.PROD_ENV_FILE }} - EOF - chmod 600 .env - - - name: 새로운 서버 시작 - working-directory: /home/ssm-user/coursepick - run: | - sudo docker compose --env-file .env up -d --pull always backend - - - name: 시작된 서버 헬스체크 - run: | - for i in $(seq 1 10) - do - SERVER_STATUS=$(curl -o /dev/null -w "%{http_code}" http://localhost:80/actuator/health || true) - - if [ "$SERVER_STATUS" -eq 200 ]; then - echo "서버 정상적으로 실행됨 status=$SERVER_STATUS" - break - else - echo "헬스체크에 실패함. 5초 후 다시 시도" - sleep 5 - fi - - if [ $i -eq 10 ]; then - echo "서버가 정상적으로 실행되지 않아 종료함" - exit 1 - fi - done - - update-nginx-to-prod-main: - name: nginx 로드밸런서 설정 파일 main runner 설정으로 변경 - needs: [ deploy-prod-main ] - if: ${{ needs.deploy-prod-main.result == 'success' }} - runs-on: [ self-hosted, prod-lb ] - steps: - - name: upstream 갱신, 리로드 - run: | - sudo ln -sfn /etc/nginx/snippets/upstream.${{ inputs.main_runner }} /etc/nginx/snippets/upstream.active - sudo nginx -t && sudo systemctl reload nginx - - shutdown-prod-sub: - name: sub runner 서버 종료 - needs: [ update-nginx-to-prod-main ] - runs-on: [ self-hosted, "${{ inputs.sub_runner }}" ] - defaults: - run: - working-directory: /home/ssm-user/coursepick - steps: - - name: 기존 서버 내리기 - working-directory: /home/ssm-user/coursepick - run: | - sudo docker compose down - sudo docker image prune -a -f diff --git a/.github/workflows/backend-prod-cd.yml b/.github/workflows/backend-prod-cd.yml index da9adc630..9fa51526d 100644 --- a/.github/workflows/backend-prod-cd.yml +++ b/.github/workflows/backend-prod-cd.yml @@ -9,8 +9,14 @@ on: workflow_dispatch: jobs: + ci: + name: CI 실행 + uses: ./.github/workflows/backend-ci.yml + secrets: inherit + build: - name: 프로젝트 빌드 + name: 도커 이미지 빌드 & 푸시 + needs: ci runs-on: ubuntu-latest defaults: run: @@ -27,16 +33,11 @@ jobs: distribution: 'corretto' cache: 'gradle' - - name: 프로젝트 gradlew 빌드 - run: ./gradlew build + - name: gradlew 실행 권한 부여 + run: chmod +x gradlew - - name: 테스트 결과 보고서 작성 - uses: dorny/test-reporter@v1 - if: success() || failure() - with: - name: 테스트 결과 - path: backend/build/test-results/test/*.xml - reporter: java-junit + - name: 프로젝트 gradlew 빌드 + run: ./gradlew clean build -x test - name: Docker 로그인 uses: docker/login-action@v3 @@ -52,68 +53,50 @@ jobs: --push \ . - # 1) 유휴 서버 탐지 - detect-idle: - name: 쉬고있는 EC2 서버 탐지 + deploy: + name: 운영서버 배포 needs: build - runs-on: [ self-hosted, prod-lb ] - outputs: - idle-server: ${{ steps.find.outputs.idle }} - steps: - - name: 탐침 스크립트 실행 - id: find - run: | - declare -A servers=( - [prod-app-1]="${{ secrets.PROD_APP_1_HEALTH_URL }}" - [prod-app-2]="${{ secrets.PROD_APP_2_HEALTH_URL }}" - ) - - idle_servers=() - - for name in "${!servers[@]}"; do - url="${servers[$name]}" - code=$(curl -s -o /dev/null -w "%{http_code}" "$url" || echo "") - if [[ "$code" -eq 200 ]]; then - echo "$name 응답 성공" - else - echo "$name 응답 실패" - idle_servers+=("$name") - fi - done - - if [[ ${#idle_servers[@]} -eq 0 ]]; then - echo "Both servers are healthy – cannot find idle server" - elif [[ ${#idle_servers[@]} -eq 1 ]]; then - idle="${idle_servers[0]}" - echo "One server is idle, idle=$idle" - echo "idle=$idle" >> $GITHUB_OUTPUT - else - idle="prod-app-1" - echo "None servers are idle – as default, deploy to server1, idle=$idle" - echo "idle=$idle" >> $GITHUB_OUTPUT - fi - - echo "idle=$idle" >> $GITHUB_OUTPUT - - # 2) 유휴 서버에 배포 (server1 / server2 중 하나만 실행) - deploy-prod-app-1: - name: prod-app-1 서버에 배포 - needs: detect-idle - if: needs.detect-idle.outputs.idle-server == 'prod-app-1' - uses: ./.github/workflows/backend-prod-app-cd.yml - with: - run_id: ${{ github.run_id }} - main_runner: prod-app-1 - sub_runner: prod-app-2 - secrets: inherit + runs-on: ubuntu-latest - deploy-prod-app-2: - name: prod-app-2 서버에 배포 - needs: detect-idle - if: needs.detect-idle.outputs.idle-server == 'prod-app-2' - uses: ./.github/workflows/backend-prod-app-cd.yml - with: - run_id: ${{ github.run_id }} - main_runner: prod-app-2 - sub_runner: prod-app-1 - secrets: inherit + steps: + - name: SSH로 배포 스크립트 실행 + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PROD_SERVER_HOST }} + username: ubuntu + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + cd /home/ubuntu/coursepick + + echo "🚀 운영 서버 배포 시작..." + + echo "📦 최신 이미지 Pull..." + sudo docker pull ${{ secrets.DOCKER_USERNAME }}/coursepick-backend:prod + + echo "🌳 .env 파일 생성..." + cat > .env <<'EOF' + ${{ secrets.PROD_ENV_FILE }} + EOF + chmod 600 .env + + echo "▶️ 새 서버 시작..." + sudo docker compose --env-file .env up -d --pull always backend + + echo "🧹 사용하지 않는 이미지 정리..." + sudo docker image prune -a -f + + echo "⛑️ 헬스 체크..." + for i in $(seq 1 10); do + SERVER_STATUS=$(curl -o /dev/null -w "%{http_code}" http://localhost:80/actuator/health || echo "000") + if [ "$SERVER_STATUS" -eq 200 ]; then + echo "✅ 배포 성공! 서버 동작 중..." + exit 0 + else + echo "Attempt $i/10: 헬스 체크 실패 (status=$SERVER_STATUS). 5초 후 재시도..." + sleep 5 + fi + done + + echo "❌ 배포 실패! 서버가 동작하지 않습니다." + sudo docker logs coursepick-backend --tail 50 + exit 1