The companion post to this one, "What the VPA Recommender Is Actually Computing," walks through the decay-weighted percentile math, the OOM bump, and the calculator that lets you punch in numbers and see what VPA would recommend. All of that is only useful once you have real numbers to punch in. This post is the practical half: where each input actually comes from in a live cluster, which command pulls it, and where the one genuinely tricky piece, historical usage samples, actually lives.
Every command below works identically whether you're on plain Kubernetes with kubectl or on OpenShift with oc. oc is a superset of kubectl, so anywhere you see kubectl you can substitute oc and get the same result, plus a couple of OpenShift-specific extras noted along the way.
Cluster capacity
This is the outer ceiling nothing else can exceed, so it's worth pulling first, even if you rarely bump into it directly.
kubectl get nodes -o custom-columns='NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory'
Enter fullscreen mode Exit fullscreen mode
This gives allocatable CPU and memory per node, not total capacity, which matters. Allocatable already subtracts what the kubelet and system daemons reserve for themselves, so it's the real number to sum across nodes if you want total schedulable capacity for the cluster.
If you just want a quick sanity check without doing the math yourself:
kubectl describe nodes | grep -A 5 "Allocated resources"
Enter fullscreen mode Exit fullscreen mode
This shows current usage against allocatable, per node, which is useful for spotting a node that's already tight before you go asking VPA to make a pod bigger on it.
Namespace-level ResourceQuota
kubectl get resourcequota -n <namespace> -o yaml
Enter fullscreen mode Exit fullscreen mode
For a faster read that doesn't dump full YAML:
kubectl describe resourcequota -n <namespace>
Enter fullscreen mode Exit fullscreen mode
This shows requests.cpu, requests.memory, limits.cpu, limits.memory, both the hard cap and what's currently used against it. If a namespace has no ResourceQuota object, this returns nothing, and that itself is useful information: it means nothing at the namespace level will block a VPA resize, only cluster capacity and LimitRange will.
On OpenShift, quotas are sometimes managed at the project level instead of directly as a ResourceQuota object:
oc describe quota -n <namespace>
Enter fullscreen mode Exit fullscreen mode
behaves the same way and is worth trying if the plain kubectl version comes back empty on an OpenShift cluster.
Namespace-level LimitRange
kubectl get limitrange -n <namespace> -o yaml
Enter fullscreen mode Exit fullscreen mode
or:
kubectl describe limitrange -n <namespace>
Enter fullscreen mode Exit fullscreen mode
This is the one that trips people up, because it's easy to check quota and stop there. describe limitrange shows default, defaultRequest, min, and max per resource type. The max value here is exactly the ceiling that silently rejects an in-place resize patch if VPA's own maxAllowed isn't set at or below it, the failure mode covered in the companion post. If a namespace has no LimitRange at all, there's no per-container ceiling below cluster capacity, which is worth knowing before you assume a resize got stuck because of a LimitRange that doesn't exist.
Current deployment-level requests and limits
Before VPA touches anything, this is the baseline:
kubectl get deployment <name> -n <namespace> -o jsonpath='{.spec.template.spec.containers[*].resources}'
Enter fullscreen mode Exit fullscreen mode
That output is compact but not always easy to read at a glance. For something more scannable:
kubectl describe deployment <name> -n <namespace>
Enter fullscreen mode Exit fullscreen mode
and look under each container's Limits and Requests fields.
The VPA object itself: live recommendation, maxAllowed, and current mode
kubectl get vpa <vpa-name> -n <namespace> -o yaml
Enter fullscreen mode Exit fullscreen mode
Two sections matter most here. status.recommendation.containerRecommendations shows target, lowerBound, and upperBound per container, the actual live output of the math the calculator is trying to reproduce by hand. spec.resourcePolicy.containerPolicies[].maxAllowed shows the ceiling you've configured directly on the object, if any. If that field is absent entirely, there is no VPA-side cap, and the only ceiling left is whatever LimitRange enforces at admission time.
Quick list of every VPA object in a namespace, if you're not sure of the name:
kubectl get vpa -n <namespace>
Enter fullscreen mode Exit fullscreen mode
The hard one: historical usage samples
Everything above is a single kubectl get away. Historical usage, the actual sequence of samples that feeds the decayed histogram, is not exposed directly by the API, because VPA's recommender keeps that state internally rather than as a queryable object. Three ways to get something usable:
Live snapshot only, no history:
kubectl top pod -n <namespace> --containers
Enter fullscreen mode Exit fullscreen mode
This gives current CPU and memory usage per container right now. Useful for a sanity check, useless for reconstructing a trend, since it's a single point in time with no memory of what came before.
Prometheus, if it's already scraping the cluster:
If there's a Prometheus instance collecting cluster metrics (common on most production clusters, whether self-managed or via a managed observability stack), the two metrics that matter are container_memory_working_set_bytes for memory and rate(container_cpu_usage_seconds_total[5m]) for CPU. Querying either over a time range and exporting a handful of daily or hourly points gives you a real dataset to paste directly into the calculator, in the same oldest-to-newest format the tool expects. This is the closest thing to VPA's own view of the world that's actually queryable on demand.
VPA's own checkpoint objects:
kubectl get verticalpodautoscalercheckpoint -n <namespace> -o yaml
Enter fullscreen mode Exit fullscreen mode
This is the ground truth. It's not raw per-sample history, it's the recommender's own decayed histogram buckets, already aggregated the same way described in the companion post. It's dense and not immediately readable, but if you want to validate the calculator's math against what the real recommender is actually storing internally rather than reconstructing an approximation from Prometheus, this is the object to inspect.
Watching for the failure modes as they happen
Two event patterns are worth grepping for once VPA is running in a namespace you're watching closely.
For eviction-based fallback (the InPlaceOrRecreate mode quietly falling back to a full pod eviction when the node can't satisfy an in-place resize):
kubectl get events -n <namespace> --field-selector reason=EvictedByVPA
Enter fullscreen mode Exit fullscreen mode
For a specific pod's resize history and any pending or rejected patches:
kubectl describe pod <pod-name> -n <namespace>
Enter fullscreen mode Exit fullscreen mode
and check the Events section at the bottom. A resize that's stuck against a LimitRange max typically shows no dramatic error here, just an absence of the expected update, which is exactly why it's worth checking describe limitrange proactively rather than waiting for something to look obviously broken.
Putting it together
None of these commands are complicated on their own. The value is in pulling all five (cluster capacity, namespace quota, LimitRange, deployment baseline, and the VPA object) into one place before trusting any single recommendation, because a mathematically correct histogram target can still get silently capped, deferred, or rejected at any layer above the pod. The calculator from the companion post is built to check exactly those five inputs against each other. These commands are how you fill it in with your cluster's actual numbers instead of guessing.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.