diff --git a/docker-compose.yml b/docker-compose.yml index 4dcd8ea..c728bde 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:v2.54.1 + container_name: momogo-prometheus + ports: + - "127.0.0.1:9090:9090" + volumes: + - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus-data:/prometheus + extra_hosts: + - "host.docker.internal:host-gateway" + + grafana: + image: grafana/grafana:11.1.0 + container_name: momogo-grafana + ports: + - "127.0.0.1:3000:3000" + volumes: + - grafana-data:/var/lib/grafana + environment: + - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER:-admin} + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD is required} + depends_on: + - prometheus + +volumes: + prometheus-data: + grafana-data: \ No newline at end of file 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..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").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/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) diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 0000000..aa08615 --- /dev/null +++ b/prometheus/prometheus.yml @@ -0,0 +1,25 @@ +global: + scrape_interval: 5s + evaluation_interval: 5s + +scrape_configs: + - job_name: 'momogo-local-api' + metrics_path: '/actuator/prometheus' + scheme: 'http' + scrape_interval: 5s + static_configs: + - targets: ['host.docker.internal:8080'] + labels: + env: 'local' + + - 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' \ No newline at end of file