Skip to content

MEDIUM: k8s: add --namespace-label-selector with sticky per-namespace watches - #857

Open
fourhu wants to merge 1 commit into
haproxytech:masterfrom
fourhu:feature/issue-853
Open

fourhu wants to merge 1 commit into
haproxytech:masterfrom
fourhu:feature/issue-853

Conversation

@fourhu

@fourhu fourhu commented Sep 21, 2026

Copy link
Copy Markdown

Fixes #853.

Replaces #855.

Summary

Add --namespace-label-selector for clusters where namespace membership is controlled by labels instead of a fixed name list.

Matching a namespace for the first time starts per-namespace resource informers. Removing the label keeps those informers running and keeps the store up to date, but drops the namespace from the generated HAProxy configuration until it matches again. Relabeling does not start a new list/watch because the store is already current. Deleting a Namespace object stops its informers; a same-name replacement is accepted only after that drain completes.

--namespace-whitelist and --namespace-blacklist keep precedence. If either is set, the selector is ignored with a warning. With the selector unset, the existing informer path is unchanged.

The --configmap namespace is always watched (same idea as whitelist mode), so controller defaults stay readable without that namespace carrying the selector labels. Its Ingresses and Gateways still need the selector. Other namespaces the controller reads through the store (publish-service, default-backend, default certificate, custom-validation-rules) must match the selector.

Implementation

  • One informer session per selected namespace, with a process-wide epoch so events from a retired session are rejected.
  • A namespace enters HAProxy configuration only after its initial informer handlers have synced (Relevant).
  • --namespace-selector-ready-timeout (default 30s) bounds how long bootstrap waits for those sessions before the first config sync. 0 waits indefinitely. Namespaces that become ready later still enter configuration.
  • Late CRD registration, TCP resources, and Gateway parentRef targets honor the same eligibility rules.
  • A failed networking.k8s.io/v1 discovery probe is not cached as "API absent", so a transient error can recover on the next session start.

Validation

Unit coverage for session lifecycle, stale epochs, teardown, bootstrap ordering, late CRDs, TCP services, Gateway references and non-selector compatibility.

The local kind suite runs 20 sequential selector scenarios. It covers startup selection, label/unlabel/relabel, set-based selectors, two matching namespaces, dormant Ingress and Service/EndpointSlice updates, bootstrap churn, namespace deletion and same-name replacement, v1 and v3 TCP CRs, late CRD discovery, and whitelist/blacklist precedence.

Local checks on the final commit:

  • NAMESPACE_SELECTOR_ISOLATED_CRD_TEST=1 go test -tags=e2e_sequential ./deploy/tests/e2e/namespace-selector -v -count=1
  • go test -race ./pkg/k8s ./pkg/haproxy/api ./pkg/gateways -count=1
  • go test ./...

Trade-off

The selector path creates informer factories per selected namespace. API traffic and goroutine count grow with the number of namespaces that have ever matched, not only those that currently match: unlabeled namespaces keep their watches until the Namespace object is deleted. Namespace discovery watches all Namespace objects and matches locally. This option is intended for a bounded tenant set. Leaving the flag unset keeps the current shared-informer behavior.

@fourhu fourhu changed the title Add dynamic namespace selection by label MEDIUM: k8s: add --namespace-label-selector with sticky per-namespace watches Sep 21, 2026
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.

Proposal: dynamic namespace selection by label

1 participant