Situation

Part 1 Ended With Two Unresolved Questions: A 250Mi Memory Target For A Dashboard Container Actually Using Only 24Mi, Sitting Suspiciously Close To The 256Mi Limit Already On The Deployment, With No Citable Reason Why. And A CPU Number That Had Never Once Caught My Attention, The One I’d Waved Through Without Checking. The Plan Was To Let It Accumulate History And Come Back To See Which Way The Memory Target Went. A Month Later It Went A Third Way I Hadn’t Considered, The Answer Had Nothing To Do With The Limit, And It Closed Both Questions At Once.

Result First:

Observation Window 2026-08-06 To 2026-09-07, One VPA Object In updateMode: "Off", Untouched
What I Expected Target Converges Down Toward Real Usage, Or Stays Anchored Near The 256Mi Limit
What Happened Target Never Moved. lowerBound, upperBound And uncappedTarget All Converged Onto It
Actual Root Cause --pod-recommendation-min-memory-mb, Default 250. The Target Was The Floor, Not A Calculation
The 256Mi Limit Resemblance Coincidence. The Floor Is 250MiB Regardless Of What The Container’s Limit Is
The CPU Number Part 1 Left Open Same Story. --pod-recommendation-min-cpu-millicores Defaults To 25

What A Month Of History Actually Changed

Same Object, Same updateMode: "Off", Nothing Touched In Between:

Day 0 (2026-08-06) Day 32 (2026-09-07)
Actual Usage 2m / 24Mi 1m / 26Mi
target 25m / 250Mi 25m / 250Mi
lowerBound 25m / 250Mi 25m / 250Mi
upperBound 640m / 2013Mi 25m / 250Mi

The Only Number That Moved Is The Upper Bound, And It Moved A Long Way. That Part Matches What Part 1 Predicted: The Confidence Multiplier Inflates The Upper Bound When There’s Barely Any History Behind It, And Decays Toward 1 As Real Observation Accumulates. A Month In, It’s Done Decaying.

What Part 1 Got Wrong Was Assuming The Target Was A Calculation That Would Also Move Once Confidence Resolved. It Wasn’t, So It Didn’t.

The Tell Is In The Full Status, Which I Should Have Read More Carefully The First Time:

status:
  conditions:
  - lastTransitionTime: "2026-08-06T03:51:58Z"
    status: "True"
    type: RecommendationProvided
  recommendation:
    containerRecommendations:
    - containerName: headlamp
      lowerBound:      { cpu: 25m, memory: 250Mi }
      target:          { cpu: 25m, memory: 250Mi }
      uncappedTarget:  { cpu: 25m, memory: 250Mi }
      upperBound:      { cpu: 25m, memory: 250Mi }

Four Fields. One Value. A 50th Percentile Estimate, A 90th Percentile Estimate And A 95th Percentile Estimate Do Not Naturally Land On The Same Number For A Workload With Real Variance In It. When They Do, You’re Not Looking At Three Estimates. You’re Looking At Something Clamping All Three To The Same Place.

The Answer To Part 1’s Open Question

The Recommender On This Cluster Runs With No Resource-Related Flags At All:

kubectl get deploy -n vpa-system vpa-vertical-pod-autoscaler-recommender \
  -o jsonpath='{.spec.template.spec.containers[0].args}'
# ["--v=4","--stderrthreshold=info","--leader-elect=true", ...leader election only...]

Which Means Every Recommendation It Produces Is Shaped By Defaults. Two Of Those Defaults Are The Whole Story:

Flag Default Help Text
--pod-recommendation-min-cpu-millicores 25 “Minimum CPU Recommendation For A Pod”
--pod-recommendation-min-memory-mb 250 “Minimum Memory Recommendation For A Pod”

The Flag Is Named mb, But The Recommender Multiplies It By 1024*1024, Not 1000*1000:

minMemory := model.ScaleResource(model.MemoryAmountFromBytes(r.minMemoryMb*1024*1024), fraction)

250 × 1024 × 1024 Is Exactly 250Mi. Not “Approximately”, Not “Close To”. The Number VPA Reported Is The Default Floor Rendered Back Verbatim.

So The Actual Chain For This Container Is: Take The 90th Percentile Of Peak Memory Per 24h Window Over 8 Windows (Roughly 26Mi Here), Add The 15% Safety Margin (--recommendation-margin-fraction, Default 0.15, Getting Us To Around 30Mi), Then Raise It To The Minimum Because 30Mi Is Below 250Mi. Every Estimator In The Chain Gets The Same Floor Applied, Which Is Why The Lower And Upper Bounds Sit There Too Once Their Confidence Multipliers Stop Distorting Them.

And The Part I Spent Real Time Being Suspicious About In Part 1, The 250Mi Target Sitting Six Mebibytes Under The Container’s 256Mi Limit, Is A Coincidence. The Floor Is 250MiB Whether The Container’s Limit Is 256Mi, 4Gi, Or Absent Entirely. Two Numbers Landed Near Each Other And I Read A Relationship Into It That Isn’t There.

Notes

  • target == lowerBound == upperBound Is A Signal, Not A Coincidence. It Means Every Estimate In The Chain Fell Below The Configured Minimum And Got Clamped. The Recommendation Is Telling You About VPA’s Defaults, Not About Your Workload.
  • A Month Of History Made The Recommendation More Confident Without Making It More Useful. Confidence And Usefulness Are Different Axes. The Bounds Collapsing Onto The Target Looks Like Convergence, And It Is, But It Converged Onto A Constant.
  • The Floor Is Per-Pod And Adjustable. If You Want VPA To Say Something Meaningful About Workloads Smaller Than 250Mi, Those Two Flags Are Where To Look. Worth Knowing Before Concluding VPA “Doesn’t Work” On Small Containers.

The Number I Waved Through In Part 1

Part 1 Closed With An Admission: I Had Gone Digging Into The Memory Recommendation And Never Checked The CPU One, For No Better Reason Than That 25m Looked Harmless Next To 2m Of Actual Usage. Here Is What Was Sitting Behind It.

25m Is --pod-recommendation-min-cpu-millicores, Default 25. The Same Floor, Doing The Same Thing To The Same Kind Of Small Workload. Both Halves Of That First Recommendation Were Defaults. Neither Was A Measurement.

The Sorting Mechanism Is The Part Worth Keeping. Both Targets Were About 10x Actual Usage. One Of Them Looked Alarming, So It Got A Source Dive. The Other Looked Plausible, So It Got A Sentence Of Explanation And A Pass. Plausibility Is Not Verification, And A Number That Matches What You Expected Is Precisely The One Nobody Checks.

Notes

  • Check Whether A Recommendation Sits Exactly On A Documented Default Before Explaining Why It’s Reasonable. It Takes One Lookup, And “Reasonable-Looking” Is Precisely The Condition Under Which Nobody Bothers.

Real-World Application

Scenario What To Do
Reading Any VPA Recommendation For A Small Container Compare The Numbers Against 25m And 250Mi First. If They Match Exactly, You’re Reading Floors And The Recommendation Carries No Information About That Workload
All Three Bounds Report The Same Value Not A Converged, High-Confidence Answer. It Means Every Estimate Landed Below The Minimum And Got Clamped To It
Running VPA Across A Cluster Of Mostly Small Workloads Applying Recommendations Wholesale Will Inflate Requests Toward 250Mi Each. Either Lower --pod-recommendation-min-memory-mb Or Treat Small Workloads As Out Of Scope
Evaluating Whether VPA Has “Learned” Your Workload Yet Watch The Upper Bound, Not The Target. The Upper Bound Is Where The Confidence Multiplier Lives, So It’s The Field That Actually Reflects Accumulated History
Inheriting A VPA Install Someone Else Set Up Read The Recommender’s Args Before Interpreting Any Output. Every Number You’re Looking At Is Shaped By Flags That Are Invisible In The VPA Object Itself

Reference: