Observability & Infrastructure Monitoring
Understanding the Grafana Observability Stack
Metrics, logs, and alerts in beautiful dashboards. Monitor your entire infrastructure!
Without Observability:
With Grafana Stack: See everything in dashboards, search logs from one place, get alerted before users notice!
| Phase | Time | Difficulty |
|---|---|---|
| Understanding Concepts | 15-20 min | Reading |
| Stack Installation | 30-45 min | Easy |
| Prometheus Setup | 20-30 min | Easy |
| Loki + Promtail | 20-30 min | Easy |
| Dashboard Creation | 30-45 min | Medium |
| Alerting Setup | 20-30 min | Medium |
Total: 3-4 hours
Understanding observability
Metrics, Logs, and Traces - the holy trinity of knowing what's happening
| Metrics (Prometheus) | Logs (Loki) |
|---|---|
| Numbers: CPU is 85% | Text: "Error connecting to database" |
| Aggregated: Average over time | Individual: Each event |
| Low storage: Just numbers | High storage: Full text |
| Good for: Dashboards, trends | Good for: Debugging, investigation |
| Query: PromQL | Query: LogQL |
| Term | Meaning |
|---|---|
| Exporter | App that exposes metrics for Prometheus to scrape |
| Scrape | Prometheus pulls metrics from exporters |
| Time Series | Data points indexed by time |
| Label | Key-value pair to identify metrics (e.g., host="server1") |
| Dashboard | Collection of visualizations (panels) in Grafana |
| Data Source | Where Grafana gets data (Prometheus, Loki, etc.) |
Metrics: Your car's speedometer, fuel gauge, RPM
Logs: Your car's detailed diagnostic codes
Dashboard: Your car's instrument cluster display
What you need before starting
This project can run standalone or enhance your existing stack
| Resource | Minimum | Recommended |
|---|---|---|
| RAM | 2 GB additional | 4 GB additional |
| Storage | 20 GB | 50+ GB (for retention) |
| CPU | 2 cores | 4 cores |
Unlike some other projects, you can run this independently. But it's even better when combined with Projects A-D!
Deploy Grafana, Prometheus, and Loki
SSH into your server first. These commands run on the server.
# Create monitoring stack directory
mkdir -p ~/monitoring/{grafana,prometheus,loki,promtail}
mkdir -p ~/monitoring/grafana/{data,provisioning/datasources,provisioning/dashboards}
mkdir -p ~/monitoring/prometheus/data
mkdir -p ~/monitoring/loki/data
# Set permissions
sudo chown -R 472:472 ~/monitoring/grafana/data
sudo chown -R 65534:65534 ~/monitoring/prometheus/data
sudo chown -R 10001:10001 ~/monitoring/loki/data
cd ~/monitoring
cat > ~/monitoring/prometheus/prometheus.yml << 'EOF'
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets: []
rule_files: []
scrape_configs:
# Prometheus itself
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Node Exporter (system metrics)
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
# Docker containers (cAdvisor)
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
# Traefik (if you have it)
- job_name: 'traefik'
static_configs:
- targets: ['traefik:8080']
EOF
cat > ~/monitoring/loki/loki-config.yml << 'EOF'
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
storage_config:
boltdb_shipper:
active_index_directory: /loki/boltdb-shipper-active
cache_location: /loki/boltdb-shipper-cache
shared_store: filesystem
filesystem:
directory: /loki/chunks
limits_config:
retention_period: 720h # 30 days
EOF
cat > ~/monitoring/promtail/promtail-config.yml << 'EOF'
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# Docker container logs
- job_name: containers
static_configs:
- targets:
- localhost
labels:
job: containerlogs
__path__: /var/lib/docker/containers/*/*log
pipeline_stages:
- json:
expressions:
output: log
stream: stream
attrs:
- json:
expressions:
tag:
source: attrs
- regex:
expression: (?P(?:[a-zA-Z0-9][a-zA-Z0-9_.-]+))
source: tag
- labels:
stream:
container_name:
- output:
source: output
# System logs
- job_name: syslog
static_configs:
- targets:
- localhost
labels:
job: syslog
__path__: /var/log/syslog
EOF
cat > ~/monitoring/grafana/provisioning/datasources/datasources.yml << 'EOF'
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
- name: Loki
type: loki
access: proxy
url: http://loki:3100
editable: false
EOF
cat > ~/monitoring/docker-compose.yml << 'EOF'
version: '3.8'
services:
# ==========================================
# GRAFANA - Visualization
# ==========================================
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin123
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- ./grafana/data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
networks:
- monitoring
# ==========================================
# PROMETHEUS - Metrics
# ==========================================
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/data:/prometheus
networks:
- monitoring
# ==========================================
# LOKI - Log Aggregation
# ==========================================
loki:
image: grafana/loki:2.9.0
container_name: loki
restart: unless-stopped
ports:
- "3100:3100"
command: -config.file=/etc/loki/loki-config.yml
volumes:
- ./loki/loki-config.yml:/etc/loki/loki-config.yml:ro
- ./loki/data:/loki
networks:
- monitoring
# ==========================================
# PROMTAIL - Log Shipper
# ==========================================
promtail:
image: grafana/promtail:2.9.0
container_name: promtail
restart: unless-stopped
volumes:
- ./promtail/promtail-config.yml:/etc/promtail/promtail-config.yml:ro
- /var/log:/var/log:ro
- /var/lib/docker/containers:/var/lib/docker/containers:ro
command: -config.file=/etc/promtail/promtail-config.yml
networks:
- monitoring
# ==========================================
# NODE EXPORTER - System Metrics
# ==========================================
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
ports:
- "9100:9100"
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
networks:
- monitoring
# ==========================================
# cADVISOR - Container Metrics
# ==========================================
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
privileged: true
devices:
- /dev/kmsg
networks:
- monitoring
networks:
monitoring:
driver: bridge
EOF
cd ~/monitoring
# Pull all images
docker compose pull
# Start the stack
docker compose up -d
# Check all containers are running
docker compose ps
# View logs
docker compose logs -f
Access Grafana at http://YOUR_SERVER_IP:3000
Default login: admin / admin123
Collect and query metrics
Prometheus scrapes metrics from your services every 15 seconds
In Prometheus UI, try these queries:
# CPU Usage (percentage)
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# Memory Usage (percentage)
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
# Disk Usage (percentage)
(1 - (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})) * 100
# Network Received (bytes/sec)
rate(node_network_receive_bytes_total[5m])
# Docker Container CPU
rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100
# Docker Container Memory
container_memory_usage_bytes{name!=""}
| Exporter | Metrics For | Port |
|---|---|---|
| node-exporter | Linux system metrics | 9100 |
| cAdvisor | Docker containers | 8080 |
| postgres-exporter | PostgreSQL database | 9187 |
| blackbox-exporter | HTTP/TCP/ICMP probes | 9115 |
| nginx-exporter | Nginx web server | 9113 |
Search all your logs in one place
No more SSH-ing into servers to read logs!
# All logs from a specific container
{container_name="grafana"}
# Filter for errors
{container_name="grafana"} |= "error"
# Case-insensitive search
{container_name="grafana"} |~ "(?i)error"
# Exclude certain patterns
{container_name="grafana"} != "healthcheck"
# Multiple filters
{job="containerlogs"} |= "error" |= "database"
# Parse JSON logs
{container_name="authentik-server"} | json
# Count errors per minute
count_over_time({container_name="grafana"} |= "error" [1m])
Combine Loki with Prometheus! When you see a spike in error metrics, click through to see the actual error logs.
Visualize your data beautifully
Import pre-built dashboards or create your own!
Import these popular dashboards from Grafana.com:
| ID | Dashboard |
|---|---|
| 1860 | Node Exporter Full (System metrics) |
| 893 | Docker & System Monitoring |
| 14282 | cAdvisor Container Dashboard |
| 13639 | Loki Dashboard |
Create dashboards for: Home Lab Overview, Docker Containers, Network Traffic, Security Events (from Wazuh), IAM Activity (from Authentik)
Get notified when things go wrong
Know about problems before your users do!
| Alert | Condition | Severity |
|---|---|---|
| High CPU | CPU > 80% for 5min | Warning |
| High Memory | Memory > 90% for 5min | Warning |
| Disk Full | Disk > 85% | Critical |
| Container Down | Container not running | Critical |
| High Error Rate | Errors > 10/min | Warning |
Make sure everything works
Test all components are working correctly
# Check all containers running
docker compose ps
# Test Prometheus
curl http://localhost:9090/-/healthy
# Test Loki
curl http://localhost:3100/ready
# Test Grafana
curl http://localhost:3000/api/health
# Check Prometheus targets
curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[].health'
# View container logs
docker compose logs grafana --tail=20
Cause: Exporter not running or network issue
Solution:
• Check container is running: docker ps
• Verify network connectivity between containers
• Check exporter logs
Cause: Promtail not shipping logs
Solution:
• Check Promtail logs: docker logs promtail
• Verify log paths exist
• Check Promtail can reach Loki
Cause: Data source misconfigured or time range
Solution:
• Check data source URL in Grafana settings
• Adjust time range to last hour
• Test query in Explore first
Your observability stack is live!
You now have enterprise-grade monitoring for your infrastructure!