Collect and Query Logs with Loki
Learn to collect and query logs with Loki in Kubernetes. This lesson covers setting up Loki, querying logs via LogQL, and troubleshooting common issues. Hands-on exercises included.
Focus: collect and query logs with loki
Logging is the backbone of debugging and observability in Kubernetes, but traditional tools like the ELK stack can be expensive and complex to manage. When you're running hundreds of pods, collecting every log line becomes a cost and performance nightmare. Loki offers a smarter way: it only indexes metadata (labels) and stores logs in compressed chunks, making log storage affordable at scale. In this lesson, you will learn to collect and query logs with Loki — a Prometheus-inspired, horizontally scalable, multi-tenant log aggregation system designed for cost-effective observability in Kubernetes.
The problem this lesson solves
Kubernetes pods can restart, scale up, or be evicted at any moment. Without a centralized log system, you'd have to chase logs across multiple nodes using kubectl logs — which is slow, non-persistent, and impossible for historical analysis. Traditional log solutions (like Elasticsearch) index every log line, leading to massive storage costs and complex operations. Loki solves this by storing logs in a compressed format and indexing only the labels — dramatically reducing storage and query costs while integrating seamlessly with Prometheus and Grafana.
Pain points addressed
- Cost: Storing terabytes of logs in Elasticsearch is expensive. Loki's compression and minimal indexing cut costs by up to 80%.
- Speed: Queries run against compressed chunks, but metadata filtering makes them fast — not as fast as full-text search, but good enough for most debugging.
- Simplicity: Loki uses Prometheus-style labels (service, namespace, pod) to organize logs, so you can reuse existing alerting and dashboards.
Core concept / mental model
Think of Loki as Prometheus for logs. Instead of storing every log line as a searchable document (like Elasticsearch), Loki stores chunks of log data on object storage (S3, GCS, MinIO) and indexes only the labels. When you query, Loki pulls only the chunks that match the label filter, then scans the raw log lines inside those chunks.
Key terminology
- Chunk: A compressed block of log data (usually 512 KB). Multiple chunks make up a log stream.
- Stream: A unique combination of labels (e.g.,
{app="nginx", namespace="default"}). Each stream gets its own set of chunks. - Ingester: Receives logs and builds chunks in memory, then flushes them to storage.
- Querier: Reads chunks from storage and runs LogQL queries.
- Distributor: Receives logs from Promtail or other agents and forwards them to the correct ingester.
Pro Tip: Because Loki doesn't index the log content, it's perfect for high-volume logs (like Kubernetes audit logs). Avoid using it for full-text search over all logs — that's Elasticsearch's domain.
How it works step by step
- Log collection: Deploy Promtail as a DaemonSet on each Kubernetes node. Promtail reads log files (usually
/var/log/pods/*.log), attaches labels (namespace, pod name, container name), and sends them to Loki's distributor. - Ingestion: The distributor hashes the labels to determine which ingester should own the stream. The ingester appends logs to an in-memory chunk and periodically flushes it to object storage (e.g., 1 MB or 1 hour max).
- Storage: Chunks are stored in a compressed format (often with gzip or snappy) in object storage. A boltdb-shipper index keeps track of which labels belong to which chunks.
- Querying: When you run a LogQL query, the querier first looks up the index to find matching chunks, then decompresses and scans the actual log lines. Results are returned to Grafana or the command line.
LogQL in a nutshell
LogQL is like PromQL but for logs. You can filter, aggregate, and extract patterns.
# Select all logs from app=nginx namespace=default
{app="nginx", namespace="default"}
# Filter lines containing 'error'
{app="nginx"} |= "error"
# Count log lines per minute by status code
rate({app="nginx"}[5m])
Hands-on walkthrough
We'll deploy Loki together with Promtail and Grafana on a Kubernetes cluster (minikube or any cluster). This is a production-ready setup using the official Helm chart.
Step 1: Add Helm repos
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
Step 2: Deploy Loki (single binary mode for simplicity)
We'll use a minimal configuration for this walkthrough. Create a file named loki-values.yaml:
# loki-values.yaml
auth_enabled: false
ingester:
chunk_idle_period: 5m
chunk_retain_period: 30s
max_chunk_age: 1h
storage_config:
boltdb_shipper:
active_index_directory: /tmp/loki/boltdb-shipper-active
cache_location: /tmp/loki/boltdb-shipper-cache
cache_ttl: 24h
shared_store: filesystem
filesystem:
directory: /tmp/loki/chunks
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
Deploy Loki:
helm upgrade --install loki grafana/loki -f loki-values.yaml --namespace=loki --create-namespace
Step 3: Deploy Promtail
Create promtail-values.yaml:
# promtail-values.yaml
config:
clients:
- url: http://loki:3100/loki/api/v1/push
helm upgrade --install promtail grafana/promtail -f promtail-values.yaml --namespace=loki
Step 4: View logs in Grafana with a Data Source
Assuming you have Grafana deployed, add a Loki data source pointing to http://loki:3100. Then explore logs:
# Show all logs from the default namespace
{namespace="default"}
# Count log lines per second
rate({namespace="default"}[5m])
Step 5: Query from command line (optional)
Use logcli, the CLI tool for Loki:
# Install logcli (on host)
brew install logcli # macOS
# Or download from releases
# Query logs
logcli query '{namespace="default"}' --addr http://localhost:3100 --output raw
Expected output: You should see raw log lines from pods in the default namespace.
Compare options / when to choose what
| Feature | Loki | Elasticsearch | Splunk (HEC) |
|---|---|---|---|
| Indexing | Labels only | Full text | Full text |
| Storage cost | Low | High | Very high |
| Query speed | Moderate | Fast | Fast |
| Ease of operation | Simple | Complex | Simple (but costly) |
| Multi-tenancy | Native via labels | Via plugins | Native |
| Integration with Grafana | Native | Built-in | Plugin |
| Best for | High-volume logs (k8s, infra) | Full-text search, analytics | Enterprise compliance |
When to use Loki: You need to collect massive amounts of logs from Kubernetes, cloud services, or microservices — where storage costs matter more than instant full-text search. Perfect for incident response, compliance archives, and trend analysis.
Troubleshooting & edge cases
Common mistakes
- Missing logs: Promtail cannot access
/var/log/pods/*.logif the DaemonSet runs as non-root. EnsuresecurityContext: privileged: trueor mount the host log directory correctly. - High memory usage: If ingester memory spikes, reduce
chunk_idle_periodor increasemax_chunk_age. The default of 1h is usually fine. - No data in Grafana: Double-check the Loki URL in your data source. For example, if Loki runs in the
lokinamespace, usehttp://loki.loki.svc.cluster.local:3100. - Rate limiting: Loki's default ingester limits (e.g.,
ingester_client.max_recv_message_size: 67108864) may drop large log lines (~64 MB). Adjust via Helm values.
Edge case: High-cardinality labels (e.g., pod IP)
If you add high-cardinality labels (like pod_ip or request_id), Loki creates too many streams, causing memory and performance issues. Never use high-cardinality labels as stream labels. Instead, use structured metadata (available in Loki 2.6+) or parse log content.
Pro Tip: Keep your label cardinality under 10k distinct label values. For example, use
namespace,app,component— notpod,container_id, orrequest_id.
What you learned & what's next
You now understand the core concept behind collecting and querying logs with Loki: minimal indexing via labels, chunk-based storage, and LogQL for queries. You have deployed Loki with Promtail on Kubernetes and queried logs from Grafana. This pattern replaces expensive log systems with a cost-effective, scalable solution.
Next steps: In the next lesson, you will learn to configure alerting rules based on log patterns using Loki's Ruler component, or dive deeper into LogQL aggregations to monitor error rates and latency from logs.
Key takeaways
- Loki stores logs in compressed chunks and indexes only labels, drastically reducing costs.
- Promtail (or any Grafana agent) collects logs from nodes and sends them to Loki.
- LogQL is like PromQL but for logs — use
|= "error"to filter,rate()to count. - Avoid high-cardinality labels to keep performance stable.
- Loki integrates natively with Grafana for visualization and alerting.
Practice recap
Deploy Loki with Promtail on a minikube cluster using the Helm commands above. Then, create a test pod that generates logs (e.g., kubectl run test --image=nginx). Query all logs from that pod using LogQL: {pod="test"}. Then, parse the output to extract HTTP status codes using pattern parser.
Practice recap
Deploy Loki and Promtail on a fresh minikube cluster using the Helm commands from the walkthrough. Create a pod with kubectl run test --image=nginx and generate some log traffic. Use Grafana or logcli to query all logs from the test pod: {pod="test"}. Then, add a filter to find lines containing 'GET' and count them per minute using rate({pod="test"} |= "GET" [5m]).
Common mistakes
- Adding high-cardinality labels like pod IP or request ID as stream labels — creates too many streams and crashes the ingester.
- Forgetting to mount the host log directory in Promtail's DaemonSet — results in zero log collection.
- Setting chunk_idle_period too low (e.g., 10s) causes frequent flush operations and high disk I/O; use at least 5m.
- Assuming Loki is a drop-in replacement for Elasticsearch — it doesn't support full-text search without labels.
Variations
- Instead of Promtail, use Grafana Alloy or Fluent Bit as a log shipper to Loki — both support structured metadata and higher throughput.
- For multi-tenant clusters, enable
auth_enabled: trueand use tenant IDs (e.g.,X-Scope-OrgIDheader) to isolate logs by team. - You can query Loki via the HTTP API directly with curl or logcli, not just from Grafana.
Real-world use cases
- Collecting and visualizing Kubernetes audit logs for security compliance — store years of logs at low cost using Loki's compression.
- Debugging microservice failures by correlating logs from multiple pods using a shared
trace_idlabel and LogQL aggregations. - Monitoring error rates across environments (staging, production) with Grafana alerts based on LogQL
rate()queries.
Key takeaways
- Loki indexes only labels, not log content — reducing storage costs by up to 80% compared to Elasticsearch.
- Promtail acts as a log agent running on each Kubernetes node, scraping
/var/log/pods/*.logand sending them to Loki. - LogQL queries use curly braces for label selectors (like PromQL) and pipe operators for filtering (
|=,!=,|~). - Keep label cardinality low — under 10k distinct values — to avoid ingester performance problems.
- Loki integrates natively with Grafana for querying and alerting, making it an essential part of the Grafana observability stack.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.