diff --git a/.github/workflows/cd.yml b/.github/workflows/dev-deploy.yml similarity index 75% rename from .github/workflows/cd.yml rename to .github/workflows/dev-deploy.yml index e435edb7..3b74a0a0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/dev-deploy.yml @@ -1,7 +1,8 @@ -name: Production CD - +name: 변경사항을 개발 서버에 배포한다 on: workflow_dispatch: + push: + branches: [ "dev" ] permissions: contents: read @@ -11,7 +12,9 @@ env: jobs: deploy: - runs-on: production + runs-on: dev + environment: + name: dev steps: - name: Get token from Submodule Reader uses: actions/create-github-app-token@v1 @@ -27,10 +30,13 @@ jobs: submodules: true token: ${{ steps.app_token.outputs.token }} + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Create bootjar - run: | - chmod +x ./gradlew - ./gradlew bootjar + uses: gradle/gradle-build-action@v3 + with: + arguments: bootjar - name: Copy jar shell: bash {0} @@ -38,45 +44,54 @@ jobs: mkdir $JAR_DIRECTORY cp ./build/libs/$JAR_NAME $JAR_DIRECTORY/$JAR_NAME - - name: Download Datadog Java Agent - working-directory: ${{ env.JAR_DIRECTORY }} - run: | - wget -O dd-java-agent.jar 'https://dtdg.co/latest-java-tracer' - - name: 현재 사용중인 어플리케이션 포트 확인 shell: bash {0} run: | - if [ -n "$(lsof -ti:${{ vars.APPLICATION_PORT_A }})" ]; then + PORT_A_PID=$(lsof -ti:${{ vars.APPLICATION_PORT_A }}) + PORT_B_PID=$(lsof -ti:${{ vars.APPLICATION_PORT_B }}) + if [ -n $PORT_A_PID -a -n $PORT_B_PID ]; then + echo "::error title=배포 실패::$PORT_A_PID, $PORT_B_PID 두 포트가 모두 사용중입니다"; + exit 1; + fi + + if [ -n $PORT_A_PID ]; then echo "BLUE_PORT=${{ vars.APPLICATION_PORT_A }}" >> "$GITHUB_ENV" echo "GREEN_PORT=${{ vars.APPLICATION_PORT_B }}" >> "$GITHUB_ENV" - else + fi + if [ -n $PORT_B_PID ]; then echo "BLUE_PORT=${{ vars.APPLICATION_PORT_B }}" >> "$GITHUB_ENV" echo "GREEN_PORT=${{ vars.APPLICATION_PORT_A }}" >> "$GITHUB_ENV" fi + - name: Download Datadog Java Agent + working-directory: ${{ env.JAR_DIRECTORY }} + run: | + wget -O dd-java-agent.jar 'https://dtdg.co/latest-java-tracer' + - name: 그린 어플리케이션 실행 env: RUNNER_TRACKING_ID: "" shell: bash working-directory: ${{ env.JAR_DIRECTORY }} run: | - nohup java -javaagent:dd-java-agent.jar \ - -Dspring.profiles.active=production \ + nohup java \ + -Dserver.port=$GREEN_PORT \ + -Dspring.profiles.active=${{ vars.ENVIRONMENT_NAME }} \ -DACCESS_TOKEN_SECRET_KEY=${{ secrets.ACCESS_TOKEN_SECRET_KEY }} \ -DACCESS_TOKEN_EXPIRY_DAYS=${{ secrets.ACCESS_TOKEN_EXPIRY_DAYS }} \ -DREFRESH_TOKEN_SECRET_KEY=${{ secrets.REFRESH_TOKEN_SECRET_KEY }} \ -DREFRESH_TOKEN_EXPIRY_DAYS=${{ secrets.REFRESH_TOKEN_EXPIRY_DAYS }} \ - -Dserver.port=$GREEN_PORT \ - -DPRODUCTION_DB_URL=${{ secrets.PRODUCTION_DB_URL }} \ - -DPRODUCTION_DB_USERNAME=${{ secrets.PRODUCTION_DB_USERNAME }} \ - -DPRODUCTION_DB_PASSWORD=${{ secrets.PRODUCTION_DB_PASSWORD }} \ + -DDB_URL=${{ secrets.DB_URL }} \ + -DDB_USERNAME=${{ secrets.DB_USERNAME }} \ + -DDB_PASSWORD=${{ secrets.DB_PASSWORD }} \ + -javaagent:dd-java-agent.jar \ -Ddd.profiling.enabled=true \ -XX:FlightRecorderOptions=stackdepth=256 \ -Ddd.logs.injection=true \ -Ddd.appsec.enabled=true \ -Ddd.iast.enabled=true \ -Ddd.service=snackgame \ - -Ddd.env=production \ + -Ddd.env=${{ vars.ENVIRONMENT_NAME }} \ -jar $JAR_NAME > ~/snackgame-server.log & - name: 그린 어플리케이션이 접속 가능할 때까지 기다린다 @@ -85,7 +100,7 @@ jobs: PROCESS_ID="$(lsof -i:$GREEN_PORT -t)" while [ "$(curl -o /dev/null -s -w %{http_code} localhost:$GREEN_PORT/rankings?by=BEST_SCORE)" != 200 ] do - if [ -n "$PROCESS_ID" ]; then + if [ -e /proc/$PROCESS_ID ]; then echo "::error title=배포 실패::블루 어플리케이션으로 롤백합니다."; exit 1; fi diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml new file mode 100644 index 00000000..ae05b00a --- /dev/null +++ b/.github/workflows/production-deploy.yml @@ -0,0 +1,126 @@ +name: 변경사항을 운영 서버에 배포한다 + +on: + workflow_dispatch: + push: + branches: [ "main" ] +permissions: + contents: read + +env: + JAR_NAME: snackgame-server.jar + JAR_DIRECTORY: /home/ubuntu/snackgame + +jobs: + deploy: + runs-on: production + environment: + name: production + steps: + - name: Get token from Submodule Reader + uses: actions/create-github-app-token@v1 + id: app_token + with: + app-id: ${{ secrets.SUBMODULE_APP_ID }} + private-key: ${{ secrets.SUBMODULE_APP_PEM }} + owner: ${{ github.repository_owner }} + + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + token: ${{ steps.app_token.outputs.token }} + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Build + uses: gradle/gradle-build-action@v3 + with: + arguments: build + + - name: Copy jar + shell: bash {0} + run: | + mkdir $JAR_DIRECTORY + cp ./build/libs/$JAR_NAME $JAR_DIRECTORY/$JAR_NAME + + - name: 현재 사용중인 어플리케이션 포트 확인 + shell: bash {0} + run: | + PORT_A_PID=$(lsof -ti:${{ vars.APPLICATION_PORT_A }}) + PORT_B_PID=$(lsof -ti:${{ vars.APPLICATION_PORT_B }}) + if [ -n $PORT_A_PID -a -n $PORT_B_PID ]; then + echo "::error title=배포 실패::$PORT_A_PID, $PORT_B_PID 두 포트가 모두 사용중입니다"; + exit 1; + fi + + if [ -n $PORT_A_PID ]; then + echo "BLUE_PORT=${{ vars.APPLICATION_PORT_A }}" >> "$GITHUB_ENV" + echo "GREEN_PORT=${{ vars.APPLICATION_PORT_B }}" >> "$GITHUB_ENV" + fi + if [ -n $PORT_B_PID ]; then + echo "BLUE_PORT=${{ vars.APPLICATION_PORT_B }}" >> "$GITHUB_ENV" + echo "GREEN_PORT=${{ vars.APPLICATION_PORT_A }}" >> "$GITHUB_ENV" + fi + + - name: Download Datadog Java Agent + working-directory: ${{ env.JAR_DIRECTORY }} + run: | + wget -O dd-java-agent.jar 'https://dtdg.co/latest-java-tracer' + + - name: 그린 어플리케이션 실행 + env: + RUNNER_TRACKING_ID: "" + shell: bash + working-directory: ${{ env.JAR_DIRECTORY }} + run: | + nohup java \ + -Dserver.port=$GREEN_PORT \ + -Dspring.profiles.active=${{ vars.ENVIRONMENT_NAME }} \ + -DACCESS_TOKEN_SECRET_KEY=${{ secrets.ACCESS_TOKEN_SECRET_KEY }} \ + -DACCESS_TOKEN_EXPIRY_DAYS=${{ secrets.ACCESS_TOKEN_EXPIRY_DAYS }} \ + -DREFRESH_TOKEN_SECRET_KEY=${{ secrets.REFRESH_TOKEN_SECRET_KEY }} \ + -DREFRESH_TOKEN_EXPIRY_DAYS=${{ secrets.REFRESH_TOKEN_EXPIRY_DAYS }} \ + -DDB_URL=${{ secrets.DB_URL }} \ + -DDB_USERNAME=${{ secrets.DB_USERNAME }} \ + -DDB_PASSWORD=${{ secrets.DB_PASSWORD }} \ + -javaagent:dd-java-agent.jar \ + -Ddd.profiling.enabled=true \ + -XX:FlightRecorderOptions=stackdepth=256 \ + -Ddd.logs.injection=true \ + -Ddd.appsec.enabled=true \ + -Ddd.iast.enabled=true \ + -Ddd.service=snackgame \ + -Ddd.env=${{ vars.ENVIRONMENT_NAME }} \ + -jar $JAR_NAME > ~/snackgame-server.log & + + - name: 그린 어플리케이션이 접속 가능할 때까지 기다린다 + shell: bash {0} + run: | + PROCESS_ID="$(lsof -i:$GREEN_PORT -t)" + while [ "$(curl -o /dev/null -s -w %{http_code} localhost:$GREEN_PORT/rankings?by=BEST_SCORE)" != 200 ] + do + if [ -e /proc/$PROCESS_ID ]; then + echo "::error title=배포 실패::블루 어플리케이션으로 롤백합니다."; + exit 1; + fi + echo "새로운 어플리케이션을 띄우는 중입니다."; + sleep 5; + done + + - name: 리버스 프록시 설정 변경 + working-directory: ${{ env.JAR_DIRECTORY }} + shell: bash {0} + run: | + echo "proxy_pass http://localhost:$GREEN_PORT;" > port.inc; + sudo nginx -s reload; + + - name: 블루 어플리케이션 종료 + shell: bash {0} + run: | + PROCESS_ID="$(lsof -i:$BLUE_PORT -t)" + if [ -n "$PROCESS_ID" ]; then + sudo kill -15 $PROCESS_ID + echo "구동중인 애플리케이션을 종료했습니다. (pid : $PROCESS_ID)\n" + fi diff --git a/.github/workflows/production-rebase-on-approval.yml b/.github/workflows/production-rebase-on-approval.yml new file mode 100644 index 00000000..cf696647 --- /dev/null +++ b/.github/workflows/production-rebase-on-approval.yml @@ -0,0 +1,49 @@ +name: 승인된 변경사항을 운영 브랜치에 반영한다 + +on: + pull_request_review: + types: [ submitted ] + +jobs: + meet-conditions: + permissions: + checks: read + contents: read + runs-on: ubuntu-latest + if: ${{ github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.ref == 'dev' && github.event.review.state == 'APPROVED'}} + steps: + - name: Check Admin Permission + uses: actions-cool/check-user-permission@v2.2.1 + with: + require: 'admin' + + - name: Check if all checks are passed + uses: wechuli/allcheckspassed@v1 + + rebase: + needs: meet-conditions + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Get token from Submodule Reader + uses: actions/create-github-app-token@v1 + id: app_token + with: + app-id: ${{ secrets.SUBMODULE_APP_ID }} + private-key: ${{ secrets.SUBMODULE_APP_PEM }} + owner: ${{ github.repository_owner }} + + - name: Checkout to main + uses: actions/checkout@v4 + with: + ref: 'main' + submodules: true + token: ${{ steps.app_token.outputs.token }} + + - name: Rebase Branch + uses: martincostello/rebaser@v2.0.0 + + - name: Push Rebased + if : ${{ steps.rebase.outputs.result == 'success' }} + run: git push diff --git a/.github/workflows/ci.yml b/.github/workflows/test.yml similarity index 80% rename from .github/workflows/ci.yml rename to .github/workflows/test.yml index 2b5e2290..f6ae015c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,9 @@ -name: build and test with Gradle +name: PR을 테스트한다 on: workflow_dispatch: pull_request: - branches: [ "main" ] + branches: [ "main", "dev" ] permissions: contents: read @@ -20,7 +20,7 @@ jobs: app-id: ${{ secrets.SUBMODULE_APP_ID }} private-key: ${{ secrets.SUBMODULE_APP_PEM }} owner: ${{ github.repository_owner }} - + - name: Checkout uses: actions/checkout@v4 with: @@ -33,12 +33,11 @@ jobs: java-version: '17' distribution: 'corretto' - - name: Grant execute permission for gradlew - run: chmod +x gradlew - + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 - name: Build with Gradle - uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + uses: gradle/gradle-build-action@v3 with: arguments: build diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 00000000..142f81ad --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,22 @@ +spring: + datasource: + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + jpa: + hibernate: + ddl-auto: update +security: + jwt: + token: + access-secret-key: ${ACCESS_TOKEN_SECRET_KEY} + access-expiry-days: ${ACCESS_TOKEN_EXPIRY_DAYS} + refresh-secret-key: ${REFRESH_TOKEN_SECRET_KEY} + refresh-expiry-days: ${REFRESH_TOKEN_EXPIRY_DAYS} +server: + port: ${APPLICATION_PORT} + forward-headers-strategy: native + servlet: + session: + cookie: + same-site: none diff --git a/src/main/resources/application-production.yml b/src/main/resources/application-production.yml index 51e23252..ebf2a427 100644 --- a/src/main/resources/application-production.yml +++ b/src/main/resources/application-production.yml @@ -1,8 +1,8 @@ spring: datasource: - url: ${PRODUCTION_DB_URL} - username: ${PRODUCTION_DB_USERNAME} - password: ${PRODUCTION_DB_PASSWORD} + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} jpa: hibernate: ddl-auto: validate