When defining a CustomResourceDefinition you must set `scope` to either `Namespaced` or `Cluster`. What actually differs between the two, and what should drive the choice?
answer
- Namespaced: (namespace,name) unique, Role-grantable, dies with the namespace
- Cluster: globally unique, ClusterRole only, survives namespace deletion
- cluster-scoped dependent cannot be owned by a namespaced object → use finalizers
- paired kinds: Issuer / ClusterIssuer
- scope is immutable — recreating the CRD deletes all objects
basics
~20 sNamespaced objects live in a namespace, are named uniquely per namespace, and are covered by namespaced Roles and namespace deletion. Cluster objects are global, unique cluster-wide, and require ClusterRoles. Choose namespaced for tenant-owned resources, cluster for shared cluster-level configuration.
solid answer
~60 sScope changes four practical things: 1. **URL and identity** — namespaced objects live at `/namespaces/<ns>/<plural>` and are unique per namespace; cluster-scoped ones are unique cluster-wide, so two teams cannot both create `production`. 2. **RBAC granularity** — namespaced resources can be granted with a `Role` in one namespace, which is how you delegate to a team. A cluster-scoped resource requires a `ClusterRole`; there is no way to grant "only your ones" without an admission policy on top. 3. **Lifecycle** — deleting a namespace deletes the namespaced objects in it; cluster-scoped objects survive. 4. **Ownership** — `ownerReferences` cannot cross from a namespaced owner to a cluster-scoped dependent, and cross-namespace ownership is invalid; garbage collection treats such references as broken. The default should be `Namespaced`: it gives you multi-tenancy and least privilege for free. Reserve `Cluster` for genuinely cluster-wide singletons — the `ClusterIssuer`-style counterpart to a namespaced kind, node-level policy, or platform configuration owned by one team. **Scope is immutable**: changing it means deleting and recreating the CRD, which deletes every object.
code
yaml · 21 lines# tenant-facing kind
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: issuers.example.com
spec:
group: example.com
scope: Namespaced
names: { plural: issuers, singular: issuer, kind: Issuer }
versions: [ { name: v1, served: true, storage: true, schema: { openAPIV3Schema: { type: object } } } ]
---
# platform-owned, referenced from any namespace
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: clusterissuers.example.com
spec:
group: example.com
scope: Cluster
names: { plural: clusterissuers, singular: clusterissuer, kind: ClusterIssuer }
versions: [ { name: v1, served: true, storage: true, schema: { openAPIV3Schema: { type: object } } } ]go deeper
Say namespaced objects live inside a namespace and cluster-scoped ones are global, and name one example of each.
Cover uniqueness, Role versus ClusterRole, cleanup on namespace deletion, and that scope cannot be changed after creation.
Add the ownerReference scope rules and finalizer-based cleanup for cluster-scoped side objects, plus the paired Issuer/ClusterIssuer pattern.
Treat scope as a tenancy and delegation decision in the platform's API design: who owns instances, what RBAC boundary you want, the blast radius of global name uniqueness, and the cost of a destructive migration if you get it wrong.
## What the flag actually changes `spec.scope` on a CRD accepts `Namespaced` or `Cluster` and determines how the API server serves, stores and authorizes the resource. **Endpoint and identity.** Namespaced resources are served at `/apis/<group>/<version>/namespaces/<ns>/<plural>/<name>`, and the unique key is `(namespace, name)`. Cluster-scoped resources are served at `/apis/<group>/<version>/<plural>/<name>` with a globally unique `name`. Global uniqueness is a real constraint in multi-tenant clusters: everyone wants to call their object `default`, `prod` or the name of their service, and only one of them can. **RBAC.** Kubernetes' authorization model is built on this distinction. A `Role` + `RoleBinding` grants access to namespaced resources **within one namespace** — the standard way to let a team manage their own objects without seeing anyone else's. Cluster-scoped resources can only be granted via `ClusterRole` + `ClusterRoleBinding`, which is all-or-nothing for that kind (you can restrict to specific `resourceNames`, but that is static and clumsy). If you make a tenant-facing kind cluster-scoped, you have effectively decided that either everyone can touch everyone's objects, or a platform team mediates every change. **Lifecycle and cleanup.** Namespaced objects are removed when their namespace is deleted, which gives you free cleanup for per-team resources. Cluster-scoped objects are not, so uninstalling a tenant leaves them behind unless something explicitly removes them. **Ownership and garbage collection.** `ownerReferences` drive cascading deletion, and they have scope rules: a namespaced dependent may reference a cluster-scoped owner, and dependents must live in the same namespace as a namespaced owner. A **cluster-scoped object cannot be owned by a namespaced object** — set that reference and the garbage collector treats the owner as missing, which in practice can lead to the dependent being deleted or to a permanent event stream complaining about an invalid reference. This bites operators that create cluster-scoped side objects (a ClusterRole, a webhook configuration) on behalf of a namespaced custom resource: they must clean them up with a **finalizer** rather than relying on ownership. **Field semantics.** `metadata.namespace` must be empty for cluster-scoped objects; setting it is an error. `kubectl get <kind> -A` is meaningless for cluster-scoped kinds, and `kubectl get <kind> -n foo` silently ignores the namespace. ## Choosing The question to ask is: **who owns an instance of this thing, and at what granularity should access be delegated?** Choose **`Namespaced`** when: - instances belong to an application or team (`Database`, `KafkaTopic`, `Certificate`); - you want per-team RBAC, quotas and namespace-scoped tooling to apply; - you want cleanup on namespace deletion; - name collisions across teams are likely. Choose **`Cluster`** when: - the object configures the cluster itself, not a workload (`ClusterIssuer`, a node policy, a storage class-like default, a global policy object); - it must be referenced from many namespaces and duplicating it per namespace would be wrong; - it is administered by the platform team only, so coarse RBAC is acceptable; - it genuinely has cluster-wide uniqueness semantics. The pattern the ecosystem converged on is the **paired kinds** approach: a namespaced kind for tenant use and a cluster-scoped counterpart for shared defaults — `Issuer`/`ClusterIssuer`, `Role`/`ClusterRole`, `Policy`/`ClusterPolicy`. That gives delegation where you want it and sharing where you need it, at the cost of two schemas and controller code that handles both. ## Consequences you cannot undo `scope` is **immutable** once the CRD exists. Changing it requires deleting the CRD — which deletes every object of that kind — and recreating it, then restoring data. Treat the scope decision as part of API design that you commit to before publishing, not something to iterate on. If you are uncertain, `Namespaced` is the safer default: you can always add a cluster-scoped sibling kind later, but you cannot demote a cluster-scoped kind without a destructive migration. A final operational note: cluster-scoped custom resources are frequently included in cluster-wide tooling by default (backup tools, policy scanners, GitOps prune scopes). Making a high-cardinality, per-workload kind cluster-scoped puts thousands of objects into that global namespace, which degrades listing performance and clutters every cluster-wide view.
- Your namespaced custom resource's controller creates a cluster-scoped object on its behalf. How do you make sure it is cleaned up?Not with ownerReferences — a cluster-scoped dependent cannot be owned by a namespaced object, and the garbage collector treats such a reference as invalid. Add a finalizer to the custom resource, and on deletion have the controller delete the cluster-scoped object explicitly before removing the finalizer. That also gives you a place to handle failures and retries.
- Can you change a CRD from Namespaced to Cluster scope after it is in use?No — scope is immutable. You would have to delete the CRD, which deletes every object of that kind, and recreate it, then restore the data through some external backup and rewrite every manifest. This is why scope belongs to the up-front API design; when in doubt choose Namespaced and add a cluster-scoped sibling kind later if sharing turns out to be needed.
- Why is a cluster-scoped kind awkward for multi-tenant use?Names are globally unique, so teams collide on obvious names, and RBAC can only be granted through ClusterRoles, which are effectively all-or-nothing for that kind. There is no namespace boundary to scope quotas, network policy or cleanup to, and namespace deletion leaves the objects behind. Namespaced resources give delegation and lifecycle for free.
saying these in an interview costs you the question
- Assuming scope can be changed later with an edit to the CRD
- Setting an ownerReference from a namespaced custom resource to a cluster-scoped object and expecting garbage collection
- Choosing Cluster scope for per-application resources because it seemed simpler to list
- Believing a Role can grant access to a cluster-scoped resource in a single namespace
- Forgetting that cluster-scoped objects survive namespace deletion, leaving tenant leftovers