diff --git a/.github/actions/deploy-module/action.yml b/.github/actions/deploy-module/action.yml new file mode 100644 index 00000000..d8b0a924 --- /dev/null +++ b/.github/actions/deploy-module/action.yml @@ -0,0 +1,176 @@ +name: 'Deploy Module' +description: 'Build, push and deploy a module to server (dev/prod)' + +inputs: + environment: + description: 'Environment (dev or prod)' + required: true + module: + description: 'Module name (apis, admin, batch)' + required: true + port: + description: 'Server port (for logging only - actual port defined in docker-compose.yml)' + required: false + default: 'N/A' + dockerhub-username: + description: 'Docker Hub username' + required: true + dockerhub-token: + description: 'Docker Hub token' + required: true + secret-properties: + description: 'Secret properties (dev or prod)' + required: true + apple-auth-key: + description: 'Apple Auth Key' + required: true + host: + description: 'Server host' + required: true + username: + description: 'Server username' + required: true + ssh-key: + description: 'Server SSH key' + required: true + ssh-port: + description: 'Server SSH port' + required: true + discord-webhook-url: + description: 'Discord webhook URL' + required: true + image-prefix: + description: 'Docker image prefix' + required: true + image-tag-type: + description: 'Image tag type (development-latest or semver)' + required: true + deploy-script: + description: 'Deploy script name (deploy-dev.sh or deploy-prod.sh)' + required: true + default: 'deploy-dev.sh' + +runs: + using: 'composite' + steps: + - name: Inject application-secret.properties from Secrets + shell: bash + run: | + mkdir -p ./secret + echo "$SECRET_CONTENT" > ./secret/application-${{ inputs.environment }}-secret.properties + echo "$APPLE_KEY_CONTENT" > ./secret/AuthKey.p8 + chmod 600 ./secret/* + env: + SECRET_CONTENT: ${{ inputs.secret-properties }} + APPLE_KEY_CONTENT: ${{ inputs.apple-auth-key }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ inputs.dockerhub-username }} + password: ${{ inputs.dockerhub-token }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: docker.io/${{ inputs.image-prefix }}-${{ inputs.module }} + tags: ${{ inputs.image-tag-type }} + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha,scope=${{ inputs.module }} + cache-to: type=gha,mode=max,scope=${{ inputs.module }} + build-args: | + MODULE=${{ inputs.module }} + + - name: Deploy to Server + uses: appleboy/ssh-action@v1.2.2 + with: + host: ${{ inputs.host }} + username: ${{ inputs.username }} + key: ${{ inputs.ssh-key }} + port: ${{ inputs.ssh-port }} + script: | + export DOCKERHUB_USERNAME="${{ inputs.dockerhub-username }}" + export DOCKERHUB_TOKEN="${{ inputs.dockerhub-token }}" + export MODULE="${{ inputs.module }}" + export SPRING_PROFILE="${{ inputs.environment }}" + export IMAGE_TAG="$(echo "${{ steps.meta.outputs.tags }}" | head -n1)" + cd ~/deploy + chmod +x ./${{ inputs.deploy-script }} + ./${{ inputs.deploy-script }} + + - name: Send Discord notification on success (Development) + uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 + if: success() && inputs.environment == 'dev' + continue-on-error: true + shell: bash + with: + webhook-url: ${{ inputs.discord-webhook-url }} + embed-title: "โœ… [${{ github.repository }}] Development Deploy Succeeded - ${{ inputs.module }}" + embed-description: | + **Module**: `${{ inputs.module }}` + **Commit**: `${{ github.sha }}` + **Author**: `${{ github.actor }}` + **Message**: `${{ github.event.head_commit.message }}` + [View Committed Changes](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) + embed-color: 65280 + + - name: Send Discord notification on success (Production) + uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 + if: success() && inputs.environment == 'prod' + continue-on-error: true + shell: bash + with: + webhook-url: ${{ inputs.discord-webhook-url }} + content: "๐Ÿš€ **Production Deploy Succeeded!**" + embed-title: "โœ… [${{ github.repository }}] Production Deploy Succeeded - ${{ inputs.module }}" + embed-description: | + **Module**: `${{ inputs.module }}` + **Deployed by**: `${{ github.actor }}` + The new version has been successfully deployed to production. + [View Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + embed-color: 65280 + + - name: Send Discord notification on failure (Development) + uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 + if: failure() && inputs.environment == 'dev' + continue-on-error: true + shell: bash + with: + webhook-url: ${{ inputs.discord-webhook-url }} + embed-title: "โŒ [${{ github.repository }}] Development Deploy Failed - ${{ inputs.module }}" + embed-description: | + **Module**: `${{ inputs.module }}` + **Commit**: `${{ github.sha }}` + **Author**: `${{ github.actor }}` + An error occurred during the workflow execution. + [View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + embed-color: 16711680 + + - name: Send Discord notification on failure (Production) + uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 + if: failure() && inputs.environment == 'prod' + continue-on-error: true + shell: bash + with: + webhook-url: ${{ inputs.discord-webhook-url }} + content: "๐Ÿšจ **Production Deploy Failed!**" + embed-title: "โŒ [${{ github.repository }}] Production Deploy Failed - ${{ inputs.module }}" + embed-description: | + **Module**: `${{ inputs.module }}` + **Deployed by**: `${{ github.actor }}` + An error occurred during the production deployment workflow. + [View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + embed-color: 16711680 diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index eeceb7a5..5975ab1b 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -58,4 +58,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew fullCheck --parallel --build-cache --info --stacktrace + run: | + # fullCheck: ๋ชจ๋“  ๋ชจ๋“ˆ (apis, admin, batch, gateway ๋“ฑ)์˜ ๋นŒ๋“œ, ํ…Œ์ŠคํŠธ, ์ •์ ๋ถ„์„ ์ˆ˜ํ–‰ + # --parallel: ๋ชจ๋“ˆ๋ณ„ ๋ณ‘๋ ฌ ๋นŒ๋“œ๋กœ ์‹œ๊ฐ„ ๋‹จ์ถ• + # --build-cache: Gradle ๋นŒ๋“œ ์บ์‹œ ์‚ฌ์šฉ + ./gradlew fullCheck --parallel --build-cache --info --stacktrace diff --git a/.github/workflows/dev-ci-cd.yml b/.github/workflows/dev-ci-cd.yml index 68e85ef2..e6956101 100644 --- a/.github/workflows/dev-ci-cd.yml +++ b/.github/workflows/dev-ci-cd.yml @@ -11,96 +11,89 @@ concurrency: env: REGISTRY: docker.io - IMAGE_NAME: ninecraft0523/ninecraft-server - MODULE: apis + IMAGE_PREFIX: ninecraft0523/ninecraft jobs: - build-push-and-deploy: + detect-changes: runs-on: ubuntu-24.04 - timeout-minutes: 20 - environment: development - + outputs: + apis: ${{ steps.filter.outputs.apis }} + # admin: ${{ steps.filter.outputs.admin }} # TODO: Uncomment when admin module is ready + batch: ${{ steps.filter.outputs.batch }} + any: ${{ steps.filter.outputs.any }} steps: - name: Checkout code uses: actions/checkout@v4 - - name: Inject application-secret.properties from Secrets - run: | - mkdir ./secret - echo "${{ secrets.DEV_SECRET_PROPERTIES }}" > ./secret/application-dev-secret.properties - echo "${{ secrets.APPLE_AUTH_KEY }}" > ./secret/AuthKey.p8 - chmod 600 ./secret/* - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 + - name: Check changed files + uses: dorny/paths-filter@v3 + id: filter with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + filters: | + apis: + - 'apis/**' + - 'domain/**' + - 'infra/**' + - 'global-utils/**' + - 'observability/**' + # admin: # TODO: Uncomment when admin module is ready + # - 'admin/**' + # - 'domain/**' + # - 'infra/**' + # - 'global-utils/**' + # - 'observability/**' + batch: + - 'batch/**' + - 'domain/**' + - 'infra/**' + - 'global-utils/**' + - 'observability/**' + any: + - 'apis/**' + # - 'admin/**' # TODO: Uncomment when admin module is ready + - 'batch/**' + - 'domain/**' + - 'infra/**' + - 'global-utils/**' + - 'observability/**' - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=raw,value=development-latest + build-push-and-deploy: + needs: detect-changes + if: needs.detect-changes.outputs.any == 'true' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + environment: development + strategy: + fail-fast: false + matrix: + include: + - module: apis + changed: ${{ needs.detect-changes.outputs.apis }} + # - module: admin # TODO: Uncomment when admin module is ready + # changed: ${{ needs.detect-changes.outputs.admin }} + - module: batch + changed: ${{ needs.detect-changes.outputs.batch }} - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - MODULE=${{ env.MODULE }} + steps: + - name: Checkout code + if: matrix.changed == 'true' + uses: actions/checkout@v4 - - name: Deploy to Development Server - uses: appleboy/ssh-action@v1.2.2 + - name: Deploy module + if: matrix.changed == 'true' + uses: ./.github/actions/deploy-module with: + environment: dev + module: ${{ matrix.module }} + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + secret-properties: ${{ secrets.DEV_SECRET_PROPERTIES }} + apple-auth-key: ${{ secrets.APPLE_AUTH_KEY }} host: ${{ secrets.DEV_HOST }} username: ${{ secrets.DEV_USERNAME }} - key: ${{ secrets.DEV_SSH_KEY }} - port: ${{ secrets.DEV_PORT }} - script: | - export DOCKERHUB_USERNAME="${{ secrets.DOCKERHUB_USERNAME }}" - export DOCKERHUB_TOKEN="${{ secrets.DOCKERHUB_TOKEN }}" - export IMAGE_TAG="$(echo "${{ steps.meta.outputs.tags }}" | head -n1)" - cd ~/deploy - chmod +x ./deploy.sh - ./deploy.sh - - - name: Send Discord notification on success - uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 - if: success() - continue-on-error: true - with: - webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }} - embed-title: "โœ… [${{ github.repository }}] Development Deploy Succeeded" - embed-description: | - **Commit**: `${{ github.sha }}` - **Author**: `${{ github.actor }}` - **Message**: `${{ github.event.head_commit.message }}` - [View Committed Changes](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) - embed-color: 65280 - - - name: Send Discord notification on failure - uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 - if: failure() - continue-on-error: true - with: - webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }} - embed-title: "โŒ [${{ github.repository }}] Development Deploy Failed" - embed-description: | - **Commit**: `${{ github.sha }}` - **Author**: `${{ github.actor }}` - An error occurred during the workflow execution. - [View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - embed-color: 16711680 + ssh-key: ${{ secrets.DEV_SSH_KEY }} + ssh-port: ${{ secrets.DEV_PORT }} + discord-webhook-url: ${{ secrets.DEV_DEPLOY_DISCORD_WEBHOOK_URL }} + image-prefix: ${{ env.IMAGE_PREFIX }} + image-tag-type: type=raw,value=development-latest + deploy-script: deploy-dev.sh diff --git a/.github/workflows/prod-ci-cd.yml b/.github/workflows/prod-ci-cd.yml index 9c89683e..a98c238b 100644 --- a/.github/workflows/prod-ci-cd.yml +++ b/.github/workflows/prod-ci-cd.yml @@ -11,97 +11,41 @@ concurrency: env: REGISTRY: docker.io - IMAGE_NAME: ninecraft0523/ninecraft-server - MODULE: apis + IMAGE_PREFIX: ninecraft0523/ninecraft jobs: build-push-and-deploy: runs-on: ubuntu-24.04 timeout-minutes: 25 environment: production + strategy: + fail-fast: false + matrix: + include: + - module: apis + # - module: admin # TODO: Uncomment when admin module is ready + - module: batch steps: - name: Checkout code uses: actions/checkout@v4 - - name: Inject application-secret.properties from Secrets - run: | - mkdir ./secret - echo "${{ secrets.PROD_SECRET_PROPERTIES }}" > ./secret/application-prod-secret.properties - echo "${{ secrets.APPLE_AUTH_KEY }}" > ./secret/AuthKey.p8 - chmod 600 ./secret/* - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=semver,pattern={{version}} - type=raw,value=production-latest - - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - MODULE=${{ env.MODULE }} - - - name: Deploy to Production Server - uses: appleboy/ssh-action@v1.2.2 + - name: Deploy module + uses: ./.github/actions/deploy-module with: + environment: prod + module: ${{ matrix.module }} + dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + secret-properties: ${{ secrets.PROD_SECRET_PROPERTIES }} + apple-auth-key: ${{ secrets.APPLE_AUTH_KEY }} host: ${{ secrets.PROD_HOST }} username: ${{ secrets.PROD_USERNAME }} - key: ${{ secrets.PROD_SSH_KEY }} - port: ${{ secrets.PROD_PORT }} - script: | - export DOCKERHUB_USERNAME="${{ secrets.DOCKERHUB_USERNAME }}" - export DOCKERHUB_TOKEN="${{ secrets.DOCKERHUB_TOKEN }}" - export IMAGE_TAG="$(echo "${{ steps.meta.outputs.tags }}" | head -n1)" - export VERSION_TAG="${{ steps.meta.outputs.version }}" - export RELEASE_VERSION="${{ github.event.release.tag_name }}" - cd ~/deploy - chmod +x ./deploy.sh - ./deploy.sh - - - name: Send Discord notification on success - if: success() - uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 - with: - webhook-url: ${{ secrets.PROD_DEPLOY_DISCORD_WEBHOOK_URL }} - content: "๐Ÿš€ **Production Deploy Succeeded!**" - embed-title: "โœ… [${{ github.repository }}] Release **${{ github.event.release.tag_name }}**" - embed-description: | - **Released by**: `${{ github.actor }}` - The new version has been successfully deployed to production. - [View Release Notes](https://github.com/${{ github.repository }}/releases/tag/${{ github.event.release.tag_name }}) - embed-color: 65280 # Green - - - name: Send Discord notification on failure - if: failure() - uses: tsickert/discord-webhook@b217a69502f52803de774ded2b1ab7c282e99645 - with: - webhook-url: ${{ secrets.PROD_DEPLOY_DISCORD_WEBHOOK_URL }} - content: "๐Ÿšจ **Production Deploy Failed!**" - embed-title: "โŒ [${{ github.repository }}] Release **${{ github.event.release.tag_name }}**" - embed-description: | - **Released by**: `${{ github.actor }}` - An error occurred during the production deployment workflow. - [View Failed Workflow](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - embed-color: 16711680 # Red + ssh-key: ${{ secrets.PROD_SSH_KEY }} + ssh-port: ${{ secrets.PROD_PORT }} + discord-webhook-url: ${{ secrets.PROD_DEPLOY_DISCORD_WEBHOOK_URL }} + image-prefix: ${{ env.IMAGE_PREFIX }} + image-tag-type: | + type=semver,pattern={{version}} + type=raw,value=production-latest + deploy-script: deploy-prod.sh diff --git a/Dockerfile b/Dockerfile index b2f69cf6..5609903d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ # Build stage FROM gradle:8.7-jdk21 AS build -ARG MODULE=apis +# MODULE: ๋นŒ๋“œํ•  ๋ชจ๋“ˆ (apis, admin, batch) - ๋นŒ๋“œ ์‹œ --build-arg MODULE=xxx ํ•„์ˆ˜ +ARG MODULE WORKDIR /app # ์˜์กด์„ฑ ์บ์‹ฑ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๋‹จ๊ณ„๋ณ„ ๋ณต์‚ฌ @@ -24,7 +25,8 @@ RUN ./gradlew :${MODULE}:bootJar --parallel --no-daemon # Run stage FROM openjdk:21-slim -ARG MODULE=apis +# MODULE: ๋นŒ๋“œํ•  ๋ชจ๋“ˆ (apis, admin, batch) - ๋นŒ๋“œ ์‹œ --build-arg MODULE=xxx ํ•„์ˆ˜ +ARG MODULE WORKDIR /app # ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋กœ ์ตœ์ข… ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์†Œํ™” @@ -39,4 +41,6 @@ ENV TZ=Asia/Seoul # JVM ์‹คํ–‰ ์„ค์ • # - Xms512m: ์ดˆ๊ธฐ ํž™ ๋ฉ”๋ชจ๋ฆฌ 512MB # - Xmx1g: ์ตœ๋Œ€ ํž™ ๋ฉ”๋ชจ๋ฆฌ 1GB -ENTRYPOINT ["java", "-Xms512m", "-Xmx1g", "-Duser.timezone=Asia/Seoul", "-jar", "app.jar"] +# - server.port: Spring Boot ์„œ๋ฒ„ ํฌํŠธ (์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰ ์‹œ -e SERVER_PORT=xxxx๋กœ ์ฃผ์ž…, ๊ธฐ๋ณธ๊ฐ’: 8080) +# - exec: shell ํ”„๋กœ์„ธ์Šค๋ฅผ java ํ”„๋กœ์„ธ์Šค๋กœ ๋Œ€์ฒดํ•˜์—ฌ graceful shutdown ์ง€์› +ENTRYPOINT ["sh", "-c", "exec java -Xms512m -Xmx1g -Duser.timezone=Asia/Seoul -Dserver.port=${SERVER_PORT:-8080} -jar app.jar"] diff --git a/Dockerfile-dev b/Dockerfile-dev deleted file mode 100644 index b2f69cf6..00000000 --- a/Dockerfile-dev +++ /dev/null @@ -1,42 +0,0 @@ -# Build stage -FROM gradle:8.7-jdk21 AS build -ARG MODULE=apis -WORKDIR /app - -# ์˜์กด์„ฑ ์บ์‹ฑ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ๋‹จ๊ณ„๋ณ„ ๋ณต์‚ฌ -# 1. ์˜์กด์„ฑ ์บ์‹ฑ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด Gradle Wrapper ๋ฐ ์˜์กด์„ฑ ๊ด€๋ จ ํŒŒ์ผ๋งŒ ๋จผ์ € ๋ณต์‚ฌ -COPY build.gradle.kts settings.gradle.kts gradlew gradlew.bat ./ -COPY gradle/wrapper/ ./gradle/wrapper/ -COPY buildSrc/ ./buildSrc/ -COPY ${MODULE}/build.gradle.kts ./${MODULE}/ - -# 2. Gradle Wrapper ์‹คํ–‰ ๊ถŒํ•œ ๋ถ€์—ฌ -RUN chmod +x gradlew - -# 3. ์†Œ์Šค์ฝ”๋“œ ์—†์ด ์˜์กด์„ฑ๋งŒ ๋‹ค์šด๋กœ๋“œ -RUN ./gradlew :${MODULE}:dependencies --no-daemon - -# 4. ์†Œ์Šค์ฝ”๋“œ ์ „์ฒด ๋ณต์‚ฌ -COPY . . - -# 5. ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋นŒ๋“œ -RUN ./gradlew :${MODULE}:bootJar --parallel --no-daemon - -# Run stage -FROM openjdk:21-slim -ARG MODULE=apis -WORKDIR /app - -# ๋ฉ€ํ‹ฐ์Šคํ…Œ์ด์ง€ ๋นŒ๋“œ๋กœ ์ตœ์ข… ์ด๋ฏธ์ง€ ํฌ๊ธฐ ์ตœ์†Œํ™” -COPY --from=build /app/${MODULE}/build/libs/${MODULE}-*.jar app.jar - -# ๋Ÿฐํƒ€์ž„์— ํ•„์š”ํ•œ secret ํด๋” ๋ณต์‚ฌ -COPY --from=build /app/secret ./secret/ - -# TimeZone KST ์„ค์ • -ENV TZ=Asia/Seoul - -# JVM ์‹คํ–‰ ์„ค์ • -# - Xms512m: ์ดˆ๊ธฐ ํž™ ๋ฉ”๋ชจ๋ฆฌ 512MB -# - Xmx1g: ์ตœ๋Œ€ ํž™ ๋ฉ”๋ชจ๋ฆฌ 1GB -ENTRYPOINT ["java", "-Xms512m", "-Xmx1g", "-Duser.timezone=Asia/Seoul", "-jar", "app.jar"] diff --git a/admin/build.gradle.kts b/admin/build.gradle.kts index 9b84b1db..05319495 100644 --- a/admin/build.gradle.kts +++ b/admin/build.gradle.kts @@ -4,6 +4,7 @@ dependencies { implementation(project(Dependencies.Projects.INFRA)) implementation(project(Dependencies.Projects.DOMAIN)) implementation(project(Dependencies.Projects.GLOBAL_UTILS)) + implementation(project(Dependencies.Projects.OBSERVABILITY)) implementation(Dependencies.Spring.BOOT_STARTER_WEB) implementation(Dependencies.Spring.BOOT_STARTER_SECURITY) diff --git a/admin/src/main/kotlin/org/yapp/admin/AdminApplication.kt b/admin/src/main/kotlin/org/yapp/admin/AdminApplication.kt index 513706a8..6c029fc8 100644 --- a/admin/src/main/kotlin/org/yapp/admin/AdminApplication.kt +++ b/admin/src/main/kotlin/org/yapp/admin/AdminApplication.kt @@ -1,11 +1,16 @@ package org.yapp.admin import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan -@SpringBootApplication +@SpringBootApplication( + exclude = [JpaRepositoriesAutoConfiguration::class] +) +@ComponentScan(basePackages = ["org.yapp"]) class AdminApplication fun main(args: Array) { - runApplication(*args) + runApplication(*args) } diff --git a/admin/src/main/resources/application.yml b/admin/src/main/resources/application.yml index b0b7ec80..a9371496 100644 --- a/admin/src/main/resources/application.yml +++ b/admin/src/main/resources/application.yml @@ -9,17 +9,25 @@ spring: group: dev: - persistence + - crosscutting - jwt - redis - external + - observability prod: - persistence + - crosscutting - jwt - redis - external + - observability test: - persistence + - crosscutting - jwt + - redis + - external + - observability servlet: multipart: max-file-size: 10MB diff --git a/apis/build.gradle.kts b/apis/build.gradle.kts index 4d04c486..c6ab8ff1 100644 --- a/apis/build.gradle.kts +++ b/apis/build.gradle.kts @@ -5,6 +5,7 @@ dependencies { implementation(project(Dependencies.Projects.DOMAIN)) implementation(project(Dependencies.Projects.GLOBAL_UTILS)) implementation(project(Dependencies.Projects.GATEWAY)) + implementation(project(Dependencies.Projects.OBSERVABILITY)) implementation(Dependencies.Spring.BOOT_STARTER_WEB) implementation(Dependencies.Spring.BOOT_STARTER_DATA_JPA) diff --git a/apis/src/main/kotlin/org/yapp/apis/ApisApplication.kt b/apis/src/main/kotlin/org/yapp/apis/ApisApplication.kt index fb5c2683..a9a1d661 100644 --- a/apis/src/main/kotlin/org/yapp/apis/ApisApplication.kt +++ b/apis/src/main/kotlin/org/yapp/apis/ApisApplication.kt @@ -3,20 +3,12 @@ package org.yapp.apis import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan -/** - * Main application class for the apis module. - */ @SpringBootApplication( - scanBasePackages = [ - "org.yapp.apis", - "org.yapp.infra", - "org.yapp.domain", - "org.yapp.gateway", - "org.yapp.globalutils" - ], exclude = [JpaRepositoriesAutoConfiguration::class] ) +@ComponentScan(basePackages = ["org.yapp"]) class ApisApplication fun main(args: Array) { diff --git a/apis/src/main/resources/application.yml b/apis/src/main/resources/application.yml index 235eeb20..dcd73de6 100644 --- a/apis/src/main/resources/application.yml +++ b/apis/src/main/resources/application.yml @@ -11,23 +11,23 @@ spring: - persistence - crosscutting - jwt - - web - redis - external + - observability prod: - persistence - crosscutting - jwt - - web - redis - external + - observability test: - persistence - crosscutting - jwt - - web - redis - external + - observability servlet: multipart: max-file-size: 10MB diff --git a/batch/build.gradle.kts b/batch/build.gradle.kts index 51836ce9..4c056659 100644 --- a/batch/build.gradle.kts +++ b/batch/build.gradle.kts @@ -4,6 +4,7 @@ dependencies { implementation(project(Dependencies.Projects.DOMAIN)) implementation(project(Dependencies.Projects.GLOBAL_UTILS)) implementation(project(Dependencies.Projects.INFRA)) + implementation(project(Dependencies.Projects.OBSERVABILITY)) implementation(Dependencies.Spring.BOOT_STARTER_WEB) implementation(Dependencies.Spring.BOOT_STARTER_SECURITY) diff --git a/batch/src/main/kotlin/org/yapp/batch/BatchApplication.kt b/batch/src/main/kotlin/org/yapp/batch/BatchApplication.kt index 554d731a..67dd7f7c 100644 --- a/batch/src/main/kotlin/org/yapp/batch/BatchApplication.kt +++ b/batch/src/main/kotlin/org/yapp/batch/BatchApplication.kt @@ -1,9 +1,14 @@ package org.yapp.batch import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan -@SpringBootApplication +@SpringBootApplication( + exclude = [JpaRepositoriesAutoConfiguration::class] +) +@ComponentScan(basePackages = ["org.yapp"]) class BatchApplication fun main(args: Array) { diff --git a/batch/src/main/resources/application.yml b/batch/src/main/resources/application.yml index 35307531..ae7e42b9 100644 --- a/batch/src/main/resources/application.yml +++ b/batch/src/main/resources/application.yml @@ -1,5 +1,5 @@ server: - port: 8080 + port: 8082 shutdown: graceful spring: @@ -9,15 +9,22 @@ spring: group: dev: - persistence - - jwt + - crosscutting - redis + - external + - observability prod: - persistence - - jwt + - crosscutting - redis + - external + - observability test: - persistence - - jwt + - crosscutting + - redis + - external + - observability servlet: multipart: max-file-size: 10MB diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index d5e946b6..cd00e90b 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -30,6 +30,7 @@ object Dependencies { const val DOMAIN = ":domain" const val GLOBAL_UTILS = ":global-utils" const val GATEWAY = ":gateway" + const val OBSERVABILITY = ":observability" } object Logging { diff --git a/gateway/build.gradle.kts b/gateway/build.gradle.kts index 52c2c904..c280b284 100644 --- a/gateway/build.gradle.kts +++ b/gateway/build.gradle.kts @@ -2,12 +2,11 @@ import org.springframework.boot.gradle.tasks.bundling.BootJar dependencies { implementation(project(Dependencies.Projects.GLOBAL_UTILS)) + implementation(project(Dependencies.Projects.OBSERVABILITY)) + implementation(Dependencies.Spring.BOOT_STARTER_WEB) implementation(Dependencies.Spring.BOOT_STARTER_SECURITY) implementation(Dependencies.Spring.BOOT_STARTER_OAUTH2_RESOURCE_SERVER) - implementation(Dependencies.Spring.BOOT_STARTER_ACTUATOR) - - implementation(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY) testImplementation(Dependencies.Spring.BOOT_STARTER_TEST) } diff --git a/gateway/src/main/kotlin/org/yapp/gateway/filter/MdcLoggingFilter.kt b/gateway/src/main/kotlin/org/yapp/gateway/filter/MdcLoggingFilter.kt deleted file mode 100644 index b2277191..00000000 --- a/gateway/src/main/kotlin/org/yapp/gateway/filter/MdcLoggingFilter.kt +++ /dev/null @@ -1,79 +0,0 @@ -package org.yapp.gateway.filter - -import jakarta.servlet.FilterChain -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.slf4j.MDC -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.jwt.Jwt -import org.springframework.stereotype.Component -import org.springframework.web.filter.OncePerRequestFilter -import java.util.* - -@Component -class MdcLoggingFilter : OncePerRequestFilter() { - companion object { - private const val TRACE_ID_HEADER = "X-Request-ID" - private const val XFF_HEADER = "X-Forwarded-For" - private const val X_REAL_IP_HEADER = "X-Real-IP" - private const val TRACE_ID_KEY = "traceId" - private const val USER_ID_KEY = "userId" - private const val CLIENT_IP_KEY = "clientIp" - private const val REQUEST_INFO_KEY = "requestInfo" - private const val DEFAULT_GUEST_USER = "GUEST" - } - - override fun doFilterInternal( - request: HttpServletRequest, - response: HttpServletResponse, - filterChain: FilterChain - ) { - val traceId = resolveTraceId(request) - populateMdc(request, traceId) - - try { - filterChain.doFilter(request, response) - } finally { - MDC.clear() - } - } - - private fun resolveTraceId(request: HttpServletRequest): String { - val incomingTraceId = request.getHeader(TRACE_ID_HEADER) - return incomingTraceId?.takeIf { it.isNotBlank() } - ?: UUID.randomUUID().toString().replace("-", "") - } - - private fun populateMdc(request: HttpServletRequest, traceId: String) { - MDC.put(TRACE_ID_KEY, traceId) - MDC.put(CLIENT_IP_KEY, extractClientIp(request)) - MDC.put(REQUEST_INFO_KEY, "${request.method} ${request.requestURI}") - - val userId = resolveUserId() - MDC.put(USER_ID_KEY, userId ?: DEFAULT_GUEST_USER) - } - - private fun extractClientIp(request: HttpServletRequest): String { - val xffHeader = request.getHeader(XFF_HEADER) - if (!xffHeader.isNullOrBlank()) { - return xffHeader.split(",").first().trim() - } - - val xRealIp = request.getHeader(X_REAL_IP_HEADER) - if (!xRealIp.isNullOrBlank()) { - return xRealIp.trim() - } - - return request.remoteAddr - } - - private fun resolveUserId(): String? { - val authentication = SecurityContextHolder.getContext().authentication ?: return null - - return when (val principal = authentication.principal) { - is Jwt -> principal.subject - else -> principal?.toString() - } - } -} - diff --git a/gateway/src/main/kotlin/org/yapp/gateway/filter/SecurityMdcLoggingFilter.kt b/gateway/src/main/kotlin/org/yapp/gateway/filter/SecurityMdcLoggingFilter.kt new file mode 100644 index 00000000..b86c8552 --- /dev/null +++ b/gateway/src/main/kotlin/org/yapp/gateway/filter/SecurityMdcLoggingFilter.kt @@ -0,0 +1,30 @@ +package org.yapp.gateway.filter + +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.jwt.Jwt +import org.springframework.stereotype.Component +import org.yapp.observability.logging.filter.BaseMdcLoggingFilter + +/** + * Spring Security์™€ JWT ์ธ์ฆ์ด ์žˆ๋Š” ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” MDC ๋กœ๊น… ํ•„ํ„ฐ + * + * SecurityContext์—์„œ JWT ํ† ํฐ์„ ์ฝ์–ด ์‚ฌ์šฉ์ž ID๋ฅผ MDC์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. + * API ์„œ๋ฒ„(apis), ๊ด€๋ฆฌ์ž ์„œ๋ฒ„(admin) ๋“ฑ ์ธ์ฆ์ด ํ•„์š”ํ•œ ์„œ๋น„์Šค์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + */ +@Component +class SecurityMdcLoggingFilter : BaseMdcLoggingFilter() { + /** + * SecurityContext์—์„œ JWT principal์„ ์ฝ์–ด ์‚ฌ์šฉ์ž ID๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. + * + * @return JWT subject (์‚ฌ์šฉ์ž ID) ๋˜๋Š” null + */ + override fun resolveUserId(): String? { + val authentication = SecurityContextHolder.getContext().authentication ?: return null + + return when (val principal = authentication.principal) { + is Jwt -> principal.subject + else -> principal?.toString() + } + } +} + diff --git a/gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt b/gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt index 2fa33de4..81b179df 100644 --- a/gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt +++ b/gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt @@ -11,8 +11,8 @@ import org.springframework.security.config.http.SessionCreationPolicy import org.springframework.security.oauth2.jwt.Jwt import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter import org.springframework.security.web.SecurityFilterChain -import org.yapp.gateway.config.ActuatorProperties -import org.yapp.gateway.filter.MdcLoggingFilter +import org.yapp.observability.metrics.config.ActuatorProperties +import org.yapp.gateway.filter.SecurityMdcLoggingFilter @Configuration @EnableWebSecurity @@ -21,7 +21,7 @@ class SecurityConfig( private val jwtAuthenticationConverter: Converter, private val customAuthenticationEntryPoint: CustomAuthenticationEntryPoint, private val customAccessDeniedHandler: CustomAccessDeniedHandler, - private val mdcLoggingFilter: MdcLoggingFilter, + private val securityMdcLoggingFilter: SecurityMdcLoggingFilter, actuatorProperties: ActuatorProperties ) { companion object { @@ -61,7 +61,7 @@ class SecurityConfig( it.requestMatchers(ADMIN_PATTERN).hasRole("ADMIN") it.anyRequest().authenticated() } - .addFilterAfter(mdcLoggingFilter, BearerTokenAuthenticationFilter::class.java) + .addFilterAfter(securityMdcLoggingFilter, BearerTokenAuthenticationFilter::class.java) .build() } } diff --git a/observability/build.gradle.kts b/observability/build.gradle.kts new file mode 100644 index 00000000..3437d36a --- /dev/null +++ b/observability/build.gradle.kts @@ -0,0 +1,21 @@ +import org.springframework.boot.gradle.tasks.bundling.BootJar + +dependencies { + // Web & Filter + implementation(Dependencies.Spring.BOOT_STARTER_WEB) + + // Metrics & Monitoring + implementation(Dependencies.Spring.BOOT_STARTER_ACTUATOR) + implementation(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY) + + // Logging + implementation(Dependencies.Logging.KOTLIN_LOGGING) + + // Test + testImplementation(Dependencies.Spring.BOOT_STARTER_TEST) +} + +tasks { + withType { enabled = true } + withType { enabled = false } +} diff --git a/observability/src/main/kotlin/org/yapp/observability/logging/filter/BaseMdcLoggingFilter.kt b/observability/src/main/kotlin/org/yapp/observability/logging/filter/BaseMdcLoggingFilter.kt new file mode 100644 index 00000000..d0469581 --- /dev/null +++ b/observability/src/main/kotlin/org/yapp/observability/logging/filter/BaseMdcLoggingFilter.kt @@ -0,0 +1,95 @@ +package org.yapp.observability.logging.filter + +import jakarta.servlet.FilterChain +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.slf4j.MDC +import org.springframework.web.filter.OncePerRequestFilter +import java.util.* + +/** + * MDC (Mapped Diagnostic Context) ๊ธฐ๋ฐ˜ ๋กœ๊น… ํ•„ํ„ฐ์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„ + * + * ์ด ํ•„ํ„ฐ๋Š” ๋ชจ๋“  HTTP ์š”์ฒญ์— ๋Œ€ํ•ด ๋‹ค์Œ ์ •๋ณด๋ฅผ MDC์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค: + * - traceId: ์š”์ฒญ ์ถ”์  ID (X-Request-ID ํ—ค๋”์—์„œ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ์ž๋™ ์ƒ์„ฑ) + * - clientIp: ํด๋ผ์ด์–ธํŠธ IP (X-Forwarded-For, X-Real-IP ํ—ค๋” ๊ณ ๋ ค) + * - requestInfo: HTTP ๋ฉ”์„œ๋“œ์™€ URI + * - userId: ์‚ฌ์šฉ์ž ID (์„œ๋ธŒํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„) + * + * ์„œ๋ธŒํด๋ž˜์Šค๋Š” resolveUserId()๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜์—ฌ ์‚ฌ์šฉ์ž ID ์ถ”์ถœ ๋กœ์ง์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + */ +abstract class BaseMdcLoggingFilter : OncePerRequestFilter() { + companion object { + const val TRACE_ID_HEADER = "X-Request-ID" + const val XFF_HEADER = "X-Forwarded-For" + const val X_REAL_IP_HEADER = "X-Real-IP" + const val TRACE_ID_KEY = "traceId" + const val USER_ID_KEY = "userId" + const val CLIENT_IP_KEY = "clientIp" + const val REQUEST_INFO_KEY = "requestInfo" + const val DEFAULT_GUEST_USER = "GUEST" + } + + override fun doFilterInternal( + request: HttpServletRequest, + response: HttpServletResponse, + filterChain: FilterChain + ) { + val traceId = resolveTraceId(request) + populateMdc(request, traceId) + + try { + filterChain.doFilter(request, response) + } finally { + MDC.clear() + } + } + + /** + * ์š”์ฒญ์—์„œ TraceId๋ฅผ ์ถ”์ถœํ•˜๊ฑฐ๋‚˜ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + * X-Request-ID ํ—ค๋”๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์šฉํ•˜๊ณ , ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. + */ + private fun resolveTraceId(request: HttpServletRequest): String { + val incomingTraceId = request.getHeader(TRACE_ID_HEADER) + return incomingTraceId?.takeIf { it.isNotBlank() } + ?: UUID.randomUUID().toString().replace("-", "") + } + + /** + * MDC์— ๋กœ๊น… ์ปจํ…์ŠคํŠธ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. + */ + private fun populateMdc(request: HttpServletRequest, traceId: String) { + MDC.put(TRACE_ID_KEY, traceId) + MDC.put(CLIENT_IP_KEY, extractClientIp(request)) + MDC.put(REQUEST_INFO_KEY, "${request.method} ${request.requestURI}") + + val userId = resolveUserId() + MDC.put(USER_ID_KEY, userId ?: DEFAULT_GUEST_USER) + } + + /** + * ํด๋ผ์ด์–ธํŠธ์˜ ์‹ค์ œ IP ์ฃผ์†Œ๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. + * X-Forwarded-For, X-Real-IP ํ—ค๋”๋ฅผ ์šฐ์„  ํ™•์ธํ•˜๊ณ , ์—†์œผ๋ฉด remoteAddr ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + */ + private fun extractClientIp(request: HttpServletRequest): String { + val xffHeader = request.getHeader(XFF_HEADER) + if (!xffHeader.isNullOrBlank()) { + return xffHeader.split(",").first().trim() + } + + val xRealIp = request.getHeader(X_REAL_IP_HEADER) + if (!xRealIp.isNullOrBlank()) { + return xRealIp.trim() + } + + return request.remoteAddr + } + + /** + * ์‚ฌ์šฉ์ž ID๋ฅผ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค. + * ์„œ๋ธŒํด๋ž˜์Šค์—์„œ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜์—ฌ Security Context, JWT ๋“ฑ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + * + * @return ์‚ฌ์šฉ์ž ID (null์ธ ๊ฒฝ์šฐ GUEST๋กœ ์ฒ˜๋ฆฌ๋จ) + */ + protected abstract fun resolveUserId(): String? +} diff --git a/observability/src/main/kotlin/org/yapp/observability/logging/filter/SimpleMdcLoggingFilter.kt b/observability/src/main/kotlin/org/yapp/observability/logging/filter/SimpleMdcLoggingFilter.kt new file mode 100644 index 00000000..c984fa95 --- /dev/null +++ b/observability/src/main/kotlin/org/yapp/observability/logging/filter/SimpleMdcLoggingFilter.kt @@ -0,0 +1,18 @@ +package org.yapp.observability.logging.filter + +import org.springframework.stereotype.Component + +/** + * ์ธ์ฆ์ด ํ•„์š” ์—†๋Š” ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋ณธ MDC ๋กœ๊น… ํ•„ํ„ฐ + * + * ์ด ํ•„ํ„ฐ๋Š” ์‚ฌ์šฉ์ž ID๋ฅผ ์ถ”์ถœํ•˜์ง€ ์•Š๊ณ  ๋ชจ๋“  ์š”์ฒญ์„ GUEST๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. + * Batch ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋‚˜ ์ธ์ฆ์ด ์—†๋Š” ๋‚ด๋ถ€ ์„œ๋น„์Šค์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. + */ +@Component +class SimpleMdcLoggingFilter : BaseMdcLoggingFilter() { + /** + * ์ธ์ฆ ์ •๋ณด๊ฐ€ ์—†์œผ๋ฏ€๋กœ null์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. + * MDC์—๋Š” GUEST๋กœ ๊ธฐ๋ก๋ฉ๋‹ˆ๋‹ค. + */ + override fun resolveUserId(): String? = null +} diff --git a/gateway/src/main/kotlin/org/yapp/gateway/config/ActuatorProperties.kt b/observability/src/main/kotlin/org/yapp/observability/metrics/config/ActuatorProperties.kt similarity index 81% rename from gateway/src/main/kotlin/org/yapp/gateway/config/ActuatorProperties.kt rename to observability/src/main/kotlin/org/yapp/observability/metrics/config/ActuatorProperties.kt index 0a381e73..41728f80 100644 --- a/gateway/src/main/kotlin/org/yapp/gateway/config/ActuatorProperties.kt +++ b/observability/src/main/kotlin/org/yapp/observability/metrics/config/ActuatorProperties.kt @@ -1,4 +1,4 @@ -package org.yapp.gateway.config +package org.yapp.observability.metrics.config import org.springframework.boot.context.properties.ConfigurationProperties diff --git a/gateway/src/main/resources/application-web.yml b/observability/src/main/resources/application-observability.yml similarity index 96% rename from gateway/src/main/resources/application-web.yml rename to observability/src/main/resources/application-observability.yml index 3923ec3b..7e659010 100644 --- a/gateway/src/main/resources/application-web.yml +++ b/observability/src/main/resources/application-observability.yml @@ -18,7 +18,7 @@ spring: management: server: - port: 8081 + port: 1234 endpoints: jmx: exposure: diff --git a/settings.gradle.kts b/settings.gradle.kts index c7847254..95b6b7ab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,5 +7,6 @@ include( "batch", "domain", "infra", - "global-utils" + "global-utils", + "observability" )