Skip to content

Prometheus Utils — Hub

Query PromQL per OpenShift, organizzate per oggetto. Una pagina per ogni cosa che puoi trovarti davanti in un ticket: un pod, un workload, un namespace, un nodo, un volume.

Dentro ogni pagina l’ordine è sempre lo stesso — triage, metriche, stato, RCA, comandi oc, alert — così sai dove guardare senza ricordare dov’è finita una query.


Le pagine

OggettoContenuto
Pod / ContainerCPU, memoria, OOM, throttling, restart, probe, rete, filesystem
WorkloadDeployment, StatefulSet, DaemonSet, Job, HPA, PDB: repliche e rollout
NamespaceConsumo aggregato, efficienza, ResourceQuota, LimitRange, rendicontazione
NodoRisorse, condizioni, overcommit, kubelet, CRI-O, capacity planning
VolumiPVC, PV, StorageClass, riempimento, inode, IOPS, storage effimero
ReteTraffico, pacchetti scartati, conntrack, OVN-Kubernetes, ingress
JVMGarbage collection, heap, memoria non-heap, thread, dimensionamento
Control planeAPI server, etcd, cluster operator, scheduler, stack di monitoring

Ogni pagina si apre con la propria sezione Triage: le tre-cinque query da lanciare per prime su quell’oggetto.


Ordine di indagine

Quando non sai da dove partire, l’ordine è per causalità, non per gravità apparente:

Control plane → Nodo → Volumi → Rete → Namespace → Workload → Pod → JVM

Un API server rotto rende inaffidabile ogni altra metrica. Un nodo NotReady spiega da solo restart, Pending e throttling di decine di pod. Salire di livello prima di scendere evita di fare RCA su un effetto.

Il percorso inverso — dal pod verso l’alto — è quello giusto quando il sintomo è isolato a un singolo workload.


Filtri standard

Vanno in quasi ogni query. Copiali da qui.

Escludere i namespace di piattaforma

namespace!~"kube-.*|openshift-.*|default"

Chiamato $APP_NS nelle pagine. Toglilo quando stai guardando la piattaforma stessa (Nodo e Control plane non lo applicano).

Variante che include la mesh, quando OSSM è condivisa e ti interessa vederla:

namespace!~"kube-.*|openshift-.*"

Escludere le metriche-fantasma di cAdvisor

container!="", container!="POD"

Chiamato $REAL_CONTAINER. Senza, ogni pod viene contato due o tre volte: cAdvisor espone anche la serie aggregata a livello pod (container="") e quella della sandbox pause (container="POD"). È l’errore più frequente in assoluto e gonfia i totali del 100% e oltre.

Unica eccezione: le metriche container_network_*, che vivono sulla network namespace del pod e quindi esistono solo sulla serie con container="". Lì il filtro va omesso.

Separare l’applicazione dal sidecar

container!="istio-proxy" # solo container applicativi
container="istio-proxy" # solo sidecar

Su namespace con injection, senza questo stai sommando app + proxy senza accorgertene.


Placeholder

PlaceholderEsempioNota
$nsmyapp-prodnamespace="$ns" esatto, namespace=~"$ns" regex
$podmyapp-7d9f8-xk2lpquasi sempre pod=~"$pod.*": l’hash cambia a ogni deploy
$containerappdal containers[].name, non l’immagine
$workloadmyappnome del Deployment/StatefulSet
$nodeocp-worker-03in node="..." per KSM, instance=~"$node.*" per node-exporter
$range5m, 1h, 24hvedi costi in fondo

I placeholder vanno sostituiti prima di lanciare la query. La console di OpenShift non interpola le variabili: namespace="$ns" cerca letteralmente un namespace chiamato $ns e restituisce No datapoints found. L’interpolazione la fa solo Grafana, o una dashboard che dichiara la variabile.

Le query che ne contengono lo segnalano nel commento in testa:

# MiB · sostituisci $ns, $pod
sum by (namespace, pod, container) (
container_memory_working_set_bytes{
namespace="$ns", pod=~"$pod.*",
container!="", container!="POD"
}
) / 1024^2

Se una query torna vuota, il primo controllo è questo. Il secondo è che la metrica esista davvero sul cluster:

# numero di serie — 0 significa che la metrica non è esposta
count(container_memory_working_set_bytes)

Metriche di riferimento

MetricaTipoCosa misura davvero
container_cpu_usage_seconds_totalcounterCPU cumulativa → serve sempre rate()
container_cpu_cfs_throttled_periods_totalcounterperiodi in cui il cgroup è stato bloccato
container_cpu_cfs_periods_totalcounterperiodi CFS totali — il denominatore del throttling
container_memory_working_set_bytesgaugela memoria che l’OOM killer guarda
container_memory_rssgaugesolo RSS: sottostima, esclude la cache attiva
container_memory_usage_bytesgaugeinclude cache liberabile: sovrastima
container_oom_events_totalcounternumero esatto di OOM kill
kube_pod_container_resource_limitsgaugelimit dichiarati — fonte migliore di container_spec_*
kube_pod_container_status_restarts_totalcounterrestart cumulativi, azzerati alla ricreazione del pod
kube_pod_infogauge=1la metrica-ponte che porta il label node
jvm_gc_pause_seconds_maxgaugela pausa GC più lunga della finestra

Perché throttled_periods e non throttled_seconds

container_cpu_cfs_throttled_seconds_total somma il tempo bloccato di tutti i thread: un container con 40 thread può totalizzare 4 secondi di throttling per secondo reale. Il numero non ha un massimo interpretabile.

Il rapporto throttled_periods / periods è una frazione 0–1 con soglie stabili:

ValoreLettura
< 1%fisiologico
1 – 5%da tenere d’occhio
5 – 20%il container soffre, latenza già degradata
> 20%grave: l’app è ferma un quinto del tempo

Percentuale nelle query, frazione negli alert

Le query di queste pagine restituiscono percentuali 0–100: le lanci in Observe → Metrics, dove nessuno formatta il risultato, e leggere 99,9 è più immediato che leggere 0,999.

Le PrometheusRule e le dashboard fanno l’opposto e restano in frazione 0–1, perché lì il valore viene formattato da qualcun altro:

  • negli alert, humanizePercentage converte 0–1 in percentuale. Passandogli già una percentuale stamperebbe 9990%.
  • nelle dashboard, l’asse percentunit fa la stessa conversione.

Se prendi una query da una pagina per metterla in un alert, togli il 100 * e riporta la soglia in frazione.

I due join che servono sempre

Aggiungere il nodo a qualunque metrica pod-level:

* on(namespace, pod) group_left(node) max by (namespace,pod,node) (kube_pod_info)

Aggiungere il workload (Deployment/StatefulSet) risolvendo la catena Pod → ReplicaSet → owner:

* on(namespace, pod) group_left(workload, workload_type)
max by (namespace, pod, workload, workload_type) (namespace_workload_pod:kube_pod_owner:relabel)

Questa recording rule è preinstallata da cluster-monitoring-operator su OCP 4.x. Meglio del label_replace con regex sull’hash del ReplicaSet, che si rompe sui nomi con trattini.

Vanno sempre in coda all’espressione, dopo l’eventuale topk. Metterli prima moltiplica il costo della query.

Perché max by (...) intorno a kube_pod_info

Senza, su molti cluster il join fallisce con:

found duplicate series for the match group {namespace="...", pod="..."}
many-to-many matching not allowed: matching labels must be unique on one side

kube_pod_info può esporre più serie per lo stesso pod — differiscono per label che al join non interessano (pod_ip, host_ip, created_by_*), oppure perché kube-state-metrics gira con più repliche o shard. group_left non sa quale scegliere e rifiuta il match.

max by (namespace, pod, node) collassa i duplicati in una serie sola tenendo solo i label che servono. Il valore è irrilevante: kube_pod_info vale sempre 1, serve solo come portatore di label.

Verifica se il tuo cluster è affetto:

# numero di serie
count by (namespace, pod) (kube_pod_info) > 1

Qualunque riga restituita significa che il join senza max by fallirà. Lo stesso vale per ogni altra metrica usata come ponte in un group_left: se duplica, va deduplicata allo stesso modo.


Recording rule preinstallate

Su cluster grandi fanno la differenza tra una query che risponde e una che va in timeout. Verifica cosa hai:

# numero di serie
count by (__name__) ({__name__=~"node_namespace_pod_container:.*|namespace_.*:kube_.*|instance:node_.*|cluster:node_.*"})
Recording ruleSostituisce
node_namespace_pod_container:container_cpu_usage_seconds_total:sum_iraterate(container_cpu_usage_seconds_total[5m]) aggregato
namespace_cpu:kube_pod_container_resource_requests:sumsomma delle CPU request per namespace
namespace_memory:kube_pod_container_resource_limits:sumsomma dei memory limit per namespace
instance:node_cpu_utilisation:rate1mutilizzo CPU per nodo
instance:node_memory_utilisation:ratioutilizzo memoria per nodo
cluster:node_cpu:ratio_rate5mutilizzo CPU dell’intero cluster
namespace_workload_pod:kube_pod_owner:relabella catena Pod → workload

Dove lanciarle

Console: Observe → Metrics.

Da riga di comando, senza port-forward:

Terminal window
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \
promtool query instant http://localhost:9090 'up == 0'

Con port-forward, per lavorare dal browser sull’interfaccia Prometheus:

Terminal window
oc -n openshift-monitoring port-forward svc/prometheus-k8s 9090:9090
# http://localhost:9090

Su user workload monitoring:

Terminal window
oc -n openshift-user-workload-monitoring exec -c prometheus prometheus-user-workload-0 -- \
promtool query instant http://localhost:9090 'ALERTS'

Query con range, in formato leggibile:

Terminal window
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \
promtool query instant http://localhost:9090 \
'topk(10, sum by (namespace) (container_memory_working_set_bytes{container!="",container!="POD"}))' \
| sed 's/=>/\n =>/'

Costi

  • [5m] è economico, [24h] no. Su cluster con molte serie una query a [30d] può mandare in timeout la richiesta o spingere Prometheus in OOM.
  • Le subquery ([7d:5m]) sono il costrutto più caro di PromQL. Allarga lo step con la finestra: [7d:5m], [30d:1h]. Mai in una dashboard con auto-refresh.
  • topk() non riduce il costo: Prometheus valuta tutte le serie e poi taglia. Per ridurre davvero, restringi con i label matcher.
  • Ordine dei matcher: prima il più selettivo (namespace=), poi i negati (!~), che sono i più cari.
  • Nelle pagine, le query segnalate come costose vanno lanciate a mano, non messe in alert o dashboard.