Prometheus – monitoring aplikacji w praktyce

TL;DR: Prometheus to open-source system monitorowania i alertingu który rewolucjonizuje sposób obserwowania aplikacji. Oparty na time-series database, automatycznie zbiera metryki z aplikacji, pozwala tworzyć dashboardy w Grafana i wysyłać alerty. Idealny dla mikrousług i nowoczesnych architektur.

Monitoring aplikacji to krytyczny element każdego systemu produkcyjnego. Bez odpowiedniego monitorowania jesteś ślepy – nie wiesz kiedy aplikacja ma problemy, nie rozumiesz jej zachowania pod obciążeniem i nie możesz szybko reagować na incydenty. Prometheus zmienia to radykalnie, oferując profesjonalne rozwiązanie monitoringu które używają największe firmy technologiczne.

Dlaczego to ważne

W erze mikrousług i rozproszonych systemów tradycyjne narzędzia monitoringu stają się niewystarczające. Potrzebujesz rozwiązania które automatycznie odkrywa usługi, zbiera metryki w czasie rzeczywistym i pozwala łatwo analizować trendy. Prometheus rozwiązuje te problemy wprowadzając model „pull-based” – to serwer monitoringu aktywnie odpytuje aplikacje o metryki, zamiast czekać na ich wysłanie. To fundamentalnie inne podejście które sprawdza się w dynamicznych środowiskach.

Co się nauczysz:

  • Czym jest Prometheus i jak działa jego architektura
  • Instalację i podstawową konfigurację Prometheus
  • Instrumentację aplikacji Java z Micrometer
  • Definiowanie i zbieranie własnych metryk
  • Tworzenie alertów i reguł monitoringu
  • Integrację z Grafana do wizualizacji
  • Best practices dla monitoringu w produkcji
Wymagania wstępne: Podstawowa znajomość Javy i Spring Boot, doświadczenie z aplikacjami webowymi, zrozumienie konceptów HTTP i REST API. Przydatna znajomość Dockera ale nie wymagana.

Architektura Prometheus

Prometheus składa się z kilku kluczowych komponentów które współpracują ze sobą:

Prometheus Server – główny komponent który zbiera („scrape”) metryki z aplikacji i przechowuje je w time-series database.
Targets – aplikacje które eksponują metryki na dedykowanych endpointach (np. /metrics).
Alertmanager – obsługuje alerty wysyłane przez Prometheus Server i kieruje je do odpowiednich kanałów (email, Slack, PagerDuty).
Pushgateway – pozwala aplikacjom krótkożyciowym (batch jobs) „wpchać” metryki zamiast czekać na scraping.
# prometheus.yml - podstawowa konfiguracja
global:
  scrape_interval: 15s # Jak często zbierać metryki
  evaluation_interval: 15s # Jak często sprawdzać reguły alertów

rule_files:
  - "alert_rules.yml"

scrape_configs:
  # Monitoring samego Prometheus
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  # Monitoring aplikacji Spring Boot
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['localhost:8080']

  # Monitoring systemu operacyjnego
  - job_name: 'node-exporter'
    static_configs:
      - targets: ['localhost:9100']

alerting:
  alertmanagers:
    - static_configs:
        - targets:
          - alertmanager:9093
Prometheus używa modelu „pull” – to znaczy że serwer aktywnie odpytuje aplikacje o metryki. To różni go od tradycyjnych systemów gdzie aplikacje „push’ują” metryki do serwera monitoringu.

Instalacja i uruchomienie

Najszybszy sposób na start z Prometheus to Docker Compose który uruchomi kompletny stack monitoringu:

# docker-compose.yml
version: '3.7'

services:
  prometheus:
    image: prom/prometheus:v2.1.0
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./alert_rules.yml:/etc/prometheus/alert_rules.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'
      - '--storage.tsdb.retention=15d'
      - '--web.enable-lifecycle'

  grafana:
    image: grafana/grafana:4.6.3
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-storage:/var/lib/grafana

  node-exporter:
    image: prom/node-exporter:v0.15.2
    container_name: node-exporter
    ports:
      - "9100:9100"

volumes:
  grafana-storage:
# Uruchomienie całego stacku
docker-compose up -d

