Namespaces partition a single Kubernetes cluster into multiple virtual clusters. Combined with Kubernetes DNS-based service discovery, namespaces enable multi-tenant workloads, resource isolation, and clear service communication patterns.

Namespaces

A namespace provides a scope for names, resource quotas, and access control. Resources in one namespace are isolated from resources in another (except at the network level, by default).

Default Namespaces

Every GKE cluster starts with four namespaces:

NamespacePurpose
defaultFor resources with no namespace specified
kube-systemKubernetes system components (kube-dns, kube-proxy, metrics-server)
kube-publicPublic, readable data (cluster config)
kube-node-leaseNode heartbeat lease objects
# List all namespaces
kubectl get namespaces
 
# View resources in a specific namespace
kubectl get pods -n kube-system

Creating Namespaces

# Create a namespace
kubectl create namespace staging
 
# Create from YAML
kubectl apply -f - <<EOF
apiVersion: v1
kind: Namespace
metadata:
  name: staging
  labels:
    environment: staging
    team: backend
EOF

Working with Namespaces

# Set default namespace for current context
kubectl config set-context --current --namespace=staging
 
# Specify namespace per command
kubectl get pods -n staging
kubectl apply -f deployment.yaml -n staging
 
# List all pods across all namespaces
kubectl get pods -A

Tip: Set your default namespace to avoid accidentally deploying to default. Use kubectl config set-context --current --namespace=staging.

When to Use Namespaces

Use CaseExample
Multiple environmentsdev, staging, production
Multi-tenant applicationstenant-a, tenant-b
Team isolationbackend, frontend, data-team
Resource quotasLimit CPU/memory per environment
Access controlRestrict teams to their namespaces via RBAC

When NOT to Use Namespaces

  • Different clusters are needed (different security requirements, different cloud regions)
  • The overhead of namespace management outweighs the benefit (small teams, simple apps)
  • You need hard security isolation (namespaces are a logical boundary, not a security boundary)

Warning: Namespaces do NOT provide network isolation by default. Pods in any namespace can communicate with pods in any other namespace. Use Network Policies for network-level isolation.

Resource Quotas

Resource Quotas limit the total resources a namespace can consume:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: staging-quota
  namespace: staging
spec:
  hard:
    requests.cpu: "4"           # Max 4 vCPU total across all pods
    requests.memory: 8Gi        # Max 8 GB memory total
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "20"                  # Max 20 pods
    services: "10"              # Max 10 services
    persistentvolumeclaims: "5" # Max 5 PVCs
# Apply the quota
kubectl apply -f quota.yaml
 
# Check quota usage
kubectl get resourcequota -n staging
kubectl describe resourcequota staging-quota -n staging

Quota Enforcement

ScenarioWhat Happens
Pod exceeds quotaPod creation fails with Forbidden: exceeded quota
All quotas consumedNo new pods can be created until existing pods are deleted
Quota increasedPending pods can now be scheduled

Key Insight: When a ResourceQuota is applied to a namespace, every pod MUST define resources.requests. Pods without resource requests are rejected.

LimitRanges

LimitRanges set default, minimum, and maximum resource values per container in a namespace:

apiVersion: v1
kind: LimitRange
metadata:
  name: staging-limits
  namespace: staging
spec:
  limits:
    - type: Container
      default:              # Default limits if not specified
        cpu: "500m"
        memory: "256Mi"
      defaultRequest:       # Default requests if not specified
        cpu: "100m"
        memory: "64Mi"
      max:                  # Maximum allowed
        cpu: "2"
        memory: "2Gi"
      min:                  # Minimum allowed
        cpu: "50m"
        memory: "32Mi"
# Apply and check
kubectl apply -f limitrange.yaml -n staging
kubectl describe limitrange staging-limits -n staging
LimitRange FieldPurpose
defaultApplied as resources.limits when not specified
defaultRequestApplied as resources.requests when not specified
maxUpper bound — any value above is rejected
minLower bound — any value below is rejected

Network Policies

Network Policies control pod-to-pod communication at the network level. By default, all pods can communicate with all other pods.

Enable Network Policy in GKE

# For Standard clusters
gcloud container clusters update my-cluster \
  --zone=us-central1-a \
  --enable-network-policy
 
# Autopilot clusters have network policy available by default

Network Policy Example

# Deny all ingress to the staging namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
  namespace: staging
spec:
  podSelector: {}          # Select all pods
  policyTypes:
    - Ingress              # Block all incoming traffic
---
# Allow only frontend pods to talk to backend pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: staging
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: frontend
      ports:
        - port: 8080
# Apply and check
kubectl apply -f networkpolicy.yaml -n staging
kubectl get networkpolicies -n staging
Policy TypeControls
IngressIncoming traffic to selected pods
EgressOutgoing traffic from selected pods
BothFull network isolation

Key Insight: Network Policies are additive. Start with a deny-all policy, then add specific allow rules. This is the “default deny” security model.

Service Discovery (DNS)

Kubernetes provides built-in DNS records for every Service. In GKE, Standard clusters use kube-dns by default, while Autopilot clusters use Cloud DNS by default.

DNS Format

SERVICE_NAME.NAMESPACE.svc.cluster.local
FormatScopeExample
serviceSame namespacebackend
service.namespaceDifferent namespacebackend.staging
service.namespace.svcExplicit service subdomainbackend.staging.svc
service.namespace.svc.cluster.localFull FQDNbackend.staging.svc.cluster.local

How DNS Resolution Works

flowchart LR
    Pod["Pod in namespace: production"] -->|curl http://backend| DNS["Cluster DNS"]
    DNS -->|backend.production.svc.cluster.local| SVC["Service: backend"]
    SVC --> P1["Pod 10.4.0.12"]
    SVC --> P2["Pod 10.4.0.23"]
# Test DNS resolution from inside a pod
kubectl exec -it my-pod -- nslookup backend
kubectl exec -it my-pod -- nslookup backend.staging
kubectl exec -it my-pod -- nslookup backend.staging.svc.cluster.local

Cross-Namespace Communication

By default, pods can reach services in other namespaces using the full DNS name:

# From a pod in the 'production' namespace
curl http://backend.staging:8080/api
curl http://shared-services.shared:3306
# Reference a service in another namespace
# In a ConfigMap or environment variable
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  STAGING_API_URL: "http://backend.staging:8080"
  SHARED_DB_HOST: "database.shared-services"

Tip: Use short names within the same namespace and namespace-qualified names across namespaces. This makes the communication intent clear.

Headless Services and Pod DNS

For StatefulSets with headless services, each pod gets its own DNS name:

POD_NAME.SERVICE_NAME.NAMESPACE.svc.cluster.local
# Example: Kafka StatefulSet pods
kafka-0.kafka.default.svc.cluster.local
kafka-1.kafka.default.svc.cluster.local
kafka-2.kafka.default.svc.cluster.local

Namespace Strategy for GKE

Strategy 1: Environment-Based Namespaces

default/           # → avoid using
production/        # production workloads
staging/           # pre-production testing
development/       # development and testing

Strategy 2: Team-Based Namespaces

default/
frontend-team/     # frontend services
backend-team/      # backend services
data-team/         # data processing services
shared-services/   # databases, message queues, monitoring
production/
  ├── production-frontend/
  ├── production-backend/
  └── production-data/
staging/
  ├── staging-frontend/
  ├── staging-backend/
  └── staging-data/
StrategyProsCons
Environment-basedSimple, clear boundariesHard to isolate teams within an environment
Team-basedTeam autonomy, clear ownershipNo environment separation
CombinedFull isolationMore namespaces to manage

RBAC and Namespace Access

Control who can do what in each namespace:

# Role: what can be done in a namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: staging
rules:
  - apiGroups: ["", "apps"]
    resources: ["pods", "deployments", "services", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "delete"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]     # Read-only secrets
---
# RoleBinding: who gets the role
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developer-binding
  namespace: staging
subjects:
  - kind: User
    name: [email protected]
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io
RBAC ConceptScopeUse Case
Role + RoleBindingSingle namespaceTeam member access to their namespace
ClusterRole + ClusterRoleBindingCluster-wideAdmin access, viewing node info
ClusterRole + RoleBindingRoleBinding restricts to a namespaceSame ClusterRole used in multiple namespaces

Common Commands

CommandPurpose
kubectl get namespacesList all namespaces
kubectl create namespace NAMECreate a namespace
kubectl get pods -n NAMEList pods in a namespace
kubectl get pods -AList pods in all namespaces
kubectl config set-context --current --namespace=NAMESet default namespace
kubectl get resourcequota -n NAMEView resource quotas
kubectl get limitrange -n NAMEView limit ranges
kubectl get networkpolicies -n NAMEView network policies
kubectl exec -it POD -- nslookup SERVICETest DNS resolution
kubectl get events -n NAME --sort-by='.lastTimestamp'View namespace events

Common Pitfalls

PitfallConsequenceFix
Deploying everything to defaultNo isolation, hard to manageCreate dedicated namespaces per environment/team
No ResourceQuotasOne team can consume all cluster resourcesApply ResourceQuotas to every namespace
Forgetting -n flagOperating on wrong namespaceSet default namespace or always specify -n
Assuming namespace = network isolationAll pods can communicate across namespacesUse Network Policies for network isolation
No LimitRangePods without resources.requests rejected when quota existsApply LimitRange to auto-set defaults
Hardcoded namespace in YAMLManifests not portable across namespacesUse kubectl apply -f -n TARGET_NS or Kustomize

TL;DR

  • Namespaces partition a cluster for multi-tenancy, environments, and teams
  • Use ResourceQuotas to limit resources per namespace and LimitRanges to set defaults
  • Namespaces do NOT provide network isolation — use Network Policies for that
  • Kubernetes DNS enables automatic service discovery within and across namespaces
  • Use short DNS names within a namespace (backend) and namespace-qualified names across namespaces (backend.staging)
  • Start with a default-deny Network Policy and add specific allow rules
  • Use RBAC to control access to namespaces per team

Resources