Bridge: Harden the prune after !1699
Problem
!1699 (merged) prunes what a render stops producing. It lists objects by the release labels, against the kinds the render uses plus status.renderedKinds (<apiVersion>/<Kind>). Review found gaps that leave objects behind for good, or cost more than needed. The label collision, version aliasing and Job propagation issues are blockers on the MR and are not tracked here.
Objects the record loses track of
- A failed List or Delete still drops the kind.
PruneOrphansreturns only the kinds the current render names. A kind that is only inPreviousKindsfalls out of the record even when its List errored or a Delete failed, and no later pass looks for it again. Only the retained-object error path keepsPreviousKinds, despite what the doc comment says. - The GitLabCore finalizer depends on the record.
sweepUnownedalways deleted the GatewayClass by label, but the finalizer now deletes only whatrenderedKindsnames. The record is empty until the new Operator completes a normal pass. That leaves out three cases: a resource deleted right after the Operator upgrade, one stuck inPhaseFailedfrom a render error, and one in the middle of a multi-minor upgrade, which never writes the record. In each case the GatewayClass leaks. - Pinning Siphon's
namespaceandmonitoring_namespacestrands existing objects. A Siphon that set either value throughspec.chart.valueshas objects outside its namespace. The prune lists only the namespace the render uses now, and the Sweeper that reached those objects on deletion is gone. Both values default to the release namespace, so only installations that overrode them are affected. - The record has no namespace. A previous kind is probed only in the owner namespace. A dropped kind that was rendered elsewhere, through a subchart
namespaceOverridesuch asgateway-helmorkubernetes-ingress, is never found, either on reconcile or on deletion.
Guards
neverPruneddoes not coverNamespace.kubernetes-ingress(namespace.create) andgateway-helm(createNamespace) render a Namespace named.Release.Namespaceby default. Where the Operator can write namespaces, turning either off or deleting the GitLabCore deletes the GitLabCore's own namespace, PVCs and Secrets included. The shipped RBAC grants no namespace permissions, so today the apply fails first.- Hooks are not excluded. The
PruneOrphansdoc says hooks are left alone, but hook objects carry the release labels and nothing filters them out. A hook that outlives its run, such as a failedhook-succeededJob, is deleted on the next pass when its kind is one the render also uses.
Cost
- Every pass lists every recorded kind. Each requeue (30 s per resource) runs one uncached, full-body LIST per kind: 17 for a full GitLab release, going by the count in the MR's docs. Orphans appear only when the render changes.
Tests and cleanup
- No test covers a failing List or Delete, or two versions of one kind.
- Only tests use
gatewayClassGVK, but it sits ininternal/controller/gitlabcore/apply.go.
Directions
- Investigate whether an object inventory should replace the kind record. Record group, kind, namespace and name for each applied object, and delete what the new render dropped by diffing. That would cover 1, 4 and most of 7. The spike should answer how large the inventory gets for a full release against the object size limit, and how to seed it for existing resources. One limit is already visible: the first pass can only record what it renders.
- Investigate whether the finalizer should combine the record with fixed targets (the GatewayClass) until every resource has a record, or whether rendering once on deletion is acceptable for resources without a record.
- Investigate whether the Siphon pin needs a one-off migration: a prune across the namespaces the previous
spec.chart.valuesnamed. - Investigate whether the deny-list should become an allow-list: prune only kinds known to be regenerable, instead of excluding destructive kinds one at a time.
Candidate technologies
| Technology | What it might buy | Known limit | Status |
|---|---|---|---|
Inventory in status, as Flux's kustomize-controller keeps status.inventory |
Per-object namespace, GroupKind and name; prune by diff with no LIST; a failed delete stays listed | Status grows with the release; existing resources need seeding | Not evaluated |
| kubectl ApplySet (KEP-3659) | Standard annotations recording group-kinds and extra namespaces on a parent object | Still LISTs to prune; kubectl support was alpha, so check its current status | Not evaluated |
metav1.PartialObjectMetadataList through mgr.GetAPIReader() |
Metadata-only LISTs, a fraction of the payload | Still one LIST per kind per pass; the cached client would start a metadata informer per kind | Not evaluated |
| Hash of the rendered object set on the status | Skip the prune when the render did not change | Misses objects a pass failed to delete; depends on 1 being fixed | Not evaluated |