Skip to content

Rendering to Many Namespaces#

One TemplateInstance can render a resource into every namespace that matches a rule, and keep the set correct as namespaces come and go. This is the fan-out pattern: a single provider object, N rendered resources, reconciled on change.

Scenario#

Bill wants a ConfigMap called cluster-info in every namespace labelled team, and a new team namespace should get one automatically.

1. List the namespaces as a parameter#

A parameter resolved from an objectFieldRef with no name runs in LIST mode: the operator lists the GVK, passes the whole list to the template, and watches it — so adding or removing a namespace re-renders the instance.

apiVersion: templates.v2.stakater.com/v2alpha1
kind: Template
metadata:
  name: cluster-info
  namespace: provider
spec:
  parameters:
    - name: namespaces
      required: true
  gotemplate: |
    {{- range $ns := .parameters.namespaces }}
    {{- $labels := default dict (index $ns.metadata "labels") }}
    {{- if and (hasKey $labels "team") (not (hasKey $ns.metadata "deletionTimestamp")) }}
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: cluster-info
      namespace: {{ $ns.metadata.name }}
    data:
      cluster: production
      team: {{ index $labels "team" | quote }}
    {{- end }}
    {{- end }}
  sync:
    mode: revert

The gotemplate filters in-template with hasKey/index rather than a server-side selector, so the one namespace watch covers every rule you might apply. Reading optional keys defensively matters here — see missing keys are an error.

The deletionTimestamp check skips a namespace that is being deleted. Without it, the rendered ConfigMap keeps the operator's finalizer, and the namespace stays in Terminating.

2. Bind it, listing all namespaces#

apiVersion: templates.v2.stakater.com/v2alpha1
kind: TemplateInstance
metadata:
  name: cluster-info
  namespace: provider
spec:
  templateRef:
    name: cluster-info
    namespace: provider
  parameters:
    - name: namespaces
      valueFrom:
        objectFieldRef:
          apiVersion: v1
          kind: Namespace
          jsonPath: .items        # no name -> LIST mode, returns every Namespace

3. Watch it track the cluster#

kubectl label namespace team-a team=a
kubectl label namespace team-b team=b
kubectl get configmap -A --field-selector metadata.name=cluster-info   # one per labelled namespace

kubectl create namespace team-c && kubectl label namespace team-c team=c
kubectl get configmap cluster-info -n team-c # appears without touching the TemplateInstance

RBAC and scope#

The rendered ConfigMaps carry an explicit metadata.namespace, so the operator writes them into those namespaces — the operator's service account needs configmaps write there, and the write is not limited by targetNamespaces. Review Security → where rendered resources land before fanning out across namespaces you do not control.

See also#