Skip to content

Conversation

@soeun2537
Copy link
Member

@soeun2537 soeun2537 commented Dec 26, 2025

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 애플리케이션 모니터링 및 성능 메트릭 수집 기능 강화
    • 분산 추적(Distributed Tracing) 기능 추가로 요청 흐름 가시성 개선
  • 개선사항

    • CI/CD 배포 파이프라인 최적화 및 안정성 향상
    • 개발 및 운영 환경의 배포 프로세스 개선
    • 로깅 및 모니터링 체계 개선

✏️ Tip: You can customize this high-level summary in your review settings.

taek2222 and others added 6 commits December 3, 2025 22:28
* chore: CI/CD 스크립트 초안 작성

* chore: 쓰기 권한 추가

* chore: 버킷 이름 수정

* chore: 주석 삭제 및 캐시 적용

* chore: CodeDeploy 요청 적용

* chore: CodeDeploy 설정 변경 삭제

* chore: appspec 경로 수정

* chore: 실행, 검증, 시작 스크립트 변경

* refactor: CloudWatchMetricsConfig 수정

* refactor: start 스크립트 수정

* chore: 헬스 체크 시간 2분 증가

* chore: clean, stop 트리거 추가

* chore: 권한 root, 이름 변경

* chore: clean 이름 변경

* chore: 파일 이름 변경

* chore: 최종 dev prod CI/CD 생성

* chore: 이름 간소화

* chore: ci-cd 버킷 시크릿 이름 변경

* chore: 환경 변수 명확한 네이밍 수정

* chore: Dev 서버 배포를 위한 브랜치 잠시 변경

* chore: 잘못된 버킷 환경 변수 이름 수정

* chore: Secrets 형태로 변경

* chore: start 스크립트 변경

* chore: prod 브랜치 트리거 변경

* chore: Time out 시간 설정

* refactor: 임시적 S3 변경

* chore: prod 브랜치 트리거 재변경

* refactor: 임시적 S3 Client Config 수정

* chore: stop 스크립트 변경

* chore: 매개변수 secrets 변경

* chore: 매개변수 secrets 변경, 브랜치 수정

* chore: CI 스크립트 브랜치 변경

* chore: release 브랜치 변경

* chore: ci 디렉토리 변경
* refactor: 사용하지 않게 된 클래스 정리

* refactor: dev 환경 S3 사용 변경

* refactor: 주석 날짜와 어노테이션 의미 추가

* refactor: 컴포넌트 어노테이션 삭제

* refactor: 프로필 dev 추가
* test: 인수 테스트 지원 클래스 정의

* test: 지원 클래스 Import 추가

* test: 테스트 지원 클래스로 변경, 컨텍스트 9회에서 2회로 감소

* test: abstract 추상 클래스 적용

* test: AcceptanceTestSupport 파일 위치 변경

* chore: 메모리 사용량 및 스레드 수 측정

* chore: import 추가

* chore: time으로 OS 기준 메모리 사용량 측정

* chore: jfr 파일을 위한 build.gradle에 옵션 추가 및 스크립트 변경

* chore: JVM 상태 측정을 위한 설정 제거

---------

Co-authored-by: changui <[email protected]>
* chore: jacoco 설정 추가

* chore: jacoco 결과 코멘트 및 결과 리포트 html 링크 출력

* chore: 아직 사용이 정해지지 않은 설정 제거

* chore: CI content write 권한 추가

* chore: sonarqube cloud 연동

* chore: sonar task로 변경 및 organization 추가, github token 추가

* chore: projectKey 추가

* chore: sonarqube 버전 변경

* chore: github token 제거

* chore: sonar qube 관련 패키지 캐싱

* chore: 전체90, 변경100 커버리지 조건 추가 및 github pages 제거

* chore: 테스트 커버리지에서 제외할 클래스 명시 (config, aspect, logging, application)
* feat: Grafana 모니터링 스택 설정 추가

* feat: 공용 로그 폴더에 로그 남기도록 변경

* feat: application.yml 불필요 설정 제거

* remove: tracing 불필요 설정 삭제

promQL로 계산 가능한 p9X 계산 설정 삭제

* refactor: 톰캣 메트릭 정보 설정 파일 위치 이동

* refactor: 빈 줄 삭제

* feat: Log, Trace AOP 분리

* feat: Alloy 설정 추가

* refactor: 생성자 주입 방식으로 변경

---------

Co-authored-by: eoehd1ek <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Dec 26, 2025

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'tools'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

이 PR은 CI/CD 파이프라인 재구성, 모니터링 인프라 업그레이드, 그리고 테스트 아키텍처 통일을 포함합니다. 주요 변경사항은: (1) GitHub Actions 워크플로우 개편으로 dev/prod 브랜치별 독립적 배포 파이프라인 구축, (2) CloudWatch 메트릭에서 Prometheus로 전환, (3) OpenTelemetry 기반 분산 추적 시스템 추가, (4) 배포 스크립트 강화(동적 프로파일 설정, 헬스 체크 추가), (5) 모든 테스트 클래스를 AcceptanceTestSupport 기본 클래스로 통일.

주요 검토 사항

🔴 배포 자동화 복잡성 증가로 인한 관리 부담

