Je hebt Prometheus voor metrics. Je kunt zien wat er gebeurt in je clusters. Maar als iets kapotgaat, vertellen metrics je dat er iets mis is — logs vertellen je waarom.

Het traditionele antwoord is Elasticsearch. Het is krachtig, flexibel, en… duur. Het indexeert alles, wat betekent dat je betaalt voor elke byte aan logdata in CPU, geheugen en opslag.

Loki kiest een andere aanpak: indexeer labels, niet content. Het is dezelfde filosofie die Prometheus efficiënt maakt voor metrics, toegepast op logs.

Waarom Loki?

Loki is ontworpen door Grafana Labs met specifieke doelen:

  1. Kostenefficiënt — Indexeer alleen metadata (labels), sla logregels gecomprimeerd op
  2. Kubernetes native — Labels van Kubernetes metadata automatisch
  3. Grafana integratie — Dezelfde dashboards, dezelfde alerting, dezelfde workflow
  4. Operationeel simpel — Geen JVM tuning, geen cluster management complexiteit

De trade-off: je kunt niet efficiënt full-text zoeken over alle logs. Je moet eerst weten op welke labels je wilt filteren. Voor Kubernetes workloads waar je meestal naar specifieke pods, namespaces of services kijkt, is dit prima.

Architectuur

flowchart TD
    subgraph cluster["Kubernetes Cluster"]
        subgraph nodes["Nodes"]
            P1["Promtail<br/>(DaemonSet)"]
            P2["Promtail"]
            P3["Promtail"]
        end
        PODS["Pod Logs<br/>(/var/log/pods)"]
    end

    P1 --> PODS
    P2 --> PODS
    P3 --> PODS

    P1 --> L["Loki"]
    P2 --> L
    P3 --> L

    L --> OS["Object Storage<br/>(chunks)"]
    L --> G["Grafana"]

Promtail draait op elke node als DaemonSet, tailt logbestanden, voegt labels toe en stuurt naar Loki.

Loki ontvangt logs, indexeert op labels, comprimeert chunks, slaat op in object storage.

Grafana bevraagt Loki met LogQL, toont in Explore of dashboards.

Loki Installeren

Met de Grafana Helm charts:

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

# Installeer Loki met simple scalable deployment
helm install loki grafana/loki \
  --namespace monitoring \
  --create-namespace \
  --values loki-values.yaml

Basis values voor een single-binary deployment (goed voor kleine clusters):

# loki-values.yaml
loki:
  auth_enabled: false

  commonConfig:
    replication_factor: 1

  storage:
    type: filesystem

  schemaConfig:
    configs:
      - from: 2024-01-01
        store: tsdb
        object_store: filesystem
        schema: v13
        index:
          prefix: index_
          period: 24h

singleBinary:
  replicas: 1
  persistence:
    size: 50Gi

# Disable componenten niet nodig voor single binary
backend:
  replicas: 0
read:
  replicas: 0
write:
  replicas: 0

Voor productie met object storage:

# loki-values.yaml
loki:
  auth_enabled: false

  commonConfig:
    replication_factor: 3

  storage:
    type: s3
    s3:
      endpoint: minio.storage:9000
      bucketnames: loki-chunks
      access_key_id: ${MINIO_ACCESS_KEY}
      secret_access_key: ${MINIO_SECRET_KEY}
      insecure: true

  schemaConfig:
    configs:
      - from: 2024-01-01
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: index_
          period: 24h

# Schaalbare deployment
backend:
  replicas: 3
read:
  replicas: 3
write:
  replicas: 3

Promtail Installeren

Promtail verzamelt logs van je nodes:

helm install promtail grafana/promtail \
  --namespace monitoring \
  --set config.clients[0].url=http://loki:3100/loki/api/v1/push

Promtail configuratie voor Kubernetes:

# promtail-values.yaml
config:
  clients:
    - url: http://loki:3100/loki/api/v1/push

  snippets:
    # Voeg Kubernetes metadata toe als labels
    pipelineStages:
      - cri: {}
      - labeldrop:
          - filename
      - match:
          selector: '{app="nginx"}'
          stages:
            - regex:
                expression: '^(?P<remote_addr>[\d\.]+) - (?P<remote_user>\S+) \[(?P<time_local>[^\]]+)\] "(?P<request>[^"]+)" (?P<status>\d+)'
            - labels:
                status:

