Skip to content

Capacity Management

Guida operativa al capacity management su cluster Kubernetes e OpenShift: come misurare Requested vs Limit vs Utilization, leggere correttamente gli output, individuare overcommit e prenotazioni fantasma.

Concetti chiave

Prima dei comandi, le quattro grandezze fondamentali:

GrandezzaCos’èChi la usa
CapacityRisorse fisiche totali del nodoInformativa
AllocatableCapacity − riserve (kube-reserved, system-reserved, eviction threshold)Lo scheduler
RequestedSomma dei resources.requests dei pod schedulati sul nodoLo scheduler (binding)
UtilizationConsumo reale (metrics-server / Prometheus)Il kernel / cgroup

Regole di lettura:

  • Free = Allocatable − Requested → è spazio schedulabile, NON risorse libere reali.
  • Un nodo può essere pieno per lo scheduler (Requested ≈ 100%) e vuoto per il kernel (Utilization al 15%): il sintomo sono pod Pending con Insufficient cpu su nodi scarichi.
  • CPU è comprimibile: overcommit sui limits → throttling CFS, mai kill.
  • Memoria NON è comprimibile: overcommit sui limits + utilizzo reale alto → OOMKill / node-pressure eviction.
  • Limits % > 100 = overcommit. Normale e spesso desiderabile per la CPU; da tenere sotto controllo per la memoria (soglia prudente: < 150% con utilization monitorata).

Comandi nativi

kubectl / oc describe node

La fonte di verità, sempre disponibile, zero tool:

Terminal window
kubectl describe node <node> | grep -A 10 "Allocated resources"
Terminal window
oc describe node <node> | grep -A 10 "Allocated resources"

Output tipico:

Allocated resources:
Resource Requests Limits
cpu 7600m (96%) 19400m (244%)
memory 9320Mi (39%) 21300Mi (90%)

Tutti i nodi in un colpo:

Terminal window
kubectl get nodes -o name | xargs -I{} sh -c 'echo "== {} =="; kubectl describe {} | grep -A 8 "Allocated resources"'

top nodes / pods (metrics-server)

Utilization reale istantanea:

Terminal window
kubectl top nodes
Terminal window
kubectl top pods -A --sort-by=cpu | head -20
Terminal window
kubectl top pods -A --sort-by=memory | head -20

Su OpenShift:

Terminal window
oc adm top nodes
Terminal window
oc adm top pods -A --sort-by=memory | head -20

Allocatable e capacity a colpo d’occhio

Terminal window
kubectl get nodes -o custom-columns='NODE:.metadata.name,CPU_ALLOC:.status.allocatable.cpu,MEM_ALLOC:.status.allocatable.memory,PODS:.status.allocatable.pods'

Pod Pending per risorse insufficienti

Il sintomo di un cluster “pieno di prenotazioni”:

Terminal window
kubectl get pods -A --field-selector=status.phase=Pending
Terminal window
kubectl get events -A --field-selector reason=FailedScheduling --sort-by='.lastTimestamp' | tail -20

kubectl-view-allocations

Binario singolo Rust, statico (build musl), ideale per ambienti air-gapped. Repo: davidB/kubectl-view-allocations.

Installazione offline

Da macchina con internet:

Terminal window
curl -LO $(curl -s https://api.github.com/repos/davidB/kubectl-view-allocations/releases/latest | grep browser_download_url | grep 'x86_64-unknown-linux-musl' | cut -d '"' -f 4)

Sul bastion:

Terminal window
tar -xzf kubectl-view-allocations_*_x86_64-unknown-linux-musl.tar.gz
Terminal window
sudo mv kubectl-view-allocations /usr/local/bin/ && sudo chmod +x /usr/local/bin/kubectl-view-allocations

Nel PATH con prefisso kubectl- funziona anche come plugin: kubectl view-allocations.

Comandi essenziali

Vista globale per risorsa (cpu, memory, ephemeral-storage, pods):

Terminal window
kubectl-view-allocations

Per nodo, con percentuali (l’equivalente leggibile di kube-capacity -a):

Terminal window
kubectl-view-allocations -g node

Con utilization reale (richiede metrics API) — la vista più completa:

Terminal window
kubectl-view-allocations -g node -u

Per namespace (chi consuma il cluster):

Terminal window
kubectl-view-allocations -g namespace -r cpu -r memory

Drill-down pod per nodo, solo CPU:

Terminal window
kubectl-view-allocations -g node -g pod -r cpu

Filtro namespace via regex:

Terminal window
kubectl-view-allocations -g namespace --namespace-filter 'prod.*'

Risorse custom / GPU:

Terminal window
kubectl-view-allocations -r 'nvidia.*'

Export per report:

Terminal window
kubectl-view-allocations -g node -o csv > capacity-$(date +%Y%m%d).csv
Terminal window
kubectl-view-allocations -g node -o json

Come leggere l’output

cpu (88%) 28.0 (200%) 63.5 31.8 0.0
├─ node-02 (18%u) (96%) 7.6 (244%) 19.4 8.0 0.0
  • Le % sono relative all’Allocatable.
  • Free 0.0 = niente più spazio schedulabile, non CPU esaurita.
  • __ = valore non impostato (pod senza limits) o non applicabile.
  • Con -u: scarto grande tra Utilization e Requested = requests sovradimensionate → candidato per rightsizing.
  • Limits CPU > 200%: sotto carico simultaneo → throttling. Limits memoria ~100% con utilization alta → rischio OOM/eviction reale.

kube-capacity

Alternativa storica (robscott/kube-capacity). La tabella di default non mostra percentuali, ma JSON e CSV sì:

Terminal window
kube-capacity -u -a

Vista compatta in % via jq:

Terminal window
kube-capacity -u -o json | jq -r '
["NODE","CPU_REQ%","CPU_LIM%","CPU_UTIL%","MEM_REQ%","MEM_LIM%","MEM_UTIL%"],
(.nodes[] | [.name,
.cpu.requestsPercent, .cpu.limitsPercent, .cpu.utilizationPercent,
.memory.requestsPercent, .memory.limitsPercent, .memory.utilizationPercent])
| @tsv' | column -t

Per pod e container:

Terminal window
kube-capacity -p # dettaglio pod
Terminal window
kube-capacity -c # dettaglio container
Terminal window
kube-capacity --pod-labels app=myapp -n mynamespace

Query PromQL

Da tenere pronte per Grafana o per la console OpenShift (Observe → Metrics).

Commit ratio cluster

# CPU: requests vs allocatable (>1 = cluster pieno per lo scheduler)
sum(kube_pod_container_resource_requests{resource="cpu"})
/ sum(kube_node_status_allocatable{resource="cpu"})
# Memoria: requests vs allocatable
sum(kube_pod_container_resource_requests{resource="memory"})
/ sum(kube_node_status_allocatable{resource="memory"})
# Overcommit limits memoria (il numero da tenere d'occhio)
sum(kube_pod_container_resource_limits{resource="memory"})
/ sum(kube_node_status_allocatable{resource="memory"})

Per nodo

# CPU requested % per nodo
sum by (node) (kube_pod_container_resource_requests{resource="cpu"})
/ on(node) kube_node_status_allocatable{resource="cpu"} * 100
# Memoria requested % per nodo
sum by (node) (kube_pod_container_resource_requests{resource="memory"})
/ on(node) kube_node_status_allocatable{resource="memory"} * 100
# Utilization CPU reale per nodo
(1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) * 100
# Utilization memoria reale per nodo
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

Lo scarto requests vs uso reale (la query del rightsizing)

# CPU: quanto ogni namespace usa rispetto a quanto prenota (<0.3 = sovradimensionato)
sum by (namespace) (rate(container_cpu_usage_seconds_total{container!="",container!="POD"}[5m]))
/ sum by (namespace) (kube_pod_container_resource_requests{resource="cpu"})
# Memoria: working set vs requests per namespace
sum by (namespace) (container_memory_working_set_bytes{container!="",container!="POD"})
/ sum by (namespace) (kube_pod_container_resource_requests{resource="memory"})
# Top 10 pod con più CPU prenotata e non usata (in core "sprecati")
topk(10,
sum by (namespace, pod) (kube_pod_container_resource_requests{resource="cpu"})
- sum by (namespace, pod) (rate(container_cpu_usage_seconds_total{container!=""}[5m]))
)

Segnali di sofferenza

# CPU throttling per pod (>25% costante = limit troppo stretto)
sum by (namespace, pod) (rate(container_cpu_cfs_throttled_periods_total[5m]))
/ sum by (namespace, pod) (rate(container_cpu_cfs_periods_total[5m])) * 100
# OOMKill recenti
sum by (namespace, pod) (increase(container_oom_events_total[1h])) > 0
# Pod Pending per unschedulability
sum(kube_pod_status_phase{phase="Pending"}) by (namespace)
# Memory pressure sui nodi
kube_node_status_condition{condition="MemoryPressure", status="true"} == 1

Pod senza requests (invisibili al capacity planning)

count by (namespace) (
kube_pod_container_info
unless on (namespace, pod, container)
kube_pod_container_resource_requests{resource="cpu"}
)

Equivalente CLI:

Terminal window
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.containers[].resources.requests == null) | "\(.metadata.namespace)/\(.metadata.name)"' | sort -u

Specifico OpenShift

Dashboard in console

Già pronte, zero setup: Observe → Dashboards

  • Kubernetes / Compute Resources / Cluster — requests/limits/usage per namespace
  • Kubernetes / Compute Resources / Node (Pods) — dettaglio per nodo
  • Node Exporter / USE Method / Node — saturazione reale hardware

Allocatable e riserve

Su OCP le riserve di sistema sono gestite automaticamente dal Machine Config Operator (system-reserved dinamico da 4.18, autoSizingReserved):

Terminal window
oc get node <node> -o jsonpath='{.status.capacity.cpu} capacity / {.status.allocatable.cpu} allocatable{"\n"}'
Terminal window
oc get kubeletconfig

ClusterResourceOverride (overcommit governato)

Operator che riscrive requests/limits al volo in base a ratio configurati — utile quando i team applicativi non fanno rightsizing:

Terminal window
oc get clusterresourceoverride cluster -o yaml

Parametri chiave: cpuRequestToLimitPercent, memoryRequestToLimitPercent, limitCPUToMemoryPercent. Si applica solo ai namespace con label clusterresourceoverrides.admission.autoscaling.openshift.io/enabled: "true".

Quote per namespace

Terminal window
oc get resourcequota -A
Terminal window
oc describe resourcequota -n <namespace>
Terminal window
oc get limitrange -A

ClusterResourceQuota (quote cross-namespace, per label/annotation):

Terminal window
oc get clusterresourcequota

Rightsizing — KRR

L’anello finale della catena: misurato lo scarto requests vs utilization, KRR (Robusta Kubernetes Resource Recommender) calcola i valori suggeriti da Prometheus history (default: CPU P95, memoria max + buffer):

Terminal window
krr simple --prometheus-url http://<prometheus>:9090
Terminal window
krr simple -n <namespace> --history-duration 336 # 14 giorni
Terminal window
krr simple -f csv > krr-$(date +%Y%m%d).csv

Flusso operativo consigliato:

  1. kubectl-view-allocations -g node -u → individua lo scarto Requested/Utilization
  2. Query PromQL “scarto per namespace” → prioritizza i namespace peggiori
  3. krr simple -n <ns> → ottieni i valori suggeriti
  4. Applica in dev/collaudo → osserva throttling e OOM per 1-2 settimane
  5. Promuovi in produzione

Checklist capacity review

Da eseguire periodicamente (o prima di ogni onboarding applicativo):

Terminal window
kubectl-view-allocations -g node -u
  1. CPU Requested > 85% su cluster/nodo → scheduling a rischio: rightsizing o scale-out
  2. Scarto Utilization/Requested > 3x → requests gonfiate, passare KRR
  3. Memory Limits > 150% con utilization > 60% → rischio OOM: ridurre overcommit
  4. Nodo con Free memoria < 500Mi → nessun nuovo pod schedulabile lì, verificare bilanciamento
  5. Squilibrio tra nodi (un nodo al 96% CPU e un altro al 76%) → valutare descheduler o riposizionamento mirato
  6. Pod senza requests → invisibili al capacity planning, imporre LimitRange di default
  7. Throttling CFS > 25% costante → limits CPU troppo stretti, alzare o rimuovere
  8. Eventi FailedScheduling ricorrenti → il cluster è “pieno di prenotazioni”: tornare al punto 2