From 9b0036e359f0124bbd2d16e227bab8ec28081de4 Mon Sep 17 00:00:00 2001 From: Konrad Heimel Date: Mon, 17 Aug 2026 16:55:48 +0200 Subject: [PATCH] :memo: docs(controller): document cluster-scope GVK backstop and upgrade impact MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #304 makes the KollectClusterTarget reconciler re-check KollectClusterScope `allowedGVKs` after the profile loads, and degrades with `ScopeGVKDenied`. That is a new user-visible condition reason on a kind whose reference page did not list it, and it is a behaviour change an operator can be surprised by: An existing, collecting ClusterTarget whose profile `targetGVK` sits outside a non-empty `allowedGVKs` now unregisters its informers and goes Degraded on the first reconcile after upgrade. Reaching that state needs no bug — admission only runs when the object is written, so every target admitted before the ceiling was created or tightened is in the affected set. - kollectclustertarget.md: `ScopeGVKDenied` and `ScopeNamespaceDenied` rows in the Degraded table (the latter was already reachable and undocumented), the two admission denials the fix adds, and the post-upgrade symptom. - kollectclusterscope.md: an Enforcement section stating which stage checks what, so the reconcile backstop ADR-0207 always specified is visible on the ceiling's own page. Includes the ClusterInventory sink reasons for completeness. - upgrading.md: a pre-upgrade audit and post-upgrade triage for the affected set, under the existing behaviour-changes section. - troubleshooting.md: the two scope reasons applied to Target *and* ClusterTarget; they named only the namespaced kind and KollectScope. Docs-only; no version invented for the note, which says "after v0.18.0". Gates: lint:markdown 0 issues (148 files), scrub ok. Requires #304 to merge first, or the reconcile column of the enforcement table describes code that is not on main yet. --- docs/crds/kollectclusterscope.md | 16 +++++++++++++ docs/crds/kollectclustertarget.md | 9 +++++++ docs/operator-manual/troubleshooting.md | 5 ++-- docs/operator-manual/upgrading.md | 32 +++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/docs/crds/kollectclusterscope.md b/docs/crds/kollectclusterscope.md index 784579ad..f6971e60 100644 --- a/docs/crds/kollectclusterscope.md +++ b/docs/crds/kollectclusterscope.md @@ -41,6 +41,22 @@ The namespaced [`KollectScope`](kollectscope.md) sample ([`config/samples/kollect_v1alpha1_kollectscope_team-a.yaml`](https://github.com/platformrelay/kollect/blob/main/config/samples/kollect_v1alpha1_kollectscope_team-a.yaml)) shows the same fields scoped to a single namespace. +## Enforcement + +The ceiling is checked twice ([ADR-0207](../adr/0207-target-collection-filtering.md)): + +| Stage | Object | Checks | On violation | +| --- | --- | --- | --- | +| Admission | [`KollectClusterTarget`](kollectclustertarget.md) | `allowedGVKs` (profile `targetGVK` and `resourceRules`), `allowedNamespaces`, `deniedNamespaces`, `allowedStaticRefNamespaces` | Create/update rejected | +| Reconcile | `KollectClusterTarget` | `allowedGVKs`, `allowedStaticRefNamespaces` | Informers unregistered; `Degraded=True` with `ScopeGVKDenied` or `ScopeNamespaceDenied` | +| Reconcile | [`KollectClusterInventory`](kollectclusterinventory.md) | `sinkRefs`, `allowedStaticRefNamespaces` on sink refs | `Degraded=True` with `ScopeSinkDenied` or `SinkNamespaceDenied` | + +Reconcile is the backstop for objects admitted before the ceiling existed or was tightened, and for +targets created while `profileRef` did not yet resolve — a missing profile makes the profile +`targetGVK` unknowable at admission time. `allowedNamespaces` and `deniedNamespaces` additionally cap +`status.effectiveNamespaces` at collect time, so a selector that matches a denied namespace filters it +out rather than degrading the target. + ## See also - [KollectScope](kollectscope.md) — namespaced ceiling diff --git a/docs/crds/kollectclustertarget.md b/docs/crds/kollectclustertarget.md index b5b3e30d..2b780f22 100644 --- a/docs/crds/kollectclustertarget.md +++ b/docs/crds/kollectclustertarget.md @@ -105,8 +105,14 @@ kubectl describe kctgt platform-argo-applications | --- | --- | --- | | `Suspended` | `spec.suspend: true` | Set `suspend: false` | | `ProfileNotFound` | No `KollectProfile` in `profileRef.namespace` | Create the `KollectProfile` in the referenced namespace | +| `ScopeGVKDenied` | Profile `targetGVK` or a `resourceRules` GVK is outside [`KollectClusterScope`](kollectclusterscope.md) `allowedGVKs` | Add the GVK to `allowedGVKs`, or point `profileRef` at a permitted profile | +| `ScopeNamespaceDenied` | `profileRef.namespace` is outside `allowedStaticRefNamespaces` ([ADR-0208](../adr/0208-cluster-static-refs-via-namespace.md)) | Move the profile into a permitted namespace, or extend the allowlist | | `InformerRegistrationFailed` | Dynamic client / GVK error | Verify CRD installed; check operator logs | +Scope violations unregister the target's informers before degrading, so collection stops. Admission +rejects these cases up front; reconcile repeats the check as backstop for objects admitted before the +ceiling existed or before `profileRef` resolved ([ADR-0207](../adr/0207-target-collection-filtering.md)). + ## RBAC | Actor | Verbs | Resource | Notes | @@ -124,7 +130,10 @@ Cluster-scoped resources require elevated RBAC — restrict to platform SRE role | Admission denied | Missing `profileRef.name` | Set the profile name | | Admission denied | Missing `profileRef.namespace` | Set the profile namespace — required on cluster kinds (ADR-0208) | | Admission denied | Missing `namespaceSelector` | Add explicit label selector | +| Admission denied | `includedNamespaces` outside the `KollectClusterScope` ceiling, or a denied namespace — checked even when `profileRef` does not resolve yet | Align `includedNamespaces` with the ceiling, or widen `allowedNamespaces` | +| Admission denied | `profileRef.namespace` outside `allowedStaticRefNamespaces` | Move the profile, or extend the allowlist | | No collection | Empty `namespaceSelector` match or RBAC denied | Label namespaces; extend operator ClusterRole for target GVK | +| `ScopeGVKDenied` after upgrade | Ceiling was tightened after the target was admitted; reconcile now enforces `allowedGVKs` | Widen `allowedGVKs` or retire the target — see [Upgrading](../operator-manual/upgrading.md#cluster-scope-gvk-enforcement-after-v0180) | | `ProfileNotFound` | No `KollectProfile` in `profileRef.namespace` | Create the profile in the referenced namespace | | `Degraded` / `Forbidden` | SAR denies list in scoped NS | Grant operator read on target GVK in workload namespaces | diff --git a/docs/operator-manual/troubleshooting.md b/docs/operator-manual/troubleshooting.md index 3f8f8258..6708c3b1 100644 --- a/docs/operator-manual/troubleshooting.md +++ b/docs/operator-manual/troubleshooting.md @@ -49,8 +49,8 @@ or namespace** in a `*SinkRefs` entry is wrong — fix the reference, not just c | `SinkNotFound` | Inventory, Target | Typo or wrong namespace in `*SinkRefs` | Match exact sink name in **same namespace** | | `SinkUnreachable` | Inventory, Target | `ConnectionVerified=False` on sink | Fix Secret, DSN, network; re-probe sink | | `ScopeSinkDenied` | Inventory | Sink not in `KollectScope` allow-list | Add sink to scope allow-list refs | -| `ScopeGVKDenied` | Target | GVK blocked by scope | Update `KollectScope.spec.allowedGVKs` | -| `ScopeNamespaceDenied` | Target | Workload namespace blocked | Add to `allowedNamespaces` | +| `ScopeGVKDenied` | Target, ClusterTarget | GVK blocked by scope | Update `allowedGVKs` on `KollectScope` (Target) or `KollectClusterScope` (ClusterTarget) | +| `ScopeNamespaceDenied` | Target, ClusterTarget | Workload namespace blocked; on ClusterTarget, `profileRef.namespace` outside `allowedStaticRefNamespaces` | Add to `allowedNamespaces`, or permit the profile namespace | | `ProfileNotFound` | Target | Missing `KollectProfile` | Apply profile in same namespace as target | | `PayloadTooLarge` | Inventory | Exceeds `maxExportBytes` | Split targets or trim attributes | | `ExportTerminal` | Inventory | Non-retryable sink error | Fix sink config; check operator logs | @@ -59,6 +59,7 @@ or namespace** in a `*SinkRefs` entry is wrong — fix the reference, not just c Full per-kind tables: [KollectInventory](../crds/kollectinventory.md#status-conditions), [KollectTarget](../crds/kollecttarget.md#status-conditions), +[KollectClusterTarget](../crds/kollectclustertarget.md#status-conditions), [KollectSnapshotSink](../crds/kollectsnapshotsink.md#status). ## Symptom → cause quick reference diff --git a/docs/operator-manual/upgrading.md b/docs/operator-manual/upgrading.md index 8c4fd53d..4ece9cd0 100644 --- a/docs/operator-manual/upgrading.md +++ b/docs/operator-manual/upgrading.md @@ -111,6 +111,38 @@ bumping versions. Common upgrade touchpoints: !!! info "Export debouncing" Debounce interval is per **`KollectInventory.spec.exportMinInterval`** (CRD default **30s**). +### Cluster-scope GVK enforcement (after v0.18.0) + +Releases after **v0.18.0** enforce [`KollectClusterScope`](../crds/kollectclusterscope.md) +`allowedGVKs` during reconcile, not only at admission — the backstop +[ADR-0207](../adr/0207-target-collection-filtering.md) always specified. + +!!! warning "Existing cluster targets can stop collecting" + A [`KollectClusterTarget`](../crds/kollectclustertarget.md) whose profile `targetGVK` or + `resourceRules` GVK sits outside a non-empty `allowedGVKs` now unregisters its informers and goes + `Degraded=True` / `reason=ScopeGVKDenied` on the first reconcile after upgrade. Targets admitted + **before** the ceiling was created or tightened are the affected set — admission only ran when + they were last written. + +Audit before upgrading, on each cluster that has a `KollectClusterScope`: + +```sh +kubectl get kollectclusterscopes.kollect.dev -o yaml | grep -A4 allowedGVKs +kubectl get kollectclustertargets.kollect.dev \ + -o custom-columns='NAME:.metadata.name,PROFILE:.spec.profileRef.name,PROFILE_NS:.spec.profileRef.namespace' +``` + +Cross-check each target's profile `targetGVK` (plus any `spec.resourceRules[].gvk`) against +`allowedGVKs`. Remediate by widening `allowedGVKs`, repointing `profileRef`, or retiring the target. +After upgrading, the affected targets are listed by: + +```sh +kubectl get kollectclustertargets.kollect.dev -o custom-columns=\ +'NAME:.metadata.name,DEGRADED:.status.conditions[?(@.type=="Degraded")].status,REASON:.status.conditions[?(@.type=="Degraded")].reason' +``` + +Widening the ceiling clears the condition on the next reconcile; nothing needs to be recreated. + ## GitOps and CI/CD For Argo CD, Flux, or similar: