Ah, Kubernetes observability. It’s the buzzword that keeps the tech world spinning, isn’t it? Everyone’s rushing to deploy the ever-popular kube-prometheus-stack, a neat little package that shoves Prometheus and Grafana into a ready-made monitoring solution for your Kubernetes workloads. On the surface, it’s like finding a unicorn that also does your laundry – seemingly the answer to all your operational prayers.
But here’s a dose of reality from your favorite curmudgeonly columnist: monitoring is not observability. And if you’re confusing the two, prepare for a bumpy ride when your clusters inevitably grow, or when that dreaded 3 AM pager decides to sing its siren song.
Consider this the inaugural dispatch in my observability series. We’re going to peel back the layers, expose the stark difference between merely monitoring and truly observing, shine a light on where kube-prometheus-stack falls short, and chart a course towards genuine Kubernetes observability.
The 3 AM Revelation: When “What” Isn’t Enough
I once consulted with a team, proud architects of microservices humming on Kubernetes. They had the works: kube-prometheus-stack deployed, Grafana dashboards that looked like works of art, and alerts meticulously configured. Everything was peachy keen until, you guessed it, 3 AM on a Tuesday. API requests started timing out like it was going out of style.
The on-call engineer, bless their soul, got paged. Prometheus, the ever-vigilant sentinel, reported CPU spikes. Grafana, the pretty picture book, showed a cascade of pod restarts. When the frantic Slack messages started flying, the team turned to me, their voices laced with desperation: “AgentKyles, do you have tools to figure out *why* these timeouts are happening?”
They then embarked on a two-hour digital archaeology expedition, manually digging through logs scattered across CloudWatch, cross-referencing recent deployments, and playing a high-stakes guessing game with database queries. The culprit? A rogue batch job, armed with an unoptimized query, ruthlessly hammering the production database.
This pattern is as familiar to me as my morning coffee. Their monitoring stack screamed, “SOMETHING IS BROKEN!” but remained stubbornly silent on the crucial question of “WHY?!” Had they possessed distributed tracing, that slow request would have been traced back to its database-destroying origin in minutes, not hours. This, my friends, is the observability gap. The profound difference? Monitoring tells you *what* broke, while observability answers *why* it broke. And bridging this gap requires a communal effort. Developers must instrument their code, making its internal workings visible. DevOps engineers must lay the groundwork to capture and expose that behavior. When both sides commit to observability, incidents don’t just get resolved faster; they become rarer, and systems become inherently more reliable.
Beyond Semantics: Monitoring vs. Observability, Deconstructed
Let’s be honest, most engineers use these terms interchangeably, like “cloud” and “someone else’s computer.” But they are distinct. Monitoring flags an issue; observability empowers you to diagnose its root cause.
- Monitoring: It’s the alarm bell. It answers “what is happening?” You meticulously collect predefined metrics (CPU, memory, disk usage) and trigger alerts when those thresholds are breached. Your alert screams: “CPU usage is 95%!” Great. Now what, Sherlock?
- Observability: This is the detective work. It answers “why is this happening?” You actively investigate, correlating interconnected data points you didn’t even realize you’d need. Which specific pod is hogging the CPU? What user request set off this chain reaction? Is a database query crawling? What nefarious change snuck into the last deployment?
The hallowed definition of observability rests upon its three pillars:
- Metrics: Numerical values, evolving over time (think CPU, latency, request counts).
- Logs: Unstructured textual records of contextual events, the digital breadcrumbs of your system.
- Traces: The intricate flow of a single request as it weaves its way across multiple services, revealing the hidden dance of your microservices.
Prometheus and Grafana are champions in the realm of metrics. They’re undeniably good at it. But to achieve true Kubernetes observability, you need all three pillars working in harmony, a symphony of data. The CNCF observability landscape is a testament to how far this ecosystem has evolved beyond mere metric-watching. If your observability strategy begins and ends with kube-prometheus-stack, you’re only holding one tiny piece of a very complex puzzle.
The Reign of the kube-prometheus-stack: A Double-Edged Sword
Let’s give credit where credit is due. kube-prometheus-stack didn’t become the default for nothing. It’s a powerhouse that delivers:
- Prometheus: Your metrics scraping maestro.
- Grafana: The canvas for your beautiful (if sometimes misleading) dashboards.
- Alertmanager: The dispatcher for your rule-based notifications.
- Node Exporter: The spy on your hardware and OS metrics.
And with Helm, you can have it up and running in a blink. This ease of deployment is precisely why it dominates Kubernetes monitoring setups today. But remember, dominance doesn’t equate to comprehensiveness.
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack
--namespace monitoring
--create-namespace
Indeed, within minutes, Prometheus is diligently scraping, Grafana is accessible on port 3000, and a gallery of pre-configured dashboards springs to life. It feels like magic, doesn’t it?
To witness this “magic”:
kubectl port-forward -n monitoring svc/kube-prometheus-stack-grafana 3000:80
Default credentials (admin / prom-operator) grant you immediate access to dashboards showcasing Kubernetes cluster health, node exporter stats, and pod resource usage. The data just flows, effortlessly.
Yet, in countless projects, I’ve seen teams proudly parade these dashboards, resplendent with their green and red lights, only to crumble during incidents. The paradox? Those impressive panels only told them *what* was broken, not *why* the wheels came off.
Navigating the Minefield: Common Pitfalls of kube-prometheus-stack
Even the most robust tools have their quirks. Kube-prometheus-stack, for all its glory, has a few lurking dangers.
The Cardinality Kraken: When Too Many Labels Crash Your Party
Cardinality is a fancy word for the number of unique time series born from combining a metric name with every possible label value. Each unique combination demands its own time series, which Prometheus dutifully (and sometimes agonizingly) stores and queries. The official Prometheus documentation on metric and label naming offers wisdom on sidestepping this beast.
Prometheus adores labels, but an overabundance can bring your cluster to its knees. If you get enthusiastic with dynamic labels like user_id or transaction_id, you’re looking at millions of time series. This isn’t just a storage headache; it’s a query performance nightmare. I’ve personally watched production clusters buckle, not under application load, but from Prometheus itself choking on an indigestible feast of metrics.
Observe this recipe for disaster, guaranteed to obliterate your Prometheus instance:
from prometheus_client import Counter
# BAD: High cardinality labels – proceed with extreme caution!
http_requests = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'user_id', 'transaction_id'] # AVOID LIKE THE PLAGUE!
)
# Imagine: 5 methods * 20 endpoints * 1000 users * 10000 transactions = 1 BILLION time series. Your Prometheus will weep.
Instead, embrace the Zen of low-cardinality labels, relegating high-cardinality data to more suitable homes:
from prometheus_client import Counter
# GOOD: Low cardinality labels – a path to sanity
http_requests = Counter(
'http_requests_total',
'Total HTTP requests',
['method', 'endpoint', 'status_code'] # A sensible, limited set of values
)
# Now you have a manageable: 5 methods * 20 endpoints * 5 status codes = a mere 500 time series. Breathe easy.
Curious if your Prometheus is secretly battling a cardinality beast? This PromQL query will reveal all:
count({__name__=~".+"}) by (__name__)
If you spot metrics with hundreds of thousands of series, you’ve found your kraken.
Scaling Everest with a Hand Lens: Prometheus’s Scalability Struggle
For your cozy little cluster, a single Prometheus instance is perfectly fine. But when you venture into the sprawling, multi-cluster enterprise landscape, it morphs into a full-blown nightmare. Without federation or clever sharding, Prometheus simply doesn’t scale gracefully. If you’re architecting multi-cluster infrastructures, a solid understanding of Kubernetes deployment patterns is non-negotiable for keeping your monitoring components alive and kicking.
For multi-cluster behemoths, you’ll inevitably gravitate towards Prometheus federation, as detailed in the Prometheus federation documentation. Here’s a sneak peek at a basic configuration for a global Prometheus instance that pulls data from its cluster-specific siblings:
scrape_configs:
- job_name: 'federate'
scrape_interval: 15s
honor_labels: true
metrics_path: '/federate'
params:
'match[]':
- '{job="kubernetes-pods"}'
- '{__name__=~"job:.*"}'
static_configs:
- targets:
- 'prometheus-cluster-1.monitoring:9090'
- 'prometheus-cluster-2.monitoring:9090'
- 'prometheus-cluster-3.monitoring:9090'
Even with federation, storage limits loom large. A single Prometheus instance tends to gasp for air beyond 10-15 million active time series.
The Symphony of Annoyance: Alert Fatigue and the Call of the Void
Kube-prometheus-stack arrives pre-loaded with a veritable arsenal of default alerts. Initially, they feel helpful, like a diligent assistant. But rapidly, they generate a cacophony of alert fatigue. Your engineers find themselves drowning in a sea of notifications that do absolutely nothing to help them actually resolve issues. It’s like listening to a perpetual smoke detector when all you’re doing is toasting bread.
To survey your current alert landscape:
kubectl get prometheusrules -n monitoring
You’ll probably discover dozens of pre-configured alerts. Behold, a classic example of a particularly noisy alert:
- alert: KubePodCrashLooping
annotations:
description: 'Pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping'
summary: Pod is crash looping.
expr: |
max_over_time(kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"}[5m]) >= 1
for: 15m
labels:
severity: warning
The flaw? This alert shrieks for *every single pod* in CrashLoopBackOff, including those in development namespaces that are meant to restart, or during expected deployment cycles. The result? Alert spam, turning your engineers into jaded notification-muters.
A more enlightened approach involves calibrating alerts based on actual criticality:
- alert: CriticalPodCrashLooping
annotations:
description: 'Critical pod {{ $labels.namespace }}/{{ $labels.pod }} is crash looping'
summary: Production-critical pod is failing.
expr: |
max_over_time(kube_pod_container_status_waiting_reason{
reason="CrashLoopBackOff",
namespace=~"production|payment|auth"
}[5m]) >= 1
for: 5m
labels:
severity: critical
Now, you only get paged when actual critical systems are in distress. This dramatically boosts your signal-to-noise ratio, empowering faster, more targeted responses.
Dashboard Delusions: Pretty Pictures That Miss the Point
Grafana dashboards are undeniably impressive. They’re visually appealing, slick, and make you feel like you’re in command. But many of them, for all their splendor, merely highlight symptoms. High CPU. Failing pods. Dropped requests. They’re telling you *what* is wrong, but they remain frustratingly silent on *why*. This is the chasm between monitoring and observability, writ large on your shiny panels.
Here’s a quintessential PromQL query you’ll find adorning many a Grafana dashboard:
# Shows CPU usage percentage
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
This pronounces the what: “CPU is at 95%!” But it offers zero insight into the why. Which unruly process? What specific pod is misbehaving? What user action triggered this mayhem? What external dependency crumbled?
You might try a bit of investigative drilling with more queries:
# Top 10 pods by CPU usage
topk(10, rate(container_cpu_usage_seconds_total[5m]))
Even this gives you a pod name, but it still falls short. It won’t reveal the specific request path, the user who initiated it, or the external dependency that brought it all down. Without distributed tracing, you’re essentially playing a high-stakes game of “guess the culprit.” You’ll find yourself on Slack, desperately typing, “Did anyone deploy something?” or “Is the database suddenly taking a siesta?”
Why Your kube-prometheus-stack Alone is a Half-Measure for Kubernetes Observability
Alright, let’s get opinionated, shall we? Kube-prometheus-stack is monitoring, not observability. It’s a robust foundation, yes, but it’s far from the finished cathedral. True Kubernetes observability demands:
- Logs (think Loki for a lean approach, or Elasticsearch for the full meal deal).
- Traces (Jaeger or Tempo will show you the path of every request).
- Correlated context (the ability to link disparate data points, not just view isolated metrics).
Without these additional heavy hitters, you’re condemned to a life of firefighting, armed only with a blurry, fragmented view of your systems.
Building a Path Towards Observability: Beyond the Basics
So, how do we finally bridge this pesky observability gap? It’s not about ditching your beloved kube-prometheus-stack, but rather expanding its capabilities:
- Start with kube-prometheus-stack, but crucially, acknowledge its limitations.
- Integrate a centralized logging solution (Loki, Elasticsearch, or whatever your preferred poison).
- Embrace distributed tracing with tools like Jaeger or Tempo.
- And prepare yourselves for the next frontier: OpenTelemetry.
Here’s how to integrate Loki, your new best friend for centralized logging, alongside your existing Prometheus setup:
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
# Install Loki for log aggregation – because your logs deserve a home
helm install loki grafana/loki
--namespace monitoring
--create-namespace
For the intricate dance of distributed tracing, Tempo is a seamless partner with Grafana:
# Install Tempo for traces – follow the request breadcrumbs
helm install tempo grafana/tempo
--namespace monitoring
Now, the magic happens in Grafana. Configure it to pull data from both Loki and Tempo:
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3100
With this setup, you unlock the ability to seamlessly pivot from a CPU spike in Prometheus, jump to the relevant logs in Loki, and then follow the specific request through its trace in Tempo. *This* is the moment when mere monitoring starts its glorious transformation into true observability.
And then there’s OpenTelemetry. It’s the game-changer, offering a vendor-neutral standard to capture metrics, logs, and traces through a single, unified pipeline. Instead of a Frankenstein’s monster of bolted-together, siloed tools, you get a cohesive foundation. But that, my eager readers, is a deep dive for another day, specifically the next post on OpenTelemetry in Kubernetes.
The Grand Finale: Beyond the Metrics, Towards Insight
Let’s recap: Kubernetes observability is far more sophisticated than just Prometheus and Grafana dashboards. While kube-prometheus-stack offers a formidable monitoring foundation, it leaves gaping holes in your ability to collect and correlate logs and traces. Relying solely on it will lead you down a thorny path of cardinality explosions, relentless alert fatigue, and dashboards that tell you *what* went wrong but remain stubbornly mute on *why*.
True Kubernetes observability demands a fundamental shift in mindset. You’re not merely collecting data points anymore. You’re engineering a system designed to answer questions you didn’t even know you’d need to ask. When that inevitable incident strikes at 3 AM, your goal isn’t just to see a red graph. It’s to trace that agonizingly slow API call from the user’s browser, through every microservice, all the way down to the database query that’s causing the timeout. Prometheus, in glorious isolation, simply cannot get you there.
To ascend to the echelons of true Kubernetes observability:
- Humbly accept kube-prometheus-stack for what it is: monitoring, not the full observability picture.
- Actively integrate logs and traces into your operational pipeline.
- Guard vigilantly against metric cardinality runaways and the cacophony of alert noise.
- Strategically plan your migration towards OpenTelemetry pipelines for a truly unified solution.
The monitoring foundation you lay today will directly dictate how swiftly and effectively you can extinguish those future infernos. Start with kube-prometheus-stack, recognize its inherent boundaries, and meticulously map your journey toward comprehensive observability. Your future self, and especially your sleep-deprived on-call team, will thank you profusely.
Stay tuned for the next installment, where we’ll demystify deploying OpenTelemetry in Kubernetes for centralized observability. That, my friends, is where the real transformation of your operational sanity begins.
Read next: OpenTelemetry in Kubernetes for centralized observability.
So, the next time your dashboard flashes red, will you merely stare at the symptom, or will you have the tools to dive deep and uncover the cure?




