Skip to content

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.

Triagei namespace da guardare per primi
CPU · Memoriaconsumo, picchi, efficienza
Quota e LimitRange
Saluterestart, pod non sani, OOM
Rete · Storage
Inventarioconteggi, QoS, oggetti senza limit
Rendicontazioneil report mensile
Comandi oc · Alert

Triage

T1. Top 10 per consumo CPU

# core CPU
topk(10,
sum by (namespace) (
rate(container_cpu_usage_seconds_total{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}[5m])
)
)

T2. Top 10 per consumo memoria

# GiB
topk(10,
sum by (namespace) (
container_memory_working_set_bytes{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}
)
) / 1024^3

T3. Namespace vicini alla quota

# percentuale 0-100
100 * topk(10,
sum by (namespace, resource) (kube_resourcequota{type="used"})
/
sum by (namespace, resource) (kube_resourcequota{type="hard"})
) > 85

T4. Namespace con più restart

# conteggio nelle ultime 24 ore
topk(10,
sum by (namespace) (
increase(kube_pod_container_status_restarts_total{
namespace!~"kube-.*|openshift-.*|default"
}[24h])
)
) > 0

T5. Namespace con pod non sani

# numero di serie
count by (namespace) (
kube_pod_status_phase{
phase=~"Pending|Failed|Unknown",
namespace!~"kube-.*|openshift-.*|default"
} == 1
) > 0

CPU

Consumo attuale (core)

