Skip to content

feat: 운영 서비스 1차 Observability 구축 - #33

Merged
mungmnb777 merged 14 commits into
mainfrom
feature/observability-phase-1
Jul 16, 2026
Merged

feat: 운영 서비스 1차 Observability 구축#33
mungmnb777 merged 14 commits into
mainfrom
feature/observability-phase-1

Conversation

@mungmnb777

@mungmnb777 mungmnb777 commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

관련 Issue

결과

Spring Boot 애플리케이션과 OCI VM의 1차 Observability를 구현하고 운영에 적용했습니다.

  • 운영 애플리케이션 commit: 949976f0fd46ff60094c54e5b3df8433a6e4de59
  • PR HEAD: dba0494107d196e9acd564b3b7d4f229b8d91f64 (운영 증거와 logical/live resource 구분만 보완한 docs-only commits)
  • 운영 배포: Actions run 29490895778 success
  • Dashboard: Mapleland / Production Overview

merge는 별도 요청 전 수행하지 않습니다.

설계 결정

  • Spring Boot 3.4.5 내장 ECS encoder로 production 로그를 /var/log/mapleland-api/mapleland-api.json에 JSON Lines로 기록합니다. Boot 전체 업그레이드는 하지 않았습니다.
  • 파일은 10 MiB, 14일, 총 250 MiB로 제한합니다. Exception은 cause 최대 8, throwable별 40 frame, 전체 16 KiB로 제한하면서 root cause type과 핵심 application frame을 보존합니다.
  • Loki4j appender와 애플리케이션 내 원격 전송 credential을 제거했습니다. 로컬 파일 기록과 Grafana Cloud 전송을 분리하고 Alloy v1.17.1이 app/host/self metric과 ECS log를 전송합니다.
  • public :8080/actuator/**는 404입니다. Management :18080은 host 127.0.0.1 publish 전용이고 Prometheus는 bearer 인증을 요구합니다. Alloy :12345127.0.0.1 전용입니다.
  • app/host/Alloy scrape는 60초입니다. Metric name allowlist와 route-template URI만 유지하며 raw URI/query/user/request/trace ID tag를 추가하지 않습니다.
  • Loki indexed label은 service_name, deployment_environment, level, cloud_provider 네 개뿐입니다. logger/thread/event/HTTP/error/trace/request/mapleland field는 structured metadata 또는 ECS body에 둡니다.
  • Dashboard JSON과 alert provisioning reference는 deploy/observability/grafana/에서 버전 관리합니다. Grafana Cloud가 file provisioning wrapper를 직접 받지 않으므로 live alert는 기존 로그인 UI에서 동일 query/threshold로 만들었습니다.

자동 검증

  • 애플리케이션 배포 commit CI: 29490705349 success
  • 최종 docs-only PR HEAD CI: 29496558499 success
  • Gradle regression: ./gradlew clean test bootJar --no-daemon, 47 tests, failure/error/skipped 0
  • 실제 random app/management HTTP port에서 Prometheus 401/200, public Actuator exact 404, health detail 비공개 검증
  • JVM/HTTP/Hikari/process metric과 route-template URI 검증
  • production logging 별도 JVM fork에서 모든 physical line의 ECS JSON, rotation, 단일-event escaped stack trace, redaction, MDC/key-value 격리 검증
  • 공식 Alloy v1.17.1 fmt --testvalidate 성공
  • host preflight, immutable digest deploy/automatic rollback, shellless image permission gate, Grafana JSON, Bash/YAML/Markdown와 git diff --check 검증
  • 코드 리뷰/운영 위험 리뷰 3개 thread 모두 해결

운영 적용

  • 최종 image digest: sha256:0560502466a7b54f2bea0596e1963f68197e1bb085be42d99cd17ae959409875; 운영 container RepoDigest와 일치
  • 이전 image ID: sha256:1b9b13b75debfe76ad755f618738bd63972a270b78d03f90d50133ee277fa3af
  • rollback tag: rollback-20260716T105725955628686-1b9b13b75deb-351732; 최종 배포에서 rollback은 실행되지 않음
  • container running, restart count 0, SERVICE_VERSION은 운영 commit과 일치
  • recreate → started 2.454초, recreate → readiness 21.680초. 외부 체감 중단 시간은 별도로 계측하지 못했습니다.
  • public 8080과 Traefik/firewall은 변경하지 않았습니다. 새 port 18080, 12345는 loopback 전용입니다.
  • root-only /var/log/mapleland-deploy/last-failure와 검증 fixture는 성공 확인 뒤 제거했습니다.

Behavioral evidence

애플리케이션·보안 경계

  • /api/v1/jobs 200
  • public :8080/actuator/prometheus, /actuator/health, /actuator/env, /actuator/configprops 모두 404
  • management Prometheus: unauthenticated 401, root-only token을 stdin curl config로 제공하면 200
  • JVM, HTTP, HikariCP, process CPU/RSS metric 존재
  • 실행 app container의 GRAFANA_CLOUD_* 환경변수 0개

ECS 로그·Alloy

  • 초기 운영 로그 42/42 physical line이 각각 유효한 단일-line ECS JSON
  • 필수 ECS/service field 누락 0, 원본 member ID·인증정보·secret field·email 패턴 0
  • indexed selector와 event/error/HTTP/mapleland structured field 조회 성공
  • 명시적 observability.stack-trace-test fixture 1건이 Loki에서 정확히 한 event로 표시됐고 error.type/message/stack_trace, event.action/outcome이 조회됨. Newline은 JSON 문자열 안에 escape됨. 애플리케이션에 운영 예외를 유발하지 않았고 fixture는 제거함.
  • Alloy를 2초 중단해도 public API 200과 로컬 로그 기록이 지속됨
  • 재시작 뒤 backlog app 1줄 + fixture 1줄만 이어서 읽고 기존 42줄을 replay하지 않음
  • rename/create rotation 전후 marker는 Loki에서 각각 1건, structured metadata event_action 집계는 2건
  • parse failure, dropped entries, dropped bytes 모두 0
  • Alloy active/enabled/ready; app 로그 파일 ACL과 rotation 후 read 권한 유지

Grafana Cloud

  • 17개 dashboard panel이 실제 데이터로 렌더링: app up, request/4xx/5xx, p50/p95, heap, GC, thread, Hikari active/idle/pending/max, process CPU/RSS, host CPU/memory/filesystem, WARN/ERROR log
  • App up UP; 30회 smoke 후 request rate와 p50/p95 실제 sample 렌더링
  • Dashboard에서 production Loki Explore로 이동하는 link 동작
  • Alert folder: Mapleland (live UID fnp4gz), group Mapleland Production, interval 1m
  • Application scrape down: 5m, No Data=Alerting, Error=Error, Normal
  • Traffic-aware 5xx increase: 5xx >5% AND requests >=20/10m, 10m, No Data=OK, Normal
  • Host disk below 10%: writable filesystem available <10%, 15m, No Data=OK, Normal
  • 반복 평가 후 Firing 0, Pending 0, Active notifications 0
  • 기존 grafana-default-email contact point와 default notification policy/route는 변경하지 않음
  • 기존 dashboard, alert, datasource, MCP/OAuth 연결은 삭제하거나 중복 생성하지 않음

성능·Free Tier

  • 대표 API 30회: 200 30/30, 평균 5.820ms, p95 7.363ms, 최대 17.869ms
  • 기존 p95 3.494ms 대비 +3.869ms로 승인 기준 max(20%, 5ms) 이내
  • 30분 prod active series 236, 배포 직전 역사 시점 대비 증가 127 (gate 5,000 / 2,500)
  • Alloy RSS 최대 198,254,592 bytes, CPU 최대 0.005583 core (gate 256 MiB / 0.10 core)
  • Loki 5분 rate 30분 최대 일일환산 410,041.71 bytes/day; 15분 rate 확인 168,274.29 bytes/day (gate 250 MiB/day)
  • ECS parse failure 0, Loki dropped entries/bytes 0
  • 단일 bounded instance=mapleland-oci-1; Loki에는 host label을 추가하지 않음

Secret 처리

  • Grafana metrics/logs token은 최소 write scope이며 root-only /etc/alloy/alloy.env에서만 읽습니다.
  • Management scrape token은 Alloy env와 app root-only env에만 저장하고 출력하지 않습니다.
  • GHCR credential은 root-only /opt/mapleland/ghcr.env의 allowlisted key만 strict parser로 읽습니다.
  • 저장소에는 .env.example placeholder만 있고 실제 token/credential은 없습니다.
  • Firebase artifact는 non-empty JSON과 archive/runtime owner/mode를 내용 출력 없이 검증합니다.

롤백

  • 애플리케이션/ECS: 보존된 exact previous image tag와 root-only Compose/env backup을 --pull never --force-recreate로 복구하고 image ID, public smoke를 재검증
  • Alloy: systemctl disable --now alloy; package rollback은 checksum 확인 → unmasked/stopped 설치 → installed binary validate → explicit start/ready
  • Grafana: 신규 rule을 먼저 pause하고 versioned dashboard source로 overwrite. Datasource/contact point/notification route/MCP connection은 삭제하지 않음
  • 전송 공백: primary positions를 보존하고 누락 시간대만 quarantine/dedup gate를 거친 별도 recovery source로 replay

전체 명령과 health/smoke/data-arrival/복구 절차는 docs/observability-runbook.md, 최종 무비밀 운영 상태는 docs/observability-rollout-checkpoint.md에 있습니다.

범위 밖·후속 위험

  • 반복 Hikari connection validation warning: 이번 PR에서 원인을 수정하지 않음. 배포 후 최근 1시간 exact-message count는 0이지만 기존 반복 이력 때문에 DB/server timeout·maxLifetime 정합성 후속 조사 필요
  • 인증 재발급 경로 NullPointerException: 이번 PR에서 원인을 수정하지 않음. 배포 후 최근 1시간 count는 0이지만 기존 발생 이력의 재현 조건과 null contract 후속 조사 필요
  • 기존 JWT/Firebase secret rotation·외부화
  • Tempo/distributed tracing, OTLP/Java agent, Sentry/Crashlytics, synthetic monitoring
  • 자연 발생한 운영 Throwable은 배포 후 없었습니다. App encoder 동작은 자동 fork test, Grafana 단일-event 표시는 안전한 명시적 fixture로 각각 검증했습니다.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive observability and telemetry pipeline for the Mapleland production environment. It replaces direct Loki4j logging with structured ECS logging to local files, which are then collected by Grafana Alloy alongside host and application metrics. It also secures the Prometheus actuator endpoint using a custom security filter with bearer token authentication, adds a custom binder for process resident memory metrics, and configures Grafana dashboards and alert rules. The reviewer identified a potential NullPointerException in the newly introduced SafeExceptionLog utility if a null throwable is passed, which should be addressed to ensure robust error logging.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/main/java/com/maple/api/common/logging/SafeExceptionLog.java

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive observability pipeline for the Mapleland application, migrating from direct Loki4j logging to Spring Boot's native ECS structured logging with local file rotation, and setting up Grafana Alloy to scrape metrics and tail logs. It adds a loopback-bound management server secured by a bearer token, custom Linux RSS memory metrics, a robust exception sanitization utility, and extensive deployment, validation, and runbook documentation. The review feedback is highly valuable and should be addressed: first, add a null check in SafeExceptionLog.addException to prevent a potential NullPointerException; second, optimize the filter chain order in SecurityConfig to ensure the Prometheus scrape token filter runs before JWT authentication filters to avoid malformed token exceptions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/main/java/com/maple/api/common/logging/SafeExceptionLog.java
@mungmnb777
mungmnb777 deployed to production July 16, 2026 10:30 — with GitHub Actions Active
@mungmnb777
mungmnb777 marked this pull request as ready for review July 16, 2026 13:05
@mungmnb777
mungmnb777 merged commit 33c5732 into main Jul 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 운영 서비스 1차 Observability 구축 (ECS 로그·Alloy 메트릭·Grafana Cloud)

1 participant