CRD Design

2 min read

The first decision is scope. The provisioner creates namespaces — and Kubernetes does not allow a namespaced resource to own a cluster-scoped one. If NamespaceClaim were scope: Namespaced, its CR couldn’t hold OwnerReferences to the Namespace, ClusterRole, or ClusterRoleBinding it creates.

RBAC in the generated bundle
Because the provisioner creates ClusterRoles and ClusterRoleBindings, Orkestra automatically adds escalate and bind to the runtime ServiceAccount’s ClusterRole in the generated bundle. escalate allows creating a ClusterRole that grants permissions the Orkestra SA does not hold. bind allows creating a ClusterRoleBinding that references such a role. Both verbs are absent from operators that do not manage RBAC resources. See RBAC security for details.

Set scope: Cluster:

spec:
  group: provisioner.orkestra.io
  scope: Cluster
  names:
    plural: namespaceclaims
    kind: NamespaceClaim
    shortNames: [nsc]

This is the right default for any CRD that manages cross-namespace or cluster-scoped resources. A cluster-scoped CRD means cluster-scoped CRs — no namespace in metadata, applied with kubectl apply -f cr.yaml directly.


Fields

Keep the spec minimal. The CR author should express intent, not Kubernetes internals:

spec:
  targetNamespace: team-alpha   # namespace to create
  team: alpha                   # used for labels and resource naming
  owner: alpha-operator         # ServiceAccount that receives admin access
  ownerNamespace: default

01-explicit adds explicit quota fields. 02-profiles replaces them with a single tier name. Both map onto the same eight output resources.


Try it

ork init --pack use-cases/namespace-provisioner
cd namespace-provisioner
cat crd.yaml