# Sprawdzenie czy wszystko działa
curl http://localhost:9090/targets  # Status targets w Prometheus
curl http://localhost:3000         # Grafana (admin/admin)
curl http://localhost:9100/metrics # Node exporter metryki
Pro tip: Prometheus domyślnie trzyma dane przez 15 dni. W produkcji ustaw dłuższy retention period używając parametru –storage.tsdb.retention lub skonfiguruj remote storage dla długoterminowego przechowywania.

Instrumentacja aplikacji Spring Boot

Spring Boot 2.0 wprowadza Micrometer – bibliotekę która standaryzuje metryki i ułatwia integrację z Prometheus:

<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>
# application.properties
management.endpoints.web.exposure.include=prometheus,health,info
management.endpoint.prometheus.enabled=true
management.metrics.export.prometheus.enabled=true

# Włączenie szczegółowych metryk JVM
management.metrics.enable.jvm=true
management.metrics.enable.process=true
management.metrics.enable.system=true
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private final MeterRegistry meterRegistry;
    private final Counter orderCounter;
    private final Timer orderProcessingTimer;
    
    public OrderController(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.orderCounter = Counter.builder("orders_total")
                .description("Total number of orders")
                .tag("status", "created")
                .register(meterRegistry);
        this.orderProcessingTimer = Timer.builder("order_processing_duration")
                .description("Time spent processing orders")
                .register(meterRegistry);
    }
    
    @PostMapping
    public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
        return orderProcessingTimer.recordCallable(() -> {
            try {
                Order order = orderService.createOrder(request);
                orderCounter.increment();
                
                // Gauge dla aktualnej liczby zamówień w systemie
                Gauge.builder("orders_pending")
                     .description("Current number of pending orders")
                     .register(meterRegistry, this, obj -> orderService.getPendingOrdersCount());
                
                return ResponseEntity.ok(order);
            } catch (Exception e) {
                Counter.builder("orders_errors_total")
                      .description("Total number of order errors")
                      .tag("error_type", e.getClass().getSimpleName())
                      .register(meterRegistry)
                      .increment();
                throw e;
            }
        });
    }
}

Typy metryk w Prometheus

Prometheus rozpoznaje cztery podstawowe typy metryk, każdy służy do innego celu:

Counter – wartość która tylko rośnie (np. liczba requestów, błędów). Resetuje się tylko przy restarcie aplikacji.
Gauge – wartość która może rosnąć i maleć (np. użycie pamięci, liczba aktywnych połączeń).
Histogram – obserwacje pogrupowane w buckets (np. czas odpowiedzi, rozmiary requestów).
Summary – podobne do histogram ale oblicza percentyle po stronie klienta.
@Component
public class CustomMetrics {
    private final MeterRegistry meterRegistry;
    
    // Counter - liczba przetworzonych płatności
    private final Counter paymentCounter = Counter.builder("payments_processed_total")
            .description("Total payments processed")
            .tag("payment_method", "card")
            .register(meterRegistry);
    
    // Gauge - aktualna liczba użytkowników online
    private final AtomicInteger usersOnline = new AtomicInteger(0);
    
    // Histogram - czas odpowiedzi API
    private final Timer apiResponseTime = Timer.builder("api_response_time")
            .description("API response time")
            .publishPercentiles(0.5, 0.95, 0.99) // 50th, 95th, 99th percentile
            .register(meterRegistry);
    
    public CustomMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        // Rejestracja Gauge
        Gauge.builder("users_online")
             .description("Current users online")
             .register(meterRegistry, usersOnline, AtomicInteger::get);
    }
    
    public void recordPayment(String method, BigDecimal amount) {
        paymentCounter.increment(Tags.of("payment_method", method));
        
        // Histogram dla kwot płatności
        DistributionSummary.builder("payment_amount")
                .description("Payment amounts")
                .tag("method", method)
                .register(meterRegistry)
                .record(amount.doubleValue());
    }
    
    @EventListener
    public void onUserLogin(UserLoginEvent event) {
        usersOnline.incrementAndGet();
    }
    
    @EventListener  
    public void onUserLogout(UserLogoutEvent event) {
        usersOnline.decrementAndGet();
    }
}
Uwaga: Nie twórz metryk z wysoką kardynalnością (dużo różnych kombinacji labelów). Każda unikalna kombinacja nazwa_metryki + labele tworzy osobną time series, co może prowadzić do problemów z wydajnością.

