Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- rabbitmq
- 마이크로서비스
- 마이크로서비스 운영
- infrastructureascode
- 분산 모니터링
- 메시징 패턴
- 모니터링
- 보안
- Kafka 클러스터
- Python
- 클라우드
- RabbitMQ Exchange
- 서비스 설계
- kubernetes
- CI/CD
- 모노리스 분해
- 이벤트 스트리밍
- 고가용성
- 분산 시스템
- ApacheBench
- devops
- docker
- 컨테이너오케스트레이션
- 마이크로서비스 통신
- 메시지 브로커
- 인메모리데이터베이스
- 세션저장소
- 서비스 메시
- 프로덕션 운영
- 클러스터
Archives
- Today
- Total
hobokai 님의 블로그
Spring Boot 완전 가이드 Part 5: 운영과 모니터링 본문
시리즈 소개: 실무에서 바로 써먹는 Spring Boot 완전 가이드
Part 1: Spring Boot 기초 - 시작하기
Part 2: 설정과 자동구성
Part 3: 웹 개발과 REST API
Part 4: 데이터 액세스와 배치
Part 5: 운영과 모니터링 ← 현재
🔍 Spring Boot Actuator - 애플리케이션 모니터링
Actuator란?
Spring Boot Actuator는 애플리케이션의 모니터링, 관리, 감사 기능을 제공하는 도구입니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
기본 엔드포인트 활성화
# application.yml
management:
endpoints:
web:
exposure:
# 모든 엔드포인트 노출 (개발환경만)
include: "*"
# 운영환경에서는 필요한 것만 노출
# include: "health,info,metrics,prometheus"
endpoint:
health:
show-details: always
shutdown:
enabled: true # 주의: 운영환경에서는 비활성화
주요 Actuator 엔드포인트
엔드포인트용도URL
| /actuator/health | 애플리케이션 상태 | GET |
| /actuator/info | 애플리케이션 정보 | GET |
| /actuator/metrics | 메트릭 정보 | GET |
| /actuator/env | 환경 변수 | GET |
| /actuator/loggers | 로그 레벨 관리 | GET/POST |
| /actuator/threaddump | 스레드 덤프 | GET |
| /actuator/heapdump | 힙 덤프 | GET |
| /actuator/prometheus | Prometheus 메트릭 | GET |
커스텀 Health Indicator
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
private final DataSource dataSource;
public DatabaseHealthIndicator(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Health health() {
try (Connection connection = dataSource.getConnection()) {
if (connection.isValid(1)) {
return Health.up()
.withDetail("database", "Available")
.withDetail("validationTimeout", "1s")
.build();
} else {
return Health.down()
.withDetail("database", "Connection validation failed")
.build();
}
} catch (SQLException e) {
return Health.down()
.withDetail("database", "Connection failed")
.withDetail("error", e.getMessage())
.build();
}
}
}
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
private final RestTemplate restTemplate;
private final String externalApiUrl;
public ExternalApiHealthIndicator(RestTemplate restTemplate,
@Value("${app.external.api.url}") String externalApiUrl) {
this.restTemplate = restTemplate;
this.externalApiUrl = externalApiUrl;
}
@Override
public Health health() {
try {
ResponseEntity<String> response = restTemplate.getForEntity(
externalApiUrl + "/health", String.class);
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("external-api", "Available")
.withDetail("responseTime", "< 1s")
.build();
} else {
return Health.down()
.withDetail("external-api", "HTTP " + response.getStatusCode())
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("external-api", "Connection failed")
.withDetail("error", e.getMessage())
.build();
}
}
}
애플리케이션 정보 설정
# application.yml
info:
app:
name: Demo Application
description: Spring Boot 완전 가이드 데모
version: 1.0.0
author: Developer Team
build:
artifact: "@project.artifactId@"
name: "@project.name@"
time: "@maven.build.timestamp@"
version: "@project.version@"
@Component
public class GitInfoContributor implements InfoContributor {
@Override
public void contribute(Info.Builder builder) {
try {
Properties gitProperties = new Properties();
gitProperties.load(getClass().getResourceAsStream("/git.properties"));
builder.withDetail("git", Map.of(
"branch", gitProperties.getProperty("git.branch"),
"commit", gitProperties.getProperty("git.commit.id.abbrev"),
"time", gitProperties.getProperty("git.commit.time")
));
} catch (Exception e) {
builder.withDetail("git", "정보를 불러올 수 없습니다");
}
}
}
📊 메트릭 수집과 모니터링
기본 메트릭 활용
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
private final MeterRegistry meterRegistry;
@GetMapping("/orders")
public ResponseEntity<List<OrderDto>> getOrders() {
Timer.Sample sample = Timer.start(meterRegistry);
try {
List<OrderDto> orders = orderService.findAllOrders();
meterRegistry.counter("order.list.success").increment();
return ResponseEntity.ok(orders);
} catch (Exception e) {
meterRegistry.counter("order.list.error").increment();
throw e;
} finally {
sample.stop(Timer.builder("order.list.duration")
.description("Order list API duration")
.register(meterRegistry));
}
}
@PostMapping("/orders")
public ResponseEntity<OrderDto> createOrder(@Valid @RequestBody CreateOrderRequest request) {
return Timer.Sample.start(meterRegistry)
.stop(meterRegistry.timer("order.create.duration"))
.recordCallable(() -> {
OrderDto order = orderService.createOrder(request);
meterRegistry.counter("order.create.success").increment();
return ResponseEntity.status(HttpStatus.CREATED).body(order);
});
}
}
커스텀 메트릭
@Component
@RequiredArgsConstructor
public class BusinessMetrics {
private final MeterRegistry meterRegistry;
private final Gauge activeUsersGauge;
private final AtomicInteger activeUsersCount = new AtomicInteger(0);
@EventListener
public void handleUserLogin(UserLoginEvent event) {
meterRegistry.counter("user.login",
"type", event.getLoginType(),
"country", event.getCountry())
.increment();
activeUsersCount.incrementAndGet();
}
@EventListener
public void handleUserLogout(UserLogoutEvent event) {
activeUsersCount.decrementAndGet();
}
@PostConstruct
public void initGauges() {
Gauge.builder("user.active.count")
.description("현재 활성 사용자 수")
.register(meterRegistry, activeUsersCount, AtomicInteger::get);
}
// 비즈니스 메트릭 메서드들
public void recordOrderAmount(BigDecimal amount) {
meterRegistry.summary("order.amount")
.record(amount.doubleValue());
}
public void recordPaymentProcessingTime(Duration duration) {
meterRegistry.timer("payment.processing.time")
.record(duration);
}
}
Prometheus 연동
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
# application.yml
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
metrics:
export:
prometheus:
enabled: true
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5,0.95,0.99
slo:
http.server.requests: 10ms,50ms,100ms,200ms,500ms
📋 로깅 전략
로깅 프레임워크 설정
# application.yml
logging:
level:
com.example: INFO
org.springframework.web: DEBUG
org.springframework.security: WARN
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
pattern:
console: "%clr(%d{ISO8601}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"
file: "%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log
max-size: 100MB
max-history: 30
total-size-cap: 1GB
구조화된 로깅 (Structured Logging)
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
public OrderDto createOrder(CreateOrderRequest request) {
String orderId = UUID.randomUUID().toString();
// 구조화된 로그
log.info("주문 생성 시작 - orderId: {}, userId: {}, amount: {}",
orderId, request.getUserId(), request.getAmount());
try {
// 주문 검증
validateOrder(request);
log.debug("주문 검증 완료 - orderId: {}", orderId);
// 주문 생성
Order order = Order.builder()
.id(orderId)
.userId(request.getUserId())
.amount(request.getAmount())
.status(OrderStatus.PENDING)
.build();
Order savedOrder = orderRepository.save(order);
log.info("주문 저장 완료 - orderId: {}, status: {}", orderId, savedOrder.getStatus());
// 결제 처리
PaymentResult paymentResult = paymentService.processPayment(
savedOrder.getId(),
savedOrder.getAmount()
);
if (paymentResult.isSuccess()) {
savedOrder.setStatus(OrderStatus.COMPLETED);
orderRepository.save(savedOrder);
log.info("주문 완료 - orderId: {}, paymentId: {}",
orderId, paymentResult.getPaymentId());
} else {
savedOrder.setStatus(OrderStatus.FAILED);
orderRepository.save(savedOrder);
log.warn("결제 실패 - orderId: {}, reason: {}",
orderId, paymentResult.getFailureReason());
}
return OrderDto.from(savedOrder);
} catch (Exception e) {
log.error("주문 생성 실패 - orderId: {}, error: {}", orderId, e.getMessage(), e);
throw new OrderCreationException("주문 생성 중 오류 발생", e);
}
}
private void validateOrder(CreateOrderRequest request) {
if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new InvalidOrderException("주문 금액은 0보다 커야 합니다");
}
if (request.getItems().isEmpty()) {
throw new InvalidOrderException("주문 상품이 없습니다");
}
log.debug("주문 검증 - userId: {}, itemCount: {}, totalAmount: {}",
request.getUserId(), request.getItems().size(), request.getAmount());
}
}
로그 집계와 분석
# logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 콘솔 출력 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- 파일 출력 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- JSON 형태 로그 (ELK Stack 연동용) -->
<appender name="JSON" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.json</file>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<loggerName/>
<message/>
<mdc/>
<arguments/>
<stackTrace/>
</providers>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.json.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 프로파일별 설정 -->
<springProfile name="local,dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="WARN">
<appender-ref ref="FILE"/>
<appender-ref ref="JSON"/>
</root>
</springProfile>
</configuration>
🚀 성능 모니터링과 최적화
JVM 메트릭 모니터링
@Configuration
public class JvmMetricsConfig {
@Bean
public JvmGcMetrics jvmGcMetrics() {
return new JvmGcMetrics();
}
@Bean
public JvmMemoryMetrics jvmMemoryMetrics() {
return new JvmMemoryMetrics();
}
@Bean
public JvmThreadMetrics jvmThreadMetrics() {
return new JvmThreadMetrics();
}
@Bean
public ProcessorMetrics processorMetrics() {
return new ProcessorMetrics();
}
@Bean
public UptimeMetrics uptimeMetrics() {
return new UptimeMetrics();
}
}
애플리케이션 성능 메트릭
@Component
@RequiredArgsConstructor
public class PerformanceMonitor {
private final MeterRegistry meterRegistry;
@EventListener
@Async
public void handleSlowQuery(SlowQueryEvent event) {
if (event.getDuration().toMillis() > 1000) {
meterRegistry.counter("database.slow.query",
"query", event.getQueryType(),
"table", event.getTableName())
.increment();
log.warn("Slow query detected - query: {}, duration: {}ms, table: {}",
event.getQueryType(), event.getDuration().toMillis(), event.getTableName());
}
}
@EventListener
@Async
public void handleHighMemoryUsage(HighMemoryUsageEvent event) {
if (event.getUsagePercent() > 80) {
meterRegistry.gauge("jvm.memory.usage.high", event.getUsagePercent());
log.warn("High memory usage detected - usage: {}%", event.getUsagePercent());
// 자동 힙 덤프 생성 (선택사항)
if (event.getUsagePercent() > 90) {
generateHeapDump();
}
}
}
private void generateHeapDump() {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
String filename = "heap-dump-" + System.currentTimeMillis() + ".hprof";
mxBean.dumpHeap(filename, true);
log.info("Heap dump generated: {}", filename);
} catch (Exception e) {
log.error("Failed to generate heap dump", e);
}
}
}
데이터베이스 성능 모니터링
@Component
@RequiredArgsConstructor
public class DatabaseMetrics {
private final MeterRegistry meterRegistry;
private final DataSource dataSource;
@Scheduled(fixedRate = 30000) // 30초마다 실행
public void recordConnectionPoolMetrics() {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
HikariPoolMXBean poolBean = hikariDataSource.getHikariPoolMXBean();
meterRegistry.gauge("hikari.connections.active", poolBean.getActiveConnections());
meterRegistry.gauge("hikari.connections.idle", poolBean.getIdleConnections());
meterRegistry.gauge("hikari.connections.total", poolBean.getTotalConnections());
meterRegistry.gauge("hikari.connections.threads.awaiting", poolBean.getThreadsAwaitingConnection());
}
}
}
🐳 컨테이너화와 배포
Docker 최적화
# Multi-stage 빌드로 이미지 크기 최적화
FROM maven:3.8.4-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# 실행 이미지
FROM openjdk:17-jdk-slim
WORKDIR /app
# 비 root 사용자로 실행
RUN addgroup --system spring && adduser --system spring --ingroup spring
USER spring:spring
# JAR 파일 복사
COPY --from=build --chown=spring:spring /app/target/*.jar app.jar
# 헬스체크
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8080/actuator/health || exit 1
# 환경별 설정
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+UseContainerSupport"
ENV SPRING_PROFILES_ACTIVE=prod
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
Docker Compose로 전체 스택 관리
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_HOST=mysql
- DB_USERNAME=myapp
- DB_PASSWORD=secret123
- REDIS_HOST=redis
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
volumes:
- ./logs:/app/logs
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: myapp
MYSQL_USER: myapp
MYSQL_PASSWORD: secret123
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- mysql_data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
restart: unless-stopped
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
restart: unless-stopped
volumes:
mysql_data:
redis_data:
grafana_data:
Kubernetes 배포
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-app
labels:
app: spring-boot-app
spec:
replicas: 3
selector:
matchLabels:
app: spring-boot-app
template:
metadata:
labels:
app: spring-boot-app
spec:
containers:
- name: app
image: spring-boot-app:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "k8s"
- name: DB_HOST
value: "mysql-service"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: spring-boot-service
spec:
selector:
app: spring-boot-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
📈 모니터링 대시보드 구성
Prometheus 설정
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app:8080']
scrape_interval: 30s
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
rule_files:
- "alert_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
Grafana 대시보드 JSON
{
"dashboard": {
"title": "Spring Boot Application Dashboard",
"panels": [
{
"title": "Application Status",
"type": "stat",
"targets": [
{
"expr": "up{job=\"spring-boot-app\"}"
}
]
},
{
"title": "Request Rate",
"type": "graph",
"targets": [
{
"expr": "rate(http_server_requests_seconds_count{job=\"spring-boot-app\"}[5m])"
}
]
},
{
"title": "Response Time",
"type": "graph",
"targets": [
{
"expr": "http_server_requests_seconds{job=\"spring-boot-app\",quantile=\"0.95\"}"
}
]
},
{
"title": "JVM Memory Usage",
"type": "graph",
"targets": [
{
"expr": "jvm_memory_used_bytes{job=\"spring-boot-app\"} / jvm_memory_max_bytes{job=\"spring-boot-app\"} * 100"
}
]
},
{
"title": "Database Connection Pool",
"type": "graph",
"targets": [
{
"expr": "hikari_connections_active{job=\"spring-boot-app\"}"
}
]
}
]
}
}
알람 규칙 설정
# alert_rules.yml
groups:
- name: spring-boot-alerts
rules:
- alert: ApplicationDown
expr: up{job="spring-boot-app"} == 0
for: 30s
labels:
severity: critical
annotations:
summary: "Spring Boot application is down"
description: "Application {{ $labels.instance }} has been down for more than 30 seconds"
- alert: HighResponseTime
expr: http_server_requests_seconds{quantile="0.95"} > 1
for: 5m
labels:
severity: warning
annotations:
summary: "High response time detected"
description: "95th percentile response time is {{ $value }}s"
- alert: HighMemoryUsage
expr: (jvm_memory_used_bytes / jvm_memory_max_bytes) * 100 > 90
for: 2m
labels:
severity: critical
annotations:
summary: "High JVM memory usage"
description: "JVM memory usage is {{ $value }}%"
- alert: DatabaseConnectionPoolExhausted
expr: hikari_connections_active >= hikari_connections_max * 0.9
for: 1m
labels:
severity: warning
annotations:
summary: "Database connection pool nearly exhausted"
description: "Connection pool usage: {{ $value }}"
🔒 보안과 운영 고려사항
Actuator 보안 설정
@Configuration
@EnableWebSecurity
public class ActuatorSecurityConfig {
@Bean
public SecurityFilterChain actuatorSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(authz -> authz
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.requestMatchers(EndpointRequest.to("prometheus")).hasRole("MONITOR")
.anyRequest().hasRole("ADMIN")
)
.httpBasic(Customizer.withDefaults())
.build();
}
}
# application-prod.yml
management:
endpoints:
web:
exposure:
# 운영환경에서는 최소한의 엔드포인트만 노출
include: "health,info,metrics,prometheus"
base-path: /internal/actuator # 기본 경로 변경
endpoint:
health:
show-details: when-authorized
shutdown:
enabled: false # 운영환경에서는 비활성화
security:
enabled: true
spring:
security:
user:
name: monitor
password: ${ACTUATOR_PASSWORD}
roles: MONITOR,ADMIN
민감한 정보 보호
# application.yml
management:
endpoint:
env:
show-values: when-authorized # 환경변수 값 숨김
configprops:
show-values: when-authorized # 설정 프로퍼티 값 숨김
# 민감한 키 마스킹
management:
endpoint:
env:
keys-to-sanitize: "password,secret,key,token,.*credentials.*,vcap_services"
🎯 Part 5 정리
핵심 포인트 요약
- 모니터링:
- Spring Boot Actuator로 애플리케이션 상태 감시
- 커스텀 Health Indicator와 메트릭 구현
- Prometheus + Grafana 대시보드 구성
- 로깅:
- 구조화된 로깅으로 분석 효율성 향상
- 환경별 로그 레벨과 출력 방식 분리
- ELK Stack 연동을 위한 JSON 로그 형식
- 성능 관리:
- JVM 메트릭과 애플리케이션 성능 지표 수집
- 데이터베이스 커넥션 풀 모니터링
- 자동 알람 및 대응 체계 구축
- 배포와 운영:
- Docker 컨테이너화와 최적화
- Kubernetes 오케스트레이션
- 보안을 고려한 Actuator 설정
실습 체크리스트
- Spring Boot Actuator 엔드포인트 활성화
- 커스텀 Health Indicator 구현
- 비즈니스 메트릭 수집 코드 작성
- 구조화된 로깅 적용
- Prometheus 메트릭 노출 설정
- Grafana 대시보드 구성
- Docker 이미지 최적화
- Kubernetes 배포 설정
- 알람 규칙 설정
- 보안 설정 강화
시리즈 완주 축하! 🎉
Spring Boot 완전 가이드 5부작을 모두 완료하셨습니다!
학습한 내용 정리:
- Part 1: Spring Boot 기초와 프로젝트 구조
- Part 2: 자동 설정과 외부 설정 관리
- Part 3: REST API 개발과 보안
- Part 4: 데이터 액세스와 배치 처리
- Part 5: 운영과 모니터링
이제 실무에서 Spring Boot로 안정적이고 확장 가능한 애플리케이션을 개발하고 운영할 수 있는 모든 지식을 갖추셨습니다!
다음 단계 추천
- 마이크로서비스 아키텍처: Spring Cloud를 활용한 분산 시스템
- 리액티브 프로그래밍: Spring WebFlux 심화
- 클라우드 네이티브: AWS/GCP Spring Boot 배포
- 테스트 전략: 통합 테스트와 성능 테스트
- 보안 심화: OAuth2, JWT 고급 활용
📚 참고 자료:
🏷️ 태그: #SpringBoot #Actuator #Prometheus #Grafana #Docker #Kubernetes #운영 #모니터링 #실무가이드
'Backend' 카테고리의 다른 글
| gRPC 강점 (0) | 2025.09.04 |
|---|---|
| OAuth2 & JWT 고급 활용 가이드 (0) | 2025.08.07 |
| Spring Boot 완전 가이드 Part 4: 데이터 액세스와 배치 (2) | 2025.08.06 |
| Spring Boot 완전 가이드 Part 3: 웹 개발과 REST API (3) | 2025.08.06 |
| Spring Boot 완전 가이드 Part 2: 설정과 자동구성 (2) | 2025.08.06 |