Merge pull request #241 from prgrms-web-devcourse-final-project/ref/chat #500
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI-CD_Pipeline | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| on: | |
| push: | |
| branches: [ main, develop ] | |
| pull_request: | |
| branches: [ main, develop ] | |
| workflow_dispatch: | |
| jobs: | |
| tests: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ ubuntu-latest ] | |
| include: | |
| - os: ubuntu-latest | |
| gradle_cmd: "./gradlew" | |
| report_path: "backend/build/reports/tests" | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| SPRING_PROFILES_ACTIVE: test-ci | |
| # ✅ Redis 서비스 추가 | |
| services: | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| env: | |
| REDIS_PASSWORD: "" | |
| # ✅ Qdrant 서비스 추가 | |
| qdrant: | |
| image: qdrant/qdrant:v1.3.1 | |
| ports: | |
| - 6333:6333 | |
| - 6334:6334 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up JDK 21 | |
| uses: actions/setup-java@v4 | |
| with: | |
| java-version: '21' | |
| distribution: 'temurin' | |
| cache: gradle | |
| # ✅ gradlew 실행 권한 부여 | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x backend/gradlew | |
| # ✅ Redis 연결 테스트 | |
| - name: Test Redis connection | |
| run: | | |
| echo "Testing Redis connection..." | |
| timeout 10s bash -c 'until printf "" 2>>/dev/null >>/dev/tcp/localhost/6379; do sleep 1; done' | |
| echo "Redis is ready!" | |
| # ✅ Qdrant 연결 테스트 | |
| - name: Wait for Qdrant | |
| run: | | |
| echo "Waiting for Qdrant to be ready..." | |
| timeout 40s bash -c 'until curl -sSf http://localhost:6333/collections >/dev/null; do sleep 1; done' | |
| echo "Qdrant is ready!" | |
| # ✅ application-test.yml에서 사용하는 모든 환경변수를 .env 파일에 생성 | |
| - name: Create test .env file | |
| working-directory: backend | |
| run: | | |
| cat > .env << 'EOF' | |
| # Datasource 설정 (application-test.yml에서 참조) | |
| TEST_DATASOURCE_URL=jdbc:h2:mem:db_test;MODE=MySQL | |
| TEST_DATASOURCE_USERNAME=sa | |
| TEST_DATASOURCE_PASSWORD= | |
| TEST_DATASOURCE_DRIVER=org.h2.Driver | |
| # JPA 설정 (application-test.yml에서 참조) | |
| TEST_JPA_HIBERNATE_DDL_AUTO=create-drop | |
| send_email_address=${{ secrets.SEND_EMAIL_ADDRESS }} | |
| send_email_password=${{ secrets.SEND_EMAIL_PASSWORD }} | |
| # Redis 설정 (application-test.yml에서 참조, GitHub Actions 서비스 사용) | |
| TEST_REDIS_HOST=localhost | |
| TEST_REDIS_PORT=6379 | |
| TEST_REDIS_PASSWORD= | |
| # Qdrant | |
| TEST_QDRANT_HOST=localhost | |
| TEST_QDRANT_PORT=6333 | |
| # CI/CD 환경에서는 Embedded Redis 끄기 | |
| SPRING_DATA_REDIS_EMBEDDED=false | |
| # JWT 설정 (application-test.yml에서 참조) | |
| CUSTOM_JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} | |
| CUSTOM_JWT_ACCESS_TOKEN_EXPIRATION_SECONDS=3600 | |
| EOF | |
| - name: Export .env into runner env | |
| working-directory: backend | |
| run: | | |
| while IFS= read -r line; do | |
| [[ -z "$line" || "$line" =~ ^# ]] && continue | |
| echo "$line" >> $GITHUB_ENV | |
| done < .env | |
| - name: Run unit, and domain tests | |
| run: ${{ matrix.gradle_cmd }} clean test | |
| working-directory: backend | |
| - name: Upload Test Reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-reports-${{ matrix.os }} | |
| path: ${{ matrix.report_path }} | |
| retention-days: 7 | |
| build-artifacts: | |
| needs: tests | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' # ✅ main 브랜치일 때만 실행 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: 21 | |
| cache: gradle | |
| # ✅ gradlew 실행 권한 부여 | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x backend/gradlew | |
| # ✅ 빌드용 .env 파일 생성 (Configuration Properties 바인딩용 최소 환경변수만) | |
| - name: Create build .env file | |
| working-directory: backend | |
| run: | | |
| cat > .env << 'EOF' | |
| # JWT Configuration Properties 바인딩용 (빌드 시 필요) | |
| CUSTOM_JWT_SECRET_KEY=${{ secrets.JWT_SECRET_KEY }} | |
| CUSTOM_JWT_ACCESS_TOKEN_EXPIRATION_SECONDS=3600 | |
| EOF | |
| - name: Gradle bootJar | |
| working-directory: backend | |
| run: ./gradlew --no-daemon clean bootJar -x test | |
| - name: Copy JAR to dist | |
| working-directory: backend | |
| run: | | |
| mkdir -p dist | |
| cp $(ls build/libs/*.jar | grep -v plain | head -n 1) dist/app.jar | |
| - name: Upload backend jar | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: backend-jar | |
| path: backend/dist/app.jar | |
| docker-build: | |
| needs: build-artifacts | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' # ✅ main 브랜치일 때만 실행 | |
| env: | |
| REGISTRY: ghcr.io | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set lowercase repo name | |
| run: | | |
| echo "IMAGE_PREFIX=$(echo $GITHUB_REPOSITORY | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV | |
| - name: Download backend jar | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: backend-jar | |
| path: backend/dist | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build & push backend (runtime-only) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: backend | |
| file: backend/Dockerfile | |
| push: true | |
| tags: | | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/balaw:${{ github.sha }} | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/balaw:latest | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| deploy: | |
| needs: docker-build | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' # ✅ main 브랜치일 때만 실행 | |
| env: | |
| DOCKER_IMAGE_NAME: balaw | |
| REGISTRY: ghcr.io | |
| steps: | |
| - name: Set lowercase repo name | |
| run: | | |
| echo "IMAGE_PREFIX=$(echo $GITHUB_REPOSITORY | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV | |
| - name: AWS SSM Send-Command | |
| uses: peterkimzz/aws-ssm-send-command@master | |
| id: ssm | |
| with: | |
| aws-region: ${{ secrets.AWS_REGION }} | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| instance-ids: "i-0f3872d9228629f28" | |
| working-directory: / | |
| comment: Deploy | |
| command: | | |
| set -euo pipefail | |
| echo "===== 현재 실행 중인 컨테이너 =====" | |
| docker ps -a || true | |
| echo "===== 기존 컨테이너 종료 & 제거 =====" | |
| docker stop app 2>/dev/null || true | |
| docker rm app 2>/dev/null || true | |
| # EC2 내부에서 prod.env 복원 (ENV_BASE64 -> 디코드) | |
| install -d -m 700 /home/ec2-user/configs | |
| cat > /home/ec2-user/configs/prod.env.b64 <<'__B64__' | |
| ${{ secrets.PROD_ENV_BASE64 }} | |
| __B64__ | |
| base64 -d /home/ec2-user/configs/prod.env.b64 > /home/ec2-user/configs/prod.env | |
| chmod 600 /home/ec2-user/configs/prod.env | |
| shred -u /home/ec2-user/configs/prod.env.b64 # 임시 파일 안전 삭제 | |
| # EC2에서 GHCR 로그인 | |
| echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin | |
| # 최신 이미지 pull & 컨테이너 실행 | |
| docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ env.DOCKER_IMAGE_NAME }}:latest | |
| echo "===== 새로운 컨테이너 실행 =====" | |
| docker run --env-file /home/ec2-user/configs/prod.env \ | |
| -e SPRING_PROFILES_ACTIVE=prod \ | |
| -d --name app \ | |
| --network common \ | |
| -p 8080:8080 \ | |
| ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ env.DOCKER_IMAGE_NAME }}:latest | |
| echo "===== 새 컨테이너 로그 출력 =====" | |
| sleep 5 | |
| docker logs --tail=100 app || true | |
| echo "===== 배포 완료 =====" | |
| # dangling image 정리 | |
| docker rmi $(docker images -f "dangling=true" -q) || true |