agent-platform release v4.27.2
Changed
- The agentgateway data plane runs two replicas behind a
PodDisruptionBudget, spread across nodes; the controller runs two replicas. Every MCP call and, with llmRouting on, every model call of the platform crosses the data-plane Deployment the agentgateway controller reconciles from the Gateway; at one replica with no budget it was one node reboot or drain away from taking every agent offline. The connectivity chart’s AgentgatewayParameters now carries deployment.spec.replicas from gateway.parameters.replicas (2), a podDisruptionBudget.spec from gateway.parameters.podDisruptionBudget (enabled: true; the keys other than enabled are the PDB spec as written, and with neither minAvailable nor maxUnavailable set the template emits maxUnavailable: 1 — maxUnavailable so a lone replica stays evictable, in the template so minAvailable can be chosen through the meta chart, where a null never reaches the connectivity defaults), and one topologySpreadConstraint per gateway.parameters.spread.topologyKeys entry (kubernetes.io/hostname; maxSkew: 1, whenUnsatisfiable: ScheduleAnyway, so a single-node lab still schedules both pods, and matchLabelKeys: [pod-template-hash] so a rollout spreads the new ReplicaSet against itself — without it the surge pod is balanced against the revision being replaced and both survivors can be left on one node, which ScheduleAnyway never corrects), selecting the data-plane pods by gateway.name. The budget’s third pass-through key is unhealthyPodEvictionPolicy, and the maxUnavailable: 1 default fills in the missing budget field rather than replacing the spec, so a policy set on its own survives it. The render refuses a budget with both fields, a key that is not one of the three passed through (keeping the two budget fields out of values.yaml is what leaves the block additionalProperties: true, so a misspelt minAvailabe would otherwise be dropped in silence), a string that is not a percentage from 0% to 100%, a fractional or negative number, an unhealthyPodEvictionPolicy outside the API’s enum, every budget that allows no eviction (an integer minAvailable at or above replicas, a percentage minAvailable that rounds up to every replica, a zero maxUnavailable — it would hang every node drain on the data plane), and spread.enabled with no topology key or an empty key in it; the schema holds replicas and maxSkew at 1 or more, and the template additionally refuses replicas, spread.maxSkew and spread.whenUnsatisfiable when they are unset — a null set through the meta chart is consumed by Helm at that layer, so the chart sees a deleted key that no schema keyword can floor, and replicas alone would otherwise render deployment.spec.replicas: 0 and scale the data plane to zero. The meta chart declares the same keys at the same defaults, and its forwarded agentgateway.controller.replicaCount is 2. Leader election is on — the controller’s manager sets LeaderElection: !DisableLeaderElection and that setting defaults to false (agentgateway controller/api/settings/settings.go), the chart grants the lease in the controller namespace and sets no AGW_DISABLE_LEADER_ELECTION — so the two pods do not both reconcile the data plane or write Gateway status: the status syncer’s NeedLeaderElection() is true (the leader alone) and the xDS Syncer’s is false (every replica serves xDS). What the second replica buys is a pod-level failure and a rolling restart, not node redundancy: the packaging chart’s strict schema declares controller.podDisruptionBudget, controller.strategy and affinity as empty objects with additionalProperties: false and declares no topologySpreadConstraints at all, so the two controller pods cannot be spread and often land on one node — a budget and a spread for the controller wait for a schema fix there (giantswarm/agentgateway#51). make verify-dataplane-ha and tests/verify-agentgateway-wiring.py hold the shape. Every installation with the agentgateway component on rolls the data plane once (a second pod, the PDB, the spread constraint) and the controller once (a second pod); gateway.parameters.replicas: 1 with podDisruptionBudget.enabled: false and spread.enabled: false restores the previous shape. On a single-node cluster a drain of that node now waits on the budget (the replacement pod cannot schedule) — turn the budget off there. Legacy SSE MCP sessions are pod-local, and two replicas break them outright rather than only across a pod change: the session lives on the pod that answered /sse, the ClusterIP Service balances each later POST on its own, and a POST that lands on the other pod finds no session. Streamable-HTTP sessions (what the platform’s own RemoteMCPServers use) survive both. gateway.parameters.podAnnotations is now empty by default: the karpenter.sh/do-not-disrupt: "true" 4.15.0 (#431) put on the data plane was the answer to a single pod, and next to two replicas, the budget and the spread it only pinned both of the pods’ nodes against Karpenter’s consolidation, drift and expiry — while the budget, which is what holds a drain to one pod at a time, was never reached. The knob is unchanged and an installation that would rather keep the streams open on a pod over node churn sets the annotation back on it; what it buys is the streams that outlive the data-plane shutdown window (10 s still accepting, draining to the 55 s deadline).