Skip to content

Add a networkService backend form to HTTPProxy - #443

Merged
scotwells merged 2 commits into
mainfrom
feat/httpproxy-networkservice-backend
Sep 10, 2026
Merged

Add a networkService backend form to HTTPProxy#443
scotwells merged 2 commits into
mainfrom
feat/httpproxy-networkservice-backend

Conversation

@scotwells

Copy link
Copy Markdown
Contributor

An HTTP proxy can point at an endpoint, a connector, or an instance, but not at the group of members a network service resolves, so a consumer still names each member's address in the proxy and keeps that list current as capacity moves between locations. This adds a fourth backend form naming a network service and one of its ports, and the proxy controller resolves the members into endpoint slices that carry each member's location so the edge receives every member in the locality its interface names. A service with no members keeps an empty slice on purpose, because the rule's backend reference names that slice and withholding it would abort the whole route loop for the gateway rather than degrade one rule. This is the fourth of six stacked pull requests splitting the network services prototype, and it builds on the network service type from the third.

Test plan

  • Unit tests cover resolving members into location-bearing slices, sharding past the member limit, and the partial programming report
  • Unit tests cover the empty slice surviving and a withheld slice failing the route loop
  • CRD validation tests cover the new backend form and the rejection of backend TLS on it
  • Full test suite and lint pass in CI

An HTTPProxy can point at an endpoint, a connector or an instance, but
not at the group of members a NetworkService resolves, so a consumer
still has to name each member's address in the proxy and keep the list
current as capacity moves between locations.

Add a fourth backend form that names a NetworkService and one of its
ports. The proxy controller resolves the service's members into
EndpointSlices that carry each member's location, so Envoy receives
every member in the locality its interface names and prefers the
nearest. A service with no members keeps an empty slice, because the
rule's backendRef names it and a missing slice aborts the whole route
loop for the Gateway. Backend TLS is rejected on this form, and a
service is only reachable inside its own network.

Key changes:
- Add the networkService backend to the HTTPProxy API and validation
- Resolve members into location-bearing EndpointSlices, sharded at 100
- Report partial programming when a service exceeds one shard
- Pin that an empty slice still resolves into a downstream Service
@ecv

ecv commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Went through the backend union and the membership resolution. The CEL rewrite is backwards compatible (with no networkService set it reduces to the old expression), the empty-slice-rather-than-no-slice decision is well argued given what a missing slice does to the rest of the Gateway's route loop, and the sharding overflow surfacing as Programmed=False is a good way to keep it visible.

One question before approving. Slice count now varies with live membership, but the apply loop in httpproxy_controller.go only iterates the desired slices and CreateOrUpdates each one. If a service shards past 100 and membership later drops back under the boundary, what removes <base>-1? It still carries a controller reference to the live HTTPProxy, so garbage collection will not take it, and it keeps the member addresses it had at its last write. Every earlier backend kind produced exactly one deterministic slice per rule and backend, so nothing needed pruning before this change.

Is that cleanup handled somewhere I have missed, or deferred to a later PR in the stack? If it needs doing here, listing the slices this proxy owns for the backend and deleting the ones outside the desired set would cover it, plus a test for a shard-count decrease.

Two minor notes while I am here. The endpoint address family is pinned from whichever member sorts first, and members whose only address is of the other family are dropped with nothing but a log line, so a mixed-family namespace loses members silently. And applyPartialProgramming overwrites Programmed unconditionally, so a shard overflow masks a more specific earlier reason such as a connector patch policy not being ready.

Base automatically changed from feat/networkservice-api to main September 9, 2026 22:33
A networkService backend's slice count follows live membership, but the
apply loop only ever created or updated the slices it wanted, so a
shard left behind when membership dropped back under the boundary kept
its stale addresses and its controller reference and nothing removed
it. The service's address family was pinned from whichever member
sorted first, so a mixed-family membership lost members with nothing
but a log line to show for it. And a shard overflow overwrote whatever
the Programmed condition already said, hiding a gateway or connector
policy that was not ready behind a less urgent reason.

Delete every slice this proxy controls that is not in the desired set
once the desired set has been applied, which also clears the slice a
removed rule leaves behind. Pick IPv6 whenever any member can be reached
over it, since the platform is IPv6-first, and name the members left
behind in status rather than a log. Only let a partial result downgrade
a condition that is otherwise True.

Key changes:
- Prune owned EndpointSlices outside the desired set after applying it
- Choose the address family across all members, defaulting to IPv6
- Report members without the chosen family as partial programming
- Withhold partial programming behind an earlier Programmed=False
- Move the EndpointSlice apply loop into its own method
@scotwells

Copy link
Copy Markdown
Contributor Author

Thanks, all three held up. Addressed in 896a856.

Stale shards. Nothing removed them. After the desired slices are applied, the controller now lists the slices in the namespace and deletes any it controls that are outside the desired set. Scoping by controller reference rather than the service label means it also catches a backend that switches away from the networkService kind, and clears the slice a removed rule leaves behind, which was the same gap. Shard zero is always desired, and shards beyond it are never named by a backend reference or given the gateway finalizer, so the prune can never delete something the route loop resolves. The reconcile-level test walks 101 members down to 50, back up past the boundary, and then off the kind entirely, asserting the shard count and that the route still names shard zero at each step.

Address family. The family is now chosen across all members before any endpoint is built: IPv6 if any member can be reached over it, since the platform is IPv6-first, else IPv4. An empty service publishes IPv6 for the same reason, so a drained-and-refilled IPv6 service no longer flips the slice through the delete-and-recreate path. Members holding no address of the chosen family are named in the Programmed condition under a new reason rather than dropped into a log line. Shard overflow keeps precedence when both apply. I went with this over one slice set per family because the gateway builds one downstream service per backend reference, which would split traffic by reference weight instead of by member count.

Programmed masking. Partial programming now only downgrades a condition that is currently True, and logs what it withheld. A gateway that is not yet programmed or a connector policy that is not ready keeps its own reason, and the partial reason resurfaces on the next reconcile once that clears. Merging reasons didn't fit because the reason is a single token the activity-event code keys on.

One structural note: the endpoint slice apply loop moved into its own method unchanged, since adding the prune call pushed the reconcile over the complexity limit. That's the large block in the diff.

@scotwells
scotwells merged commit 28b48fc into main Sep 10, 2026
12 of 13 checks passed
@scotwells
scotwells deleted the feat/httpproxy-networkservice-backend branch September 10, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants