From a0fe53ba6aeec51fe5de7c9c74021d68e2c2aec0 Mon Sep 17 00:00:00 2001 From: randirao Date: Tue, 6 Jan 2026 11:56:44 +0900 Subject: [PATCH 1/9] =?UTF-8?q?fix:=20=EC=8A=B5=EB=93=9D=20=EC=8B=A0?= =?UTF-8?q?=EA=B3=A0=EC=9E=90=20=EC=A0=95=EB=B3=B4=20=ED=95=84=EC=88=98=20?= =?UTF-8?q?->=20=EC=84=A0=ED=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ItemRegistrationService.java | 16 +++++++++++++++- .../dto/request/ItemRegistrationForm.java | 8 +++----- .../presentation/dto/request/ItemUpdateForm.java | 6 ++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/eod/eod/domain/item/application/ItemRegistrationService.java b/src/main/java/com/eod/eod/domain/item/application/ItemRegistrationService.java index 6f514a6..4ab5d5b 100644 --- a/src/main/java/com/eod/eod/domain/item/application/ItemRegistrationService.java +++ b/src/main/java/com/eod/eod/domain/item/application/ItemRegistrationService.java @@ -54,9 +54,23 @@ private LocalDateTime validateAndParseFoundAt(String foundAt, Long placeId) { } /** - * 학생 코드와 이름으로 학생 조회 + * 학생 코드와 이름으로 학생 조회 (선택적) + * + * @param reporterStudentCode 신고자 학생 코드 (null 가능) + * @param reporterName 신고자 이름 (null 가능) + * @return 학생 정보 또는 null (둘 다 null인 경우) */ private User findStudentByCodeAndName(Integer reporterStudentCode, String reporterName) { + // 둘 다 null이면 신고자 정보 없음 + if (reporterStudentCode == null && (reporterName == null || reporterName.isBlank())) { + return null; + } + + // 둘 중 하나만 있으면 에러 + if (reporterStudentCode == null || reporterName == null || reporterName.isBlank()) { + throw new IllegalArgumentException("신고자 학생 코드와 이름은 함께 입력해야 합니다."); + } + int grade = reporterStudentCode / 1000; int classNo = (reporterStudentCode / 100) % 10; int studentNo = reporterStudentCode % 100; diff --git a/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java b/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java index dbb6fbe..cd3547b 100644 --- a/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java +++ b/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java @@ -19,15 +19,13 @@ public class ItemRegistrationForm { @Schema(description = "물품 이름", example = "아이패드", requiredMode = Schema.RequiredMode.REQUIRED) private String name; - @NotNull(message = "필수 항목이 누락되었습니다.") @Min(value = 1101, message = "신고자 학생 코드는 1101 이상이어야 합니다.") @Max(value = 3417, message = "신고자 학생 코드는 3417 이하여야 합니다.") - @Schema(description = "신고자 학생 코드", example = "2109", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "신고자 학생 코드 (선택)", example = "2109", requiredMode = Schema.RequiredMode.NOT_REQUIRED) private Integer reporterStudentCode; - @NotBlank(message = "필수 항목이 누락되었습니다.") - @Size(max = 50, message = "신고자 이름은 최대 50자까지 입력 가능합니다.") - @Schema(description = "신고자 이름", example = "홍길동", requiredMode = Schema.RequiredMode.REQUIRED) + @Size(max = 50, message = "신고자 이름은 최대 10자까지 입력 가능합니다.") + @Schema(description = "신고자 이름 (선택)", example = "이하은", requiredMode = Schema.RequiredMode.NOT_REQUIRED) private String reporterName; @NotBlank(message = "필수 항목이 누락되었습니다.") diff --git a/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemUpdateForm.java b/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemUpdateForm.java index e980140..071f585 100644 --- a/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemUpdateForm.java +++ b/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemUpdateForm.java @@ -17,15 +17,13 @@ public class ItemUpdateForm { @Schema(description = "물품 이름", example = "아이패드", requiredMode = Schema.RequiredMode.REQUIRED) private String name; - @NotNull(message = "필수 항목이 누락되었습니다.") @Min(value = 1101, message = "신고자 학생 코드는 1101 이상이어야 합니다.") @Max(value = 3417, message = "신고자 학생 코드는 3417 이하여야 합니다.") - @Schema(description = "신고자 학생 코드", example = "2109", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "신고자 학생 코드 (선택)", example = "2109", requiredMode = Schema.RequiredMode.NOT_REQUIRED) private Integer reporterStudentCode; - @NotBlank(message = "필수 항목이 누락되었습니다.") @Size(max = 50, message = "신고자 이름은 최대 50자까지 입력 가능합니다.") - @Schema(description = "신고자 이름", example = "홍길동", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "신고자 이름 (선택)", example = "홍길동", requiredMode = Schema.RequiredMode.NOT_REQUIRED) private String reporterName; @NotBlank(message = "필수 항목이 누락되었습니다.") From aa0f5f1622f6f394dba875234e7e9cb621000bc2 Mon Sep 17 00:00:00 2001 From: randirao Date: Tue, 6 Jan 2026 11:58:16 +0900 Subject: [PATCH 2/9] =?UTF-8?q?style:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/presentation/dto/request/ItemRegistrationForm.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java b/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java index cd3547b..57bfbb0 100644 --- a/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java +++ b/src/main/java/com/eod/eod/domain/item/presentation/dto/request/ItemRegistrationForm.java @@ -6,8 +6,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.validator.constraints.Length; -import org.springframework.web.multipart.MultipartFile; @Getter @Setter From 13f16ca6944c3ab0d80cada2a4b7c19f1e3f4842 Mon Sep 17 00:00:00 2001 From: jaeminjo732 Date: Tue, 6 Jan 2026 15:46:05 +0900 Subject: [PATCH 3/9] =?UTF-8?q?fix=20::=20CD=20=ED=8C=8C=EC=9D=B4=ED=94=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EC=84=A4=EC=A0=95=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 개발/운영 환경 docker-compose 파일명 불일치 해결 - 운영 환경 전용 CD 파이프라인 추가 (cd-prod.yml) - 개발/운영 환경 Docker Compose 파일 분리 - 운영 환경 SSH secrets 분리 (PROD_* prefix) - MySQL 볼륨 이름 개발/운영 환경별 구분 (-dev, -prod) - CI 파이프라인에 production 브랜치 추가 Co-Authored-By: jaeminjo732 --- .github/workflows/cd-prod.yml | 195 ++++++++++++++++++++++++++++++++++ .github/workflows/cd.yml | 12 +-- .github/workflows/ci.yml | 4 +- docker-compose.prod.yml | 12 +-- docker-compose.yml | 61 +++++++++++ 5 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/cd-prod.yml create mode 100644 docker-compose.yml diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml new file mode 100644 index 0000000..6752b9d --- /dev/null +++ b/.github/workflows/cd-prod.yml @@ -0,0 +1,195 @@ +name: CD - Production Deploy + +on: + push: + branches: [production] + workflow_dispatch: + inputs: + confirm: + description: '운영 배포를 진행하시겠습니까?' + required: true + type: boolean + default: false + +env: + REGISTRY: ghcr.io + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: 코드 체크아웃 + uses: actions/checkout@v4 + + - name: Java 17 설정 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: 'gradle' + + - name: Gradle 실행 권한 부여 + run: chmod +x ./gradlew + + - name: 애플리케이션 JAR 빌드 + run: ./gradlew bootJar --no-daemon -x test + + - name: QEMU 설정 (ARM64 크로스 컴파일) + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + + - name: 이미지 이름 소문자 변환 + id: image_name + run: | + IMAGE_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + echo "name=${IMAGE_NAME}" >> $GITHUB_OUTPUT + echo "소문자 변환된 이미지 이름: ${IMAGE_NAME}" + + - name: Docker Buildx 설정 + uses: docker/setup-buildx-action@v3 + + - name: GitHub Container Registry 로그인 + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker 메타데이터 추출 + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ steps.image_name.outputs.name }} + tags: | + type=raw,value=production + type=sha,prefix=prod- + + - name: Docker 이미지 빌드 및 푸시 + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + platforms: linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=eod-prod-build + cache-to: type=gha,scope=eod-prod-build,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 + + - name: 이미지 다이제스트 출력 + run: echo ${{ steps.meta.outputs.digest }} + + deploy: + needs: build-and-push + runs-on: ubuntu-latest + + steps: + - name: 서버에 SSH 접속 및 Docker Compose 배포 + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PROD_SSH_HOST }} + username: ${{ secrets.PROD_SSH_USERNAME }} + key: ${{ secrets.PROD_SSH_KEY }} + port: ${{ secrets.PROD_SSH_PORT || 22 }} + script: | + # 배포 디렉토리 생성 및 이동 + mkdir -p ~/eod-prod + cd ~/eod-prod + + # GitHub Repository Clone 또는 Pull + if [ -d ".git" ]; then + echo "기존 저장소 업데이트 중..." + git fetch origin production + git reset --hard origin/production + else + echo "저장소 복제 중..." + rm -rf * .* 2>/dev/null || true + git clone -b production https://github.com/${{ github.repository }}.git . + fi + + # .env 파일 생성 (GitHub Secrets 사용) + REPO_LOWERCASE=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + cat > .env << EOF + GITHUB_REPOSITORY=${REPO_LOWERCASE} + DOCKER_IMAGE_TAG=production + SPRING_DATASOURCE_URL=${{ secrets.PROD_DB_URL }} + SPRING_DATASOURCE_USERNAME=${{ secrets.PROD_DB_USERNAME }} + SPRING_DATASOURCE_PASSWORD=${{ secrets.PROD_DB_PASSWORD }} + MYSQL_ROOT_PASSWORD=${{ secrets.PROD_MYSQL_ROOT_PASSWORD }} + MYSQL_DATABASE=${{ secrets.PROD_MYSQL_DATABASE }} + MYSQL_USER=${{ secrets.PROD_DB_USERNAME }} + MYSQL_PASSWORD=${{ secrets.PROD_DB_PASSWORD }} + JWT_SECRET=${{ secrets.JWT_SECRET }} + BASE_URL=${{ secrets.PROD_BASE_URL }} + FRONTEND_BASE_URL=${{ secrets.PROD_FRONTEND_BASE_URL }} + LOG_DIR=${{ secrets.PROD_LOG_DIR }} + BSM_CLIENT_ID=${{ secrets.BSM_CLIENT_ID }} + BSM_CLIENT_SECRET=${{ secrets.BSM_CLIENT_SECRET }} + BSM_OAUTH_BASE_URL=${{ secrets.BSM_OAUTH_BASE_URL }} + BSM_REDIRECT_URI=${{ secrets.BSM_REDIRECT_URI }} + FILE_UPLOAD_BASE_URL=${{ secrets.FILE_UPLOAD_BASE_URL }} + EOF + + # GitHub Container Registry 로그인 + echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + # 환경 변수 확인 (디버깅) + echo "GITHUB_REPOSITORY (소문자): ${REPO_LOWERCASE}" + + # Docker Compose로 배포 (production 태그 사용) + export DOCKER_IMAGE_TAG=production + docker compose -f docker-compose.prod.yml pull + docker compose -f docker-compose.prod.yml down + docker compose -f docker-compose.prod.yml up -d + + # 컨테이너 시작 대기 + echo "컨테이너 시작 대기 중..." + sleep 10 + + # 사용하지 않는 이미지 정리 + docker image prune -af + + - name: 배포 상태 확인 + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.PROD_SSH_HOST }} + username: ${{ secrets.PROD_SSH_USERNAME }} + key: ${{ secrets.SSH_KEY }} + port: ${{ secrets.PROD_SSH_PORT || 22 }} + script: | + cd ~/eod-prod + echo "=========================================" + echo "🚀 PRODUCTION 환경 배포 완료" + echo "=========================================" + echo "📋 환경 변수 확인 (민감 정보 마스킹):" + cat .env | sed 's/PASSWORD=.*/PASSWORD=***/g' | sed 's/SECRET=.*/SECRET=***/g' + echo "=========================================" + echo "🐳 컨테이너 실행 상태:" + docker compose -f docker-compose.prod.yml ps + echo "=========================================" + echo "📊 Docker 이미지 확인:" + docker images | grep eod + echo "=========================================" + echo "📝 MySQL 로그 (최근 20줄):" + docker compose -f docker-compose.prod.yml logs --tail=20 mysql + echo "=========================================" + echo "📝 애플리케이션 로그 (최근 50줄):" + docker compose -f docker-compose.prod.yml logs --tail=50 app + echo "=========================================" + echo "🔍 포트 사용 확인:" + netstat -tuln | grep -E ':(8000|3306)' + + - name: 배포 완료 알림 + if: success() + run: | + IMAGE_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + echo "✅ PRODUCTION Docker Compose 배포 완료!" + echo "서버: ${{ secrets.PROD_SSH_HOST }}" + echo "이미지: ${{ env.REGISTRY }}/${IMAGE_NAME}:production" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 1eb27e4..b514211 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -141,9 +141,9 @@ jobs: echo "GITHUB_REPOSITORY (소문자): ${REPO_LOWERCASE}" # Docker Compose로 배포 - docker compose -f docker-compose.prod.yml pull - docker compose -f docker-compose.prod.yml down - docker compose -f docker-compose.prod.yml up -d + docker compose -f docker-compose.yml pull + docker compose -f docker-compose.yml down + docker compose -f docker-compose.yml up -d # 컨테이너 시작 대기 echo "컨테이너 시작 대기 중..." @@ -166,16 +166,16 @@ jobs: cat .env | sed 's/PASSWORD=.*/PASSWORD=***/g' echo "=========================================" echo "🐳 컨테이너 실행 상태:" - docker compose -f docker-compose.prod.yml ps + docker compose -f docker-compose.yml ps echo "=========================================" echo "📊 Docker 이미지 확인:" docker images | grep eod echo "=========================================" echo "📝 MySQL 로그 (최근 20줄):" - docker compose -f docker-compose.prod.yml logs --tail=20 mysql + docker compose -f docker-compose.yml logs --tail=20 mysql echo "=========================================" echo "📝 애플리케이션 로그 (최근 50줄):" - docker compose -f docker-compose.prod.yml logs --tail=50 app + docker compose -f docker-compose.yml logs --tail=50 app echo "=========================================" echo "🔍 포트 사용 확인:" netstat -tuln | grep -E ':(8000|3306)' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbe3f8e..33a35ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ "main", "master", "develop" ] + branches: [ "main", "master", "develop", "production" ] pull_request: - branches: [ "main", "master", "develop" ] + branches: [ "main", "master", "develop", "production" ] jobs: build: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index fea1ba3..c017d07 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,11 +3,11 @@ services: image: ghcr.io/${GITHUB_REPOSITORY}:latest container_name: eod-app volumes: - - /eod/logs:/logs + - /eod/prod/logs:/logs - /eod/uploads:/eod/uploads restart: always ports: - - "8000:8080" + - "8020:8080" environment: - SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL} - SPRING_DATASOURCE_USERNAME=${SPRING_DATASOURCE_USERNAME} @@ -40,9 +40,9 @@ services: MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} ports: - - "3306:3306" + - "3307:3306" volumes: - - mysql-data:/var/lib/mysql + - mysql-data-prod:/var/lib/mysql networks: - eod-network healthcheck: @@ -52,9 +52,9 @@ services: retries: 10 volumes: - mysql-data: + mysql-data-prod: external: true - name: eod_mysql-data + name: eod_mysql-data-prod networks: eod-network: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5cdba1b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,61 @@ +services: + app: + image: ghcr.io/${GITHUB_REPOSITORY}:latest + container_name: eod-app-dev + volumes: + - /eod/logs:/logs + - /eod/uploads:/eod/uploads + restart: always + ports: + - "8000:8080" + environment: + - SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL} + - SPRING_DATASOURCE_USERNAME=${SPRING_DATASOURCE_USERNAME} + - SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD} + - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} + - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} + - JWT_SECRET=${JWT_SECRET} + - BASE_URL=${BASE_URL} + - FRONTEND_BASE_URL=${FRONTEND_BASE_URL} + - LOG_DIR=${LOG_DIR} + - BSM_CLIENT_ID=${BSM_CLIENT_ID} + - BSM_CLIENT_SECRET=${BSM_CLIENT_SECRET} + - BSM_OAUTH_BASE_URL=${BSM_OAUTH_BASE_URL} + - BSM_REDIRECT_URI=${BSM_REDIRECT_URI} + - FILE_UPLOAD_DIR=/eod/uploads + - FILE_UPLOAD_BASE_URL=${FILE_UPLOAD_BASE_URL} + depends_on: + mysql: + condition: service_healthy + networks: + - eod-network-dev + + mysql: + image: mysql:8.0 + container_name: eod-mysql-dev + restart: always + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} + MYSQL_DATABASE: ${MYSQL_DATABASE} + MYSQL_USER: ${MYSQL_USER} + MYSQL_PASSWORD: ${MYSQL_PASSWORD} + ports: + - "3306:3306" + volumes: + - mysql-data-dev:/var/lib/mysql + networks: + - eod-network-dev + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + interval: 10s + timeout: 5s + retries: 10 + +volumes: + mysql-data-dev: + external: true + name: eod_mysql-data-dev + +networks: + eod-network-dev: + driver: bridge From 405a8e9ae23541d95c727865cbbcff9e78a2a417 Mon Sep 17 00:00:00 2001 From: jaeminjo732 Date: Tue, 6 Jan 2026 15:59:58 +0900 Subject: [PATCH 4/9] =?UTF-8?q?fix(#162)=20:=20ssh=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-prod.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index 6752b9d..7db2421 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -159,10 +159,10 @@ jobs: - name: 배포 상태 확인 uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.PROD_SSH_HOST }} - username: ${{ secrets.PROD_SSH_USERNAME }} + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} key: ${{ secrets.SSH_KEY }} - port: ${{ secrets.PROD_SSH_PORT || 22 }} + port: ${{ secrets.SSH_PORT || 22 }} script: | cd ~/eod-prod echo "=========================================" From 465e38ce2383d846cd6f403b92d29bf61d0f78a2 Mon Sep 17 00:00:00 2001 From: jaeminjo732 Date: Tue, 6 Jan 2026 16:14:47 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix=20::=20CD=20=ED=8C=8C=EC=9D=B4=ED=94=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker compose up에 --remove-orphans 플래그 추가 - 배포 직후 컨테이너 상태 즉시 확인 로직 추가 - app 컨테이너 실행 검증 로직 추가 (실패 시 100줄 로그 출력) - 모든 grep 명령어에 || true 추가하여 오탐 방지 - SECRET 정보 마스킹 추가 - 운영 환경 포트 확인 수정 (8020, 3307) Co-Authored-By: jaeminjo732 --- .github/workflows/cd-prod.yml | 28 +++++++++++++++++++++++----- .github/workflows/cd.yml | 30 ++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index 7db2421..9d71ef6 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -144,10 +144,19 @@ jobs: echo "GITHUB_REPOSITORY (소문자): ${REPO_LOWERCASE}" # Docker Compose로 배포 (production 태그 사용) + echo "=========================================" + echo "🚀 PRODUCTION Docker Compose 배포 시작" + echo "=========================================" export DOCKER_IMAGE_TAG=production docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml down - docker compose -f docker-compose.prod.yml up -d + docker compose -f docker-compose.prod.yml up -d --remove-orphans + + # 배포 직후 상태 확인 + echo "=========================================" + echo "📊 배포 직후 컨테이너 상태:" + docker compose -f docker-compose.prod.yml ps + echo "=========================================" # 컨테이너 시작 대기 echo "컨테이너 시작 대기 중..." @@ -173,18 +182,27 @@ jobs: echo "=========================================" echo "🐳 컨테이너 실행 상태:" docker compose -f docker-compose.prod.yml ps + + # 컨테이너 실행 검증 (app 컨테이너가 반드시 떠있어야 함) + if ! docker compose -f docker-compose.prod.yml ps | grep -q "eod-app.*Up"; then + echo "❌ ERROR: app 컨테이너가 실행되지 않았습니다!" + docker compose -f docker-compose.prod.yml logs --tail=100 app + exit 1 + fi + echo "✅ app 컨테이너 정상 실행 확인" + echo "=========================================" echo "📊 Docker 이미지 확인:" - docker images | grep eod + docker images | grep eod || true echo "=========================================" echo "📝 MySQL 로그 (최근 20줄):" - docker compose -f docker-compose.prod.yml logs --tail=20 mysql + docker compose -f docker-compose.prod.yml logs --tail=20 mysql || true echo "=========================================" echo "📝 애플리케이션 로그 (최근 50줄):" - docker compose -f docker-compose.prod.yml logs --tail=50 app + docker compose -f docker-compose.prod.yml logs --tail=50 app || true echo "=========================================" echo "🔍 포트 사용 확인:" - netstat -tuln | grep -E ':(8000|3306)' + netstat -tuln | grep -E ':(8020|3307)' || true - name: 배포 완료 알림 if: success() diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index b514211..794c8b0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -141,9 +141,18 @@ jobs: echo "GITHUB_REPOSITORY (소문자): ${REPO_LOWERCASE}" # Docker Compose로 배포 + echo "=========================================" + echo "🚀 Docker Compose 배포 시작" + echo "=========================================" docker compose -f docker-compose.yml pull docker compose -f docker-compose.yml down - docker compose -f docker-compose.yml up -d + docker compose -f docker-compose.yml up -d --remove-orphans + + # 배포 직후 상태 확인 + echo "=========================================" + echo "📊 배포 직후 컨테이너 상태:" + docker compose -f docker-compose.yml ps + echo "=========================================" # 컨테이너 시작 대기 echo "컨테이너 시작 대기 중..." @@ -163,22 +172,31 @@ jobs: cd ~/eod echo "=========================================" echo "📋 환경 변수 확인 (민감 정보 마스킹):" - cat .env | sed 's/PASSWORD=.*/PASSWORD=***/g' + cat .env | sed 's/PASSWORD=.*/PASSWORD=***/g' | sed 's/SECRET=.*/SECRET=***/g' echo "=========================================" echo "🐳 컨테이너 실행 상태:" docker compose -f docker-compose.yml ps + + # 컨테이너 실행 검증 (app 컨테이너가 반드시 떠있어야 함) + if ! docker compose -f docker-compose.yml ps | grep -q "eod-app-dev.*Up"; then + echo "❌ ERROR: app 컨테이너가 실행되지 않았습니다!" + docker compose -f docker-compose.yml logs --tail=100 app + exit 1 + fi + echo "✅ app 컨테이너 정상 실행 확인" + echo "=========================================" echo "📊 Docker 이미지 확인:" - docker images | grep eod + docker images | grep eod || true echo "=========================================" echo "📝 MySQL 로그 (최근 20줄):" - docker compose -f docker-compose.yml logs --tail=20 mysql + docker compose -f docker-compose.yml logs --tail=20 mysql || true echo "=========================================" echo "📝 애플리케이션 로그 (최근 50줄):" - docker compose -f docker-compose.yml logs --tail=50 app + docker compose -f docker-compose.yml logs --tail=50 app || true echo "=========================================" echo "🔍 포트 사용 확인:" - netstat -tuln | grep -E ':(8000|3306)' + netstat -tuln | grep -E ':(8000|3306)' || true - name: 배포 완료 알림 if: success() From 88fb4be8b9816d40c7b90352ec6e289770443ead Mon Sep 17 00:00:00 2001 From: jaeminjo732 Date: Tue, 6 Jan 2026 16:28:01 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix=20::=20CD=20=ED=8C=8C=EC=9D=B4=ED=94=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=EC=97=90=20MySQL=20=EB=B3=BC=EB=A5=A8=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 배포 전 볼륨 존재 여부 확인 및 자동 생성 - 개발 환경: eod_mysql-data-dev 볼륨 자동 생성 - 운영 환경: eod_mysql-data-prod 볼륨 자동 생성 - "external volume not found" 오류 해결 Co-Authored-By: jaeminjo732 --- .github/workflows/cd-prod.yml | 12 ++++++++++++ .github/workflows/cd.yml | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index 9d71ef6..f95dbf7 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -143,6 +143,18 @@ jobs: # 환경 변수 확인 (디버깅) echo "GITHUB_REPOSITORY (소문자): ${REPO_LOWERCASE}" + # MySQL 볼륨 존재 여부 확인 및 생성 + echo "=========================================" + echo "📦 MySQL 볼륨 확인 중..." + if ! docker volume inspect eod_mysql-data-prod >/dev/null 2>&1; then + echo "⚠️ 볼륨이 존재하지 않습니다. 새로 생성합니다..." + docker volume create eod_mysql-data-prod + echo "✅ 볼륨 생성 완료: eod_mysql-data-prod" + else + echo "✅ 볼륨이 이미 존재합니다: eod_mysql-data-prod" + fi + echo "=========================================" + # Docker Compose로 배포 (production 태그 사용) echo "=========================================" echo "🚀 PRODUCTION Docker Compose 배포 시작" diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 794c8b0..d52a78c 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -140,6 +140,18 @@ jobs: # 환경 변수 확인 (디버깅) echo "GITHUB_REPOSITORY (소문자): ${REPO_LOWERCASE}" + # MySQL 볼륨 존재 여부 확인 및 생성 + echo "=========================================" + echo "📦 MySQL 볼륨 확인 중..." + if ! docker volume inspect eod_mysql-data-dev >/dev/null 2>&1; then + echo "⚠️ 볼륨이 존재하지 않습니다. 새로 생성합니다..." + docker volume create eod_mysql-data-dev + echo "✅ 볼륨 생성 완료: eod_mysql-data-dev" + else + echo "✅ 볼륨이 이미 존재합니다: eod_mysql-data-dev" + fi + echo "=========================================" + # Docker Compose로 배포 echo "=========================================" echo "🚀 Docker Compose 배포 시작" From bfb8c1adcf67ce0ba8dbba8169c5b5a042ee8b21 Mon Sep 17 00:00:00 2001 From: jaeminjo732 Date: Tue, 6 Jan 2026 16:45:43 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix(#162)=20:=20ssh=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-prod.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index f95dbf7..41fc0e2 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -100,8 +100,8 @@ jobs: port: ${{ secrets.PROD_SSH_PORT || 22 }} script: | # 배포 디렉토리 생성 및 이동 - mkdir -p ~/eod-prod - cd ~/eod-prod + mkdir -p ~/eod/prod + cd ~/eod/prod # GitHub Repository Clone 또는 Pull if [ -d ".git" ]; then From cc5dfac2d0969b3dbf85eba18f2585c5b3ab03b3 Mon Sep 17 00:00:00 2001 From: jaeminjo732 Date: Tue, 6 Jan 2026 16:55:48 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix(#162)=20:=20ssh=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd-prod.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index 41fc0e2..4e0f584 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -96,7 +96,7 @@ jobs: with: host: ${{ secrets.PROD_SSH_HOST }} username: ${{ secrets.PROD_SSH_USERNAME }} - key: ${{ secrets.PROD_SSH_KEY }} + key: ${{ secrets.SSH_KEY }} port: ${{ secrets.PROD_SSH_PORT || 22 }} script: | # 배포 디렉토리 생성 및 이동 @@ -185,7 +185,7 @@ jobs: key: ${{ secrets.SSH_KEY }} port: ${{ secrets.SSH_PORT || 22 }} script: | - cd ~/eod-prod + cd ~/eod/prod echo "=========================================" echo "🚀 PRODUCTION 환경 배포 완료" echo "=========================================" @@ -221,5 +221,5 @@ jobs: run: | IMAGE_NAME=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') echo "✅ PRODUCTION Docker Compose 배포 완료!" - echo "서버: ${{ secrets.PROD_SSH_HOST }}" + echo "서버: ${{ secrets.SSH_HOST }}" echo "이미지: ${{ env.REGISTRY }}/${IMAGE_NAME}:production" From b6626ca1b1ea8ddd162f359dde4a81240a5e3641 Mon Sep 17 00:00:00 2001 From: jaeminjo732 Date: Tue, 6 Jan 2026 17:00:42 +0900 Subject: [PATCH 9/9] =?UTF-8?q?fix(#162)=20:=20env=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.prod.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index c017d07..fb5932a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -12,8 +12,6 @@ services: - SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL} - SPRING_DATASOURCE_USERNAME=${SPRING_DATASOURCE_USERNAME} - SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD} - - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} - - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} - JWT_SECRET=${JWT_SECRET} - BASE_URL=${BASE_URL} - FRONTEND_BASE_URL=${FRONTEND_BASE_URL}