# DaemonSet tolerations voor alle nodes
tolerations:
  - operator: Exists

Labels Begrijpen

Labels zijn alles in Loki. Ze bepalen hoe logs worden geïndexeerd en bevraagd.

Default Kubernetes labels van Promtail:

LabelBronVoorbeeld
namespacePod namespacedefault, monitoring
podPod naamnginx-abc123
containerContainer naamnginx, sidecar
node_nameNodeworker-1
appPod labelnginx
jobScrape configkubernetes-pods

High cardinality waarschuwing: Voeg geen labels toe die veel unieke waarden hebben (zoals request IDs, user IDs of timestamps). Dit vernietigt Loki’s performance. Houd labels beperkt tot dimensies waarop je filtert.

Goede labels:

  • namespace, app, environment, team

Slechte labels:

  • request_id, user_id, trace_id, timestamp

LogQL: Logs Bevragen

LogQL is Loki’s query taal. Het lijkt op PromQL maar voor logs.

Basis Queries

# Alle logs van een namespace
{namespace="production"}

# Specifieke app
{app="frontend", namespace="production"}

# Meerdere containers
{container=~"nginx|envoy"}

# Exclude een namespace
{namespace!="kube-system"}

Content Filteren

# Regels met "error"
{app="frontend"} |= "error"

# Regels ZONDER "health"
{app="frontend"} != "health"

# Regex match
{app="frontend"} |~ "status=(4|5)[0-9]{2}"

# Case insensitive
{app="frontend"} |~ "(?i)error"

Parsen en Extracten

# Parse JSON logs
{app="api"} | json

# Extract specifiek veld
{app="api"} | json | status_code >= 500

# Parse met pattern
{app="nginx"} | pattern `<ip> - - [<_>] "<method> <path> <_>" <status>`

# Gebruik geëxtraheerde velden
{app="nginx"} | pattern `<_> - - [<_>] "<method> <path> <_>" <status>` | status >= 400

Aggregaties (Log Metrics)

# Tel errors per app
sum by (app) (count_over_time({namespace="production"} |= "error" [5m]))

# Rate van requests
sum(rate({app="nginx"} | pattern `<_> "<method> <path> <_>" <status>` [1m])) by (status)

# Bytes per namespace
sum by (namespace) (bytes_over_time({job="kubernetes-pods"}[1h]))

Grafana Integratie

Voeg Loki toe als data source in Grafana:

apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-datasources
  namespace: monitoring
data:
  loki.yaml: |
    apiVersion: 1
    datasources:
      - name: Loki
        type: loki
        url: http://loki:3100
        access: proxy
        jsonData:
          maxLines: 1000

Explore View

Grafana Explore is perfect voor log onderzoek:

  1. Selecteer Loki data source
  2. Bouw query met label browser
  3. Filter met content matches
  4. Klik op logregels voor context

Dashboard Panels

Voeg logs toe aan je dashboards:

{
  "type": "logs",
  "datasource": "Loki",
  "targets": [
    {
      "expr": "{namespace=\"production\", app=\"frontend\"} |= \"error\"",
      "refId": "A"
    }
  ],
  "options": {
    "showTime": true,
    "showLabels": false,
    "wrapLogMessage": true
  }
}

Metrics en Logs Correleren

De kracht van Grafana: hetzelfde dashboard toont metrics en logs.

# Prometheus panel die error rate toont
sum(rate(http_requests_total{status=~"5.."}[5m])) by (app)

# Loki panel die error logs toont
{app="$app"} |= "error"

Variabele $app linkt beide panels. Klik op een spike in de metrics, zie de errors in de logs.

Alerting op Logs

Loki ondersteunt alerting via Grafana of zijn eigen ruler:

# Grafana alert rule
apiVersion: 1
groups:
  - name: LogAlerts
    rules:
      - alert: HighErrorRate
        expr: |
          sum(count_over_time({namespace="production"} |= "error" [5m])) > 100
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Hoge error rate in productie logs"

Met Loki’s ruler component:

# loki-rules.yaml
groups:
  - name: errors
    rules:
      - alert: CriticalError
        expr: |
          count_over_time({app="payment-service"} |= "CRITICAL" [1m]) > 0
        for: 0m
        labels:
          severity: critical
        annotations:
          summary: "Kritieke error in payment service"

Retentie en Opslag

Configureer retentie in Loki:

