Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Monitorare job Kubernetes brevi su larga scala

By Ivan YurochkoMay 11, 20266 min read

Questa pagina è disponibile anche in English, Deutsch, Español, Français, 日本語 e Português.


Monitorare job Kubernetes brevi su larga scala

Immagine di copertina: Monitorare job Kubernetes brevi su larga scala Come abbiamo risolto la perdita di metriche, l'esplosione di cardinalità e le dashboard inaffidabili sui workloads effimeri grazie alla streaming aggregation di vmagent


Introduzione

Inizia a usare Kubernetes e a distribuire qualche applicazione qua e là. Espone le metriche, adotta Prometheus e tutto fila liscio. Va tutto bene.

Poi ha qualche job da eseguire. Stesso pacchetto Prometheus, stesse metriche esposte. All'improvviso scatta l'alert. Prometheus è in OOM. Apre le dashboard e in effetti compaiono moltissime nuove serie temporali, tutte univoche per job_id. Verifica di nuovo le dashboard e si accorge che, per i job brevi, i dati non tornano.

È una storia in cui, prima o poi, ci si imbatte tutti.

In questo articolo raccontiamo come l'abbiamo affrontata.

Contesto

I job di breve durata sono molto diffusi in Kubernetes:

  • workloads batch
  • CronJob
  • Pipeline event-driven
  • Compute ottimizzato per i costi

Sono efficienti e scalabili, ma mettono in difficoltà i sistemi di monitoraggio pensati per servizi di lunga durata.

Esistono due famiglie di sistemi di monitoraggio: pull-based e push-based. Con i sistemi push-based si inviano i dati quando si vuole. I sistemi pull-based effettuano lo scrape dei target a intervalli prefissati.

Prometheus (e altri) sono pull-based di default. Mantengono un inventario dei target, effettuano lo scrape delle metriche a intervalli T in formato Prometheus da un endpoint specificato e le archiviano in un database locale.

Screenshot 2026-04-09 at 14.02.02

Per i job brevi questo meccanismo non regge sull'intervallo: se un job termina tra T1 e T2, le sue metriche non verranno mai raccolte. Un altro problema ricorrente è la cardinalità. Ogni job avrà un pod_name o un altro identificatore univoco, rendendo ogni serie temporale unica agli occhi di Prometheus.

Di job così ne abbiamo molti e sono mission critical. Perdere metriche non è accettabile: rischiamo di calcolare male gli SLO, di ricevere alert falsi negativi e di perdere dati cruciali per le decisioni. A ciò si aggiunge che la cardinalità dei pod name per questi job è notevole. Prometheus è diventato un divoratore di memoria nel nostro cluster e qualsiasi query basata su queste metriche risultava lenta e onerosa in termini di risorse.

Definizione del problema

  • Raccogliere tutte le metriche dei job brevi
  • Garantire la correttezza delle metriche
  • Mantenere costi e prestazioni ragionevoli
  • Supportare sia dashboarding sia alerting
  • Renderlo engineer friendly (pacchetto condiviso, facile da riutilizzare)

Saremmo partiti dai requisiti irrinunciabili e poi ci saremmo concentrati sulla UX. Significa prima raccolta e correttezza, poi costi, prestazioni e pacchetti condivisi.

Punto di partenza

Approccio grezzo:

  • Usare Prometheus Pushgateway
  • Registrare le metriche con il registry di default
  • Inviare le metriche al completamento del job
pusher := metric.NewPusher("reports", cfg.PromPushGatewayAddr)
defer func() {
if err := pusher.PushMetrics(); err != nil {
logger.Errorf("could not push metrics: %s", err)
}
}()

Come piccolo miglioramento possiamo aggiungere un pusher periodico. Si tratta di una goroutine che invia periodicamente le metriche al push gateway.

pusher := metric.NewPusher(jobName, cfg.PromPushGatewayAddr)
pusher.PushPeriodicMetrics(logger.With(), 30*time.Second)
defer func() {
if err := pusher.PushMetrics(); err != nil {
logger.Errorf("could not push metrics: %s", err)
}
}()

Meglio di niente, ma abbiamo riscontrato problemi di correttezza. A ogni push si verificavano collisioni di metriche tra job diversi. Il risultato era che il valore finale veniva sovrascritto in Prometheus, facendo oscillare i contatori in alto e in basso (un problema di correttezza).

Screenshot 2026-04-16 at 13.33.05

Abbiamo deciso di aggiungere pod_name per evitare queste collisioni e garantire la correttezza. POD_NAME sarebbe stato fornito dalla Downward API di Kubernetes.

podName, exists := os.LookupEnv("POD_NAME")
if !exists {
podName = cfg.Conf.SvcID
}
pusher := metric.NewPusher(podName, cfg.PromPushGatewayAddr)

Verificando i dati, sembravano corretti. Tuttavia non riuscivamo a costruire dashboard né alert. In poche parole, increase non funzionava. Perché increase funzioni serve calcolare il delta tra T1 e T2. I dati però arrivavano solo a T2, quindi se la metrica era assente al primo scrape (il primo push non avviene) non c'è delta e non c'è increase. Abbiamo notato inoltre che Prometheus iniziava a consumare moltissima memoria. I job brevi sono brevi, ne abbiamo tanti, ognuno con il proprio pod name univoco, e la cardinalità schizza in alto.

Screenshot 2026-04-16 at 13.34.50

Avevamo quindi dati corretti e un pacchetto condiviso facile da usare. Restavano da risolvere la costruzione di dashboard e alert, il miglioramento delle prestazioni e la riduzione dei costi.

Miglioramento

Abbiamo iniziato dalla soluzione più ovvia: usare le recording rule integrate di Prometheus. Abbiamo aggiunto questo:

record: metric_name:aggr
exp: sum(metric_name) without (job)

Screenshot 2026-04-16 at 13.33.59

Il limite di questo approccio era che increase continuava a non funzionare. Per le metriche inviate di frequente andava bene, ma per i job brevi e poco frequenti mancava l'importante valore iniziale di 0.

Problema ancora più grave: la memoria di Prometheus continuava a crescere. Oltre alla memoria, l'espressione veniva ricalcolata e memorizzata una volta ogni N intervalli, generando anche un consumo elevato di CPU.

Affinamento finale

Ciò che volevamo era questo: prima che Prometheus memorizzi qualunque cosa, eseguire l'aggregazione e solo dopo archiviare il risultato. Un plus sarebbe stato inviare uno 0 prima della comparsa del valore effettivo. Un ulteriore plus poter contare su altre regole di aggregazione, con modifiche minime a dashboard e alert e di facile adozione per i team di engineering. Qualcosa di simile a questo, ma in linguaggio Prometheus:

SELECT *, SUM(value)
FROM METRICS
WHERE METRIC_NAME LIKE 'COUNT'
GROUP BY * WITHOUT JOB;

Entra in scena la streaming aggregation di vmagent. vmagenthttps://docs.victoriametrics.com/victoriametrics/vmagent/ è un agente di raccolta metriche leggero e ad alte prestazioni, progettato da VictoriaMetrics per effettuare lo scrape, elaborare e ingerire dati da diverse sorgenti nei sistemi di monitoraggio. Fa esattamente questo: si comporta come un push gateway e invia metriche aggregate a Prometheus.

Esempio di regola per le metriche count che inviamo:

- match: '{__name__=~".+_count"}'
interval: 2m
outputs: ["total"]
without: ["pod_name"]
staleness_interval: 15m
keep_metric_names: true
flush_on_shutdown: true

E i nostri grafici hanno iniziato ad apparire esattamente come ce li aspettavamo.

Screenshot 2026-04-16 at 13.35.58

Soluzione finale

+-----------------------------+
| K8s Jobs |
| (shared metrics package) |
+-------------+---------------+
|
| push metrics
v
+-----------------------------+
| Push Endpoint |
| (e.g. Pushgateway) |
+-------------+---------------+
|
| metrics flow
v
+-----------------------------+
| vmagent |
| (streaming aggregation) |
| - reduce cardinality |
| - aggregate counters |
+-------------+---------------+
|
| push
v
+-----------------------------+
| Prometheus |
| (stores aggregated data) |
+-----------------------------+

Per evidenziare il flusso: un pacchetto condiviso invia periodicamente le metriche a vmagent, includendo pod_name come label. A fine esecuzione esegue un push finale.

I dati arrivano a vmagent, che mantiene lo stato dell'aggregazione. Somma i contatori in ingresso per nome metrica. Una volta per intervallo invia il risultato a Prometheus.

A questo punto abbiamo soddisfatto tutti i nostri requisiti.

Problemi

Pre-aggregare i dati significa perdere i dati grezzi. Se qualcuno vuole recuperare le metriche di un pod specifico, non è più possibile.

vmagent mantiene lo stato. In caso di reset si riparte da 0. Per dashboard e alert va bene e si può usare una normale sum() per coprire le lacune. Tuttavia, durante l'indisponibilità di vmagent perdiamo dati.

A tutto questo si aggiunge un componente in più da configurare e gestire.


Monitorare workloads brevi su larga scala richiede ben più degli strumenti predefiniti: servono scelte architetturali consapevoli. Se il suo team sta affrontando sfide simili di observability su Kubernetes, i nostri engineer possono aiutarla a progettare e implementare soluzioni su misura per il suo stack. Scopra di più su https://www.doit.com/solutions/forward-deployed-engineering.