Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .circleci/custom.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# `make verify-engine` + `make verify-self` + `make verify-labels` + `make verify-postgres` +
# `make verify-secrets` + `make verify-login-connector` +
# `make verify-components` + `make verify-auto` + `make verify-identity` +
# `make verify-wiring` + `make verify-llm-routing` + `make verify-dataplane-ha` checks on branches (ingress-mode guards, the global.*
# `make verify-wiring` + `make verify-llm-routing` + `make verify-dataplane-ha` + `make verify-dataplane-vpa` checks on branches (ingress-mode guards, the global.*
# contract, the model-manager / agent-manager wiring, the kagent tool-server
# egress, app-of-apps meta-package render, the bundled Flux engine's two
# shapes, the self-management shapes, the postgres.backup wiring and guards,
Expand Down Expand Up @@ -91,10 +91,15 @@ jobs:
# verify-llm-routing (the llmRouting toggle, both states, and its
# guards) and verify-dataplane-ha (the agentgateway data plane's replicas,
# PodDisruptionBudget and node spread by default, the knobs, the guards,
# the meta chart's forwarding) are the single source of truth, also run locally. The meta chart's one
# the meta chart's forwarding) and verify-dataplane-vpa (the agentgateway
# VerticalPodAutoscalers behind one auto knob: the data-plane VPA on the
# Gateway's Deployment in the fleet shape, none without the API or in
# muster-direct, explicit wins both ways, the meta chart forwards the
# knob resolved and empties the controller's VPA where the API is not
# served, the controller budget and spread forwarded) are the single source of truth, also run locally. The meta chart's one
# dependency is the flux-engine subchart, a directory in its charts/,
# which needs no `helm dependency build`.
command: make verify-modes verify-global verify-meta verify-engine verify-self verify-labels verify-insecure verify-managers verify-kagent-netpol verify-kagent-discovery verify-postgres verify-secrets verify-login-connector verify-presets verify-components verify-components-charts verify-auto verify-identity verify-wiring verify-llm-routing verify-dataplane-ha
command: make verify-modes verify-global verify-meta verify-engine verify-self verify-labels verify-insecure verify-managers verify-kagent-netpol verify-kagent-discovery verify-postgres verify-secrets verify-login-connector verify-presets verify-components verify-components-charts verify-auto verify-identity verify-wiring verify-llm-routing verify-dataplane-ha verify-dataplane-vpa

workflows:
build:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- The agentgateway data plane runs the **Giant Swarm line of agentgateway**: the connectivity chart's `agentgateway.proxy.image.tag` (rendered into `AgentgatewayParameters`) is `v1.5.1-gs.1` — upstream v1.5.0 rebuilt, scanned and signed in [giantswarm/agentgateway-upstream](https://github.com/giantswarm/agentgateway-upstream) (its `FORK.md`; tracking giantswarm/giantswarm#37758) and mirrored into gsoci by the retagger — the same release the `giantswarm/agentgateway` packaging chart pins for the controller, so a fix the platform needs in agentgateway can be carried on the line and reaches every installation as the next `-gs.N` release. **Every installation with the agentgateway component on rolls its data-plane pods once** to an image built from the same source as before; nothing else in the render changes.
- **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 the chart's default; every replica serves xDS) so a data-plane pod that starts on a rebooted node finds its config unless both controller pods sat on that node — the packaging chart's strict schema declares `controller.podDisruptionBudget`, `controller.strategy` and `affinity` as empty objects, so the controller's own budget and spread wait for a schema there. `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 `RemoteMCPServer`s use) survive both.
- **The agentgateway data plane and controller each get a `VerticalPodAutoscaler` behind one `auto` knob; the controller gets a `PodDisruptionBudget` and a hostname spread (agentgateway chart 2.1.2).** The controller sets the data-plane container's cpu/memory requests from its own defaults (100m/128Mi; the pod idles at 1m/10Mi on the fleet), and the controller itself uses ~185Mi against the 128Mi it requests — sizes chosen for nothing in particular on the path every MCP and model call crosses. The connectivity chart renders a `VerticalPodAutoscaler` named after the Gateway (`gateway.name`) on the data-plane `Deployment` (`templates/agentgateway/verticalpodautoscaler.yaml`: `targetRef` apps/v1 `Deployment` `<gateway.name>` — the deployer names the Deployment after the Gateway, not after the release; `updateMode: Auto`; one `"*"` container policy controlling cpu and memory between `minAllowed` 50m/64Mi — the floor keeps a burst of streamed LLM responses from starving a pod the recommender saw idle — and `maxAllowed` 2/2Gi; ephemeral-storage stays the `AgentgatewayParameters`' `dataPlaneResources`), gated on the new knob **`gateway.parameters.verticalPodAutoscaler.enabled: auto`** — a cluster-shape knob like the others, `true` when `autoscaling.k8s.io/v1` is served (`agent-platform.shape.dataPlaneVPA`; a kind lab has no VPA CRD, the fleet's management clusters run VPA 1.5.1 in eviction-based `Auto` mode), `true` / `false` forcing it — and on the data plane rendering at all (an `agentgateway-*` mode). Evictions apply a new size one pod at a time behind the data-plane budget above. The meta chart declares the block at the same defaults, resolves the knob **once** in its shape pass (`agent-platform.shape.apply`) and forwards it resolved; the same answer drives the controller's own VPA: the forwarded `agentgateway.controller.verticalPodAutoscaler` (`Auto`; `"*"`; cpu + memory, requests only — the chart's fixed limits 500m/512Mi bound the recommendation, so the limits never scale with the request; 50m/128Mi–500m/512Mi; the packaging chart adds the `targetRef`) is emptied to `{}` — which the chart renders as no VPA — where the knob resolves false, left as written where it resolves true, and left absent when an operator removed the key. The forwarded `agentgateway:` block also carries `controller.podDisruptionBudget: {maxUnavailable: 1}` (one controller pod evictable at a time; `maxUnavailable` so a lone replica stays evictable) and a root `topologySpreadConstraints` entry (`kubernetes.io/hostname`, `maxSkew: 1`, `ScheduleAnyway`, selecting the controller pods by the chart's `agentgateway: agentgateway` label), so a drain evicts one controller pod at a time and the scheduler keeps the two apart where it can (`ScheduleAnyway` is best effort), and a rebooting node rarely takes the data plane's xDS with it. The packaging chart's strict schema accepts these keys from **agentgateway 2.1.2** (giantswarm/agentgateway#51; 2.1.1 declares them as empty objects and rejects every key inside), so `components.agentgateway.versionRange` is **`>=2.1.2 <3.0.0`** (was `2.x`) and `examples/customer-bom.yaml` pins `2.1.2`: with the floor the agentgateway `OCIRepository` re-resolves to a chart that accepts the values before the release applies them — until 2.1.2 is published it reports the unresolved range, the agentgateway `HelmRelease` is not Ready and every release that depends on it — connectivity, agent-platform-mcps and everything behind connectivity — holds its next generation while the running releases keep running ([UPGRADE.md](./UPGRADE.md), the merge-order dependency: release giantswarm/agentgateway#51 first). New `make verify-dataplane-vpa` (the data-plane VPA's shape, following `gateway.name`, none without the API or in `muster-direct`, explicit wins both ways, the schema, the meta chart forwarding the knob resolved and emptying the controller's VPA); `make verify-auto` gains the `vpa-only` shape and the knob on every shape (`tests/verify-cluster-shape.py`); `tests/verify-agentgateway-wiring.py` holds the controller's budget and spread; `make verify-components-charts` now renders the forwarded `agentgateway` block against the published chart the range resolves — **red until 2.1.2 is published** (on this line the pull finds no version satisfying the range; on main the render against 2.1.1 rejects the keys), the intended dependency signal. **What rolls** on installations with the agentgateway component on, once the `OCIRepository` resolves 2.1.2: the controller gets a `PodDisruptionBudget`, a spread constraint and — where the VPA API is served — a `VerticalPodAutoscaler` (the chart upgrade and the spread are one Deployment revision; the VPA then re-sizes the controller pods by eviction once it has a recommendation, one at a time behind the budget), the data plane gets a `VerticalPodAutoscaler` (a new object; no pod changes until the recommender's first eviction, one at a time behind the budget). On a cluster without `autoscaling.k8s.io/v1` nothing autoscaling-related renders. `gateway.parameters.verticalPodAutoscaler.enabled: false` opts out of both VPAs (with the API served too); `agentgateway.controller.verticalPodAutoscaler: null` removes the controller's alone.

### Changed

Expand Down
Loading
Loading