diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 84dc9718..97c5458d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,19 @@ ## πŸ‘©β€πŸ’» Contents + ## πŸ“ Review Note + ## πŸ“£ Related Issue + + - closed # + +## βœ… 점검사항 + +- [ ] docker-compose.yml νŒŒμΌμ— λ§ˆμ΄κ·Έλ ˆμ΄μ…˜ ν•œ API의 ν¬μ›Œλ”©μ„ λ³€κ²½ν•΄μ€¬λ‚˜μš”? +- [ ] Spring Secret 값을 μˆ˜μ •ν•˜κ±°λ‚˜ μΆ”κ°€ν–ˆλ‹€λ©΄ Github Secretμ—μ„œ μˆ˜μ •μ„ ν•΄μ€¬λ‚˜μš”? +- [ ] Nestjs Secret 값을 μˆ˜μ •ν•˜κ±°λ‚˜ μΆ”κ°€ν–ˆλ‹€λ©΄ Docker-Compose.yml 파일 및 μΈμŠ€ν„΄μŠ€ λ‚΄λΆ€μ˜ .env νŒŒμΌμ„ μˆ˜μ •ν–ˆλ‚˜μš”? \ 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..65ee12d9 --- /dev/null +++ b/.github/workflows/cd-dev.yml @@ -0,0 +1,55 @@ +name: Makers_Crew Dev CD +on: + workflow_run: + workflows: [ "Makers_Crew CI" ] + branches: [ "develop" ] + types: [ completed ] + + push: + branches: [ "develop" ] +jobs: + deploy: + runs-on: ubuntu-22.04 + env: + working-directory-spring: main + working-directory-nestjs: server + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Docker build κ°€λŠ₯ν•˜λ„λ‘ ν™˜κ²½ μ„€μ • + uses: docker/setup-buildx-action@v2 + + - name: Create application.properties from secret + run: | + echo "${{ secrets.APPLICATION_SECRET_SPRING_DEV }}" > ./main/src/main/resources/application-secret.properties + shell: bash + + - name: Docker hub에 둜그인 + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_LOGIN_USERNAME }} + password: ${{ secrets.DOCKERHUB_LOGIN_ACCESSTOKEN }} + + - name: docker image λΉŒλ“œ 및 ν‘Έμ‹œ (Spring) + run: | + docker build --platform linux/amd64 -t makerscrew/main . + docker push makerscrew/main + working-directory: ${{ env.working-directory-spring }} + + - name: docker image λΉŒλ“œ 및 ν‘Έμ‹œ (nestjs) + run: | + docker build --platform linux/amd64 -t makerscrew/server . + docker push makerscrew/server + working-directory: ${{ env.working-directory-nestjs }} + + - name: 도컀 μ»¨ν…Œμ΄λ„ˆ μ‹€ν–‰ + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.DEV_SERVER_IP }} + username: ${{ secrets.DEV_SERVER_USER }} + key: ${{ secrets.DEV_SERVER_KEY }} + script: | + cd ~ + chmod +x ./deploy.sh + ./deploy.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..55a220f0 --- /dev/null +++ b/.github/workflows/cd-prod.yml @@ -0,0 +1,55 @@ +name: Makers_Crew Main CD +on: + workflow_run: + workflows: [ "Makers_Crew CI" ] + branches: [ "main" ] + types: [ completed ] + + push: + branches: [ "main" ] +jobs: + deploy: + runs-on: ubuntu-22.04 + env: + working-directory-spring: main + working-directory-nestjs: server + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Docker build κ°€λŠ₯ν•˜λ„λ‘ ν™˜κ²½ μ„€μ • + uses: docker/setup-buildx-action@v2 + + - name: Create application.properties from secret + run: | + echo "${{ secrets.APPLICATION_SECRET_SPRING_PROD }}" > ./main/src/main/resources/application-secret.properties + shell: bash + + - name: Docker hub에 둜그인 + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_LOGIN_USERNAME }} + password: ${{ secrets.DOCKERHUB_LOGIN_ACCESSTOKEN }} + + - name: docker image λΉŒλ“œ 및 ν‘Έμ‹œ (Spring) + run: | + docker build --platform linux/amd64 -t makerscrew/main . + docker push makerscrew/main + working-directory: ${{ env.working-directory-spring }} + + - name: docker image λΉŒλ“œ 및 ν‘Έμ‹œ (nestjs) + run: | + docker build --platform linux/amd64 -t makerscrew/server . + docker push makerscrew/server + working-directory: ${{ env.working-directory-nestjs }} + + - name: 도컀 μ»¨ν…Œμ΄λ„ˆ μ‹€ν–‰ + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.PROD_SERVER_IP }} + username: ${{ secrets.PROD_SERVER_USER }} + key: ${{ secrets.PROD_SERVER_KEY }} + script: | + cd ~ + chmod +x ./deploy.sh + ./deploy.sh \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..4c7c8c4c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,77 @@ +name: Makers_Crew CI + +on: + push: + branches: + - '**' + + pull_request: + types: [ opened, synchronize, reopened ] +jobs: + build-springboot: + name: Build and analyze (SpringBoot) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + + - name: Create application.properties from secret + run: | + echo "${{ secrets.APPLICATION_SECRET_SPRING_DEV }}" > ./main/src/main/resources/application-secret.properties + shell: bash + + - name: Build and analyze (SpringBoot) + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd main + ./gradlew clean build + +# NsetJs μ„œλ²„μ˜ 경우 변경될 사항이 μ—†λ‹€κ³  μƒκ°ν•˜μ—¬ CI μž‘μ—… μƒλž΅ / λ§Œμ•½ CI μž‘μ—…μ΄ ν•„μš”ν•˜λ‹€λ©΄ 맨 λ°‘μ˜ 주석 μ°Έκ³  +# build-nestjs: +# name: Build and analyze (NestJS) +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v3 +# with: +# fetch-depth: 0 + +# - name: Set up Node.js +# uses: actions/setup-node@v3 +# with: +# node-version: '21' + +# - name: Cache npm packages +# uses: actions/cache@v3 +# with: +# path: server/node_modules +# key: ${{ runner.os }}-node-${{ hashFiles('server/package-lock.json') }} + +# - name: Create .dev.env from secret +# run: | +# echo "${{ secrets.APPLICATION_SECRET_NESTJS_DEV }}" > ./server/.dev.env +# shell: bash + +# - name: Build and analyze (NestJS) +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# run: | +# cd server +# npm ci +# npm run start +# Create .dev.env μž‘μ—…μ„ 톡해 .dev.env νŒŒμΌμ„ μƒμ„±ν–ˆμŒμ—λ„ CI μž‘μ—…μ΄ μ‹€νŒ¨ν•¨. \ No newline at end of file diff --git a/.github/workflows/dev-CD.yml b/.github/workflows/dev-CD.yml deleted file mode 100644 index 6103ae2a..00000000 --- a/.github/workflows/dev-CD.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: DEV CD - -on: - push: - branches: [ "develop" ] - -jobs: - deploy-ci: - runs-on: ubuntu-22.04 - env: - working-directory-spring: main - working-directory-nestjs: server - - steps: - - name: checkout - uses: actions/checkout@v3 - - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: '17' - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: '18' - - - name: Install dependencies (npm μ˜μ‘΄μ„± μ„€μΉ˜) - run: npm install - working-directory: ${{ env.working-directory-nestjs }} - - - name: resources 폴더 λ‚΄ application-secret.properties 파일 생성 - run: | - mkdir -p src/main/resources - echo "${{ secrets.APPLICATION_SECRET }}" > src/main/resources/application-secret.properties - working-directory: ${{ env.working-directory-spring }} - - - name: nestjs 폴더 λ‚΄ .dev.env 파일 생성 - run: | - echo "${{ secrets.APPLICATION_SECRET }}" > .dev.env - working-directory: ${{ env.working-directory-nestjs }} - - - name: Build (Spring) - run: | - chmod +x gradlew - ./gradlew clean build - working-directory: ${{ env.working-directory-spring }} - shell: bash - - - name: Build (nestjs) - run: npm run build - working-directory: ${{ env.working-directory-nestjs }} - - ### μ—¬κΈ°κΉŒμ§€ CI와 거의 동일 - - - name: docker build κ°€λŠ₯ν•˜λ„λ‘ ν™˜κ²½ μ„€μ • - uses: docker/setup-buildx-action@v2 - - - name: docker hub에 둜그인 - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_LOGIN_USERNAME }} - password: ${{ secrets.DOCKERHUB_LOGIN_ACCESSTOKEN }} - - - name: docker image λΉŒλ“œ 및 ν‘Έμ‹œ (Spring) - run: | - docker build --platform linux/amd64 -t makerscrew/main . - docker push makerscrew/main - working-directory: ${{ env.working-directory-spring }} - - - name: docker image λΉŒλ“œ 및 ν‘Έμ‹œ (nestjs) - run: | - docker build --platform linux/amd64 -t makerscrew/server . - docker push makerscrew/server - working-directory: ${{ env.working-directory-nestjs }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index b8f22461..bad7d20a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .vscode -/.idea/ +/.idea/ \ No newline at end of file diff --git a/deploy-dev.sh b/deploy-dev.sh new file mode 100644 index 00000000..6f5738ec --- /dev/null +++ b/deploy-dev.sh @@ -0,0 +1,123 @@ +#!/bin/bash +export $(grep -v '^#' docker/.env | sed 's/ *= */=/g' | xargs) + +IS_GREEN_EXIST=$(sudo docker ps | grep green) +DIR=~/docker + +SPRING_GREEN_PORT=4001 +SPRING_BLUE_PORT=4002 +NESTJS_GREEN_PORT=3001 +NESTJS_BLUE_PORT=3002 +DOCKERHUB_PASSWORD=$DOCKERHUB_PASSWORD + +SPRING_GREEN_NAME="spring-green" +SPRING_BLUE_NAME="spring-blue" +NESTJS_GREEN_NAME="nestjs-green" +NESTJS_BLUE_NAME="nestjs-blue" + + +# green up +if [ -z $IS_GREEN_EXIST ];then + echo "### BLUE -> GREEN ####" + echo ">>> pull green image" + + sudo docker login -u makerscrew -p DOCKERHUB_PASSWORD + + (cd $DIR && sudo docker compose pull caddy) + (cd $DIR && sudo docker compose pull swagger) + (cd $DIR && sudo docker compose pull nestjs-green) + (cd $DIR && sudo docker compose pull spring-green) + + (cd $DIR && sed -i "s/^SPRING_NAME=.*/SPRING_NAME=${SPRING_GREEN_NAME}/" .env) + (cd $DIR && sed -i "s/^NESTJS_NAME=.*/NESTJS_NAME=${NESTJS_GREEN_NAME}/" .env) + + (cd $DIR && sudo docker network create caddy) + + sleep 5 + + echo ">>> up green container" + (cd $DIR && sudo docker compose --env-file .env -f docker-compose.yml -f docker-compose.dev.yml up caddy nestjs-green spring-green swagger -d --build) + + while [ 1 = 1 ]; do + echo ">>> nestjs green health check ..." + sleep 3 + STATUS_CODE_NESTJS=$(curl -o /dev/null -s -w "%{http_code}" localhost:3001/) + if [ "$STATUS_CODE_NESTJS" -eq 200 ]; then + echo ">>> nestjs health check success !" + break; + fi + done; + + + while [ 1 = 1 ]; do + echo ">>> spring green health check ..." + sleep 3 + STATUS_CODE_SPRING=$(curl -o /dev/null -s -w "%{http_code}" localhost:4001/health/v2) + if [ "$STATUS_CODE_SPRING" -eq 200 ]; then + echo ">>> spring health check success !" + break; + fi + done; + + sleep 3 + echo ">>> down blue container" + (cd $DIR && sudo docker compose stop spring-blue) + (cd $DIR && sudo docker compose stop nestjs-blue) + + (cd $DIR && sudo docker compose rm -f spring-blue) + (cd $DIR && sudo docker compose rm -f nestjs-blue) + +# blue up +else + echo "### GREEN -> BLUE ###" + echo ">>> pull blue image" + + sudo docker login -u makerscrew -p DOCKERHUB_PASSWORD + + (cd $DIR && sudo docker compose pull caddy) + (cd $DIR && sudo docker compose pull swagger) + (cd $DIR && sudo docker compose pull nestjs-blue) + (cd $DIR && sudo docker compose pull spring-blue) + + (cd $DIR && sed -i "s/^SPRING_NAME=.*/SPRING_NAME=${SPRING_BLUE_NAME}/" .env) + (cd $DIR && sed -i "s/^NESTJS_NAME=.*/NESTJS_NAME=${NESTJS_BLUE_NAME}/" .env) + + (cd $DIR && sudo docker network create caddy) + + sleep 5 + + echo ">>> up blue container" + (cd $DIR && sudo docker compose --env-file .env -f docker-compose.yml -f docker-compose.dev.yml up caddy nestjs-blue spring-blue swagger -d --build) + + + while [ 1 = 1 ]; do + echo ">>> nestjs blue health check ..." + sleep 3 + STATUS_CODE_NESTJS=$(curl -o /dev/null -s -w "%{http_code}" localhost:3002/) + if [ "$STATUS_CODE_NESTJS" -eq 200 ]; then + echo ">>> nestjs health check success !" + break; + fi + done; + + + while [ 1 = 1 ]; do + echo ">>> spring blue health check ..." + sleep 3 + STATUS_CODE_SPRING=$(curl -o /dev/null -s -w "%{http_code}" localhost:4002/health/v2) + if [ "$STATUS_CODE_SPRING" -eq 200 ]; then + echo ">>> spring health check success !" + break; + fi + done; + + sleep 3 + echo ">>> down green container" + (cd $DIR && sudo docker compose stop spring-green) + (cd $DIR && sudo docker compose stop nestjs-green) + + (cd $DIR && sudo docker compose rm -f spring-green) + (cd $DIR && sudo docker compose rm -f spring-green) +fi + +sudo docker image prune -f \ No newline at end of file diff --git a/deploy-prod.sh b/deploy-prod.sh new file mode 100644 index 00000000..0bc84e4f --- /dev/null +++ b/deploy-prod.sh @@ -0,0 +1,123 @@ +#!/bin/bash +export $(grep -v '^#' docker/.env | sed 's/ *= */=/g' | xargs) + +IS_GREEN_EXIST=$(sudo docker ps | grep green) +DIR=~/docker + +SPRING_GREEN_PORT=4001 +SPRING_BLUE_PORT=4002 +NESTJS_GREEN_PORT=3001 +NESTJS_BLUE_PORT=3002 +DOCKERHUB_PASSWORD=$DOCKERHUB_PASSWORD + +SPRING_GREEN_NAME="spring-green" +SPRING_BLUE_NAME="spring-blue" +NESTJS_GREEN_NAME="nestjs-green" +NESTJS_BLUE_NAME="nestjs-blue" + + +# green up +if [ -z $IS_GREEN_EXIST ];then + echo "### BLUE -> GREEN ####" + echo ">>> pull green image" + + sudo docker login -u makerscrew -p DOCKERHUB_PASSWORD + + (cd $DIR && sudo docker compose pull caddy) + (cd $DIR && sudo docker compose pull swagger) + (cd $DIR && sudo docker compose pull nestjs-green) + (cd $DIR && sudo docker compose pull spring-green) + + (cd $DIR && sed -i "s/^SPRING_NAME=.*/SPRING_NAME=${SPRING_GREEN_NAME}/" .env) + (cd $DIR && sed -i "s/^NESTJS_NAME=.*/NESTJS_NAME=${NESTJS_GREEN_NAME}/" .env) + + (cd $DIR && sudo docker network create caddy) + + sleep 5 + + echo ">>> up green container" + (cd $DIR && sudo docker compose --env-file .env -f docker-compose.yml -f docker-compose.prod.yml up caddy nestjs-green spring-green swagger -d --build) + + while [ 1 = 1 ]; do + echo ">>> nestjs green health check ..." + sleep 3 + STATUS_CODE_NESTJS=$(curl -o /dev/null -s -w "%{http_code}" localhost:3001/) + if [ "$STATUS_CODE_NESTJS" -eq 200 ]; then + echo ">>> nestjs health check success !" + break; + fi + done; + + + while [ 1 = 1 ]; do + echo ">>> spring green health check ..." + sleep 3 + STATUS_CODE_SPRING=$(curl -o /dev/null -s -w "%{http_code}" localhost:4001/health/v2) + if [ "$STATUS_CODE_SPRING" -eq 200 ]; then + echo ">>> spring health check success !" + break; + fi + done; + + sleep 3 + echo ">>> down blue container" + (cd $DIR && sudo docker compose stop spring-blue) + (cd $DIR && sudo docker compose stop nestjs-blue) + + (cd $DIR && sudo docker compose rm -f spring-blue) + (cd $DIR && sudo docker compose rm -f nestjs-blue) + +# blue up +else + echo "### GREEN -> BLUE ###" + echo ">>> pull blue image" + + sudo docker login -u makerscrew -p DOCKERHUB_PASSWORD + + (cd $DIR && sudo docker compose pull caddy) + (cd $DIR && sudo docker compose pull swagger) + (cd $DIR && sudo docker compose pull nestjs-blue) + (cd $DIR && sudo docker compose pull spring-blue) + + (cd $DIR && sed -i "s/^SPRING_NAME=.*/SPRING_NAME=${SPRING_BLUE_NAME}/" .env) + (cd $DIR && sed -i "s/^NESTJS_NAME=.*/NESTJS_NAME=${NESTJS_BLUE_NAME}/" .env) + + (cd $DIR && sudo docker network create caddy) + + sleep 5 + + echo ">>> up blue container" + (cd $DIR && sudo docker compose --env-file .env -f docker-compose.yml -f docker-compose.prod.yml up caddy nestjs-blue spring-blue swagger -d --build) + + + while [ 1 = 1 ]; do + echo ">>> nestjs blue health check ..." + sleep 3 + STATUS_CODE_NESTJS=$(curl -o /dev/null -s -w "%{http_code}" localhost:3002/) + if [ "$STATUS_CODE_NESTJS" -eq 200 ]; then + echo ">>> nestjs health check success !" + break; + fi + done; + + + while [ 1 = 1 ]; do + echo ">>> spring blue health check ..." + sleep 3 + STATUS_CODE_SPRING=$(curl -o /dev/null -s -w "%{http_code}" localhost:4002/health/v2) + if [ "$STATUS_CODE_SPRING" -eq 200 ]; then + echo ">>> spring health check success !" + break; + fi + done; + + sleep 3 + echo ">>> down green container" + (cd $DIR && sudo docker compose stop spring-green) + (cd $DIR && sudo docker compose stop nestjs-green) + + (cd $DIR && sudo docker compose rm -f spring-green) + (cd $DIR && sudo docker compose rm -f spring-green) +fi + +sudo docker image prune -f \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c20624d8..1746571d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -5,14 +5,26 @@ services: labels: caddy: localhost crew.api.dev.sopt.org - spring: + nestjs-green: + environment: + - NODE_ENV=dev + labels: + caddy: localhost crew.api.dev.sopt.org + + spring-green: environment: - SPRING_PROFILES_ACTIVE=dev labels: caddy: localhost crew.api.dev.sopt.org - nestjs: + nestjs-blue: environment: - NODE_ENV=dev labels: caddy: localhost crew.api.dev.sopt.org + + spring-blue: + environment: + - SPRING_PROFILES_ACTIVE=dev + labels: + caddy: localhost crew.api.dev.sopt.org diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 4a791899..7c6da8d6 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -5,14 +5,26 @@ services: labels: caddy: localhost - spring: + spring-green: environment: - SPRING_PROFILES_ACTIVE=dev labels: caddy: localhost - nestjs: + nestjs-green: environment: - NODE_ENV=dev labels: caddy: localhost + + spring-blue: + environment: + - SPRING_PROFILES_ACTIVE=dev + labels: + caddy: localhost + + nestjs-blue: + environment: + - NODE_ENV=dev + labels: + caddy: localhost \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4343357a..29081fba 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -4,15 +4,26 @@ services: swagger: labels: caddy: localhost crew.api.prod.sopt.org - - spring: + spring-green: environment: - SPRING_PROFILES_ACTIVE=prod labels: caddy: localhost crew.api.prod.sopt.org - nestjs: + nestjs-green: environment: - NODE_ENV=prod labels: caddy: localhost crew.api.prod.sopt.org + + spring-blue: + environment: + - SPRING_PROFILES_ACTIVE=prod + labels: + caddy: localhost crew.api.prod.sopt.org + + nestjs-blue: + environment: + - NODE_ENV=prod + labels: + caddy: localhost crew.api.prod.sopt.org \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 7f723aeb..b4e16cd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,15 +13,12 @@ services: - caddy volumes: - /var/run/docker.sock:/var/run/docker.sock - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "10" restart: unless-stopped swagger: image: swaggerapi/swagger-ui + env_file: + - ./.env container_name: swagger environment: - URLS= @@ -30,35 +27,87 @@ services: {url:'/api-docs-json', name:'nestjs'}, ] - BASE_URL=/docs + - SPRING_NAME=${SPRING_NAME} + - NESTJS_NAME=${NESTJS_NAME} depends_on: - - nestjs - - spring + - ${SPRING_NAME} + - ${NESTJS_NAME} networks: - caddy - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "10" labels: caddy.route: /docs* caddy.route.reverse_proxy: "{{ upstreams 8080 }}" - spring: - build: - context: ./main - dockerfile: Dockerfile - container_name: spring + nestjs-green: + image: makerscrew/server:latest + env_file: + - ./.env + environment: + - TZ=Asia/Seoul + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DB_USERNAME=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + - DB_NAME=${DB_NAME} + - DB_SCHEMA=${DB_SCHEMA} + - AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME} + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_REGION=${AWS_REGION} + - JWT_SECRET=${JWT_SECRET} + + container_name: nestjs-green + ports: + - 3001:3000 restart: unless-stopped + networks: + - caddy + labels: + # for Swagger spec + caddy.route_0: /api-docs-json + caddy.route_0.reverse_proxy: "{{ upstreams 3000 }}" + # for health check + caddy.route_1: / + caddy.route_1.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_2: /auth + caddy.route_2.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_3: /comment/v1 + caddy.route_3.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_4: /comment/v1/* + caddy.route_4.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_5: /meeting/apply + caddy.route_5.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_6: /meeting + caddy.route_6.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_7: /meeting/v1/* + caddy.route_7.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_8: /meeting/* + caddy.route_8.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_9: /notice/v1 + caddy.route_9.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_10: /notice/v1/* + caddy.route_10.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_11: /post/v1 + caddy.route_11.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_12: /post/v1/* + caddy.route_12.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_13: /users + caddy.route_13.reverse_proxy: "{{ upstreams 3000 }}" + caddy.route_14: /users/* + caddy.route_14.reverse_proxy: "{{ upstreams 3000 }}" + + + + spring-green: + image: makerscrew/main:latest environment: - TZ=Asia/Seoul + container_name: spring-green + ports: + - 4001:4000 + restart: unless-stopped depends_on: - - nestjs - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "10" + - nestjs-green networks: - caddy labels: @@ -66,7 +115,7 @@ services: caddy.route_0: /api-docs/json caddy.route_0.reverse_proxy: "{{ upstreams 4000 }}" # for health check - caddy.route_1: /health + caddy.route_1: /health/* caddy.route_1.reverse_proxy: "{{ upstreams 4000 }}" caddy.route_2: /user/v2 caddy.route_2.reverse_proxy: "{{ upstreams 4000 }}" @@ -79,21 +128,30 @@ services: caddy.route_6: /comment/v2 caddy.route_6.reverse_proxy: "{{ upstreams 4000 }}" - nestjs: - build: - context: ./server - dockerfile: Dockerfile - container_name: nestjs - restart: unless-stopped + nestjs-blue: + image: makerscrew/server:latest + env_file: + - ./.env environment: - TZ=Asia/Seoul + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DB_USERNAME=${DB_USERNAME} + - DB_PASSWORD=${DB_PASSWORD} + - DB_NAME=${DB_NAME} + - DB_SCHEMA=${DB_SCHEMA} + - AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME} + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_REGION=${AWS_REGION} + - JWT_SECRET=${JWT_SECRET} + + container_name: nestjs-blue + ports: + - 3002:3000 + restart: unless-stopped networks: - caddy - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "10" labels: # for Swagger spec caddy.route_0: /api-docs-json @@ -128,6 +186,33 @@ services: caddy.route_14: /users/* caddy.route_14.reverse_proxy: "{{ upstreams 3000 }}" + + spring-blue: + image: makerscrew/main:latest + environment: + - TZ=Asia/Seoul + container_name: spring-blue + ports: + - 4002:4000 + restart: unless-stopped + depends_on: + - nestjs-blue + networks: + - caddy + labels: + # for Swagger spec + caddy.route_0: /api-docs/json + caddy.route_0.reverse_proxy: "{{ upstreams 4000 }}" + # for health check + caddy.route_1: /health/* + caddy.route_1.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_2: /user/v2 + caddy.route_2.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_3: /user/v2/* + caddy.route_3.reverse_proxy: "{{ upstreams 4000 }}" + caddy.route_4: /meeting/v2/* + caddy.route_4.reverse_proxy: "{{ upstreams 4000 }}" + networks: caddy: external: true diff --git a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java index 53c5a2e0..a586c7ee 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/common/config/SecurityConfig.java @@ -25,116 +25,120 @@ @EnableWebSecurity public class SecurityConfig { - private final JwtTokenProvider jwtTokenProvider; - private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final JwtTokenProvider jwtTokenProvider; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; - private static final String[] SWAGGER_URL = { - "/swagger-resources/**", - "/favicon.ico", - "/api-docs/**", - "/swagger-ui/**", - "/swagger-ui.html", - "/swagger-ui/index.html", - "/docs/swagger-ui/index.html", - "/swagger-ui/swagger-ui.css", - }; + private static final String[] SWAGGER_URL = { + "/swagger-resources/**", + "/favicon.ico", + "/api-docs/**", + "/swagger-ui/**", + "/swagger-ui.html", + "/swagger-ui/index.html", + "/docs/swagger-ui/index.html", + "/swagger-ui/swagger-ui.css", + }; - private static final String[] AUTH_WHITELIST = { - "/health", - "/meeting/v2/org-user/**" - }; + private static final String[] AUTH_WHITELIST = { + "/health", + "/health/v2", + "/meeting/v2/org-user/**" + }; - @Bean - @Profile("dev") - SecurityFilterChain devSecurityFilterChain(HttpSecurity http) throws Exception { - http.csrf((csrfConfig) -> csrfConfig.disable()) - .cors(Customizer.withDefaults()) - .sessionManagement( - (sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests( - authorize -> authorize.requestMatchers(Stream - .of(SWAGGER_URL) - .map(AntPathRequestMatcher::antMatcher) - .toArray(AntPathRequestMatcher[]::new)).permitAll() - .requestMatchers(Stream + @Bean + @Profile("dev") + SecurityFilterChain devSecurityFilterChain(HttpSecurity http) throws Exception { + http.csrf((csrfConfig) -> csrfConfig.disable()) + .cors(Customizer.withDefaults()) + .sessionManagement( + (sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests( + authorize -> authorize.requestMatchers(Stream + .of(SWAGGER_URL) + .map(AntPathRequestMatcher::antMatcher) + .toArray(AntPathRequestMatcher[]::new)).permitAll() + .requestMatchers(Stream .of(AUTH_WHITELIST) .map(AntPathRequestMatcher::antMatcher) .toArray(AntPathRequestMatcher[]::new)).permitAll() - .anyRequest().authenticated()) - .addFilterBefore( - new JwtAuthenticationFilter(this.jwtTokenProvider, this.jwtAuthenticationEntryPoint), - UsernamePasswordAuthenticationFilter.class) - .exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); - return http.build(); - } + .anyRequest().authenticated()) + .addFilterBefore( + new JwtAuthenticationFilter(this.jwtTokenProvider, + this.jwtAuthenticationEntryPoint), + UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); + return http.build(); + } - @Bean - @Profile("test") - SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { - http.csrf((csrfConfig) -> csrfConfig.disable()) + @Bean + @Profile("test") + SecurityFilterChain testSecurityFilterChain(HttpSecurity http) throws Exception { + http.csrf((csrfConfig) -> csrfConfig.disable()) .cors(Customizer.withDefaults()) .sessionManagement( - (sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS)) + (sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)) .authorizeHttpRequests( - authorize -> authorize.requestMatchers(Stream - .of(SWAGGER_URL) - .map(AntPathRequestMatcher::antMatcher) - .toArray(AntPathRequestMatcher[]::new)).permitAll() - .requestMatchers(Stream - .of(AUTH_WHITELIST) - .map(AntPathRequestMatcher::antMatcher) - .toArray(AntPathRequestMatcher[]::new)).permitAll() - .anyRequest().authenticated()) + authorize -> authorize.requestMatchers(Stream + .of(SWAGGER_URL) + .map(AntPathRequestMatcher::antMatcher) + .toArray(AntPathRequestMatcher[]::new)).permitAll() + .requestMatchers(Stream + .of(AUTH_WHITELIST) + .map(AntPathRequestMatcher::antMatcher) + .toArray(AntPathRequestMatcher[]::new)).permitAll() + .anyRequest().authenticated()) .addFilterBefore( - new JwtAuthenticationFilter(this.jwtTokenProvider, this.jwtAuthenticationEntryPoint), - UsernamePasswordAuthenticationFilter.class) + new JwtAuthenticationFilter(this.jwtTokenProvider, + this.jwtAuthenticationEntryPoint), + UsernamePasswordAuthenticationFilter.class) .exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); - return http.build(); - } + .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); + return http.build(); + } - @Bean - @Profile("prod") - SecurityFilterChain prodSecurityFilterChain(HttpSecurity http) throws Exception { - http.csrf((csrfConfig) -> csrfConfig.disable()) - .cors(Customizer.withDefaults()) - .sessionManagement( - (sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests( + @Bean + @Profile("prod") + SecurityFilterChain prodSecurityFilterChain(HttpSecurity http) throws Exception { + http.csrf((csrfConfig) -> csrfConfig.disable()) + .cors(Customizer.withDefaults()) + .sessionManagement( + (sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests( authorize -> authorize.requestMatchers(Stream - .of(SWAGGER_URL) - .map(AntPathRequestMatcher::antMatcher) - .toArray(AntPathRequestMatcher[]::new)).permitAll() - .requestMatchers(Stream - .of(AUTH_WHITELIST) - .map(AntPathRequestMatcher::antMatcher) - .toArray(AntPathRequestMatcher[]::new)).permitAll() - .anyRequest().authenticated()) - .addFilterBefore( - new JwtAuthenticationFilter(this.jwtTokenProvider, this.jwtAuthenticationEntryPoint), - UsernamePasswordAuthenticationFilter.class) - .exceptionHandling(exceptionHandling -> exceptionHandling - .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); - return http.build(); - } + .of(SWAGGER_URL) + .map(AntPathRequestMatcher::antMatcher) + .toArray(AntPathRequestMatcher[]::new)).permitAll() + .requestMatchers(Stream + .of(AUTH_WHITELIST) + .map(AntPathRequestMatcher::antMatcher) + .toArray(AntPathRequestMatcher[]::new)).permitAll() + .anyRequest().authenticated()) + .addFilterBefore( + new JwtAuthenticationFilter(this.jwtTokenProvider, + this.jwtAuthenticationEntryPoint), + UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint(this.jwtAuthenticationEntryPoint)); + return http.build(); + } - @Bean - CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins( - Arrays.asList("https://playground.sopt.org/", "http://localhost:3000/", - "https://sopt-internal-dev.pages.dev/")); - configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS")); - configuration.addAllowedHeader("*"); - configuration.setAllowCredentials(false); + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins( + Arrays.asList("https://playground.sopt.org/", "http://localhost:3000/", + "https://sopt-internal-dev.pages.dev/")); + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "DELETE", "OPTIONS")); + configuration.addAllowedHeader("*"); + configuration.setAllowCredentials(false); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } } \ No newline at end of file diff --git a/main/src/main/java/org/sopt/makers/crew/main/health/v1/HealthController.java b/main/src/main/java/org/sopt/makers/crew/main/health/v1/HealthController.java index 34816630..921d85ca 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/health/v1/HealthController.java +++ b/main/src/main/java/org/sopt/makers/crew/main/health/v1/HealthController.java @@ -12,10 +12,15 @@ @RequestMapping("/health") public class HealthController { - private final HealthService healthService; + private final HealthService healthService; - @GetMapping("") - public ResponseEntity getHealth() { - return this.healthService.getHealth(); - } + @GetMapping("") + public ResponseEntity getHealth() { + return this.healthService.getHealth(); + } + + @GetMapping("/v2") + public ResponseEntity getHealthV2() { + return ResponseEntity.status(200).body("Health Check V2"); + } } \ No newline at end of file