OBSERVE
Platform Ops Kubernetes Observability
Issue #051 · July 2026

OBSERVABILITY
STACK

Metrics · Logs · Traces · Alerting · OpsCore

The three pillars — metrics, logs, and traces — plus the alerting layer that turns them into pages, and a look at how an internal platform like OpsCore consolidates all of it into one pane of glass.

15
Concepts Covered
3
Pillars
1
Platform Tie-In
Metrics3
PrometheusPromQLGrafana
Logs3
LokiELKLogQL
Traces3
OpenTelemetryJaegerSpans
Alerting3
AlertmanagerSLOsBurn Rate
OpsCore3
Flask APIUnified DashboardCorrelation
Prometheus PromQL Grafana Loki Elasticsearch Logstash Kibana OpenTelemetry Jaeger Alertmanager Error Budget OpsCore Prometheus PromQL Grafana Loki Elasticsearch Logstash Kibana OpenTelemetry Jaeger Alertmanager Error Budget OpsCore

THE THREE PILLARS (+ 2)

Metrics tell you something is wrong. Logs tell you what. Traces tell you where. Alerting decides when to wake someone up. OpsCore is where all four live together.

🔴 Metrics
Prometheus Server
kube-state-metrics
node-exporter
Grafana Dashboards
Remote Write / Thanos
🟠 Logs
Fluent Bit / Promtail
Loki / Elasticsearch
Logstash Pipeline
Kibana / Grafana Explore
Log Retention Policy
🔵 Traces
OTel SDK / Instrumentation
OTel Collector
Jaeger Backend
Trace Context Propagation
Span Sampling
🟢 Alerting
Alertmanager
Recording Rules
SLO Burn-Rate Alerts
Routing / Silences
On-Call Escalation
🟣 OpsCore
Flask API Layer
Metrics/Log Aggregator
Unified Dashboard
Cross-Signal Correlation
Incident Timeline
Deep-Dive

OBSERVABILITY REFERENCE

Click any pillar to explore concepts, queries, and production-tested guidance.

METRICS — PROMETHEUS + GRAFANA
Pull-based time-series collection and the dashboards built on top of it
3 Concepts
📊
How Prometheus Actually Collects Data
Prometheus scrapes — it doesn't receive pushes. Every target exposes a /metrics HTTP endpoint that gets polled on an interval.
Must Know
Must Know
ServiceMonitor (Prometheus Operator)
yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
spec:
  selector: {matchLabels: {app: billing-api}}
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics
Core Exporters
🔴node-exporter: host-level CPU, memory, disk, network — one per node
🔴kube-state-metrics: object state — pod counts, deployment status, PVC phase
🟠cAdvisor: built into kubelet — per-container resource usage
Metric Types
1
Counter — only goes up (request count, errors total)
2
Gauge — goes up or down (memory usage, queue depth)
3
Histogram — buckets of observations (request latency)
🔎
PromQL Patterns Worth Memorizing
A handful of query shapes cover most dashboards and alerts you'll ever write.
Important
Important
PromQL
# Request rate over 5 minutes
rate(http_requests_total[5m])
# p99 latency from a histogram
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
# Error ratio
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
# Pods not Ready right now
kube_pod_status_ready{condition="false"} == 1
📈
Long-Term Storage & Grafana
Prometheus itself is not built for years of retention — that's where Thanos/Mimir and Grafana's dashboarding layer come in.
Scaling
Recommended
Retention Problem
🔵Local Prometheus TSDB is fine for 15–30 days; longer needs remote storage
🔵Thanos or Mimir add object-storage-backed long-term retention + global query view across clusters
Grafana Practices
1
Use variables ($namespace, $pod) — one dashboard, many contexts
2
Version dashboards as JSON in git, provision via ConfigMap or the Grafana operator
3
Keep one "golden signals" dashboard per service: latency, traffic, errors, saturation
LOGS — LOKI + ELK
Two philosophies for the same problem: finding the one line that explains what happened
3 Concepts
⚖️
Loki vs the ELK Stack
Loki indexes only labels and stores log content as compressed chunks. Elasticsearch full-text-indexes every field. That single design choice explains most of the cost/capability trade-off.
Must Know
Must Know
TraitLokiELK (Elasticsearch)
IndexingLabels only — cheapFull-text on every field — expensive
Query languageLogQLKQL / Lucene / DSL
Storage costLow (object storage, chunks)High (inverted index overhead)
Best fitKubernetes-native, Grafana shops, cost-sensitiveComplex free-text search, security/SIEM use cases
Native toGrafana ecosystemKibana
🚚
Shipping Logs Out of Pods
Neither stack reads pod stdout directly in production — a collector agent runs on every node and ships it.
Important
Important
Collector Options
🟠Promtail — Loki's own DaemonSet agent, tails /var/log/pods, attaches pod/namespace labels
🟠Fluent Bit — lightweight, ships to either Loki, Elasticsearch, or both
🔵Logstash — heavier, does parsing/enrichment/filtering in the pipeline itself
Structured logging (application side)
json
{"level":"error","service":"billing-api",
 "trace_id":"a1b2c3","msg":"payment gateway timeout"}
