Skip to content

Drain traffic from instances that are no longer serving - #253

Merged
scotwells merged 2 commits into
mainfrom
proto/network-service-health
Aug 26, 2026
Merged

Drain traffic from instances that are no longer serving#253
scotwells merged 2 commits into
mainfrom
proto/network-service-health

Conversation

@scotwells

Copy link
Copy Markdown
Contributor

Summary

A NetworkService only sends traffic to instances that are actually serving. Compute is the one service that knows whether an instance is serving, so it now says so directly on the network interface each instance holds.

Networking deliberately does not know what a workload is. It reads a single condition, HolderAvailable, meaning "whatever holds this interface reports itself ready to serve". This PR makes compute the writer of that condition, sourced from the instance's own Available state.

What consumers get

An instance starts serving and its addresses begin receiving traffic. An instance stops serving, for any reason, and traffic stops reaching it before it goes away.

That second half is the part that matters in practice. Scaling down, rolling out a new version, or redeploying all delete instances, and traffic now drains from an instance as soon as it begins terminating rather than continuing until it disappears. Instances that never became available, including ones held up waiting on quota, never receive traffic at all.

Notes for reviewers

Draining happens before any other deletion work, and it is best effort: a failed status write can never keep an instance stuck in Terminating.

Availability is derived fresh from the current holder on every pass rather than merged with whatever is already on the interface. With reclaimPolicy: Retain an interface outlives its instance so the next instance in the slot reuses the same addresses, and deriving fresh is what stops a new instance inheriting its predecessor's word before it is serving.

Pairs with datum-cloud/network-services-operator#411, which reads this condition. The two sides currently agree by string rather than by symbol, because compute pins a network-services-operator version predating the constant. Worth swapping to the imported symbol at the next pin bump.

A networking NetworkService needs to know whether a member can serve, but
the only health signal on a NetworkInterface was Programmed, written
solely by the VPC fabric. Where the fabric is not attached nothing ever
writes it, so every member reads unhealthy and no traffic is served even
though membership and locality resolve correctly.

Compute holds the interface and knows whether the instance behind it is
serving, so compute publishes that. A HolderAvailable condition is
projected onto every NetworkInterface an instance holds, mirroring the
instance's own Available condition. Networking reads it and never learns
that the holder is a workload.

Anything other than Available=True projects as False carrying the
instance's own reason, so Unknown drains the member rather than leaving
it silently taking traffic. The projection runs on every reconcile, not
only on a transition, so interfaces that predate it converge without
their workload being redeployed, and it writes only when the condition
moves.

Key changes:
- Project HolderAvailable onto each interface named by
  status.networkInterfaces[].networkInterfaceRef
- Run the projection on the suspension path too, so a suspended instance
  drains before the hub is told
- Grant the controller networkinterfaces/status write access
Deletion is the drain path that matters. Scale-down, rolling update, and
redeploy all end an instance by deleting it, and the deletion reconcile
returns before availability was ever projected. Nothing clears the
instance's Available condition on the way out either, so HolderAvailable
stayed True for the whole deletion window and the edge kept routing to a
process shutting down.

Deletion is now judged ahead of the instance's own Available condition,
and the deletion reconcile drains the interfaces it held before releasing
anything else. That write is best effort: an instance that cannot reach
its interfaces must still finish deleting, or a transient API error
strands it in Terminating forever.

A retained interface outlives the instance that held it, so a True left
behind could be inherited by the next instance in the slot before it is
serving. Draining on delete removes it at the source, and the projection
derives the condition wholly from the current holder rather than merging
with what is on the interface, so a successor starts False regardless.

The projection also moved above the quota and status-update returns in
the main reconcile. Those returns skipped it, and an instance stuck
waiting on quota is exactly one that should not be receiving traffic.

Key changes:
- Report HolderTerminating=False for an instance with a deletion timestamp
- Drain from reconcileDeletion before quota cleanup and finalizer removal,
  logging rather than returning a drain failure
- Project before the early returns that previously skipped it
@scotwells
scotwells requested review from a team, JoseSzycho and privateip August 26, 2026 20:45
@scotwells
scotwells marked this pull request as ready for review August 26, 2026 20:45
@scotwells
scotwells enabled auto-merge August 26, 2026 21:19
@scotwells
scotwells merged commit 09a657a into main Aug 26, 2026
12 checks passed
@scotwells
scotwells deleted the proto/network-service-health branch August 26, 2026 21:19
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