Skip to content

Rete

Le metriche container_network_* sono raccolte da cAdvisor sulla network namespace del pod, quindi esistono solo sulla serie con container="". È l’unico caso in cui il filtro container!="" va omesso: usarlo restituisce una serie vuota.

Triagedrop, OVN, ingress
Traffico pod · Traffico nodo
Drop ed errori · Conntrack
OVN-Kubernetes
Ingress e router
EgressIP
Service Meshquando c’è OSSM
Verifica connettività · Alert

Triage

T1. Pacchetti scartati

# pacchetti al secondo
topk(20,
sum by (namespace, pod) (
rate(container_network_receive_packets_dropped_total[5m])
+
rate(container_network_transmit_packets_dropped_total[5m])
)
) > 0

T2. Componenti OVN non pronti

# elenco: ogni serie restituita soddisfa la condizione
kube_pod_container_status_ready{namespace="openshift-ovn-kubernetes"} == 0

T3. Route senza backend attivi

# elenco: ogni serie restituita soddisfa la condizione
haproxy_backend_up == 0

Route esposta ma nessun pod pronto dietro: gli utenti vedono 503 e la causa è la readiness dei pod, non il router.

T4. 5xx dal router

# eventi al secondo
topk(20,
sum by (namespace, route) (rate(haproxy_backend_http_responses_total{code="5xx"}[5m]))
) > 0

T5. Conntrack vicino al limite

# conteggio
node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.80

Sopra 0.80 le nuove connessioni iniziano a essere rifiutate in modo apparentemente casuale, e l’applicazione lo vede come timeout.


Traffico dei pod

Top per traffico in ingresso

# MiB
topk(20,
sum by (namespace, pod) (
rate(container_network_receive_bytes_total{
namespace!~"kube-.*|openshift-.*|default"
}[5m])
)
* on(namespace, pod) group_left(node) max by (namespace,pod,node) (kube_pod_info)
) / 1024^2

Top per traffico in uscita

# MiB
topk(20,
sum by (namespace, pod) (
rate(container_network_transmit_bytes_total{
namespace!~"kube-.*|openshift-.*|default"
}[5m])
)
* on(namespace, pod) group_left(node) max by (namespace,pod,node) (kube_pod_info)
) / 1024^2

Un pod specifico

# MiB · sostituisci $ns, $pod
sum by (pod) (rate(container_network_receive_bytes_total{namespace="$ns", pod=~"$pod.*"}[5m])) / 1024^2
# MiB · sostituisci $ns, $pod
sum by (pod) (rate(container_network_transmit_bytes_total{namespace="$ns", pod=~"$pod.*"}[5m])) / 1024^2

Per interfaccia

# MiB · sostituisci $ns, $pod
sum by (pod, interface) (
rate(container_network_receive_bytes_total{namespace="$ns", pod=~"$pod.*"}[5m])
) / 1024^2

Su pod con più interfacce (Multus), il label interface le separa. eth0 è la rete di default, net1 e successive sono le NetworkAttachmentDefinition.

Spike brevi

# MiB
topk(20,
max_over_time(
sum by (namespace, pod) (
rate(container_network_transmit_bytes_total{
namespace!~"kube-.*|openshift-.*|default"
}[1m])
)[6h:1m]
)
) / 1024^2

Finestra [1m] dentro una subquery a 6 ore: cattura i picchi che una rate([5m]) media via. È la query per identificare job notturni, sincronizzazioni verso sistemi esterni, dump.

Pacchetti al secondo

# pacchetti al secondo
topk(20,
sum by (namespace, pod) (
rate(container_network_receive_packets_total{
namespace!~"kube-.*|openshift-.*|default"
}[5m])
)
)

Dimensione media del pacchetto

# byte al secondo · sostituisci $ns, $pod
rate(container_network_receive_bytes_total{namespace="$ns", pod=~"$pod.*"}[5m])
/
rate(container_network_receive_packets_total{namespace="$ns", pod=~"$pod.*"}[5m])

Pacchetti sotto i 200 byte ad alta frequenza indicano protocolli chatty o mancanza di batching: costano CPU al kernel e al sidecar molto più del volume di dati. Il sintomo collaterale è un softirq alto sul nodo.

Sbilanciamento tra repliche

# MiB · sostituisci $ns, $workload
sum by (pod) (
rate(container_network_receive_bytes_total{namespace="$ns", pod=~"$workload-.*"}[5m])
) / 1024^2

Repliche dietro lo stesso Service devono ricevere traffico comparabile. Una a zero è fuori dagli endpoint (readiness fallita); una a 5× le altre indica sessioni sticky o un client che non rifà la risoluzione DNS.

Traffico per namespace

# MiB
topk(20,
sum by (namespace) (
rate(container_network_transmit_bytes_total{
namespace!~"kube-.*|openshift-.*|default"
}[5m])
)
) / 1024^2

Traffico dei nodi

Per nodo, aggregato dai pod

# MiB
sum by (node) (
rate(container_network_receive_bytes_total[5m])
* on(namespace, pod) group_left(node) max by (namespace,pod,node) (kube_pod_info)
) / 1024^2

Sbilanciamenti forti tra nodi indicano che un workload ad alto traffico non è distribuito, oppure che quel nodo ospita l’ingress.

Interfacce fisiche

# MiB
topk(10,
sum by (instance) (
rate(node_network_receive_bytes_total{device!~"lo|veth.*|ovn.*|br-.*|genev.*|tun.*"}[5m])
)
) / 1024^2
# MiB
topk(10,
sum by (instance) (
rate(node_network_transmit_bytes_total{device!~"lo|veth.*|ovn.*|br-.*|genev.*|tun.*"}[5m])
)
) / 1024^2

Comprende tutto: traffico applicativo, storage, replica etcd, tunnel Geneve. Su nodi con una sola NIC condivisa tra dati e storage, la saturazione qui spiega latenze applicative altrimenti inspiegabili.

Saturazione della NIC

# byte al secondo
sum by (instance, device) (
rate(node_network_receive_bytes_total{device!~"lo|veth.*"}[5m])
) * 8
/
(node_network_speed_bytes{device!~"lo|veth.*"} * 8)

Frazione della banda nominale in uso. Sopra 0.70 sostenuto la NIC inizia a essere il collo di bottiglia.

MTU

# byte
node_network_mtu_bytes{device!~"lo|veth.*"}

Un MTU disallineato tra nodi (o tra nodo e rete fisica) produce il sintomo peggiore da diagnosticare: le connessioni si stabiliscono, i pacchetti piccoli passano, e i trasferimenti grandi si bloccano. Su OVN-K con incapsulamento Geneve l’MTU dell’overlay è tipicamente 100 byte sotto quello fisico.

# numero di serie
count by (mtu) (node_network_mtu_bytes{device="ens192"})

Più di un valore = disallineamento.


Drop ed errori

Drop a livello pod

# pacchetti al secondo
topk(20,
sum by (namespace, pod) (
rate(container_network_receive_packets_dropped_total{
namespace!~"kube-.*|openshift-.*|default"
}[5m])
)
) > 0
# pacchetti al secondo
topk(20,
sum by (namespace, pod) (
rate(container_network_transmit_packets_dropped_total{
namespace!~"kube-.*|openshift-.*|default"
}[5m])
)
) > 0

Deve essere zero. Drop in ricezione = buffer del socket pieno, l’applicazione non consuma abbastanza in fretta. Drop in trasmissione = coda dell’interfaccia satura.

Drop come frazione del traffico

# pacchetti al secondo · sostituisci $ns
sum by (namespace, pod) (rate(container_network_receive_packets_dropped_total{namespace=~"$ns"}[5m]))
/
sum by (namespace, pod) (rate(container_network_receive_packets_total{namespace=~"$ns"}[5m]))

Sopra 0.001 (un pacchetto su mille) è già anomalo su rete interna.

Errori sui pod

# eventi al secondo
sum by (namespace, pod) (
rate(container_network_receive_errors_total[5m])
+
rate(container_network_transmit_errors_total[5m])
) > 0

Errori sulle interfacce fisiche

# eventi al secondo
sum by (instance, device) (rate(node_network_receive_errs_total{device!~"lo|veth.*"}[5m])) > 0
# eventi al secondo
sum by (instance, device) (rate(node_network_transmit_errs_total{device!~"lo|veth.*"}[5m])) > 0

Errori sulle interfacce fisiche sono hardware o driver: cavo, SFP, MTU mismatch, offload buggato. Escalation infrastruttura, non applicativa.

Drop sulle interfacce fisiche

# pacchetti scartati al secondo
sum by (instance, device) (rate(node_network_receive_drop_total{device!~"lo|veth.*"}[5m])) > 0

Coda di trasmissione

# numero di pacchetti in coda sull'interfaccia
node_network_transmit_queue_length{device!~"lo|veth.*"}

Conntrack

# conteggio
node_nf_conntrack_entries / node_nf_conntrack_entries_limit
# conteggio
topk(10, node_nf_conntrack_entries)
# numero massimo di connessioni tracciabili
node_nf_conntrack_entries_limit

Il conntrack tiene traccia di ogni connessione che attraversa il nodo. Quando si riempie, le nuove connessioni vengono scartate silenziosamente e l’applicazione le vede come timeout, in modo intermittente e apparentemente casuale.

Sintomo classico su nodi che ospitano molti pod con connessioni brevi: client HTTP senza keep-alive, scraper, health check frequenti.

Terminal window
oc debug node/$node -- chroot /host sysctl net.netfilter.nf_conntrack_max
oc debug node/$node -- chroot /host sysctl net.netfilter.nf_conntrack_count
oc debug node/$node -- chroot /host dmesg | grep -i "conntrack table full"

nf_conntrack: table full, dropping packet nel dmesg è la conferma definitiva.


OVN-Kubernetes

Su OCP 4.18 è la SDN di default.

Metriche disponibili sul tuo cluster

# numero di serie
count by (__name__) ({__name__=~"ovnkube.*|ovs_.*|ovn_.*"})

Lancia questa per prima. I nomi delle metriche OVN cambiano tra minor di OCP, e copiare una query per una metrica che non esiste fa perdere tempo.

Restart dei componenti

# conteggio nell'ultima ora
sum by (pod, container) (
increase(kube_pod_container_status_restarts_total{
namespace="openshift-ovn-kubernetes"
}[1h])
) > 0

ovnkube-node, ovnkube-control-plane, ovs-node non devono restartare in produzione. Se lo fanno, i pod sui nodi interessati hanno rete parziale o assente, e i sintomi applicativi che stai indagando sono conseguenza.

Componenti non ready

# elenco: ogni serie restituita soddisfa la condizione
kube_pod_container_status_ready{namespace="openshift-ovn-kubernetes"} == 0

Copertura del DaemonSet

# numero di pod del DaemonSet
kube_daemonset_status_number_ready{namespace="openshift-ovn-kubernetes"}
!=
kube_daemonset_status_desired_number_scheduled{namespace="openshift-ovn-kubernetes"}

Un nodo senza ovnkube-node ha rete rotta pur risultando Ready.

Latenza di configurazione della rete di un pod

# secondi
histogram_quantile(0.99,
sum by (le) (rate(ovnkube_controller_ready_duration_seconds_bucket[5m]))
)

Tempo che OVN impiega a rendere operativa la rete di un pod appena schedulato. Oltre i 5 secondi il control plane è in affanno e i deploy diventano lenti in modo inspiegabile.

Elezione del leader

# conteggio nell'ultima ora
increase(ovnkube_controller_leader_changes_total[1h]) > 0

Risorse consumate da OVN

# core CPU
sum by (pod) (
rate(container_cpu_usage_seconds_total{
namespace="openshift-ovn-kubernetes", container!="", container!="POD"
}[5m])
)
# MiB
sum by (pod) (
container_memory_working_set_bytes{
namespace="openshift-ovn-kubernetes", container!="", container!="POD"
}
) / 1024^2

Su cluster grandi ovnkube-node può consumare parecchia memoria: cresce con il numero di pod, servizi e NetworkPolicy. Un OOM di ovnkube-node porta giù la rete del nodo.

Errori OVS

# eventi al secondo
sum by (instance) (rate(ovs_vswitchd_netlink_overflow_total[5m])) > 0

Flow installati

# numero di flow installati nel datapath
ovs_vswitchd_dp_flows_total

Un numero di flow che cresce senza sosta indica NetworkPolicy troppo granulari o un leak nel datapath.

Comandi

Terminal window
oc get pods -n openshift-ovn-kubernetes -o wide
oc get ds -n openshift-ovn-kubernetes
# Log del controller su un nodo
POD=$(oc get pods -n openshift-ovn-kubernetes -l app=ovnkube-node --field-selector spec.nodeName=$node -o name | head -1)
oc logs -n openshift-ovn-kubernetes $POD -c ovnkube-controller --tail=100
# Database OVN (sui nodi del control plane)
oc rsh -n openshift-ovn-kubernetes -c northd $(oc get pods -n openshift-ovn-kubernetes -l app=ovnkube-control-plane -o name | head -1) \
ovn-nbctl show 2>/dev/null | head -40
# NetworkPolicy di un namespace
oc get networkpolicy -n $ns
oc describe networkpolicy -n $ns

Ingress e router

Il router OpenShift è basato su HAProxy. Se soffre lui, soffre tutto ciò che è esposto.

Restart del router

# conteggio nell'ultima ora
sum by (pod, container) (
increase(kube_pod_container_status_restarts_total{
namespace="openshift-ingress", container="router"
}[1h])
) > 0

Backend senza server attivi

# elenco: ogni serie restituita soddisfa la condizione
haproxy_backend_up == 0

Route esposta, nessun pod pronto dietro. Gli utenti ricevono 503 e la causa è la readiness dei pod applicativi, non il router. È la distinzione che chiude più in fretta le segnalazioni di irraggiungibilità.

5xx per route

# eventi al secondo
topk(20,
sum by (namespace, route) (
rate(haproxy_backend_http_responses_total{code="5xx"}[5m])
)
) > 0

I 5xx contati qui sono generati dal backend applicativo, non da HAProxy. Un 503 di HAProxy stesso (nessun endpoint) si vede con la query precedente.

Distribuzione dei codici di risposta

# eventi al secondo
sum by (code) (rate(haproxy_backend_http_responses_total[5m]))
# eventi al secondo · sostituisci $ns
sum by (namespace, route, code) (
rate(haproxy_backend_http_responses_total{namespace="$ns"}[5m])
)

Tasso di errore per route

# eventi al secondo
sum by (namespace, route) (rate(haproxy_backend_http_responses_total{code=~"5xx"}[5m]))
/
sum by (namespace, route) (rate(haproxy_backend_http_responses_total[5m]))

Coda del backend

# numero di richieste in coda
topk(20, haproxy_backend_current_queue) > 0

HAProxy accoda quando i backend non tengono il ritmo. Coda sostenuta = throughput applicativo insufficiente o maxconn troppo basso.

Sessioni attive

# numero di sessioni attive
sum by (pod) (haproxy_frontend_current_sessions)
# percentuale 0-100 delle sessioni disponibili in uso
100 * (
sum by (pod) (haproxy_frontend_current_sessions)
/
sum by (pod) (haproxy_frontend_limit_sessions)
)

Tempo di risposta del backend

# secondi
haproxy_backend_response_latency_average_seconds
# secondi
topk(20, haproxy_backend_connect_latency_average_seconds) > 0.1

Latenza di connessione alta significa che il router fatica a raggiungere i pod: rete, o pod sovraccarichi che non accettano connessioni.

Connessioni rifiutate

# connessioni rifiutate al secondo
sum by (pod) (rate(haproxy_frontend_connections_denied_total[5m])) > 0

Numero di route

# numero di serie
count(kube_service_info{namespace="openshift-ingress"})
# numero di route per IngressController
sum by (name) (route_metrics_route_type)

Oltre alcune migliaia di route per IngressController, il reload di HAProxy diventa costoso e ogni modifica a una route provoca un blip su tutte le altre. La soluzione è lo sharding per label.

Terminal window
oc get ingresscontroller -n openshift-ingress-operator
oc get routes -A --no-headers | wc -l
oc get routes -A -o json | jq -r '.items[] | .spec.host' | awk -F. '{print $NF}' | sort | uniq -c

Reload di HAProxy

# eventi al secondo
rate(haproxy_process_reloads_total[5m])

Un tasso di reload alto significa che le route cambiano continuamente: qualcosa sta ricreando oggetti in loop.


EgressIP

Terminal window
oc get egressip
oc get cloudprivateipconfig
oc describe egressip <nome> | sed -n '/Status:/,$p'
oc get nodes -l k8s.ovn.org/egress-assignable

Namespace e pod coinvolti

# numero di serie
count by (namespace) (kube_pod_labels{label_egress_group!=""})

Il selettore effettivo dipende da come è configurata la EgressIP: può essere per namespace (namespaceSelector) o per pod (podSelector).

Terminal window
oc get egressip <nome> -o jsonpath='{.spec}' | jq
oc get ns -l <label-del-namespaceSelector>

Verifica dell’IP sorgente effettivo

Terminal window
oc rsh -n $ns $pod curl -s --max-time 5 https://ifconfig.me

Su cluster air-gapped serve un endpoint interno che rifletta l’IP sorgente. In alternativa, dal lato del sistema di destinazione: guardare i log di connessione e verificare che l’IP visto sia quello dell’EgressIP e non quello del nodo.

Un failover di EgressIP produce interruzioni brevi verso i sistemi esterni, che l’applicazione vede come connection reset senza nessuna traccia lato Kubernetes. Se il sintomo è intermittente e coincide con manutenzioni sui nodi egress-assignable, è quello.

Terminal window
# Su quale nodo è assegnato l'IP adesso
oc get egressip <nome> -o jsonpath='{.status.items}' | jq
# Storico degli spostamenti
oc get events -A --field-selector reason=EgressIPAssigned --sort-by=.lastTimestamp | tail

Service Mesh

Se il namespace ha injection, le metriche di Envoy danno molto più di container_network_*: codici di risposta, servizio di destinazione, latenza per percentile.

Metriche disponibili

# numero di serie
count by (__name__) ({__name__=~"istio_.*"})

Richieste per servizio

# eventi al secondo · sostituisci $ns
sum by (destination_service_name, response_code) (
rate(istio_requests_total{destination_workload_namespace="$ns"}[5m])
)

Tasso di errore

# eventi al secondo · sostituisci $ns
sum by (destination_service_name) (
rate(istio_requests_total{destination_workload_namespace="$ns", response_code=~"5.."}[5m])
)
/
sum by (destination_service_name) (
rate(istio_requests_total{destination_workload_namespace="$ns"}[5m])
)

Latenza per percentile

# millisecondi · sostituisci $ns
histogram_quantile(0.99,
sum by (le, destination_service_name) (
rate(istio_request_duration_milliseconds_bucket{destination_workload_namespace="$ns"}[5m])
)
)

Chi chiama chi

# eventi al secondo · sostituisci $ns
sum by (source_workload, destination_service_name) (
rate(istio_requests_total{destination_workload_namespace="$ns"}[5m])
)

È l’informazione che container_network_* non può dare: la topologia reale delle chiamate. Serve per capire quale dipendenza sta causando il degrado.

Flag di risposta di Envoy

# eventi al secondo · sostituisci $ns
sum by (response_flags, destination_service_name) (
rate(istio_requests_total{destination_workload_namespace="$ns", response_flags!="-"}[5m])
)
response_flagsSignificato
UFupstream connection failure: il backend non risponde
UOupstream overflow: circuit breaker scattato
URXsuperato il limite di retry
UTupstream timeout
NRnessuna route configurata
DCdownstream connection termination: il client ha chiuso

UO è quello che si diagnostica peggio senza queste metriche: il circuit breaker della DestinationRule scatta e le richieste vengono rifiutate dal sidecar prima di raggiungere l’applicazione, che quindi nei suoi log non vede nulla.

Consumo dei sidecar

# GiB
sum by (namespace) (
container_memory_working_set_bytes{container="istio-proxy"}
) / 1024^3
# core CPU
sum by (namespace) (
rate(container_cpu_usage_seconds_total{container="istio-proxy"}[5m])
)

Un istio-proxy che consuma molta memoria riceve troppi endpoint da istiod: si limita con una risorsa Sidecar che restringe la visibilità del namespace.


Verifica della connettività

Le metriche dicono quanto traffico passa, non se una destinazione è raggiungibile. Per quello servono i comandi.

Terminal window
NS=<namespace>; POD=<pod>
# Risoluzione DNS
oc rsh -n $NS $POD getent hosts <host>
oc rsh -n $NS $POD cat /etc/resolv.conf
# Porta TCP aperta (senza installare nulla)
oc rsh -n $NS $POD timeout 5 bash -c "</dev/tcp/<host>/<porta>" && echo OPEN || echo CLOSED
# Con debug pod se il container è minimale
oc debug -n $NS pod/$POD -- sh -c 'timeout 5 bash -c "</dev/tcp/<host>/<porta>" && echo OPEN || echo CLOSED'
# Connessioni aperte, con processo
oc rsh -n $NS $POD ss -tnp
# Conteggio per stato
oc rsh -n $NS $POD ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn
# MTU effettivo dell'interfaccia
oc rsh -n $NS $POD ip link show eth0
# Endpoint dietro un Service
oc get endpointslice -n $NS -l kubernetes.io/service-name=<svc>
# NetworkPolicy applicabili
oc get networkpolicy -n $NS -o yaml

Molte connessioni in TIME_WAIT non sono un problema. Molte in CLOSE_WAIT sì: l’applicazione non chiude i socket, ed è un file descriptor leak che porterà a Too many open files.

Approfondimento sui casi con mesh ed EgressIP: Test di connettività da pod.


Limiti delle metriche di rete in container

Va detto, perché evita di cercare risposte dove non ci sono:

  • Non c’è latenza. cAdvisor conta byte e pacchetti, non tempi di round-trip. Per la latenza servono le metriche applicative o quelle di Envoy.
  • Non c’è la destinazione. Da container_network_* non si può sapere con chi sta parlando un pod. Servono le metriche della mesh, i NetFlow di OVN, o ss -tnp dal pod.
  • Il traffico host-network non compare. I pod con hostNetwork: true usano l’interfaccia del nodo: cercali in node_network_*.
  • Le NetworkPolicy non hanno metriche di drop. Un pacchetto bloccato da una policy semplicemente non arriva, e non compare da nessuna parte. La verifica è per esclusione.

Alert

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: network-health-custom
namespace: openshift-monitoring
labels:
role: alert-rules
spec:
groups:
- name: network.pod
rules:
- alert: PodPacketsDropped
expr: |
sum by (namespace, pod) (
rate(container_network_receive_packets_dropped_total[5m])
+ rate(container_network_transmit_packets_dropped_total[5m])
) > 1
for: 15m
labels:
severity: warning
annotations:
summary: "{{ $labels.pod }} ({{ $labels.namespace }}): {{ $value | printf \"%.1f\" }} pacchetti/s scartati"
- name: network.node
rules:
- alert: NodeConntrackNearlyFull
expr: node_nf_conntrack_entries / node_nf_conntrack_entries_limit > 0.80
for: 15m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }}: conntrack all'{{ $value | humanizePercentage }}"
description: "Le nuove connessioni verranno scartate in modo intermittente e l'applicazione le vedrà come timeout."
- alert: NodeNICErrors
expr: |
sum by (instance, device) (
rate(node_network_receive_errs_total{device!~"lo|veth.*"}[5m])
+ rate(node_network_transmit_errs_total{device!~"lo|veth.*"}[5m])
) > 0
for: 15m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} {{ $labels.device }}: errori sull'interfaccia fisica"
description: "Hardware o driver: cavo, SFP, MTU mismatch, offload. Escalation infrastruttura."
- name: network.ovn
rules:
- alert: OVNComponentRestarting
expr: |
increase(kube_pod_container_status_restarts_total{
namespace="openshift-ovn-kubernetes"
}[1h]) > 0
labels:
severity: critical
annotations:
summary: "{{ $labels.pod }} in openshift-ovn-kubernetes riavviato"
description: "I pod sui nodi interessati possono avere rete parziale o assente."
- alert: OVNDaemonSetIncomplete
expr: |
kube_daemonset_status_number_ready{namespace="openshift-ovn-kubernetes"}
!= kube_daemonset_status_desired_number_scheduled{namespace="openshift-ovn-kubernetes"}
for: 15m
labels:
severity: critical
annotations:
summary: "{{ $labels.daemonset }}: manca su alcuni nodi"
description: "Un nodo senza ovnkube-node ha rete rotta pur risultando Ready."
- name: network.ingress
rules:
- alert: RouteBackendDown
expr: haproxy_backend_up == 0
for: 10m
labels:
severity: critical
annotations:
summary: "Route {{ $labels.route }} ({{ $labels.namespace }}): nessun backend attivo"
description: "Gli utenti ricevono 503. La causa è la readiness dei pod, non il router."
- alert: RouteHighErrorRate
expr: |
sum by (namespace, route) (rate(haproxy_backend_http_responses_total{code="5xx"}[5m]))
/
sum by (namespace, route) (rate(haproxy_backend_http_responses_total[5m]))
> 0.05
for: 10m
labels:
severity: warning
annotations:
summary: "Route {{ $labels.route }}: {{ $value | humanizePercentage }} di risposte 5xx"
description: "Errori generati dal backend applicativo, non dal router."
- alert: RouterQueueGrowing
expr: haproxy_backend_current_queue > 10
for: 15m
labels:
severity: warning
annotations:
summary: "Route {{ $labels.route }}: {{ $value }} richieste in coda"
description: "Il backend non tiene il ritmo. Throughput insufficiente o maxconn troppo basso."

Soglie di riferimento

MetricaSanoAttenzioneCritico
Drop pacchetti0> 0> 0.1% del traffico
Errori NIC0> 0> 0
Conntrack< 50%50 – 80%> 80%
Saturazione NIC< 50%50 – 70%> 70%
5xx per route< 0.1%0.1 – 1%> 5%
Coda HAProxy01 – 10> 10
Latenza connect HAProxy< 10 ms10 – 100 ms> 100 ms
Restart componenti OVN0> 0> 0

Vedi anche