# core CPU
topk(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 $ns
sum by (namespace) (
node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{
namespace=~"$ns"
}
)

Picco su 7 giorni

# secondi · sostituisci $ns
max_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 $ns
avg_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 $ns
max_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-100
100 * (
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.

ValoreLettura
> 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 CPU
topk(20,
sum by (namespace) (
namespace_cpu:kube_pod_container_resource_requests:sum{
namespace!~"kube-.*|openshift-.*|default"
}
)
)

Limit dichiarati

# core CPU
topk(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 bloccato
100 * (
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)

# GiB
topk(20,
sum by (namespace) (
container_memory_working_set_bytes{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}
)
) / 1024^3

Su cluster dove cAdvisor è scrapato da più job, aggiungi i matcher per non contare doppio:

# GiB · sostituisci $ns
sum by (namespace) (
container_memory_working_set_bytes{
job="kubelet", metrics_path="/metrics/cadvisor",
namespace=~"$ns", container!="", container!="POD"
}
) / 1024^3

Picco su 7 giorni (GiB)

# GiB · sostituisci $ns
max_over_time(
sum by (namespace) (
container_memory_working_set_bytes{
job="kubelet", metrics_path="/metrics/cadvisor",
namespace=~"$ns", container!="", container!="POD"
}
)[7d:5m]
) / 1024^3

La divisione va fuori dal max_over_time: dentro, l’aggregazione lavorerebbe su valori già divisi.

RSS invece di working set

# GiB · sostituisci $ns
sum by (namespace) (
container_memory_rss{
namespace=~"$ns", container!="", container!="POD"
}
) / 1024^3

La 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-100
100 * (
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 $ns
100 * (
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-100
100 * (
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-100
100 * (
sum by (namespace, resource) (kube_resourcequota{type="used"})
/
sum by (namespace, resource) (kube_resourcequota{type="hard"})
) > 90

Prima 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 $ns
kube_resourcequota{namespace="$ns", type="hard"}
# valore nell'unità della risorsa: core per cpu, byte per memory, conteggio per pods · sostituisci $ns
kube_resourcequota{namespace="$ns", type="used"}

Margine residuo

# margine residuo, nell'unità della risorsa · sostituisci $ns
sum 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:

Terminal window
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-ns

Su 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 $ns
kube_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.

Terminal window
oc get limitrange -n $ns -o yaml

Salute del namespace

Restart totali

# conteggio nelle ultime 24 ore
topk(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 ore
topk(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 condizione
sum by (namespace, phase) (
kube_pod_status_phase{namespace!~"kube-.*|openshift-.*|default"} == 1
)

Container in waiting

# elenco: ogni serie restituita soddisfa la condizione
sum by (namespace, reason) (
kube_pod_container_status_waiting_reason{
namespace!~"kube-.*|openshift-.*|default"
} == 1
)

OOM kill per namespace

# conteggio nelle ultime 24 ore
topk(10,
sum by (namespace) (
increase(container_oom_events_total{
namespace!~"kube-.*|openshift-.*|default"
}[24h])
)
) > 0

Pod non Ready

# numero di serie
count by (namespace) (
kube_pod_status_ready{
condition="false",
namespace!~"kube-.*|openshift-.*|default"
} == 1
) > 0

Percentuale di pod sani

# numero di serie
count 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

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

Traffico in ingresso

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

Rapporto ingress/egress

# byte al secondo · sostituisci $ns
sum 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 $ns
max_over_time(
sum by (namespace) (
rate(container_network_transmit_bytes_total{namespace=~"$ns"}[5m])
)[7d:5m]
) / 1024^2

Approfondimento: Rete.


Storage

Spazio PVC richiesto

# GiB
topk(20,
sum by (namespace) (
kube_persistentvolumeclaim_resource_requests_storage_bytes{
namespace!~"kube-.*|openshift-.*|default"
}
)
) / 1024^3

Spazio PVC realmente usato

# GiB
topk(20,
sum by (namespace) (
kubelet_volume_stats_used_bytes{namespace!~"kube-.*|openshift-.*|default"}
)
) / 1024^3

Efficienza dello storage

# percentuale 0-100
100 * (
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 serie
count by (namespace) (
kube_persistentvolumeclaim_info{namespace!~"kube-.*|openshift-.*|default"}
)

Filesystem effimero

# GiB
topk(20,
sum by (namespace) (
container_fs_usage_bytes{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}
)
) / 1024^3

Approfondimento: Volumi.


Inventario e configurazione

Numero di pod

# numero di serie
topk(20,
count by (namespace) (
kube_pod_info{namespace!~"kube-.*|openshift-.*|default"}
)
)

Numero di container

# numero di serie
count by (namespace) (
kube_pod_container_info{
container!="POD",
namespace!~"kube-.*|openshift-.*|default"
}
)

Ripartizione per classe QoS

# elenco: ogni serie restituita soddisfa la condizione
sum by (namespace, qos_class) (
kube_pod_status_qos_class{namespace!~"kube-.*|openshift-.*|default"} == 1
)

Namespace con pod BestEffort

# numero di serie
count by (namespace) (
kube_pod_status_qos_class{
qos_class="BestEffort",
namespace!~"kube-.*|openshift-.*|default"
} == 1
) > 0

In produzione bancaria non dovrebbero esistere: sono i primi a essere evictati sotto pressione.

Container senza limit, per namespace

# numero di serie
count 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 serie
count 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 serie
count by (namespace) (
kube_pod_container_info{container="istio-proxy"}
)

Distribuzione sui nodi

# numero di serie · sostituisci $ns
count 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 $ns
kube_namespace_labels{namespace="$ns"}

Se i namespace sono etichettati per team o per applicazione, si può aggregare per quel label invece che per nome:

# GiB
sum 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^3

Questo è 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)

# secondi
avg_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)

# GiB
avg_over_time(
sum by (namespace) (
container_memory_working_set_bytes{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}
)[30d:1h]
) / 1024^3

Picco del mese

# GiB
max_over_time(
sum by (namespace) (
container_memory_working_set_bytes{
namespace!~"kube-.*|openshift-.*|default",
container!="", container!="POD"
}
)[30d:1h]
) / 1024^3

La coppia media/picco è ciò che serve: media 30% e picco 95% descrivono un carico da livellare, non da ampliare.

Crescita mese su mese

# GiB
topk(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^3

GiB 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 confronto
topk(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

# byte
sum 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

Terminal window
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:

Terminal window
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

Terminal window
# Quota e utilizzo
oc get resourcequota -n $ns
oc describe resourcequota -n $ns
# LimitRange
oc get limitrange -n $ns -o yaml
# Consumo reale
oc adm top pods -n $ns --sum
oc adm top pods -n $ns --sort-by=memory
# Tutti i namespace per consumo
oc adm top pods -A --sum | sort -k3 -hr | head -20
# Inventario oggetti
oc get all -n $ns
oc 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 limit
oc get pods -n $ns -o json | jq -r '
.items[] | .metadata.name as $p
| .spec.containers[] | select(.resources.limits == null)
| "\($p)\t\(.name)"'
# Classi QoS
oc get pods -n $ns -o custom-columns=POD:.metadata.name,QOS:.status.qosClass
# Distribuzione sui nodi
oc get pods -n $ns -o wide --sort-by=.spec.nodeName | awk '{print $7}' | sort | uniq -c
# Eventi del namespace
oc get events -n $ns --sort-by=.lastTimestamp | tail -30

Alert

Gli alert per namespace vanno tipicamente in openshift-monitoring (li gestisce il team di piattaforma), non nel namespace stesso.

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: namespace-governance
namespace: openshift-monitoring
labels:
role: alert-rules
spec:
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