Tenancy Basics
This section covers the hands-on primitives of shared-cluster tenancy: creating namespaces and attaching the RBAC, quota, and network policy that turn a name scope into a real boundary.
Prerequisites
- Kubernetes 1.36.2 cluster access with
kubectlconfigured against a context you can create namespaces in. - A CNI that enforces NetworkPolicy (Calico, Cilium, or equivalent) for the network examples.
- Cluster-admin or delegated rights to create RBAC, ResourceQuota, and NetworkPolicy objects.
- Optional:
helm(Helm 3) if you template tenant bundles as charts.
kubectl version -o yaml | grep gitVersion
kubectl auth can-i create namespacesBasic Examples
1. Create a namespace
The namespace is the unit of tenancy. Everything else binds to it.
kubectl create namespace team-a
kubectl label namespace team-a tenant=team-a environment=prodkubectl create namespacemakes the scope; the object itself isolates nothing yet.- Labels like
tenantandenvironmentare how policies and NetworkPolicies later select this namespace. - Prefer declarative manifests in Git over imperative
createfor anything permanent. - Deleting a namespace cascades to every namespaced object inside it, so treat deletion as destructive.
2. Declare the namespace as YAML
Managing the namespace declaratively keeps it in version control and GitOps flows.
apiVersion: v1
kind: Namespace
metadata:
name: team-a
labels:
tenant: team-a
environment: prod
pod-security.kubernetes.io/enforce: baseline- The
pod-security.kubernetes.io/enforcelabel activates the built-in Pod Security admission for this namespace. - Valid values are
privileged,baseline, andrestricted; userestrictedfor untrusted tenants. - Labels applied here are the seam that connects the namespace to quota, RBAC, and network policy.
- Apply with
kubectl apply -f namespace.yamlso re-applies are idempotent.
3. Scope a ServiceAccount per tenant
Workloads authenticate to the API as a ServiceAccount, so each tenant should have its own.
apiVersion: v1
kind: ServiceAccount
metadata:
name: team-a-app
namespace: team-a- ServiceAccounts are namespaced, so
team-a-appis invisible to other tenants. - Pods reference it with
spec.serviceAccountNameto get a scoped identity. - Avoid running tenant workloads under the namespace
defaultServiceAccount. - Restrict
automountServiceAccountTokenwhere the workload does not call the API.
4. Grant tenant access with RBAC
RBAC decides who may act inside the namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-devs
namespace: team-a
subjects:
- kind: Group
name: team-a
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.io- A
RoleBindinggrants rights only within its own namespace, even when it references aClusterRole. - The built-in
editClusterRole is a sensible default for app teams;adminadds RBAC management. - Bind to groups, not individuals, so membership is managed in your identity provider.
- Verify with
kubectl auth can-i --as-group=team-a get pods -n team-a.
5. Cap consumption with a ResourceQuota
A quota stops a tenant from taking the whole cluster.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
requests.cpu: "8"
requests.memory: 16Gi
limits.cpu: "16"
limits.memory: 32Gi
pods: "50"- The quota caps the aggregate across all pods in the namespace, not any single pod.
- Once a compute quota exists, every pod must declare requests and limits or admission rejects it.
- Include object counts like
podsto bound churn and etcd pressure. - Check usage with
kubectl describe resourcequota team-a-quota -n team-a.
6. Set per-pod defaults with a LimitRange
A LimitRange fills in defaults so tenants are not forced to hand-set every value.
apiVersion: v1
kind: LimitRange
metadata:
name: team-a-limits
namespace: team-a
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Midefaultsupplies limits anddefaultRequestsupplies requests when a container omits them.- This lets a compute quota coexist with pods that do not spell out every field.
- Add
maxandminto enforce ceilings and floors per container. - LimitRange applies at admission, so it only affects newly created pods.
7. Inspect what a tenant can see
Confirm the boundary behaves as intended.
kubectl get all -n team-a
kubectl describe namespace team-a
kubectl auth can-i --list -n team-a --as-group=team-aget allshows only namespaced workload objects in that tenant.describe namespacesurfaces attached quotas and Pod Security labels.auth can-i --listis the fastest way to audit a tenant's effective permissions.
Intermediate Examples
8. Default-deny network isolation
By default all pods can talk across namespaces; a default-deny policy reverses that.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: team-a
spec:
podSelector: {}
policyTypes:
- Ingress- An empty
podSelectorselects every pod in the namespace. - Listing
Ingresswith noingressrules denies all inbound traffic. - Add explicit allow policies afterward for the flows you actually need.
- This has no effect unless your CNI enforces NetworkPolicy.
9. Allow traffic only from the same tenant
Follow default-deny with a narrow allow rule.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-same-namespace
namespace: team-a
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
tenant: team-a- Policies are additive, so this allow layers on top of the default-deny.
- The
namespaceSelectormatches thetenant: team-alabel set on the namespace earlier. - Keep cross-tenant allows explicit and few - each one widens the blast radius.
- Test reachability with a temporary
kubectl runpod andcurlbetween namespaces.
10. Bundle a tenant as one manifest
Ship namespace, quota, RBAC, and policy together so onboarding is repeatable.
kubectl apply -f tenant-team-a/
kubectl get resourcequota,limitrange,networkpolicy -n team-a- Storing the tenant bundle as a folder or Helm chart makes provisioning a single reviewable change.
- Apply order rarely matters since
kubectl applyreconciles the whole set. - Verify all four primitives landed before handing the namespace to the team.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).