문제점:

  • ci-cd-dev.ymlci-cd-prod.yml이 거의 동일한 구조를 반복하고 있어 DRY(Don't Repeat Yourself) 원칙 위반입니다. 앞으로 배포 로직이 변경될 때마다 두 파일을 동시에 수정해야 하는 유지보수 비용이 발생합니다.
  • infra/start.sh의 동적 프로파일 결정 로직(DEPLOYMENT_GROUP_NAME 기반)은 배포 환경 설정이 AWS CodeDeploy의 환경 변수에만 의존하게 만들어, 로컬 테스트나 다른 배포 도구 도입 시 어려움을 초래할 수 있습니다.

개선 제안:

  • 선택지 1: GitHub Actions 재사용 가능 워크플로우(Reusable Workflow) 도입

    • 장점: 공통 로직을 한 곳에서 관리, 버전 관리 용이
    • 단점: 초기 구성 복잡도 증가, 학습곡선 존재
  • 선택지 2: 환경 변수를 GitHub Secrets 또는 Terraform으로 관리

    • 장점: 배포 설정과 스크립트 분리, 인프라코드화
    • 단점: 추가 도구 도입 필요

🟡 테스트 기본 클래스 도입의 숨겨진 결합도

문제점:

  • AcceptanceTestSupportClock, FestivalNotificationManager, ShuffleManager 세 가지 목(Mock)을 한 곳에 집중시켰습니다. 이는 단일 책임 원칙(SRP) 위반으로, 실제로는 특정 기능을 테스트하지 않는 클래스도 불필요한 Mock을 상속받게 됩니다.
  • 예를 들어, DeviceControllerTestFestivalNotificationManagerShuffleManager가 필요하지 않지만, 기본 클래스 상속으로 인해 이들이 자동으로 생성됩니다. 이는 테스트 실행 속도 저하와 테스트 간 상태 공유 위험을 증가시킵니다.

개선 제안:

  • 선택지 1: 기능별 중간 기본 클래스 계층 추가

    AcceptanceTestSupport (기본)
    ├── NotificationTestSupport (FestivalNotificationManager)
    ├── StorageTestSupport (Clock)
    └── ScheduleTestSupport (ShuffleManager)
    
    • 장점: 필요한 Mock만 주입, 테스트 독립성 향상
    • 단점: 클래스 계층 증가로 코드 복잡도 상승
  • 선택지 2: 팩토리 패턴 또는 @Qualifier 활용으로 선택적 주입

    • 장점: 기존 구조 유지하면서 유연성 증대
    • 단점: Mock 초기화 로직이 분산됨

🟡 OpenTelemetry 통합의 불완전성

문제점:

  • TracingAspect는 추가되었지만, 기존 LoggingAspect에서 OpenTelemetry를 완전히 제거했습니다. 두 개의 Aspect가 동일한 포인트컷(applicationLayers())에 적용되므로 실행 순서에 따른 예측 불가능한 동작이 발생할 수 있습니다.
  • @Order 값이 HIGHEST_PRECEDENCE(0)에서 HIGHEST_PRECEDENCE + 1로 변경되어 실행 순서가 변경되었지만, 이것이 의도적인지 혹은 문제 해결인지 명확하지 않습니다.

개선 제안:

  • LoggingAspect@Order 주석이나 문서화를 추가하여 실행 순서 의도를 명확히 할 필요가 있습니다.
  • 두 Aspect의 역할 분담을 재정의: LoggingAspect는 메서드 인자/반환값 로깅에, TracingAspect는 분산 추적에만 집중하도록 명시.

🟡 배포 스크립트의 환경 의존성

문제점:

  • infra/validate.sh가 고정된 포트 9000과 엔드포인트 /actuator/health를 하드코딩했습니다. 만약 포트나 엔드포인트가 변경된다면 스크립트를 수정해야 합니다.
  • 90초 제한 시간이 적절한지 검증 없이 설정되어 있어, 느린 네트워크나 느린 시작을 하는 애플리케이션에서는 배포가 실패할 수 있습니다.

개선 제안:

# 환경 변수화 제안
HEALTH_CHECK_PORT=${HEALTH_CHECK_PORT:-9000}
HEALTH_CHECK_ENDPOINT=${HEALTH_CHECK_ENDPOINT:-/actuator/health}
HEALTH_CHECK_TIMEOUT=${HEALTH_CHECK_TIMEOUT:-90}

🟢 긍정적 측면

  • Prometheus 전환으로 더 나은 메트릭 시각화 및 알림 기능 확보
  • 헬스 체크 로직(validate.sh) 추가로 배포 신뢰성 향상
  • 테스트 기본 클래스 도입으로 보일러플레이트 코드 감소

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

근거:

  • CI/CD 워크플로우 3개 파일의 유사 로직 중복(DRY 위반)
  • 배포 스크립트의 환경 의존성과 동적 설정 검증 필요
  • OpenTelemetry 통합 시 Aspect 실행 순서 및 부작용 검토 필수
  • 모든 테스트 클래스의 기본 클래스 변경에 따른 광범위한 영향 분석
  • 모니터링 인프라 전환(CloudWatch → Prometheus)에 대한 운영 영향도 검토 필요

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목이 실제 변경 사항과 일치하지 않습니다. 제목은 단순한 브랜치 병합을 나타내지만, 실제로는 CI/CD 파이프라인 재구축, 모니터링 및 트레이싱 기능 추가, 클라우드워치에서 프로메테우스로의 마이그레이션 등 광범위한 기술적 변경이 포함되어 있습니다. 제목을 '[Release] 모니터링 및 CI/CD 파이프라인 현대화' 또는 '[Release] DEV->PROD 병합: 프로메테우스 마이그레이션 및 배포 자동화' 등으로 변경하여 주요 기술적 개선 사항을 명확히 반영해주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

@soeun2537 soeun2537 merged commit b3df2b1 into prod Dec 26, 2025
8 of 10 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java (1)

26-27: @value 주입이 작동하지 않을 것입니다.

@Component가 제거되어 이 클래스는 Spring Bean으로 등록되지 않으므로, @Value 어노테이션도 처리되지 않습니다. 만약 이 클래스를 정말 보존하려면 위의 제안대로 @Component@Profile("local")을 추가하거나, 완전히 제거하는 것이 좋겠습니다.

src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java (1)

509-509: URL 경로 오류: 슬래시 누락 및 복수형 불일치

"announcement/{announcementId}/pin" 경로가 잘못되었습니다. 다른 테스트(예: line 342)에서는 "/announcements/{announcementId}/pin"으로 사용하고 있어 일관성이 맞지 않습니다. 이 경로는 선행 슬래시(/)가 누락되었고, announcementannouncements(복수형)여야 합니다.

이 오류로 인해 테스트가 잘못된 엔드포인트를 호출하거나 404 오류가 발생할 수 있습니다.

🔎 수정 제안
-                        .post("announcement/{announcementId}/pin", initAnnouncement.getId());
+                        .patch("/announcements/{announcementId}/pin", initAnnouncement.getId());
build.gradle (1)

97-107: 중복된 테스트 설정 제거 필요

useJUnitPlatform() 호출이 두 번 정의되어 있습니다:

  • Line 97-99: tasks.named('test') 블록
  • Line 105-107: test 블록

두 설정은 동일한 역할을 하므로 하나를 제거해야 합니다. 중복 설정은 빌드 스크립트의 가독성을 저하시키고, 추후 유지보수 시 혼란을 야기할 수 있습니다.

🔎 수정 제안
 tasks.named('test') {
     useJUnitPlatform()
 }

 springBoot {
     buildInfo()
 }
-
-test {
-    useJUnitPlatform()
-}
🧹 Nitpick comments (18)
infra/validate.sh (2)

3-3: 헬스 체크 URL이 하드코딩되어 있습니다.

현재 http://localhost:9000으로 포트가 고정되어 있는데, 환경별로 포트가 다를 수 있습니다. 환경 변수로 포트를 주입받도록 하면 더 유연하게 대응할 수 있습니다.

🔎 환경 변수를 사용한 개선안
+PORT=${SERVER_PORT:-9000}
-HEALTH_URL="http://localhost:9000/actuator/health"
+HEALTH_URL="http://localhost:${PORT}/actuator/health"

이렇게 하면 배포 시 SERVER_PORT 환경 변수로 포트를 지정할 수 있고, 기본값은 9000으로 유지됩니다.


8-19: 헬스 체크 로직이 견고하게 구현되어 있습니다.

90회 재시도에 1초 간격은 충분히 안전한 타임아웃입니다. curl 오류 시 "000"을 반환하도록 처리한 것도 좋습니다. 다만, 90초라는 타임아웃 값의 근거를 스크립트 상단에 주석으로 남겨두면 향후 유지보수 시 도움이 될 것 같습니다.

예시:

# 헬스 체크 최대 대기 시간: 90초
# - 애플리케이션 시작 시간(~60초) + 버퍼(30초)
for i in {1..90}
.github/workflows/ci-cd-prod.yml (2)

42-50: 실행 가능한 JAR 파일 탐색 로직이 명확합니다.

*plain.jar를 제외하고 첫 번째 JAR를 찾는 방식이 적절합니다. 다만 head -n 1을 사용하기 때문에 빌드 결과에 여러 실행 가능한 JAR가 생성될 경우 의도하지 않은 파일이 선택될 수 있습니다. 현재 프로젝트 구조에서는 단일 JAR만 생성되므로 문제없지만, 향후 멀티 모듈로 확장될 가능성이 있다면 JAR 이름 패턴을 더 구체적으로 지정하는 것도 고려해볼 수 있습니다.

💡 향후 멀티 모듈 환경을 고려한 개선안
# 특정 모듈 이름이나 패턴으로 제한
JAR_PATH=$(find build/libs -name "festabook-*.jar" ! -name "*plain.jar" | head -n 1)

현재는 불필요하지만, 멀티 모듈 구조로 전환할 계획이 있다면 참고하시면 좋겠습니다.


58-59: 인프라 파일 복사가 안전하게 처리되어 있습니다.

2>/dev/null || true를 사용해 파일이 없어도 워크플로우가 중단되지 않도록 했습니다. appspec.yml과 쉘 스크립트는 배포에 필수적이므로, 실제로는 항상 존재해야 하는데 이렇게 선택적으로 처리하면 파일 누락 시 문제를 조기에 발견하기 어려울 수 있습니다.

🔎 필수 파일 검증을 추가한 개선안
       - name: Create deploy bundle
         id: bundle
         run: |
           mkdir -p deploy
           cp "${{ steps.jar.outputs.jar_path }}" deploy/
 
-          cp infra/appspec.yml deploy/ 2>/dev/null || true
-          cp infra/*.sh deploy/ 2>/dev/null || true
+          # appspec.yml은 배포에 필수
+          if [ ! -f infra/appspec.yml ]; then
+            echo "❌ appspec.yml not found"; exit 1;
+          fi
+          cp infra/appspec.yml deploy/
+          
+          # 쉘 스크립트가 있으면 복사
+          if ls infra/*.sh 1> /dev/null 2>&1; then
+            cp infra/*.sh deploy/
+          fi

이렇게 하면 필수 파일(appspec.yml)은 검증하고, 선택적 파일(쉘 스크립트)은 유연하게 처리할 수 있습니다.

.github/workflows/ci-cd-dev.yml (1)

1-84: Dev와 Prod 워크플로우의 구조가 일관적입니다.

두 워크플로우가 거의 동일한 구조를 가지고 있어 이해하기 쉽고, 환경 간 일관성이 보장됩니다. 다만 중복 코드가 많아 향후 변경 시 두 파일을 모두 수정해야 하는 부담이 있습니다.

현재 상태: 각 환경별로 독립적인 워크플로우 → 명확하고 디버깅이 쉬움
대안: GitHub Actions의 Reusable Workflow 또는 Composite Action 사용 → 중복 제거, 유지보수성 향상

현재는 두 환경만 있고 워크플로우가 안정적이므로 지금 구조도 충분히 합리적입니다. 다만 환경이 더 추가되거나(예: staging) 워크플로우 변경이 빈번해진다면 공통 로직을 추출하는 것을 고려해보시면 좋겠습니다.

💡 Reusable Workflow를 사용한 리팩토링 예시

.github/workflows/reusable-deploy.yml 생성:

name: Reusable Deploy Workflow

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      secret_yml_name:
        required: true
        type: string
      deployment_group:
        required: true
        type: string
    secrets:
      SECRET_YML:
        required: true
      # ... 기타 시크릿

그러면 dev와 prod는 이렇게 단순화됩니다:

jobs:
  deploy:
    uses: ./.github/workflows/reusable-deploy.yml
    with:
      environment: dev
      secret_yml_name: DEV_SECRET_YML
      deployment_group: ${{ secrets.CODEDEPLOY_DEPLOYMENT_GROUP_DEV }}
    secrets:
      SECRET_YML: ${{ secrets.DEV_SECRET_YML }}

지금은 선택 사항이지만, 향후 확장 계획이 있다면 참고하세요.

.github/workflows/ci.yml (1)

66-72: 커버리지 기준이 매우 엄격하게 설정되어 있습니다.

전체 커버리지 90%는 도전적이지만 합리적인 목표입니다. 하지만 변경된 파일에 대해 100% 커버리지를 요구하는 것은 실무적으로 어려울 수 있습니다.

엄격한 기준의 장점:

  • 모든 새 코드가 완전히 테스트됨
  • 기술 부채 증가 방지

현실적인 한계:

  • DTO, Config 클래스 등 테스트 가치가 낮은 코드도 포함
  • 예외 처리 분기나 방어 코드의 모든 경로를 테스트하기 어려움
  • Lombok 생성 코드나 프레임워크 콜백 메서드 등이 커버리지에 포함될 수 있음
  • PR이 머지 블로킹될 수 있어 개발 속도에 영향

제안: 100%는 유지하되, 특정 유형의 파일은 제외하거나, 처음에는 95% 정도로 시작해서 팀이 적응한 후 상향하는 것도 고려해볼 수 있습니다.

🔎 더 유연한 커버리지 기준 예시
- name: Comment coverage on PR
  uses: madrapps/[email protected]
  with:
    paths: build/reports/jacoco/test/jacocoTestReport.xml
    token: ${{ secrets.GITHUB_TOKEN }}
    min-coverage-overall: 90
    min-coverage-changed-files: 95  # 100 -> 95로 완화

또는 build.gradle에서 특정 패키지를 커버리지 계산에서 제외:

jacocoTestReport {
    reports {
        xml.required = true
    }
    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: [
                '**/dto/**',
                '**/config/**',
                '**/*Request.class',
                '**/*Response.class'
            ])
        }))
    }
}
src/main/java/com/daedan/festabook/global/logging/LocalLoggingAspect.java (1)

20-20: 포인트컷 중앙화가 잘 적용되었습니다.

인라인으로 작성되어 있던 포인트컷 표현식을 LoggingPointcuts.applicationLayers()로 추출한 것은 좋은 리팩토링입니다:

  • 재사용성: 여러 Aspect(LoggingAspect, TracingAspect 등)에서 동일한 포인트컷을 일관되게 사용
  • 유지보수성: 포인트컷 변경 시 한 곳만 수정하면 됨
  • 가독성: 표현식의 의도가 메서드 이름으로 명확하게 드러남

동작에는 변화가 없고 구조적 개선만 이루어진 변경입니다.

infra/stop.sh (2)

3-10: 프로세스 식별 패턴과 권한 관리를 개선할 수 있습니다.

현재 구현에서 개선 가능한 부분들:

1. 프로세스 매칭 정확도

  • 현재 패턴 "festabook.*\.jar"은 다른 festabook 관련 프로세스도 매칭할 수 있습니다.
  • 더 구체적인 패턴이나 전체 경로를 사용하는 것이 안전합니다.

2. sudo 사용

  • 애플리케이션과 동일한 유저로 스크립트를 실행한다면 sudo가 불필요합니다.
  • sudo가 필요한 경우 비밀번호 프롬프트 없이 실행되도록 sudoers 설정이 필요합니다.
🔎 개선된 프로세스 식별 방식
-PID=$(pgrep -f "festabook.*\.jar")
+# 전체 경로로 더 정확하게 매칭
+PID=$(pgrep -f "/home/ubuntu/app/festabook.*\.jar")
+
+# 또는 pidfile 방식 사용 (start.sh에서 PID 파일 생성하는 경우)
+# PIDFILE="/home/ubuntu/app/application.pid"
+# if [ -f "$PIDFILE" ]; then
+#     PID=$(cat "$PIDFILE")
+# fi
 else
     echo "> Killing process $PID"
 
-    sudo kill -15 $PID
+    # 동일 유저로 실행 시 sudo 제거
+    kill -15 $PID

12-32: 타임아웃 값이 스프링 graceful shutdown 설정과 조화롭게 설정되었습니다.

현재 로직을 분석하면:

  • Spring graceful shutdown: 15초 (application.yml의 timeout-per-shutdown-phase)
  • 스크립트 대기 시간: 30초
  • 여유 시간: 15초

이는 충분한 버퍼를 제공하는 좋은 구성입니다. 다만 배포 파이프라인의 전체 소요 시간을 고려한다면 최적화 여지가 있습니다:

🔎 타임아웃 최적화 옵션

배포 속도를 개선하고 싶다면 타임아웃을 줄일 수 있습니다:

-    TIMEOUT=30
+    # Spring graceful shutdown(15초) + 버퍼(5초)
+    TIMEOUT=20
     COUNT=0

선택 기준:

  • 30초 유지: 안정성 우선, 느린 요청 처리나 복잡한 shutdown 훅이 있는 경우
  • 20초로 단축: 배포 속도 개선, 대부분의 일반적인 케이스에 충분

현재 설정(30초)은 보수적이지만 안전한 선택입니다.

infra/appspec.yml (1)

23-26: 헬스 체크 로직이 잘 구현되었으나, 오류 진단 개선 고려

validate.sh 스크립트는 헬스 체크의 핵심 요소들을 올바르게 구현했습니다:

  • ✅ management port 9000에서 /actuator/health 엔드포인트 검증
  • ✅ 90초 타임아웃 내 재시도 로직 구현 (90회 × 1초 간격)
  • ✅ curl 실패 시 기본값 처리로 안정성 확보

다만 몇 가지 개선할 점이 있습니다:

1. 오류 진단 정보 부족

현재 실패 메시지는 "헬스 체크 실패"만 출력되어 원인 파악이 어렵습니다. 배포 실패 시 로그에서 마지막 HTTP 상태코드나 curl 에러 메시지가 나타나면 디버깅이 훨씬 수월합니다:

# 제안: 최종 상태 정보 로깅
echo "❌ 헬스 체크 실패 (마지막 상태코드: $STATUS_CODE)"

2. 재시도 간격 검토

1초 간격은 애플리케이션 시작 중에 더 빠른 피드백을 제공하지만, 동시에 서버에 더 많은 요청을 보냅니다. Spring Boot 애플리케이션이 일반적으로 안정화되는 데 소요되는 시간을 고려하면, 3초 간격 (30회 재시도)도 충분할 수 있습니다. 두 접근 방식 모두 90초 타임아웃 내에 동작하므로, 팀의 배포 경험에 따라 선택하면 됩니다.

src/test/java/com/daedan/festabook/festival/controller/FestivalControllerTest.java (1)

281-281: TODO 주석: 테스트 격리 문제 해결 필요

테스트 격리 문제가 존재한다면, @Transactional 어노테이션 사용 또는 테스트 간 데이터 정리 로직 추가를 고려해볼 수 있습니다. AcceptanceTestSupport에서 공통 teardown 메서드를 제공하는 것도 하나의 방법입니다.

이 문제를 해결하는 코드를 생성해 드릴까요, 아니면 이 작업을 추적할 새 이슈를 열어드릴까요?

src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java (1)

73-78: 동시성 테스트의 정확성 검증

100개의 동시 요청 중 1개만 성공하고 99개가 충돌 오류를 반환해야 한다는 검증은 논리적으로 타당합니다. 다만, 네트워크 지연이나 스레드 스케줄링에 따라 일부 요청이 순차적으로 처리될 가능성이 있어, 극히 드문 경우 테스트가 실패할 수 있습니다.

더 안정적인 검증을 원한다면 assertThat(duplicateErrorCount.get()).isGreaterThanOrEqualTo(requestCount - 1)와 같이 최소 기대치를 검증하는 방식도 고려해볼 수 있습니다.

infra/start.sh (2)

24-25: sudo와 리다이렉션 순서 문제

현재 sudo nohup java ... > $LOG_FILE 구문에서 리다이렉션(>)은 sudo보다 먼저 셸에서 처리됩니다. 따라서 로그 파일에 대한 쓰기 권한이 현재 사용자가 아닌 root 권한으로 수행되지 않습니다.

/tmp 디렉토리는 일반적으로 모든 사용자가 쓰기 가능하므로 현재 상황에서는 문제가 없을 가능성이 높습니다. 그러나 로그 파일 경로가 변경되거나 권한이 필요한 경우를 대비해 아래 대안을 고려해볼 수 있습니다:

🔎 대안 1: sudo sh -c 사용
-sudo nohup java -jar -Duser.timezone=Asia/Seoul "$JAR_NAME" \
-         --spring.profiles.active=$PROFILE > $LOG_FILE 2>&1 &
+sudo sh -c "nohup java -jar -Duser.timezone=Asia/Seoul '$JAR_NAME' \
+         --spring.profiles.active=$PROFILE > '$LOG_FILE' 2>&1 &"
🔎 대안 2: sudo 없이 실행 (권장)

애플리케이션이 root 권한 없이 실행 가능하다면 sudo를 제거하는 것이 보안상 더 좋습니다:

-sudo nohup java -jar -Duser.timezone=Asia/Seoul "$JAR_NAME" \
-         --spring.profiles.active=$PROFILE > $LOG_FILE 2>&1 &
+nohup java -jar -Duser.timezone=Asia/Seoul "$JAR_NAME" \
+         --spring.profiles.active=$PROFILE > $LOG_FILE 2>&1 &

27-28: 헬스 체크 추가 권장

현재 스크립트는 Java 프로세스를 백그라운드로 시작한 후 즉시 종료합니다. 애플리케이션이 실제로 정상 기동되었는지 확인하지 않으므로, 배포가 성공으로 보고되더라도 실제 서비스는 실패한 상태일 수 있습니다.

배포 신뢰성을 높이려면 애플리케이션의 health 엔드포인트를 확인하는 로직을 추가하는 것을 고려해보세요.

🔎 헬스 체크 추가 예시
echo "✅ Spring WAS 실행 명령 전송 완료"

# 헬스 체크 (최대 60초 대기)
MAX_RETRY=30
RETRY_INTERVAL=2
HEALTH_URL="http://localhost:8080/actuator/health"

for i in $(seq 1 $MAX_RETRY); do
  sleep $RETRY_INTERVAL
  HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL 2>/dev/null)
  if [ "$HTTP_CODE" = "200" ]; then
    echo "✅ 애플리케이션 헬스 체크 성공"
    exit 0
  fi
  echo "⏳ 헬스 체크 대기 중... ($i/$MAX_RETRY)"
done

echo "❌ 애플리케이션 시작 실패 (헬스 체크 타임아웃)"
exit 1
src/main/java/com/daedan/festabook/global/logging/TracingAspect.java (2)

6-6: 사용되지 않는 import 제거 권장

@RequiredArgsConstructor가 import되었지만 실제로 사용되지 않습니다. 명시적 생성자(lines 25-31)가 정의되어 있어 Lombok 어노테이션이 필요하지 않습니다.

🔎 수정 제안
-import lombok.RequiredArgsConstructor;

33-47: 예외 발생 시 Span에 에러 정보 기록 권장

현재 구현에서는 예외가 발생해도 Span에 에러 정보가 기록되지 않습니다. OpenTelemetry의 모범 사례에 따르면, 예외 발생 시 span.recordException()span.setStatus()를 호출하여 추적 데이터에서 오류를 명확히 식별할 수 있도록 하는 것이 좋습니다.

이렇게 하면 분산 추적 시스템에서 어느 Span에서 오류가 발생했는지 쉽게 파악할 수 있어, 프로덕션 환경에서의 디버깅 효율성이 크게 향상됩니다.

🔎 수정 제안
     @Around("com.daedan.festabook.global.logging.LoggingPointcuts.applicationLayers()")
     public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
         String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
         String methodName = joinPoint.getSignature().getName();

         String spanName = className + "::" + methodName;
         Span span = tracer.spanBuilder(spanName).startSpan();
         span.setAttribute("env", env);

         try (Scope scope = span.makeCurrent()) {
             return joinPoint.proceed();
+        } catch (Throwable t) {
+            span.recordException(t);
+            span.setStatus(io.opentelemetry.api.trace.StatusCode.ERROR, t.getMessage());
+            throw t;
         } finally {
             span.end();
         }
     }
src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java (1)

34-37: 테스트 격리를 위한 RestAssured 상태 초기화 고려

현재 @BeforeEach에서 RestAssured.port만 설정하고 있습니다. RestAssured는 정적 상태를 가지고 있어, 이전 테스트에서 설정된 기본 경로(basePath), 헤더, 필터 등이 다음 테스트에 영향을 줄 수 있습니다.

테스트 간 완전한 격리를 보장하려면 RestAssured.reset()을 호출한 후 port를 설정하는 것이 더 안전합니다. 현재 테스트들이 각 요청마다 모든 설정을 명시적으로 지정하고 있다면 당장 문제가 되지는 않지만, 향후 확장성을 고려하면 초기화 로직 추가를 권장합니다.

🔎 수정 제안
     @BeforeEach
     void setUp() {
+        RestAssured.reset();
         RestAssured.port = port;
     }
src/main/java/com/daedan/festabook/global/logging/LoggingAspect.java (1)

7-7: 불필요한 Lombok 어노테이션 제거 고려

@RequiredArgsConstructor가 사용되고 있지만, 현재 클래스에는 final 필드가 없습니다. 이 어노테이션은 final 필드를 파라미터로 받는 생성자를 생성하는데, 해당하는 필드가 없으므로 기본 생성자만 생성됩니다.

코드를 더 명확하게 하려면 @RequiredArgsConstructor를 제거하거나, 명시적으로 기본 생성자가 필요한 의도를 표현할 수 있습니다. 다만, 현재 상태로도 동작에는 문제가 없습니다.

🔎 수정 제안
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 ...
 @Slf4j
 @Aspect
 @Component
 @Profile("prod | dev")
-@RequiredArgsConstructor
 @Order(Ordered.HIGHEST_PRECEDENCE + 1)
 public class LoggingAspect {

Also applies to: 22-22

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f34b1ac and 9abb394.

📒 Files selected for processing (52)
  • .github/workflows/backend-cd-dev.yml
  • .github/workflows/ci-cd-dev.yml
  • .github/workflows/ci-cd-prod.yml
  • .github/workflows/ci.yml
  • .github/workflows/release-drafter.yml
  • .github/workflows/slack-notify-opened.yml
  • .github/workflows/slack-notify-rerequested.yml
  • .github/workflows/slack-notify-submitted.yml
  • build.gradle
  • infra/appspec.yml
  • infra/clean.sh
  • infra/start.sh
  • infra/stop.sh
  • infra/validate.sh
  • src/main/java/com/daedan/festabook/global/config/CloudWatchMetricsConfig.java
  • src/main/java/com/daedan/festabook/global/config/S3ClientConfig.java
  • src/main/java/com/daedan/festabook/global/logging/LocalLoggingAspect.java
  • src/main/java/com/daedan/festabook/global/logging/LoggingAspect.java
  • src/main/java/com/daedan/festabook/global/logging/LoggingPointcuts.java
  • src/main/java/com/daedan/festabook/global/logging/TracingAspect.java
  • src/main/java/com/daedan/festabook/global/security/config/SecurityConfig.java
  • src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java
  • src/main/java/com/daedan/festabook/storage/infrastructure/S3StorageManager.java
  • src/main/resources/application-monitoring.yml
  • src/main/resources/application.yml
  • src/main/resources/logback-spring.xml
  • src/test/java/com/daedan/festabook/FestabookApplicationTests.java
  • src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java
  • src/test/java/com/daedan/festabook/council/controller/CouncilControllerTest.java
  • src/test/java/com/daedan/festabook/device/controller/DeviceControllerTest.java
  • src/test/java/com/daedan/festabook/event/controller/EventControllerTest.java
  • src/test/java/com/daedan/festabook/event/controller/EventDateControllerTest.java
  • src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java
  • src/test/java/com/daedan/festabook/festival/controller/FestivalControllerTest.java
  • src/test/java/com/daedan/festabook/festival/controller/FestivalNotificationSubscriptionControllerTest.java
  • src/test/java/com/daedan/festabook/festival/controller/IosFestivalNotificationSubscriptionControllerTest.java
  • src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java
  • src/test/java/com/daedan/festabook/global/argumentresolver/FestivalIdArgumentResolverTest.java
  • src/test/java/com/daedan/festabook/global/config/WebMvcConfigTest.java
  • src/test/java/com/daedan/festabook/global/exception/GlobalExceptionHandlerTest.java
  • src/test/java/com/daedan/festabook/global/security/config/SecurityConfigTest.java
  • src/test/java/com/daedan/festabook/lineup/controller/LineupControllerTest.java
  • src/test/java/com/daedan/festabook/lostitem/controller/LostItemControllerTest.java
  • src/test/java/com/daedan/festabook/place/controller/PlaceAnnouncementControllerTest.java
  • src/test/java/com/daedan/festabook/place/controller/PlaceControllerTest.java
  • src/test/java/com/daedan/festabook/place/controller/PlaceFavoriteControllerTest.java
  • src/test/java/com/daedan/festabook/place/controller/PlaceGeographyControllerTest.java
  • src/test/java/com/daedan/festabook/place/controller/PlaceImageControllerTest.java
  • src/test/java/com/daedan/festabook/question/controller/QuestionControllerTest.java
  • src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java
  • src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java
  • src/test/java/com/daedan/festabook/timetag/controller/TimeTagControllerTest.java
💤 Files with no reviewable changes (2)
  • src/main/java/com/daedan/festabook/global/config/CloudWatchMetricsConfig.java
  • .github/workflows/backend-cd-dev.yml
🧰 Additional context used
🧬 Code graph analysis (5)
src/test/java/com/daedan/festabook/festival/controller/FestivalNotificationSubscriptionControllerTest.java (1)
src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java (7)
  • Nested (55-117)
  • Nested (119-254)
  • Nested (256-314)
  • Nested (316-383)
  • Nested (385-409)
  • Nested (411-441)
  • Nested (443-521)
src/test/java/com/daedan/festabook/global/argumentresolver/FestivalIdArgumentResolverTest.java (1)
src/test/java/com/daedan/festabook/global/config/WebMvcConfigTest.java (1)
  • RestController (47-55)
src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java (1)
src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java (7)
  • Nested (55-117)
  • Nested (119-254)
  • Nested (256-314)
  • Nested (316-383)
  • Nested (385-409)
  • Nested (411-441)
  • Nested (443-521)
src/test/java/com/daedan/festabook/festival/controller/IosFestivalNotificationSubscriptionControllerTest.java (1)
src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java (7)
  • Nested (55-117)
  • Nested (119-254)
  • Nested (256-314)
  • Nested (316-383)
  • Nested (385-409)
  • Nested (411-441)
  • Nested (443-521)
src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java (1)
src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java (1)
  • Nested (38-145)
🪛 Shellcheck (0.11.0)
infra/start.sh

[warning] 25-25: sudo doesn't affect redirects. Use ..| sudo tee file

(SC2024)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run-PR-Test
🔇 Additional comments (43)
src/main/java/com/daedan/festabook/global/security/config/SecurityConfig.java (1)

34-55: Prometheus 엔드포인트의 인증 부재에 대한 보안 검토 필요

/actuator/prometheus 엔드포인트가 GET_WHITELIST에 포함되어 인증 없이 접근 가능하도록 설정되어 있습니다. 다만 application-monitoring.yml에서 management.server.port: 9000으로 설정되어 있어, 이 엔드포인트는 메인 애플리케이션 포트(80)가 아닌 별도의 관리 포트에서 서빙됩니다.

이 아키텍처 패턴 자체는 업계 표준이지만, 보안 효과는 인프라 계층의 접근 제어에 전적으로 의존합니다:

  • 포트 9000이 인터넷에 노출된 경우: Prometheus 메트릭(요청 패턴, 에러율, 메모리 사용량, DB 연결 상태 등)이 인증 없이 공개되어 공격자에게 시스템 취약점 추론 정보를 제공하게 됩니다.
  • 포트 9000이 VPC/내부 네트워크로 제한된 경우: 인프라 레벨에서 접근 제어가 이루어져 실질적 위험은 낮습니다.

권장사항:

  1. 인프라 확인: 현재 프로덕션 환경에서 포트 9000이 AWS Security Group, 방화벽, 또는 네트워크 ACL로 내부 네트워크 또는 모니터링 도구의 특정 IP 범위로만 제한되어 있는지 확인하세요.

  2. 방어 심화 (Defense in Depth): 인프라 제한이 충분하더라도, 애플리케이션 계층에서 추가 인증을 추가하면 더욱 안전합니다:

    .requestMatchers("/actuator/prometheus").hasIpAddress("10.0.0.0/8")
    // 또는 별도의 인증 토큰 추가

현재 코드에는 Prometheus 엔드포인트에 대한 애플리케이션 계층 인증이 없으므로, 인프라 레벨 접근 제어가 보안의 유일한 방어선입니다. 이를 명시적으로 확인하고 필요시 애플리케이션 계층 보안을 강화하기를 권장합니다.

src/main/java/com/daedan/festabook/global/config/S3ClientConfig.java (1)

11-11: S3 설정이 dev 환경까지 확장된 점 확인했습니다.

LocalStorageManager가 deprecated되면서 dev 환경도 S3를 사용하도록 변경된 것으로 보입니다. S3StorageManager의 프로필 변경과도 일관성 있게 조정되어 있어 좋습니다.

.github/workflows/release-drafter.yml (1)

7-7: 브랜치 전략이 main에서 prod로 변경된 점 확인했습니다.

Release Drafter가 새로운 브랜치 전략에 맞게 잘 조정되었습니다. prod 브랜치로 푸시 시 자동으로 릴리스가 발행되도록 설정되어 있어 배포 자동화가 원활하게 작동할 것으로 보입니다.

Also applies to: 29-29

.github/workflows/ci-cd-dev.yml (1)

26-29: 환경별 시크릿 분리가 명확합니다.

dev 환경에서는 DEV_SECRET_YML을 사용하고, prod는 PROD_SECRET_YML을 사용하도록 잘 구분되어 있습니다. 이렇게 하면 환경별 설정(DB 연결 정보, API 키 등)을 안전하게 분리할 수 있어 좋습니다.

.github/workflows/ci.yml (4)

6-7: PR 대상 브랜치가 새 전략에 맞게 업데이트되었습니다.

backend-prod, backend-dev에서 prod, dev로 변경되어 브랜치 구조가 단순해졌습니다. 새로운 CI/CD 파이프라인과 일관성 있게 조정되었습니다.


13-15: PR 작업에 필요한 권한이 적절히 부여되었습니다.

contents, pull-requests, checks에 write 권한이 필요한 이유는 Jacoco 커버리지 리포트를 PR에 코멘트로 남기고(Line 66-72), 테스트 결과를 체크로 표시하기 위함입니다. 권한 상승이 기능 요구사항과 명확히 연결되어 있어 적절합니다.


27-32: SonarQube 패키지 캐싱이 추가되었습니다.

SonarQube 분석 시 매번 패키지를 다운로드하지 않도록 캐싱을 추가한 점이 좋습니다. CI 실행 시간 단축에 도움이 될 것입니다.


52-55: Jacoco와 SonarQube 통합이 추가되었습니다.

테스트 커버리지 수집과 코드 품질 분석이 CI 파이프라인에 통합되어 코드 품질 관리가 체계화되었습니다. SONAR_TOKEN을 통해 SonarCloud 또는 SonarQube 서버에 결과를 전송하는 것으로 보입니다.

SonarQube 프로젝트 설정이 올바른지 확인하려면 다음을 체크해보세요:

#!/bin/bash
# Description: build.gradle에 SonarQube 설정이 있는지 확인

rg -n "sonar" build.gradle -A 10
src/main/java/com/daedan/festabook/storage/infrastructure/S3StorageManager.java (1)

20-20: dev 환경에서도 S3가 활성화되는 변경인데, 버킷 환경 분리 설정을 실제로 확인해야 합니다.

S3ClientConfig와의 프로필 동기화는 적절하고, LocalStorageManager의 deprecated 처리도 의도가 명확합니다. 다만 리뷰의 제안에 보완이 필요합니다.

S3StorageManager는 생성자에서 ${cloud.aws.s3.bucket} 프로퍼티를 주입받는데, 현재 저장소의 application.yml에는 S3 설정이 보이지 않습니다. 설정이 .gitignore된 별도 파일(예: application-secret.yml 등)이나 환경 변수에서 제공된다면, 다음을 확인해야 합니다:

검증 포인트:

  • dev 환경에서 실제로 다른 S3 버킷명이 설정되어 있는지 (prod 버킷과 분리되었는지)
  • 버킷 설정이 프로필별로 구분되어 있는 방식 (여러 application-{profile}.yml 파일 vs 환경 변수 vs 다른 외부 설정 소스)

application-dev.yml과 application-prod.yml 파일이 리포지토리에 없다면, 실제 설정 방식을 확인한 후 그에 맞는 검증을 진행하는 것이 좋겠습니다. 예를 들어 환경 변수 기반이라면 배포 파이프라인에서 CLOUD_AWS_S3_BUCKET 같은 변수가 환경별로 다르게 설정되어 있는지 확인하는 방식이 될 것입니다.

src/main/resources/application.yml (1)

21-21: 변경 사항이 적절합니다.

  1. Line 21: monitoring 프로파일 추가는 PR의 모니터링 인프라 업그레이드 목표(CloudWatch → Prometheus/OpenTelemetry)와 일관성 있게 구성되었습니다.

  2. Line 34: 이미지 경로 prefix를 명시적으로 설정하여 스토리지 구조를 명확하게 관리할 수 있게 되었습니다.

Also applies to: 34-34

src/test/java/com/daedan/festabook/festival/controller/IosFestivalNotificationSubscriptionControllerTest.java (1)

19-19: 테스트 아키텍처 통일이 잘 적용되었습니다.

AcceptanceTestSupport로의 마이그레이션이 깔끔하게 이루어졌습니다:

  • 표준화: PR 목표에 맞게 테스트 기반 클래스를 통일
  • 보일러플레이트 제거: @SpringBootTest, @LocalServerPort, RestAssured 포트 설정 등이 상위 클래스로 이동
  • Mock 관리 일원화: festivalNotificationManager가 상위 클래스에서 제공되어 테스트 간 일관성 확보

기존 테스트 로직과 검증 방식은 그대로 유지되면서 구조만 개선되었습니다.

Also applies to: 27-27, 66-143

src/test/java/com/daedan/festabook/place/controller/PlaceAnnouncementControllerTest.java (1)

23-23: 테스트 기반 클래스 통일이 일관되게 적용되었습니다.

다른 컨트롤러 테스트들과 동일한 방식으로 AcceptanceTestSupport로 마이그레이션되었습니다. 이를 통해:

  • 테스트 설정의 중복 제거
  • 프로젝트 전체 테스트 스위트의 일관성 확보
  • 유지보수성 향상

기존 테스트 케이스들의 검증 로직은 모두 보존되었습니다.

Also applies to: 35-35

src/test/java/com/daedan/festabook/place/controller/PlaceFavoriteControllerTest.java (1)

21-21: 테스트 아키텍처 통일 작업이 완료되었습니다.

PlaceFavoriteControllerTestAcceptanceTestSupport 기반으로 성공적으로 전환되었습니다.

이번 PR에서 검토한 모든 테스트 클래스들이 일관된 패턴으로 마이그레이션되어, 프로젝트의 테스트 아키텍처가 체계적으로 정리되었습니다.

향후 새로운 컨트롤러 테스트 작성 시에도 동일한 기반 클래스를 사용하여 일관성을 유지할 수 있습니다.

Also applies to: 29-29

src/test/java/com/daedan/festabook/timetag/controller/TimeTagControllerTest.java (1)

13-31: 테스트 인프라 마이그레이션 - 적절한 변경

AcceptanceTestSupport 기반으로의 전환이 컨트롤러 테스트에 적합합니다.

긍정적인 점:

  • RestAssured 기반 API 테스트는 인수 테스트(acceptance test)의 전형적인 패턴입니다
  • 포트 설정, 테스트 더블 구성 등 반복적인 설정 코드가 제거되었습니다
  • 모든 컨트롤러 테스트가 동일한 기반을 사용하여 일관성이 향상되었습니다

확인 완료:

  • @Autowired 필드 인젝션이 정상 동작 (JpaRepository, JwtTestHelper)
  • 기존 테스트 로직은 변경되지 않음
  • Nested 테스트 구조 유지
src/test/java/com/daedan/festabook/lostitem/controller/LostItemControllerTest.java (1)

22-35: 일관된 테스트 인프라 적용 확인

이전 파일들과 동일한 패턴으로 AcceptanceTestSupport를 적용했습니다. 테스트 로직 변경 없이 인프라만 전환된 안전한 리팩토링입니다.

src/test/java/com/daedan/festabook/global/security/config/SecurityConfigTest.java (1)

11-25: 보안 설정 테스트 마이그레이션 검증

보안 관련 테스트가 AcceptanceTestSupport로 전환되었습니다.

중요 확인 사항:

  • AcceptanceTestSupport가 별도의 테스트 보안 설정을 제공하지 않는지 확인이 필요합니다
  • CORS, 인증, 화이트리스트 검증이 실제 운영 설정과 동일한 조건에서 테스트되어야 합니다
  • AI Summary에서 @Import(TestSecurityConfig.class) 제거가 언급되었는데, 이것이 AcceptanceTestSupport 내부에 포함된 것인지 확인하세요

테스트 대상:

  • CORS 설정 (Lines 34-204)
  • 화이트리스트 (Lines 206-253)
  • 인증 필요 자원 (Lines 256-268)

이들이 모두 정상 동작한다면 문제없지만, 실제 운영 SecurityConfig와의 일관성을 한 번 더 검증하세요.

src/test/java/com/daedan/festabook/event/controller/EventDateControllerTest.java (1)

24-37: 테스트 설정 단순화 - Clock MockBean 제거

AI Summary에 따르면 @MockitoBean Clock 필드가 제거되었습니다.

분석:

  • 제공된 테스트 코드를 살펴본 결과, EventDate 테스트들은 명시적인 날짜 값을 사용하고 있어 (예: LocalDate.of(2025, 7, 20)) Clock에 의존하지 않습니다
  • EventDateFixture가 현재 시간을 사용한다면 Clock MockBean이 필요하지만, 그런 경우가 보이지 않습니다
  • AcceptanceTestSupport에서 필요시 Clock 설정을 제공할 수 있습니다

결론:
시간 기반 로직이 없는 한 Clock 제거는 안전합니다. 만약 향후 "현재 날짜 기준" 테스트가 추가된다면 AcceptanceTestSupport에 Clock 설정을 추가하는 것을 고려하세요.

src/test/java/com/daedan/festabook/global/config/WebMvcConfigTest.java (1)

9-19: WebMvc 설정 테스트 마이그레이션 완료

WebMvcConfig의 커스텀 ArgumentResolver 테스트가 AcceptanceTestSupport로 전환되었습니다.

검증 포인트:

  • @FestivalId 파라미터 리졸버가 정상 동작하는지 테스트 (Line 52)
  • AcceptanceTestSupport가 WebMvc 전체 컨텍스트를 로드하여 커스텀 설정이 적용되는지 확인

확인 완료:
테스트 로직이 변경되지 않았고 ArgumentResolver 검증이 유지되므로, 실제 테스트가 통과한다면 AcceptanceTestSupport가 WebMvc 설정을 올바르게 로드하는 것으로 판단할 수 있습니다.

infra/appspec.yml (2)

9-12: ApplicationStop 훅 구현이 안전하게 설계되었습니다

stop.sh 스크립트를 확인한 결과, 애플리케이션 종료 로직이 기본적인 안전 요구사항을 모두 충족하고 있습니다:

  • 프로세스 식별: pgrep -f "festabook.*\.jar"로 명확하게 jar 파일을 대상으로 하므로 프로세스를 안정적으로 찾습니다
  • 우아한 종료 구현: SIGTERM(kill -15) → 30초 대기 → SIGKILL(kill -9) 패턴을 적용하여 데이터 손실 없이 정상 종료 기회를 제공합니다
  • 에러 처리: 프로세스가 이미 종료된 상태에서도 에러 없이 성공으로 처리합니다 (조건문으로 PID 확인 후 존재하지 않으면 무시)

한 가지 확인할 점: 스크립트 내부의 TIMEOUT 값(30초)과 appspec.yml의 timeout 값(60초)이 분리되어 있습니다. 현재 설정상 실제로는 30초 안에 정상 종료를 시도하고, 나머지 30초는 안전장치로 작동하는 구조인데, 향후 유지보수 시 두 값의 연관성을 명시적으로 문서화하면 의도가 더 명확해질 것입니다.


5-5: 배포 번들 구성을 잘못 이해했습니다

source: /에 대한 우려는 타당하지 않습니다. 현재 배포 프로세스는 실제로 매우 효율적으로 설계되어 있습니다.

GitHub Actions 워크플로우의 "Create deploy bundle" 단계를 보면, 배포할 zip 번들에는 오직 3가지만 포함됩니다:

  • 실행 가능한 JAR 파일 (빌드 결과물)
  • appspec.yml (배포 설정)
  • infra 디렉토리의 shell 스크립트들 (stop.sh, clean.sh, start.sh, validate.sh)

따라서 source: /는 "저장소 전체"가 아니라 이 최소한의 번들 내용을 의미하므로, 테스트 코드, 문서, .git, IDE 설정 파일 등이 배포되지 않습니다. 현재 설정은 불필요한 파일 배포를 잘 방지하고 있으며, 패키지 크기 최적화도 잘 이루어지고 있습니다.

Likely an incorrect or invalid review comment.

src/test/java/com/daedan/festabook/FestabookApplicationTests.java (1)

3-10: 변경 사항 확인됨

AcceptanceTestSupport가 @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)를 기반으로 설계되어 있어, 기존 @SpringBootTest 방식과 동일한 수준의 스프링 애플리케이션 컨텍스트 검증을 제공합니다. 따라서 contextLoads() 테스트는 정상적으로 전체 빈 로드와 애플리케이션 설정을 검증하는 스모크 테스트로 작동합니다.