🔍
LogQL Query Patterns
LogQL wraps a label selector — same syntax family as PromQL — around a text/metric pipeline.
Query
Recommended
LogQL
# All error lines from the billing-api pods
{app="billing-api"} |= "error"
# Parse JSON and filter on a field
{app="billing-api"} | json | level="error"
# Rate of error lines over time (metric from logs)
sum(rate({app="billing-api"} |= "error" [5m]))
DISTRIBUTED TRACING — OTEL + JAEGER
Following one request across every service it touched
3 Concepts
🕸️
Spans, Trace Context & Propagation
A trace is a tree of spans. Each span represents one unit of work; context propagation is what stitches spans from different services into the same trace.
Must Know
Must Know
Core Terms
🔴Trace ID: one ID per end-to-end request
🔴Span: one operation — an HTTP call, a DB query — with start/end time
🟠Parent/child spans: build the call tree — CMP calling NGW shows up as a child span
Propagation header (W3C Trace Context)
http
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
# version - trace-id - parent-span-id - flags
🛰️
OpenTelemetry Collector
A vendor-neutral pipeline: receive traces (and metrics/logs) from apps, process them, export to Jaeger, Tempo, or any OTLP-compatible backend.
Important
Important
otel-collector-config.yaml
yaml
receivers: {otlp: {protocols: {grpc: {}, http: {}}}}
processors: {batch: {}, memory_limiter: {limit_mib: 512}}
exporters: {jaeger: {endpoint: "jaeger-collector:14250"}}
service: {pipelines: {traces: {receivers: [otlp], processors: [batch], exporters: [jaeger]}}}
🎚️
Sampling Strategy
Tracing every request at scale is expensive to store and query. Sampling decides which traces survive.
Cost Control
Recommended
Sampling Strategies
🔵Head-based: decide at trace start (e.g. 10% of traces) — cheap, simple, might miss rare errors
🔵Tail-based: decide after seeing the whole trace — always keep errors/slow traces, drop the boring fast ones
Rule of Thumb
1
Always sample 100% of error traces regardless of overall rate
2
Start with head-based at 5–10% for high-volume services, move to tail-based once volume justifies it
ALERTING & SLOs
Turning signals into pages — without paging anyone at 3am for nothing
3 Concepts
🚨
Alertmanager Routing
Prometheus fires alerts based on rules; Alertmanager decides who gets notified, how, and whether to group or silence them.
Must Know
Must Know
alert-rule.yaml + routing
yaml
- alert: HighErrorRate
  expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
  for: 10m
  labels: {severity: critical, team: billing-ops}
  annotations: {summary: "Error rate above 5% for 10m"}
🔥
SLOs & Error Budget Burn Rate
A static threshold alert ("errors > 5%") tells you nothing about urgency. Burn-rate alerts ask: at this rate, how soon do we blow the monthly budget?
Important
Important
The Idea
🟠SLO: 99.9% availability = 43m of allowed downtime per month ("error budget")
🟠Fast burn (budget gone in hours) → page immediately
🔵Slow burn (budget gone in days) → ticket, not a page
Practical Guidance
1
Define SLOs on user-facing symptoms (latency, error rate) — not internal metrics like CPU
2
Use multi-window burn-rate rules (e.g. 1h + 5m) to catch both fast and slow burns
🔕
Reducing Alert Fatigue
The fastest way to lose an on-call rotation's trust is a pager that fires for things nobody needs to act on.
Operations
Recommended
1
Every alert needs a linked runbook — no page without a "here's what to do"
2
Group related alerts (group_by: [alertname, cluster]) instead of one page per pod
3
Review fired-but-not-actioned alerts monthly — delete or downgrade anything nobody acts on
TYING IT TOGETHER: OPSCORE
Why a dedicated internal Flask observability layer earns its keep
3 Concepts
🧭
Why Not Just Grafana?
Grafana visualizes whatever's in Prometheus/Loki/Jaeger. It doesn't know your org's billing pipelines, VIL-CMP deployment topology, or which team owns which alert.
Must Know
Must Know
What a Platform Layer Adds
🔴Domain context: maps raw metrics to business entities (CMP module, NGW gateway, billing run)
🟠Ownership routing: knows which team owns which service without hardcoding it into every alert rule
🔵A single API surface other internal tools can query instead of hitting Prometheus/Loki directly
Typical Flask Layer Shape
1
REST endpoints wrap PromQL/LogQL queries — frontend never talks to Prometheus directly
2
A background worker polls Alertmanager and persists incident history to a DB for trend reporting
3
Auth/RBAC sits in front — engineers only see the services their team owns
🔗
Cross-Signal Correlation
The highest-value feature of a unified platform: jumping from a metric spike straight to the logs and trace that explain it, with no manual timestamp matching.
Important
Important
1
Propagate the same trace_id into structured logs — makes "metric spike → logs → trace" a single click, not three tabs
2
Store an incident timeline: alert fired → first log match → resolution — this is what turns into postmortem data automatically
3
Expose a single /incidents/<id> view that pulls metrics graph + log window + trace waterfall together
🛠️
Build vs Buy Trade-off
A commercial APM (Datadog, New Relic) gives you this out of the box — at a price and with less control over data residency and custom domain modeling.
Decision
Recommended
FactorInternal Platform (OpsCore-style)Commercial APM
Cost modelInfra + engineering timePer-host / per-GB licensing
Domain fitExact match to internal services & ownershipGeneric, needs custom tagging
Data residencyFully on-prem/internalVendor cloud, unless self-hosted tier
Time to valueSlower — you build itFast — mostly configuration
OC
Built This Pattern Into OpsCore
This is the exact model behind the internal observability platform I lead — a Python/Flask layer sitting on top of the metrics/logs/traces stack, adding the domain context and ownership routing that Grafana alone doesn't give you.
Python / Flask Prometheus + Loki Incident Timeline

COMMAND CHEATSHEET

Prometheus
kubectl -n monitoring port-forward svc/prometheus 9090
kubectl get servicemonitors -A
promtool check rules alert.rules.yaml
curl localhost:9090/api/v1/targets
Loki / Logs
logcli query '{app="billing-api"}'
kubectl -n monitoring logs -l app=promtail
kubectl logs -f deploy/billing-api --since=10m
Tracing
kubectl -n observability port-forward svc/jaeger-query 16686
kubectl logs -l app=otel-collector --tail=50
Alertmanager
amtool alert query
amtool silence add alertname=HighErrorRate --duration=2h
kubectl -n monitoring port-forward svc/alertmanager 9093
Grafana
kubectl -n monitoring port-forward svc/grafana 3000
curl -H "Authorization: Bearer $TOKEN" grafana/api/dashboards/uid/<uid>
General
kubectl top pods -A --sort-by=memory
kubectl get events -A --sort-by=.lastTimestamp
VA
Vishal Abhinav
Platform Ops Engineer · @6D Technologies · Ops Newsletter — Issue #051