Conversation
EnsureWaypointHPA builds the waypoint HPA but never sets spec.behavior, so Kubernetes defaults apply: scale up instantly with no stabilization, then five minutes later allow removing 100% of the pods in a single 15s step. Every waypoint pod removed terminates the gRPC connections routed through it. Because a waypoint is a shared L7 proxy, one aggressive step is felt by every caller of the namespace at once, as: rpc error: code = Unavailable desc = upstream connect error or disconnect/reset before headers. reset reason: connection termination Observed in production: 129 waypoint ScalingReplicaSet events across the fleet in 30 minutes, and 88 scale-downs of the 4->3 transition alone for pushnotifications-production on production-yolk-c between Sept 5 and 8. The waypoints move in near-lockstep across all four clusters, which turns into notifications/users gRPC latency SLO burn alerts in #eng-watch-alerts several times a day. The observed 5m20s gap between the scale-up burst and the scale-down burst is exactly the default scaleDown.stabilizationWindowSeconds of 300. Set spec.behavior.scaleDown with two policies and SelectPolicy: Max: - Percent 25% / 60s does the damping at higher replica counts. - Pods 1 / 60s keeps the HPA able to make progress at low counts. A percent-only rule rounds down to zero pods there -- at 3 replicas ceil(3 * 0.75) = 3 -- and would pin the waypoint above its floor. scaleUp is deliberately left on the Kubernetes defaults. Scaling up terminates no connections, so damping it buys nothing for this failure and would only slow the waypoint's response to a traffic spike. Also adds ensureWaypointHPA_test.go, closing half of the "ZERO test files" gap AGENTS.md records for the waypoint plans. The test pins the rendered spec; it does not exercise the HPA controller's runtime interpretation of the policies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dosemwengie
approved these changes
Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
EnsureWaypointHPAbuilds the waypoint HPA but never setsspec.behavior, so Kubernetes defaults apply:scaleUp.stabilizationWindowSecondsscaleUppoliciesscaleDown.stabilizationWindowSecondsscaleDownpolicyScale up instantly, then five minutes later drop up to everything in one step. That is a sawtooth generator.
Every waypoint pod removed terminates the gRPC connections routed through it. A waypoint is a shared L7 proxy, so one aggressive step is felt by every caller of the namespace simultaneously:
Evidence from production
129 waypoint
ScalingReplicaSetevents fleet-wide in 30 minutes (09:45–10:15 UTC, Sept 8).88 scale-downs of the
4→3transition alone forpushnotifications-productiononproduction-yolk-c, Sept 5–8.Near-lockstep across all four clusters — same global traffic curve, so the HPAs move together:
That 5m20s gap being exactly the 300s default is what pinned this down.
Downstream, this surfaces as
notifications/usersgRPC latency SLO burn alerts in #eng-watch-alerts several times a day, plus matching errors onnotifications→pushnotifications/authandno healthy upstreamonusers→postservice.The change
spec.behavior.scaleDownwith two policies andSelectPolicy: Max:ceil(3 × 0.75) = 3) and would pin the waypoint above its floor indefinitely.scaleUpis deliberately left on the Kubernetes defaults. Scaling up terminates no connections, so damping it buys nothing for this failure mode and would only slow the waypoint's response to a traffic spike. Setting onlyScaleDownis safe — the HPA controller fills inscaleUpdefaults when the side is nil.Needs a reviewer's eyes
SelectPolicy: Maxsemantics. I read this as "allow the policy permitting the larger change", so at 3 replicas the pods policy (remove 1) wins over the percent policy (remove 0), and at 100 replicas the percent policy (remove 25) wins over pods (remove 1). If that is backwards, the pods policy becomes the binding constraint everywhere and scale-down from high replica counts gets very slow — cost, not availability. Worth confirming against the HPA controller source rather than taking my word for it.maxReplicas: 100coming back down to their floor take roughly 8–10 minutes under 25%/60s. That seems right for a proxy fleet but is a judgement call.minAvailable = MinReplicas - 1. Slower scale-down means more sustained pods, which should only make evictions easier, but I have not exercised the two together.Testing
Adds
controllers/plan/ensureWaypointHPA_test.go— three tests covering the rendered behavior, the min/max/CPU defaults (including theMinReplicas >= 2floor from PICCHU-INV-MESH-3), and the nil-spec no-op.This closes half of the gap AGENTS.md records: "ensureWaypointHPA.go and ensureWaypointPDB.go have ZERO test files."
ensureWaypointPDB.gostill has none. Note the test pins the rendered spec only — it does not exercise the HPA controller's runtime interpretation of the policies, which is the part flagged above.Not in this PR
terminationGracePeriodSeconds+ preStop, orEXIT_ON_ZERO_ACTIVE_CONNECTIONS). This is the higher-value fix — it makes scale-downs harmless regardless of frequency, where this PR only reduces how often they happen. Separate PR, separate release.Applyreturns early whenp.HPA == nilandDeleteWaypointHPAonly runs when ambient is switched off, so removingwaypointHPAfrom an app.yml strands the HPA.writerstatshas nowaypointHPAin any target yet has one actively scaling it (2→3, 53 times since Sept 4).syncer.goandensureWaypointHPA.go. This PR does not touch those, but the behavior constants are deliberately defined in one place only.Related
Medium/mono#109365 — raises
pushnotificationswaypointHPA.minReplicas2→6, setstargetCPUUtilizationPercentageandwaypointResources. That is the app-level canary and can land first; this PR is the fleet-wide root-cause fix and should not land in the same observation window.🤖 Generated with Claude Code