06: Observability Stack (Prometheus, Grafana, Loki, Promtail, Jaeger)¶
6.1 Introduction¶
Observability is a critical foundation of any modern microservices platform. In LocalCloudLab, your system consists of:
• Kubernetes (k3s)
• Envoy Gateway
• Multiple .NET APIs (Search, Checkin, etc.)
• PostgreSQL, Redis, RabbitMQ, and other components
To understand what is happening inside such a system, you need metrics, logs, and traces. Observability gives you the capability to answer questions like:
• Is the cluster healthy?
• Are APIs responding quickly?
• Why did a request fail?
• What is the database latency?
• Which service caused a spike in errors?
• How does a request flow through all components?
This section describes how to install and configure a complete observability suite:
• **Prometheus** — metrics collector
• **Grafana** — dashboards and visualization
• **Loki** — log aggregation
• **Promtail** — log ingestion into Loki
• **Jaeger** — distributed tracing
• **OpenTelemetry** — unifying traces, metrics, and logs
• **Correlating logs + traces** inside Grafana UI
This is one of the most powerful sections in the entire LocalCloudLab book.
6.2 Observability Architecture in LocalCloudLab¶
The architecture follows modern cloud-native principles:
+------------------------+
| Grafana UI |
| Dashboards + Explore |
+-----------+------------+
|
+-------------------+-----------------------+
| | |
+----+----+ +----+----+ +----+----+ | Prometheus| | Loki | | Jaeger | | (metrics) | | (logs) | | (traces)| +----+------+ +----+------+ +----+---+ | | | | | | +-----+------+ +------+--------+ +-----+-------+ | kube-state | | Promtail | | OpenTelemetry| | metrics | | (log agent) | | (SDK) | +------------+ +---------------+ +--------------+
Signal origins:
• Kubernetes components → Prometheus + Loki
• Envoy Proxy → Prometheus metrics + access logs
• .NET APIs → Loki logs + Jaeger traces + Prometheus metrics (OTEL exporter)
• Node metrics (CPU/mem/disk) → Prometheus node exporter
By the end of this chapter, you can open Grafana and examine:
• CPU, memory, network of all nodes
• Pod restarts, failures, readiness issues
• API request latency (p50/p90/p99)
• Database latency from your .NET apps
• Full request traces across Search API → Database → Return
6.3 Installing Prometheus and Grafana (kube-prometheus-stack)¶
We will install a modern, non-deprecated, unified chart:
kube-prometheus-stack
This bundle includes:
✔ Prometheus
✔ Grafana
✔ Node exporter
✔ Kube-state-metrics
✔ Alertmanager
This is the most widely used approach for Kubernetes monitoring.
6.3.1 Add Prometheus Helm repo¶
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
6.3.2 Create monitoring namespace¶
kubectl create namespace monitoring
6.3.3 Install kube-prometheus-stack¶
helm install monitoring prometheus-community/kube-prometheus-stack -n monitoring --set grafana.service.type=LoadBalancer
MetalLB will assign a public IP for Grafana.
If you want Grafana internal-only, change LoadBalancer to ClusterIP.
6.3.4 Verify installation¶
kubectl get pods -n monitoring
You should see:
alertmanager-monitoring-kube-prom-alertmanager-0 Running
grafana-xxxxxx Running
prometheus-monitoring-kube-prom-prometheus-0 Running
kube-state-metrics-xxxxxx Running
prometheus-node-exporter-xxxxx Running
6.3.5 Accessing Grafana¶
Check service:
kubectl get svc -n monitoring | grep grafana
Example output:
monitoring-grafana LoadBalancer 172.18.255.201 80:31848/TCP
Visit in browser:
http://172.18.255.201
Default credentials:
username: admin
password: prom-operator
You should change the password immediately from Grafana UI.
6.3.6 Importing Dashboards¶
Grafana ships with built-in dashboards:
• Kubernetes / Compute Resources / Node
• Kubernetes / Compute Resources / Pod
• API Server
• Node Exporter
• etc.
You can also import community dashboards, such as:
• Envoy Proxy metrics
• OpenTelemetry Collector metrics
• .NET runtime metrics
• PostgreSQL dashboards
We will configure these later.
(End of Part 1 — Part 2 will cover Loki, Promtail, log pipeline setup, and integration with Grafana.)
6.4 Installing Loki (Log Aggregation)¶
Loki is a horizontally-scalable, cost‑efficient, and highly efficient log aggregation system created by Grafana Labs. It is designed to work similarly to Prometheus:
• Prometheus → metrics
• Loki → logs
But unlike Elasticsearch or Splunk, Loki does not index log content, only labels. This makes Loki extremely fast and lightweight — perfect for LocalCloudLab.
6.4.1 Why Loki for LocalCloudLab?¶
✔ Works seamlessly with Grafana
✔ Very low storage requirements
✔ Ideal for microservices
✔ High performance
✔ Perfect integration with Promtail
In LocalCloudLab:
• .NET APIs send logs to stdout → containerd → Promtail → Loki
• Loki stores the logs
• Grafana Explore provides powerful search capabilities
• Logs include TraceIds → used for correlation with Jaeger traces
Loki is also extremely easy to operate inside k3s.
6.4.2 Install Loki with Helm (non-deprecated chart)¶
We install the official Helm chart for Loki single binary mode, which is the simplest and fits LocalCloudLab perfectly.
Add the repo:
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
Install:
helm install loki grafana/loki -n monitoring --create-namespace --set singleBinary.enabled=true
Verify:
kubectl get pods -n monitoring | grep loki
6.4.3 Loki Storage Considerations¶
Loki stores logs in:
/var/loki/chunks
/var/loki/index
By default, Loki uses emptyDir (ephemeral). For production-like environments, you may switch to:
• persistentVolumeClaim
• filesystem storage
• object storage (S3/MinIO)
For LocalCloudLab, ephemeral storage is fine because:
• Logs are not mission-critical
• Traces + metrics cover long-term observability
• You can always export logs when needed
Later in Section 12 (Disaster Recovery) we describe how to make Loki persistent.
6.5 Installing Promtail (Log Ingestion)¶
Promtail is Loki’s log collector. It runs on every Kubernetes node to scrape:
• /var/log/containers
• /var/log/pods
• /var/log/syslog
• containerd logs
Promtail then enriches logs with Kubernetes labels and sends them to Loki.
6.5.1 Install Promtail via Helm¶
Use the official Grafana chart:
helm install promtail grafana/promtail -n monitoring --set config.lokiAddress=http://loki.monitoring.svc.cluster.local:3100/loki/api/v1/push
Verify:
kubectl get pods -n monitoring | grep promtail
6.5.2 How Promtail Reads Logs¶
Promtail scrapes logs from:
/var/log/pods/<pod>/<container>.log
Since containerd writes logs to this folder, Promtail captures everything.
It automatically parses:
• timestamps
• log level
• message
• Kubernetes labels (namespace, pod, container)
6.5.3 Adding TraceId to .NET Logs (Already Done in Previous Sections)¶
Your .NET APIs already include:
• middleware injecting TraceId into logs
• Serilog output with TraceId and SpanId
• OpenTelemetry tracing
Promtail will pick up these logs.
Later in this document, you will see how Grafana can correlate:
Logs → Traces → Metrics
All in a single UI screen.
6.6 Configuring Grafana to Use Loki¶
Once Loki and Promtail are running, you must add Loki as a data source in Grafana.
6.6.1 Login to Grafana¶
Visit:
http://<grafana-external-ip>
Login with:
admin / prom-operator
(unless you have already changed the password)
6.6.2 Add Loki Data Source¶
In Grafana UI:
→ Configuration (gear icon)
→ Data Sources
→ Add data source
→ Select “Loki”
Set URL:
http://loki.monitoring.svc.cluster.local:3100
Click Save & Test.
You should see:
Data source is working
6.6.3 Exploring Logs in Grafana¶
Go to:
Explore → Loki
Try filtering logs by namespace:
{namespace="search"}
Or by TraceId (assuming your log format includes “TraceId=”):
{app="search-api"} |= "TraceId="
This allows you to jump directly from logs to traces.
6.6.4 Useful LogQL Examples¶
All logs for Search API:¶
{app="search-api"}
Only warnings and errors:¶
{app="search-api"} |= "warn"
{app="search-api"} |= "error"
Filter by path:¶
{app="search-api"} |= "/api/search"
Filter by TraceId:¶
{namespace="search"} |= "TraceId=12345"
Count logs per level:¶
sum by (level) (count_over_time({app="search-api"}[5m]))
You will get a histogram of logs by severity.
6.7 Connecting Logs to Traces (Cross-Signal Correlation)¶
This is one of the most powerful capabilities in LocalCloudLab.
Because:
• Logs include TraceId
• Traces include TraceId
• Grafana can read both Loki and Jaeger
Grafana allows you to:
1. View a log line
2. Click “View Trace”
3. Jump directly into the full request trace in Jaeger
Or the reverse:
1. Open a trace in Jaeger
2. Click a span
3. See logs emitted during that span
This gives a production-grade debugging workflow identical to:
• AWS X-Ray
• Datadog
• NewRelic
• Elastic APM
Later in Section 6.9 we configure Jaeger fully.
(End of Part 2 — Part 3 will cover Jaeger, OpenTelemetry configs, and full logs + traces correlation.)
6.8 Installing Jaeger (Distributed Tracing)¶
Jaeger is a CNCF graduated tracing system originally developed by Uber. It provides:
• End-to-end request tracing
• Span visualization
• Latency analysis
• Service dependency graphs
• Integration with OpenTelemetry
In LocalCloudLab, Jaeger receives traces from:
• Your .NET APIs via OTLP exporter
• Envoy Gateway (optional)
• Any future microservices
We will deploy the Jaeger All-In-One Helm chart, which is perfectly suitable for a single-node environment and lightweight enough for k3s.
6.8.1 Add Jaeger Helm Repository¶
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm repo update
6.8.2 Install Jaeger All-In-One¶
helm install jaeger jaegertracing/jaeger -n monitoring --set provisionDataStore.cassandra=false
This installs:
• Jaeger collector
• Jaeger query UI
• Jaeger ingester
• Jaeger agent (deprecated but still included)
• In-memory storage (sufficient for LocalCloudLab)
Check pods:
kubectl get pods -n monitoring | grep jaeger
6.8.3 Expose Jaeger UI¶
By default, the Jaeger query service is ClusterIP. To access from your browser, patch it:
kubectl patch svc jaeger-query -n monitoring -p '{"spec": {"type": "LoadBalancer"}}'
MetalLB will assign an IP, e.g.:
172.18.255.202
6.8.4 Accessing Jaeger¶
Open:
http://172.18.255.202
You will see:
• Search traces
• Filter by service
• View spans
• Timeline waterfall UI
• Trace logs/events
Later, we will enable:
• Trace → Log correlation
• Service dependency graph
6.9 Configuring .NET APIs for OpenTelemetry Tracing¶
Your APIs already have OpenTelemetry installed, but this section describes the best practices for:
• Automatic instrumentation
• Database tracing
• Custom spans
• Correct OTLP configuration
OpenTelemetry sends traces to Jaeger via:
OTLP → Jaeger Collector → Jaeger UI
6.9.1 Required NuGet Packages¶
OpenTelemetry
OpenTelemetry.Extensions.Hosting
OpenTelemetry.Instrumentation.AspNetCore
OpenTelemetry.Instrumentation.Http
OpenTelemetry.Instrumentation.SqlClient
OpenTelemetry.Exporter.OpenTelemetryProtocol
Install:
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
6.9.2 Configure OTEL in Program.cs¶
Example:
builder.Services.AddOpenTelemetry()
.WithTracing(tpb =>
{
tpb.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("SearchAPI"))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddOtlpExporter(o =>
{
o.Endpoint = new Uri("http://jaeger-collector.monitoring.svc.cluster.local:4318");
o.Protocol = OtlpExportProtocol.HttpProtobuf;
});
});
Replace "SearchAPI" with your actual service name.
For Checkin API:
AddService("CheckinAPI")
6.9.3 Database Spans¶
OpenTelemetry automatically traces SQL queries when AddSqlClientInstrumentation is enabled.
You will see spans like:
SELECT * FROM Hotels WHERE Id = $1
INSERT INTO SearchHistory …
Each includes:
• DB latency
• Response size
• Error details
• Connection time
6.9.4 Custom Spans¶
Example:
using var span = tracer.StartActiveSpan("CalculatePrice");
span.SetAttribute("hotelId", hotelId);
await _priceService.CalculateAsync();
6.9.5 TraceId Propagation to Logs¶
You already enrich logs with:
TraceId={TraceId} SpanId={SpanId}
Promtail → Loki → Grafana Jaeger → Grafana
Grafana can now correlate logs + traces end-to-end.
6.10 Adding Jaeger as a Data Source in Grafana¶
Grafana natively supports Jaeger.
6.10.1 In Grafana UI:¶
→ Configuration → Data Sources → Add data source → Jaeger
Set URL:
http://jaeger-query.monitoring.svc.cluster.local:16686
Click Save & Test.
Status should be:
Data source is working
6.10.2 Viewing Traces in Grafana
Go to:
Explore → Jaeger
You can:
• Search for specific services
• Filter by operation names
• View end-to-end latency
• Drill into span attributes
6.11 End-to-End Observability Validation¶
Now let’s validate that:
• Logs work
• Metrics work
• Traces work
• Correlation works
6.11.1 Generate Traffic¶
From your machine:
curl https://search.hershkowitz.co.il/health
curl https://search.hershkowitz.co.il/api/search?q=test
curl https://checkin.hershkowitz.co.il/health
6.11.2 Validate Metrics in Grafana Dashboards¶
Dashboards to check:
• Kubernetes / Compute Resources / Pod
• API Server dashboard
• Node Exporter dashboard
• .NET Runtime metrics dashboard (importable)
6.11.3 Validate Logs (Loki)¶
In Grafana Explore:
{namespace="search"} |= "TraceId="
You should see logs from Search API with a TraceId.
6.11.4 Validate Traces (Jaeger)¶
In Jaeger UI:
Service → SearchAPI
Find Traces
You should see request flow:
Client → Envoy → Search API → PostgreSQL
6.11.5 Validate Log ↔ Trace Correlation¶
In Grafana Explore (Loki):
Pick a log from Search API → click “View Trace”.
Grafana should open:
Jaeger → TraceView → Spans
6.11.6 Validate Trace ↔ Log Correlation¶
Inside Jaeger span:
• Events section
• Logs section
• Attributes (TraceId, SpanId)
Click "Find logs" → Grafana search opens automatically.
6.12 Summary of Section 6¶
At this point, LocalCloudLab has a world-class observability suite:
✔ Prometheus for metrics
✔ Grafana for visualization
✔ Loki for logs
✔ Promtail for ingestion
✔ Jaeger for tracing
✔ OpenTelemetry for unified instrumentation
✔ Cross-system correlation between logs, traces, and metrics
You now have the ability to diagnose:
• Slow database queries
• API latency spikes
• Network bottlenecks
• Failing routes
• Distributed system issues
The observability stack you have built competes with:
• Datadog
• NewRelic
• Elastic Observability
• AWS X-Ray
• Azure Monitor
At a fraction of the cost—because it is all open-source.
Next section (Section 7) will cover: PostgreSQL, Schema Management, Backups, and Failover Strategy
(End of Section 06 — Complete)