MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Kubernetes Namespaces, RBAC, and Resource Quotas

Secure K8s clusters: namespaces for isolation, RBAC for access control, and resource quotas.

KubernetesSecurityRBACDevOps

By MinhVo

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.

Kubernetes security

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 --all thinking 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 workloads
  • payments-staging — Payment service staging environment
  • orders-development — Order service development environment
  • shared-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"

RBAC diagram

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 pods

Object 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:
  - NotTerminating

This 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:

EnvironmentCPU RequestsMemory RequestsPodsStorage
Development4 cores8Gi3050Gi
Staging8 cores16Gi50100Gi
Production32 cores64Gi200500Gi
Data Processing64 cores128Gi502Ti

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: Container

LimitRange 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.

Resource management

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.io

This 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.io

The 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-production

Security audit

Network 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: 53

Allowing 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: 5432

Use 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 PluginNetworkPolicy SupportAdditional Features
CalicoFullGlobalNetworkPolicy, FlowLogs, BGP
CiliumFull (extended)L7 policies, Hubble observability, eBPF
Weave NetFullEncryption, multicast
AntreaFullTraceflow, NetworkPolicy stats
FlannelNoneBasic 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:

StandardUse CaseKey Restrictions
privilegedSystem namespaces (kube-system)No restrictions
baselineDevelopment namespacesPrevents known privilege escalations (hostNetwork, hostPID, privileged containers)
restrictedProduction namespacesRequires 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: restricted

The 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.io

Hierarchical 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: payments

HNC 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-development

OPA/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.

Policy enforcement

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-sa

Step 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.com

Step 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:

  1. Created namespaces for each team and environment (15 namespaces total)
  2. Defined RBAC roles (developer, lead, admin) and bound them to teams
  3. Set resource quotas proportional to team size
  4. Applied NetworkPolicy default-deny with explicit cross-namespace rules
  5. 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:

  • restricted Pod 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 namespace

Cost 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

PitfallImpactSolution
Using default namespaceNo isolation, confusing resource namesCreate explicit namespaces
ClusterRoleBinding with cluster-adminFull access for all usersUse namespace-scoped Roles with RoleBindings
No resource quotasOne namespace consumes all resourcesSet quotas on every namespace
No LimitRangesPods without limits consume unbounded resourcesSet default limits via LimitRange
No Network PoliciesAny pod can communicate with any podApply restrictive NetworkPolicy by default
Binding to individual usersHard to manage when team changesBind to groups from identity provider
Not setting resource requestsPods get scheduled on wrong nodesAlways set requests and limits
list on secretsServiceAccount can dump all secretsUse resourceNames to restrict access
No Pod Security StandardsContainers run as root, privilegedApply restricted standard to production
No RBAC auditsPermissions accumulate over timeAudit quarterly, remove stale bindings

Best Practices for Production

  1. Never use the default namespace: Create explicit namespaces for every team and environment. The default namespace should remain empty.

  2. Follow the principle of least privilege: Grant only the permissions that users need. Start with no permissions and add them as needed.

  3. Use groups in RoleBindings: Bind roles to groups (from your identity provider) instead of individual users. This simplifies access management when team members change.

  4. Set resource quotas on every namespace: Without quotas, a single namespace can consume all cluster resources. Always set CPU, memory, and pod count limits.

  5. Use LimitRanges for default limits: LimitRanges ensure that every container has resource limits, even if the developer forgets to specify them.

  6. Apply Network Policies: By default, all pods can communicate with all other pods. Network policies restrict this to only necessary communication paths.

  7. 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.

  8. Regularly audit RBAC permissions: Use tools like kubectl-who-can or rbac-lookup to audit who has what permissions. Remove permissions that are no longer needed.

  9. Apply Pod Security Standards: Use the restricted standard for production namespaces. This prevents containers from running as root and restricts dangerous capabilities.

  10. 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.