cmd/k8s-operator: stabilize StaticEndpoints order in ProxyGroup reconciles (#19755)

findStaticEndpoints built its return slice by iterating nodes.Items in
the order returned by r.List, which is not guaranteed to be stable
across calls. When the resulting set of addresses already matched the
existing config Secret, the slice could still permute between
reconciles, making the marshalled config Secret differ byte-for-byte.
That tripped the DeepEqual check on the config Secret, which rewrote
the Secret, which fired a watch event, which re-enqueued the
ProxyGroup, looping forever.

Detect this case and return the existing currAddrs slice unchanged
when the resulting set is the same, preserving the "use the currently
used IPs first" intent without spurious writes.

Fixes #19700

Signed-off-by: Jason Dillingham <jasonmdillingham@gmail.com>
This commit is contained in:
Jason Dillingham
2026-05-27 14:28:04 +01:00
committed by GitHub
parent e2a0d45418
commit 0e2b3f31af
2 changed files with 110 additions and 0 deletions
+26
View File
@@ -1104,9 +1104,35 @@ func (r *ProxyGroupReconciler) findStaticEndpoints(ctx context.Context, existing
return nil, &FindStaticEndpointErr{msg: fmt.Sprintf("failed to find any `status.addresses` of type %q on nodes using configured Selectors on `spec.staticEndpoints.nodePort.selectors` for ProxyClass %q", corev1.NodeExternalIP, proxyClass.Name)}
}
// If we ended up selecting the same set of addresses already in use, keep
// the existing order. nodes.Items from r.List is not guaranteed to be in
// a stable order across calls, so without this the slice can permute on
// each reconcile, making the marshalled config Secret differ byte-for-byte
// even though nothing has effectively changed. That trips the DeepEqual
// check on the config Secret, which writes the Secret, which fires a
// watch event, which re-enqueues the ProxyGroup, and so on.
if len(currAddrs) > 0 && sameAddrPortSet(endpoints, currAddrs) {
return currAddrs, nil
}
return endpoints, nil
}
// sameAddrPortSet reports whether a and b contain the same AddrPorts,
// ignoring order. Both slices are assumed to be free of duplicates, which
// holds for callers in this package.
func sameAddrPortSet(a, b []netip.AddrPort) bool {
if len(a) != len(b) {
return false
}
for _, x := range a {
if !slices.Contains(b, x) {
return false
}
}
return true
}
func getStaticEndpointAddress(a *corev1.NodeAddress, port uint16) *netip.AddrPort {
addr, err := netip.ParseAddr(a.Address)
if err != nil {