
Observability Monitoring
- 315 installs
- 61 repo stars
- Updated June 13, 2026
- manutej/luxor-claude-marketplace
Set up observability with metrics, logs, traces, dashboards, and alerting to detect failures and diagnose performance issues in running production services.
About
Helps implement observability and monitoring stacks with metrics, logs, traces, dashboards, and alerting so teams can detect outages, trace requests, and maintain production service health.
- Metrics instrumentation
- Structured logging practices
- Distributed tracing setup
- Dashboard and SLO design
- Alerting and incident signals
Observability Monitoring by the numbers
- 315 all-time installs (skills.sh)
- +19 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #326 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill observability-monitoringAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 315 |
|---|---|
| repo stars | ★ 61 |
| Last updated | June 13, 2026 |
| Repository | manutej/luxor-claude-marketplace ↗ |
What it does
Set up observability with metrics, logs, traces, dashboards, and alerting to detect failures and diagnose performance issues in running production services.
Files
Observability & Monitoring
A comprehensive skill for implementing production-grade observability and monitoring using Prometheus, Grafana, and the wider cloud-native monitoring ecosystem. This skill covers metrics collection, time-series analysis, alerting, visualization, and operational excellence patterns.
When to Use This Skill
Use this skill when:
- Setting up monitoring for production systems and applications
- Implementing metrics collection and observability for microservices
- Creating dashboards and visualizations for system health monitoring
- Defining alerting rules and incident response automation
- Analyzing system performance and capacity using time-series data
- Implementing SLIs, SLOs, and SLAs for service reliability
- Debugging production issues using metrics and traces
- Building custom exporters for application-specific metrics
- Setting up federation for multi-cluster monitoring
- Migrating from legacy monitoring to cloud-native solutions
- Implementing cost monitoring and optimization tracking
- Creating real-time operational dashboards for DevOps teams
Core Concepts
The Four Pillars of Observability
Modern observability is built on four fundamental pillars:
1. Metrics: Numerical measurements of system behavior over time
- Counter: Monotonically increasing values (requests served, errors)
- Gauge: Point-in-time values that go up and down (memory usage, temperature)
- Histogram: Distribution of values (request duration buckets)
- Summary: Similar to histogram but calculates quantiles on client-side
2. Logs: Discrete events with contextual information
- Structured logging (JSON, key-value pairs)
- Centralized log aggregation (ELK, Loki)
- Correlation with metrics and traces
3. Traces: Request flow through distributed systems
- Span: Single unit of work with start/end time
- Trace: Collection of spans representing end-to-end request
- OpenTelemetry for distributed tracing
4. Events: Significant occurrences in system lifecycle
- Deployments, configuration changes
- Scaling events, incidents
- Business events and user actions
Prometheus Architecture
Prometheus is a pull-based monitoring system with key components:
Time-Series Database (TSDB)
- Stores metrics as time-series data
- Efficient compression and retention policies
- Local storage with optional remote storage
Scrape Targets
- Service discovery (Kubernetes, Consul, EC2, etc.)
- Static configuration
- Relabeling for flexible target selection
PromQL Query Engine
- Powerful query language for metrics analysis
- Aggregation, filtering, and mathematical operations
- Range vectors and instant vectors
Alertmanager
- Alert rule evaluation
- Grouping, silencing, and routing
- Integration with PagerDuty, Slack, email, webhooks
Exporters
- Bridge between Prometheus and systems
- Node exporter, cAdvisor, custom exporters
- Third-party exporters for databases, services
Metric Labels and Cardinality
Labels are key-value pairs attached to metrics:
http_requests_total{method="GET", endpoint="/api/users", status="200"}Label Best Practices:
- Use labels for dimensions you query/aggregate by
- Avoid high-cardinality labels (user IDs, timestamps)
- Keep label names consistent across metrics
- Use relabeling to normalize external labels
Cardinality Considerations:
- Each unique label combination = new time-series
- High cardinality = increased memory and storage
- Monitor cardinality with
prometheus_tsdb_symbol_table_size_bytes - Use recording rules to pre-aggregate high-cardinality metrics
Recording Rules
Pre-compute frequently-used or expensive queries:
groups:
- name: api_performance
interval: 30s
rules:
- record: api:http_request_duration_seconds:p99
expr: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
- record: api:http_requests:rate5m
expr: rate(http_requests_total[5m])Benefits:
- Faster dashboard loading
- Reduced query load on Prometheus
- Consistent metric naming conventions
- Enable complex aggregations
Service Level Objectives (SLOs)
Define and track reliability targets:
SLI (Service Level Indicator): Metric measuring service quality
- Availability: % of successful requests
- Latency: % of requests under threshold
- Throughput: Requests per second
SLO (Service Level Objective): Target for SLI
- 99.9% availability (43.8 minutes downtime/month)
- 95% of requests < 200ms
- 1000 RPS sustained
SLA (Service Level Agreement): Contract with consequences
- External commitments to customers
- Financial penalties for SLO violations
Error Budget: Acceptable failure rate
- Error budget = 100% - SLO
- 99.9% SLO = 0.1% error budget
- Use budget for innovation vs. reliability tradeoff
Prometheus Setup and Configuration
Basic Prometheus Configuration
# prometheus.yml
global:
scrape_interval: 15s # Default scrape interval
evaluation_interval: 15s # Alert rule evaluation interval
external_labels:
cluster: 'production'
region: 'us-west-2'
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
# Load rules
rule_files:
- 'rules/*.yml'
- 'alerts/*.yml'
# Scrape configurations
scrape_configs:
# Prometheus self-monitoring
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Node exporter for system metrics
- job_name: 'node'
static_configs:
- targets:
- 'node1:9100'
- 'node2:9100'
- 'node3:9100'
relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '([^:]+):.*'
replacement: '${1}'
# Application metrics
- job_name: 'api'
static_configs:
- targets: ['api-1:8080', 'api-2:8080', 'api-3:8080']
labels:
env: 'production'
tier: 'backend'Kubernetes Service Discovery
scrape_configs:
# Kubernetes API server
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
# Kubernetes pods with prometheus.io annotations
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Only scrape pods with prometheus.io/scrape: "true" annotation
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
# Use the port from prometheus.io/port annotation
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: (\d+)
target_label: __address__
replacement: ${1}:${2}
# Use the path from prometheus.io/path annotation
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
# Add namespace label
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
# Add pod name label
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
# Kubernetes services
- job_name: 'kubernetes-services'
kubernetes_sd_configs:
- role: service
metrics_path: /probe
params:
module: [http_2xx]
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
action: keep
regex: true
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: blackbox-exporter:9115
- source_labels: [__param_target]
target_label: instanceStorage and Retention
# Storage configuration
storage:
tsdb:
path: /prometheus/data
retention.time: 15d
retention.size: 50GB
# Remote write for long-term storage
remote_write:
- url: "https://prometheus-remote-storage.example.com/api/v1/write"
basic_auth:
username: prometheus
password_file: /etc/prometheus/remote_storage_password
queue_config:
capacity: 10000
max_shards: 50
max_samples_per_send: 5000
write_relabel_configs:
# Drop high-cardinality metrics
- source_labels: [__name__]
regex: 'container_network_.*'
action: drop
# Remote read for querying historical data
remote_read:
- url: "https://prometheus-remote-storage.example.com/api/v1/read"
basic_auth:
username: prometheus
password_file: /etc/prometheus/remote_storage_password
read_recent: truePromQL: The Prometheus Query Language
Instant Vectors and Selectors
# Basic metric selection
http_requests_total
# Filter by label
http_requests_total{job="api", status="200"}
# Regex matching
http_requests_total{status=~"2..|3.."}
# Negative matching
http_requests_total{status!="500"}
# Multiple label matchers
http_requests_total{job="api", method="GET", status=~"2.."}Range Vectors and Aggregations
# 5-minute range vector
http_requests_total[5m]
# Rate of increase per second
rate(http_requests_total[5m])
# Increase over time window
increase(http_requests_total[1h])
# Average over time
avg_over_time(cpu_usage[5m])
# Max/Min over time
max_over_time(response_time_seconds[10m])
min_over_time(response_time_seconds[10m])
# Standard deviation
stddev_over_time(response_time_seconds[5m])Aggregation Operators
# Sum across all instances
sum(rate(http_requests_total[5m]))
# Sum grouped by job
sum by (job) (rate(http_requests_total[5m]))
# Average grouped by multiple labels
avg by (job, instance) (cpu_usage)
# Count number of series
count(up == 1)
# Topk and bottomk
topk(5, rate(http_requests_total[5m]))
bottomk(3, node_memory_available_bytes)
# Quantile across instances
quantile(0.95, http_request_duration_seconds)Mathematical Operations
# Arithmetic operations
(node_memory_total_bytes - node_memory_available_bytes) / node_memory_total_bytes * 100
# Comparison operators
http_request_duration_seconds > 0.5
# Logical operators
up == 1 and rate(http_requests_total[5m]) > 100
# Vector matching
rate(http_requests_total[5m]) / on(instance) group_left rate(http_responses_total[5m])Advanced PromQL Patterns
# Request success rate
sum(rate(http_requests_total{status=~"2.."}[5m]))
/
sum(rate(http_requests_total[5m])) * 100
# Error rate percentage
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) * 100
# Latency percentiles (histogram)
histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
# Predict linear growth
predict_linear(node_filesystem_free_bytes[1h], 4 * 3600)
# Detect anomalies with standard deviation
abs(cpu_usage - avg_over_time(cpu_usage[1h]))
>
3 * stddev_over_time(cpu_usage[1h])
# Calculate saturation (RED method)
sum(rate(cpu_seconds_total{mode!="idle"}[5m])) by (instance)
/
count(cpu_seconds_total{mode="idle"}) by (instance)
# Burn rate for SLO
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
)
>
(14.4 * (1 - 0.999)) # For 99.9% SLOAlerting with Prometheus and Alertmanager
Alert Rule Definitions
# alerts/api_alerts.yml
groups:
- name: api_alerts
interval: 30s
rules:
# High error rate alert
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
> 0.05
for: 5m
labels:
severity: critical
team: backend
annotations:
summary: "High error rate on {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} on {{ $labels.service }}"
runbook_url: "https://runbooks.example.com/HighErrorRate"
# High latency alert
- alert: HighLatency
expr: |
histogram_quantile(0.99,
sum by (service, le) (rate(http_request_duration_seconds_bucket[5m]))
) > 1
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "High latency on {{ $labels.service }}"
description: "P99 latency is {{ $value }}s on {{ $labels.service }}"
# Service down alert
- alert: ServiceDown
expr: up{job="api"} == 0
for: 2m
labels:
severity: critical
team: sre
annotations:
summary: "Service {{ $labels.instance }} is down"
description: "{{ $labels.job }} on {{ $labels.instance }} has been down for more than 2 minutes"
# Disk space alert
- alert: DiskSpaceLow
expr: |
(node_filesystem_avail_bytes{mountpoint="/"}
/
node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10
for: 5m
labels:
severity: warning
team: sre
annotations:
summary: "Low disk space on {{ $labels.instance }}"
description: "Disk space is {{ $value | humanize }}% on {{ $labels.instance }}"
# Memory pressure alert
- alert: HighMemoryUsage
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 90
for: 10m
labels:
severity: warning
team: sre
annotations:
summary: "High memory usage on {{ $labels.instance }}"
description: "Memory usage is {{ $value | humanize }}% on {{ $labels.instance }}"
# CPU saturation alert
- alert: HighCPUUsage
expr: |
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 15m
labels:
severity: warning
team: sre
annotations:
summary: "High CPU usage on {{ $labels.instance }}"
description: "CPU usage is {{ $value | humanize }}% on {{ $labels.instance }}"Alertmanager Configuration
# alertmanager.yml
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
pagerduty_url: 'https://events.pagerduty.com/v2/enqueue'
# Templates for notifications
templates:
- '/etc/alertmanager/templates/*.tmpl'
# Route tree for alert distribution
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 10s
group_interval: 10s
repeat_interval: 12h
receiver: 'team-default'
routes:
# Critical alerts go to PagerDuty
- match:
severity: critical
receiver: 'pagerduty-critical'
continue: true
# Critical alerts also go to Slack
- match:
severity: critical
receiver: 'slack-critical'
group_wait: 0s
# Warning alerts to Slack only
- match:
severity: warning
receiver: 'slack-warnings'
# Team-specific routing
- match:
team: backend
receiver: 'team-backend'
- match:
team: frontend
receiver: 'team-frontend'
# Database alerts to DBA team
- match_re:
service: 'postgres|mysql|mongodb'
receiver: 'team-dba'
# Alert receivers/integrations
receivers:
- name: 'team-default'
slack_configs:
- channel: '#alerts'
title: 'Alert: {{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
send_resolved: true
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: 'YOUR_PAGERDUTY_SERVICE_KEY'
description: '{{ .GroupLabels.alertname }}: {{ .GroupLabels.service }}'
severity: '{{ .CommonLabels.severity }}'
- name: 'slack-critical'
slack_configs:
- channel: '#incidents'
title: 'CRITICAL: {{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.summary }}\n{{ .Annotations.description }}{{ end }}'
color: 'danger'
send_resolved: true
- name: 'slack-warnings'
slack_configs:
- channel: '#monitoring'
title: 'Warning: {{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'
color: 'warning'
send_resolved: true
- name: 'team-backend'
slack_configs:
- channel: '#team-backend'
send_resolved: true
email_configs:
- to: 'backend-team@example.com'
from: 'alertmanager@example.com'
smarthost: 'smtp.gmail.com:587'
auth_username: 'alertmanager@example.com'
auth_password_file: '/etc/alertmanager/email_password'
- name: 'team-frontend'
slack_configs:
- channel: '#team-frontend'
send_resolved: true
- name: 'team-dba'
slack_configs:
- channel: '#team-dba'
send_resolved: true
pagerduty_configs:
- service_key: 'DBA_PAGERDUTY_KEY'
# Inhibition rules (suppress alerts)
inhibit_rules:
# Inhibit warnings if critical alert is firing
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'instance']
# Don't alert on instance down if cluster is down
- source_match:
alertname: 'ClusterDown'
target_match_re:
alertname: 'InstanceDown|ServiceDown'
equal: ['cluster']Multi-Window Multi-Burn-Rate Alerts for SLOs
# SLO-based alerting using burn rate
groups:
- name: slo_alerts
interval: 30s
rules:
# Fast burn (1h window, 5m burn)
- alert: ErrorBudgetBurnFast
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) > (14.4 * (1 - 0.999))
and
(
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
) > (14.4 * (1 - 0.999))
for: 2m
labels:
severity: critical
slo: "99.9%"
annotations:
summary: "Fast error budget burn detected"
description: "Error rate is burning through 99.9% SLO budget 14.4x faster than normal"
# Slow burn (6h window, 30m burn)
- alert: ErrorBudgetBurnSlow
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[6h]))
/
sum(rate(http_requests_total[6h]))
) > (6 * (1 - 0.999))
and
(
sum(rate(http_requests_total{status=~"5.."}[30m]))
/
sum(rate(http_requests_total[30m]))
) > (6 * (1 - 0.999))
for: 15m
labels:
severity: warning
slo: "99.9%"
annotations:
summary: "Slow error budget burn detected"
description: "Error rate is burning through 99.9% SLO budget 6x faster than normal"Grafana Dashboards and Visualization
Dashboard JSON Structure
{
"dashboard": {
"title": "API Performance Dashboard",
"tags": ["api", "performance", "production"],
"timezone": "browser",
"editable": true,
"graphTooltip": 1,
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m"],
"time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "7d"]
},
"templating": {
"list": [
{
"name": "cluster",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(up, cluster)",
"refresh": 1,
"multi": false,
"includeAll": false
},
{
"name": "service",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(up{cluster=\"$cluster\"}, service)",
"refresh": 1,
"multi": true,
"includeAll": true
},
{
"name": "interval",
"type": "interval",
"query": "1m,5m,10m,30m,1h",
"auto": true,
"auto_count": 30,
"auto_min": "10s"
}
]
},
"panels": [
{
"id": 1,
"title": "Request Rate",
"type": "graph",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"targets": [
{
"expr": "sum(rate(http_requests_total{service=~\"$service\"}[$interval])) by (service)",
"legendFormat": "{{ service }}",
"refId": "A"
}
],
"yaxes": [
{"format": "reqps", "label": "Requests/sec"},
{"format": "short"}
],
"legend": {
"show": true,
"values": true,
"current": true,
"avg": true,
"max": true
}
},
{
"id": 2,
"title": "Error Rate",
"type": "graph",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [
{
"expr": "sum(rate(http_requests_total{service=~\"$service\",status=~\"5..\"}[$interval])) by (service) / sum(rate(http_requests_total{service=~\"$service\"}[$interval])) by (service) * 100",
"legendFormat": "{{ service }} error %",
"refId": "A"
}
],
"yaxes": [
{"format": "percent", "label": "Error Rate"},
{"format": "short"}
],
"alert": {
"conditions": [
{
"evaluator": {"params": [5], "type": "gt"},
"operator": {"type": "and"},
"query": {"params": ["A", "5m", "now"]},
"reducer": {"params": [], "type": "avg"},
"type": "query"
}
],
"executionErrorState": "alerting",
"frequency": "1m",
"handler": 1,
"name": "High Error Rate",
"noDataState": "no_data",
"notifications": []
}
},
{
"id": 3,
"title": "Latency Percentiles",
"type": "graph",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 8},
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{service=~\"$service\"}[$interval])) by (service, le))",
"legendFormat": "{{ service }} p99",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{service=~\"$service\"}[$interval])) by (service, le))",
"legendFormat": "{{ service }} p95",
"refId": "B"
},
{
"expr": "histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket{service=~\"$service\"}[$interval])) by (service, le))",
"legendFormat": "{{ service }} p50",
"refId": "C"
}
],
"yaxes": [
{"format": "s", "label": "Duration"},
{"format": "short"}
]
}
]
}
}RED Method Dashboard
The RED method focuses on Request rate, Error rate, and Duration:
{
"panels": [
{
"title": "Request Rate (per service)",
"targets": [
{
"expr": "sum(rate(http_requests_total[$__rate_interval])) by (service)"
}
]
},
{
"title": "Error Rate % (per service)",
"targets": [
{
"expr": "sum(rate(http_requests_total{status=~\"5..\"}[$__rate_interval])) by (service) / sum(rate(http_requests_total[$__rate_interval])) by (service) * 100"
}
]
},
{
"title": "Duration p99 (per service)",
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[$__rate_interval])) by (service, le))"
}
]
}
]
}USE Method Dashboard
The USE method monitors Utilization, Saturation, and Errors:
{
"panels": [
{
"title": "CPU Utilization %",
"targets": [
{
"expr": "100 - (avg by (instance) (irate(node_cpu_seconds_total{mode=\"idle\"}[$__rate_interval])) * 100)"
}
]
},
{
"title": "CPU Saturation (Load Average)",
"targets": [
{
"expr": "node_load1"
}
]
},
{
"title": "Memory Utilization %",
"targets": [
{
"expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100"
}
]
},
{
"title": "Disk I/O Utilization %",
"targets": [
{
"expr": "rate(node_disk_io_time_seconds_total[$__rate_interval]) * 100"
}
]
},
{
"title": "Network Errors",
"targets": [
{
"expr": "rate(node_network_receive_errs_total[$__rate_interval]) + rate(node_network_transmit_errs_total[$__rate_interval])"
}
]
}
]
}Exporters and Metric Collection
Node Exporter for System Metrics
# Install node_exporter
wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz
tar xvfz node_exporter-1.6.1.linux-amd64.tar.gz
cd node_exporter-1.6.1.linux-amd64
./node_exporter --web.listen-address=":9100" \
--collector.filesystem.mount-points-exclude="^/(dev|proc|sys|var/lib/docker/.+)($|/)" \
--collector.netclass.ignored-devices="^(veth.*|br.*|docker.*|lo)$"Key Metrics from Node Exporter:
node_cpu_seconds_total: CPU usage by modenode_memory_MemTotal_bytes,node_memory_MemAvailable_bytes: Memorynode_disk_io_time_seconds_total: Disk I/Onode_network_receive_bytes_total,node_network_transmit_bytes_total: Networknode_filesystem_size_bytes,node_filesystem_avail_bytes: Disk space
Custom Application Exporter (Python)
# app_exporter.py
from prometheus_client import start_http_server, Counter, Gauge, Histogram, Summary
import time
import random
# Define metrics
REQUEST_COUNT = Counter(
'app_requests_total',
'Total app requests',
['method', 'endpoint', 'status']
)
REQUEST_DURATION = Histogram(
'app_request_duration_seconds',
'Request duration in seconds',
['method', 'endpoint'],
buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 2.5, 5.0, 10.0]
)
ACTIVE_USERS = Gauge(
'app_active_users',
'Number of active users'
)
QUEUE_SIZE = Gauge(
'app_queue_size',
'Current queue size',
['queue_name']
)
DATABASE_CONNECTIONS = Gauge(
'app_database_connections',
'Number of database connections',
['pool', 'state']
)
CACHE_HITS = Counter(
'app_cache_hits_total',
'Total cache hits',
['cache_name']
)
CACHE_MISSES = Counter(
'app_cache_misses_total',
'Total cache misses',
['cache_name']
)
def simulate_metrics():
"""Simulate application metrics"""
while True:
# Simulate requests
method = random.choice(['GET', 'POST', 'PUT', 'DELETE'])
endpoint = random.choice(['/api/users', '/api/products', '/api/orders'])
status = random.choice(['200', '200', '200', '400', '500'])
REQUEST_COUNT.labels(method=method, endpoint=endpoint, status=status).inc()
# Simulate request duration
duration = random.uniform(0.01, 2.0)
REQUEST_DURATION.labels(method=method, endpoint=endpoint).observe(duration)
# Update gauges
ACTIVE_USERS.set(random.randint(100, 1000))
QUEUE_SIZE.labels(queue_name='jobs').set(random.randint(0, 50))
QUEUE_SIZE.labels(queue_name='emails').set(random.randint(0, 20))
# Database connection pool
DATABASE_CONNECTIONS.labels(pool='main', state='active').set(random.randint(5, 20))
DATABASE_CONNECTIONS.labels(pool='main', state='idle').set(random.randint(10, 30))
# Cache metrics
if random.random() > 0.3:
CACHE_HITS.labels(cache_name='redis').inc()
else:
CACHE_MISSES.labels(cache_name='redis').inc()
time.sleep(1)
if __name__ == '__main__':
# Start metrics server on port 8000
start_http_server(8000)
print("Metrics server started on port 8000")
simulate_metrics()Custom Exporter (Go)
package main
import (
"log"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
requestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "app_requests_total",
Help: "Total number of requests",
},
[]string{"method", "endpoint", "status"},
)
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "app_request_duration_seconds",
Help: "Request duration in seconds",
Buckets: prometheus.ExponentialBuckets(0.01, 2, 10),
},
[]string{"method", "endpoint"},
)
activeUsers = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "app_active_users",
Help: "Number of active users",
},
)
databaseConnections = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_database_connections",
Help: "Number of database connections",
},
[]string{"pool", "state"},
)
)
func init() {
prometheus.MustRegister(requestsTotal)
prometheus.MustRegister(requestDuration)
prometheus.MustRegister(activeUsers)
prometheus.MustRegister(databaseConnections)
}
func simulateMetrics() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
// Simulate requests
methods := []string{"GET", "POST", "PUT", "DELETE"}
endpoints := []string{"/api/users", "/api/products", "/api/orders"}
statuses := []string{"200", "200", "200", "400", "500"}
method := methods[rand.Intn(len(methods))]
endpoint := endpoints[rand.Intn(len(endpoints))]
status := statuses[rand.Intn(len(statuses))]
requestsTotal.WithLabelValues(method, endpoint, status).Inc()
requestDuration.WithLabelValues(method, endpoint).Observe(rand.Float64() * 2)
// Update gauges
activeUsers.Set(float64(rand.Intn(900) + 100))
databaseConnections.WithLabelValues("main", "active").Set(float64(rand.Intn(15) + 5))
databaseConnections.WithLabelValues("main", "idle").Set(float64(rand.Intn(20) + 10))
}
}
func main() {
go simulateMetrics()
http.Handle("/metrics", promhttp.Handler())
log.Println("Starting metrics server on :8000")
log.Fatal(http.ListenAndServe(":8000", nil))
}PostgreSQL Exporter
# docker-compose.yml for postgres_exporter
version: '3.8'
services:
postgres-exporter:
image: prometheuscommunity/postgres-exporter
environment:
DATA_SOURCE_NAME: "postgresql://user:password@postgres:5432/dbname?sslmode=disable"
ports:
- "9187:9187"
command:
- '--collector.stat_statements'
- '--collector.stat_database'
- '--collector.replication'Key PostgreSQL Metrics:
pg_up: Database reachabilitypg_stat_database_tup_returned: Rows readpg_stat_database_tup_inserted: Rows insertedpg_stat_database_deadlocks: Deadlock countpg_stat_replication_lag: Replication lag in secondspg_locks_count: Active locks
Blackbox Exporter for Probing
# blackbox.yml
modules:
http_2xx:
prober: http
timeout: 5s
http:
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
valid_status_codes: [200]
method: GET
preferred_ip_protocol: "ip4"
http_post_json:
prober: http
http:
method: POST
headers:
Content-Type: application/json
body: '{"key":"value"}'
valid_status_codes: [200, 201]
tcp_connect:
prober: tcp
timeout: 5s
icmp:
prober: icmp
timeout: 5s
icmp:
preferred_ip_protocol: "ip4"# Prometheus config for blackbox exporter
scrape_configs:
- job_name: 'blackbox-http'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://example.com
- https://api.example.com/health
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115Best Practices
Metric Naming Conventions
Follow Prometheus naming best practices:
# Format: <namespace>_<subsystem>_<metric>_<unit>
# Good examples
http_requests_total # Counter
http_request_duration_seconds # Histogram
database_connections_active # Gauge
cache_hits_total # Counter
memory_usage_bytes # Gauge
# Include unit suffixes
_seconds, _bytes, _total, _ratio, _percentage
# Avoid
RequestCount # Use snake_case
http_requests # Missing _total for counter
request_time # Missing unit (should be _seconds)Label Guidelines
# Good: Low cardinality labels
http_requests_total{method="GET", endpoint="/api/users", status="200"}
# Bad: High cardinality labels (avoid)
http_requests_total{user_id="12345", session_id="abc-def-ghi"}
# Good: Use bounded label values
http_requests_total{status_class="2xx"}
# Bad: Unbounded label values
http_requests_total{response_size="1234567"}Recording Rule Patterns
groups:
- name: performance_rules
interval: 30s
rules:
# Pre-aggregate expensive queries
- record: job:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (job)
# Namespace aggregations
- record: namespace:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (namespace)
# SLI calculations
- record: job:http_requests_success:rate5m
expr: sum(rate(http_requests_total{status=~"2.."}[5m])) by (job)
- record: job:http_requests_error_rate:ratio
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)Alert Design Principles
1. Alert on symptoms, not causes: Alert on user-facing issues 2. Make alerts actionable: Include runbook links 3. Use appropriate severity levels: Critical, warning, info 4. Set proper thresholds: Based on historical data 5. Include context in annotations: Help on-call engineers 6. Group related alerts: Reduce alert fatigue 7. Use inhibition rules: Suppress redundant alerts 8. Test alert rules: Verify they fire when expected
Dashboard Best Practices
1. One dashboard per audience: SRE, developers, business 2. Use consistent time ranges: Make comparisons easier 3. Include SLI/SLO metrics: Show business impact 4. Add annotations for deploys: Correlate changes with metrics 5. Use template variables: Make dashboards reusable 6. Show trends and aggregates: Not just raw metrics 7. Include links to runbooks: Enable quick response 8. Use appropriate visualizations: Graphs, gauges, tables
High Availability Setup
# Prometheus HA with Thanos
# Deploy multiple Prometheus instances with same config
# Use Thanos to deduplicate and provide global view
# prometheus-1.yml
global:
external_labels:
cluster: 'prod'
replica: '1'
# prometheus-2.yml
global:
external_labels:
cluster: 'prod'
replica: '2'
# Thanos sidecar configuration
# Uploads blocks to object storage
# Provides StoreAPI for queryingCapacity Planning Queries
# Disk space exhaustion prediction
predict_linear(node_filesystem_free_bytes[1h], 4 * 3600) < 0
# Memory growth trend
predict_linear(node_memory_MemAvailable_bytes[1h], 24 * 3600)
# Request rate growth
predict_linear(sum(rate(http_requests_total[1h]))[24h:1h], 7 * 24 * 3600)
# Storage capacity planning
prometheus_tsdb_storage_blocks_bytes / (30 * 24 * 3600)Advanced Patterns
Federation for Multi-Cluster Monitoring
# Global Prometheus federating from cluster Prometheus instances
scrape_configs:
- job_name: 'federate'
scrape_interval: 15s
honor_labels: true
metrics_path: '/federate'
params:
'match[]':
- '{job="prometheus"}'
- '{__name__=~"job:.*"}' # All recording rules
static_configs:
- targets:
- 'prometheus-us-west:9090'
- 'prometheus-us-east:9090'
- 'prometheus-eu-central:9090'Cost Monitoring Pattern
# Track cloud costs with custom metrics
groups:
- name: cost_tracking
rules:
- record: cloud:cost:hourly_rate
expr: |
(
sum(kube_pod_container_resource_requests{resource="cpu"}) * 0.03 # CPU cost/hour
+
sum(kube_pod_container_resource_requests{resource="memory"} / 1024 / 1024 / 1024) * 0.005 # Memory cost/hour
)
- record: cloud:cost:monthly_estimate
expr: cloud:cost:hourly_rate * 730 # Hours in average monthCustom SLO Implementation
# SLO: 99.9% availability for API
groups:
- name: api_slo
interval: 30s
rules:
# Success rate SLI
- record: api:sli:success_rate
expr: |
sum(rate(http_requests_total{job="api",status=~"2.."}[5m]))
/
sum(rate(http_requests_total{job="api"}[5m]))
# Error budget remaining (30 days)
- record: api:error_budget:remaining
expr: |
1 - (
(1 - api:sli:success_rate)
/
(1 - 0.999)
)
# Latency SLI (p99 < 500ms)
- record: api:sli:latency_success_rate
expr: |
(
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{job="api"}[5m])) by (le)
) < 0.5
)Examples Summary
This skill includes 20+ comprehensive examples covering:
1. Prometheus configuration (basic, Kubernetes SD, storage) 2. PromQL queries (instant vectors, range vectors, aggregations) 3. Mathematical operations and advanced patterns 4. Alert rule definitions (error rate, latency, resource usage) 5. Alertmanager configuration (routing, receivers, inhibition) 6. Multi-window multi-burn-rate SLO alerts 7. Grafana dashboard JSON (full dashboard, RED method, USE method) 8. Custom exporters (Python, Go) 9. Third-party exporters (PostgreSQL, Blackbox) 10. Recording rules for performance 11. Federation for multi-cluster monitoring 12. Cost monitoring and SLO implementation 13. High availability patterns 14. Capacity planning queries
---
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Observability, Monitoring, SRE, DevOps Compatible With: Prometheus, Grafana, Alertmanager, OpenTelemetry, Kubernetes
Observability & Monitoring Examples
This document provides comprehensive, production-ready examples for implementing monitoring and observability using Prometheus, Grafana, and related tools.
Table of Contents
1. Prometheus Configuration Examples 2. PromQL Query Examples 3. Alert Rule Examples 4. Grafana Dashboard Examples 5. Custom Exporter Examples 6. Recording Rule Examples 7. Service Discovery Examples 8. SLO Monitoring Examples 9. Multi-Cluster Federation Examples 10. Advanced Patterns
---
Prometheus Configuration Examples
Example 1: Production Prometheus Configuration
Complete production-ready Prometheus configuration with multiple scrape targets and best practices.
# prometheus.yml
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
external_labels:
cluster: 'production-us-west-2'
environment: 'production'
region: 'us-west-2'
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
- 'alertmanager-1:9093'
- 'alertmanager-2:9093'
timeout: 10s
api_version: v2
# Rule files
rule_files:
- '/etc/prometheus/rules/*.yml'
- '/etc/prometheus/alerts/*.yml'
# Storage configuration
storage:
tsdb:
path: /prometheus/data
retention.time: 30d
retention.size: 100GB
# Remote write for long-term storage
remote_write:
- url: "https://thanos-receive.example.com/api/v1/receive"
queue_config:
capacity: 10000
max_shards: 50
min_shards: 1
max_samples_per_send: 5000
batch_send_deadline: 5s
min_backoff: 30ms
max_backoff: 100ms
write_relabel_configs:
# Keep only production metrics
- source_labels: [environment]
regex: 'production'
action: keep
# Drop verbose debug metrics
- source_labels: [__name__]
regex: 'debug_.*'
action: drop
# Scrape configurations
scrape_configs:
# Prometheus self-monitoring
- job_name: 'prometheus'
honor_labels: true
static_configs:
- targets:
- 'localhost:9090'
labels:
service: 'prometheus'
# Node exporter for system metrics
- job_name: 'node'
static_configs:
- targets:
- 'node-1.prod.example.com:9100'
- 'node-2.prod.example.com:9100'
- 'node-3.prod.example.com:9100'
labels:
datacenter: 'dc1'
tier: 'infrastructure'
relabel_configs:
# Extract hostname from FQDN
- source_labels: [__address__]
regex: '([^.]+)\..*'
target_label: instance
replacement: '${1}'
# Add custom labels
- target_label: job_type
replacement: 'system_metrics'
# API service
- job_name: 'api'
static_configs:
- targets:
- 'api-1.prod.example.com:8080'
- 'api-2.prod.example.com:8080'
- 'api-3.prod.example.com:8080'
labels:
environment: 'production'
tier: 'backend'
service: 'api'
version: 'v2.1.0'
metric_relabel_configs:
# Drop high-cardinality metrics
- source_labels: [__name__]
regex: 'http_request_duration_microseconds_.*'
action: drop
# Normalize status codes to classes
- source_labels: [status]
regex: '([0-9])[0-9]{2}'
target_label: status_class
replacement: '${1}xx'
# PostgreSQL exporter
- job_name: 'postgres'
static_configs:
- targets:
- 'postgres-exporter-1:9187'
- 'postgres-exporter-2:9187'
labels:
database_type: 'postgresql'
tier: 'database'
# Redis exporter
- job_name: 'redis'
static_configs:
- targets:
- 'redis-exporter:9121'
labels:
cache_type: 'redis'
tier: 'cache'
# Blackbox exporter for endpoint probing
- job_name: 'blackbox-http'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://api.example.com/health
- https://www.example.com
- https://admin.example.com
labels:
probe_type: 'http'
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
# Custom application metrics
- job_name: 'webapp'
static_configs:
- targets:
- 'webapp-1:9090'
- 'webapp-2:9090'
- 'webapp-3:9090'
labels:
app: 'webapp'
tier: 'frontend'
scrape_interval: 10s # Override global for critical serviceExample 2: Kubernetes Service Discovery
Complete Kubernetes service discovery configuration for pod, service, and node monitoring.
scrape_configs:
# Kubernetes API server
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
# Kubernetes nodes (kubelet)
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
# Kubernetes pods
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
# Only scrape pods with prometheus.io/scrape: "true"
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
# Use custom scrape port if defined
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port, __meta_kubernetes_pod_ip]
action: replace
regex: (\d+);([^:]+)(?::\d+)?
replacement: $2:$1
target_label: __address__
# Use custom metrics path if defined
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
# Add namespace as label
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
# Add pod name as label
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
# Add pod labels as metric labels
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
# Add pod phase as label
- source_labels: [__meta_kubernetes_pod_phase]
action: replace
target_label: kubernetes_pod_phase
# Kubernetes services
- job_name: 'kubernetes-services'
kubernetes_sd_configs:
- role: service
metrics_path: /probe
params:
module: [http_2xx]
relabel_configs:
# Only probe services with prometheus.io/probe: "true"
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_probe]
action: keep
regex: true
- source_labels: [__address__]
target_label: __param_target
- target_label: __address__
replacement: blackbox-exporter:9115
- source_labels: [__param_target]
target_label: instance
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
target_label: kubernetes_service_name
# Kubernetes ingresses
- job_name: 'kubernetes-ingresses'
kubernetes_sd_configs:
- role: ingress
metrics_path: /probe
params:
module: [http_2xx]
relabel_configs:
- source_labels: [__meta_kubernetes_ingress_scheme, __address__, __meta_kubernetes_ingress_path]
regex: (.+);(.+);(.+)
replacement: ${1}://${2}${3}
target_label: __param_target
- target_label: __address__
replacement: blackbox-exporter:9115
- source_labels: [__param_target]
target_label: instance
- action: labelmap
regex: __meta_kubernetes_ingress_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_ingress_name]
target_label: kubernetes_ingress_nameExample 3: AWS EC2 Service Discovery
Automatically discover and monitor EC2 instances using AWS service discovery.
scrape_configs:
- job_name: 'ec2-nodes'
ec2_sd_configs:
- region: us-west-2
port: 9100
filters:
- name: tag:Environment
values:
- production
- name: tag:Monitoring
values:
- enabled
- name: instance-state-name
values:
- running
relabel_configs:
# Use private IP
- source_labels: [__meta_ec2_private_ip]
target_label: __address__
replacement: '${1}:9100'
# Add instance ID
- source_labels: [__meta_ec2_instance_id]
target_label: instance_id
# Add instance type
- source_labels: [__meta_ec2_instance_type]
target_label: instance_type
# Add availability zone
- source_labels: [__meta_ec2_availability_zone]
target_label: availability_zone
# Add EC2 tags as labels
- source_labels: [__meta_ec2_tag_Name]
target_label: instance_name
- source_labels: [__meta_ec2_tag_Environment]
target_label: environment
- source_labels: [__meta_ec2_tag_Team]
target_label: team
- source_labels: [__meta_ec2_tag_Service]
target_label: service---
PromQL Query Examples
Example 4: Request Rate and Throughput
# Requests per second (RPS) - instant rate
rate(http_requests_total[5m])
# Total RPS across all instances
sum(rate(http_requests_total[5m]))
# RPS grouped by service
sum(rate(http_requests_total[5m])) by (service)
# RPS grouped by method and endpoint
sum(rate(http_requests_total[5m])) by (method, endpoint)
# Top 5 endpoints by request volume
topk(5, sum(rate(http_requests_total[5m])) by (endpoint))
# Total requests in the last hour
sum(increase(http_requests_total[1h]))
# Average RPS over the last 24 hours
avg_over_time(sum(rate(http_requests_total[5m]))[24h:5m])
# Predict RPS in 4 hours based on 1-hour trend
predict_linear(sum(rate(http_requests_total[5m]))[1h:], 4 * 3600)Example 5: Error Rate Analysis
# Error rate (5xx errors) as percentage
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) * 100
# Error rate per service
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service) * 100
# Success rate (2xx responses)
sum(rate(http_requests_total{status=~"2.."}[5m]))
/
sum(rate(http_requests_total[5m])) * 100
# 4xx client error rate
sum(rate(http_requests_total{status=~"4.."}[5m]))
/
sum(rate(http_requests_total[5m])) * 100
# Errors grouped by status code
sum(rate(http_requests_total{status=~"5.."}[5m])) by (status)
# Services with error rate > 1%
(
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
) > 0.01
# Error spike detection (current vs 1 hour ago)
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total{status=~"5.."}[5m] offset 1h))
> 2Example 6: Latency Percentiles and Histograms
# P50 (median) latency
histogram_quantile(0.50,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
# P95 latency
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
# P99 latency
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
# P99 latency per service
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le)
)
# P99 latency per endpoint
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (endpoint, le)
)
# Average latency (from histogram)
sum(rate(http_request_duration_seconds_sum[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
# Latency standard deviation
stddev_over_time(
(
sum(rate(http_request_duration_seconds_sum[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
)[10m:1m]
)
# Requests exceeding 1 second latency
sum(rate(http_request_duration_seconds_bucket{le="1.0"}[5m]))
/
sum(rate(http_request_duration_seconds_bucket{le="+Inf"}[5m]))Example 7: Resource Utilization
# CPU usage percentage
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# CPU usage by core
sum by (cpu) (irate(node_cpu_seconds_total{mode!="idle"}[5m])) * 100
# Memory usage percentage
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# Memory usage in GB
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / 1024 / 1024 / 1024
# Disk usage percentage
(
node_filesystem_size_bytes{mountpoint="/"} -
node_filesystem_avail_bytes{mountpoint="/"}
)
/
node_filesystem_size_bytes{mountpoint="/"} * 100
# Disk I/O utilization
rate(node_disk_io_time_seconds_total[5m]) * 100
# Network receive throughput (MB/s)
rate(node_network_receive_bytes_total[5m]) / 1024 / 1024
# Network transmit throughput (MB/s)
rate(node_network_transmit_bytes_total[5m]) / 1024 / 1024
# Load average per CPU
node_load1 / count(node_cpu_seconds_total{mode="idle"}) by (instance)
# Container memory usage
sum(container_memory_usage_bytes{container!=""}) by (pod, namespace)
# Container CPU usage
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, namespace)---
Alert Rule Examples
Example 8: Comprehensive Application Alerts
# alerts/application_alerts.yml
groups:
- name: application_health
interval: 30s
rules:
# Service down
- alert: ServiceDown
expr: up{job="api"} == 0
for: 2m
labels:
severity: critical
team: backend
category: availability
annotations:
summary: "Service {{ $labels.instance }} is down"
description: "{{ $labels.job }} on {{ $labels.instance }} has been unreachable for more than 2 minutes."
runbook_url: "https://runbooks.example.com/ServiceDown"
dashboard_url: "https://grafana.example.com/d/service-health"
# High error rate
- alert: HighErrorRate
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
) > 0.05
for: 5m
labels:
severity: critical
team: backend
category: errors
annotations:
summary: "High error rate on {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} on {{ $labels.service }} (threshold: 5%)"
runbook_url: "https://runbooks.example.com/HighErrorRate"
# Elevated error rate (warning)
- alert: ElevatedErrorRate
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
) > 0.01
for: 10m
labels:
severity: warning
team: backend
category: errors
annotations:
summary: "Elevated error rate on {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} on {{ $labels.service }} (threshold: 1%)"
# High latency (P99)
- alert: HighLatencyP99
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le)
) > 1.0
for: 10m
labels:
severity: warning
team: backend
category: performance
annotations:
summary: "High P99 latency on {{ $labels.service }}"
description: "P99 latency is {{ $value }}s on {{ $labels.service }} (threshold: 1s)"
runbook_url: "https://runbooks.example.com/HighLatency"
# Critical latency (P99)
- alert: CriticalLatencyP99
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le)
) > 5.0
for: 5m
labels:
severity: critical
team: backend
category: performance
annotations:
summary: "Critical P99 latency on {{ $labels.service }}"
description: "P99 latency is {{ $value }}s on {{ $labels.service }} (threshold: 5s)"
# Low request volume
- alert: LowRequestVolume
expr: |
sum(rate(http_requests_total[5m])) by (service) < 10
for: 15m
labels:
severity: warning
team: backend
category: traffic
annotations:
summary: "Low request volume on {{ $labels.service }}"
description: "Request rate is {{ $value | humanize }} req/s on {{ $labels.service }} (expected > 10 req/s)"
# Traffic spike
- alert: TrafficSpike
expr: |
sum(rate(http_requests_total[5m])) by (service)
/
avg_over_time(sum(rate(http_requests_total[5m])) by (service)[1h:5m])
> 3
for: 5m
labels:
severity: warning
team: backend
category: traffic
annotations:
summary: "Traffic spike detected on {{ $labels.service }}"
description: "Current traffic is {{ $value }}x normal levels on {{ $labels.service }}"Example 9: Infrastructure Alerts
# alerts/infrastructure_alerts.yml
groups:
- name: infrastructure_health
interval: 30s
rules:
# High CPU usage
- alert: HighCPUUsage
expr: |
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 15m
labels:
severity: warning
team: sre
category: resources
annotations:
summary: "High CPU usage on {{ $labels.instance }}"
description: "CPU usage is {{ $value | humanize }}% on {{ $labels.instance }} (threshold: 80%)"
runbook_url: "https://runbooks.example.com/HighCPU"
# Critical CPU usage
- alert: CriticalCPUUsage
expr: |
100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 95
for: 5m
labels:
severity: critical
team: sre
category: resources
annotations:
summary: "Critical CPU usage on {{ $labels.instance }}"
description: "CPU usage is {{ $value | humanize }}% on {{ $labels.instance }} (threshold: 95%)"
# High memory usage
- alert: HighMemoryUsage
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 85
for: 10m
labels:
severity: warning
team: sre
category: resources
annotations:
summary: "High memory usage on {{ $labels.instance }}"
description: "Memory usage is {{ $value | humanize }}% on {{ $labels.instance }} (threshold: 85%)"
# Critical memory usage
- alert: CriticalMemoryUsage
expr: |
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 > 95
for: 5m
labels:
severity: critical
team: sre
category: resources
annotations:
summary: "Critical memory usage on {{ $labels.instance }}"
description: "Memory usage is {{ $value | humanize }}% on {{ $labels.instance }} (threshold: 95%)"
# Low disk space
- alert: LowDiskSpace
expr: |
(
node_filesystem_avail_bytes{mountpoint="/", fstype!="rootfs"}
/
node_filesystem_size_bytes{mountpoint="/", fstype!="rootfs"}
) * 100 < 15
for: 5m
labels:
severity: warning
team: sre
category: storage
annotations:
summary: "Low disk space on {{ $labels.instance }}"
description: "Disk space available is {{ $value | humanize }}% on {{ $labels.instance }}:{{ $labels.mountpoint }}"
# Critical disk space
- alert: CriticalDiskSpace
expr: |
(
node_filesystem_avail_bytes{mountpoint="/", fstype!="rootfs"}
/
node_filesystem_size_bytes{mountpoint="/", fstype!="rootfs"}
) * 100 < 5
for: 2m
labels:
severity: critical
team: sre
category: storage
annotations:
summary: "Critical disk space on {{ $labels.instance }}"
description: "Disk space available is {{ $value | humanize }}% on {{ $labels.instance }}:{{ $labels.mountpoint }}"
# Disk will fill in 4 hours
- alert: DiskWillFillSoon
expr: |
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[1h], 4 * 3600) < 0
for: 5m
labels:
severity: warning
team: sre
category: capacity
annotations:
summary: "Disk predicted to fill in 4 hours on {{ $labels.instance }}"
description: "Based on current trend, disk will be full in approximately 4 hours on {{ $labels.instance }}"
# High disk I/O
- alert: HighDiskIO
expr: |
rate(node_disk_io_time_seconds_total[5m]) * 100 > 80
for: 10m
labels:
severity: warning
team: sre
category: performance
annotations:
summary: "High disk I/O on {{ $labels.instance }}"
description: "Disk I/O utilization is {{ $value | humanize }}% on {{ $labels.instance }}:{{ $labels.device }}"
# Network errors
- alert: NetworkErrors
expr: |
rate(node_network_receive_errs_total[5m]) + rate(node_network_transmit_errs_total[5m]) > 10
for: 5m
labels:
severity: warning
team: sre
category: network
annotations:
summary: "Network errors on {{ $labels.instance }}"
description: "Network error rate is {{ $value | humanize }} errors/s on {{ $labels.instance }}:{{ $labels.device }}"
# High load average
- alert: HighLoadAverage
expr: |
node_load15 / count(node_cpu_seconds_total{mode="idle"}) by (instance) > 2
for: 15m
labels:
severity: warning
team: sre
category: performance
annotations:
summary: "High load average on {{ $labels.instance }}"
description: "Load average per CPU is {{ $value | humanize }} on {{ $labels.instance }}"Example 10: Database Alerts
# alerts/database_alerts.yml
groups:
- name: database_health
interval: 30s
rules:
# PostgreSQL down
- alert: PostgreSQLDown
expr: pg_up == 0
for: 1m
labels:
severity: critical
team: dba
category: availability
annotations:
summary: "PostgreSQL database is down on {{ $labels.instance }}"
description: "PostgreSQL exporter cannot connect to database on {{ $labels.instance }}"
runbook_url: "https://runbooks.example.com/PostgreSQLDown"
# Too many connections
- alert: PostgreSQLTooManyConnections
expr: |
sum(pg_stat_activity_count) by (instance)
/
pg_settings_max_connections * 100 > 80
for: 5m
labels:
severity: warning
team: dba
category: resources
annotations:
summary: "PostgreSQL connection pool near capacity on {{ $labels.instance }}"
description: "Connection usage is {{ $value | humanize }}% on {{ $labels.instance }}"
# Replication lag
- alert: PostgreSQLReplicationLag
expr: pg_replication_lag > 30
for: 5m
labels:
severity: warning
team: dba
category: replication
annotations:
summary: "PostgreSQL replication lag on {{ $labels.instance }}"
description: "Replication lag is {{ $value | humanize }} seconds on {{ $labels.instance }}"
# High transaction rate
- alert: PostgreSQLHighTransactionRate
expr: |
rate(pg_stat_database_xact_commit[5m]) + rate(pg_stat_database_xact_rollback[5m]) > 10000
for: 10m
labels:
severity: warning
team: dba
category: performance
annotations:
summary: "High transaction rate on {{ $labels.instance }}"
description: "Transaction rate is {{ $value | humanize }} tx/s on {{ $labels.instance }}"
# Deadlocks detected
- alert: PostgreSQLDeadlocks
expr: rate(pg_stat_database_deadlocks[5m]) > 0
for: 5m
labels:
severity: warning
team: dba
category: locks
annotations:
summary: "Deadlocks detected on {{ $labels.instance }}"
description: "Deadlock rate is {{ $value | humanize }}/s on {{ $labels.instance }}"
# Slow queries
- alert: PostgreSQLSlowQueries
expr: |
pg_stat_activity_max_tx_duration > 300
for: 5m
labels:
severity: warning
team: dba
category: performance
annotations:
summary: "Slow queries detected on {{ $labels.instance }}"
description: "Long-running query detected ({{ $value | humanize }}s) on {{ $labels.instance }}"
# Low cache hit ratio
- alert: PostgreSQLLowCacheHitRatio
expr: |
(
sum(pg_stat_database_blks_hit) by (instance)
/
(sum(pg_stat_database_blks_hit) by (instance) + sum(pg_stat_database_blks_read) by (instance))
) < 0.90
for: 10m
labels:
severity: warning
team: dba
category: performance
annotations:
summary: "Low cache hit ratio on {{ $labels.instance }}"
description: "Cache hit ratio is {{ $value | humanizePercentage }} on {{ $labels.instance }} (expected > 90%)"---
Grafana Dashboard Examples
Example 11: Complete RED Method Dashboard
{
"dashboard": {
"title": "RED Method - Service Performance",
"tags": ["red", "performance", "slo"],
"timezone": "browser",
"refresh": "30s",
"time": {
"from": "now-1h",
"to": "now"
},
"templating": {
"list": [
{
"name": "cluster",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(up, cluster)",
"refresh": 2,
"multi": false,
"includeAll": false
},
{
"name": "service",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(up{cluster=\"$cluster\"}, service)",
"refresh": 2,
"multi": true,
"includeAll": true,
"allValue": ".*"
},
{
"name": "percentile",
"type": "custom",
"query": "0.50,0.95,0.99",
"multi": false,
"current": {
"value": "0.99",
"text": "p99"
}
}
]
},
"panels": [
{
"id": 1,
"title": "Request Rate (req/s)",
"type": "graph",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"targets": [
{
"expr": "sum(rate(http_requests_total{cluster=\"$cluster\",service=~\"$service\"}[$__rate_interval])) by (service)",
"legendFormat": "{{ service }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "reqps",
"decimals": 2
}
},
"options": {
"legend": {
"displayMode": "table",
"placement": "right",
"calcs": ["lastNotNull", "mean", "max"]
},
"tooltip": {
"mode": "multi"
}
}
},
{
"id": 2,
"title": "Error Rate (%)",
"type": "graph",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [
{
"expr": "sum(rate(http_requests_total{cluster=\"$cluster\",service=~\"$service\",status=~\"5..\"}[$__rate_interval])) by (service) / sum(rate(http_requests_total{cluster=\"$cluster\",service=~\"$service\"}[$__rate_interval])) by (service) * 100",
"legendFormat": "{{ service }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{"value": null, "color": "green"},
{"value": 1, "color": "yellow"},
{"value": 5, "color": "red"}
]
}
}
},
"alert": {
"conditions": [
{
"evaluator": {"params": [5], "type": "gt"},
"operator": {"type": "and"},
"query": {"params": ["A", "5m", "now"]},
"reducer": {"params": [], "type": "avg"},
"type": "query"
}
],
"name": "High Error Rate Alert"
}
},
{
"id": 3,
"title": "Latency (Duration)",
"type": "graph",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 8},
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{cluster=\"$cluster\",service=~\"$service\"}[$__rate_interval])) by (service, le))",
"legendFormat": "{{ service }} p99",
"refId": "A"
},
{
"expr": "histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{cluster=\"$cluster\",service=~\"$service\"}[$__rate_interval])) by (service, le))",
"legendFormat": "{{ service }} p95",
"refId": "B"
},
{
"expr": "histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket{cluster=\"$cluster\",service=~\"$service\"}[$__rate_interval])) by (service, le))",
"legendFormat": "{{ service }} p50",
"refId": "C"
}
],
"fieldConfig": {
"defaults": {
"unit": "s",
"decimals": 3
}
}
},
{
"id": 4,
"title": "Request Volume by Status",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 16},
"targets": [
{
"expr": "sum(rate(http_requests_total{cluster=\"$cluster\",service=~\"$service\"}[$__rate_interval])) by (status)",
"legendFormat": "{{ status }}",
"refId": "A"
}
],
"options": {
"stacking": {
"mode": "normal"
}
}
},
{
"id": 5,
"title": "Top Endpoints by Request Rate",
"type": "bargauge",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16},
"targets": [
{
"expr": "topk(10, sum(rate(http_requests_total{cluster=\"$cluster\",service=~\"$service\"}[$__rate_interval])) by (endpoint))",
"legendFormat": "{{ endpoint }}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "reqps"
}
},
"options": {
"orientation": "horizontal",
"displayMode": "gradient"
}
}
]
}
}Example 12: SLO Dashboard
{
"dashboard": {
"title": "SLO Tracking Dashboard",
"tags": ["slo", "sli", "reliability"],
"panels": [
{
"id": 1,
"title": "Availability SLI (Target: 99.9%)",
"type": "stat",
"gridPos": {"h": 6, "w": 6, "x": 0, "y": 0},
"targets": [
{
"expr": "sum(rate(http_requests_total{status=~\"2..\"}[30d])) / sum(rate(http_requests_total[30d])) * 100",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 3,
"thresholds": {
"mode": "absolute",
"steps": [
{"value": null, "color": "red"},
{"value": 99.9, "color": "green"}
]
}
}
},
"options": {
"graphMode": "area",
"colorMode": "background"
}
},
{
"id": 2,
"title": "Error Budget Remaining (30 days)",
"type": "gauge",
"gridPos": {"h": 6, "w": 6, "x": 6, "y": 0},
"targets": [
{
"expr": "(1 - ((1 - (sum(rate(http_requests_total{status=~\"2..\"}[30d])) / sum(rate(http_requests_total[30d])))) / (1 - 0.999))) * 100",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{"value": null, "color": "red"},
{"value": 20, "color": "orange"},
{"value": 50, "color": "yellow"},
{"value": 80, "color": "green"}
]
}
}
}
},
{
"id": 3,
"title": "Latency SLI - % Requests < 500ms",
"type": "stat",
"gridPos": {"h": 6, "w": 6, "x": 12, "y": 0},
"targets": [
{
"expr": "sum(rate(http_request_duration_seconds_bucket{le=\"0.5\"}[30d])) / sum(rate(http_request_duration_seconds_bucket{le=\"+Inf\"}[30d])) * 100",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 2,
"thresholds": {
"mode": "absolute",
"steps": [
{"value": null, "color": "red"},
{"value": 95, "color": "green"}
]
}
}
}
},
{
"id": 4,
"title": "SLO Burn Rate (1h window)",
"type": "graph",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 6},
"targets": [
{
"expr": "(sum(rate(http_requests_total{status=~\"5..\"}[1h])) / sum(rate(http_requests_total[1h]))) / (1 - 0.999)",
"legendFormat": "1h burn rate",
"refId": "A"
},
{
"expr": "(sum(rate(http_requests_total{status=~\"5..\"}[6h])) / sum(rate(http_requests_total[6h]))) / (1 - 0.999)",
"legendFormat": "6h burn rate",
"refId": "B"
}
],
"options": {
"legend": {
"displayMode": "table",
"placement": "bottom"
}
}
}
]
}
}---
Custom Exporter Examples
Example 13: Advanced Python Exporter with Multiple Metrics
#!/usr/bin/env python3
"""
Advanced application exporter for Prometheus
Exposes various application metrics including business metrics
"""
from prometheus_client import start_http_server, Counter, Gauge, Histogram, Summary, Enum, Info
from prometheus_client import CollectorRegistry, generate_latest, CONTENT_TYPE_LATEST
import time
import random
import psutil
import requests
from flask import Flask, Response
from threading import Thread
# Create custom registry
registry = CollectorRegistry()
# HTTP Request metrics
http_requests_total = Counter(
'app_http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status', 'service'],
registry=registry
)
http_request_duration_seconds = Histogram(
'app_http_request_duration_seconds',
'HTTP request latency in seconds',
['method', 'endpoint', 'service'],
buckets=[0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0],
registry=registry
)
http_request_size_bytes = Summary(
'app_http_request_size_bytes',
'HTTP request size in bytes',
['method', 'endpoint'],
registry=registry
)
http_response_size_bytes = Summary(
'app_http_response_size_bytes',
'HTTP response size in bytes',
['method', 'endpoint'],
registry=registry
)
# Application state metrics
active_users = Gauge(
'app_active_users',
'Number of active users',
registry=registry
)
active_sessions = Gauge(
'app_active_sessions',
'Number of active sessions',
registry=registry
)
queue_size = Gauge(
'app_queue_size',
'Current queue size',
['queue_name', 'priority'],
registry=registry
)
# Database metrics
database_connections = Gauge(
'app_database_connections',
'Number of database connections',
['pool', 'state'],
registry=registry
)
database_query_duration_seconds = Histogram(
'app_database_query_duration_seconds',
'Database query duration',
['query_type', 'table'],
buckets=[0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0],
registry=registry
)
# Cache metrics
cache_operations_total = Counter(
'app_cache_operations_total',
'Total cache operations',
['cache_name', 'operation'],
registry=registry
)
cache_hit_ratio = Gauge(
'app_cache_hit_ratio',
'Cache hit ratio',
['cache_name'],
registry=registry
)
# Business metrics
orders_total = Counter(
'app_orders_total',
'Total orders processed',
['status', 'payment_method'],
registry=registry
)
revenue_total = Counter(
'app_revenue_total',
'Total revenue in USD',
['currency', 'payment_method'],
registry=registry
)
# Feature flags
feature_flag = Enum(
'app_feature_flag_state',
'Feature flag states',
['feature_name'],
states=['enabled', 'disabled', 'canary'],
registry=registry
)
# Application info
app_info = Info(
'app_version',
'Application version information',
registry=registry
)
# Set static info
app_info.info({
'version': '2.1.0',
'build': '20250118',
'environment': 'production',
'region': 'us-west-2'
})
# Worker threads metrics
worker_threads_active = Gauge(
'app_worker_threads_active',
'Number of active worker threads',
['worker_type'],
registry=registry
)
worker_tasks_processed_total = Counter(
'app_worker_tasks_processed_total',
'Total tasks processed by workers',
['worker_type', 'status'],
registry=registry
)
# Resource usage metrics
process_cpu_usage_percent = Gauge(
'app_process_cpu_usage_percent',
'Process CPU usage percentage',
registry=registry
)
process_memory_bytes = Gauge(
'app_process_memory_bytes',
'Process memory usage in bytes',
['type'],
registry=registry
)
def collect_system_metrics():
"""Collect system-level metrics"""
process = psutil.Process()
# CPU usage
process_cpu_usage_percent.set(process.cpu_percent(interval=1))
# Memory usage
memory_info = process.memory_info()
process_memory_bytes.labels(type='rss').set(memory_info.rss)
process_memory_bytes.labels(type='vms').set(memory_info.vms)
def simulate_application_metrics():
"""Simulate application metrics"""
methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
endpoints = ['/api/users', '/api/products', '/api/orders', '/api/auth', '/api/payments']
statuses = ['200', '201', '400', '404', '500', '502']
status_weights = [70, 10, 5, 3, 1, 1] # Weighted distribution
while True:
# Simulate HTTP requests
method = random.choice(methods)
endpoint = random.choice(endpoints)
status = random.choices(statuses, weights=status_weights)[0]
http_requests_total.labels(
method=method,
endpoint=endpoint,
status=status,
service='api'
).inc()
# Simulate latency
latency = random.gauss(0.1, 0.05) # Normal distribution
if status.startswith('5'):
latency *= 3 # Slower for errors
http_request_duration_seconds.labels(
method=method,
endpoint=endpoint,
service='api'
).observe(max(0.001, latency))
# Request/response sizes
http_request_size_bytes.labels(method=method, endpoint=endpoint).observe(
random.randint(100, 5000)
)
http_response_size_bytes.labels(method=method, endpoint=endpoint).observe(
random.randint(500, 50000)
)
# Active users and sessions
active_users.set(random.randint(100, 1000))
active_sessions.set(random.randint(150, 1500))
# Queue metrics
queue_size.labels(queue_name='jobs', priority='high').set(random.randint(0, 20))
queue_size.labels(queue_name='jobs', priority='normal').set(random.randint(0, 100))
queue_size.labels(queue_name='emails', priority='normal').set(random.randint(0, 50))
# Database connections
database_connections.labels(pool='main', state='active').set(random.randint(5, 30))
database_connections.labels(pool='main', state='idle').set(random.randint(10, 50))
database_connections.labels(pool='readonly', state='active').set(random.randint(2, 15))
# Database query duration
database_query_duration_seconds.labels(
query_type='SELECT',
table='users'
).observe(random.uniform(0.001, 0.1))
# Cache operations
if random.random() > 0.3:
cache_operations_total.labels(cache_name='redis', operation='hit').inc()
else:
cache_operations_total.labels(cache_name='redis', operation='miss').inc()
# Update cache hit ratio
hits = cache_operations_total.labels(cache_name='redis', operation='hit')._value.get()
total = (
cache_operations_total.labels(cache_name='redis', operation='hit')._value.get() +
cache_operations_total.labels(cache_name='redis', operation='miss')._value.get()
)
if total > 0:
cache_hit_ratio.labels(cache_name='redis').set(hits / total)
# Business metrics (occasionally)
if random.random() > 0.9:
payment_method = random.choice(['credit_card', 'paypal', 'stripe'])
order_status = random.choice(['completed', 'pending', 'cancelled'])
orders_total.labels(
status=order_status,
payment_method=payment_method
).inc()
if order_status == 'completed':
revenue = random.uniform(10, 500)
revenue_total.labels(
currency='USD',
payment_method=payment_method
).inc(revenue)
# Worker metrics
worker_threads_active.labels(worker_type='background').set(random.randint(3, 10))
worker_tasks_processed_total.labels(
worker_type='background',
status='success'
).inc()
# Collect system metrics
collect_system_metrics()
time.sleep(0.5)
# Flask app for serving metrics
app = Flask(__name__)
@app.route('/metrics')
def metrics():
"""Metrics endpoint"""
return Response(generate_latest(registry), mimetype=CONTENT_TYPE_LATEST)
@app.route('/health')
def health():
"""Health check endpoint"""
return {'status': 'healthy', 'timestamp': time.time()}
if __name__ == '__main__':
# Start metric simulation in background thread
simulator_thread = Thread(target=simulate_application_metrics, daemon=True)
simulator_thread.start()
# Start Flask server
print("Starting metrics server on port 8000")
print("Metrics available at http://localhost:8000/metrics")
app.run(host='0.0.0.0', port=8000)Example 14: Go Exporter with Custom Collector
package main
import (
"log"
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Custom collector for dynamic metrics
type AppCollector struct {
requestsTotal *prometheus.CounterVec
requestDuration *prometheus.HistogramVec
activeConnections *prometheus.GaugeVec
queueLength *prometheus.GaugeVec
errorRate *prometheus.GaugeVec
}
func NewAppCollector() *AppCollector {
return &AppCollector{
requestsTotal: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "app_http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "status", "instance"},
),
requestDuration: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "app_http_request_duration_seconds",
Help: "HTTP request latency in seconds",
Buckets: prometheus.ExponentialBuckets(0.001, 2, 15),
},
[]string{"method", "endpoint", "instance"},
),
activeConnections: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_active_connections",
Help: "Number of active connections",
},
[]string{"instance", "state"},
),
queueLength: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_queue_length",
Help: "Current queue length",
},
[]string{"queue_name", "instance"},
),
errorRate: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "app_error_rate",
Help: "Current error rate (errors/second)",
},
[]string{"service", "instance"},
),
}
}
func (c *AppCollector) Describe(ch chan<- *prometheus.Desc) {
c.requestsTotal.Describe(ch)
c.requestDuration.Describe(ch)
c.activeConnections.Describe(ch)
c.queueLength.Describe(ch)
c.errorRate.Describe(ch)
}
func (c *AppCollector) Collect(ch chan<- prometheus.Metric) {
c.requestsTotal.Collect(ch)
c.requestDuration.Collect(ch)
c.activeConnections.Collect(ch)
c.queueLength.Collect(ch)
c.errorRate.Collect(ch)
}
func (c *AppCollector) simulateMetrics() {
instance := "api-server-1"
methods := []string{"GET", "POST", "PUT", "DELETE"}
endpoints := []string{"/api/users", "/api/products", "/api/orders", "/api/health"}
statuses := []string{"200", "201", "400", "404", "500"}
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
// Simulate requests
method := methods[rand.Intn(len(methods))]
endpoint := endpoints[rand.Intn(len(endpoints))]
status := statuses[rand.Intn(len(statuses))]
c.requestsTotal.WithLabelValues(method, endpoint, status, instance).Inc()
c.requestDuration.WithLabelValues(method, endpoint, instance).Observe(rand.Float64() * 2)
// Update gauges
c.activeConnections.WithLabelValues(instance, "active").Set(float64(rand.Intn(100) + 50))
c.activeConnections.WithLabelValues(instance, "idle").Set(float64(rand.Intn(50) + 10))
c.queueLength.WithLabelValues("jobs", instance).Set(float64(rand.Intn(100)))
c.queueLength.WithLabelValues("emails", instance).Set(float64(rand.Intn(50)))
c.errorRate.WithLabelValues("api", instance).Set(rand.Float64() * 10)
}
}
func main() {
// Create and register collector
collector := NewAppCollector()
prometheus.MustRegister(collector)
// Start simulating metrics
go collector.simulateMetrics()
// Expose metrics endpoint
http.Handle("/metrics", promhttp.Handler())
// Health endpoint
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
log.Println("Starting metrics server on :8000")
log.Println("Metrics endpoint: http://localhost:8000/metrics")
log.Fatal(http.ListenAndServe(":8000", nil))
}---
Recording Rule Examples
Example 15: Comprehensive Recording Rules
# recording_rules.yml
groups:
- name: http_performance_rules
interval: 30s
rules:
# Request rate aggregations
- record: job:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (job)
- record: job:http_requests:rate1m
expr: sum(rate(http_requests_total[1m])) by (job)
- record: service:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (service)
- record: instance:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (instance)
# Error rate calculations
- record: job:http_requests_errors:rate5m
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
- record: job:http_requests_error_ratio:rate5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)
- record: service:http_requests_error_ratio:rate5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
# Success rate
- record: job:http_requests_success_ratio:rate5m
expr: |
sum(rate(http_requests_total{status=~"2.."}[5m])) by (job)
/
sum(rate(http_requests_total[5m])) by (job)
# Latency percentiles
- record: job:http_request_duration:p50
expr: histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le))
- record: job:http_request_duration:p95
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le))
- record: job:http_request_duration:p99
expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le))
- record: service:http_request_duration:p99
expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le))
# Average latency
- record: job:http_request_duration:avg
expr: |
sum(rate(http_request_duration_seconds_sum[5m])) by (job)
/
sum(rate(http_request_duration_seconds_count[5m])) by (job)
- name: resource_aggregations
interval: 30s
rules:
# CPU usage by instance
- record: instance:node_cpu_usage:percent
expr: 100 - (avg by (instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Memory usage by instance
- record: instance:node_memory_usage:percent
expr: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# Disk usage by instance and mountpoint
- record: instance:node_disk_usage:percent
expr: |
(
node_filesystem_size_bytes{fstype!="rootfs"} -
node_filesystem_avail_bytes{fstype!="rootfs"}
)
/
node_filesystem_size_bytes{fstype!="rootfs"} * 100
# Network throughput
- record: instance:node_network_receive:rate5m
expr: rate(node_network_receive_bytes_total[5m])
- record: instance:node_network_transmit:rate5m
expr: rate(node_network_transmit_bytes_total[5m])
- name: slo_tracking
interval: 30s
rules:
# Availability SLI (30 day window)
- record: slo:availability:30d
expr: |
sum(rate(http_requests_total{status=~"2.."}[30d]))
/
sum(rate(http_requests_total[30d]))
# Latency SLI (% of requests under 500ms)
- record: slo:latency:30d
expr: |
sum(rate(http_request_duration_seconds_bucket{le="0.5"}[30d]))
/
sum(rate(http_request_duration_seconds_bucket{le="+Inf"}[30d]))
# Error budget remaining (30 days, 99.9% target)
- record: slo:error_budget:remaining_percent
expr: |
(1 - ((1 - slo:availability:30d) / (1 - 0.999))) * 100
# Burn rate (1 hour window)
- record: slo:burn_rate:1h
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) / (1 - 0.999)
# Burn rate (6 hour window)
- record: slo:burn_rate:6h
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[6h]))
/
sum(rate(http_requests_total[6h]))
) / (1 - 0.999)---
SLO Monitoring Examples
Example 16: Complete SLO Implementation
# slo_rules.yml
groups:
- name: api_slo_availability
interval: 30s
rules:
# Define SLI: Availability (success rate)
- record: api:sli:availability:5m
expr: |
sum(rate(http_requests_total{job="api",status=~"2.."}[5m]))
/
sum(rate(http_requests_total{job="api"}[5m]))
- record: api:sli:availability:1h
expr: |
sum(rate(http_requests_total{job="api",status=~"2.."}[1h]))
/
sum(rate(http_requests_total{job="api"}[1h]))
- record: api:sli:availability:24h
expr: |
sum(rate(http_requests_total{job="api",status=~"2.."}[24h]))
/
sum(rate(http_requests_total{job="api"}[24h]))
- record: api:sli:availability:30d
expr: |
sum(rate(http_requests_total{job="api",status=~"2.."}[30d]))
/
sum(rate(http_requests_total{job="api"}[30d]))
# Error budget calculations (99.9% SLO = 0.1% error budget)
- record: api:error_budget:consumed:1h
expr: (1 - api:sli:availability:1h) / (1 - 0.999)
- record: api:error_budget:consumed:24h
expr: (1 - api:sli:availability:24h) / (1 - 0.999)
- record: api:error_budget:consumed:30d
expr: (1 - api:sli:availability:30d) / (1 - 0.999)
- record: api:error_budget:remaining:30d
expr: 1 - api:error_budget:consumed:30d
# Burn rate alerts (multi-window, multi-burn-rate)
# Page-worthy: 2% budget burn in 1 hour
- alert: APIErrorBudgetBurnCritical
expr: |
api:error_budget:consumed:1h > 0.02
and
api:error_budget:consumed:5m > 0.02
for: 2m
labels:
severity: critical
slo: "availability"
window: "1h"
annotations:
summary: "Critical error budget burn on API"
description: "API is burning through error budget at {{ $value | humanizePercentage }} of monthly budget per hour"
# Ticket-worthy: 5% budget burn in 6 hours
- alert: APIErrorBudgetBurnHigh
expr: |
api:error_budget:consumed:6h > 0.05
and
api:error_budget:consumed:30m > 0.05
for: 15m
labels:
severity: warning
slo: "availability"
window: "6h"
annotations:
summary: "High error budget burn on API"
description: "API is burning through error budget at {{ $value | humanizePercentage }} of monthly budget per 6 hours"
# Exhausted error budget
- alert: APIErrorBudgetExhausted
expr: api:error_budget:remaining:30d < 0
for: 5m
labels:
severity: critical
slo: "availability"
annotations:
summary: "API error budget exhausted"
description: "API has consumed entire 30-day error budget. Error budget remaining: {{ $value | humanizePercentage }}"
- name: api_slo_latency
interval: 30s
rules:
# Define SLI: Latency (% requests under threshold)
- record: api:sli:latency_under_500ms:5m
expr: |
sum(rate(http_request_duration_seconds_bucket{job="api",le="0.5"}[5m]))
/
sum(rate(http_request_duration_seconds_bucket{job="api",le="+Inf"}[5m]))
- record: api:sli:latency_under_500ms:1h
expr: |
sum(rate(http_request_duration_seconds_bucket{job="api",le="0.5"}[1h]))
/
sum(rate(http_request_duration_seconds_bucket{job="api",le="+Inf"}[1h]))
- record: api:sli:latency_under_500ms:30d
expr: |
sum(rate(http_request_duration_seconds_bucket{job="api",le="0.5"}[30d]))
/
sum(rate(http_request_duration_seconds_bucket{job="api",le="+Inf"}[30d]))
# Latency SLO: 95% of requests under 500ms
- record: api:latency_error_budget:consumed:30d
expr: (1 - api:sli:latency_under_500ms:30d) / (1 - 0.95)
- alert: APILatencySLOViolation
expr: api:sli:latency_under_500ms:1h < 0.95
for: 10m
labels:
severity: warning
slo: "latency"
annotations:
summary: "API latency SLO violation"
description: "Only {{ $value | humanizePercentage }} of requests are under 500ms (target: 95%)"---
Multi-Cluster Federation Examples
Example 17: Federation Configuration
# Global Prometheus federating from regional Prometheus instances
global:
scrape_interval: 30s
evaluation_interval: 30s
external_labels:
cluster: 'global'
environment: 'production'
scrape_configs:
# Federate from US West region
- job_name: 'federate-us-west'
scrape_interval: 30s
honor_labels: true
metrics_path: '/federate'
params:
'match[]':
# Federate job-level aggregations
- '{job="prometheus"}'
- '{__name__=~"job:.*"}'
- '{__name__=~"service:.*"}'
- '{__name__=~"slo:.*"}'
# Federate alerts
- '{__name__=~"ALERTS.*"}'
static_configs:
- targets:
- 'prometheus-us-west-1.example.com:9090'
- 'prometheus-us-west-2.example.com:9090'
labels:
region: 'us-west'
# Federate from US East region
- job_name: 'federate-us-east'
scrape_interval: 30s
honor_labels: true
metrics_path: '/federate'
params:
'match[]':
- '{job="prometheus"}'
- '{__name__=~"job:.*"}'
- '{__name__=~"service:.*"}'
- '{__name__=~"slo:.*"}'
- '{__name__=~"ALERTS.*"}'
static_configs:
- targets:
- 'prometheus-us-east-1.example.com:9090'
- 'prometheus-us-east-2.example.com:9090'
labels:
region: 'us-east'
# Federate from EU region
- job_name: 'federate-eu-central'
scrape_interval: 30s
honor_labels: true
metrics_path: '/federate'
params:
'match[]':
- '{job="prometheus"}'
- '{__name__=~"job:.*"}'
- '{__name__=~"service:.*"}'
- '{__name__=~"slo:.*"}'
- '{__name__=~"ALERTS.*"}'
static_configs:
- targets:
- 'prometheus-eu-central-1.example.com:9090'
labels:
region: 'eu-central'
# Global recording rules
rule_files:
- 'global_rules.yml'# global_rules.yml
groups:
- name: global_aggregations
interval: 60s
rules:
# Global request rate across all regions
- record: global:http_requests:rate5m
expr: sum(job:http_requests:rate5m) by (job)
# Request rate by region
- record: region:http_requests:rate5m
expr: sum(job:http_requests:rate5m) by (region, job)
# Global error rate
- record: global:http_requests_error_ratio:rate5m
expr: |
sum(job:http_requests_errors:rate5m)
/
sum(job:http_requests:rate5m)
# Global availability SLI
- record: global:slo:availability:30d
expr: |
sum(slo:availability:30d) by (region)
/
count(slo:availability:30d)---
Advanced Patterns
Example 18: Anomaly Detection with PromQL
# Detect CPU usage anomalies using standard deviation
abs(
instance:node_cpu_usage:percent
-
avg_over_time(instance:node_cpu_usage:percent[1h])
)
>
3 * stddev_over_time(instance:node_cpu_usage:percent[1h])
# Detect request rate anomalies
abs(
sum(rate(http_requests_total[5m]))
-
avg_over_time(sum(rate(http_requests_total[5m]))[1h:5m])
)
>
2 * stddev_over_time(sum(rate(http_requests_total[5m]))[1h:5m])
# Detect latency spikes
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
>
1.5 * histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m] offset 1h)) by (le)
)Example 19: Cost Monitoring
# cost_monitoring.yml
groups:
- name: cost_tracking
interval: 5m
rules:
# EC2 instance cost (example rates)
- record: cloud:ec2:cost_per_hour
expr: |
sum(
ec2_instance_running
*
on(instance_type) group_left
ec2_instance_type_cost_per_hour
) by (region, environment)
# Kubernetes CPU cost
- record: cloud:k8s:cpu_cost_per_hour
expr: |
sum(
kube_pod_container_resource_requests{resource="cpu"}
) * 0.03 # $0.03 per vCPU hour
# Kubernetes memory cost
- record: cloud:k8s:memory_cost_per_hour
expr: |
sum(
kube_pod_container_resource_requests{resource="memory"}
/ 1024 / 1024 / 1024
) * 0.005 # $0.005 per GB hour
# Total cloud cost per hour
- record: cloud:total_cost_per_hour
expr: |
sum(cloud:ec2:cost_per_hour)
+
sum(cloud:k8s:cpu_cost_per_hour)
+
sum(cloud:k8s:memory_cost_per_hour)
# Monthly cost projection
- record: cloud:monthly_cost_projection
expr: cloud:total_cost_per_hour * 730
# Cost per service
- record: service:cost_per_hour
expr: |
sum(
kube_pod_container_resource_requests{resource="cpu"}
* 0.03
+
kube_pod_container_resource_requests{resource="memory"}
/ 1024 / 1024 / 1024
* 0.005
) by (namespace, service)Example 20: Capacity Planning Queries
# Predict when disk will be full (4 hour prediction)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[1h], 4 * 3600) < 0
# Predict when memory will be exhausted
predict_linear(node_memory_MemAvailable_bytes[2h], 6 * 3600) < (1024 * 1024 * 1024) # 1GB
# Database growth rate (bytes/day)
deriv(
pg_database_size_bytes[7d]
) * 86400
# Request volume growth (requests/day)
(
avg_over_time(sum(rate(http_requests_total[5m]))[24h:5m])
-
avg_over_time(sum(rate(http_requests_total[5m] offset 7d))[24h:5m])
)
/
avg_over_time(sum(rate(http_requests_total[5m] offset 7d))[24h:5m])
* 100
# Time until connection pool exhausted (at current rate)
(
pg_settings_max_connections
-
sum(pg_stat_activity_count)
)
/
deriv(sum(pg_stat_activity_count)[1h])---
Total Examples: 20 comprehensive examples covering all aspects of observability and monitoring with Prometheus, Grafana, and related tools.
Categories Covered:
- Prometheus configuration (3 examples)
- PromQL queries (4 examples)
- Alert rules (3 examples)
- Grafana dashboards (2 examples)
- Custom exporters (2 examples)
- Recording rules (1 example)
- SLO monitoring (1 example)
- Multi-cluster federation (1 example)
- Advanced patterns (3 examples)
File Version: 1.0.0 Last Updated: October 2025
Observability & Monitoring Skill
A comprehensive skill for implementing production-grade observability and monitoring using Prometheus, Grafana, and the cloud-native monitoring ecosystem.
Overview
This skill provides complete guidance for building robust monitoring systems that enable teams to understand system behavior, detect issues early, and maintain high reliability. It covers the full monitoring stack from metric collection through visualization and alerting.
What This Skill Covers
Core Technologies
- Prometheus: Time-series database and monitoring system
- Grafana: Visualization and dashboarding platform
- Alertmanager: Alert routing and notification management
- Exporters: Metric collection agents (Node, PostgreSQL, custom)
- PromQL: Powerful query language for metric analysis
Key Concepts
The Four Pillars of Observability 1. Metrics - Numerical measurements over time 2. Logs - Discrete event records 3. Traces - Request flow through systems 4. Events - Significant system occurrences
Monitoring Methodologies
- RED Method (Request rate, Error rate, Duration)
- USE Method (Utilization, Saturation, Errors)
- Golden Signals (Latency, Traffic, Errors, Saturation)
- SLIs, SLOs, and Error Budgets
What You'll Learn
Prometheus Setup
- Installation and configuration
- Service discovery (static, Kubernetes, cloud providers)
- Storage and retention strategies
- Remote write/read for long-term storage
- High availability and federation
PromQL Mastery
- Instant and range vectors
- Aggregation operators (sum, avg, max, min, count)
- Mathematical operations and comparisons
- Rate calculations and derivatives
- Histogram quantiles for latency percentiles
- Prediction and anomaly detection
Alerting Strategy
- Writing effective alert rules
- Multi-window multi-burn-rate alerts for SLOs
- Alertmanager routing and receivers
- Integration with PagerDuty, Slack, email
- Alert grouping and inhibition
- Reducing alert fatigue
Grafana Dashboards
- Dashboard design principles
- Template variables for flexibility
- Panel types and visualizations
- RED and USE method dashboards
- SLO tracking dashboards
- Annotations for deployments and incidents
Custom Exporters
- Building application-specific exporters
- Metric types: Counter, Gauge, Histogram, Summary
- Best practices for metric naming and labels
- Managing cardinality
- Client libraries (Python, Go, Java, Node.js)
Production Best Practices
- Metric naming conventions
- Label design for low cardinality
- Recording rules for performance
- Capacity planning and forecasting
- Cost monitoring and optimization
- Security and authentication
Quick Start
1. Install Prometheus
# Download Prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz
tar xvfz prometheus-2.45.0.linux-amd64.tar.gz
cd prometheus-2.45.0.linux-amd64
# Create basic configuration
cat > prometheus.yml <<EOF
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
EOF
# Start Prometheus
./prometheus --config.file=prometheus.ymlAccess Prometheus UI at http://localhost:9090
2. Install Node Exporter
# Download and run node_exporter
wget https://github.com/prometheus/node_exporter/releases/download/v1.6.1/node_exporter-1.6.1.linux-amd64.tar.gz
tar xvfz node_exporter-1.6.1.linux-amd64.tar.gz
cd node_exporter-1.6.1.linux-amd64
./node_exporterAdd to prometheus.yml:
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']3. Install Grafana
# Using Docker
docker run -d -p 3000:3000 --name=grafana grafana/grafana
# Or download binary
wget https://dl.grafana.com/oss/release/grafana-10.0.0.linux-amd64.tar.gz
tar -zxvf grafana-10.0.0.linux-amd64.tar.gz
cd grafana-10.0.0
./bin/grafana-serverAccess Grafana at http://localhost:3000 (default: admin/admin)
4. Configure Grafana Data Source
1. Navigate to Configuration > Data Sources 2. Add Prometheus data source 3. URL: http://localhost:9090 4. Click "Save & Test"
5. Create Your First Dashboard
Import pre-built Node Exporter dashboard: 1. Click "+" > Import 2. Enter dashboard ID: 1860 3. Select Prometheus data source 4. Click Import
Common Use Cases
Monitoring a Web Application
# Add application to Prometheus
scrape_configs:
- job_name: 'webapp'
static_configs:
- targets: ['app-1:8080', 'app-2:8080']
labels:
env: 'production'
tier: 'frontend'Key Metrics to Track:
- Request rate:
rate(http_requests_total[5m]) - Error rate:
rate(http_requests_total{status=~"5.."}[5m]) - Latency:
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) - Active connections:
http_active_connections
Database Monitoring
# Run PostgreSQL exporter
docker run -d \
-p 9187:9187 \
-e DATA_SOURCE_NAME="postgresql://user:password@localhost:5432/dbname?sslmode=disable" \
prometheuscommunity/postgres-exporterKey Metrics:
- Query rate:
rate(pg_stat_database_xact_commit[5m]) - Connection pool:
pg_stat_database_numbackends - Replication lag:
pg_stat_replication_lag - Cache hit ratio:
pg_stat_database_blks_hit / (pg_stat_database_blks_hit + pg_stat_database_blks_read)
Kubernetes Monitoring
# Kubernetes pod discovery
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (\d+)
replacement: ${1}:${2}Annotate your pods:
apiVersion: v1
kind: Pod
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"Implementing SLOs
# Define SLO: 99.9% availability
groups:
- name: api_slo
rules:
- record: api:sli:availability
expr: |
sum(rate(http_requests_total{status=~"2.."}[5m]))
/
sum(rate(http_requests_total[5m]))
- alert: SLOBudgetBurn
expr: api:sli:availability < 0.999
for: 5m
labels:
severity: critical
annotations:
summary: "API availability below SLO target"
description: "Current availability: {{ $value | humanizePercentage }}"Creating Alerts
# High error rate alert
groups:
- name: alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value | humanizePercentage }}"Architecture Patterns
Single Prometheus Instance
Best for:
- Small deployments (< 1000 targets)
- Single cluster/datacenter
- Simple infrastructure
Setup:
Application Servers
↓ (scrape)
Prometheus
↓ (query)
Grafana
↓ (alerts)
AlertmanagerPrometheus with Remote Storage
Best for:
- Long-term metric retention
- High query load
- Multiple teams querying data
Setup:
Prometheus (local TSDB)
↓ (remote_write)
Long-term Storage (Thanos, Cortex, Mimir)
↓ (query)
GrafanaFederated Prometheus
Best for:
- Multiple clusters/regions
- Hierarchical monitoring
- Aggregated global view
Setup:
Cluster Prometheus 1 ──┐
Cluster Prometheus 2 ──┼─→ Global Prometheus → Grafana
Cluster Prometheus 3 ──┘High Availability Setup
Best for:
- Production systems requiring 99.9%+ uptime
- Critical monitoring infrastructure
- Large-scale deployments
Setup:
Prometheus Instance 1 ──┐
Prometheus Instance 2 ──┼─→ Load Balancer → Grafana
(Identical config) │
└─→ Thanos (deduplication)Metric Collection Strategies
Pull-Based (Prometheus Default)
Advantages:
- Centralized control of scraping
- Easy to detect down targets
- Better for service discovery
When to use:
- Internal services you control
- Kubernetes environments
- Standard HTTP endpoints
Push-Based (with Pushgateway)
Advantages:
- Works for batch jobs
- Firewall-friendly
- Short-lived processes
When to use:
- Batch jobs and cron tasks
- Serverless functions
- Behind NAT/firewalls
# Push metrics to Pushgateway
echo "job_last_success_time $(date +%s)" | curl --data-binary @- \
http://pushgateway:9091/metrics/job/backupHybrid Approach
Combine pull and push for comprehensive coverage:
- Pull for long-running services
- Push for batch jobs and serverless
- Remote write for cross-datacenter
Visualization Best Practices
Dashboard Organization
1. Executive Dashboard
- High-level business metrics
- Overall system health
- SLO compliance
- Cost trends
2. Service Dashboard
- RED method (Request, Error, Duration)
- Service-specific metrics
- Dependencies and downstream services
- Recent deployments
3. Infrastructure Dashboard
- USE method (Utilization, Saturation, Errors)
- Resource consumption
- Capacity planning
- Hardware health
4. Debugging Dashboard
- Detailed metrics for troubleshooting
- Logs correlation
- Trace links
- Historical comparisons
Panel Types
Time Series Graph: Trends over time
- Request rates, latency, resource usage
- Best for continuous metrics
Gauge: Current state
- % disk usage, active connections
- Best for point-in-time values
Stat: Single number
- Total requests, uptime
- Best for aggregated metrics
Table: Multiple dimensions
- Per-service metrics, resource breakdown
- Best for comparative analysis
Heatmap: Distribution visualization
- Latency distributions
- Best for understanding spread
Alerting Philosophy
Alert Tiers
Critical (Page immediately)
- User-facing service down
- Data loss in progress
- Security breach detected
- SLO budget exhausted
Warning (Review within hours)
- Degraded performance
- Resource approaching limits
- High error rates (not critical yet)
- SLO budget burning fast
Info (Review during business hours)
- Capacity planning triggers
- Optimization opportunities
- Deployment notifications
- Unusual but not urgent patterns
Alert Attributes
Every alert should have: 1. Clear summary: What's wrong? 2. Detailed description: What's the impact? 3. Runbook link: How to fix it? 4. Severity level: How urgent? 5. Team label: Who's responsible?
Reducing Alert Fatigue
Techniques:
- Use
forduration to avoid flapping - Set appropriate thresholds based on data
- Group related alerts
- Inhibit lower-priority alerts when critical fires
- Regular alert review and tuning
- Dead man's switch for monitoring health
Integration Ecosystem
Popular Integrations
Notification Channels:
- Slack, Microsoft Teams
- PagerDuty, Opsgenie
- Email, SMS
- Webhooks for custom integrations
Log Aggregation:
- Loki (Grafana's log system)
- Elasticsearch + Kibana
- Splunk
Tracing:
- Jaeger
- Tempo (Grafana's tracing)
- Zipkin
APM Tools:
- OpenTelemetry
- New Relic
- Datadog
Cloud Platforms:
- AWS CloudWatch
- Google Cloud Monitoring
- Azure Monitor
Troubleshooting Guide
Prometheus Not Scraping Targets
Check: 1. Target reachability: curl http://target:port/metrics 2. Service discovery config 3. Firewall rules 4. Label matchers in scrape config 5. Prometheus logs for errors
High Cardinality Issues
Symptoms:
- Prometheus using excessive memory
- Slow queries
- High CPU usage
Solutions:
- Identify high-cardinality metrics
- Drop or relabel problematic labels
- Use recording rules for pre-aggregation
- Set shorter retention periods
Missing Metrics
Causes:
- Metric not exposed by exporter
- Scrape interval too long
- Metric expired (removed by application)
- Relabeling dropping metrics
Debug:
# Check if metric exists at all
{__name__=~".*your_metric.*"}
# Check specific target's metrics
up{instance="target:port"}Alert Not Firing
Verify: 1. Alert rule syntax: promtool check rules alerts.yml 2. Rule evaluation: Check "Alerts" tab in Prometheus UI 3. Alert state: pending → firing transition 4. Alertmanager receives alert 5. Routing configuration in Alertmanager
Performance Tuning
Prometheus Optimization
# Tune for high-cardinality environments
global:
scrape_interval: 30s # Increase if too many targets
evaluation_interval: 30s
storage:
tsdb:
min-block-duration: 2h # Default
max-block-duration: 2h # Keep same as min
retention.time: 15d # Adjust based on needs
retention.size: 50GB # Limit storage growthQuery Optimization
# Bad: High cardinality, expensive
sum(rate(http_requests_total[5m])) by (user_id)
# Good: Pre-aggregated, efficient
sum(rate(http_requests_total[5m])) by (service, status_class)
# Use recording rules for expensive queries
job:http_requests:rate5mGrafana Dashboard Performance
- Use shorter time ranges when possible
- Limit number of series per panel
- Use query caching (Grafana Enterprise)
- Create separate dashboards for different use cases
- Use dashboard folder organization
Security Considerations
Authentication and Authorization
# Enable basic auth in Prometheus
basic_auth_users:
admin: $2y$10$hashed_password_here
# TLS configuration
tls_server_config:
cert_file: server.crt
key_file: server.keyNetwork Security
- Use TLS for scraping sensitive targets
- Implement firewall rules
- Use VPN for cross-datacenter federation
- Restrict Prometheus API access
- Enable authentication on exporters
Data Privacy
- Avoid collecting PII in metrics
- Use aggregation to anonymize data
- Implement data retention policies
- Secure remote storage credentials
- Regular security audits
Resources and Learning
Official Documentation
- Prometheus: https://prometheus.io/docs/
- Grafana: https://grafana.com/docs/
- Alertmanager: https://prometheus.io/docs/alerting/latest/alertmanager/
- PromQL: https://prometheus.io/docs/prometheus/latest/querying/basics/
Community Resources
- Prometheus Mailing List
- CNCF Slack #prometheus channel
- Grafana Community Forums
- GitHub repositories for exporters
Books and Guides
- "Prometheus: Up & Running" by Brian Brazil
- "Site Reliability Engineering" by Google
- "The Site Reliability Workbook" by Google
- CNCF Cloud Native Landscape
Practice Labs
- Prometheus Demo: https://demo.prometheus.io
- Grafana Play: https://play.grafana.org
- Katacoda Prometheus Scenarios
- Local Kubernetes with kind/minikube
Next Steps
1. Start Small: Install Prometheus and Node Exporter locally 2. Learn PromQL: Practice queries on your metrics 3. Build Dashboards: Create a simple RED method dashboard 4. Add Alerts: Define one critical alert for your system 5. Iterate: Gradually expand coverage and sophistication 6. Share Knowledge: Document runbooks and share with team
---
Skill Version: 1.0.0 Maintained By: Observability Community License: MIT