단순히 상속 구조를 통해 테스트 인프라를 정리한 것일 뿐, 실질적인 검증 범위나 신뢰도가 감소하지 않습니다. 테스트 커버리지 동등성을 확인할 필요는 없습니다.

src/test/java/com/daedan/festabook/place/controller/PlaceImageControllerTest.java (1)

23-23: 테스트 기반 클래스 전환이 올바르게 적용되었습니다.

AcceptanceTestSupport 상속을 통해 테스트 설정을 중앙화한 점이 좋습니다. 여러 nested 테스트 클래스(addPlaceImage, updateFestivalImagesSequence, deletePlaceImageByPlaceImageId)가 모두 동일한 테스트 인프라를 공유하게 되어 일관성이 보장됩니다.

Also applies to: 34-34

src/test/java/com/daedan/festabook/event/controller/EventControllerTest.java (1)

25-25: 테스트 인프라 통합이 올바르게 수행되었습니다.

AcceptanceTestSupport로의 전환이 잘 이루어졌습니다. 특히 이 테스트는 setFixedClock() 메서드(lines 316-327)에서 mocked clock bean을 사용하는데, AcceptanceTestSupport가 이러한 테스트 더블을 제공하고 있어 테스트 로직의 변경 없이 마이그레이션이 가능했다는 점이 좋습니다.

Also applies to: 43-43

src/test/java/com/daedan/festabook/lineup/controller/LineupControllerTest.java (1)

19-19: 테스트 기반 클래스 통합이 적절하게 완료되었습니다.

여러 nested 테스트 클래스(addLineup, getAllLineupByFestivalId, updateLineup, deleteLineupByLineupId)가 모두 공통 테스트 인프라를 활용하도록 개선되었습니다.

Also applies to: 32-32

src/test/java/com/daedan/festabook/device/controller/DeviceControllerTest.java (1)

13-13: 테스트 인프라 표준화가 잘 적용되었습니다.

AcceptanceTestSupport 상속으로 테스트 설정이 간소화되었고, 실제 테스트 로직에는 영향이 없습니다.

Also applies to: 21-21

src/test/java/com/daedan/festabook/question/controller/QuestionControllerTest.java (1)

19-19: 테스트 기반 클래스 전환이 성공적으로 완료되었습니다.

5개의 nested 테스트 클래스(createQuestion, getAllQuestionByFestivalId, updateQuestionAndAnswer, updateSequence, deleteQuestionByQuestionId)가 모두 통합된 테스트 인프라를 사용하게 되어, 향후 테스트 환경 변경 시 일관성 있게 관리할 수 있습니다.

Also applies to: 31-31

src/test/java/com/daedan/festabook/global/exception/GlobalExceptionHandlerTest.java (1)

11-11: 예외 핸들러 테스트의 인프라 통합이 올바르게 완료되었습니다.

AcceptanceTestSupport로 전환하면서도 이 테스트에 특화된 로거 설정(lines 48-62의 @BeforeEach/@AfterEach)은 그대로 유지되어, 데이터베이스 예외에 대한 로그 레벨 검증 로직이 정상적으로 동작할 수 있습니다. 테스트별 특수 설정과 공통 인프라를 적절히 분리한 좋은 예시입니다.

Also applies to: 35-35

src/test/java/com/daedan/festabook/place/controller/PlaceGeographyControllerTest.java (1)

18-18: 테스트 인프라 통합이 적절하게 수행되었습니다.

AcceptanceTestSupport로 전환하면서도 이 테스트에 특화된 RestAssured 설정(lines 60-70의 @BeforeAll/@afterall)은 적절히 보존되었습니다. 좌표 정보 검증을 위한 JsonPathConfig.NumberReturnType.DOUBLE 설정이 필요한 이 테스트의 특성을 고려하여, 공통 인프라와 테스트별 설정을 잘 분리했습니다.

이는 테스트 인프라 표준화 시 "모든 것을 기반 클래스에 넣기"보다는 "공통적인 부분만 추출하고 특수한 설정은 각 테스트에 유지"하는 원칙을 잘 따른 사례입니다.

Also applies to: 41-41

src/test/java/com/daedan/festabook/festival/controller/FestivalControllerTest.java (1)

26-26: AcceptanceTestSupport 기반 클래스로의 마이그레이션이 적절합니다.

테스트 인프라를 AcceptanceTestSupport로 통일하여 코드 중복을 줄이고 일관성을 확보한 좋은 변경입니다. 이를 통해 RestAssured 포트 설정, 모의 객체 주입 등이 중앙에서 관리되어 유지보수성이 향상됩니다.

Also applies to: 45-45

src/main/java/com/daedan/festabook/global/logging/LoggingPointcuts.java (1)

1-21: 중앙화된 포인트컷 설계가 좋습니다.

여러 Aspect에서 사용되는 포인트컷을 한 곳에 모아 관리하면 일관성 유지와 수정 시 변경 범위를 최소화할 수 있습니다. final 클래스와 private 생성자를 통해 유틸리티 클래스임을 명확히 했고, 텍스트 블록을 활용해 복잡한 포인트컷 표현식의 가독성도 확보했습니다.

다만, 향후 포인트컷이 추가될 경우를 대비해 클래스 레벨에 간단한 Javadoc 주석을 추가하면 다른 개발자가 이 클래스의 목적을 빠르게 파악하는 데 도움이 될 수 있습니다.

src/test/java/com/daedan/festabook/place/controller/PlaceControllerTest.java (1)

42-42: AcceptanceTestSupport 상속을 통한 테스트 인프라 통일

AcceptanceTestSupport로 마이그레이션하면서 shuffleManager 모의 객체를 기반 클래스에서 상속받아 사용하는 구조가 됩니다. 이는 테스트 코드의 보일러플레이트를 줄이고, 모의 객체 설정의 일관성을 보장합니다.

이전에 별도로 @MockitoBeanLocalServerPort 설정을 관리하던 방식에서 벗어나, 기반 클래스에서 공통 설정을 제공받는 구조로 개선되었습니다.

Also applies to: 61-61

src/test/java/com/daedan/festabook/festival/service/FestivalNotificationConcurrencyTest.java (1)

15-15: 동시성 테스트의 AcceptanceTestSupport 마이그레이션 완료

AcceptanceTestSupport 기반으로 전환되어 일관된 테스트 환경에서 동시성 검증이 가능합니다.

Also applies to: 25-25

src/test/java/com/daedan/festabook/festival/controller/FestivalNotificationSubscriptionControllerTest.java (1)

21-21: AcceptanceTestSupport 기반으로 테스트 인프라 통일

festivalNotificationManager 모의 객체가 이제 AcceptanceTestSupport에서 제공되어, 기존의 개별 @MockitoBean 선언이 필요 없어졌습니다. 이를 통해 테스트 코드가 간결해지고, 모의 객체 설정의 일관성이 보장됩니다.

Also applies to: 30-30

src/test/java/com/daedan/festabook/announcement/controller/AnnouncementControllerTest.java (1)

29-29: AcceptanceTestSupport 마이그레이션 완료

festivalNotificationManager를 통한 알림 전송 검증이 AcceptanceTestSupport 기반 클래스에서 제공되는 모의 객체를 사용하도록 전환되었습니다.

Also applies to: 42-42

src/test/java/com/daedan/festabook/global/argumentresolver/FestivalIdArgumentResolverTest.java (2)

9-9: AcceptanceTestSupport 기반 테스트로 전환

AcceptanceTestSupport로의 마이그레이션이 올바르게 수행되었습니다.

Also applies to: 19-19


93-101: TestController의 Spring 컨텍스트 등록은 이미 정상적으로 작동합니다

AcceptanceTestSupport@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 설정에서는 테스트 클래스 내의 @RestController 같은 Spring 스테레오타입 어노테이션이 자동으로 발견되고 등록됩니다. 이는 Spring Boot 테스트 인프라의 표준 동작이므로, 현재 구조에서 별도의 @Import 설정이나 추가 등록 없이도 /test/resolveArgument 엔드포인트는 정상적으로 작동합니다. 테스트 코드에서 RestAssured를 통해 직접 HTTP 호출을 하고 있으며, 이들이 기대한 응답을 받도록 작성된 것은 이미 컨트롤러가 적절히 등록되어 있음을 의미합니다.

infra/start.sh (1)

6-10: 동적 프로파일 선택 로직 추가

DEPLOYMENT_GROUP_NAME을 기반으로 devprod 프로파일을 자동으로 선택하는 로직이 좋습니다. 이를 통해 하드코딩된 프로파일 설정을 제거하고, 배포 환경에 따라 자동으로 적절한 설정이 적용됩니다.

src/test/java/com/daedan/festabook/council/controller/CouncilControllerTest.java (1)

19-30: AcceptanceTestSupport로의 마이그레이션이 적절합니다.

테스트 인프라의 통합이 잘 이루어졌습니다. AcceptanceTestSupport@SpringBootTest, @LocalServerPort, RestAssured.port 설정을 모두 제공하므로, 개별 테스트 클래스에서의 중복 설정이 제거되어 코드 유지보수성이 향상되었습니다.

다만, AcceptanceTestSupport에서 제공하는 Clock, FestivalNotificationManager, ShuffleManager mock bean들이 이 테스트에서는 사용되지 않는 것으로 보입니다. 현재는 문제가 되지 않지만, 필요 없는 mock이 주입되는 것은 테스트 컨텍스트에 불필요한 의존성을 추가하는 점을 인지해 두시면 좋겠습니다.

build.gradle (1)

109-127: Jacoco 제외 패턴의 범위 확인 필요

현재 제외 패턴이 꽤 넓은 범위를 가지고 있습니다:

  • **/*Application*: Application 클래스 제외 (적절함)
  • **/*Config*: 모든 Config 포함 클래스 제외 (ConfigService, ConfigController 등도 포함될 수 있음)
  • **/*Aspect*: Aspect 클래스 제외 (적절함)
  • **/*Logging*: Logging 포함 클래스 제외 (LoggingService 등도 포함될 수 있음)