loki:
  limits_config:
    retention_period: 30d

  compactor:
    working_directory: /var/loki/compactor
    retention_enabled: true
    retention_delete_delay: 2h
    retention_delete_worker_count: 150

Per-tenant retentie (bij multi-tenancy):

loki:
  limits_config:
    retention_period: 30d  # Default

  overrides:
    production:
      retention_period: 90d  # Houd productie logs langer
    development:
      retention_period: 7d   # Dev logs vervallen sneller

Performance Tuning

Chunk Size

Grotere chunks = minder index entries, betere compressie, hogere latency voor kleine queries:

loki:
  ingester:
    chunk_target_size: 1572864  # 1.5MB
    chunk_idle_period: 30m
    max_chunk_age: 2h

Query Limieten

Voorkom runaway queries:

loki:
  limits_config:
    max_query_length: 721h           # Max tijdsbereik
    max_query_parallelism: 32        # Concurrent sub-queries
    max_entries_limit_per_query: 5000

Caching

Voeg caching toe voor betere query performance:

loki:
  memcached:
    chunk_cache:
      enabled: true
      host: memcached.monitoring
    results_cache:
      enabled: true
      host: memcached.monitoring

Structured Logging Best Practices

Om het meeste uit Loki te halen, log in gestructureerd formaat:

{
  "level": "error",
  "message": "Failed to process order",
  "order_id": "12345",
  "error": "payment declined",
  "duration_ms": 234
}

Bevraag gestructureerde logs eenvoudig:

{app="order-service"} | json | level="error" | duration_ms > 1000

Configureer je apps om JSON te outputten:

# Spring Boot
logging.pattern.console: '{"timestamp":"%d","level":"%p","logger":"%c","message":"%m"}%n'

# Node.js met winston
const logger = winston.createLogger({
  format: winston.format.json(),
});

# Go met zap
logger, _ := zap.NewProduction()

Mijn Productie Setup

# Loki met object storage
loki:
  auth_enabled: false
  commonConfig:
    replication_factor: 3
  storage:
    type: s3
    s3:
      endpoint: minio.storage:9000
      bucketnames: loki-data
  limits_config:
    retention_period: 30d
    ingestion_rate_mb: 10
    ingestion_burst_size_mb: 20

# Drie-replica deployment
backend:
  replicas: 3
  persistence:
    size: 50Gi
read:
  replicas: 3
write:
  replicas: 3
  persistence:
    size: 50Gi

# Promtail op alle nodes
promtail:
  tolerations:
    - operator: Exists
  config:
    clients:
      - url: http://loki:3100/loki/api/v1/push

Belangrijke keuzes:

  • Object storage: MinIO voor sovereignty, geen cloud afhankelijkheid
  • 30 dagen retentie: Genoeg voor debugging, niet oneindig
  • Drie replica’s: Overleeft node failures
  • Alle nodes: Promtail draait overal, inclusief control plane

Loki vs Elasticsearch

AspectLokiElasticsearch
IndexeringAlleen labelsFull-text
Opslag kostenLagerHoger
Query flexibiliteitLabel-firstFull-text search
OperationsSimpelerComplex
GeheugengebruikLagerHoger (JVM)
Grafana integratieNativeGoed

Kies Loki wanneer:

  • Je bevraagt op bekende dimensies (namespace, app, pod)
  • Kosten belangrijk zijn
  • Je operationele simpelheid wilt
  • Je al in het Grafana ecosysteem zit

Kies Elasticsearch wanneer:

  • Je full-text search nodig hebt over alle logs
  • Je niet weet waar je naar zoekt
  • Log analytics een primaire use case is

Waarom Dit Ertoe Doet

Logs zijn het verhaal van je systeem. Metrics vertellen je de gezondheidsscore, logs vertellen je het verhaal.

Met Loki krijg je:

  • Betaalbare log retentie — Bewaar logs zonder het budget te breken
  • Kubernetes-native labels — Bevraag op wat ertoe doet (namespace, app, pod)
  • Unified observability — Dezelfde Grafana, dezelfde workflow als metrics

Gecombineerd met Prometheus/Thanos voor metrics en traces (apart behandeld), heb je complete observability zonder de operationele complexiteit van de ELK stack.


Logs zijn het verhaal dat je systeem over zichzelf vertelt. Loki zorgt dat je het je kunt veroorloven om te luisteren.