From 8e2e0a190af4d34b2bc10e7ba97b940ce0acd7a7 Mon Sep 17 00:00:00 2001 From: minsu20 Date: Wed, 3 Jan 2024 19:37:03 +0900 Subject: [PATCH] =?UTF-8?q?deploy:=20=EB=B0=B0=ED=8F=AC=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=EC=84=B8=EC=8A=A4=20prod,=20dev=20=EB=82=98=EB=88=84?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .deploy/Dockerfile | 16 +++++ .deploy/deploy_dev.sh | 67 ++++++++++++++++++ .deploy/deploy_prod.sh | 67 ++++++++++++++++++ .github/workflows/CD-dev.yml | 123 ++++++++++++++++++++++++++++++++++ .github/workflows/CD-prod.yml | 123 ++++++++++++++++++++++++++++++++++ .github/workflows/CI-dev.yml | 57 ++++++++++++++++ .github/workflows/CI-prod.yml | 57 ++++++++++++++++ 7 files changed, 510 insertions(+) create mode 100644 .deploy/Dockerfile create mode 100644 .deploy/deploy_dev.sh create mode 100644 .deploy/deploy_prod.sh create mode 100644 .github/workflows/CD-dev.yml create mode 100644 .github/workflows/CD-prod.yml create mode 100644 .github/workflows/CI-dev.yml create mode 100644 .github/workflows/CI-prod.yml diff --git a/.deploy/Dockerfile b/.deploy/Dockerfile new file mode 100644 index 00000000..25848cbe --- /dev/null +++ b/.deploy/Dockerfile @@ -0,0 +1,16 @@ +FROM openjdk:11-jdk + +# 타임존 설정 +ENV TZ=Asia/Seoul +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +ARG CACHEBREAKER=1 +ARG JAR_FILE=./build/libs/backend-0.0.1-SNAPSHOT.jar +COPY ${JAR_FILE} app.jar + +# 문서를 이미지의 /static/docs 디렉토리에 복사 +COPY ./build/docs/asciidoc/*.html /static/docs/ + +# 애플리케이션 실행 시 -cp 옵션을 사용하여 /static/docs 디렉토리를 클래스패스에 추가 +ENTRYPOINT ["java","-cp",".:/static/docs","-jar","/app.jar"] + diff --git a/.deploy/deploy_dev.sh b/.deploy/deploy_dev.sh new file mode 100644 index 00000000..0c2181d8 --- /dev/null +++ b/.deploy/deploy_dev.sh @@ -0,0 +1,67 @@ +DOCKER_APP_NAME=meetup +DOCKER_USERNAME=modagbul + +# 최신 이미지 가져오기 +docker pull ${DOCKER_USERNAME}/moing_dev:blue +docker pull ${DOCKER_USERNAME}/moing_dev:green + +# 현재 실행 중인 컨테이너를 확인 (blue 또는 green) +EXIST_BLUE=$(docker ps --filter name=${DOCKER_APP_NAME}-blue --filter status=running -q) +EXIST_GREEN=$(docker ps --filter name=${DOCKER_APP_NAME}-green --filter status=running -q) + +# 둘 다 실행 중이지 않을 경우 blue 실행 +if [ -z "$EXIST_BLUE" ] && [ -z "$EXIST_GREEN" ]; then + echo "No containers running. Starting blue up" + + # 만약 컨테이너가 중지된 상태로 존재하면 삭제한다. + if [ "$(docker ps -a --filter name=${DOCKER_APP_NAME}-blue -q)" ]; then + docker rm ${DOCKER_APP_NAME}-blue + fi + + docker run -d --name ${DOCKER_APP_NAME}-blue -p 8081:8080 -e TZ=Asia/Seoul ${DOCKER_USERNAME}/moing_dev:blue + BEFORE_COMPOSE_COLOR="green" + AFTER_COMPOSE_COLOR="blue" +elif [ -z "$EXIST_BLUE" ]; then + echo "blue up" + + # 만약 컨테이너가 중지된 상태로 존재하면 삭제한다. + if [ "$(docker ps -a --filter name=${DOCKER_APP_NAME}-blue -q)" ]; then + docker rm ${DOCKER_APP_NAME}-blue + fi + + docker run -d --name ${DOCKER_APP_NAME}-blue -p 8081:8080 -e TZ=Asia/Seoul ${DOCKER_USERNAME}/moing_dev:blue + BEFORE_COMPOSE_COLOR="green" + AFTER_COMPOSE_COLOR="blue" +else + echo "green up" + + # 만약 컨테이너가 중지된 상태로 존재하면 삭제한다. + if [ "$(docker ps -a --filter name=${DOCKER_APP_NAME}-green -q)" ]; then + docker rm ${DOCKER_APP_NAME}-green + fi + + docker run -d --name ${DOCKER_APP_NAME}-green -p 8082:8080 -e TZ=Asia/Seoul ${DOCKER_USERNAME}/moing_dev:green + BEFORE_COMPOSE_COLOR="blue" + AFTER_COMPOSE_COLOR="green" +fi + + +sleep 40 + +# 새로운 컨테이너가 제대로 실행되었는지 확인 +EXIST_AFTER=$(docker ps --filter name=${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} --filter status=running -q) +if [ -n "$EXIST_AFTER" ]; then + # nginx.config를 컨테이너에 맞게 변경해주고 reload 한다 + cp ./nginx.${AFTER_COMPOSE_COLOR}.conf /etc/nginx/nginx.conf + sudo nginx -s reload + + # 이전 컨테이너 종료 + docker stop ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} + docker rm ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} + echo "$BEFORE_COMPOSE_COLOR down" +else + # Docker logs + LOGS=$(docker logs ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} 2>&1) + echo "Error: ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} failed to start." + echo "$LOGS" +fi \ No newline at end of file diff --git a/.deploy/deploy_prod.sh b/.deploy/deploy_prod.sh new file mode 100644 index 00000000..91c86b59 --- /dev/null +++ b/.deploy/deploy_prod.sh @@ -0,0 +1,67 @@ +DOCKER_APP_NAME=meetup +DOCKER_USERNAME=modagbul + +# 최신 이미지 가져오기 +docker pull ${DOCKER_USERNAME}/moing_prod:blue +docker pull ${DOCKER_USERNAME}/moing_prod:green + +# 현재 실행 중인 컨테이너를 확인 (blue 또는 green) +EXIST_BLUE=$(docker ps --filter name=${DOCKER_APP_NAME}-blue --filter status=running -q) +EXIST_GREEN=$(docker ps --filter name=${DOCKER_APP_NAME}-green --filter status=running -q) + +# 둘 다 실행 중이지 않을 경우 blue 실행 +if [ -z "$EXIST_BLUE" ] && [ -z "$EXIST_GREEN" ]; then + echo "No containers running. Starting blue up" + + # 만약 컨테이너가 중지된 상태로 존재하면 삭제한다. + if [ "$(docker ps -a --filter name=${DOCKER_APP_NAME}-blue -q)" ]; then + docker rm ${DOCKER_APP_NAME}-blue + fi + + docker run -d --name ${DOCKER_APP_NAME}-blue -p 8081:8080 -e TZ=Asia/Seoul ${DOCKER_USERNAME}/moing_prod:blue + BEFORE_COMPOSE_COLOR="green" + AFTER_COMPOSE_COLOR="blue" +elif [ -z "$EXIST_BLUE" ]; then + echo "blue up" + + # 만약 컨테이너가 중지된 상태로 존재하면 삭제한다. + if [ "$(docker ps -a --filter name=${DOCKER_APP_NAME}-blue -q)" ]; then + docker rm ${DOCKER_APP_NAME}-blue + fi + + docker run -d --name ${DOCKER_APP_NAME}-blue -p 8081:8080 -e TZ=Asia/Seoul ${DOCKER_USERNAME}/moing_prod:blue + BEFORE_COMPOSE_COLOR="green" + AFTER_COMPOSE_COLOR="blue" +else + echo "green up" + + # 만약 컨테이너가 중지된 상태로 존재하면 삭제한다. + if [ "$(docker ps -a --filter name=${DOCKER_APP_NAME}-green -q)" ]; then + docker rm ${DOCKER_APP_NAME}-green + fi + + docker run -d --name ${DOCKER_APP_NAME}-green -p 8082:8080 -e TZ=Asia/Seoul ${DOCKER_USERNAME}/moing_prod:green + BEFORE_COMPOSE_COLOR="blue" + AFTER_COMPOSE_COLOR="green" +fi + + +sleep 40 + +# 새로운 컨테이너가 제대로 실행되었는지 확인 +EXIST_AFTER=$(docker ps --filter name=${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} --filter status=running -q) +if [ -n "$EXIST_AFTER" ]; then + # nginx.config를 컨테이너에 맞게 변경해주고 reload 한다 + cp ./nginx.${AFTER_COMPOSE_COLOR}.conf /etc/nginx/nginx.conf + sudo nginx -s reload + + # 이전 컨테이너 종료 + docker stop ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} + docker rm ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR} + echo "$BEFORE_COMPOSE_COLOR down" +else + # Docker logs + LOGS=$(docker logs ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} 2>&1) + echo "Error: ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR} failed to start." + echo "$LOGS" +fi \ No newline at end of file diff --git a/.github/workflows/CD-dev.yml b/.github/workflows/CD-dev.yml new file mode 100644 index 00000000..40cd9a33 --- /dev/null +++ b/.github/workflows/CD-dev.yml @@ -0,0 +1,123 @@ +name: CD-dev + +on: + push: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + env: + working-directory: ./ + APPLICATION: ${{ secrets.APPLICATION_DEV }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_DEV }} + APPLE_KEY: ${{ secrets.APPLE_KEY_DEV }} + + steps: + # 소스 코드 체크아웃 + - uses: actions/checkout@v2 + + # JDK 11 설정 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + # Gradle 패키지 캐시 + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # 설정 파일 생성 + - run: | + mkdir ./src/main/resources + cd ./src/main/resources + touch ./application.yml + echo "${{env.APPLICATION}}" > ./application.yml + touch ./firebase-key.json + echo "${{env.GOOGLE_APPLICATION_CREDENTIALS}}" | base64 --decode > ./firebase-key.json + touch ./apple-key.p8 + echo "${{env.APPLE_KEY}}" > ./apple-key.p8 + + # 설정 파일을 작업공간에 저장 + - uses: actions/upload-artifact@v2 + with: + name: application.yml + path: ./src/main/resources/application.yml + + - uses: actions/upload-artifact@v2 + with: + name: firebase-key.json + path: ./src/main/resources/firebase-key.json + + - uses: actions/upload-artifact@v2 + with: + name: apple-key.p8 + path: ./src/main/resources/apple-key.p8 + + # gradlew 권한 설정 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + working-directory: ${{ env.working-directory }} + + # Gradle로 빌드 + - name: Build with Gradle + run: ./gradlew build + working-directory: ${{ env.working-directory }} + + # Gradle 캐시 정리 + - name: Cleanup Gradle Cache + if: ${{ always() }} + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties + + # Docker 이미지 빌드 및 푸시 + - name: Docker build + run: | + docker build --no-cache -f ./.deploy/Dockerfile -t ${{ secrets.DOCKER_USERNAME_DEV }}/moing_dev:green . + docker build --no-cache -f ./.deploy/Dockerfile -t ${{ secrets.DOCKER_USERNAME_DEV }}/moing_dev:blue . + + + - name: Docker Hub Login + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME_DEV }} + password: ${{ secrets.DOCKER_PASSWORD_DEV }} + + - name: Docker push + run: | + docker push ${{ secrets.DOCKER_USERNAME_DEV }}/moing_dev:green + docker push ${{ secrets.DOCKER_USERNAME_DEV }}/moing_dev:blue + + # EC2로 deploy.sh 전송 + - name: Deploy deploy.sh to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_SERVER_HOST_DEV }} + username: ec2-user + key: ${{ secrets.PRIVATE_KEY_DEV }} + source: "./.deploy/deploy_dev.sh" + target: "/home/ec2-user/" + + # 배포 스크립트 실행 + - name: Deploy on EC2 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_SERVER_HOST_DEV }} + username: ec2-user + key: ${{ secrets.PRIVATE_KEY_DEV }} + envs: GITHUB_SHA + script: | + chmod +x /home/ec2-user/deploy_dev.sh + /home/ec2-user/deploy_dev.sh \ No newline at end of file diff --git a/.github/workflows/CD-prod.yml b/.github/workflows/CD-prod.yml new file mode 100644 index 00000000..2c132f0c --- /dev/null +++ b/.github/workflows/CD-prod.yml @@ -0,0 +1,123 @@ +name: CD-prod + +on: + push: + branches: [ "release" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + env: + working-directory: ./ + APPLICATION: ${{ secrets.APPLICATION_PROD }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_PROD }} + APPLE_KEY: ${{ secrets.APPLE_KEY_PROD }} + + steps: + # 소스 코드 체크아웃 + - uses: actions/checkout@v2 + + # JDK 11 설정 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + # Gradle 패키지 캐시 + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # 설정 파일 생성 + - run: | + mkdir ./src/main/resources + cd ./src/main/resources + touch ./application.yml + echo "${{env.APPLICATION}}" > ./application.yml + touch ./firebase-key.json + echo "${{env.GOOGLE_APPLICATION_CREDENTIALS}}" | base64 --decode > ./firebase-key.json + touch ./apple-key.p8 + echo "${{env.APPLE_KEY}}" > ./apple-key.p8 + + # 설정 파일을 작업공간에 저장 + - uses: actions/upload-artifact@v2 + with: + name: application.yml + path: ./src/main/resources/application.yml + + - uses: actions/upload-artifact@v2 + with: + name: firebase-key.json + path: ./src/main/resources/firebase-key.json + + - uses: actions/upload-artifact@v2 + with: + name: apple-key.p8 + path: ./src/main/resources/apple-key.p8 + + # gradlew 권한 설정 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + working-directory: ${{ env.working-directory }} + + # Gradle로 빌드 + - name: Build with Gradle + run: ./gradlew build + working-directory: ${{ env.working-directory }} + + # Gradle 캐시 정리 + - name: Cleanup Gradle Cache + if: ${{ always() }} + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties + + # Docker 이미지 빌드 및 푸시 + - name: Docker build + run: | + docker build --no-cache -f ./.deploy/Dockerfile -t ${{ secrets.DOCKER_USERNAME_PROD }}/moing_prod:green . + docker build --no-cache -f ./.deploy/Dockerfile -t ${{ secrets.DOCKER_USERNAME_PROD }}/moing_prod:blue . + + + - name: Docker Hub Login + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME_PROD }} + password: ${{ secrets.DOCKER_PASSWORD_PROD }} + + - name: Docker push + run: | + docker push ${{ secrets.DOCKER_USERNAME_PROD }}/moing_prod:green + docker push ${{ secrets.DOCKER_USERNAME_PROD }}/moing_prod:blue + + # EC2로 deploy.sh 전송 + - name: Deploy deploy.sh to EC2 + uses: appleboy/scp-action@master + with: + host: ${{ secrets.EC2_SERVER_HOST_PROD }} + username: ec2-user + key: ${{ secrets.PRIVATE_KEY_PROD }} + source: "./.deploy/deploy_prod.sh" + target: "/home/ec2-user/" + + # 배포 스크립트 실행 + - name: Deploy on EC2 + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_SERVER_HOST_PROD }} + username: ec2-user + key: ${{ secrets.PRIVATE_KEY_PROD }} + envs: GITHUB_SHA + script: | + chmod +x /home/ec2-user/deploy_prod.sh + /home/ec2-user/deploy_prod.sh \ No newline at end of file diff --git a/.github/workflows/CI-dev.yml b/.github/workflows/CI-dev.yml new file mode 100644 index 00000000..5970e76f --- /dev/null +++ b/.github/workflows/CI-dev.yml @@ -0,0 +1,57 @@ +name: CI-dev + +on: + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + env: + working-directory: ./ + APPLICATION: ${{ secrets.APPLICATION_DEV }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_DEV }} + APPLE_KEY: ${{ secrets.APPLE_KEY_DEV }} + + steps: + # 소스 코드 체크아웃 + - uses: actions/checkout@v2 + + # JDK 11 설정 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + # Gradle 패키지 캐시 + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # gradlew 권한 설정 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + working-directory: ${{ env.working-directory }} + + # Gradle로 빌드 + - name: Build with Gradle + run: ./gradlew build + working-directory: ${{ env.working-directory }} + + # Gradle 캐시 정리 + - name: Cleanup Gradle Cache + if: ${{ always() }} + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties diff --git a/.github/workflows/CI-prod.yml b/.github/workflows/CI-prod.yml new file mode 100644 index 00000000..8a4b599a --- /dev/null +++ b/.github/workflows/CI-prod.yml @@ -0,0 +1,57 @@ +name: CI-prod + +on: + pull_request: + branches: + - release + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + env: + working-directory: ./ + APPLICATION: ${{ secrets.APPLICATION_PROD }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_PROD }} + APPLE_KEY: ${{ secrets.APPLE_KEY_PROD }} + + steps: + # 소스 코드 체크아웃 + - uses: actions/checkout@v2 + + # JDK 11 설정 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + # Gradle 패키지 캐시 + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + # gradlew 권한 설정 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + working-directory: ${{ env.working-directory }} + + # Gradle로 빌드 + - name: Build with Gradle + run: ./gradlew build + working-directory: ${{ env.working-directory }} + + # Gradle 캐시 정리 + - name: Cleanup Gradle Cache + if: ${{ always() }} + run: | + rm -f ~/.gradle/caches/modules-2/modules-2.lock + rm -f ~/.gradle/caches/modules-2/gc.properties