Preserve external annotations on operator Services#5731
Merged
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #5731 +/- ##
==========================================
+ Coverage 70.65% 70.69% +0.04%
==========================================
Files 682 682
Lines 68854 68852 -2
==========================================
+ Hits 48646 48675 +29
+ Misses 16662 16619 -43
- Partials 3546 3558 +12 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
aponcedeleonch
previously approved these changes
Jul 6, 2026
aponcedeleonch
left a comment
Member
There was a problem hiding this comment.
Nice fix, the subset-check + merge approach is a clean reuse of the existing Deployment pattern and it clearly kills the hot-loop. Left two non-blocking comments inline.
0b3891d to
c7777c9
Compare
A Service co-owned by an external controller (e.g. GKE NEG/Gateway, which writes `cloud.google.com/*` annotations) made `serviceNeedsUpdate` fire on every reconcile and the update wholesale-replace those annotations, hot-looping `Update` against the concurrent writer with `resourceVersion` conflicts. * Compare operator-owned labels/annotations with `MapIsSubset` instead of `maps.Equal`, so externally-managed keys are not treated as drift * Merge (not replace) labels/annotations on write via `MergeLabels`/ `MergeAnnotations`, preserving external keys * Apply the fix to the VirtualMCPServer, MCPServer, MCPRemoteProxy, and EmbeddingServer Service reconcilers * Add detection and write-path regression tests for all four Fixes #5730
c7777c9 to
3f6125f
Compare
ChrisJBurns
approved these changes
Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A
VirtualMCPServer's backingvmcp-<name>Service (and the equivalent Services forMCPServer,MCPRemoteProxy, andEmbeddingServer) is co-owned by external controllers on some platforms. On GKE with the Gateway API, the NEG/Gateway controllers continuously writecloud.google.com/*annotations onto it. The operator's Service reconcile did not tolerate this:serviceNeedsUpdatecompared annotations withmaps.Equalagainst an operator-only expected set, so any externally-added annotation read as drift and the check returnedtrueon every reconcile.service.Annotations = newService.Annotations, wholesale-replacing the map and stripping the external annotations.The result was a tight hot-loop of
Updatecalls racing the concurrent cloud controller, each losing theresourceVersionrace withthe object has been modifiedand requeuing immediately (~700-835 conflict log lines/minute observed). Serving was unaffected, but it drove continuous kube-apiserver churn and ~1M+ log lines/day per workload.This is the Service-object sibling of #5616 (which fixed the same class of build/diff asymmetry on the Deployment). The fix reuses helpers that already exist in the repo rather than introducing Server-Side Apply (which
.claude/rules/operator.mdexplicitly disallows as a one-off):ctrlutil.MapIsSubsetinstead ofmaps.Equal, so externally-managed keys are ignored and the reconcile is a genuine no-op once converged.ctrlutil.MergeLabels/ctrlutil.MergeAnnotations(operator-owned values win, external keys preserved), matching what the Deployment path already does.Applied consistently to all four controllers that reconcile a proxy Service:
VirtualMCPServer,MCPServer,MCPRemoteProxy, andEmbeddingServer.EmbeddingServeralready tolerated external annotations in detection but still stripped them on write, so it would flap the NEG annotations on every legitimate Service update; it is fixed here too.Type of change
Changes
virtualmcpserver_controller.goserviceNeedsUpdatesubset check; merge-on-write for labels/annotationsmcpserver_controller.gomcpremoteproxy_controller.goembeddingserver_controller.goMapIsSubsetfor consistency*_test.go(4 files)UpdateTest plan
cmd/thv-operator/controllersandpkg/controllerutil)cloud.google.com/neg-statusannotation must not trigger an update (all four controllers)task lint-fix(no new findings on changed files)Does this introduce a user-facing change?
No. Behavior is unchanged for Services with no external co-owner; the fix only stops spurious
Update/conflict churn when another controller writes annotations on the operator's Service.Special notes for reviewers
Fixes #5730