From 7613a2fd6a5c1ba895bc3a5a9a7d622ca0075a4d Mon Sep 17 00:00:00 2001 From: alstn113 Date: Wed, 8 Jan 2025 16:49:21 +0900 Subject: [PATCH] =?UTF-8?q?=EC=84=9C=EB=B2=84=EB=A5=BC=20=EB=AC=B4?= =?UTF-8?q?=EC=A4=91=EB=8B=A8=20=EB=B0=B0=ED=8F=AC=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EA=B2=8C=20=ED=95=9C=EB=8B=A4.=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/server_cd.yml | 6 +- README.md | 17 +- server/build.gradle | 3 + server/compose.local.yml | 2 +- server/compose.yml | 34 ++- server/scripts/deploy.sh | 50 ++++ server/src/main/resources/application.yml | 19 +- .../main/resources/db/migration/V1__init.sql | 247 ++++-------------- 8 files changed, 165 insertions(+), 213 deletions(-) create mode 100644 server/scripts/deploy.sh diff --git a/.github/workflows/server_cd.yml b/.github/workflows/server_cd.yml index 5dc41dc..8f42997 100644 --- a/.github/workflows/server_cd.yml +++ b/.github/workflows/server_cd.yml @@ -84,11 +84,13 @@ jobs: - name: View run: cat .env - - name: Compose Docker image + - name: Compose Docker image and Update Nginx configuration env: DOCKER_APP_IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} - run: docker compose -f compose.yml up -d + run: | + chmod +x ./scripts/deploy.sh # 실행 권한 부여 + sudo -E ./scripts/deploy.sh # 환경 변수 유지 및 실행 - name: Docker remove unused images run: docker image prune -af diff --git a/README.md b/README.md index d6c7e09..1926b49 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,18 @@ **▷ 개발 기간 : 2024.11 ~ 현재
** **▷ 개발 인원 : 1명** -서버 주소: https://api.fluppy.run -API 명세서: https://api.fluffy.run/docs/index.html -웹 프론트엔드 주소: https://fluppy.run +## 프로젝트 소개 -## Server +플러피(Fluffy)는 온라인 시험 문제 제작 및 관리 서비스입니다. + +## 프로젝트 개요 + +- 서버 주소: https://api.fluppy.run +- API 명세서: https://api.fluffy.run/docs/index.html +- 웹 프론트엔드 주소: https://fluppy.run +- 서비스 개발 블로그: https://alstn113.tistory.com/tag/플러피 + +## 서버 ### 기술 스택 @@ -16,7 +23,7 @@ API 명세서: https://api.fluffy.run/docs/index.html - redis - supabase(postgreSQL) -## Web Frontend +## 웹 프론트엔드 ### 기술 스택 diff --git a/server/build.gradle b/server/build.gradle index e231346..a486cd4 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -68,6 +68,9 @@ dependencies { annotationProcessor 'jakarta.annotation:jakarta.annotation-api' annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + // monitoring + implementation 'org.springframework.boot:spring-boot-starter-actuator' + // test testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' diff --git a/server/compose.local.yml b/server/compose.local.yml index 63cdc80..7eeae7c 100644 --- a/server/compose.local.yml +++ b/server/compose.local.yml @@ -1,6 +1,6 @@ services: redis: - container_name: fluffy-redis + container_name: redis image: redis:7.4.1 restart: always ports: diff --git a/server/compose.yml b/server/compose.yml index 85ca484..2e73ad3 100644 --- a/server/compose.yml +++ b/server/compose.yml @@ -1,18 +1,28 @@ +x-app: &app + image: ${DOCKER_APP_IMAGE} + env_file: + - .env + environment: + TZ: Asia/Seoul + SPRING_PROFILES_ACTIVE: prod + restart: always + depends_on: + - redis + services: - app: - container_name: fluffy-app - image: ${DOCKER_APP_IMAGE} - ports: - - '8080:8080' - env_file: - - .env - environment: - TZ: Asia/Seoul - SPRING_PROFILES_ACTIVE: prod - restart: always redis: - container_name: fluffy-redis + container_name: redis image: redis:7.4.1 restart: always ports: - '6379:6379' + app-blue: + <<: *app + container_name: app-blue + ports: + - '8080:8080' + app-green: + <<: *app + container_name: app-green + ports: + - '8081:8080' diff --git a/server/scripts/deploy.sh b/server/scripts/deploy.sh new file mode 100644 index 0000000..3b3deca --- /dev/null +++ b/server/scripts/deploy.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +NGINX_CONFIG_PATH="/etc/nginx/sites-available/api.fluffy.run" +HOST_HEALTH_CHECK_ENDPOINT="http://localhost:8082/actuator/health" +HEALTH_CHECK_ATTEMPTS=5 +HEALTH_CHECK_DELAY=3 + +health_check() { + for i in $(seq 1 $HEALTH_CHECK_ATTEMPTS); do + echo "Health check attempt ($i/$HEALTH_CHECK_ATTEMPTS)" + response=$(curl -s -o /dev/null -w "%{http_code}" $HOST_HEALTH_CHECK_ENDPOINT) + + if [ $response -eq 200 ]; then + echo "Health check passed" + return 0 + fi + + sleep $HEALTH_CHECK_DELAY + done + + echo "Health check failed" + return 1 +} + +switch_container() { + local prev_container=$1 + local next_container=$2 + + docker compose -f compose.yml up $next_container -d + + if ! health_check; then + echo "Health check failed, rolling back" + docker compose -f compose.yml down $next_container + return 1 + fi + + sed -i "s/server $prev_container:8080;/server $next_container:8080;/" "$NGINX_CONFIG_PATH" + + sudo nginx -s reload +} + +IS_GREEN=$(docker container ps | grep app-green) + +if [ -z "$IS_GREEN" ]; then + echo "### BLUE >> GREEN ###" + switch_container "app-blue" "app-green" +else + echo "### GREEN >> BLUE ###" + switch_container "app-green" "app-blue" +fi diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index 31b2b00..24a020b 100644 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -1,7 +1,6 @@ spring: profiles: active: local - --- spring: config: @@ -61,6 +60,14 @@ logging: org.springframework.orm.transaction: DEBUG org.hibernate.orm.jdbc.bind: trace +management: + server: + port: 8082 + endpoints: + web: + exposure: + include: health + --- spring: config: @@ -84,7 +91,7 @@ spring: ddl-auto: validate data: redis: - host: fluffy-redis + host: redis port: 6379 api-host: https://api.fluffy.run @@ -118,3 +125,11 @@ logging: org.springframework.orm.jpa: DEBUG org.springframework.orm.transaction: DEBUG org.hibernate.orm.jdbc.bind: trace + +management: + server: + port: 8082 + endpoints: + web: + exposure: + include: health \ No newline at end of file diff --git a/server/src/main/resources/db/migration/V1__init.sql b/server/src/main/resources/db/migration/V1__init.sql index 5d3ec9f..2e24869 100644 --- a/server/src/main/resources/db/migration/V1__init.sql +++ b/server/src/main/resources/db/migration/V1__init.sql @@ -1,225 +1,90 @@ CREATE TABLE IF NOT EXISTS member ( - id - BIGINT - GENERATED - BY - DEFAULT AS - IDENTITY, - name - VARCHAR -( - 255 -) NOT NULL, - email VARCHAR -( - 255 -), - avatar_url VARCHAR -( - 255 -) NOT NULL, - social_id VARCHAR -( - 255 -) NOT NULL, - provider VARCHAR -( - 20 -) NOT NULL, - created_at TIMESTAMP -( - 6 -) NOT NULL, - updated_at TIMESTAMP -( - 6 -) NOT NULL, - PRIMARY KEY -( - id -) - ); + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255), + avatar_url VARCHAR(255) NOT NULL, + social_id VARCHAR(255) NOT NULL, + provider VARCHAR(20) NOT NULL, + created_at TIMESTAMP(6) NOT NULL, + updated_at TIMESTAMP(6) NOT NULL, + PRIMARY KEY (id) +); CREATE TABLE IF NOT EXISTS exam ( - id - BIGINT - GENERATED - BY - DEFAULT AS - IDENTITY, - member_id - BIGINT - NOT - NULL, - title - VARCHAR -( - 255 -) NOT NULL, + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + member_id BIGINT NOT NULL, + title VARCHAR(255) NOT NULL, description TEXT NOT NULL, - status VARCHAR -( - 20 -) NOT NULL, - created_at TIMESTAMP -( - 6 -) NOT NULL, - start_at TIMESTAMP -( - 6 -), - end_at TIMESTAMP -( - 6 -), - updated_at TIMESTAMP -( - 6 -) NOT NULL, - PRIMARY KEY -( - id -) - ); + status VARCHAR(20) NOT NULL, + created_at TIMESTAMP(6) NOT NULL, + start_at TIMESTAMP(6), + end_at TIMESTAMP(6), + updated_at TIMESTAMP(6) NOT NULL, + PRIMARY KEY (id) +); CREATE TABLE IF NOT EXISTS answer ( - id - BIGINT - GENERATED - BY - DEFAULT AS - IDENTITY, - question_id - BIGINT - NOT - NULL, - submission_id - BIGINT, - text - VARCHAR -( - 255 -), - PRIMARY KEY -( - id -) - ); + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + question_id BIGINT NOT NULL, + submission_id BIGINT, + text VARCHAR(255), + PRIMARY KEY (id) +); CREATE TABLE IF NOT EXISTS answer_choice ( - answer_id - BIGINT - NOT - NULL, - question_option_id - BIGINT - NOT - NULL + answer_id BIGINT NOT NULL, + question_option_id BIGINT NOT NULL ); CREATE TABLE IF NOT EXISTS question ( - id - BIGINT - GENERATED - BY - DEFAULT AS - IDENTITY, - exam_id - BIGINT, - text - VARCHAR -( - 255 -) NOT NULL, - correct_answer VARCHAR -( - 255 -), - type VARCHAR -( - 50 -) NOT NULL, - PRIMARY KEY -( - id -) - ); + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + exam_id BIGINT, + text VARCHAR(255) NOT NULL, + correct_answer VARCHAR(255), + type VARCHAR(50) NOT NULL, + PRIMARY KEY (id) +); CREATE TABLE IF NOT EXISTS question_option ( - id - BIGINT - GENERATED - BY - DEFAULT AS - IDENTITY, - question_id - BIGINT, - text - VARCHAR -( - 255 -) NOT NULL, - is_correct BOOLEAN NOT NULL, - PRIMARY KEY -( - id -) - ); + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + question_id BIGINT, + text VARCHAR(255) NOT NULL, + is_correct BOOLEAN NOT NULL, + PRIMARY KEY (id) +); CREATE TABLE IF NOT EXISTS submission ( - id - BIGINT - GENERATED - BY - DEFAULT AS - IDENTITY, - exam_id - BIGINT - NOT - NULL, - member_id - BIGINT - NOT - NULL, - created_at - TIMESTAMP -( - 6 -) NOT NULL, - updated_at TIMESTAMP -( - 6 -) NOT NULL, - PRIMARY KEY -( - id -) - ); + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + exam_id BIGINT NOT NULL, + member_id BIGINT NOT NULL, + created_at TIMESTAMP(6) NOT NULL, + updated_at TIMESTAMP(6) NOT NULL, + PRIMARY KEY (id) +); ALTER TABLE IF EXISTS answer ADD CONSTRAINT fk_submission - FOREIGN KEY (submission_id) - REFERENCES submission (id); + FOREIGN KEY (submission_id) + REFERENCES submission (id); ALTER TABLE IF EXISTS answer_choice ADD CONSTRAINT fk_answer - FOREIGN KEY (answer_id) - REFERENCES answer (id); + FOREIGN KEY (answer_id) + REFERENCES answer (id); ALTER TABLE IF EXISTS question ADD CONSTRAINT fk_exam - FOREIGN KEY (exam_id) - REFERENCES exam (id); + FOREIGN KEY (exam_id) + REFERENCES exam (id); ALTER TABLE IF EXISTS question_option ADD CONSTRAINT fk_question - FOREIGN KEY (question_id) - REFERENCES question (id); + FOREIGN KEY (question_id) + REFERENCES question (id); \ No newline at end of file