Introduction
A Kubernetes cluster without proper isolation is a security incident waiting to happen. When every team deploys to the same namespace, every developer has cluster-admin access, and there are no resource limits, the result is chaos: one team's runaway deployment consumes all cluster resources, a developer accidentally deletes production workloads, and debugging becomes impossible because hundreds of unrelated pods share the same namespace.
Kubernetes provides three complementary mechanisms for solving these problems: namespaces for logical isolation, Role-Based Access Control (RBAC) for authorization, and resource quotas for consumption limits. Together, these mechanisms let you build a multi-tenant cluster where teams can work independently without interfering with each other or compromising security.
Namespaces partition cluster resources into virtual sub-clusters. RBAC defines who can do what on which resources. Resource quotas prevent any single namespace from consuming more than its fair share of CPU, memory, and storage. Mastering these three mechanisms is essential for anyone operating Kubernetes in production.
This guide covers each mechanism in depth, from basic configuration to advanced patterns. We will explore real-world multi-tenant setups, discuss the security implications of different configurations, and walk through the YAML manifests that implement production-grade isolation.
Why Namespace Isolation Matters
Without namespaces, every resource in your cluster lives in a single flat space. A deployment called api in team A conflicts with a deployment called api in team B. A developer debugging a staging issue can accidentally delete production workloads. A misbehaving service consumes all available CPU, starving critical workloads.
Namespaces solve these problems by creating logical boundaries within a single physical cluster. Each namespace is an isolated environment with its own set of resources, access controls, and consumption limits. Think of namespaces as virtual clusters within your physical cluster—they share the same underlying infrastructure but operate independently.
The Cost of No Isolation
Consider a real-world scenario at a mid-size company with 8 development teams sharing a single Kubernetes cluster. Without namespaces:
- Resource contention: The data team's Spark job consumed 80% of cluster CPU, causing the payments service to crash during peak hours
- Accidental deletion: A junior developer ran
kubectl delete deployment --allthinking they were in their dev namespace, but they were in production - Security breach: A compromised service in the frontend namespace could access database credentials in the backend namespace because there were no network policies
- Debugging nightmare: With 400+ pods in the default namespace, finding logs for a specific service required grep-ing through thousands of unrelated log entries
The fix cost 2 weeks of engineering time. The incident it prevented would have cost far more.
Understanding Kubernetes Security: Core Concepts
Namespaces
Namespaces are the foundation of Kubernetes isolation. They provide a scope for names—two resources can have the same name if they are in different namespaces. More importantly, namespaces are the unit of access control and resource quota enforcement.
Kubernetes creates four namespaces by default: default (for user workloads), kube-system (for Kubernetes system components), kube-public (for publicly readable resources), and kube-node-lease (for node heartbeat data). Production clusters should never use the default namespace for application workloads.
# Create a namespace for the payments team
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
team: payments
environment: production
cost-center: fintech
annotations:
description: "Namespace for payment processing services"
contact: "payments-team@company.com"Naming Conventions
Effective namespace naming conventions make cluster management significantly easier. The most common pattern combines team and environment:
<team>-<environment>
Examples:
payments-production— Payment service production workloadspayments-staging— Payment service staging environmentorders-development— Order service development environmentshared-infra— Shared infrastructure (monitoring, logging, service mesh)platform-system— Platform tooling (CI/CD, internal dashboards)
This convention enables clear RBAC bindings (grant the payments team access to payments-* namespaces), straightforward resource quota allocation (production namespaces get more resources than development), and intuitive cost allocation (tag all payments-* namespaces to the fintech cost center).
RBAC (Role-Based Access Control)
RBAC controls who can perform what actions on which resources. It consists of four resource types:
- Role: Defines permissions within a namespace
- ClusterRole: Defines permissions cluster-wide
- RoleBinding: Grants a Role to a user or group within a namespace
- ClusterRoleBinding: Grants a ClusterRole to a user or group cluster-wide
# Role: Allow reading and writing pods in the payments namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: payments
name: pod-manager
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]RBAC vs. ABAC
Kubernetes supports two authorization modes: RBAC and ABAC (Attribute-Based Access Control). RBAC is the standard and recommended approach. ABAC is powerful but difficult to manage at scale because policy files must be present on every API server node and require a restart to update. RBAC policies are stored in Kubernetes itself and can be managed with kubectl and GitOps workflows. Always use RBAC unless you have a specific, well-understood reason for ABAC.
Resource Quotas
Resource quotas limit the total resource consumption within a namespace. They prevent any single namespace from monopolizing cluster resources:
apiVersion: v1
kind: ResourceQuota
metadata:
name: payments-quota
namespace: payments
spec:
hard:
requests.cpu: "8"
requests.memory: "16Gi"
limits.cpu: "16"
limits.memory: "32Gi"
pods: "50"
services: "20"
persistentvolumeclaims: "10"
requests.storage: "100Gi"Deep Dive: Resource Quotas
Resource quotas are the mechanism that prevents noisy neighbors. Without them, a single namespace can consume all cluster resources, starving every other workload. Understanding quota types, enforcement behavior, and design patterns is critical for production operations.
Types of Resource Quotas
Resource quotas enforce limits across multiple dimensions:
Compute quotas limit CPU and memory consumption:
spec:
hard:
requests.cpu: "16" # Total CPU requests across all pods
requests.memory: "32Gi" # Total memory requests across all pods
limits.cpu: "32" # Total CPU limits across all pods
limits.memory: "64Gi" # Total memory limits across all podsObject count quotas limit the number of Kubernetes objects:
spec:
hard:
pods: "100"
services: "30"
deployments.apps: "50"
configmaps: "100"
secrets: "100"
persistentvolumeclaims: "20"Storage quotas limit persistent volume consumption:
spec:
hard:
requests.storage: "500Gi"
persistentvolumeclaims: "20"Extended resource quotas for GPU and custom resources:
spec:
hard:
requests.nvidia.com/gpu: "8"
limits.nvidia.com/gpu: "8"Quota Scopes
Quota scopes let you apply different limits to different classes of pods within the same namespace:
apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-high-priority
namespace: payments-production
spec:
hard:
pods: "10"
scopes:
- PriorityClass
scopeSelector:
matchExpressions:
- scopeName: PriorityClass
operator: In
values: ["high"]
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-not-terminating
namespace: payments-production
spec:
hard:
pods: "80"
scopes:
- NotTerminatingThis pattern lets you reserve a small number of high-priority pods for critical workloads while the bulk of the namespace capacity goes to normal workloads. The NotTerminating scope excludes Jobs and CronJobs, which are expected to complete and should not count against your always-on workload budget.
How Quota Enforcement Works
When you create a ResourceQuota, Kubernetes admission controllers enforce it at object creation time. If a pod creation would exceed the quota, the API server rejects it with a 403 Forbidden error. This is a hard enforcement—there is no graceful degradation or queuing.
The critical behavior to understand is that quotas apply to requests, not limits. A pod with requests.cpu: 100m and limits.cpu: 1000m consumes 100m against the requests.cpu quota, not 1000m. This means you can set high limits for burstable workloads while keeping requests conservative to maximize quota utilization.
If a quota has requests.cpu set but no limits.cpu, pods that don't specify CPU requests will be rejected. Similarly, if a quota has requests.memory set, every pod must specify memory requests. This forces developers to be explicit about resource requirements, which improves scheduling quality.
Designing Quota Tiers
Different environments need different quota profiles:
| Environment | CPU Requests | Memory Requests | Pods | Storage |
|---|---|---|---|---|
| Development | 4 cores | 8Gi | 30 | 50Gi |
| Staging | 8 cores | 16Gi | 50 | 100Gi |
| Production | 32 cores | 64Gi | 200 | 500Gi |
| Data Processing | 64 cores | 128Gi | 50 | 2Ti |
Create these quotas as templates in your GitOps repository. When a team requests a new namespace, the platform team applies the appropriate template. This ensures consistency across namespaces and prevents ad-hoc quota decisions.
Deep Dive: LimitRanges
LimitRanges set default resource requests and limits for containers in a namespace. They serve as a safety net for pods that don't specify explicit resource requirements:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: payments
spec:
limits:
- default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
type: ContainerLimitRange Types
LimitRanges can enforce constraints at three levels:
Container-level limits constrain individual containers:
- type: Container
default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"Pod-level limits constrain the total resources across all containers in a pod:
- type: Pod
max:
cpu: "4"
memory: "8Gi"
min:
cpu: "100m"
memory: "128Mi"PersistentVolumeClaim-level limits constrain storage requests:
- type: PersistentVolumeClaim
max:
storage: "100Gi"
min:
storage: "1Gi"Interaction Between LimitRange and ResourceQuota
LimitRange and ResourceQuota work together but serve different purposes. ResourceQuota limits the total consumption across all pods in a namespace. LimitRange sets defaults and boundaries for individual pods.
When both are present, Kubernetes applies LimitRange defaults first (to pods without explicit resource specifications), then checks whether the pod's resource requests fit within the namespace's ResourceQuota. This means a LimitRange with generous defaults can cause ResourceQuota rejections if too many pods are created.
Design your LimitRange defaults conservatively. A LimitRange with default.cpu: 1 means every pod requests 1 core. If your namespace quota is 16 cores, you can only run 16 pods. Set defaults to the minimum viable amount and let developers request more for pods that need it.
Deep Dive: RBAC
RBAC is the primary authorization mechanism in Kubernetes. Misconfigured RBAC is one of the most common security vulnerabilities in production clusters. Understanding the full RBAC model is essential for secure cluster operations.
Roles vs. ClusterRoles
Roles are namespace-scoped. They define permissions within a single namespace. ClusterRoles are cluster-wide—they can grant access to cluster-scoped resources (nodes, namespaces, PersistentVolumes) and to resources across all namespaces.
# ClusterRole: Allow reading nodes (cluster-scoped resource)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]A common pattern is to use ClusterRoles for common permission sets and bind them to different namespaces with RoleBindings:
# ClusterRole defining a standard developer permission set
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-developer
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps", "secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets", "statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods/portforward"]
verbs: ["create"]
---
# Bind the ClusterRole to the payments team in the payments namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-developers
namespace: payments-production
subjects:
- kind: Group
name: payments-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: namespace-developer
apiGroup: rbac.authorization.k8s.ioThis pattern (ClusterRole + namespace RoleBinding) is the recommended approach for standard permission sets. It avoids duplicating Role definitions across namespaces while maintaining namespace-level access control.
ServiceAccounts
ServiceAccounts are identities for pods. When a pod runs, it can authenticate to the Kubernetes API using its ServiceAccount token. By default, pods use the default ServiceAccount in their namespace, which has minimal permissions.
Create dedicated ServiceAccounts for each application with only the permissions it needs:
apiVersion: v1
kind: ServiceAccount
metadata:
name: payments-api-sa
namespace: payments-production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: payments-api-role
namespace: payments-production
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["payments-api-config"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-api-binding
namespace: payments-production
subjects:
- kind: ServiceAccount
name: payments-api-sa
namespace: payments-production
roleRef:
kind: Role
name: payments-api-role
apiGroup: rbac.authorization.k8s.ioThe resourceNames field in the Role restricts access to a specific secret, not all secrets. This is critical for security—a ServiceAccount that can read all secrets in a namespace can access database credentials, API keys, and TLS certificates for every service in that namespace.
Common RBAC Anti-Patterns
Using cluster-admin for everything: The cluster-admin ClusterRole grants full access to every resource in the cluster. Never bind it to human users or ServiceAccounts except for the cluster administration team. Use kubectl auth can-i --list to verify that users have only the permissions they need.
Binding to individual users: When you bind a Role to a specific user (kind: User), you must update the binding every time team membership changes. Bind to groups instead—your identity provider (Okta, Azure AD, Google Workspace) manages group membership, and RBAC bindings remain stable.
Granting list and watch on secrets: A ServiceAccount with list secrets permissions can dump every secret in the namespace. This includes database passwords, API keys, and TLS certificates. Use resourceNames to restrict access to specific secrets.
Not auditing RBAC: RBAC permissions accumulate over time. Developers get temporary permissions for debugging that are never revoked. ServiceAccounts gain permissions as features are added but permissions are never removed when features are deprecated. Run RBAC audits quarterly.
RBAC Auditing Tools
# Check if a user can perform an action
kubectl auth can-i create deployments \
--namespace payments-production \
--as alice@company.com
# Check if a service account can list secrets
kubectl auth can-i list secrets \
--namespace payments-production \
--as system:serviceaccount:payments:payments-api-sa
# List all permissions for a user
kubectl get rolebindings,clusterrolebindings --all-namespaces -o json | \
jq -r '.items[] | select(.subjects[]?.name=="alice@company.com") |
.metadata.namespace + "/" + .roleRef.name'
# Using rbac-lookup (install: go install github.com/FairwindsOps/rbac-lookup@latest)
rbac-lookup alice@company.com --output wide
# Using kubectl-who-can
kubectl-who-can create deployments --namespace payments-productionNetwork Policies for Namespace Isolation
Resource quotas and RBAC control compute resources and API access, but network policies control pod-to-pod communication. By default, all pods in a Kubernetes cluster can communicate with each other across namespaces. Network policies allow you to restrict this communication, creating network-level isolation between namespaces.
Default-Deny Pattern
The most effective approach is to start with a default-deny policy for every namespace, then explicitly allow only the traffic patterns you need:
# Default deny all ingress and egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments-production
spec:
podSelector: {} # Applies to all pods in the namespace
policyTypes:
- Ingress
- Egress
---
# Allow DNS resolution (required for almost all workloads)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: payments-production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53Allowing Cross-Namespace Communication
With default-deny in place, every cross-namespace communication path must be explicitly declared. This prevents accidental coupling between teams and makes the network topology visible in version-controlled manifests:
# Allow the API gateway to reach payments-api
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-gateway-to-payments
namespace: payments-production
spec:
podSelector:
matchLabels:
app: payments-api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
team: gateway
podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 8080
---
# Allow payments-api to reach the database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-payments-to-db
namespace: shared-production
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
team: payments
ports:
- protocol: TCP
port: 5432Use namespace selectors with labels rather than IP ranges for maintainability—labels survive namespace recreation while IP addresses do not. Monitor NetworkPolicy violations using CNI plugins that support logging (like Calico's FlowLog or Cilium's Hubble). When a policy blocks traffic, the pod receives no error—the connection simply times out, making debugging difficult without proper observability.
CNI Plugin Requirements
Not all CNI plugins support NetworkPolicy. The default kubenet plugin on many managed Kubernetes services does not enforce NetworkPolicy. You need a CNI plugin that supports it:
| CNI Plugin | NetworkPolicy Support | Additional Features |
|---|---|---|
| Calico | Full | GlobalNetworkPolicy, FlowLogs, BGP |
| Cilium | Full (extended) | L7 policies, Hubble observability, eBPF |
| Weave Net | Full | Encryption, multicast |
| Antrea | Full | Traceflow, NetworkPolicy stats |
| Flannel | None | Basic overlay networking only |
If you are using a managed Kubernetes service (EKS, GKE, AKS), verify that your CNI plugin supports NetworkPolicy before relying on it for security.
Pod Security Standards
Beyond RBAC and resource quotas, Kubernetes Pod Security Standards define three levels of security restrictions enforced by the built-in Pod Security Admission controller:
| Standard | Use Case | Key Restrictions |
|---|---|---|
| privileged | System namespaces (kube-system) | No restrictions |
| baseline | Development namespaces | Prevents known privilege escalations (hostNetwork, hostPID, privileged containers) |
| restricted | Production namespaces | Requires non-root, read-only root filesystem, drops all capabilities, seccomp profile |
Apply these standards at the namespace level using labels:
apiVersion: v1
kind: Namespace
metadata:
name: payments-production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedThe enforce label rejects pods that violate the policy. The audit label logs violations to the audit log without blocking. The warn label shows a warning to the user applying the manifest but does not block. Use all three labels simultaneously—enforce for production safety, audit for monitoring, and warn for developer feedback.
The restricted standard is particularly valuable for production namespaces. It prevents containers from running as root, restricts volume types to configMap, emptyDir, persistentVolumeClaim, secret, and projected, drops all Linux capabilities except NET_BIND_SERVICE, and requires a seccomp profile. This significantly reduces the blast radius of a container compromise.
Multi-Tenancy Patterns
Hard Multi-Tenancy
Hard multi-tenancy means complete isolation between tenants. Each tenant gets their own namespace, RBAC roles, resource quotas, network policies, and Pod Security Standards. No tenant can access or affect another tenant's workloads.
This pattern is appropriate when tenants are different organizations or when regulatory requirements mandate complete isolation. The cost is higher operational complexity—each tenant namespace requires its own set of policies and quotas.
Soft Multi-Tenancy
Soft multi-tenancy means logical isolation with some shared resources. Tenants get separate namespaces and RBAC, but may share cluster-wide services (monitoring, logging, ingress controllers). Network policies allow some cross-namespace communication for shared services.
This pattern is appropriate for teams within the same organization. The shared services reduce operational overhead, and the relaxed network policies enable legitimate cross-team communication. The risk is that a compromised shared service can affect all tenants.
Namespace-as-a-Service
The most mature approach is Namespace-as-a-Service (NaaS), where a platform team provides self-service namespace provisioning. Developers request a namespace through an internal portal or API, and the platform automatically applies the appropriate RBAC, resource quotas, network policies, and Pod Security Standards.
Tools like Crossplane, ArgoCD ApplicationSets, and Hierarchical Namespace Controller (HNC) enable NaaS patterns. The platform team defines namespace templates, and the tooling ensures every provisioned namespace matches the template.
Step-by-Step: Complete Production Namespace Setup
Create a complete namespace with all security controls:
# 1. Namespace
apiVersion: v1
kind: Namespace
metadata:
name: payments-production
labels:
team: payments
environment: production
cost-center: fintech
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
---
# 2. Resource Quota
apiVersion: v1
kind: ResourceQuota
metadata:
name: payments-quota
namespace: payments-production
spec:
hard:
requests.cpu: "16"
requests.memory: "32Gi"
limits.cpu: "32"
limits.memory: "64Gi"
pods: "100"
services: "30"
persistentvolumeclaims: "20"
requests.storage: "500Gi"
count/deployments.apps: "50"
---
# 3. LimitRange
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: payments-production
spec:
limits:
- default:
cpu: "500m"
memory: "512Mi"
defaultRequest:
cpu: "100m"
memory: "128Mi"
max:
cpu: "2"
memory: "4Gi"
min:
cpu: "50m"
memory: "64Mi"
type: Container
---
# 4. Default deny network policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments-production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 5. Allow DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: payments-production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
---
# 6. ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: payments-sa
namespace: payments-production
---
# 7. Developer Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: payments-production
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/portforward"]
verbs: ["create"]
---
# 8. Developer RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-developers
namespace: payments-production
subjects:
- kind: Group
name: payments-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer
apiGroup: rbac.authorization.k8s.io
---
# 9. Admin Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: admin
namespace: payments-production
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
# 10. Admin RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-admins
namespace: payments-production
subjects:
- kind: Group
name: payments-leads
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: admin
apiGroup: rbac.authorization.k8s.ioHierarchical Namespaces
The Hierarchical Namespace Controller (HNC) from Kubernetes SIGs enables namespace hierarchies where child namespaces inherit policies from their parents. This is invaluable for organizations with complex team structures:
apiVersion: hnc.x-k8s.io/v1alpha2
kind: HierarchyConfiguration
metadata:
name: hierarchy
namespace: payments-production
spec:
parent: paymentsHNC propagates RBAC RoleBindings, NetworkPolicies, and ResourceQuotas from parent to child namespaces. When you create a new team namespace under an existing parent, the child automatically inherits the parent's security baseline.
# Install HNC
kubectl apply -f https://github.com/kubernetes-sigs/hierarchical-namespaces/releases/latest/download/hnc-manager.yaml
# View the namespace hierarchy
kubectl hns tree payments
# payments
# ├── payments-production
# ├── payments-staging
# └── payments-developmentOPA/Gatekeeper Policies
Open Policy Agent (OPA) Gatekeeper extends Kubernetes admission control with policy-as-code. Unlike built-in Pod Security Standards, Gatekeeper lets you define custom policies using the Rego language:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels:
- key: "team"
- key: "environment"Gatekeeper also supports audit mode, which reports policy violations on existing resources without blocking new admissions. This is essential for gradually enforcing policies on clusters with legacy workloads.
Debugging RBAC Issues
RBAC misconfigurations are among the most common Kubernetes issues. When a user or ServiceAccount gets a 403 Forbidden error, debugging requires understanding the full authorization chain.
Step-by-Step RBAC Debugging
Step 1: Verify the identity
# Who is making the request?
kubectl auth whoami
# For service accounts:
kubectl auth whoami --as system:serviceaccount:payments:payments-api-saStep 2: Check specific permissions
# Can this user create deployments in the payments namespace?
kubectl auth can-i create deployments \
--namespace payments-production \
--as alice@company.com
# What can this user do in the namespace?
kubectl auth can-i --list \
--namespace payments-production \
--as alice@company.comStep 3: Inspect bindings
# List all RoleBindings in the namespace
kubectl get rolebindings -n payments-production -o wide
# Check if a specific user is bound
kubectl get rolebindings -n payments-production -o json | \
jq '.items[] | select(.subjects[]?.name=="alice@company.com")'Step 4: Check for deny rules
RBAC has no deny rules—it's purely additive. If a user is getting denied, it means no binding grants the permission. Common causes:
- The binding references a non-existent Role
- The binding's namespace doesn't match the resource's namespace
- The user's group membership doesn't match the binding's subject
- The ServiceAccount token has expired (check pod restart)
Step 5: Enable audit logging
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
users: ["system:serviceaccount:payments:payments-api-sa"]
verbs: ["create", "update", "delete"]Audit logs show every API request with the authenticated user, the resource, and the verb. This is invaluable for understanding why a request was denied.
Real-World Case Studies
Case Study 1: Startup Scaling to Multi-Team
A fintech startup with 3 engineers started with a single Kubernetes cluster using the default namespace. When the team grew to 20 engineers across 5 teams, they faced constant issues: naming collisions, accidental cross-team deployments, and a runaway cron job that consumed all cluster resources.
The migration took 2 weeks:
- Created namespaces for each team and environment (15 namespaces total)
- Defined RBAC roles (developer, lead, admin) and bound them to teams
- Set resource quotas proportional to team size
- Applied NetworkPolicy default-deny with explicit cross-namespace rules
- Added LimitRanges with conservative defaults
The result: zero naming collisions, no more resource contention, and developers could only deploy to their own namespaces. The migration paid for itself within a month through reduced incidents and faster debugging.
Case Study 2: Enterprise Compliance
A healthcare company needed to comply with HIPAA requirements for their Kubernetes cluster. They used namespaces to isolate PHI (Protected Health Information) workloads from non-PHI workloads. PHI namespaces had:
restrictedPod Security Standards- Network policies that only allowed traffic from known sources
- RBAC that limited access to compliance-trained engineers
- Audit logging enabled for every API request
- Resource quotas with dedicated node pools for PHI workloads
Non-PHI namespaces had relaxed policies for development and experimentation. The clear namespace boundary made it straightforward to demonstrate compliance during audits.
Case Study 3: Cost Allocation
A SaaS company with 12 product teams used namespace labels for cost allocation:
metadata:
labels:
cost-center: "CC-1234"
team: "payments"
product: "payment-gateway"They used Kubecost to track resource consumption per namespace and generated monthly cost reports by cost center. This visibility led to a 30% reduction in cloud spending as teams optimized their resource requests based on actual usage data.
Cost Allocation with Namespaces
Namespaces enable precise cost allocation across teams and products. By tagging namespaces with cost-center labels and tracking resource consumption per namespace, organizations can attribute infrastructure costs to specific teams.
Tools like Kubecost, OpenCost, and Kubernetes Resource Report provide namespace-level cost visibility. They track CPU, memory, storage, and network costs per namespace, broken down by pod, deployment, and label.
# Install Kubecost
helm install kubecost kubecost/cost-analyzer \
--namespace kubecost \
--create-namespace \
--set kubecostToken="your-token"
# View costs by namespace
kubectl port-forward -n kubecost svc/kubecost-cost-analyzer 9090:9090
# Open http://localhost:9090 and filter by namespaceCost allocation data drives optimization. When teams see their namespace costs, they naturally optimize resource requests, delete unused deployments, and right-size their workloads. The visibility itself is the intervention.
Common Pitfalls and Solutions
| Pitfall | Impact | Solution |
|---|---|---|
Using default namespace | No isolation, confusing resource names | Create explicit namespaces |
| ClusterRoleBinding with cluster-admin | Full access for all users | Use namespace-scoped Roles with RoleBindings |
| No resource quotas | One namespace consumes all resources | Set quotas on every namespace |
| No LimitRanges | Pods without limits consume unbounded resources | Set default limits via LimitRange |
| No Network Policies | Any pod can communicate with any pod | Apply restrictive NetworkPolicy by default |
| Binding to individual users | Hard to manage when team changes | Bind to groups from identity provider |
| Not setting resource requests | Pods get scheduled on wrong nodes | Always set requests and limits |
list on secrets | ServiceAccount can dump all secrets | Use resourceNames to restrict access |
| No Pod Security Standards | Containers run as root, privileged | Apply restricted standard to production |
| No RBAC audits | Permissions accumulate over time | Audit quarterly, remove stale bindings |
Best Practices for Production
-
Never use the
defaultnamespace: Create explicit namespaces for every team and environment. Thedefaultnamespace should remain empty. -
Follow the principle of least privilege: Grant only the permissions that users need. Start with no permissions and add them as needed.
-
Use groups in RoleBindings: Bind roles to groups (from your identity provider) instead of individual users. This simplifies access management when team members change.
-
Set resource quotas on every namespace: Without quotas, a single namespace can consume all cluster resources. Always set CPU, memory, and pod count limits.
-
Use LimitRanges for default limits: LimitRanges ensure that every container has resource limits, even if the developer forgets to specify them.
-
Apply Network Policies: By default, all pods can communicate with all other pods. Network policies restrict this to only necessary communication paths.
-
Use Service Accounts with minimal permissions: Each application should have its own service account with only the permissions it needs. Never mount the default service account with cluster-admin permissions.
-
Regularly audit RBAC permissions: Use tools like
kubectl-who-canorrbac-lookupto audit who has what permissions. Remove permissions that are no longer needed. -
Apply Pod Security Standards: Use the
restrictedstandard for production namespaces. This prevents containers from running as root and restricts dangerous capabilities. -
Version control all policies: Store RBAC roles, resource quotas, network policies, and namespace definitions in Git. Use GitOps tools (ArgoCD, Flux) to apply them automatically.
Conclusion
Kubernetes namespaces, RBAC, and resource quotas are the foundation of cluster security and multi-tenancy. Namespaces provide logical isolation. RBAC enforces the principle of least privilege. Resource quotas prevent resource monopolization. LimitRanges ensure every container has limits. Network Policies restrict communication. Pod Security Standards prevent privilege escalation.
Together, these mechanisms enable safe multi-tenancy where teams can work independently on the same cluster without interfering with each other or compromising security. If you are running Kubernetes in production with multiple teams, implementing these mechanisms is not optional—it is essential.
Start with namespaces and RBAC, add resource quotas and limit ranges, apply network policies, and progressively tighten Pod Security Standards as your cluster matures. The investment in proper isolation pays dividends in security, reliability, and operational efficiency.