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:
| Grandezza | Cos’è | Chi la usa |
|---|---|---|
| Capacity | Risorse fisiche totali del nodo | Informativa |
| Allocatable | Capacity − riserve (kube-reserved, system-reserved, eviction threshold) | Lo scheduler |
| Requested | Somma dei resources.requests dei pod schedulati sul nodo | Lo scheduler (binding) |
| Utilization | Consumo 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
PendingconInsufficient cpusu 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:
kubectl describe node <node> | grep -A 10 "Allocated resources"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:
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:
kubectl top nodeskubectl top pods -A --sort-by=cpu | head -20kubectl top pods -A --sort-by=memory | head -20Su OpenShift:
oc adm top nodesoc adm top pods -A --sort-by=memory | head -20Allocatable e capacity a colpo d’occhio
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”:
kubectl get pods -A --field-selector=status.phase=Pendingkubectl get events -A --field-selector reason=FailedScheduling --sort-by='.lastTimestamp' | tail -20kubectl-view-allocations
Binario singolo Rust, statico (build musl), ideale per ambienti air-gapped. Repo: davidB/kubectl-view-allocations.
Installazione offline
Da macchina con internet:
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:
tar -xzf kubectl-view-allocations_*_x86_64-unknown-linux-musl.tar.gzsudo mv kubectl-view-allocations /usr/local/bin/ && sudo chmod +x /usr/local/bin/kubectl-view-allocationsNel PATH con prefisso kubectl- funziona anche come plugin: kubectl view-allocations.
Comandi essenziali
Vista globale per risorsa (cpu, memory, ephemeral-storage, pods):
kubectl-view-allocationsPer nodo, con percentuali (l’equivalente leggibile di kube-capacity -a):
kubectl-view-allocations -g nodeCon utilization reale (richiede metrics API) — la vista più completa:
kubectl-view-allocations -g node -uPer namespace (chi consuma il cluster):
kubectl-view-allocations -g namespace -r cpu -r memoryDrill-down pod per nodo, solo CPU:
kubectl-view-allocations -g node -g pod -r cpuFiltro namespace via regex:
kubectl-view-allocations -g namespace --namespace-filter 'prod.*'Risorse custom / GPU:
kubectl-view-allocations -r 'nvidia.*'Export per report:
kubectl-view-allocations -g node -o csv > capacity-$(date +%Y%m%d).csvkubectl-view-allocations -g node -o jsonCome 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ì:
kube-capacity -u -aVista compatta in % via jq:
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 -tPer pod e container:
kube-capacity -p # dettaglio podkube-capacity -c # dettaglio containerkube-capacity --pod-labels app=myapp -n mynamespaceQuery 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 allocatablesum(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 nodosum by (node) (kube_pod_container_resource_requests{resource="cpu"}) / on(node) kube_node_status_allocatable{resource="cpu"} * 100# Memoria requested % per nodosum 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) * 100Lo 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 namespacesum 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 recentisum by (namespace, pod) (increase(container_oom_events_total[1h])) > 0# Pod Pending per unschedulabilitysum(kube_pod_status_phase{phase="Pending"}) by (namespace)# Memory pressure sui nodikube_node_status_condition{condition="MemoryPressure", status="true"} == 1Pod 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:
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.containers[].resources.requests == null) | "\(.metadata.namespace)/\(.metadata.name)"' | sort -uSpecifico 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):
oc get node <node> -o jsonpath='{.status.capacity.cpu} capacity / {.status.allocatable.cpu} allocatable{"\n"}'oc get kubeletconfigClusterResourceOverride (overcommit governato)
Operator che riscrive requests/limits al volo in base a ratio configurati — utile quando i team applicativi non fanno rightsizing:
oc get clusterresourceoverride cluster -o yamlParametri chiave: cpuRequestToLimitPercent, memoryRequestToLimitPercent, limitCPUToMemoryPercent. Si applica solo ai namespace con label clusterresourceoverrides.admission.autoscaling.openshift.io/enabled: "true".
Quote per namespace
oc get resourcequota -Aoc describe resourcequota -n <namespace>oc get limitrange -AClusterResourceQuota (quote cross-namespace, per label/annotation):
oc get clusterresourcequotaRightsizing — 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):
krr simple --prometheus-url http://<prometheus>:9090krr simple -n <namespace> --history-duration 336 # 14 giornikrr simple -f csv > krr-$(date +%Y%m%d).csvFlusso operativo consigliato:
kubectl-view-allocations -g node -u→ individua lo scarto Requested/Utilization- Query PromQL “scarto per namespace” → prioritizza i namespace peggiori
krr simple -n <ns>→ ottieni i valori suggeriti- Applica in dev/collaudo → osserva throttling e OOM per 1-2 settimane
- Promuovi in produzione
Checklist capacity review
Da eseguire periodicamente (o prima di ogni onboarding applicativo):
kubectl-view-allocations -g node -u- CPU Requested > 85% su cluster/nodo → scheduling a rischio: rightsizing o scale-out
- Scarto Utilization/Requested > 3x → requests gonfiate, passare KRR
- Memory Limits > 150% con utilization > 60% → rischio OOM: ridurre overcommit
- Nodo con Free memoria < 500Mi → nessun nuovo pod schedulabile lì, verificare bilanciamento
- Squilibrio tra nodi (un nodo al 96% CPU e un altro al 76%) → valutare descheduler o riposizionamento mirato
- Pod senza requests → invisibili al capacity planning, imporre LimitRange di default
- Throttling CFS > 25% costante → limits CPU troppo stretti, alzare o rimuovere
- Eventi FailedScheduling ricorrenti → il cluster è “pieno di prenotazioni”: tornare al punto 2