의도한 바가 맞다면 문제없지만, 비즈니스 로직이 포함된 클래스가 실수로 제외될 가능성이 있습니다. 더 명시적인 패턴(예: **/config/**/*Config.class)을 사용하거나, 제외 사유를 주석으로 문서화하는 것을 권장합니다.

src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java (1)

17-29: 테스트 베이스 클래스 구조가 잘 설계되었습니다.

AcceptanceTestSupport가 공통 테스트 인프라를 효과적으로 통합하고 있습니다:

  • @Import(TestSecurityConfig.class): 보안 설정 재사용
  • @SpringBootTest(RANDOM_PORT): 통합 테스트 환경 제공
  • @DisplayNameGeneration: 테스트 출력 가독성 향상
  • Protected mock beans: 하위 클래스에서 필요시 접근 가능

20개 이상의 테스트 클래스가 이 베이스 클래스를 사용하게 되어, 테스트 코드의 일관성과 유지보수성이 크게 향상될 것입니다.

src/test/java/com/daedan/festabook/festival/controller/AndroidFestivalNotificationSubscriptionControllerTest.java (2)

19-27: AcceptanceTestSupport 마이그레이션이 올바르게 수행되었습니다.

FcmNotificationManager에서 festivalNotificationManager로의 전환이 일관되게 적용되었습니다. AcceptanceTestSupport에서 제공하는 mock bean을 활용하여 테스트 코드가 간결해졌습니다.


66-67: Mock 검증 패턴이 일관되게 적용되었습니다.

성공 케이스에서는 then(festivalNotificationManager).should().subscribeAndroidFestivalTopic(any(), any())로 호출을 검증하고, 예외 케이스에서는 shouldHaveNoInteractions()로 호출이 없었음을 확인하는 패턴이 적절합니다.

이 패턴은 AnnouncementControllerTestsendAnnouncementNotification 테스트와도 일관되어 있어 코드베이스 전반의 통일성이 잘 유지되고 있습니다.

Also applies to: 96-96, 119-119, 143-143

src/main/java/com/daedan/festabook/global/logging/LoggingAspect.java (1)

23-26: 관심사 분리가 잘 적용되었습니다.

@Order(Ordered.HIGHEST_PRECEDENCE + 1) 설정으로 TracingAspect(HIGHEST_PRECEDENCE) 이후에 실행되도록 하여, 추적(tracing)과 로깅(logging)의 책임이 명확히 분리되었습니다.

또한 중앙 집중식 pointcut(LoggingPointcuts.applicationLayers())을 사용함으로써:

  • 여러 Aspect에서 동일한 pointcut을 재사용 가능
  • pointcut 수정 시 한 곳에서만 변경하면 됨
  • 코드 중복 감소 및 유지보수성 향상

이런 구조화는 단일 책임 원칙(SRP)과 DRY 원칙을 잘 준수하고 있습니다.

