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
| Oggetto | Contenuto |
|---|---|
| Pod / Container | CPU, memoria, OOM, throttling, restart, probe, rete, filesystem |
| Workload | Deployment, StatefulSet, DaemonSet, Job, HPA, PDB: repliche e rollout |
| Namespace | Consumo aggregato, efficienza, ResourceQuota, LimitRange, rendicontazione |
| Nodo | Risorse, condizioni, overcommit, kubelet, CRI-O, capacity planning |
| Volumi | PVC, PV, StorageClass, riempimento, inode, IOPS, storage effimero |
| Rete | Traffico, pacchetti scartati, conntrack, OVN-Kubernetes, ingress |
| JVM | Garbage collection, heap, memoria non-heap, thread, dimensionamento |
| Control plane | API 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 → JVMUn 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 applicativicontainer="istio-proxy" # solo sidecarSu namespace con injection, senza questo stai sommando app + proxy senza accorgertene.
Placeholder
| Placeholder | Esempio | Nota |
|---|---|---|
$ns | myapp-prod | namespace="$ns" esatto, namespace=~"$ns" regex |
$pod | myapp-7d9f8-xk2lp | quasi sempre pod=~"$pod.*": l’hash cambia a ogni deploy |
$container | app | dal containers[].name, non l’immagine |
$workload | myapp | nome del Deployment/StatefulSet |
$node | ocp-worker-03 | in node="..." per KSM, instance=~"$node.*" per node-exporter |
$range | 5m, 1h, 24h | vedi 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, $podsum by (namespace, pod, container) ( container_memory_working_set_bytes{ namespace="$ns", pod=~"$pod.*", container!="", container!="POD" }) / 1024^2Se 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 è espostacount(container_memory_working_set_bytes)Metriche di riferimento
| Metrica | Tipo | Cosa misura davvero |
|---|---|---|
container_cpu_usage_seconds_total | counter | CPU cumulativa → serve sempre rate() |
container_cpu_cfs_throttled_periods_total | counter | periodi in cui il cgroup è stato bloccato |
container_cpu_cfs_periods_total | counter | periodi CFS totali — il denominatore del throttling |
container_memory_working_set_bytes | gauge | la memoria che l’OOM killer guarda |
container_memory_rss | gauge | solo RSS: sottostima, esclude la cache attiva |
container_memory_usage_bytes | gauge | include cache liberabile: sovrastima |
container_oom_events_total | counter | numero esatto di OOM kill |
kube_pod_container_resource_limits | gauge | limit dichiarati — fonte migliore di container_spec_* |
kube_pod_container_status_restarts_total | counter | restart cumulativi, azzerati alla ricreazione del pod |
kube_pod_info | gauge=1 | la metrica-ponte che porta il label node |
jvm_gc_pause_seconds_max | gauge | la 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:
| Valore | Lettura |
|---|---|
< 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,
humanizePercentageconverte 0–1 in percentuale. Passandogli già una percentuale stamperebbe9990%. - nelle dashboard, l’asse
percentunitfa 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 sidekube_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 seriecount by (namespace, pod) (kube_pod_info) > 1Qualunque 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 seriecount by (__name__) ({__name__=~"node_namespace_pod_container:.*|namespace_.*:kube_.*|instance:node_.*|cluster:node_.*"})| Recording rule | Sostituisce |
|---|---|
node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate | rate(container_cpu_usage_seconds_total[5m]) aggregato |
namespace_cpu:kube_pod_container_resource_requests:sum | somma delle CPU request per namespace |
namespace_memory:kube_pod_container_resource_limits:sum | somma dei memory limit per namespace |
instance:node_cpu_utilisation:rate1m | utilizzo CPU per nodo |
instance:node_memory_utilisation:ratio | utilizzo memoria per nodo |
cluster:node_cpu:ratio_rate5m | utilizzo CPU dell’intero cluster |
namespace_workload_pod:kube_pod_owner:relabel | la catena Pod → workload |
Dove lanciarle
Console: Observe → Metrics.
Da riga di comando, senza port-forward:
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:
oc -n openshift-monitoring port-forward svc/prometheus-k8s 9090:9090# http://localhost:9090Su user workload monitoring:
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:
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.