Zapytania PromQL

PromQL to język zapytań Prometheus który pozwala analizować metryki i tworzyć alerty:

# Podstawowe zapytania
http_requests_total                    # Wszystkie requesty HTTP
http_requests_total{status="200"}      # Tylko successful requests
http_requests_total{job="spring-app"} # Tylko z konkretnej aplikacji

# Rate i increase - tempo zmian
rate(http_requests_total[5m])          # Requests per second (5min window)
increase(http_requests_total[1h])      # Wzrost w ciągu godziny

# Aggregation functions
sum(rate(http_requests_total[5m]))     # Całkowity RPS
avg(response_time_seconds)             # Średni czas odpowiedzi
max(memory_usage_bytes)                # Maksymalne użycie pamięci

# Percentile z histogram
histogram_quantile(0.95, 
  rate(api_response_time_bucket[5m])   # 95th percentile response time
)

# Alerty i warunki
rate(http_requests_total{status=~"5.."}[5m]) > 0.1  # Error rate > 10%
up == 0                                              # Aplikacja nie odpowiada
(time() - process_start_time_seconds) < 60          # Restart w ostatniej minucie
PromQL to jak SQL dla metryk - pozwala filtrować, grupować i agregować dane time-series. Rate() to "DELTA", sum() to "GROUP BY", a histogram_quantile() to "PERCENTILE".

Alerting i reguły

Prometheus może automatycznie wykrywać problemy i wysyłać alerty:

# alert_rules.yml
groups:
- name: application_alerts
  rules:
  # Alert gdy aplikacja nie odpowiada
  - alert: ApplicationDown
    expr: up{job="spring-boot-app"} == 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "Application {{ $labels.instance }} is down"
      description: "Application has been down for more than 1 minute"

  # Alert przy wysokim error rate
  - alert: HighErrorRate
    expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "High error rate on {{ $labels.instance }}"
      description: "Error rate is {{ $value | humanizePercentage }}"

  # Alert przy wysokim użyciu pamięci
  - alert: HighMemoryUsage
    expr: (jvm_memory_used_bytes / jvm_memory_max_bytes) > 0.9
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High memory usage on {{ $labels.instance }}"
      description: "Memory usage is above 90%: {{ $value | humanizePercentage }}"

  # Alert przy długich czasach odpowiedzi
  - alert: SlowResponseTime
    expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
    for: 3m
    labels:
      severity: warning
    annotations:
      summary: "Slow response times on {{ $labels.instance }}"
      description: "95th percentile response time is {{ $value }}s"
# alertmanager.yml
global:
  smtp_smarthost: 'localhost:587'
  smtp_from: 'alertmanager@company.com'

route:
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 1h
  receiver: 'web-team'

receivers:
- name: 'web-team'
  email_configs:
  - to: 'team@company.com'
    subject: 'ALERT: {{ .GroupLabels.alertname }}'
    body: |
      {{ range .Alerts }}
      Alert: {{ .Annotations.summary }}
      Description: {{ .Annotations.description }}
      {{ end }}
  slack_configs:
  - api_url: 'YOUR_SLACK_WEBHOOK_URL'
    channel: '#alerts'
    title: 'Production Alert'
    text: '{{ .CommonAnnotations.summary }}'

Integracja z Grafana

Grafana to idealne narzędzie do wizualizacji metryk z Prometheus:

{
  "dashboard": {
    "title": "Application Monitoring",
    "panels": [
      {
        "title": "Request Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(http_requests_total[5m])",
            "legendFormat": "{{ instance }} - {{ status }}"
          }
        ]
      },
      {
        "title": "Response Time (95th percentile)",
        "type": "singlestat",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(api_response_time_bucket[5m]))",
            "legendFormat": "95th percentile"
          }
        ]
      },
      {
        "title": "Memory Usage",
        "type": "graph",
        "targets": [
          {
            "expr": "jvm_memory_used_bytes / jvm_memory_max_bytes * 100",
            "legendFormat": "{{ instance }} Memory %"
          }
        ]
      }
    ]
  }
}
Grafana oferuje gotowe dashboardy dla popularnych aplikacji. Sprawdź https://grafana.com/dashboards - znajdziesz tam setki gotowych dashboardów dla Spring Boot, JVM, baz danych i innych technologii.

Best practices dla produkcji

Monitoring strategie:

  • USE Method: Utilization, Saturation, Errors dla każdego zasobu
  • RED Method: Rate, Errors, Duration dla każdego serwisu
  • Four Golden Signals: Latency, Traffic, Errors, Saturation
@Configuration
public class MetricsConfiguration {
    
    @Bean
    public MeterRegistryCustomizer<MeterRegistry> commonTags() {
        return registry -> registry.config()
                .commonTags("application", "order-service")
                .commonTags("version", getClass().getPackage().getImplementationVersion())
                .commonTags("environment", System.getProperty("spring.profiles.active", "unknown"));
    }
    
    @Bean
    @ConditionalOnProperty("app.metrics.detailed.enabled")
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}

// Automatyczne mierzenie czasu wykonania metod
@Service
public class OrderService {
    
    @Timed(name = "order.creation.time", description = "Time taken to create order")
    public Order createOrder(CreateOrderRequest request) {
        // Business logic
        return order;
    }
    
    @Counted(name = "order.validation.attempts", description = "Order validation attempts")
    public void validateOrder(Order order) {
        // Validation logic
    }
}
Pułapka: Nie monitoruj wszystkiego! Zbyt wiele metryk prowadzi do "alert fatigue" i problemów z wydajnością. Skup się na metrykach które rzeczywiście pomagają w diagnozowaniu problemów.
Czy Prometheus nadaje się do małych aplikacji?

Tak! Prometheus świetnie skaluje się w dół. Nawet dla pojedynczej aplikacji daje ogromną wartość przez wgląd w performance i możliwość wczesnego wykrywania problemów. Docker Compose setup to dosłownie 5 minut pracy.

Jak długo Prometheus przechowuje dane?

Domyślnie 15 dni lokalnie. Dla długoterminowego storage możesz skonfigurować remote storage (InfluxDB, Cortex) lub zwiększyć retention period. W praktyce większość alertów potrzebuje danych z ostatnich godzin/dni.

Jaka jest różnica między Histogram a Summary?

Histogram grupuje obserwacje w buckets i pozwala obliczać percentyle po stronie serwera. Summary oblicza percentyle po stronie klienta. Histogram jest bardziej elastyczny ale wymaga więcej zasobów.

Czy mogę używać Prometheus z aplikacjami nie-Java?

Oczywiście! Prometheus ma biblioteki klienckie dla Python, Go, .NET, Node.js i wielu innych. Istnieją też exporters dla baz danych, message queues, i większości popularnych technologii.

Jak radzić sobie z wysoką kardynalnością metryk?

Unikaj labelów z wartościami user ID, IP addresses, czy timestamps. Używaj labelów tylko dla kategorii (status codes, methods, regions). Jeśli potrzebujesz high-cardinality, rozważ sampling lub osobne narzędzie do detailed tracing.

Czy Prometheus wymaga dużo zasobów?

Dla typowej aplikacji Prometheus zużywa ~200MB RAM i minimalny CPU. Zużycie rośnie liniowo z liczbą metryk i częstotliwością scrapingu. 1GB RAM wystarcza dla większości środowisk developerskich i małych produkcji.

Jak testować alerty Prometheus?

Użyj 'amtool' do testowania konfiguracji Alertmanager. Możesz też tworzyć "test" metryki które celowo przekraczają thresholdy. W Prometheus UI możesz sprawdzać czy reguły alertów są poprawnie skonfigurowane.

Przydatne zasoby:

🚀 Zadanie dla Ciebie

Zbuduj kompletny monitoring stack: Stwórz aplikację Spring Boot z custom metrykami (liczba użytkowników online, czas procesowania zamówień). Skonfiguruj Prometheus + Grafana + Alertmanager używając Docker Compose. Stwórz dashboard z kluczowymi metrykami i skonfiguruj alert który wyśle email gdy error rate przekroczy 5%. Bonus: dodaj Node Exporter dla metryk systemu operacyjnego!

Czy już używasz Prometheus w swoich projektach? Jakie metryki uważasz za najważniejsze w monitoringu aplikacji? Podziel się swoimi doświadczeniami w komentarzach!

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry