De eerste keer dat iemand me vroeg “was dit vorige maand trager dan nu?”, had ik geen antwoord. Mijn Prometheus onthield maar twee weken. De data die ik nodig had was allang van lokale schijf gevallen en verwijderd. Dat gat is de hele reden dat deze post bestaat.
Prometheus is de standaard voor Kubernetes metrics, en met goede reden. Het werkt prachtig, precies tot het moment dat je lange-termijn opslag nodig hebt, of een overzicht over meerdere clusters, of echte hoge beschikbaarheid. Dan loop je tegen de muur.
Thanos breidt Prometheus uit in plaats van het te vervangen. Je houdt de setup die je al snapt, schroeft er een paar componenten op, en krijgt onbeperkte retentie en globale queries. Dat “houd wat je kent” gedeelte is belangrijk voor mij. Ik laat liever een systeem groeien dat ik al kan doorgronden dan dat ik het inruil voor een black box die belooft alles te doen.
Waar standalone Prometheus stopt
Een enkele Prometheus heeft vier harde grenzen:
- Single node - Geen native clustering of HA
- Lokale storage - Retentie wordt begrensd door schijfgrootte
- Single cluster view - Je kunt niet queryen over clusters heen
- Geen downsampling - Oude data vreet evenveel ruimte als nieuwe data
Voor een enkel klein cluster met twee weken retentie doet dit geen pijn. Voor een productie multi-cluster setup met compliance eisen is elk van deze punten een blokker. De vraag is niet of je ze raakt, maar wanneer.
De vorm van Thanos
Thanos wikkelt een handvol componenten rond je bestaande Prometheus:
flowchart TD
subgraph clusterA["Cluster A"]
PA["Prometheus + Sidecar"]
end
subgraph clusterB["Cluster B"]
PB["Prometheus + Sidecar"]
end
PA --> OS["Object Storage<br/>(S3/MinIO/GCS)"]
PB --> OS
OS --> Q["Querier"]
OS --> SG["Store Gateway"]
OS --> C["Compactor"]
SG --> Q
Q --> G["Grafana"]
Sidecar - Draait naast Prometheus, uploadt blocks naar object storage Store Gateway - Serveert historische data terug vanuit object storage Querier - Aggregeert data van sidecars en de store gateway Compactor - Downsamplet en dedupliceert data in object storage
Vier bewegende delen, en elk doet één taak die je in je hoofd kunt houden. Dat is het soort ontwerp dat ik vertrouw.
De simpelste versie: gewoon installeren
Begin met de Bitnami Helm chart en zet iets draaiend:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install thanos bitnami/thanos \
--namespace monitoring \
--create-namespace \
--set objstoreConfig="$(cat thanos-objstore.yaml)"
De object store config (thanos-objstore.yaml) wijst Thanos naar je bucket:
type: s3
config:
bucket: thanos-metrics
endpoint: minio.storage:9000
access_key: ${MINIO_ACCESS_KEY}
secret_key: ${MINIO_SECRET_KEY}
insecure: true # Voor MinIO zonder TLS
Dat is het hele skelet. Alles wat hierna komt is laag voor laag echte detail erop stapelen.
Prometheus aan de sidecar knopen
Vervolgens vertel je Prometheus om de Thanos sidecar ernaast te draaien:
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: prometheus
namespace: monitoring
spec:
replicas: 2 # HA paar
retention: 2h # Korte lokale retentie, Thanos handelt lange-termijn
# Thanos sidecar configuratie
thanos:
baseImage: quay.io/thanos/thanos
version: v0.34.0
objectStorageConfig:
key: thanos.yaml
name: thanos-objstore-secret
# Externe labels voor deduplicatie
externalLabels:
cluster: production
replica: $(POD_NAME)
# Laat Thanos sidecar Prometheus data benaderen
storage:
volumeClaimTemplate:
spec:
storageClassName: longhorn
resources:
requests:
storage: 50Gi
De sidecar doet drie dingen:
- Stelt Prometheus data beschikbaar aan de Thanos Querier via gRPC
- Uploadt voltooide TSDB blocks naar object storage
- Beantwoordt Store API queries voor recente data
Let op de retention: 2h. Lokale schijf houdt nu alleen het meest recente stukje vast. De lange historie leeft in object storage, waar het thuishoort.
Verder gaan: de drie server componenten
Met de sidecar die blocks verstuurt, heb je de componenten nodig die ze weer teruglezen.
Querier
apiVersion: apps/v1
kind: Deployment
metadata:
name: thanos-querier
namespace: monitoring
spec:
replicas: 2
template:
spec:
containers:
- name: thanos-query
image: quay.io/thanos/thanos:v0.34.0
args:
- query
- --http-address=0.0.0.0:9090
- --grpc-address=0.0.0.0:10901
# Verbind met sidecars
- --store=dnssrv+_grpc._tcp.prometheus-operated.monitoring.svc
# Verbind met store gateway
- --store=dnssrv+_grpc._tcp.thanos-store.monitoring.svc
# Deduplicatie
- --query.replica-label=replica
ports:
- name: http
containerPort: 9090
- name: grpc
containerPort: 10901
Store Gateway
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: thanos-store
namespace: monitoring
spec:
replicas: 2
template:
spec:
containers:
- name: thanos-store
image: quay.io/thanos/thanos:v0.34.0
args:
- store
- --http-address=0.0.0.0:10902
- --grpc-address=0.0.0.0:10901
- --data-dir=/var/thanos/store
- --objstore.config-file=/etc/thanos/objstore.yaml
volumeMounts:
- name: objstore-config
mountPath: /etc/thanos
- name: data
mountPath: /var/thanos/store
volumes:
- name: objstore-config
secret:
secretName: thanos-objstore-secret
volumeClaimTemplates:
- metadata:
name: data
spec:
storageClassName: longhorn
resources:
requests:
storage: 10Gi
Compactor
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: thanos-compactor
namespace: monitoring
spec:
replicas: 1 # Slechts één compactor!
template:
spec:
containers:
- name: thanos-compact
image: quay.io/thanos/thanos:v0.34.0
args:
- compact
- --http-address=0.0.0.0:10902
- --data-dir=/var/thanos/compact
- --objstore.config-file=/etc/thanos/objstore.yaml
- --retention.resolution-raw=30d
- --retention.resolution-5m=90d
- --retention.resolution-1h=1y
- --wait
volumeMounts:
- name: objstore-config
mountPath: /etc/thanos
- name: data
mountPath: /var/thanos/compact
Die replicas: 1 op de compactor is geen typefout. Draai er twee en ze vechten om dezelfde blocks en verminken je data. Ik heb geleerd om daar een dikke comment neer te zetten zodat de toekomstige ik niet slim probeert te doen.
De retentie tiers doen het zware werk op kosten:
- Raw data: 30 dagen op volledige resolutie
- 5m gedownsampled: 90 dagen
- 1h gedownsampled: 1 jaar
Oudere data neemt minder ruimte omdat het gedownsampled is. Je ruilt resolutie voor historie, precies de ruil die je wilt voor alles ouder dan een paar weken.
GitOps deployment
Ik klik niets hiervan met de hand in elkaar. Het gaat door ArgoCD net als al het andere, zodat de cluster state in Git leeft waar ik het kan lezen, reviewen en terugrollen:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: thanos
namespace: argocd
spec:
project: default
source:
repoURL: https://charts.bitnami.com/bitnami
chart: thanos
targetRevision: 12.20.0
helm:
values: |
objstoreConfig: |-
type: s3
config:
bucket: thanos-metrics
endpoint: minio.storage:9000
insecure: true
query:
enabled: true
replicaCount: 2
stores:
- dnssrv+_grpc._tcp.prometheus-operated.monitoring.svc
storegateway:
enabled: true
replicaCount: 2
persistence:
size: 20Gi
compactor:
enabled: true
retentionResolutionRaw: 30d
retentionResolution5m: 90d
retentionResolution1h: 1y
persistence:
size: 50Gi
ruler:
enabled: false # Gebruik Prometheus rules in plaats daarvan
receive:
enabled: false # Gebruiken sidecar mode
destination:
server: https://kubernetes.default.svc
namespace: monitoring
Geavanceerde patronen: HA die een node echt overleeft
Twee Prometheus replicas die dezelfde targets scrapen is alleen nuttig als iets hun resultaten weer aan elkaar naait. Thanos is dat iets.
# Draai twee Prometheus instances
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
spec:
replicas: 2
externalLabels:
replica: $(POD_NAME) # Verschillend voor elke replica
Beide instances scrapen alles. De Querier dedupliceert op het replica label zodat je geen dubbele grafieken ziet:
# Querier configuratie
args:
- --query.replica-label=replica
- --query.replica-label=prometheus_replica
Queries komen automatisch gededupliceerd terug. Verlies een node, verlies een Prometheus pod, en je dashboards blijven werken vanaf de overlevende replica. Dat is resilience die je tijdens een incident echt voelt, in plaats van een vinkje in een runbook.
Eén globaal overzicht over clusters
Wijs meerdere clusters naar dezelfde Thanos deployment en geef elk een eigen cluster label.
Cluster A Prometheus:
externalLabels:
cluster: production-eu
replica: $(POD_NAME)
Cluster B Prometheus:
externalLabels:
cluster: production-us
replica: $(POD_NAME)
Nu aggregeert de Querier over beide:
# Totaal requests over alle clusters
sum(rate(http_requests_total[5m]))
# Requests per cluster
sum by (cluster) (rate(http_requests_total[5m]))
Eén PromQL query, elk cluster, één antwoord. Geen tab-gejongleer meer tussen drie verschillende Grafana instances om te reconstrueren wat er gebeurde.
Grafana integratie
Grafana hoeft niet te weten dat Thanos bestaat. Wijs het naar de Querier en het denkt dat het tegen gewone Prometheus praat:
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-datasources
data:
thanos.yaml: |
apiVersion: 1
datasources:
- name: Thanos
type: prometheus
url: http://thanos-query.monitoring:9090
access: proxy
isDefault: true
jsonData:
timeInterval: "15s"
Elk dashboard dat je al gebouwd hebt blijft werken. Wijs het naar Thanos en ga verder.
Recording rules voor performance
Sommige queries zijn duur genoeg dat je ze niet bij elke dashboard refresh wilt draaien. Bereken ze één keer voor en lees het resultaat:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: recording-rules
spec:
groups:
- name: aggregations
interval: 1m
rules:
# Pre-aggregeer request rate per service
- record: service:http_requests:rate5m
expr: sum by (service) (rate(http_requests_total[5m]))
# Pre-aggregeer error rate
- record: service:http_errors:rate5m
expr: sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
# Pre-compute availability
- record: service:availability:ratio
expr: |
1 - (
service:http_errors:rate5m /
service:http_requests:rate5m
)
Dashboards queryen daarna de goedkope service:* metrics in plaats van bij elke load door raw data te malen.
Alerting architectuur
Houd alerting dicht bij de data. Draai Alertmanager met Prometheus in plaats van met Thanos:
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
spec:
alerting:
alertmanagers:
- namespace: monitoring
name: alertmanager
port: web
ruleSelector:
matchLabels:
role: alert-rules
Thanos Ruler bestaat en het werkt, maar het voegt nog een component toe om over na te denken en nog een failure mode om om 3 uur ’s nachts te debuggen. Voor de meeste setups is Prometheus alerting ruim voldoende, en minder bewegende delen betekent minder dingen die je verrassen.
Thanos zelf monitoren
Thanos stelt zijn eigen Prometheus metrics beschikbaar, wat handig is omdat het ding dat je systemen bewaakt zelf een systeem is dat stuk kan gaan. Houd deze in de gaten:
# Sidecar upload succes
thanos_shipper_uploads_total
thanos_shipper_upload_failures_total
# Store gateway performance
thanos_bucket_store_series_fetch_duration_seconds
thanos_bucket_store_block_loads_total
# Compactor health
thanos_compact_group_compactions_total
thanos_compact_group_compaction_failures_total
# Querier performance
thanos_query_gate_duration_seconds
En alert wanneer uploads beginnen te falen, want een stille sidecar betekent dat je stilletjes historie verliest:
- alert: ThanosSidecarUploadFailing
expr: increase(thanos_shipper_upload_failures_total[1h]) > 0
for: 15m
labels:
severity: warning
annotations:
summary: "Thanos sidecar faalt bij uploaden van blocks"
Storage overwegingen
Hier verdient downsampling zichzelf terug:
| Resolutie | Data per dag | 1 jaar kosten (S3) |
|---|---|---|
| Raw (15s) | ~100MB/target | ~$4/target |
| 5m downsample | ~3MB/target | ~$0.12/target |
| 1h downsample | ~0.5MB/target | ~$0.02/target |
Raw data voor altijd bewaren is hoe je eindigt met een storage rekening die stilletjes uitgroeit tot een probleem. Downsample agressief en oude data kost bijna niets.
Voor self-hosted object storage doet MinIO het werk en houdt het de data op hardware die ik zelf bezit:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: minio
spec:
template:
spec:
containers:
- name: minio
image: minio/minio:latest
args:
- server
- /data
- --console-address
- ":9001"
env:
- name: MINIO_ROOT_USER
valueFrom:
secretKeyRef:
name: minio-credentials
key: root-user
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: minio-credentials
key: root-password
Het volledige plaatje: mijn productie setup
Alle lagen bij elkaar gebracht, dit is ongeveer wat er in productie draait:
# Prometheus met sidecar
prometheus:
replicas: 2
retention: 6h # Zeer kort, Thanos handelt lange-termijn
thanos:
objectStorageConfig:
name: thanos-objstore
externalLabels:
cluster: production
environment: prod
# Thanos componenten
thanos:
query:
replicaCount: 2
stores:
- dnssrv+_grpc._tcp.prometheus-operated.monitoring.svc
- dnssrv+_grpc._tcp.thanos-store.monitoring.svc
storegateway:
replicaCount: 2
persistence:
size: 50Gi
compactor:
retentionResolutionRaw: 14d
retentionResolution5m: 60d
retentionResolution1h: 365d
persistence:
size: 100Gi
# Object storage
minio:
replicas: 4
persistence:
size: 500Gi
De beslissingen achter die getallen:
- 6h lokale retentie - De sidecar uploadt vaak, dus er is geen reden om data op lokale schijf te hamsteren
- 14d raw retentie - Volledige resolutie dekt elke recente debug-sessie
- 1 jaar 1h retentie - Genoeg trend-historie voor capacity planning
- Self-hosted MinIO - Data soevereiniteit, geen cloud rekening, geen derde partij die mijn metrics vasthoudt
Vergelijk dat met de drie-regel Helm install bovenaan. Dezelfde bouwblokken, alleen afgesteld op een systeem waar ik mee moet leven.
Waarom dit ertoe doet
Metrics zijn hoe ik mijn systemen begrijp in plaats van erover te gokken. Ze beantwoorden de vragen die bij elk incident en elke planningssessie opduiken:
- Is deze service gezond?
- Wat veranderde er vlak voor het incident?
- Halen we onze SLOs?
- Waar is optimalisatie de moeite echt waard?
Verlies lange-termijn metrics en je verliest het vermogen om “vergeleken met wanneer?” te beantwoorden. Verlies cross-cluster queries en je ziet altijd maar één stuk van het plaatje tegelijk. Prometheus plus Thanos geeft je onbeperkte retentie, een globaal overzicht en hoge beschikbaarheid, allemaal achter dezelfde Prometheus interface die je al kunt lezen.
Die vraag van het begin van deze post, “was dit vorige maand trager?”, heeft nu een antwoord dat ik in één query kan ophalen. Van “wat gebeurt er nu” naar “wat is er gebeurd” gaan is het verschil tussen brandjes blussen en het systeem echt sturen.
