Kubernetes Namespaces: Multi-Tenant Best Practices

Kubernetes namespaces provide a mechanism for isolating groups of resources within a single cluster. In a multi-tenant environment — whether that means multiple teams, multiple applications, or multiple environments (dev/staging/production) — namespaces are the foundational building block for implementing access control, resource limits, network isolation, and operational boundaries. Understanding how to use namespaces effectively is one of the most important skills for operating a shared Kubernetes cluster at scale.

Namespace Basics and Scope

Namespaces divide cluster resources into virtual sub-clusters. Most Kubernetes objects are namespace-scoped: Pods, Services, Deployments, ConfigMaps, Secrets, ServiceAccounts, and PersistentVolumeClaims all live within a namespace. A few resources are cluster-scoped and exist outside any namespace: Nodes, PersistentVolumes, StorageClasses, ClusterRoles, and ClusterRoleBindings.

Kubernetes creates four system namespaces by default:

  • default — the namespace for objects with no specified namespace. Avoid deploying production workloads here.
  • kube-system — Kubernetes system components: API server, controller manager, CoreDNS, kube-proxy
  • kube-public — publicly readable resources; contains the cluster-info ConfigMap
  • kube-node-lease — node heartbeat Lease objects used by the node lifecycle controller
# List all namespaces
kubectl get namespaces

# Create a new namespace
kubectl create namespace team-payments

# Or declaratively
kubectl apply -f - <

Naming Conventions and Lifecycle

Consistent namespace naming makes cluster management predictable. Common patterns include:

  • Team-based: team-payments, team-identity, team-platform
  • Environment-based: payments-dev, payments-staging, payments-production
  • Service-based: svc-api-gateway, svc-notification

Apply standard labels to every namespace for policy enforcement and cost allocation:

apiVersion: v1
kind: Namespace
metadata:
  name: team-payments-production
  labels:
    team: payments
    environment: production
    cost-center: "cc-1042"
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/warn: restricted
  annotations:
    owner: payments-team@company.com
    slack-channel: "#payments-oncall"
    runbook: "https://wiki.company.com/payments/runbook"

Namespace deletion is permanent and cascades to all resources within. Protect critical namespaces from accidental deletion with a finalizer or admission webhook that blocks deletion of namespaces labelled protected: true.

RBAC for Namespace Isolation

Role-based access control (RBAC) is the primary mechanism for enforcing who can do what within a namespace. Grant namespace-scoped permissions with Role and RoleBinding, not ClusterRole.

# Developer role — can read/write most resources but not secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: team-payments
rules:
  - apiGroups: ["", "apps", "batch"]
    resources: ["pods", "services", "deployments", "jobs", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["pods/log", "pods/exec"]
    verbs: ["get", "list"]
  # Read-only on secrets
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]

---
# Bind the role to a group (all members of the payments team)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developer-binding
  namespace: team-payments
subjects:
  - kind: Group
    name: payments-developers    # maps to OIDC group claim
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io
Principle of least privilege: Never grant ClusterAdmin to application teams. Instead, grant Role bindings scoped to their namespace(s). Use Groups rather than individual Users in bindings so that team membership changes in your IdP are automatically reflected in Kubernetes without updating YAML files.

Network Isolation with Network Policies

By default, all pods in a Kubernetes cluster can communicate with all other pods regardless of namespace. Apply a default-deny NetworkPolicy to each namespace and then explicitly allow the required communication paths.

# Default deny all ingress and egress within the namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: team-payments
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

---
# Allow DNS egress (required for all pods to resolve service names)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: team-payments
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - ports:
        - port: 53
          protocol: UDP
        - port: 53
          protocol: TCP

---
# Allow payments API to reach the database namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-payments-to-db
  namespace: team-payments
spec:
  podSelector:
    matchLabels:
      app: payments-api
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              team: database
      ports:
        - port: 5432

Resource Quotas per Namespace

ResourceQuota prevents a single namespace from consuming all cluster resources, protecting other tenants from noisy-neighbour effects.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-payments-quota
  namespace: team-payments
spec:
  hard:
    # Compute
    requests.cpu: "8"
    requests.memory: 16Gi
    limits.cpu: "16"
    limits.memory: 32Gi
    # Storage
    requests.storage: 200Gi
    persistentvolumeclaims: "10"
    # Object counts
    pods: "50"
    services: "20"
    secrets: "50"
    configmaps: "30"
    # LoadBalancer services (expensive on cloud)
    services.loadbalancers: "2"
# Check current quota usage
kubectl describe resourcequota team-payments-quota -n team-payments

LimitRanges for Default Constraints

LimitRange sets default resource requests and limits for pods that do not specify them, and enforces minimum/maximum bounds. Without LimitRange, a pod with no resource requests can schedule on any node and consume unlimited resources.

apiVersion: v1
kind: LimitRange
metadata:
  name: team-payments-limits
  namespace: team-payments
spec:
  limits:
    - type: Container
      default:          # applied when no limits specified
        cpu: 500m
        memory: 512Mi
      defaultRequest:   # applied when no requests specified
        cpu: 100m
        memory: 128Mi
      max:              # container cannot exceed these
        cpu: "4"
        memory: 4Gi
      min:              # container must request at least these
        cpu: 50m
        memory: 64Mi
    - type: Pod
      max:
        cpu: "8"
        memory: 8Gi
    - type: PersistentVolumeClaim
      max:
        storage: 50Gi
      min:
        storage: 1Gi

Namespace Templates with Helm

Creating a new team namespace involves deploying a Namespace, ResourceQuota, LimitRange, NetworkPolicies, Roles, and RoleBindings consistently. Package this as a Helm chart to ensure every new namespace gets the same baseline security and resource controls.

# Helm chart structure for namespace template
namespace-template/
├── Chart.yaml
├── values.yaml
└── templates/
    ├── namespace.yaml
    ├── resourcequota.yaml
    ├── limitrange.yaml
    ├── networkpolicies.yaml
    ├── role-developer.yaml
    └── rolebinding-developer.yaml
# Deploy a new namespace for team-identity
helm upgrade --install team-identity ./namespace-template \
  --set team.name=identity \
  --set team.environment=production \
  --set team.costCenter=cc-1055 \
  --set team.slackChannel="#identity-oncall" \
  --set quota.cpuRequests=4 \
  --set quota.memoryRequests=8Gi \
  --set rbac.developerGroup=identity-developers

Multi-Tenancy Patterns and Limitations

Kubernetes namespaces provide soft multi-tenancy: isolation through RBAC and network policies, but sharing the same kernel, node resources, and control plane. This is appropriate for trusted tenants (different teams in the same company) but insufficient for untrusted tenants (customers running arbitrary workloads).

For stricter isolation, consider these patterns:

  • Virtual clusters (vcluster): Run a lightweight Kubernetes API server inside a namespace. Each tenant gets their own API server with full admin access while sharing the underlying node pool. Strong isolation for devops/platform teams.
  • Node isolation: Dedicate specific nodes to specific namespaces using node selectors and taints. Prevents cross-tenant resource contention at the kernel level.
  • Separate clusters: For truly untrusted tenants or strict regulatory separation, separate clusters provide the strongest isolation boundary. Tools like Cluster API or cloud provider managed clusters make multi-cluster management feasible.
Hierarchical Namespaces: The Hierarchical Namespace Controller (HNC) from the Kubernetes SIG-multitenancy adds parent-child namespace relationships, allowing policy propagation from parent to child namespaces. This is useful for modelling org hierarchies like company > division > team > environment.