From c8b7a309a034592d4af1676073742b08550ded1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=84=B1=ED=9C=98?= Date: Fri, 7 Aug 2026 14:52:02 +0900 Subject: [PATCH 1/3] feat: setup Spring Boot Actuator and Prometheus metrics endpoint --- .../momogo/api/auth/config/SecurityConfig.java | 2 +- momogo-api/src/main/resources/application.yaml | 17 +++++++++++++++++ momogo-core/build.gradle | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java b/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java index 327878e..6608185 100644 --- a/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java +++ b/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java @@ -74,7 +74,7 @@ public SecurityFilterChain filterChain( .requestMatchers("/", "/index.html").permitAll() .requestMatchers("/ws/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/actuator/health", "/actuator/info").permitAll() + .requestMatchers("/actuator/health", "/actuator/info", "/actuator/prometheus").permitAll() .requestMatchers("/actuator/**").hasRole("SUPER_ADMIN") .requestMatchers("/api/super-admin/**").hasRole("SUPER_ADMIN") diff --git a/momogo-api/src/main/resources/application.yaml b/momogo-api/src/main/resources/application.yaml index 6e0506d..3f589aa 100644 --- a/momogo-api/src/main/resources/application.yaml +++ b/momogo-api/src/main/resources/application.yaml @@ -147,3 +147,20 @@ app: server: port: 8080 + +# monitoring +management: + endpoints: + web: + exposure: + include: health, info, prometheus, metrics + prometheus: + metrics: + export: + enabled: true + metrics: + tags: + application: ${spring.application.name:momogo-api} + distribution: + percentiles-histogram: + http.server.requests: true \ No newline at end of file diff --git a/momogo-core/build.gradle b/momogo-core/build.gradle index 51ef047..244a632 100644 --- a/momogo-core/build.gradle +++ b/momogo-core/build.gradle @@ -11,6 +11,7 @@ dependencies { api 'org.springframework.boot:spring-boot-starter-data-jpa' api 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-actuator' + api 'io.micrometer:micrometer-registry-prometheus' runtimeOnly 'org.postgresql:postgresql' // QueryDSL (Spring Boot 3.x / Jakarta) From 9ce771490a66cd85fc5c2df49dcabbc07531b529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=84=B1=ED=9C=98?= Date: Fri, 7 Aug 2026 15:54:25 +0900 Subject: [PATCH 2/3] feat: complete Prometheus and Grafana monitoring stack setup --- docker-compose.yml | 28 ++++++++++++++++++++++++++++ prometheus/prometheus.yml | 25 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 prometheus/prometheus.yml diff --git a/docker-compose.yml b/docker-compose.yml index 4dcd8ea..230b7fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,3 +31,31 @@ services: KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092 depends_on: - kafka + + prometheus: + image: prom/prometheus:latest + container_name: momogo-prometheus + ports: + - "9090:9090" + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus-data:/prometheus + extra_hosts: + - "host.docker.internal:host-gateway" + + grafana: + image: grafana/grafana:latest + container_name: momogo-grafana + ports: + - "3000:3000" + volumes: + - grafana-data:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=admin + depends_on: + - prometheus + +volumes: + prometheus-data: + grafana-data: \ No newline at end of file diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 0000000..8c07c7e --- /dev/null +++ b/prometheus/prometheus.yml @@ -0,0 +1,25 @@ +global: + scrape_interval: 5s # 5초마다 메트릭 수집 (부하 테스트 모니터링을 위해 조금 짧게 설정) + evaluation_interval: 5s + +scrape_configs: + - job_name: 'momogo-local-api' + metrics_path: '/actuator/prometheus' + scheme: 'http' + scrape_interval: 5s + static_configs: + # 로컬에서 호스트 PC의 Spring Boot 서버(8080)에 접근하기 위해 hoost.docker.internal 사용 + - targets: ['host.docker.internal:8080'] + labels: + env: 'local' + application: 'momogo-api' + + - job_name: 'momogo-prod-api' + metrics_path: '/actuator/prometheus' + scheme: 'http' + scrape_interval: 5s + static_configs: + - targets: ['momogo.kro.kr'] + labels: + env: 'production' + application: 'momogo-api' \ No newline at end of file From 04041bfb2c6679ed8e9a49fc6310e434ba8e63ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=84=B1=ED=9C=98?= Date: Fri, 7 Aug 2026 16:29:01 +0900 Subject: [PATCH 3/3] security: apply Basic Auth and environment profile separation for Prometheus scraping --- docker-compose.yml | 12 ++++----- .../api/auth/config/SecurityConfig.java | 27 +++++++++++++++---- prometheus/prometheus.yml | 10 +++---- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 230b7fe..c728bde 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,10 +33,10 @@ services: - kafka prometheus: - image: prom/prometheus:latest + image: prom/prometheus:v2.54.1 container_name: momogo-prometheus ports: - - "9090:9090" + - "127.0.0.1:9090:9090" volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus @@ -44,15 +44,15 @@ services: - "host.docker.internal:host-gateway" grafana: - image: grafana/grafana:latest + image: grafana/grafana:11.1.0 container_name: momogo-grafana ports: - - "3000:3000" + - "127.0.0.1:3000:3000" volumes: - grafana-data:/var/lib/grafana environment: - - GF_SECURITY_ADMIN_USER=admin - - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-admin} + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD is required} depends_on: - prometheus diff --git a/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java b/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java index 6608185..4eb6ceb 100644 --- a/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java +++ b/momogo-api/src/main/java/com/momogo/api/auth/config/SecurityConfig.java @@ -9,6 +9,8 @@ import jakarta.servlet.DispatcherType; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.http.HttpMethod; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; @@ -18,6 +20,7 @@ import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -59,8 +62,11 @@ public SecurityFilterChain filterChain( CustomAuthenticationEntryPoint customAuthenticationEntryPoint, OAuth2UserDetailsService oAuth2UserDetailsService, OAuth2LoginSuccessHandler oauth2LoginSuccessHandler, - OAuth2LoginFailureHandler oauth2LoginFailureHandler + OAuth2LoginFailureHandler oauth2LoginFailureHandler, + Environment env ) throws Exception { + boolean isDevOrLocal = env.acceptsProfiles(Profiles.of("local", "dev")); + http .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(csrf -> csrf @@ -68,13 +74,24 @@ public SecurityFilterChain filterChain( .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) .ignoringRequestMatchers("/api/auth/sign-in") ) - .authorizeHttpRequests(auth -> auth + .httpBasic(Customizer.withDefaults()) + .authorizeHttpRequests(auth -> { + auth .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() // 문서 및 소켓 관련 .requestMatchers("/", "/index.html").permitAll() .requestMatchers("/ws/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/actuator/health", "/actuator/info", "/actuator/prometheus").permitAll() + .requestMatchers("/actuator/health", "/actuator/info").permitAll(); + + // 개발/로컬 프로필일 때만 k6 부하 테스트 스크랩용 prometheus 엔드포인트 공개, 운영 시 인증 요구 + if (isDevOrLocal) { + auth.requestMatchers("/actuator/prometheus").permitAll(); + } else { + auth.requestMatchers("/actuator/prometheus").hasAnyRole("METRICS", "SUPER_ADMIN"); + } + + auth .requestMatchers("/actuator/**").hasRole("SUPER_ADMIN") .requestMatchers("/api/super-admin/**").hasRole("SUPER_ADMIN") @@ -105,8 +122,8 @@ public SecurityFilterChain filterChain( .requestMatchers(HttpMethod.GET, "/uploads/**").permitAll() .requestMatchers("/api/**").authenticated() - .anyRequest().authenticated() - ) + .anyRequest().authenticated(); + }) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml index 8c07c7e..aa08615 100644 --- a/prometheus/prometheus.yml +++ b/prometheus/prometheus.yml @@ -1,5 +1,5 @@ global: - scrape_interval: 5s # 5초마다 메트릭 수집 (부하 테스트 모니터링을 위해 조금 짧게 설정) + scrape_interval: 5s evaluation_interval: 5s scrape_configs: @@ -8,18 +8,18 @@ scrape_configs: scheme: 'http' scrape_interval: 5s static_configs: - # 로컬에서 호스트 PC의 Spring Boot 서버(8080)에 접근하기 위해 hoost.docker.internal 사용 - targets: ['host.docker.internal:8080'] labels: env: 'local' - application: 'momogo-api' - job_name: 'momogo-prod-api' metrics_path: '/actuator/prometheus' scheme: 'http' scrape_interval: 5s + basic_auth: + username: '${PROMETHEUS_USER:?PROMETHEUS_USER is required}' + password: '${PROMETHEUS_PASS:?PROMETHEUS_PASS is required}' static_configs: - targets: ['momogo.kro.kr'] labels: - env: 'production' - application: 'momogo-api' \ No newline at end of file + env: 'production' \ No newline at end of file