Comment on lines +79 to +84
- name: Trigger CodeDeploy deployment
run: |
aws deploy create-deployment \
--application-name "${{ secrets.CODEDEPLOY_APP_NAME }}" \
--deployment-group-name "${{ secrets.CODEDEPLOY_DEPLOYMENT_GROUP_PROD }}" \
--s3-location bucket=${{ secrets.S3_ARTIFACT_BUCKET }},bundleType=zip,key=prod/builds/${{ steps.bundle.outputs.zip_name }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# 워크플로우 파일 조회 및 검토
cat -n .github/workflows/ci-cd-prod.yml

Repository: festabook/backend

Length of output: 3395


CodeDeploy 배포 트리거 구성이 기본 원칙을 잘 따르고 있습니다.

S3 경로를 prod/builds/로 체계적으로 분리하고, 배포 그룹도 환경별로 구분한 점은 좋은 관행입니다. 다만 현재 워크플로우에는 배포 단계의 실패를 처리하는 메커니즘이 완전히 빠져있다는 점이 아쉽습니다.

구체적으로 개선이 필요한 부분:

  1. 배포 실패 시 알림 부재 - CodeDeploy 배포 명령이 실패하면 현재는 조용히 실패합니다. 실패 여부를 감지하고 팀에 알릴 수 있도록 Slack 알림이나 GitHub 체크 실패 같은 메커니즘을 추가하면, 배포 문제를 빠르게 감지할 수 있습니다.

  2. 배포 상태 확인 부재 - aws deploy create-deployment는 배포를 트리거하기만 하고, 실제 배포가 성공적으로 완료되었는지는 확인하지 않습니다. 배포 ID를 캡처해서 aws deploy wait deployment-successful 같은 명령으로 완료를 대기하거나, get-deployment API로 최종 상태를 확인하는 방식을 고려해보세요. 이렇게 하면 부분 실패를 감지할 수 있습니다.

추가로, CodeDeploy 배포 그룹 설정 확인도 권장합니다:

  • 자동 롤백이 활성화되어 있는지 (배포 실패 시 이전 버전으로 복구)
  • 트래픽 제어 설정 여부 (Blue/Green 배포로 무중단 배포 가능)

이런 AWS 측 설정과 워크플로우 레벨의 모니터링을 함께 구성하면, 프로덕션 배포의 안정성을 크게 높일 수 있습니다.

plugins {
id 'java'
id 'jacoco'
id "org.sonarqube" version "7.2.0.6526"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

SonarQube Gradle plugin latest version 2025

💡 Result:

The latest SonarQube Gradle plugin release in 2025 is version 6.2.0.5505 (published May 15, 2025). [1][2]

Sources:

  • Gradle Plugin Portal — org.sonarqube 6.2.0.5505. [1]
  • SonarSource GitHub (sonar-scanner-gradle) releases. [2]

🌐 Web query:

org.sonarqube Gradle plugin 7.2.0.6526 compatibility

💡 Result:

Short answer: SonarScanner for Gradle 7.2.0.6526 (released Dec 4, 2025) adds official support for Gradle's configuration-cache but had a bug affecting concurrent task modifications; 7.2.1 and 7.2.2 follow to fix those issues. The 7.2.x line also documents dropping support for Gradle 8.3 (recommend using 8.4–8.14 or Gradle 9.x). See Sonar docs and the Sonar community posts for details. [1][2][3][4]

Sources:

  • SonarScanner for Gradle release listing (7.2.0.6526). [1]
  • Sonar community announcement and follow-ups (7.2.0 → 7.2.1 → 7.2.2, config-cache notes). [2]
  • Community thread reporting behavior changes / wildcard issue in 7.2.x. [3]
  • Gradle Plugin Portal entry for org.sonarqube versions. [4]

🏁 Script executed:

# Check if there's a Gradle version specified in the repository
find . -name "gradle.properties" -o -name "gradle-wrapper.properties" -o -name "build.gradle" -o -name "build.gradle.kts" | head -10

Repository: festabook/backend

Length of output: 118


🏁 Script executed:

cat ./gradle/wrapper/gradle-wrapper.properties

Repository: festabook/backend

Length of output: 313


SonarQube 플러그인 버전 업그레이드 권장

현재 사용 중인 7.2.0.6526 버전은 2025년 12월에 배포된 최신 릴리스이지만, 동시 작업 수정(concurrent task modifications)과 관련된 알려진 버그를 포함하고 있습니다. 이후 배포된 7.2.1, 7.2.2에서 이러한 문제들이 해결되었으므로 패치 버전으로의 업그레이드를 권장합니다.

현재 사용 중인 Gradle 버전 8.14.2는 7.2.x 라인과 완벽하게 호환되므로 호환성 측면에서는 문제가 없습니다. 다만 build 안정성을 위해 다음과 같은 옵션을 고려할 수 있습니다:

  • 즉시 업그레이드: 7.2.1 또는 7.2.2로 업그레이드하면 알려진 버그를 제거하면서도 최신 기능(예: configuration-cache 공식 지원)을 유지할 수 있습니다.
  • 보수적 선택: 6.2.0.5505로 다운그레이드하면 더 오래되고 안정적인 버전을 사용할 수 있지만, 최신 기능은 포기하게 됩니다.

현재 상황에서는 7.2.x 라인의 패치 버전 중 하나로의 업그레이드가 가장 합리적입니다.

🤖 Prompt for AI Agents
In build.gradle around line 4, the SonarQube plugin is pinned to "org.sonarqube"
version "7.2.0.6526" which contains a known bug; update the version to a patched
release (preferably "7.2.2" or at minimum "7.2.1"), then run a full build and
Sonar analysis locally/CI to verify compatibility, update any plugin-lock or
gradle-wrapper caches if present, and commit the version bump with a short
changelog note referencing the bugfix.

Comment on lines +14 to +16
BeforeInstall:
- location: clean.sh
timeout: 60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

BeforeInstall 훅의 runas 설정 누락

BeforeInstall 훅에 runas 파라미터가 없습니다.

문제점:

  • 다른 훅들(ApplicationStop, ApplicationStart, ValidateService)은 모두 runas: ubuntu를 명시하고 있습니다
  • runas가 없으면 기본적으로 root 권한으로 실행되어 권한 불일치 문제가 발생할 수 있습니다
  • 특히 clean.sh가 파일을 삭제하는 작업이라면, root로 실행 후 생성된 파일이 이후 ubuntu 사용자에 의해 접근 불가능할 수 있습니다

해결 방법:

🔎 runas 파라미터 추가
 BeforeInstall:
   - location: clean.sh
     timeout: 60
+    runas: ubuntu
🤖 Prompt for AI Agents
In infra/appspec.yml around lines 14 to 16, the BeforeInstall hook is missing a
runas parameter so clean.sh will run as root by default; update the
BeforeInstall hook to include runas: ubuntu (matching the other hooks) so
clean.sh runs with the ubuntu user and avoids root-owned files/permission
mismatches.

Comment on lines +1 to +3
#!/bin/bash
echo "> Cleaning old jar files"
rm -rf /home/ubuntu/app/*
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

rm -rf 명령어의 안전장치가 필요합니다.

현재 구현은 검증 없이 바로 디렉토리를 삭제하는데, 배포 파이프라인에서 자동으로 실행되는 스크립트이기 때문에 다음과 같은 위험이 있습니다:

  1. 경로가 잘못 설정된 경우 의도하지 않은 파일 삭제
  2. 디렉토리가 존재하지 않을 때의 에러 핸들링 부재
  3. 삭제 실패 시 배포가 계속 진행되어 예상치 못한 동작 가능

개선 방안:

🔎 안전성을 강화한 개선안
 #!/bin/bash
+
+APP_DIR="/home/ubuntu/app"
+
+# 디렉토리 존재 여부 확인
+if [ ! -d "$APP_DIR" ]; then
+    echo "> Directory $APP_DIR does not exist. Creating..."
+    mkdir -p "$APP_DIR"
+    exit 0
+fi
+
+# 삭제 전 디렉토리 검증 (예: 최소 경로 길이 체크)
+if [ ${#APP_DIR} -lt 10 ]; then
+    echo "> ERROR: APP_DIR path is too short. Aborting for safety."
+    exit 1
+fi
+
 echo "> Cleaning old jar files"
-rm -rf /home/ubuntu/app/*
+
+# jar 파일만 선택적으로 삭제 (더 안전한 방식)
+find "$APP_DIR" -maxdepth 1 -name "*.jar" -type f -delete
+
+# 또는 전체 삭제가 필요한 경우에도 에러 핸들링 추가
+# rm -rf "$APP_DIR"/* || { echo "> Failed to clean directory"; exit 1; }

추가 고려사항:

  • JAR 파일만 삭제가 필요한지, 아니면 전체 디렉토리 정리가 필요한지 명확히 하는 것이 좋습니다.
  • 로그 파일이나 설정 파일 등 보존해야 할 파일이 있는지 확인해주세요.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#!/bin/bash
echo "> Cleaning old jar files"
rm -rf /home/ubuntu/app/*
#!/bin/bash
APP_DIR="/home/ubuntu/app"
# 디렉토리 존재 여부 확인
if [ ! -d "$APP_DIR" ]; then
echo "> Directory $APP_DIR does not exist. Creating..."
mkdir -p "$APP_DIR"
exit 0
fi
# 삭제 전 디렉토리 검증 (예: 최소 경로 길이 체크)
if [ ${#APP_DIR} -lt 10 ]; then
echo "> ERROR: APP_DIR path is too short. Aborting for safety."
exit 1
fi
echo "> Cleaning old jar files"
# jar 파일만 선택적으로 삭제 (더 안전한 방식)
find "$APP_DIR" -maxdepth 1 -name "*.jar" -type f -delete
# 또는 전체 삭제가 필요한 경우에도 에러 핸들링 추가
# rm -rf "$APP_DIR"/* || { echo "> Failed to clean directory"; exit 1; }
🤖 Prompt for AI Agents
In infra/clean.sh around lines 1 to 3, the script unconditionally runs rm -rf
/home/ubuntu/app/* which is dangerous; change it to validate and limit deletion:
verify the TARGET path is non-empty and equals the expected directory (or set
via a trusted variable), ensure the directory exists before deleting, restrict
removal to only *.jar files (or make it configurable), log what will be deleted,
perform the deletion and check its exit status, and exit with a non-zero code on
failure so the pipeline stops; also consider adding a dry-run or explicit
confirmation flag for extra safety.

Comment on lines +18 to +23
@Deprecated(since = "2025/11/27")
/**
* @Deprecated 날짜 : 2025/11/27
* Dev 환경 로컬 저장 방식에서 S3 방식으로 변경되어 사용하지 않게 되었습니다.
* 하지만, 추후 로컬에서 사용할 수 있는 가능성이 있어 남겨둡니다.
*/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Deprecation 의도는 명확하지만 구현이 불완전합니다.

Javadoc에 "추후 로컬에서 사용할 수 있는 가능성이 있어 남겨둡니다"라고 했는데, @Component@Profile이 제거되어 실제로는 Spring 컨텍스트에 등록되지 않습니다. 또한 Line 26-27의 @Value 주입도 작동하지 않을 것입니다.

현재 상태의 문제점:

  • 클래스는 남아있지만 실제로 사용 불가능
  • 누군가 나중에 사용하려 해도 추가 수정 필요
  • 중간 상태로 혼란을 줄 수 있음

대안 1 (권장): 완전히 제거

  • 필요하면 Git 히스토리에서 복구 가능
  • 코드베이스를 깔끔하게 유지
  • Deprecated 코드 관리 부담 제거

대안 2: 정말 남기고 싶다면 사용 가능한 상태로 유지

@Component
@Profile("local")  // 로컬 개발용 프로필
@Deprecated(since = "2025/11/27")
public class LocalStorageManager implements StorageManager {

현재 프로젝트에 local 프로필이 없다면 추가 작업이 필요하지만, 이렇게 하면 실제로 로컬 환경에서 재활용 가능합니다.

🔎 제안하는 수정안

옵션 A: 완전 제거 (추천)

# 파일 삭제 후 필요시 Git에서 복구
git rm src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java

옵션 B: 사용 가능하도록 유지

 @Slf4j
 @Loggable
+@Component
+@Profile("local")
 @Deprecated(since = "2025/11/27")
 /**
  * @Deprecated 날짜 : 2025/11/27
  * Dev 환경 로컬 저장 방식에서 S3 방식으로 변경되어 사용하지 않게 되었습니다.
- * 하지만, 추후 로컬에서 사용할 수 있는 가능성이 있어 남겨둡니다.
+ * 로컬 개발 환경(spring.profiles.active=local)에서만 활성화됩니다.
  */
 public class LocalStorageManager implements StorageManager {

그리고 application-local.yml 추가:

local:
  storage:
    base-path: ./uploads
🤖 Prompt for AI Agents
In
src/main/java/com/daedan/festabook/storage/infrastructure/LocalStorageManager.java
around lines 18 to 23, the class is marked @Deprecated but was stripped of
@Component/@Profile and @Value injections, leaving it non-functional and
confusing; either delete the file entirely (remove from repo so it can be
recovered from Git history) or restore Spring wiring to keep it usable by
re-adding @Component and @Profile("local") and re-enabling the @Value fields
plus an application-local.yml entry for local.storage.base-path so the class can
be auto-configured for local development.

Comment on lines +6 to +12
management:
server:
port: 9000
endpoints:
web:
exposure:
include: prometheus, health
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# 기본 디렉토리 구조 확인
find . -maxdepth 2 -type d | head -20

Repository: festabook/backend

Length of output: 257


🏁 Script executed:

# infra 디렉토리와 보안 관련 파일 검색
fd "infra|security|\.github" -t d . | head -20

Repository: festabook/backend

Length of output: 1229


🏁 Script executed:

# YAML 설정 파일에서 보안/포트 관련 내용 검색
rg -i "security.*group|ingress|9000|management.*port" -t yaml | head -30

Repository: festabook/backend

Length of output: 121


🏁 Script executed:

# infra/validate.sh 파일 확인
find . -name "validate.sh" -o -name "*security*" -type f | grep -E "(infra|\.github)" | head -20

Repository: festabook/backend

Length of output: 80


🏁 Script executed:

# infra 디렉토리 내용 확인
ls -la ./infra/

Repository: festabook/backend

Length of output: 530


🏁 Script executed:

# validate.sh 내용 확인
cat -n ./infra/validate.sh

Repository: festabook/backend

Length of output: 637


🏁 Script executed:

# .github/workflows 확인
ls -la ./.github/workflows/

Repository: festabook/backend

Length of output: 651


🏁 Script executed:

# Spring Security 설정 확인
find ./src/main/java/com/daedan/festabook/global/security -type f -name "*.java" | head -10

Repository: festabook/backend

Length of output: 722


🏁 Script executed:

# application*.yml 파일 전체 확인 (application-monitoring.yml 제외)
fd "application.*\.yml" src/main/resources/ -x cat -n

Repository: festabook/backend

Length of output: 1968


🏁 Script executed:

# SecurityConfig.java 전체 내용 확인
cat -n ./src/main/java/com/daedan/festabook/global/security/config/SecurityConfig.java

Repository: festabook/backend

Length of output: 5868


🏁 Script executed:

# appspec.yml 확인 (AWS 배포 설정)
cat -n ./infra/appspec.yml

Repository: festabook/backend

Length of output: 641


🏁 Script executed:

# buildspec.yml 확인 (AWS 빌드 설정)
cat -n ./infra/buildspec.yml

Repository: festabook/backend

Length of output: 1654


🏁 Script executed:

# application-secret.yml 또는 환경 변수 설정 확인
find ./src/main/resources -name "*secret*" -o -name "*prod*" -o -name "*dev*" | xargs cat -n 2>/dev/null | head -50

Repository: festabook/backend

Length of output: 43


관리 포트(9000) 보안 설정 재검토 필요

포트 9000으로 관리 엔드포인트를 분리한 설계는 좋습니다. 다만, 현재 구현에 몇 가지 보안 개선이 필요합니다.

현재 상태 분석:

SecurityConfig.java에서 /actuator/health/actuator/prometheus를 GET_WHITELIST로 설정했습니다(52-53줄). 그러나 이는 메인 애플리케이션 포트(80)의 보안 규칙일 뿐, 별도 서버로 실행되는 management.server.port: 9000의 엔드포인트를 보호하지 않습니다.

문제점:

  1. 관리 엔드포인트에 인증이 없음: 포트 9000의 Prometheus와 health 엔드포인트는 현재 누구나 접근 가능합니다. 시스템 메트릭, 리소스 사용량, 의존성 상태 정보가 노출될 수 있습니다.

  2. 잘못된 보안 설정 예시: 일반적인 Spring Security 설정으로는 management.server.port에서 실행되는 엔드포인트를 보호할 수 없습니다. 별도의 설정이 필요합니다.

  3. 네트워크 접근 제한 미확인: validate.sh는 localhost로만 헬스 체크하므로 배포 단계에서는 안전하지만, 프로덕션에서 포트 9000이 실제로 내부 네트워크로만 제한되는지 확인 필요합니다.

권장 개선 방향:

  1. 관리 엔드포인트 인증 추가 - application-monitoring.yml에 다음 설정 추가:

    management:
      endpoints:
        web:
          base-path: /actuator
      endpoint:
        health:
          show-details: when-authorized
      endpoints-supplied-by-boot:
        health:
          enabled: true
  2. 인프라 레벨 제한 - AWS 보안 그룹에서 9000 포트를 Prometheus 서버 또는 모니터링 도구의 IP만 허용하도록 설정

  3. 선택지 비교:

    • 기본 Spring Boot 관리 보안: 구현 간단하지만 비밀번호 하드코딩 가능
    • OAuth2/JWT: 더 강력하지만 구현 복잡도 증가
    • 네트워크 레벨 제한: 인프라 설정만으로 해결되므로 애플리케이션 코드 변경 불필요

현재 코드에서는 포트 분리만으로 보안을 담당하고 있으니, 추가적인 인증 계층이나 네트워크 제한 설정을 검토하시기 바랍니다.

🤖 Prompt for AI Agents
src/main/resources/application-monitoring.yml around lines 6-12: the management
server is exposed on port 9000 but has no authentication or details-restriction
configured; add management properties to set a base-path (/actuator), limit
health details (show-details: when-authorized) and enable the actuator endpoints
explicitly, implement application-side protection by adding a separate Spring
Security configuration that applies only to requests on management.server.port
(e.g., a SecurityFilterChain or request matcher bound to port 9000) to require
authentication (Basic/OAuth2/JWT) for /actuator/** and /actuator/prometheus, and
ensure infrastructure-level network restrictions (AWS security group / firewall)
only allow the Prometheus server IPs to reach port 9000; finally, keep
validate.sh logic but rework it to authenticate or run against localhost if
authentication is enforced.

Comment on lines +20 to +22
tracing:
sampling:
probability: 1.0
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

프로덕션 환경에서 100% 트레이싱은 성능 오버헤드 유발

sampling.probability: 1.0은 모든 요청을 추적(trace)합니다.

문제점:

  • 높은 CPU/메모리 오버헤드: 모든 요청에 대해 스팬(span) 생성, 컨텍스트 전파, 직렬화 작업이 발생합니다
  • 네트워크 부하: OTLP 엔드포인트로 대량의 추적 데이터가 전송됩니다
  • 스토리지 비용: 추적 백엔드(Jaeger, Tempo 등)에 모든 데이터가 저장되어 비용이 급증합니다
  • 프로덕션 성능 저하: 트래픽이 많은 시스템에서는 실제 응답 시간이 늘어날 수 있습니다

권장 대안:

  1. 환경별 샘플링 비율 설정 (권장):

    # application-dev.yml
    management:
      tracing:
        sampling:
          probability: 1.0  # 개발 환경: 100%
    
    # application-prod.yml
    management:
      tracing:
        sampling:
          probability: 0.1  # 프로덕션: 10%
  2. 적응형 샘플링: 오류가 발생한 요청은 항상 추적, 정상 요청은 낮은 비율로 샘플링

    • Spring Boot 3.x에서는 TracingSampler 빈을 커스터마이징하여 구현 가능

업계 표준:

  • Low traffic (< 100 req/min): 0.5 ~ 1.0
  • Medium traffic (< 1000 req/min): 0.1 ~ 0.3
  • High traffic (> 1000 req/min): 0.01 ~ 0.05

Comment on lines +23 to +26
otlp:
tracing:
endpoint: "http://localhost:4317"
transport: grpc
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for OTLP and OpenTelemetry related configurations
echo "=== Searching for OTLP/OpenTelemetry references ==="
rg -i "otel|opentelemetry|collector" --type yaml --type yml . 2>/dev/null | head -30

echo -e "\n=== Checking for deployment configurations ==="
find . -type f \( -name "docker-compose*.yml" -o -name "docker-compose*.yaml" -o -name "*.deployment.yml" -o -name "*.deployment.yaml" \) 2>/dev/null | head -20

echo -e "\n=== Checking for Kubernetes configurations ==="
find . -type f -path "*/k8s/*" -name "*.yml" -o -path "*/k8s/*" -name "*.yaml" 2>/dev/null | head -20

echo -e "\n=== Searching infrastructure directory ==="
find infra/ -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null | head -20

echo -e "\n=== Checking .github/workflows for OTLP references ==="
find .github/workflows -type f -name "*.yml" -o -name "*.yaml" 2>/dev/null

Repository: festabook/backend

Length of output: 648


🏁 Script executed:

#!/bin/bash
# Check the actual application-monitoring.yml file for context
echo "=== Full application-monitoring.yml content ==="
find . -name "application-monitoring.yml" -type f 2>/dev/null -exec cat -n {} \;

echo -e "\n=== Check application.yml or application.yaml ==="
find . -name "application.yaml" -o -name "application.yml" | grep -E "resources|config" | head -5 | xargs cat -n 2>/dev/null | head -50

Repository: festabook/backend

Length of output: 2082


🏁 Script executed:

#!/bin/bash
# Search for Spring Boot OTLP/tracing related configurations in all config files
echo "=== All management.tracing references ==="
rg "management:.*tracing|tracing:.*endpoint" --type yaml . 2>/dev/null

echo -e "\n=== Check for http vs grpc scheme usage in configs ==="
rg "http://localhost:4317|grpc://localhost:4317|localhost:4317" . 2>/dev/null

Repository: festabook/backend

Length of output: 246


🏁 Script executed:

#!/bin/bash
# Check docker-compose.yml for OTLP collector setup
echo "=== docker-compose.yml content ==="
cat -n ./src/main/resources/docker-compose.yml

Repository: festabook/backend

Length of output: 893


🌐 Web query:

Spring Boot management.otlp.tracing.endpoint http vs grpc scheme

💡 Result:

Short answer: management.otlp.tracing.endpoint expects an OTLP URL whose scheme/port selects the transport. Spring Boot defaults to HTTP/protobuf (typically http://host:4318/v1/traces). To use gRPC you either set management.otlp.tracing.transport=grpc or define an OtlpGrpcSpanExporter (gRPC commonly uses port 4317). Sources: Spring blog/docs and OtlpAutoConfiguration. [1][2][3]

Examples:

  • HTTP/protobuf (default):
    management.otlp.tracing.endpoint=http://localhost:4318/v1/traces. [1][2]

  • gRPC (explicit):
    management.otlp.tracing.transport=grpc
    management.otlp.tracing.endpoint=localhost:4317
    (or provide an OtlpGrpcSpanExporter bean). [2][3]

References

  1. Spring blog — OpenTelemetry with Spring Boot (exporter default http/protobuf).
  2. Spring Boot OtlpTracing/OtlpAutoConfiguration docs (default HTTP/protobuf; set transport=grpc or provide OtlpGrpcSpanExporter).
  3. Spring Boot API docs noting ports: 4318 for HTTP, 4317 for gRPC.

🌐 Web query:

OpenTelemetry Java OTLP endpoint localhost:4317 correct format

💡 Result:

Use a full URL with scheme. For OTLP/gRPC the correct endpoint is:

If you set signal-specific endpoints:

Notes: OTLP/HTTP defaults to port 4318 and requires signal paths (e.g. /v1/traces) when using the HTTP/protobuf exporter. [1][2]

Sources: [1] OpenTelemetry Java configuration. [2] OTLP exporter configuration.


OTLP Collector가 배포되지 않았습니다

코드를 검토해보니 application-monitoring.yml에는 OTLP 엔드포인트가 설정되어 있지만, 실제로 이 엔드포인트로 연결할 OpenTelemetry Collector가 어디에도 배포되지 않은 상태입니다.

구체적인 문제점:

  1. docker-compose.yml에 Collector가 없음: 로컬 개발 환경을 위한 docker-compose.yml에는 MySQL만 있고, OTLP Collector 서비스가 정의되어 있지 않습니다. 이는 개발 중 span 전송이 실패할 것을 의미합니다.

  2. 배포 환경에도 Collector 설정 없음: .github/workflows, infra/ 디렉토리에서도 OTLP Collector 관련 배포 설정을 찾을 수 없습니다. 프로덕션 환경에서도 이 연결은 작동하지 않을 것입니다.

권장사항:

  1. 로컬 개발용: docker-compose.yml에 OpenTelemetry Collector 서비스를 추가하세요. 이렇게 하면 개발 중에도 실제 트레이싱 동작을 검증할 수 있습니다.

    otel-collector:
      image: otel/opentelemetry-collector:latest
      container_name: otel_collector
      ports:
        - "4317:4317"  # gRPC 수신 포트
      environment:
        - GOGC=80
      networks:
        - app_network
  2. 스킴 간소화 (선택사항): 현재 http://localhost:4317는 유효하지만, gRPC 사용 시에는 localhost:4317처럼 포트만 지정하는 것이 더 명확합니다. 두 형식 모두 작동하므로 팀의 컨벤션에 맞게 선택하세요.

  3. 연결 실패 처리: Collector가 없을 때 애플리케이션이 시작되지 않도록 하거나, 로그에서 OTLP 연결 오류를 모니터링하는 구조를 고려해보세요.

🤖 Prompt for AI Agents
In src/main/resources/application-monitoring.yml around lines 23-26, the OTLP
endpoint is configured but no OpenTelemetry Collector is deployed; add an
otel-collector service to docker-compose.yml for local development (expose gRPC
4317 and attach to app network), add corresponding deployment/manifest or infra
config under infra/ and CI workflow steps in .github/workflows to ensure the
collector exists in staging/production, optionally normalize the endpoint to
"localhost:4317" for gRPC clarity, and implement application-side
startup/connection handling or fallback logging so the app fails fast or logs
OTLP connection errors when the collector is absent.

<!-- DEV, PROD: JSON 형식으로 로그 파일 기록 -->
<springProfile name="prod | dev">
<property name="LOG_FILE_PATH" value="/home/ubuntu/2025-festabook/spring-logs"/>
<property name="LOG_FILE_PATH" value="/var/log"/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# 1. logback-spring.xml의 전체 내용 확인
cat -n src/main/resources/logback-spring.xml

Repository: festabook/backend

Length of output: 1895


🏁 Script executed:

# 2. 배포/인프라 관련 스크립트 찾기
find . -type f \( -name "*.sh" -o -name "Dockerfile" -o -name "docker-compose.yml" \) | head -20

Repository: festabook/backend

Length of output: 170


🏁 Script executed:

# 3. 로그 디렉토리 관련 설정 찾기
rg "LOG_FILE_PATH|/var/log|log.*path" --type yaml --type xml -i

Repository: festabook/backend

Length of output: 379


🏁 Script executed:

# 4. 애플리케이션 실행 유저 관련 설정 찾기
rg "ubuntu|user|chown|chmod" -i | grep -E "\.(sh|dockerfile|yml|properties|xml):" | head -20

Repository: festabook/backend

Length of output: 700


🏁 Script executed:

# 배포 스크립트 실제 내용 확인
cat -n ./infra/start.sh

Repository: festabook/backend

Length of output: 931


🏁 Script executed:

# clean 스크립트 내용 확인
cat -n ./infra/clean.sh

Repository: festabook/backend

Length of output: 151


🏁 Script executed:

# appspec.yml 내용 확인 (CodeDeploy 설정)
cat -n ./infra/appspec.yml

Repository: festabook/backend

Length of output: 641


🏁 Script executed:

# 다른 배포 관련 파일 확인
ls -la ./infra/

Repository: festabook/backend

Length of output: 530


배포 스크립트에 로그 디렉토리 생성 및 권한 설정이 누락되어 있습니다.

logback 설정에서 /var/log 경로를 지정했으나, 배포 자동화 스크립트(infra/start.sh, infra/clean.sh, infra/appspec.yml)에 디렉토리 생성이나 권한 설정 로직이 없습니다. 현재 상태로는 애플리케이션 실행 시 로그 쓰기가 실패합니다.

구체적인 문제점:

  • /var/log는 일반적으로 root 소유이며 ubuntu 사용자는 쓰기 권한이 없음
  • start.sh에서 sudo를 사용하지만, 파일 쓰기 권한은 프로세스 소유권에 따라 결정됨
  • 배포 자동화에 디렉토리 생성 단계가 전혀 없어서 서버 배포 시마다 수동 설정이 필요함
  • CodeDeploy의 BeforeInstall 단계에서 디렉토리 초기화를 하지 않음

개선 방안:

애플리케이션 전용 서브디렉토리를 사용하고, 배포 스크립트에서 자동으로 처리하는 것을 권장합니다:

  1. logback-spring.xml 수정:

    <property name="LOG_FILE_PATH" value="/var/log/festabook"/>
  2. infra/start.sh 수정 (BeforeInstall이나 ApplicationStart 전에 실행):

    # 로그 디렉토리 생성 및 권한 설정 (start.sh 상단에 추가)
    mkdir -p /var/log/festabook
    sudo chown ubuntu:ubuntu /var/log/festabook
    sudo chmod 755 /var/log/festabook

    또는 sudo 권한이 필요하다면:

    sudo mkdir -p /var/log/festabook && \
    sudo chown ubuntu:ubuntu /var/log/festabook && \
    sudo chmod 755 /var/log/festabook
  3. appspec.yml BeforeInstall 단계에 별도 스크립트 추가 (권장):

    • infra/setup-logs.sh 같은 새 스크립트 생성
    • BeforeInstall에서 이 스크립트를 runas: root 권한으로 실행

이렇게 하면 배포할 때마다 로그 디렉토리가 자동으로 생성되어 프로덕션과 개발 환경에서 일관되게 동작합니다.

🤖 Prompt for AI Agents
In src/main/resources/logback-spring.xml around line 5 the LOG_FILE_PATH points
to /var/log which is root-owned and causes write failures; change the property
to an application-specific subdirectory (e.g., /var/log/festabook) and update
deployment scripts: add a new infra/setup-logs.sh (or inline commands) to create
the directory, set ownership to the app user and set permissions (mkdir -p,
chown ubuntu:ubuntu, chmod 755), invoke that script from appspec.yml
BeforeInstall with runas: root, and ensure infra/start.sh and infra/clean.sh
either call or mirror the same creation/permission logic (use sudo when
executing commands as a non-root user).

import com.daedan.festabook.festival.infrastructure.FestivalJpaRepository;
import com.daedan.festabook.global.security.JwtTestHelper;
import com.daedan.festabook.global.security.role.RoleType;
import com.daedan.festabook.support.AcceptanceTestSupport;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "AcceptanceTestSupport.java" -type f

Repository: festabook/backend

Length of output: 132


🏁 Script executed:

find . -name "ImageStoreControllerTest.java" -type f

Repository: festabook/backend

Length of output: 146


🏁 Script executed:

cat -n ./src/test/java/com/daedan/festabook/support/AcceptanceTestSupport.java

Repository: festabook/backend

Length of output: 1630


🏁 Script executed:

wc -l ./src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java

Repository: festabook/backend

Length of output: 149


🏁 Script executed:

cat -n ./src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java

Repository: festabook/backend

Length of output: 2945


테스트 인프라 통합이 효과적으로 적용되었습니다.

AcceptanceTestSupport를 상속함으로써 테스트 설정 보일러플레이트를 제거한 점이 좋습니다. 확인해보니 부모 클래스에서 @SpringBootTest, @LocalServerPort, RestAssured.port 초기화가 모두 처리되고 있어, 테스트 클래스는 테스트 로직에만 집중할 수 있게 되었습니다.

이런 방식의 공통 기반 클래스 사용은 다음과 같은 장점이 있습니다:

  • 중복 제거: 동일한 설정 코드를 매번 작성하지 않아도 됩니다
  • 유지보수성 향상: 향후 테스트 인프라 변경 시(예: WebEnvironment 변경, 새로운 Mock 추가) AcceptanceTestSupport만 수정하면 모든 하위 테스트에 반영됩니다
  • 일관성 확보: 프로젝트 전체의 통합 테스트가 동일한 환경에서 실행됩니다

다만 이렇게 공통 기반 클래스에 의존할 때 주의할 점이 있습니다. 부모 클래스의 설정이 증가할수록 각 테스트의 독립성과 명시성이 낮아질 수 있으므로, 기본 설정과 선택적 설정을 구분하거나 필요시 문서화하는 것이 좋습니다. 현재 구조에서는 AcceptanceTestSupport가 제공하는 Mock들(Clock, FestivalNotificationManager, ShuffleManager)이 모든 테스트에서 필요한지 검토해두면, 나중에 더 세밀한 테스트 전략이 필요할 때 수정하기 수월할 것입니다.

🤖 Prompt for AI Agents
In
src/test/java/com/daedan/festabook/storage/controller/ImageStoreControllerTest.java
around line 11, the review notes that inheriting AcceptanceTestSupport
centralizes test infra (annotations, port setup, RestAssured) but that optional
mocks (Clock, FestivalNotificationManager, ShuffleManager) provided by the base
may be unnecessary for all tests; inspect which of those mocks
ImageStoreControllerTest actually relies on and then either remove unused mocks
from the shared base or make them opt-in (e.g., provide protected
factory/boolean hooks or separate lightweight base classes for tests that don't
need the mocks), and add a short comment or README entry in
AcceptanceTestSupport listing the default mocks and how to opt out so future
tests remain explicit and independent.

@coderabbitai coderabbitai bot mentioned this pull request Jan 7, 2026
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants