Namespace
Il livello dell’attribuzione: chi consuma cosa, chi sta crescendo, chi ha esaurito la propria quota. È la pagina da cui si estraggono i numeri da portare a un team applicativo o in una riunione di capacity.
| Triage | i namespace da guardare per primi |
| CPU · Memoria | consumo, picchi, efficienza |
| Quota e LimitRange | |
| Salute | restart, pod non sani, OOM |
| Rete · Storage | |
| Inventario | conteggi, QoS, oggetti senza limit |
| Rendicontazione | il report mensile |
Comandi oc · Alert |
Triage
T1. Top 10 per consumo CPU
# core CPUtopk(10, sum by (namespace) ( rate(container_cpu_usage_seconds_total{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" }[5m]) ))T2. Top 10 per consumo memoria
# GiBtopk(10, sum by (namespace) ( container_memory_working_set_bytes{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" } )) / 1024^3T3. Namespace vicini alla quota
# percentuale 0-100100 * topk(10, sum by (namespace, resource) (kube_resourcequota{type="used"}) / sum by (namespace, resource) (kube_resourcequota{type="hard"})) > 85T4. Namespace con più restart
# conteggio nelle ultime 24 oretopk(10, sum by (namespace) ( increase(kube_pod_container_status_restarts_total{ namespace!~"kube-.*|openshift-.*|default" }[24h]) )) > 0T5. Namespace con pod non sani
# numero di seriecount by (namespace) ( kube_pod_status_phase{ phase=~"Pending|Failed|Unknown", namespace!~"kube-.*|openshift-.*|default" } == 1) > 0CPU
Consumo attuale (core)
# core CPUtopk(20, sum by (namespace) ( rate(container_cpu_usage_seconds_total{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" }[5m]) ))Versione economica con la recording rule, da preferire su cluster grandi o finestre lunghe:
# secondi · sostituisci $nssum by (namespace) ( node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{ namespace=~"$ns" })Picco su 7 giorni
# secondi · sostituisci $nsmax_over_time( sum by (namespace) ( node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=~"$ns"} )[7d:5m])Il numero per dimensionare la quota. Su finestre più lunghe allarga lo step: [30d:1h].
Media su 7 giorni
# secondi · sostituisci $nsavg_over_time( sum by (namespace) ( node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=~"$ns"} )[7d:5m])Fattore di burst
# secondi · sostituisci $nsmax_over_time(sum by (namespace) (node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=~"$ns"})[7d:5m])/avg_over_time(sum by (namespace) (node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=~"$ns"})[7d:5m])< 2 = carico piatto. > 5 = carico a burst (batch, job schedulati): servono limit generosi ma request basse, altrimenti si prenota cluster che resta inutilizzato.
Richieste vs consumo — l’efficienza
# percentuale 0-100100 * ( sum by (namespace) ( rate(container_cpu_usage_seconds_total{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" }[5m]) ) / sum by (namespace) ( namespace_cpu:kube_pod_container_resource_requests:sum{ namespace!~"kube-.*|openshift-.*|default" } ))Frazione della CPU prenotata che viene realmente usata.
| Valore | Lettura |
|---|---|
> 70% | request ben dimensionate |
30 – 70% | margine normale |
< 30% | il team prenota tre volte quello che usa: sta occupando cluster che nessun altro può schedulare |
È il numero da portare a una discussione di capacity. Un namespace con efficienza 0.15 e una richiesta di aumento quota va discusso, non approvato.
Chi prenota di più
# core CPUtopk(20, sum by (namespace) ( namespace_cpu:kube_pod_container_resource_requests:sum{ namespace!~"kube-.*|openshift-.*|default" } ))Limit dichiarati
# core CPUtopk(20, sum by (namespace) ( namespace_cpu:kube_pod_container_resource_limits:sum{ namespace!~"kube-.*|openshift-.*|default" } ))Throttling medio del namespace
# percentuale 0-100 dei periodi CFS in cui il container è stato bloccato100 * ( topk(10, avg by (namespace) ( rate(container_cpu_cfs_throttled_periods_total{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" }[1h]) / rate(container_cpu_cfs_periods_total{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" }[1h]) ) ))avg e non sum: sommare frazioni tra container diversi non ha significato fisico. Un namespace con 50 container tutti al 6% non sta peggio di uno con 3 container al 30%.
Memoria
Consumo attuale (GiB)
# GiBtopk(20, sum by (namespace) ( container_memory_working_set_bytes{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" } )) / 1024^3Su cluster dove cAdvisor è scrapato da più job, aggiungi i matcher per non contare doppio:
# GiB · sostituisci $nssum by (namespace) ( container_memory_working_set_bytes{ job="kubelet", metrics_path="/metrics/cadvisor", namespace=~"$ns", container!="", container!="POD" }) / 1024^3Picco su 7 giorni (GiB)
# GiB · sostituisci $nsmax_over_time( sum by (namespace) ( container_memory_working_set_bytes{ job="kubelet", metrics_path="/metrics/cadvisor", namespace=~"$ns", container!="", container!="POD" } )[7d:5m]) / 1024^3La divisione va fuori dal max_over_time: dentro, l’aggregazione lavorerebbe su valori già divisi.
RSS invece di working set
# GiB · sostituisci $nssum by (namespace) ( container_memory_rss{ namespace=~"$ns", container!="", container!="POD" }) / 1024^3La differenza tra working set e RSS a livello namespace dice quanta della memoria contabilizzata è page cache: se è molta, il namespace scrive molti file e potrebbe andare in OOM per colpa della cache.
Efficienza
# percentuale 0-100100 * ( sum by (namespace) ( container_memory_working_set_bytes{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" } ) / sum by (namespace) ( namespace_memory:kube_pod_container_resource_requests:sum{ namespace!~"kube-.*|openshift-.*|default" } ))Headroom sui limit
# percentuale 0-100 · sostituisci $ns100 * ( sum by (namespace) ( container_memory_working_set_bytes{namespace=~"$ns", container!="", container!="POD"} ) / sum by (namespace) ( namespace_memory:kube_pod_container_resource_limits:sum{namespace=~"$ns"} ))Aggregato a livello namespace nasconde i casi singoli, ma è il dato con cui valutare una richiesta di aumento risorse. Un valore di 0.35 indica che il namespace usa un terzo di quanto già dichiara.
ResourceQuota e LimitRange
Utilizzo della quota, tutte le risorse
# percentuale 0-100100 * ( sum by (namespace, resource) (kube_resourcequota{type="used"}) / sum by (namespace, resource) (kube_resourcequota{type="hard"}))Il label resource copre requests.cpu, requests.memory, limits.cpu, limits.memory, pods, persistentvolumeclaims, services, secrets, configmaps.
Solo quelle vicine al limite
# percentuale 0-100100 * ( sum by (namespace, resource) (kube_resourcequota{type="used"}) / sum by (namespace, resource) (kube_resourcequota{type="hard"})) > 90Prima query da lanciare quando un deploy viene rifiutato e il cluster ha capacity libera.
Valori assoluti
# valore nell'unità della risorsa: core per cpu, byte per memory, conteggio per pods · sostituisci $nskube_resourcequota{namespace="$ns", type="hard"}# valore nell'unità della risorsa: core per cpu, byte per memory, conteggio per pods · sostituisci $nskube_resourcequota{namespace="$ns", type="used"}Margine residuo
# margine residuo, nell'unità della risorsa · sostituisci $nssum by (namespace, resource) (kube_resourcequota{namespace="$ns", type="hard"})-sum by (namespace, resource) (kube_resourcequota{namespace="$ns", type="used"})Quanto resta prima che il prossimo deploy venga rifiutato.
Namespace senza quota
Non c’è una metrica diretta. Confronto lato oc:
oc get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' \ | grep -vE '^(kube-|openshift-|default$)' | sort > /tmp/all-ns
oc get resourcequota -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' \ | sort -u > /tmp/quota-ns
comm -23 /tmp/all-ns /tmp/quota-nsSu un cluster multi-tenant ogni namespace applicativo dovrebbe averne una: senza, un solo team può saturare il cluster.
LimitRange
# valore del vincolo, nell'unità della risorsa (core o byte) · sostituisci $nskube_limitrange{namespace="$ns"}I label resource, type (Container o Pod) e constraint (min, max, default, defaultRequest).
Il defaultRequest è la parte che conta: garantisce che un pod senza request esplicite non venga trattato come a costo zero dallo scheduler. Namespace senza LimitRange e con pod senza request producono i nodi sbilanciati.
oc get limitrange -n $ns -o yamlSalute del namespace
Restart totali
# conteggio nelle ultime 24 oretopk(20, sum by (namespace) ( increase(kube_pod_container_status_restarts_total{ namespace!~"kube-.*|openshift-.*|default" }[24h]) ))Restart normalizzati sul numero di pod
# conteggio nelle ultime 24 oretopk(20, sum by (namespace) ( increase(kube_pod_container_status_restarts_total{ namespace!~"kube-.*|openshift-.*|default" }[24h]) ) / count by (namespace) ( kube_pod_info{namespace!~"kube-.*|openshift-.*|default"} ))Restart per pod. Un namespace con 200 pod e 40 restart sta meglio di uno con 3 pod e 12 restart, ma la query precedente dice il contrario. Questa corregge.
Pod per fase
# elenco: ogni serie restituita soddisfa la condizionesum by (namespace, phase) ( kube_pod_status_phase{namespace!~"kube-.*|openshift-.*|default"} == 1)Container in waiting
# elenco: ogni serie restituita soddisfa la condizionesum by (namespace, reason) ( kube_pod_container_status_waiting_reason{ namespace!~"kube-.*|openshift-.*|default" } == 1)OOM kill per namespace
# conteggio nelle ultime 24 oretopk(10, sum by (namespace) ( increase(container_oom_events_total{ namespace!~"kube-.*|openshift-.*|default" }[24h]) )) > 0Pod non Ready
# numero di seriecount by (namespace) ( kube_pod_status_ready{ condition="false", namespace!~"kube-.*|openshift-.*|default" } == 1) > 0Percentuale di pod sani
# numero di seriecount by (namespace) ( kube_pod_status_ready{condition="true", namespace!~"kube-.*|openshift-.*|default"} == 1)/count by (namespace) ( kube_pod_info{namespace!~"kube-.*|openshift-.*|default"})Il numero singolo da mettere in un cruscotto per team.
Rete
Traffico in uscita
# MiBtopk(20, sum by (namespace) ( rate(container_network_transmit_bytes_total{ namespace!~"kube-.*|openshift-.*|default" }[5m]) )) / 1024^2Traffico in ingresso
# MiBtopk(20, sum by (namespace) ( rate(container_network_receive_bytes_total{ namespace!~"kube-.*|openshift-.*|default" }[5m]) )) / 1024^2Rapporto ingress/egress
# byte al secondo · sostituisci $nssum by (namespace) (rate(container_network_transmit_bytes_total{namespace=~"$ns"}[5m]))/sum by (namespace) (rate(container_network_receive_bytes_total{namespace=~"$ns"}[5m]))Un frontend web ha rapporto > 1 (risposte più grandi delle richieste). Un consumer di code o un batch di ingestion ha rapporto << 1. Un’inversione improvvisa rispetto al normale merita indagine.
Picco di traffico su 7 giorni
# MiB · sostituisci $nsmax_over_time( sum by (namespace) ( rate(container_network_transmit_bytes_total{namespace=~"$ns"}[5m]) )[7d:5m]) / 1024^2Approfondimento: Rete.
Storage
Spazio PVC richiesto
# GiBtopk(20, sum by (namespace) ( kube_persistentvolumeclaim_resource_requests_storage_bytes{ namespace!~"kube-.*|openshift-.*|default" } )) / 1024^3Spazio PVC realmente usato
# GiBtopk(20, sum by (namespace) ( kubelet_volume_stats_used_bytes{namespace!~"kube-.*|openshift-.*|default"} )) / 1024^3Efficienza dello storage
# percentuale 0-100100 * ( sum by (namespace) (kubelet_volume_stats_used_bytes{namespace!~"kube-.*|openshift-.*|default"}) / sum by (namespace) (kubelet_volume_stats_capacity_bytes{namespace!~"kube-.*|openshift-.*|default"}))Frazione dello spazio provisionato che è realmente occupata. Sotto 0.20 il namespace ha chiesto volumi molto più grandi del necessario, e su storage a pagamento è un costo diretto.
Numero di PVC
# numero di seriecount by (namespace) ( kube_persistentvolumeclaim_info{namespace!~"kube-.*|openshift-.*|default"})Filesystem effimero
# GiBtopk(20, sum by (namespace) ( container_fs_usage_bytes{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" } )) / 1024^3Approfondimento: Volumi.
Inventario e configurazione
Numero di pod
# numero di serietopk(20, count by (namespace) ( kube_pod_info{namespace!~"kube-.*|openshift-.*|default"} ))Numero di container
# numero di seriecount by (namespace) ( kube_pod_container_info{ container!="POD", namespace!~"kube-.*|openshift-.*|default" })Ripartizione per classe QoS
# elenco: ogni serie restituita soddisfa la condizionesum by (namespace, qos_class) ( kube_pod_status_qos_class{namespace!~"kube-.*|openshift-.*|default"} == 1)Namespace con pod BestEffort
# numero di seriecount by (namespace) ( kube_pod_status_qos_class{ qos_class="BestEffort", namespace!~"kube-.*|openshift-.*|default" } == 1) > 0In produzione bancaria non dovrebbero esistere: sono i primi a essere evictati sotto pressione.
Container senza limit, per namespace
# numero di seriecount by (namespace) ( count by (namespace, pod, container) ( kube_pod_container_info{container!="POD", namespace!~"kube-.*|openshift-.*|default"} ) unless count by (namespace, pod, container) ( kube_pod_container_resource_limits{resource="memory"} ))Lista dei namespace che stanno mettendo a rischio i nodi. È l’audit da girare prima di ogni onboarding di un nuovo team.
Container senza request
# numero di seriecount by (namespace) ( count by (namespace, pod, container) ( kube_pod_container_info{container!="POD", namespace!~"kube-.*|openshift-.*|default"} ) unless count by (namespace, pod, container) ( kube_pod_container_resource_requests{resource="memory"} ))Namespace con injection della mesh
# numero di seriecount by (namespace) ( kube_pod_container_info{container="istio-proxy"})Distribuzione sui nodi
# numero di serie · sostituisci $nscount by (namespace, node) ( kube_pod_info{namespace="$ns"})Un namespace i cui pod sono tutti su due nodi ha un rischio di disponibilità che il numero di repliche non racconta.
Label e annotazioni
# sempre 1: metrica portatrice di label · sostituisci $nskube_namespace_labels{namespace="$ns"}Se i namespace sono etichettati per team o per applicazione, si può aggregare per quel label invece che per nome:
# GiBsum by (label_team) ( sum by (namespace) ( container_memory_working_set_bytes{container!="", container!="POD"} ) * on(namespace) group_left(label_team) max by (namespace, label_team) (kube_namespace_labels)) / 1024^3Questo è il modo pulito di fare rendicontazione per team quando un team ha più namespace.
Rendicontazione
Le query per il report periodico verso i team o il cliente.
Consumo medio del mese (CPU, core)
# secondiavg_over_time( sum by (namespace) ( node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{ namespace!~"kube-.*|openshift-.*|default" } )[30d:1h])Query costosa: subquery a 30 giorni. Da lanciare a mano una volta al mese, mai in dashboard.
Consumo medio del mese (memoria, GiB)
# GiBavg_over_time( sum by (namespace) ( container_memory_working_set_bytes{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" } )[30d:1h]) / 1024^3Picco del mese
# GiBmax_over_time( sum by (namespace) ( container_memory_working_set_bytes{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" } )[30d:1h]) / 1024^3La coppia media/picco è ciò che serve: media 30% e picco 95% descrivono un carico da livellare, non da ampliare.
Crescita mese su mese
# GiBtopk(20, sum by (namespace) (namespace_memory:kube_pod_container_resource_requests:sum) - sum by (namespace) (namespace_memory:kube_pod_container_resource_requests:sum offset 30d)) / 1024^3GiB di memoria prenotata in più rispetto a 30 giorni fa. La riga da mettere accanto al nome del team nel report mensile.
Crescita CPU mese su mese
# delta rispetto alla finestra di confrontotopk(20, sum by (namespace) (namespace_cpu:kube_pod_container_resource_requests:sum) - sum by (namespace) (namespace_cpu:kube_pod_container_resource_requests:sum offset 30d))Quota della capacity totale
# bytesum by (namespace) ( namespace_memory:kube_pod_container_resource_requests:sum{ namespace!~"kube-.*|openshift-.*|default" })/scalar(sum(kube_node_status_allocatable{resource="memory"}))Frazione del cluster prenotata da ciascun namespace. È la base di qualunque modello di ripartizione dei costi.
Estrazione in CSV
PROM="oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- promtool query instant http://localhost:9090"
echo "namespace,cpu_cores,mem_gib,pods"$PROM 'sum by (namespace) (rate(container_cpu_usage_seconds_total{namespace!~"kube-.*|openshift-.*|default",container!="",container!="POD"}[5m]))' \ | sed -E 's/.*namespace="([^"]+)".*=> ([0-9.e+-]+).*/\1,\2/'Per un report strutturato conviene passare dall’API HTTP con jq:
oc -n openshift-monitoring exec -c prometheus prometheus-k8s-0 -- \ curl -sg 'http://localhost:9090/api/v1/query?query=sum by (namespace) (container_memory_working_set_bytes{container!="",container!="POD"})' \ | jq -r '.data.result[] | [.metric.namespace, (.value[1]|tonumber/1073741824|floor)] | @csv'Comandi oc
# Quota e utilizzooc get resourcequota -n $nsoc describe resourcequota -n $ns
# LimitRangeoc get limitrange -n $ns -o yaml
# Consumo realeoc adm top pods -n $ns --sumoc adm top pods -n $ns --sort-by=memory
# Tutti i namespace per consumooc adm top pods -A --sum | sort -k3 -hr | head -20
# Inventario oggettioc get all -n $nsoc api-resources --namespaced=true -o name | xargs -I{} sh -c 'echo -n "{}: "; oc get {} -n '"$ns"' --no-headers 2>/dev/null | wc -l'
# Pod senza limitoc get pods -n $ns -o json | jq -r ' .items[] | .metadata.name as $p | .spec.containers[] | select(.resources.limits == null) | "\($p)\t\(.name)"'
# Classi QoSoc get pods -n $ns -o custom-columns=POD:.metadata.name,QOS:.status.qosClass
# Distribuzione sui nodioc get pods -n $ns -o wide --sort-by=.spec.nodeName | awk '{print $7}' | sort | uniq -c
# Eventi del namespaceoc get events -n $ns --sort-by=.lastTimestamp | tail -30Alert
Gli alert per namespace vanno tipicamente in openshift-monitoring (li gestisce il team di piattaforma), non nel namespace stesso.
apiVersion: monitoring.coreos.com/v1kind: PrometheusRulemetadata: name: namespace-governance namespace: openshift-monitoring labels: role: alert-rulesspec: groups: - name: namespace.quota rules: - alert: NamespaceQuotaNearlyFull expr: | ( sum by (namespace, resource) (kube_resourcequota{type="used"}) / sum by (namespace, resource) (kube_resourcequota{type="hard"}) ) > 0.90 for: 30m labels: severity: warning annotations: summary: "{{ $labels.namespace }}: quota {{ $labels.resource }} al {{ $value | humanizePercentage }}" description: "I prossimi deploy verranno rifiutati. Verificare se serve più quota o se il namespace prenota più di quanto usa."
- alert: NamespaceQuotaExhausted expr: | ( sum by (namespace, resource) (kube_resourcequota{type="used"}) / sum by (namespace, resource) (kube_resourcequota{type="hard"}) ) >= 1 for: 10m labels: severity: critical annotations: summary: "{{ $labels.namespace }}: quota {{ $labels.resource }} esaurita"
- name: namespace.efficiency rules: - alert: NamespaceLowEfficiency expr: | ( sum by (namespace) ( rate(container_cpu_usage_seconds_total{ namespace!~"kube-.*|openshift-.*|default", container!="", container!="POD" }[1h]) ) / sum by (namespace) ( namespace_cpu:kube_pod_container_resource_requests:sum{ namespace!~"kube-.*|openshift-.*|default" } ) ) < 0.20 and sum by (namespace) ( namespace_cpu:kube_pod_container_resource_requests:sum{ namespace!~"kube-.*|openshift-.*|default" } ) > 4 for: 24h labels: severity: info annotations: summary: "{{ $labels.namespace }}: usa il {{ $value | humanizePercentage }} della CPU prenotata" description: "Oltre 4 core prenotati e usati sotto il 20% da 24 ore. Candidato a revisione delle request."
- name: namespace.health rules: - alert: NamespaceHighRestartRate expr: | sum by (namespace) ( increase(kube_pod_container_status_restarts_total{ namespace!~"kube-.*|openshift-.*|default" }[1h]) ) / count by (namespace) ( kube_pod_info{namespace!~"kube-.*|openshift-.*|default"} ) > 2 for: 30m labels: severity: warning annotations: summary: "{{ $labels.namespace }}: {{ $value | printf \"%.1f\" }} restart per pod nell'ultima ora"L’alert sull’efficienza ha severity: info di proposito: non è un incident, è un elemento per la revisione periodica. Il secondo predicato (> 4 core) evita di segnalare namespace piccoli, dove una bassa efficienza è irrilevante.
Vedi anche
- Workload — il livello sotto
- Pod / Container — il dettaglio
- Nodo — dove finisce quello che i namespace prenotano
- Volumi — storage per namespace