Skip to content

Conversation

@zjhj0814
Copy link
Collaborator

@zjhj0814 zjhj0814 commented Aug 18, 2025

✨ 작업 개요

  • Sentry 모니터링 시스템 구축
  • 프론트엔드 도메인 변경으로 인한 CORS 설정
  • application.yml 비밀값 관리를 spring profile + .env 방식으로 전환 (env 파일은 노션에 있습니다!)

✅ 작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요.

📌 참고 사항(선택)

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요.

🔗 관련 이슈

CCI-80

Summary by CodeRabbit

  • New Features

    • Enabled error monitoring and tracing.
    • Introduced environment-based configuration profiles (prod, JWT, OAuth2, AWS, domain).
    • Added OAuth2 settings and JWT secret configuration.
    • Added AWS/S3 configuration support.
    • Allowed production domain access via CORS (https://www.injob.store).
  • Chores

    • Streamlined deployment: server-managed .env, load env from file, and pull latest container image before run.
    • Standardized container timezone to Asia/Seoul.
    • Updated ignore rules to exclude .env and include relevant config files.

@zjhj0814 zjhj0814 self-assigned this Aug 18, 2025
@zjhj0814 zjhj0814 added the 📦 chore build task label Aug 18, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

Walkthrough

Updates CI/CD to manage environment via a remote .env file and docker --env-file, adjusts Dockerfile ordering/timezone, integrates Sentry and dotenv in Gradle, modifies CORS origins, replaces a Lombok constructor with an explicit one, updates .gitignore, and adds multiple Spring Boot profile-based YAML configs.

Changes

Cohort / File(s) Summary of Changes
CI/CD workflow
.github/workflows/github-actions.yml
Removed dev-only application.yml generation. In prod deploy: create/write /home/ubuntu/interview-be/.env from secrets, chmod 600; added docker pull; run container with --env-file; reordered steps.
Containerization
Dockerfile
Moved tzdata/timezone setup to top; positioned ENTRYPOINT at end; retained ARG/COPY/EXPOSE.
Build and observability
build.gradle
Added Sentry Gradle plugin; added Sentry Spring Boot starter and spring-dotenv deps; added sentry { includeSourceContext, org, projectName, authToken } block.
Version control ignores
.gitignore
Ignore .env and keystore.p12; stop ignoring application.yml and application-test.yml; minor whitespace tweak.
Security/CORS
src/main/java/.../config/auth/SecurityConfig.java
Replaced allowed origin https://cloud-computing-fe-two.vercel.app with https://www.injob.store; other CORS/security settings unchanged.
Exception handling
src/main/java/.../apiPayload/exception/GeneralException.java
Removed Lombok @AllArgsConstructor; added explicit constructor formatting message from BaseErrorCode; imports adjusted.
Application configuration (profiles)
src/main/resources/application.yml, src/main/resources/application-*.yml
Added profile includes (prod, jwt, oauth2, aws, domain, sentry) and server.forward-headers-strategy. New profile files: aws (auth/s3 from env), domain (backend/ai URLs), jwt (secret), oauth2 (Google + redirects via env), prod (MySQL/Hikari/Quartz/logging; note typos in max-liefetime and connection-tiemout), sentry (dsn/debug/pii/etc.).

Sequence Diagram(s)

sequenceDiagram
  participant Dev as GitHub Actions
  participant VM as Prod Server
  participant Docker as Docker Engine
  Dev->>VM: SSH: mkdir -p /home/ubuntu/interview-be
  Dev->>VM: Write /home/ubuntu/interview-be/.env (from secrets), chmod 600
  Dev->>Docker: docker pull <image>
  Dev->>Docker: docker stop rm <container> (if running)
  Dev->>Docker: docker run --env-file /home/ubuntu/interview-be/.env <image>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • CHORE: Release v1.4 #57 — Also modifies GitHub Actions workflow and deployment steps, overlapping with docker pull/run and config generation.

Suggested reviewers

  • jlhyunii

Poem

I tucked new secrets in a neat .env burrow,
Pulled a fresh image as stars blinked in furrow,
Sentry watches softly, ears pricked for a clue,
CORS gates adjusted, let the right winds through,
With jars and ymls aligned—hip, hop, deploy anew! 🐇🚀

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/CCI-80

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@zjhj0814 zjhj0814 changed the title [CCI-80] Sentry 모니터링 시스템 구축 & 환경변수 [CCI-80] Sentry 모니터링 시스템 구축 & 환경변수 비밀값 방식 적용 & 프론트엔드 CORS 설정 Aug 18, 2025
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: 5

🔭 Outside diff range comments (2)
.github/workflows/github-actions.yml (2)

55-59: Fail fast on remote errors: enable script_stop

If any remote command fails, the step should stop immediately rather than continuing to subsequent commands that may assume prior success.

Add script_stop: true:

         with:
           host: ${{ secrets.HOST_PROD }} # EC2 퍼블릭 IPv4 DNS
           username: ubuntu
           key: ${{ secrets.PRIVATE_KEY }}
+          script_stop: true
           envs: GITHUB_SHA

42-47: Pin image by commit SHA to avoid “latest” drift and race conditions

Tagging and deploying by ${{ github.sha }} ensures the same image that was built is the one deployed. You already pass GITHUB_SHA to the SSH step.

       - name: Docker build & push to prod
         if: contains(github.ref, 'dev')
         run: |
           docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
-          docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/interview-be-service .
-          docker push ${{ secrets.DOCKER_USERNAME }}/interview-be-service
+          docker build -f Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/interview-be-service:${{ github.sha }} .
+          docker push ${{ secrets.DOCKER_USERNAME }}/interview-be-service:${{ github.sha }}
-            sudo docker pull ${{ secrets.DOCKER_USERNAME }}/interview-be-service # 최신 이미지 pull
-            sudo docker run -d --name interview-be-app -p 8080:8080 --env-file /home/ubuntu/interview-be/.env ${{ secrets.DOCKER_USERNAME }}/interview-be-service # 환경변수 주입하여 컨테이너 실행
+            sudo docker pull ${{ secrets.DOCKER_USERNAME }}/interview-be-service:${GITHUB_SHA} # 해당 커밋 이미지 pull
+            sudo docker run -d --name interview-be-app -p 8080:8080 --restart unless-stopped --env-file /home/ubuntu/interview-be/.env ${{ secrets.DOCKER_USERNAME }}/interview-be-service:${GITHUB_SHA} # 환경변수 주입하여 컨테이너 실행

Also applies to: 72-74

🧹 Nitpick comments (15)
src/main/java/cloudcomputinginha/demo/apiPayload/exception/GeneralException.java (1)

11-14: Add null-safety in constructor to avoid potential NPEs when code or reason is null

Guard against null code and code.getReason() while building the message. Keeps the super(...) call first, as required by Java.

Apply this diff:

 public GeneralException(BaseErrorCode code) {
-        super("[" + code.getReason().getCode() + "] " + code.getReason().getMessage());  // 에러 메시지 전달
-        this.code = code;
+        super("[" 
+              + java.util.Objects.requireNonNull(code, "BaseErrorCode must not be null")
+                  .getReason()
+                  .getCode()
+              + "] "
+              + java.util.Objects.requireNonNull(code.getReason(), "ErrorReason must not be null")
+                  .getMessage());  // 에러 메시지 전달
+        this.code = code;
 }

Optional follow-ups (no need to block this PR):

  • Consider making the code field final to enforce immutability.
  • Consider adding an overload to capture a cause:
public GeneralException(BaseErrorCode code, Throwable cause) {
    super("[" + code.getReason().getCode() + "] " + code.getReason().getMessage(), cause);
    this.code = code;
}
.gitignore (1)

39-45: Ignore common .env variants too.

Add patterns for per-environment and editor-created env files (e.g., .env.local, .env.prod, .envrc) to prevent accidental commits.

- .env
+ .env
+ .env.*
+ .envrc

Confirm that your CI/CD only relies on --env-file with a known filename to avoid unexpected matches.

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

12-12: Add newline at end of file.

Fixes the YAML linter warning.

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

1-2: Add EOF newline to application-jwt.yml

The JWT property binding is already correct—JwtProvider.java uses @Value("${jwt.secret}") to load this value.

• src/main/resources/application-jwt.yml: please add a newline at the end of the file to satisfy linters.

src/main/java/cloudcomputinginha/demo/config/auth/SecurityConfig.java (2)

35-42: Include apex domain in CORS allowlist (avoid FE CORS issues).

Only www is allowed. If the FE serves from https://injob.store (apex), requests will be blocked by CORS.

Apply this small addition:

                                 configuration.setAllowedOrigins(List.of(
                                         "http://localhost:8080",
                                         "http://localhost:3000",
                                         "http://127.0.0.1:5500",
                                         "https://www.injob.store",
+                                        "https://injob.store",
                                         domainProperties.getAi(),
                                         domainProperties.getBackend()
                                 ));

If the FE might use multiple subdomains, consider using allowedOriginPatterns with explicit patterns instead.


35-42: Optional: avoid hardcoding and null-safety for dynamic origins.

  • Hardcoding injob.store makes future domain changes harder; consider moving FE domain(s) into DomainProperties/application-domain.yml alongside backend/AI.
  • If any of the dynamic origins are null, setAllowedOrigins will include null entries. Filter them out for safety.

One way (requires two imports):

-                                configuration.setAllowedOrigins(List.of(
-                                        "http://localhost:8080",
-                                        "http://localhost:3000",
-                                        "http://127.0.0.1:5500",
-                                        "https://www.injob.store",
-                                        domainProperties.getAi(),
-                                        domainProperties.getBackend()
-                                ));
+                                configuration.setAllowedOrigins(
+                                        java.util.stream.Stream.of(
+                                                "http://localhost:8080",
+                                                "http://localhost:3000",
+                                                "http://127.0.0.1:5500",
+                                                "https://www.injob.store",
+                                                "https://injob.store",
+                                                domainProperties.getAi(),
+                                                domainProperties.getBackend()
+                                        ).filter(java.util.Objects::nonNull).toList()
+                                );

Additional imports needed outside this hunk:

import java.util.Objects;
import java.util.stream.Stream;
Dockerfile (1)

2-2: Use --no-cache when installing tzdata and set TZ file (smaller, reproducible image).

Avoids persisting apk indexes and makes timezone explicit for tooling that reads /etc/timezone.

-RUN apk add tzdata && ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
+RUN apk add --no-cache tzdata \
+    && ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime \
+    && echo "Asia/Seoul" > /etc/timezone
build.gradle (1)

84-89: Scope dotenv to development only (avoid shipping it to prod images unless required).

If Docker/CI provide envs via --env-file/secrets, you don’t need dotenv on prod. Keep it for local dev.

-    // 환경변수(.env) 파일 관리
-    implementation 'me.paulschwarz:spring-dotenv:4.0.0'
+    // 환경변수(.env) 파일 관리 (개발 환경 전용)
+    developmentOnly 'me.paulschwarz:spring-dotenv:4.0.0'

If you don’t have it yet, declare the configuration (Boot adds it by default, but if missing add):

configurations {
    developmentOnly
}
.github/workflows/github-actions.yml (3)

63-66: Reduce heredoc delimiter collision risk

If ${{ secrets.ENV_FILE }} ever contains a line with just EOF, the heredoc will terminate prematurely. Use a less likely delimiter.

-            cat > .env <<'EOF' # 멀티라인 환경변수 입력으로 사용
+            cat > .env <<'ENVEOF' # 멀티라인 환경변수 입력으로 사용
             ${{ secrets.ENV_FILE }}
-            EOF
+            ENVEOF

60-67: Double-check secret exposure in logs; consider scp alternative

Multiline secrets are generally masked, but heredoc scripts can be echoed in logs depending on action behavior. If you want to minimize any risk, consider using appleboy/scp-action to transfer a generated .env file instead of embedding the secret into the script, or store a base64-encoded secret and decode on the remote.

I can provide an scp-based variant of this step if you want to switch. Let me know.

Also applies to: 72-74


67-67: Fix YAML trailing spaces flagged by linter

Trailing spaces cause YAML lint errors on Lines 67 and 71.

-            
+
@@
-            
+

Also applies to: 71-71

src/main/resources/application-prod.yml (4)

36-40: Metrics config key likely ineffective

Spring Boot expects flat keys like management.metrics.enable.<meter> (e.g., management.metrics.enable.jvm=false). The nested enable: processor: enabled: false shape won’t bind, and processor isn’t a standard meter id.

If your goal is to disable process-related meters, use:

  • Spring Boot 3.x example:
    • management.metrics.enable.jvm: false
    • management.metrics.enable.process: false
    • management.metrics.enable.system: false

Otherwise, remove this block to avoid confusion. I can adjust to the exact meters you want disabled if you confirm your intent.


23-25: Avoid hbm2ddl.auto=update in production

Automatic schema updates in prod can lead to unintended changes/data loss. Prefer managed migrations (Flyway/Liquibase) and set none (Boot 3) or remove this property.

-        hbm2ddl:
-          auto: update
+        # hbm2ddl:
+        #   auto: none  # prefer managed migrations in production

27-29: Quartz schema initialization “always” in prod

initialize-schema: always attempts schema creation on every startup. After the schema is created, consider never to reduce startup work and log noise.

-    jdbc:
-      initialize-schema: always
+    jdbc:
+      initialize-schema: never

3-6: Minor: review JDBC URL flags

allowPublicKeyRetrieval=true is typically used when not using SSL. With useSSL=true, it’s usually unnecessary. Not harmful, but you can simplify if not needed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43fd783 and 55d9075.

📒 Files selected for processing (13)
  • .github/workflows/github-actions.yml (1 hunks)
  • .gitignore (1 hunks)
  • Dockerfile (1 hunks)
  • build.gradle (2 hunks)
  • src/main/java/cloudcomputinginha/demo/apiPayload/exception/GeneralException.java (1 hunks)
  • src/main/java/cloudcomputinginha/demo/config/auth/SecurityConfig.java (1 hunks)
  • src/main/resources/application-aws.yml (1 hunks)
  • src/main/resources/application-domain.yml (1 hunks)
  • src/main/resources/application-jwt.yml (1 hunks)
  • src/main/resources/application-oauth2.yml (1 hunks)
  • src/main/resources/application-prod.yml (1 hunks)
  • src/main/resources/application-sentry.yml (1 hunks)
  • src/main/resources/application.yml (1 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
src/main/resources/application-oauth2.yml

[error] 14-14: no new line character at the end of file

(new-line-at-end-of-file)

src/main/resources/application-domain.yml

[error] 3-3: no new line character at the end of file

(new-line-at-end-of-file)

src/main/resources/application-sentry.yml

[error] 7-7: no new line character at the end of file

(new-line-at-end-of-file)

src/main/resources/application.yml

[error] 12-12: no new line character at the end of file

(new-line-at-end-of-file)

.github/workflows/github-actions.yml

[error] 67-67: trailing spaces

(trailing-spaces)


[error] 71-71: trailing spaces

(trailing-spaces)

src/main/resources/application-aws.yml

[error] 6-6: no new line character at the end of file

(new-line-at-end-of-file)

src/main/resources/application-jwt.yml

[error] 2-2: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (8)
src/main/java/cloudcomputinginha/demo/apiPayload/exception/GeneralException.java (1)

20-22: Whitespace-only change — OK

No functional impact. Method remains consistent with existing API.

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

1-7: Sane defaults; DSN/server name via env look good.

Using env placeholders for DSN and server-name aligns with the new .env-driven deployment. No issues there.

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

11-12: Forward headers strategy LGTM.

FRAMEWORK is the right choice behind reverse proxies in Spring Boot 3+.


1-10: Configure ‘prod’ as a profile group instead of including it by default

Including prod in spring.profiles.include will merge production settings into every run (local, dev, tests), risking accidental connections to live databases or services. Instead, define a profile group so that activating prod brings in the other profiles, but they don’t load on their own:

src/main/resources/application.yml

-spring:
-  profiles:
-    include:
-      - prod
-      - jwt
-      - oauth2
-      - aws
-      - domain
-      - sentry
+spring:
+  profiles:
+    group:
+      prod:
+        - jwt
+        - oauth2
+        - aws
+        - domain
+        - sentry

• Ensure you only set SPRING_PROFILES_ACTIVE=prod (or -Dspring.profiles.active=prod) in your production environment.
• Verify there are no other YAML/property files (e.g. application-dev.yml) or CI/local scripts that enable prod by default.
• Remove any hard-coded prod activations from test or launcher scripts.

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

1-3: Minor YAML readability and CORS origin sanity check

  • Moved inline comment to its own line for clarity:
- domain: #도메인 정보 URL 생성, CORS 설정, FeignClient 등에 사용할 예정
+ # 도메인 정보 URL 생성, CORS 설정, FeignClient 등에 사용할 예정
+ domain:
    backend: ${BACKEND_URL}
    ai: ${AI_URL}
  • Ensure BACKEND_URL and AI_URL are exact origins (scheme + host + optional port), with no trailing slash, to satisfy Spring’s CORS origin matching.

  • Add a newline at EOF.

Note: The DomainProperties class (src/main/java/cloudcomputinginha/demo/config/properties/DomainProperties.java) is annotated with
@component and @ConfigurationProperties(prefix = "domain"), so it’s already registered and bound—no extra @EnableConfigurationProperties needed.

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

1-6: cloud.aws properties are correctly bound and in use

  • CloudProperties is annotated with @ConfigurationProperties(prefix = "cloud.aws") and picks up auth, s3.bucket, and s3.region.
  • S3Config wires cloudProperties.getS3().getRegion() into both S3Client and S3Presigner.
  • ResumeS3Service and S3UrlValidator both call cloudProperties.getS3().getBucket() (and use it in PutObjectRequest.builder().bucket(...) and URL generation).

Nitpicks:

  • The auth property is currently declared but not consumed—either implement its use for credential selection or remove it.
  • Add a trailing newline to src/main/resources/application-aws.yml to satisfy linters.
src/main/resources/application-oauth2.yml (2)

9-10: Guard against double slashes in callback URL.

If BACKEND_URL ends with “/” or GOOGLE_CALLBACK_ENDPOINT begins with “/”, you’ll end up with a “//” in the URL. Prefer one source of the slash.

If your env defines GOOGLE_CALLBACK_ENDPOINT without a leading slash (recommended), apply:

-      url: ${BACKEND_URL}/${GOOGLE_CALLBACK_ENDPOINT}
+      url: ${BACKEND_URL}${GOOGLE_CALLBACK_ENDPOINT}

Alternatively, ensure GOOGLE_CALLBACK_ENDPOINT never starts with “/”.


12-14: Ensure allowed-redirect-uris format matches the binder type.

If the bound property is a List<String>, Spring Boot will split comma-separated values. Confirm ALLOWED_REDIRECT_URIS follows “uri1,uri2,uri3” with no spaces, and that code expects a list.

Want me to scan for the properties class that reads these (e.g., frontend.allowed-redirect-uris) to confirm the expected type and delimiter?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📦 chore build task

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants