Set up production-grade monitoring with Prometheus and Grafana to catch issues before they become problems
By the end of this tutorial, you'll know how to:
- Collect metrics from your Kubernetes applications
- Create dashboards to visualize performance data
- Set up alerts to catch issues early
- Monitor resource usage (CPU, memory, disk)
- Track application health in real-time
Monitoring isn't optional in production. This tutorial teaches you the same observability patterns used by companies like Datadog and New Relic to track application performance and prevent outages before they happen.
What this means for you: Professional DevOps engineers spend 30% of their time on monitoring. Learning these tools makes you valuable to any team that runs production applications.
ℹ️ Simple Explanation: Monitoring is like having a dashboard in your car. It shows you speed (request rate), fuel level (memory usage), and engine temperature (CPU usage) so you can catch problems before they break your application.
If you want to get monitoring working quickly:
# 1. Deploy monitoring stack
kubectl create namespace monitoring
kubectl apply -f k8s/prometheus-rbac.yaml
kubectl apply -f k8s/monitoring.yaml
# 2. Start port-forwards
kubectl port-forward svc/prometheus 9090:9090 -n monitoring &
kubectl port-forward svc/grafana 3000:3000 -n monitoring &
kubectl port-forward svc/backend 3001:3001 -n humor-game &
# 3. Generate sample data
chmod +x scripts/populate-game-metrics.sh
./scripts/populate-game-metrics.sh
# 4. Access dashboards and import comprehensive dashboard
# Grafana: http://localhost:3000 (admin/admin123)
# Import: k8s/comprehensive-dashboard.json
or
# Import: k8s/advanced-custom-dashboard.json
# Prometheus: http://localhost:9090
# OPTIONAL: Set up ingress access (no port-forwarding)
# chmod +x scripts/setup-monitoring-ingress.sh
# ./scripts/setup-monitoring-ingress.sh
# Access: http://grafana.gameapp.local:8080# Create monitoring namespace and RBAC permissions
kubectl apply -f k8s/prometheus-rbac.yamlExpected Output:
namespace/monitoring created
serviceaccount/prometheus created
clusterrole.rbac.authorization.k8s.io/prometheus created
clusterrolebinding.rbac.authorization.k8s.io/prometheus created# Deploy Prometheus and Grafana stack
kubectl apply -f k8s/monitoring.yamlExpected Output:
configmap/grafana-datasources created
configmap/prometheus-config created
deployment.apps/prometheus created
deployment.apps/grafana created
service/prometheus created
service/grafana created
```bash
# Wait for monitoring services to be ready (this takes a few minutes)
kubectl wait --for=condition=ready pod -l app=prometheus -n monitoring --timeout=300s
kubectl wait --for=condition=ready pod -l app=grafana -n monitoring --timeout=300s
# Verify monitoring stack is running
kubectl get pods -n monitoring
# Should show prometheus and grafana pods with "1/1 Running"Expected Output:
NAME READY STATUS RESTARTS AGE
grafana-7d8f9c8f9c-abc12 1/1 Running 0 3m
prometheus-8e9f0d1e2f-def34 1/1 Running 0 3mOption 1: Port-Forwarding (Traditional Method)
# Access Prometheus (metrics database)
kubectl port-forward svc/prometheus 9090:9090 -n monitoring &Expected Output:
Forwarding from 127.0.0.1:9090 -> 9090
Forwarding from [::1]:9090 -> 9090# Access Grafana (dashboard interface)
kubectl port-forward svc/grafana 3000:3000 -n monitoring &Expected Output:
Forwarding from 127.0.0.1:3000 -> 3000
Forwarding from [::1]:3000 -> 3000# Open monitoring interfaces
open http://localhost:9090 # Prometheus UI
open http://localhost:3000 # Grafana UI (login: admin/admin123)Option 2: Ingress-Based Access (No Port-Forwarding Required)
Set up ingress access for convenient monitoring without port-forwarding:
# Set up monitoring ingress and DNS
chmod +x scripts/setup-monitoring-ingress.sh
./scripts/setup-monitoring-ingress.sh
# Expected output:
# ✅ Added prometheus.gameapp.local to /etc/hosts
# ✅ Added grafana.gameapp.local to /etc/hosts
# 🎉 Monitoring ingress setup complete!Access URLs (No Port-Forwarding Required):
- Prometheus: http://prometheus.gameapp.local:8080
- Grafana: http://grafana.gameapp.local:8080
💡 Benefits of Ingress Access:
- No need to manage multiple port-forward processes
- Clean, memorable URLs for monitoring services
- Works automatically once configured
- Production-like setup for learning
Open http://localhost:9090 and explore the metrics Prometheus is collecting:
Basic queries to try:
# In Prometheus query interface, try these:
# Pod CPU usage
rate(container_cpu_usage_seconds_total[5m])Expected Output:
{container="humor-game-backend",namespace="humor-game",pod="humor-game-backend-7d8f9c8f9c-abc12"} 0.001234
{container="humor-game-frontend",namespace="humor-game",pod="humor-game-frontend-8e9f0d1e2f-def34"} 0.000567# Pod memory usage
container_memory_usage_bytesExpected Output:
{container="humor-game-backend",namespace="humor-game",pod="humor-game-backend-7d8f9c8f9c-abc12"} 156789012
{container="humor-game-frontend",namespace="humor-game",pod="humor-game-frontend-8e9f0d1e2f-def34"} 23456789# HTTP requests to your backend
rate(http_requests_total[5m])Expected Output:
{method="GET",status="200",endpoint="/api/health"} 0.1
{method="POST",status="200",endpoint="/api/game"} 0.05# Kubernetes pod restarts
increase(kube_pod_container_status_restarts_total[1h])Expected Output:
{namespace="humor-game",pod="humor-game-backend-7d8f9c8f9c-abc12"} 0
{namespace="humor-game",pod="humor-game-frontend-8e9f0d1e2f-def34"} 0Open http://localhost:3000 and login with admin/admin123.
You have two options for dashboard creation:
Import Basic Custom Dashboard:
- Click the "+" icon in the left sidebar → "Import"
- Click "Upload JSON file"
- Select:
k8s/custom-dashboard.json - Click "Load"
- Verify Data Source shows: "Prometheus (default)"
- Click "Import"
Import Advanced Production Dashboard:
- Click the "+" icon in the left sidebar → "Import"
- Click "Upload JSON file"
- Select:
k8s/advanced-custom-dashboard.json - Click "Load"
- Verify Data Source shows: "Prometheus (default)"
- Click "Import"
Import Comprehensive Dashboard (Recommended - Shows All Metrics):
- Click the "+" icon in the left sidebar → "Import"
- Click "Upload JSON file"
- Select:
k8s/comprehensive-dashboard.json - Click "Load"
- Verify Data Source shows: "Prometheus (default)"
- Click "Import"
💡 Tip: The comprehensive dashboard includes all available metrics in a single view with:
- Application health status
- HTTP request rates and error rates
- Response time percentiles
- Memory and CPU usage
- Database connections
- Real-time error tracking
Create a new dashboard:
- Click the "+" icon and select "Create Dashboard"
- Click "Add a new panel"
- Add these panels one by one:
Panel 1: Pod CPU Usage
# Query:
rate(container_cpu_usage_seconds_total{namespace="humor-game"}[5m])
# Panel title: "Pod CPU Usage"
# Unit: "percent (0.0-1.0)"Panel 2: Pod Memory Usage
# Query:
container_memory_usage_bytes{namespace="humor-game"}
# Panel title: "Pod Memory Usage"
# Unit: "custom units: bytes"Panel 3: HTTP Request Rate
# Query:
rate(nginx_ingress_controller_requests[5m])
# Panel title: "HTTP Requests per Second"
# Unit: "reqps"Panel 4: Pod Status
# Query:
kube_pod_status_phase{namespace="humor-game"}
# Panel title: "Pod Status"
# Visualization: "Stat"Instead of building dashboards from scratch, import our pre-built production-ready dashboards:
Option 1: Import Basic Custom Dashboard
# 1. In Grafana, click the "+" icon → "Import"
# 2. Click "Upload JSON file"
# 3. Select: k8s/custom-dashboard.json
# 4. Click "Load"
# 5. Verify Data Source: Should show "Prometheus (default)"
# 6. Click "Import"Option 2: Import Advanced Production Dashboard
# 1. In Grafana, click the "+" icon → "Import"
# 2. Click "Upload JSON file"
# 3. Select: k8s/advanced-custom-dashboard.json
# 4. Click "Load"
# 5. Verify Data Source: Should show "Prometheus (default)"
# 6. Click "Import"Now that you've imported the dashboards, you need to generate traffic to see actual data. Here are the tested working steps:
Step 6a: Start Required Port-Forwards
# These should already be running from previous steps, but if not:
kubectl port-forward svc/prometheus 9090:9090 -n monitoring &
kubectl port-forward svc/grafana 3000:3000 -n monitoring &
kubectl port-forward svc/backend 3001:3001 -n humor-game &Step 6b: Populate Sample Metrics (Recommended)
# Run the metrics population script
chmod +x scripts/populate-game-metrics.sh
./scripts/populate-game-metrics.sh
# Expected output:
# - 1000+ HTTP requests generated
# - App health metrics populated
# - Error metrics for testingExpected Results After Running Script:
✅ Metrics population complete!
📊 HTTP Requests: 1163
📊 Active Games: 0
📊 App Health: 1 (healthy)
💡 Available working metrics:
• http_requests_total
• http_errors_total
• http_request_duration_seconds
• app_health_status
• app_memory_usage_bytes
• app_cpu_usage_percent
• database_connections_currentStep 6c: Verify Metrics in Prometheus
# Check HTTP requests
curl -s 'http://localhost:9090/api/v1/query?query=http_requests_total' | jq '.data.result[0].value[1]'
# Check app health
curl -s 'http://localhost:9090/api/v1/query?query=app_health_status' | jq '.data.result[0].value[1]'If you see "No data" in your Grafana dashboard panels, follow these steps:
Step 7a: Check Panel Queries
- Click on the panel showing "No data"
- Click "Edit" (pencil icon)
- In the query editor, replace non-working queries with these tested working queries:
Working Panel Queries:
# Panel 1: HTTP Request Rate (WORKING)
rate(http_requests_total[5m])
# Panel 2: Application Health Status (WORKING)
app_health_status
# Panel 3: Error Rate (WORKING)
rate(http_errors_total[5m])
# Panel 4: Response Time (WORKING)
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
# Panel 5: Memory Usage (WORKING)
app_memory_usage_bytes / 1024 / 1024
# Panel 6: Database Connections (WORKING)
database_connections_currentStep 7b: Refresh and Verify
# After updating queries:
1. Click "Apply" to save panel changes
2. Return to dashboard view
3. Set time range to "Last 15 minutes"
4. Click "Refresh" button
5. Generate more traffic if needed: ./scripts/populate-game-metrics.shMonitoring Stack Status:
NAME READY STATUS RESTARTS AGE
prometheus-7c8b7c8b7c8b 1/1 Running 0 15m
grafana-9d8e7d6c5b-def34 1/1 Running 0 20m
Prometheus Targets Page (/targets):
- Should show multiple
kubernetes-podstargets - All targets should display "UP" status
- Namespace should show
humor-gamefor your app pods
Grafana Dashboard with 4 Panels:
- Panel 1: Pod CPU Usage showing real-time data
- Panel 2: Pod Memory Usage with stable values
- Panel 3: HTTP Request Rate with traffic spikes
- Panel 4: Pod Status showing all pods as healthy
Expected Output:
✅ Prometheus: 5+ targets UP
✅ Grafana: All 4 panels showing data
✅ Metrics: Real-time updates during load testing
✅ RBAC: No permission errors in logsYour monitoring is working when:
- ✅ Prometheus collects metrics at
http://localhost:9090 - ✅ Grafana shows dashboards at
http://localhost:3000 - ✅ Login to Grafana works with
admin/admin123 - ✅ Custom dashboards imported successfully
- ✅ HTTP request metrics show real data (1000+ requests)
- ✅ Error tracking works (404s appear in dashboard)
- ✅ Prometheus targets page shows all services "UP"
- ✅ Metrics update in real-time when you generate traffic
Cause: Dashboard queries looking for metrics that don't exist yet Command to confirm: Check what metrics are actually available Fix:
# Check available metrics
curl -s 'http://localhost:9090/api/v1/label/__name__/values' | jq '.data[]' | grep -E "(http_|app_|game_)"
# Generate sample data
./scripts/populate-game-metrics.sh
# Update dashboard queries to use working metrics:
# - Change game_scores_total to http_requests_total
# - Change unique_users_total to app_health_status
# - Use rate(http_requests_total[5m]) for request rateCause: Prometheus not scraping targets or data source not configured Command to confirm: Go to http://localhost:9090/targets Fix:
# Check Prometheus is scraping targets
# Go to http://localhost:9090/targets
# All targets should show "UP" status
# Verify backend pod has annotations
kubectl get pod -n humor-game -o yaml | grep prometheus.io
# Check if metrics endpoint responds
curl -s http://localhost:3001/metrics | head -10Cause: Prometheus data source not working or no metrics collected Command to confirm: Test Prometheus data source in Grafana Fix:
# Test Prometheus data source in Grafana
# Go to Configuration -> Data Sources -> Test
# Should show "Data source is working"
# Check Prometheus has data
# In Prometheus UI, try query: up
# Should return 1 for healthy targetsCause: Incorrect namespace in queries or no traffic generated Command to confirm: Verify correct namespace in queries Fix:
# Verify correct namespace in queries
# Query should include: {namespace="humor-game"}
# Check metric names are correct
# In Prometheus, use "Metrics" dropdown to see available metrics
# Generate some traffic to create data
curl -H "Host: gameapp.local" http://localhost:8080/api/healthCause: RBAC configuration issues or resource constraints
Command to confirm: kubectl get pods -n monitoring
Fix:
# Check RBAC configuration
kubectl get serviceaccount -n monitoring
kubectl get clusterrole | grep prometheus
# If missing, apply RBAC
kubectl apply -f k8s/prometheus-rbac.yamlCause: Port conflicts or processes already using ports
Command to confirm: lsof -i :3000 and lsof -i :9090
Fix:
# Kill conflicting processes
lsof -ti:3000 | xargs kill -9
lsof -ti:9090 | xargs kill -9
# Restart port-forwards
kubectl port-forward -n monitoring svc/grafana 3000:3000 &
kubectl port-forward -n monitoring svc/prometheus 9090:9090 &If you need to start over or fix issues:
# Remove monitoring stack
kubectl delete namespace monitoring
# Remove specific monitoring components
kubectl delete deployment prometheus -n monitoring
kubectl delete deployment grafana -n monitoring
# Reset Grafana to factory defaults
kubectl exec -it deployment/grafana -n monitoring -- rm -rf /var/lib/grafana/*
# Restart monitoring services
kubectl rollout restart deployment/prometheus -n monitoring
kubectl rollout restart deployment/grafana -n monitoring
# Check monitoring status
kubectl get pods -n monitoring
kubectl logs -n monitoring -l app=prometheus
kubectl logs -n monitoring -l app=grafanaFollow these tested working steps to validate your monitoring:
# Check monitoring pods
kubectl get pods -n monitoring
# Expected: prometheus and grafana pods "1/1 Running"
# Check port-forwards are active
lsof -i :9090 -i :3000 -i :3001
# Expected: Should show active connections# Test Prometheus is collecting metrics
curl -s 'http://localhost:9090/api/v1/query?query=up' | jq '.data.result | length'
# Expected: Should return number > 5 (multiple targets)
# Check specific app metrics
curl -s 'http://localhost:9090/api/v1/query?query=http_requests_total' | jq '.data.result[0].value[1]'
# Expected: Should return a number (request count)# Test Grafana health
curl -s http://localhost:3000/api/health | jq '.database'
# Expected: "ok"
# Login test: Go to http://localhost:3000
# Username: admin, Password: admin123
# Expected: Successful login to Grafana interface# Run the tested metrics script
./scripts/populate-game-metrics.sh
# Verify metrics appear in Prometheus
curl -s 'http://localhost:9090/api/v1/query?query=http_requests_total' | jq '.data.result[0].value[1]'
# Expected: Number should increase (1000+)
# Check in Grafana dashboard
# Expected: Panels should show data within 30 secondsThe Three Pillars of Observability:
- Metrics: Numerical data over time (CPU, memory, request rates)
- Logs: Event records with context (error messages, user actions)
- Traces: Request flow through distributed services
Key Metrics Categories:
- RED Metrics: Rate, Errors, Duration (user-facing performance)
- USE Metrics: Utilization, Saturation, Errors (resource health)
- Business Metrics: Game sessions, user signups, revenue
You've implemented enterprise observability:
- Metrics collection with Prometheus for time-series data
- Data visualization with Grafana for operational dashboards
- Custom dashboards tailored to your application's needs
- Load testing to validate monitoring under stress
- Production monitoring patterns used by major technology companies
- Observability architecture that scales to thousands of services
- Dashboard creation for different stakeholder audiences
- Metrics-driven debugging to identify performance bottlenecks
- Capacity planning using historical resource utilization data
- Troubleshooting complex monitoring issues with systematic approaches
Observability milestone completed successfully. Prometheus and Grafana running, dashboards populated, ready for 06-gitops.md.