TL;DR Prometheus alleen bewaart een week of twee aan metrics op lokale schijf en kan geen vraag over meerdere clusters heen beantwoorden. Thanos zet een sidecar naast elke Prometheus, stuurt de blocks naar object storage en zet er een querier voor die alles terugleest via dezelfde PromQL. Hieronder de installatie, de vier componenten, de bedrading voor HA en multi-cluster, en de getallen die ik in productie draai.
De eerste keer dat iemand me vroeg “was dit vorige maand trager dan nu?”, had ik geen antwoord. M’n Prometheus onthield twee weken. De data die ik nodig had was allang van de lokale schijf af en weg. Dat gat is de reden dat deze post bestaat.
Prometheus is de standaard voor Kubernetes-metrics, en ik zou hem zo weer kiezen. Het werkt, precies tot het moment dat je langetermijnopslag nodig hebt, of één blik over meer dan één cluster, of hoge beschikbaarheid die een node overleeft. Dan houdt het op, en hard ook.
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 je retentie wordt begrensd door je bucket in plaats van je schijf, met één query over al je clusters erbij. Dat “houd wat je kent” is wat mij over de streep trok. Ik laat liever een systeem groeien dat ik al kan doorgronden dan dat ik het inruil voor iets dat alles belooft en niks uitlegt.
Waar één Prometheus ophoudt
Een enkele Prometheus heeft vier harde grenzen. Het is één node, zonder ingebouwde clustering of HA. Het schrijft naar lokale schijf, dus de retentie is wat erop past. Het ziet één cluster, en over twee clusters heen queryen kan gewoon niet. En het downsamplet nooit, dus een metric van vorig jaar kost evenveel ruimte als eentje van vanochtend.
Voor één klein cluster met twee weken retentie doet dat geen pijn. Voor een multi-cluster-productiesetup met compliance-eisen is elk van die vier een blokker. Je loopt ertegenaan. De enige vraag is welke het eerst.
Wat Thanos toevoegt
Thanos wikkelt een handvol componenten om de Prometheus die je al hebt:
De sidecar draait naast Prometheus en uploadt z’n blocks naar object storage. De store gateway serveert die historie weer terug uit de bucket. De querier beantwoordt PromQL door de sidecars en de store gateway te bevragen en samen te voegen wat er terugkomt. De compactor downsamplet en dedupliceert wat er in de bucket ligt.
Vier bewegende delen, elk met één taak die ik in m’n hoofd kan houden. Zo’n ontwerp vertrouw ik een stuk meer dan één binary die alles doet.
Installeren met de Bitnami-chart
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 in 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 skelet. Er wordt nog niks in geüpload, en dat is de volgende stap.
Prometheus met de sidecar
Vertel Prometheus dat de Thanos-sidecar ernaast moet 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
Kijk naar die retention: 2h. De lokale schijf houdt nu alleen het meest recente stukje vast. De historie staat in object storage, en de sidecar blijft die aanvullen.
De drie componenten die blocks teruglezen
Nu de sidecar blocks verstuurt, heb je de onderdelen 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 maken je data kapot. Op die regel staat bij mij een dikke comment, zodat de toekomstige ik niet slim gaat doen.
De retentievlaggen zijn waar de kosten omlaag gaan. Raw data blijft 30 dagen op volle resolutie, de 5m-reeksen blijven 90 dagen en de 1h-reeksen 1 jaar. Oudere data neemt minder ruimte in omdat er per uur minder van is. Je ruilt resolutie voor historie, en voor alles ouder dan een paar weken is dat precies de ruil die ik wil.
GitOps-deployment
Niks hiervan klik ik met de hand in elkaar. Het gaat door ArgoCD net als de rest, zodat de clusterstate in Git staat waar ik hem kan lezen, reviewen en terugdraaien:
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
Twee Prometheus-replica’s, één set grafieken
Twee Prometheus-replica’s die dezelfde targets scrapen hebben alleen zin 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 niet elke lijn dubbel ziet:
# Querier configuratie
args:
- --query.replica-label=replica
- --query.replica-label=prometheus_replica
Valt er een node weg, of gewoon één Prometheus-pod, dan tekenen de dashboards verder vanaf de replica die over is. Daar draai je die tweede voor.
Eén query over al je clusters
Wijs meerdere clusters naar dezelfde Thanos-deployment en geef elk z’n eigen cluster-label. De eerste:
externalLabels:
cluster: production-eu
replica: $(POD_NAME)
En de tweede:
externalLabels:
cluster: production-us
replica: $(POD_NAME)
Nu aggregeert de querier over allebei:
# 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 drie Grafana-tabbladen meer open, één per cluster, terwijl je de getallen in je hoofd optelt.
Grafana hoeft het niet te weten
Grafana hoeft niet te weten dat Thanos bestaat. Wijs het naar de querier en het denkt dat het tegen een 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. Aan de Grafana-kant is die ene URL de hele verandering.
Recording rules voor de dure queries
Sommige queries zijn duur genoeg dat je ze niet bij elke refresh van een dashboard wilt draaien. Reken ze één keer per minuut uit 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:*-reeksen in plaats van bij elke load door de raw data te malen.
Alerting blijft bij Prometheus
Houd alerting dicht bij de data. Draai Alertmanager tegen Prometheus, niet tegen 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 werkt. Het is ook wéér een component om te snappen en wéér iets dat stuk kan zijn terwijl je al iets anders aan het debuggen bent. Voor de meeste setups is Prometheus-alerting ruim genoeg, en ik heb liever minder onderdelen die me kunnen verrassen.
Thanos zelf in de gaten houden
Thanos stelt z’n eigen Prometheus-metrics beschikbaar, en dat is maar goed ook, want het ding dat je systemen bewaakt is zelf een systeem dat stuk gaat. Deze houd ik 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 zodra uploads beginnen te falen, want een stille sidecar betekent dat je historie kwijtraakt zonder dat iemand het merkt:
- 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"
Wat de opslag kost
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 |
Bewaar raw data voor altijd en de storagerekening groeit elke maand een beetje, tot iemand ernaar vraagt. Downsample, en oude data kost bijna niks.
Voor self-hosted object storage doet MinIO het werk en blijft de data op hardware die van mij is:
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
Wat ik draai, alles bij elkaar
Zet de stukken bij elkaar en 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
Elk van die getallen heeft een reden. De lokale retentie is 6h omdat de sidecar vaak uploadt, dus hamsteren op lokale schijf heeft geen zin. De raw retentie is 14d, en dat dekt elke debugsessie die ik waarschijnlijk ga hebben. De 1h-reeksen blijven 1 jaar, genoeg trendhistorie voor capacity planning. En de bucket is self-hosted MinIO , dus de metrics blijven op m’n eigen hardware, zonder cloudrekening en zonder derde partij die ze vasthoudt.
Dezelfde chart als de installatie bovenaan. De values zijn wat er veranderd is.
Waar de metrics voor zijn
Metrics zijn hoe ik m’n systemen begrijp in plaats van erover te gokken. De vragen zijn bij elk incident en elke planningssessie dezelfde:
- Is deze service gezond?
- Wat veranderde er vlak voordat het misging?
- Halen we onze SLO’s ?
- Waar is optimalisatie de moeite waard?
Verlies je langetermijnmetrics, dan heeft “vergeleken met wanneer?” geen antwoord meer. Verlies je queries over clusters heen, dan zie je altijd maar één stuk tegelijk. Prometheus plus Thanos geeft je retentie in jaren, één blik over al je clusters en een HA-paar, 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. Eén query tegen de querier, en de data is er omdat de sidecar hem al die tijd heeft geüpload.
