Llevamos todo el módulo escribiendo rutas-norte-dev en cada manifiesto y en cada comando, sin habernos detenido a explicar qué es exactamente ese namespace ni qué nos da. Y al final de la lección anterior apareció una propiedad muy interesante: la configuración de api-reservas apunta a redis-cache y a postgres-reservas por su nombre corto, sin mencionar el entorno, porque el DNS lo resuelve dentro del namespace donde corre el pod. Esa propiedad es la que permite desplegar el mismo manifiesto tres veces y obtener tres plataformas independientes. En esta lección montamos los tres entornos de Rutas Norte —rutas-norte-dev, rutas-norte-pre y rutas-norte-pro—, veremos qué aísla un namespace y qué no aísla en absoluto (que es la parte que más incidentes provoca), distinguiremos los recursos con y sin namespace, conoceremos los namespaces del sistema y por qué nunca se despliega en default, trabajaremos cómodamente con -n y con el contexto, resolveremos nombres DNS entre namespaces, y terminaremos con una advertencia muy seria sobre lo que se lleva por delante un kubectl delete namespace.

Contenido

  1. Qué es un namespace y qué aísla
  2. Recursos con y sin namespace
  3. Los namespaces del sistema y por qué no se despliega en default
  4. Los tres entornos de Rutas Norte
  5. El mismo manifiesto en varios entornos
  6. Trabajar con namespaces sin volverse loco
  7. DNS entre namespaces
  8. Qué NO garantiza un namespace
  9. Borrar un namespace: la orden más peligrosa de kubectl
  10. Namespaces frente a clústeres separados

  1. Qué es un namespace y qué aísla

Un namespace es una partición lógica del clúster que agrupa recursos. La definición operativa, la que de verdad sirve:

Un namespace es, sobre todo, un espacio de nombres: dentro de él, el nombre de cada objeto debe ser único; fuera, puede repetirse libremente.

Eso permite que existan simultáneamente tres objetos llamados api-reservas, uno en cada entorno, sin colisionar. Y a partir de ahí se derivan sus demás usos.

Lo que un namespace aporta:

Función Qué significa en la práctica
Unicidad de nombres api-reservas puede existir en dev, pre y pro a la vez
Ámbito de las consultas kubectl get pods solo muestra los del namespace actual
Punto de anclaje de las cuotas Un ResourceQuota limita el consumo total de un namespace (03-04)
Unidad de permisos de RBAC Un Role concede permisos dentro de un namespace (08-01)
Sujeto de las políticas de red Una NetworkPolicy puede seleccionar por namespace (04-06)
Ámbito del DNS corto redis-cache resuelve al Service del namespace del pod que pregunta
Ámbito de los selectores Un Service o un ReplicaSet solo ven pods de su propio namespace
Borrado en bloque Borrar el namespace elimina todo lo que contiene

Y lo que no aporta, que veremos en detalle en el apartado 8: no aísla la red, no limita recursos por sí solo, no impide el acceso y no separa los nodos.

flowchart TB
    subgraph CL["Clúster minikube · perfil rutas-norte"]
        subgraph DEV["namespace rutas-norte-dev"]
            D1["Deployment api-reservas"]
            D2["Service api-reservas"]
            D3["Deployment postgres-reservas"]
        end
        subgraph PRE["namespace rutas-norte-pre"]
            E1["Deployment api-reservas"]
            E2["Service api-reservas"]
            E3["Deployment postgres-reservas"]
        end
        subgraph SYS["namespace kube-system"]
            S1["CoreDNS"]
            S2["kube-proxy"]
        end
        NODOS["Nodos, PersistentVolumes, StorageClasses<br/>(sin namespace: comunes a todo el clúster)"]
    end

Fíjate en el detalle clave del diagrama: los objetos con el mismo nombre conviven en namespaces distintos, pero los nodos son compartidos. Un pod de rutas-norte-dev y otro de rutas-norte-pre pueden acabar en la misma máquina.

  1. Recursos con y sin namespace

No todos los objetos de Kubernetes viven dentro de un namespace. Los hay que pertenecen al clúster entero, y confundirlos genera errores desconcertantes.

La forma de saberlo con certeza, sin memorizar nada:

kubectl api-resources --namespaced=true | head -15
NAME                    SHORTNAMES   APIVERSION    NAMESPACED   KIND
bindings                             v1            true         Binding
configmaps              cm           v1            true         ConfigMap
endpoints               ep           v1            true         Endpoints
events                  ev           v1            true         Event
limitranges             limits       v1            true         LimitRange
persistentvolumeclaims  pvc          v1            true         PersistentVolumeClaim
pods                    po           v1            true         Pod
replicationcontrollers  rc           v1            true         ReplicationController
resourcequotas          quota        v1            true         ResourceQuota
secrets                              v1            true         Secret
serviceaccounts         sa           v1            true         ServiceAccount
services                svc          v1            true         Service
deployments             deploy       apps/v1       true         Deployment
replicasets             rs           apps/v1       true         ReplicaSet
statefulsets            sts          apps/v1       true         StatefulSet
kubectl api-resources --namespaced=false
NAME                        SHORTNAMES   APIVERSION                        NAMESPACED   KIND
componentstatuses           cs           v1                                false        ComponentStatus
namespaces                  ns           v1                                false        Namespace
nodes                       no           v1                                false        Node
persistentvolumes           pv           v1                                false        PersistentVolume
mutatingwebhookconfigurations            admissionregistration.k8s.io/v1   false        MutatingWebhookConfiguration
customresourcedefinitions   crd          apiextensions.k8s.io/v1           false        CustomResourceDefinition
apiservices                              apiregistration.k8s.io/v1         false        APIService
tokenreviews                             authentication.k8s.io/v1          false        TokenReview
clusterrolebindings                      rbac.authorization.k8s.io/v1      false        ClusterRoleBinding
clusterroles                             rbac.authorization.k8s.io/v1      false        ClusterRole
priorityclasses             pc           scheduling.k8s.io/v1              false        PriorityClass
csidrivers                               storage.k8s.io/v1                 false        CSIDriver
storageclasses              sc           storage.k8s.io/v1                 false        StorageClass
volumeattachments                        storage.k8s.io/v1                 false        VolumeAttachment

La lógica detrás del reparto es coherente: lo que representa infraestructura física o configuración global del clúster no lleva namespace; lo que representa carga de trabajo o configuración de aplicación, sí.

Con namespace Sin namespace
Pod, Deployment, ReplicaSet, StatefulSet, DaemonSet, Job, CronJob Node
Service, Endpoints, EndpointSlice, Ingress, NetworkPolicy Namespace
ConfigMap, Secret, ServiceAccount PersistentVolume, StorageClass, CSIDriver
PersistentVolumeClaim ClusterRole, ClusterRoleBinding
Role, RoleBinding CustomResourceDefinition, PriorityClass
ResourceQuota, LimitRange, HorizontalPodAutoscaler IngressClass

Dos parejas merecen atención porque son fuente constante de confusión:

  • PersistentVolume (sin namespace) frente a PersistentVolumeClaim (con namespace). El disco es un recurso del clúster; la reclamación de ese disco pertenece a una aplicación concreta en un namespace. Módulo 5.
  • Role/RoleBinding (con namespace) frente a ClusterRole/ClusterRoleBinding (sin namespace). Los primeros conceden permisos dentro de un namespace; los segundos, en todo el clúster. Módulo 8.

Un error práctico derivado de esto:

kubectl get nodes -n rutas-norte-dev
NAME          STATUS   ROLES           AGE   VERSION
rutas-norte   Ready    control-plane   3d    v1.30.0

El -n se ha ignorado en silencio. Los nodos no tienen namespace, así que el flag no filtra nada. No es un error, pero puede hacerte creer que estás viendo algo filtrado cuando no lo estás.

  1. Los namespaces del sistema y por qué no se despliega en default

Todo clúster nace con cuatro namespaces:

kubectl get namespaces
NAME              STATUS   AGE
default           Active   3d
kube-node-lease   Active   3d
kube-public       Active   3d
kube-system       Active   3d
rutas-norte-dev   Active   2d
Namespace Contenido ¿Debes tocarlo?
default Vacío al principio. Es donde van los objetos si no indicas namespace No despliegues aquí
kube-system Los componentes del propio Kubernetes: CoreDNS, kube-proxy, controladores CSI, addons Nunca despliegues aquí; mirar sí, tocar no
kube-public Legible sin autenticar; contiene un ConfigMap con datos públicos del clúster Casi nunca se usa
kube-node-lease Un objeto Lease por nodo, que el kubelet renueva como latido de vida Nunca

Echa un vistazo a kube-system para ver el clúster por dentro:

kubectl get pods -n kube-system
NAME                                  READY   STATUS    RESTARTS   AGE
coredns-668d6bf9bc-x8k2m              1/1     Running   0          3d
etcd-rutas-norte                      1/1     Running   0          3d
kube-apiserver-rutas-norte            1/1     Running   0          3d
kube-controller-manager-rutas-norte   1/1     Running   0          3d
kube-proxy-7wfnz                      1/1     Running   0          3d
kube-scheduler-rutas-norte            1/1     Running   0          3d
metrics-server-6d94bc8694-p4rzt       1/1     Running   0          3d
storage-provisioner                   1/1     Running   0          3d

Ahí están, corriendo como pods, todos los componentes que estudiaste en Arquitectura de Kubernetes. El plano de control de tu minikube es visible y palpable.

kube-node-lease explica algo que quizá te preguntaste: cómo sabe el plano de control si un nodo sigue vivo.

kubectl get leases -n kube-node-lease
NAME          HOLDER        AGE
rutas-norte   rutas-norte   3d

El kubelet renueva ese objeto cada pocos segundos. Si deja de hacerlo, el nodo se marca NotReady.

Por qué no se despliega en default

Es el namespace que Kubernetes usa cuando no dices nada, y precisamente por eso es un mal sitio para trabajar:

  1. Es el vertedero del clúster. Todo lo que alguien aplique sin -n acaba ahí. En pocos meses es un cajón de sastre donde nadie sabe qué es de quién.
  2. No se puede borrar. Los namespaces del sistema son indestructibles, así que no puedes limpiarlo de un golpe como harías con rutas-norte-dev.
  3. Los accidentes son fáciles. Un kubectl delete deploy --all sin -n, ejecutado creyendo estar en otro sitio, se lleva por delante lo que haya en default.
  4. Imposibilita el aislamiento. Si todo está en default, no puedes aplicar cuotas por equipo, ni RBAC por entorno, ni políticas de red diferenciadas.
  5. Impide reutilizar nombres. Con todo en un mismo namespace, api-reservas de desarrollo y de producción no pueden coexistir.

La regla del proyecto es explícita:

En Rutas Norte, default está prohibido. Todo objeto vive en un namespace nombrado, declarado en su propio manifiesto.

  1. Los tres entornos de Rutas Norte

Ha llegado el momento de crear la estructura completa. Ya conoces rutas-norte-dev del módulo 1; le añadimos preproducción y producción, de forma declarativa y en un solo fichero.

En YAML, --- separa varios documentos dentro de un mismo fichero. Es la forma habitual de agrupar objetos relacionados:

# k8s/base/namespaces.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: rutas-norte-dev
  labels:
    app.kubernetes.io/part-of: rutas-norte
    entorno: dev
---
apiVersion: v1
kind: Namespace
metadata:
  name: rutas-norte-pre
  labels:
    app.kubernetes.io/part-of: rutas-norte
    entorno: pre
---
apiVersion: v1
kind: Namespace
metadata:
  name: rutas-norte-pro
  labels:
    app.kubernetes.io/part-of: rutas-norte
    entorno: pro
  annotations:
    rutasnorte.example/responsable: [email protected]
    rutasnorte.example/aviso: "Entorno productivo. Cambios solo por pipeline."
kubectl apply -f k8s/base/namespaces.yaml
kubectl get namespaces -l app.kubernetes.io/part-of=rutas-norte
namespace/rutas-norte-dev configured
namespace/rutas-norte-pre created
namespace/rutas-norte-pro created

NAME              STATUS   AGE
rutas-norte-dev   Active   2d
rutas-norte-pre   Active   4s
rutas-norte-pro   Active   4s

Fíjate en dos decisiones deliberadas:

  • Los namespaces llevan etiquetas. No es decorativo: en el módulo 4, las NetworkPolicies seleccionarán namespaces por la etiqueta entorno, y así una política podrá decir "solo acepto tráfico de pods del mismo entorno".
  • Producción lleva anotaciones informativas. Cualquiera que haga kubectl describe ns rutas-norte-pro sabrá a quién avisar y qué reglas rigen.

La estructura del repositorio que ya tenemos definida encaja de forma natural:

k8s/
├── base/                  # manifiestos comunes a los tres entornos
│   ├── namespaces.yaml
│   ├── tienda-web-deployment.yaml
│   ├── tienda-web-service.yaml
│   ├── api-reservas-deployment.yaml
│   └── ...
└── entornos/
    ├── dev/               # diferencias del entorno de desarrollo
    ├── pre/
    └── pro/

Cómo se combinan base y entornos sin duplicar YAML es trabajo de Kustomize o Helm, en 10-04 y 10-03. En este módulo lo haremos de la forma más simple posible.

  1. El mismo manifiesto en varios entornos

Aquí está la propiedad que hace valiosos los namespaces. Vamos a desplegar tienda-web y su Service en preproducción usando exactamente los mismos ficheros que en desarrollo.

Nuestros manifiestos llevan namespace: rutas-norte-dev escrito dentro, así que hay dos formas de llevarlos a otro entorno.

Opción A: quitar el namespace del manifiesto y decidirlo al aplicar.

# k8s/base/tienda-web-deployment.yaml (fragmento, sin namespace)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tienda-web
  # sin campo namespace: lo decide quien aplica
  labels:
    app: tienda-web
    app.kubernetes.io/part-of: rutas-norte
kubectl apply -f k8s/base/tienda-web-deployment.yaml -n rutas-norte-pre
kubectl apply -f k8s/base/tienda-web-service.yaml -n rutas-norte-pre

Opción B: mantener el namespace en el manifiesto y sustituirlo al desplegar. Es lo que harán Kustomize o Helm por ti; a mano se vería así:

sed 's/rutas-norte-dev/rutas-norte-pre/g' k8s/base/tienda-web-deployment.yaml | kubectl apply -f -
sed 's/rutas-norte-dev/rutas-norte-pre/g' k8s/base/tienda-web-service.yaml | kubectl apply -f -

Hay una regla importante que conviene conocer: si un manifiesto declara metadata.namespace y además pasas -n con otro valor distinto, kubectl da error en lugar de elegir por su cuenta.

kubectl apply -f k8s/base/tienda-web-deployment.yaml -n rutas-norte-pre
error: the namespace from the provided object "rutas-norte-dev" does not match
the namespace "rutas-norte-pre". You must pass '--namespace=rutas-norte-dev' to perform this operation.

Es un comportamiento deseable: evita desplegar en producción por accidente.

Hagámoslo con la opción B, que respeta nuestros manifiestos actuales:

for f in tienda-web-deployment tienda-web-service api-reservas-deployment api-reservas-service redis-cache-deployment redis-cache-service; do
  sed 's/rutas-norte-dev/rutas-norte-pre/g' k8s/base/$f.yaml | kubectl apply -f -
done
kubectl get deploy,svc -n rutas-norte-pre
NAME                          READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/api-reservas  2/2     2            2           25s
deployment.apps/redis-cache   1/1     1            1           24s
deployment.apps/tienda-web    3/3     3            3           26s

NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/api-reservas   ClusterIP   10.96.229.14    <none>        3000/TCP   25s
service/redis-cache    ClusterIP   10.96.88.107    <none>        6379/TCP   24s
service/tienda-web     ClusterIP   10.96.44.71     <none>        80/TCP     26s

Ahora observa la situación completa del clúster:

kubectl get pods -A -l app.kubernetes.io/part-of=rutas-norte
NAMESPACE         NAME                                     READY   STATUS    RESTARTS   AGE
rutas-norte-dev   api-reservas-7f1d3e942-b8mrs             1/1     Running   0          48m
rutas-norte-dev   api-reservas-7f1d3e942-n4jvt             1/1     Running   0          48m
rutas-norte-dev   postgres-reservas-59d7c4b8f-k3xqp        1/1     Running   0          40m
rutas-norte-dev   redis-cache-6c8f9d745-w2mtb              1/1     Running   0          52m
rutas-norte-dev   tienda-web-6f9c4b8d7-42kxr               1/1     Running   0          55m
rutas-norte-dev   tienda-web-6f9c4b8d7-8vnwq               1/1     Running   0          55m
rutas-norte-dev   tienda-web-6f9c4b8d7-t9mzd               1/1     Running   0          55m
rutas-norte-pre   api-reservas-7f1d3e942-c9plk             1/1     Running   0          1m
rutas-norte-pre   api-reservas-7f1d3e942-r5vwj             1/1     Running   0          1m
rutas-norte-pre   redis-cache-6c8f9d745-h8nqz              1/1     Running   0          1m
rutas-norte-pre   tienda-web-6f9c4b8d7-d3jbx               1/1     Running   0          1m
rutas-norte-pre   tienda-web-6f9c4b8d7-m7kwr               1/1     Running   0          1m
rutas-norte-pre   tienda-web-6f9c4b8d7-p2fst               1/1     Running   0          1m

Dos plataformas Rutas Norte completas, con los mismos nombres de Deployment y de Service, conviviendo sin la menor colisión. Ese es el valor central del namespace.

Y una comprobación que ilustra el aislamiento de nombres:

kubectl get svc api-reservas -n rutas-norte-dev -o jsonpath='{.spec.clusterIP}{"\n"}'
kubectl get svc api-reservas -n rutas-norte-pre -o jsonpath='{.spec.clusterIP}{"\n"}'
10.96.184.22
10.96.229.14

Mismo nombre, dos objetos distintos, dos IPs virtuales distintas.

  1. Trabajar con namespaces sin volverse loco

Escribir -n rutas-norte-dev cien veces al día es tedioso y, peor, olvidarlo es la vía rápida a un accidente.

El flag -n y -A

kubectl get pods -n rutas-norte-pre          # un namespace concreto
kubectl get pods --all-namespaces            # todos
kubectl get pods -A                          # abreviatura de lo anterior

-A es imprescindible cuando buscas algo y no recuerdas dónde está:

kubectl get deploy -A -l app=api-reservas
NAMESPACE         NAME           READY   UP-TO-DATE   AVAILABLE   AGE
rutas-norte-dev   api-reservas   2/2     2            2           50m
rutas-norte-pre   api-reservas   2/2     2            2           3m

Fijar el namespace del contexto

Es la opción que más tiempo ahorra. Modifica tu kubeconfig para que todos los comandos usen ese namespace por defecto:

kubectl config set-context --current --namespace=rutas-norte-pre
kubectl get pods
Context "rutas-norte" modified.

NAME                           READY   STATUS    RESTARTS   AGE
api-reservas-7f1d3e942-c9plk   1/1     Running   0          4m
api-reservas-7f1d3e942-r5vwj   1/1     Running   0          4m
redis-cache-6c8f9d745-h8nqz    1/1     Running   0          4m
tienda-web-6f9c4b8d7-d3jbx     1/1     Running   0          4m
tienda-web-6f9c4b8d7-m7kwr     1/1     Running   0          4m
tienda-web-6f9c4b8d7-p2fst     1/1     Running   0          4m

Y la comprobación que deberías hacer un reflejo antes de cualquier comando destructivo:

kubectl config view --minify -o jsonpath='{..namespace}{"\n"}'
rutas-norte-pre

Consejo muy recomendable: configura el prompt de tu terminal para mostrar contexto y namespace. Herramientas como kube-ps1 lo hacen, y ver (rutas-norte:rutas-norte-pro) en pantalla antes de teclear delete ha salvado muchas tardes.

kubens

Del conjunto kubectx, kubens cambia de namespace de forma interactiva:

kubens                       # lista los namespaces y marca el actual
kubens rutas-norte-pro       # cambia a produccion
kubens -                     # vuelve al anterior

Es azúcar sintáctico sobre kubectl config set-context, pero con listado interactivo y salto rápido al anterior. Se instala como binario suelto o mediante krew, el gestor de plugins de kubectl que vimos en La CLI de Kubernetes.

Vuelve a desarrollo antes de continuar:

kubectl config set-context --current --namespace=rutas-norte-dev

  1. DNS entre namespaces

Ya conoces el nombre completo de un Service: <servicio>.<namespace>.svc.cluster.local. Los namespaces son la razón de que exista esa estructura.

Cuando un pod resuelve un nombre, el DNS del clúster prueba una serie de sufijos de búsqueda en orden. Míralos en un pod real:

kubectl exec deploy/api-reservas -- cat /etc/resolv.conf
search rutas-norte-dev.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5

La primera línea explica todo el comportamiento: al buscar redis-cache, el pod prueba primero redis-cache.rutas-norte-dev.svc.cluster.local. Como existe, ahí termina. El nombre corto siempre resuelve dentro del propio namespace.

Desde un pod de rutas-norte-dev, si escribe... Resuelve a...
redis-cache redis-cache.rutas-norte-dev.svc.cluster.local
redis-cache.rutas-norte-pre redis-cache.rutas-norte-pre.svc.cluster.local
redis-cache.rutas-norte-pro.svc.cluster.local Ese mismo, sin ambigüedad

Compruébalo:

kubectl run test-dns --rm -it --image=busybox:1.36 --restart=Never -n rutas-norte-dev -- \
  sh -c 'nslookup api-reservas; nslookup api-reservas.rutas-norte-pre'
Name:      api-reservas.rutas-norte-dev.svc.cluster.local
Address:   10.96.184.22

Name:      api-reservas.rutas-norte-pre.svc.cluster.local
Address:   10.96.229.14

pod "test-dns" deleted

Aquí hay dos noticias. La buena: el mismo manifiesto de api-reservas, con DATABASE_HOST: postgres-reservas, funciona en los tres entornos y cada uno habla con su propia base de datos, sin condicionales ni plantillas.

La mala, y es importante: un pod de desarrollo acaba de resolver y podría conectarse a un Service de preproducción. Nada se lo impide. Eso nos lleva directamente al apartado siguiente.

  1. Qué NO garantiza un namespace

Este es el apartado que evita incidentes. La creencia de que "cada equipo en su namespace y así están aislados" es peligrosamente falsa.

No es una frontera de red

Por defecto, cualquier pod de cualquier namespace puede conectarse a cualquier pod o Service de cualquier otro namespace. El modelo de red plano de Kubernetes es explícito en esto: todos los pods se ven entre sí.

Demostración incómoda: un pod de desarrollo escribiendo en la caché de preproducción.

kubectl run intruso --rm -it --image=redis:7.2-alpine --restart=Never -n rutas-norte-dev -- \
  redis-cli -h redis-cache.rutas-norte-pre SET plazas:BIL-SAN:2026-08-14 0
OK
pod "intruso deleted"

Acabamos de poner a cero las plazas disponibles en la caché de preproducción desde desarrollo. Si hubiera sido rutas-norte-pro, sería un incidente serio. Y nadie ha necesitado permisos especiales: ha bastado con conocer el nombre.

La solución no es el namespace, son las NetworkPolicies de 04-06, que sí permiten declarar reglas del tipo "postgres-reservas solo acepta conexiones de pods con la etiqueta app=api-reservas del mismo namespace".

No es una frontera de seguridad ni de permisos

Crear un namespace no restringe quién puede hacer qué en él. Si tus credenciales tienen permisos amplios sobre el clúster, los tienes sobre todos los namespaces, existentes y futuros.

kubectl auth can-i delete deployments -n rutas-norte-pro
yes

Con un usuario de administrador, la respuesta es yes para cualquier namespace. El aislamiento de permisos lo dan los Role y RoleBinding de RBAC. El namespace es solo el ámbito sobre el que esas reglas se aplican: necesario, pero no suficiente.

No limita el consumo de recursos

Un namespace no impone ningún techo. Un Deployment mal configurado en rutas-norte-dev puede consumir toda la CPU y la memoria del clúster y dejar sin recursos a los pods de rutas-norte-pro, porque comparten los mismos nodos.

kubectl describe namespace rutas-norte-dev
Name:         rutas-norte-dev
Labels:       app.kubernetes.io/part-of=rutas-norte
              entorno=dev
Status:       Active

No resource quota.

No LimitRange resource.

Esas dos líneas finales lo dicen todo: sin cuota y sin límites. Ponerlos es materia de Cuotas y Límites de Recursos y LimitRanges y QoS.

No aísla los nodos

Los pods de todos los namespaces se reparten por los mismos nodos. Un pod de desarrollo y uno de producción pueden ser vecinos en la misma máquina, compartiendo CPU, memoria y disco.

kubectl get pods -A -l app=tienda-web -o custom-columns=NS:.metadata.namespace,POD:.metadata.name,NODO:.spec.nodeName
NS                POD                          NODO
rutas-norte-dev   tienda-web-6f9c4b8d7-42kxr   rutas-norte
rutas-norte-pre   tienda-web-6f9c4b8d7-d3jbx   rutas-norte

Separar cargas por nodos requiere taints, tolerations y afinidad (06-05).

Resumen honesto

Creencia habitual Realidad
"Los namespaces aíslan la red" Falso. Todo se ve con todo. Hacen falta NetworkPolicies
"Los namespaces aíslan permisos" Falso por sí solos. Hacen falta Roles y RoleBindings
"Un namespace no puede consumir recursos de otro" Falso. Hace falta ResourceQuota
"Producción está protegida por estar en otro namespace" Falso. Es una separación organizativa, no una barrera
"Los namespaces separan los nodos" Falso. Los nodos son comunes
"Los namespaces evitan colisiones de nombres" Verdadero. Esa sí es su función nativa

La frase que resume el apartado: el namespace es el ámbito sobre el que se aplican los mecanismos de aislamiento, no el mecanismo de aislamiento. Sin cuotas, RBAC y políticas de red encima, un namespace es solo una carpeta.

  1. Borrar un namespace: la orden más peligrosa de kubectl

kubectl delete namespace rutas-norte-pre

Ese comando borra todo lo que hay dentro: Deployments, ReplicaSets, Pods, Services, ConfigMaps, Secrets, PersistentVolumeClaims, ServiceAccounts, Roles... Y con los PVC se van, según la política de recuperación, los datos.

Características que lo hacen especialmente peligroso:

  • No pide confirmación. Ninguna.
  • No hay deshacer. No existe papelera ni kubectl undelete.
  • Es asíncrono. El namespace pasa a estado Terminating mientras se borra el contenido, y en ese estado no admite objetos nuevos.
  • Un error de tecleo es catastrófico. rutas-norte-pro en lugar de rutas-norte-pre son tres caracteres de diferencia.

Míralo en acción, con un namespace de usar y tirar:

kubectl create namespace prueba-borrado
kubectl run temporal --image=nginx:1.27-alpine -n prueba-borrado
kubectl delete namespace prueba-borrado
kubectl get namespace prueba-borrado
namespace/prueba-borrado created
pod/temporal created
namespace "prueba-borrado" deleted

Error from server (NotFound): namespaces "prueba-borrado" not found

El pod se fue con él, sin preguntar nada.

Namespaces atascados en Terminating

Un problema que encontrarás tarde o temprano: un namespace que se queda en Terminating indefinidamente. La causa casi siempre es un finalizer —esos ganchos que estudiamos en Objetos y Manifiestos— que no puede completarse, típicamente el de una API extendida cuyo servidor ya no existe.

kubectl get namespace rutas-norte-pre -o jsonpath='{.spec.finalizers}{"\n"}'
kubectl get namespace rutas-norte-pre -o jsonpath='{.status.conditions}' | python3 -m json.tool

Antes de forzar nada, averigua qué recurso está bloqueando, porque quitar el finalizer a lo bruto deja objetos huérfanos en etcd. La forma correcta es eliminar el recurso conflictivo o restaurar el servicio que debe procesarlo.

Medidas de protección para Rutas Norte

  1. Etiquetar y anotar producción, como hicimos en namespaces.yaml: al describir el namespace se lee el aviso.
  2. RBAC restrictivo: nadie tiene permiso de delete namespaces en rutas-norte-pro salvo un par de personas (08-01).
  3. Verificar el contexto antes de cualquier borrado, con el reflejo del apartado 6.
  4. Copias de seguridad: la única red real. Herramientas como Velero permiten restaurar un namespace entero, y es materia de Copias de Seguridad y Restauración.
  5. Comprobar antes de disparar, siempre:
kubectl get all -n rutas-norte-pre

Un buen hábito adicional: usar --dry-run=client mentalmente. Antes de borrar, pregúntate en voz alta qué namespace estás mirando.

Deja preproducción montada, la seguiremos usando:

kubectl get ns -l app.kubernetes.io/part-of=rutas-norte
NAME              STATUS   AGE
rutas-norte-dev   Active   2d
rutas-norte-pre   Active   25m
rutas-norte-pro   Active   25m

  1. Namespaces frente a clústeres separados

La pregunta estratégica: ¿deben los tres entornos de Rutas Norte vivir en un clúster con tres namespaces, o en tres clústeres separados?

Criterio Namespaces en un clúster Clústeres separados
Coste Un plano de control, nodos compartidos: mucho más barato Multiplicado por el número de clústeres
Aislamiento real Lógico; requiere cuotas, RBAC y NetworkPolicies bien hechas Total: infraestructura distinta
Radio de impacto de un fallo Un problema del plano de control afecta a todo Un clúster caído no toca a los demás
Versión de Kubernetes La misma para todos los entornos Cada uno la suya: se puede probar una actualización en pre
Complejidad operativa Baja: un kubeconfig, un juego de herramientas Alta: multiplica monitorización, actualizaciones y accesos
Riesgo de error humano Alto: un -n mal puesto toca producción Bajo: cambiar de clúster es un acto consciente
Cumplimiento normativo Difícil de justificar ante un auditor Fácil de justificar
Latencia entre entornos Nula, comparten red Requiere conectividad externa

Criterios prácticos de decisión:

Namespaces en un clúster cuando:

  • Los entornos son de confianza equivalente (dev y pre del mismo equipo).
  • El equipo es pequeño y el presupuesto limitado.
  • Se domina RBAC, cuotas y políticas de red.

Clústeres separados cuando:

  • Hay datos regulados: personales, sanitarios, financieros.
  • Se ofrece servicio a clientes que no confían entre sí (multi-tenant real).
  • Se necesitan versiones distintas de Kubernetes o de componentes del clúster.
  • Un fallo de producción no puede depender de la salud de un entorno de pruebas.

La decisión de Rutas Norte

La empresa tiene datos personales de clientes —nombre, DNI, teléfono y correo— en postgres-reservas. La decisión, y su porqué:

Entorno Ubicación Justificación
rutas-norte-dev Namespace en el clúster de no producción Datos anonimizados, coste mínimo, iteración rápida
rutas-norte-pre Namespace en el mismo clúster que dev Réplica funcional de producción con datos ficticios
rutas-norte-pro Clúster propio y separado Datos personales reales, requisitos de disponibilidad y auditoría, radio de impacto acotado

Es decir: dos clústeres, tres namespaces. El namespace rutas-norte-pro existe también en el clúster de no producción para que los manifiestos sean idénticos y se pueda ensayar el despliegue, pero la producción real vive aparte.

Durante el curso trabajaremos con un solo minikube y los tres namespaces, que es lo práctico para aprender. La gestión de varios clústeres a la vez, con sus contextos y sus herramientas, se trata en Gestión Multi-Clúster.

Un último apunte sobre cómo organizar namespaces: por entorno (lo que hacemos), por equipo (equipo-pagos, equipo-rutas) o por aplicación (rutas-norte, intranet). Lo habitual es combinar dos ejes: rutas-norte-pro, intranet-pro. Lo que nunca funciona es un namespace por microservicio: multiplica la burocracia sin aportar aislamiento útil.

Errores Comunes y Consejos

  • Desplegar en default por olvido. El síntoma es kubectl get pods sin resultados donde esperabas verlos. Comprueba con kubectl get pods -A -l app=<componente>.
  • Creer que un namespace aísla la red. No lo hace. Cualquier pod puede conectarse a cualquier Service de otro namespace conociendo su nombre.
  • Creer que un namespace aísla permisos o recursos. Hacen falta RBAC y ResourceQuota. El namespace es el ámbito, no el mecanismo.
  • Poner -n a un recurso sin namespace. kubectl get nodes -n loquesea ignora el flag en silencio y puede confundirte.
  • Discrepancia entre metadata.namespace y -n. kubectl da error en lugar de decidir por ti. Es una protección, no un fastidio.
  • Buscar en el namespace equivocado y concluir que algo no existe. Ante la duda, -A.
  • Borrar un namespace sin mirar qué contiene. No hay confirmación ni deshacer. kubectl get all -n <ns> antes, siempre.
  • Forzar el borrado de un namespace atascado quitándole los finalizers. Deja objetos huérfanos en etcd. Investiga primero qué recurso bloquea.
  • Un namespace por microservicio. Multiplica la carga administrativa sin aportar aislamiento real. Organiza por entorno, equipo o aplicación.
  • Consejo: configura el prompt para mostrar contexto y namespace (kube-ps1). Ver (rutas-norte:rutas-norte-pro) antes de teclear delete evita accidentes.
  • Consejo: declara metadata.namespace en los manifiestos que sean específicos de un entorno y omítelo en los que sean genéricos. La ambigüedad es lo que provoca despliegues en el sitio equivocado.
  • Consejo: etiqueta siempre tus namespaces. Las NetworkPolicies del módulo 4 seleccionarán namespaces por etiqueta, y sin ellas tendrás que volver atrás a ponerlas.

Ejercicios

Ejercicio 1: Clasificar recursos y explorar el sistema

  1. Con un solo comando, cuenta cuántos tipos de recursos tienen namespace y cuántos no.
  2. Determina, sin consultar tablas, si estos recursos llevan namespace: PersistentVolume, PersistentVolumeClaim, Role, ClusterRole, Ingress, StorageClass.
  3. Lista los pods de kube-system e identifica cuáles son los cinco componentes del plano de control que estudiaste en el módulo 1.
  4. Averigua qué hay dentro de kube-public y explica qué tiene de particular ese namespace.

Ejercicio 2: Desplegar la plataforma en producción y comprobar el aislamiento

  1. Despliega tienda-web (Deployment y Service) en rutas-norte-pro, con la etiqueta entorno: pro en lugar de dev.
  2. Demuestra con un solo comando que existen tres Deployments llamados tienda-web en el clúster, en namespaces distintos.
  3. Comprueba que las ClusterIP de los tres Services tienda-web son diferentes.
  4. Desde un pod efímero en rutas-norte-dev, haz una petición HTTP al tienda-web de rutas-norte-pro usando el nombre DNS completo. ¿Funciona? Explica qué implica esto y qué objeto habría que crear para impedirlo.

Ejercicio 3: Auditoría de namespaces

Te incorporas al equipo de plataforma de Rutas Norte y te encargan una auditoría rápida del clúster. Responde con comandos y su salida:

  1. ¿Qué namespaces existen y cuáles pertenecen a la plataforma Rutas Norte?
  2. ¿Hay algún objeto desplegado en default? Si lo hay, muévelo al namespace correcto.
  3. ¿Qué namespaces tienen ResourceQuota definida? ¿Qué implica que no la tengan?
  4. ¿Puedes borrar el namespace de producción con tus credenciales actuales? ¿Qué mecanismo debería impedirlo y en qué lección se estudia?
  5. Elabora una tabla con los tres entornos indicando: número de pods, número de Services y si tienen cuota.

Soluciones

Solución 1

echo "Con namespace: $(kubectl api-resources --namespaced=true --no-headers | wc -l)"
echo "Sin namespace: $(kubectl api-resources --namespaced=false --no-headers | wc -l)"
Con namespace: 43
Sin namespace: 26

Los números varían según los CRDs y addons instalados en tu clúster.

  1. El razonamiento, antes de comprobarlo: lleva namespace lo que pertenece a una aplicación concreta; no lo lleva lo que es infraestructura o configuración global.
Recurso ¿Namespace? Razonamiento
PersistentVolume No Es un disco del clúster, existe antes de que nadie lo reclame
PersistentVolumeClaim Es la petición de una aplicación concreta
Role Concede permisos dentro de un namespace
ClusterRole No Concede permisos en todo el clúster
Ingress Enruta hacia Services, que tienen namespace
StorageClass No Define un tipo de almacenamiento disponible para todo el clúster
kubectl api-resources --no-headers | grep -E "^(persistentvolumes|persistentvolumeclaims|roles|clusterroles|ingresses|storageclasses) "
persistentvolumeclaims  pvc   v1                             true    PersistentVolumeClaim
persistentvolumes       pv    v1                             false   PersistentVolume
storageclasses          sc    storage.k8s.io/v1              false   StorageClass
ingresses               ing   networking.k8s.io/v1           true    Ingress
clusterroles                  rbac.authorization.k8s.io/v1   false   ClusterRole
roles                         rbac.authorization.k8s.io/v1   true    Role
# 3. Componentes del plano de control
kubectl get pods -n kube-system --no-headers | awk '{print $1}' | grep -E "apiserver|etcd|scheduler|controller-manager|coredns"
coredns-668d6bf9bc-x8k2m
etcd-rutas-norte
kube-apiserver-rutas-norte
kube-controller-manager-rutas-norte
kube-scheduler-rutas-norte

Los cinco: kube-apiserver (la única puerta a la API), etcd (el almacén de estado), kube-scheduler (asigna pods a nodos), kube-controller-manager (donde viven los controladores de ReplicaSet, Deployment, endpoints...) y CoreDNS (que resuelve los nombres de Service que usamos en la lección anterior).

# 4. kube-public
kubectl get configmaps -n kube-public
kubectl get configmap cluster-info -n kube-public -o jsonpath='{.data.jws-kubeconfig-*}' | head -3
NAME               DATA   AGE
cluster-info       2      3d
kube-root-ca.crt   1      3d

kube-public es el único namespace legible sin autenticarse: cualquiera que alcance el apiserver puede leer su contenido. Contiene cluster-info, con los datos que un nodo necesita para unirse al clúster durante el proceso de kubeadm join. Por eso nunca debe guardarse nada sensible ahí.

Solución 2

# 1. Desplegar en produccion
for f in tienda-web-deployment tienda-web-service; do
  sed -e 's/rutas-norte-dev/rutas-norte-pro/g' -e 's/entorno: dev/entorno: pro/g' \
    k8s/base/$f.yaml | kubectl apply -f -
done
kubectl get deploy,svc -n rutas-norte-pro
deployment.apps/tienda-web created
service/tienda-web created

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/tienda-web   3/3     3            3           15s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/tienda-web   ClusterIP   10.96.171.55    <none>        80/TCP    15s
# 2. Los tres Deployments
kubectl get deploy tienda-web -A
NAMESPACE         NAME         READY   UP-TO-DATE   AVAILABLE   AGE
rutas-norte-dev   tienda-web   3/3     3            3           1h
rutas-norte-pre   tienda-web   3/3     3            3           35m
rutas-norte-pro   tienda-web   3/3     3            3           1m
# 3. Tres ClusterIP distintas
kubectl get svc tienda-web -A -o custom-columns=NS:.metadata.namespace,NOMBRE:.metadata.name,IP:.spec.clusterIP
NS                NOMBRE       IP
rutas-norte-dev   tienda-web   10.96.12.209
rutas-norte-pre   tienda-web   10.96.44.71
rutas-norte-pro   tienda-web   10.96.171.55
# 4. Desde dev hacia pro
kubectl run intruso --rm -it --image=curlimages/curl:8.8.0 --restart=Never -n rutas-norte-dev -- \
  curl -s -o /dev/null -w "%{http_code}\n" http://tienda-web.rutas-norte-pro.svc.cluster.local/
200
pod "intruso" deleted

Funciona. Un pod de desarrollo ha accedido sin ningún obstáculo a un servicio de producción. Implicación directa: el namespace no es una frontera de red. En el modelo por defecto de Kubernetes, todos los pods se ven entre sí, y basta conocer el nombre DNS para conectarse.

Para impedirlo hace falta una NetworkPolicy en rutas-norte-pro que rechace todo el tráfico entrante salvo el que proceda de namespaces con la etiqueta entorno: pro (por eso etiquetamos los namespaces al crearlos). Es el contenido de Políticas de Red y Seguridad de Red.

Solución 3

# 1. Namespaces existentes y los de la plataforma
kubectl get ns
kubectl get ns -l app.kubernetes.io/part-of=rutas-norte
NAME              STATUS   AGE
default           Active   3d
kube-node-lease   Active   3d
kube-public       Active   3d
kube-system       Active   3d
rutas-norte-dev   Active   2d
rutas-norte-pre   Active   40m
rutas-norte-pro   Active   40m

NAME              STATUS   AGE
rutas-norte-dev   Active   2d
rutas-norte-pre   Active   40m
rutas-norte-pro   Active   40m

La etiqueta común permite distinguir de un vistazo los namespaces de la plataforma de los del sistema.

# 2. Objetos en default
kubectl get all -n default
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   3d

Solo aparece service/kubernetes, que es el Service del propio apiserver y debe estar ahí: es el que permite a los pods hablar con la API. Si hubiera cualquier otra cosa, el procedimiento sería exportarla, corregir su namespace y volver a aplicarla:

kubectl get deploy <nombre> -n default -o yaml > /tmp/objeto.yaml
# editar metadata.namespace y quitar metadata.uid, resourceVersion y status
kubectl apply -f /tmp/objeto.yaml -n rutas-norte-dev
kubectl delete deploy <nombre> -n default
# 3. Cuotas
kubectl get resourcequota -A
No resources found

Ningún namespace tiene cuota. Implicación: cualquier Deployment de rutas-norte-dev puede consumir toda la CPU y la memoria del clúster y dejar sin recursos a los pods de rutas-norte-pro, porque comparten nodos. Un bucle mal escrito en desarrollo puede tumbar la producción. Se corrige con ResourceQuota y LimitRange en 03-04 y 03-05.

# 4. Permisos de borrado
kubectl auth can-i delete namespace rutas-norte-pro
yes

Sí puedo borrarlo, y con él toda la producción, sin confirmación y sin deshacer. Lo que debería impedirlo es RBAC: un Role/ClusterRole que no incluya el verbo delete sobre namespaces para las credenciales habituales, dejando ese permiso a un rol de emergencia usado por dos personas. Se estudia en Control de Acceso Basado en Roles. Como red de seguridad complementaria, copias de seguridad con Velero (05-06).

# 5. Tabla resumen
for ns in rutas-norte-dev rutas-norte-pre rutas-norte-pro; do
  pods=$(kubectl get pods -n $ns --no-headers 2>/dev/null | wc -l)
  svcs=$(kubectl get svc -n $ns --no-headers 2>/dev/null | wc -l)
  quota=$(kubectl get resourcequota -n $ns --no-headers 2>/dev/null | wc -l)
  echo "$ns | $pods pods | $svcs services | cuotas: $quota"
done
rutas-norte-dev | 7 pods | 4 services | cuotas: 0
rutas-norte-pre | 6 pods | 3 services | cuotas: 0
rutas-norte-pro | 3 pods | 1 services | cuotas: 0
Entorno Pods Services ¿Cuota? Observación
rutas-norte-dev 7 4 No Entorno completo: los cuatro componentes desplegados
rutas-norte-pre 6 3 No Falta postgres-reservas
rutas-norte-pro 3 1 No Solo tienda-web; sin cuota es el riesgo más grave del clúster

Conclusión

Los namespaces han dejado de ser esa palabra que repetíamos en cada comando. Sabes que su función nativa es la unicidad de nombres, y que de ella se derivan el ámbito de las consultas, de los selectores, del DNS corto, de las cuotas, del RBAC y de las políticas de red. Distingues los recursos que llevan namespace —pods, Deployments, Services, ConfigMaps, Secrets, PVCs— de los que pertenecen al clúster entero —nodos, PersistentVolumes, StorageClasses, ClusterRoles—, y sabes averiguarlo en cualquier momento con kubectl api-resources --namespaced. Conoces los cuatro namespaces del sistema, has visto el plano de control corriendo como pods en kube-system, y tienes claro por qué default es un vertedero en el que Rutas Norte no despliega nada.

Has montado los tres entornos del proyecto de forma declarativa y con etiquetas que las NetworkPolicies aprovecharán después, y has desplegado los mismos manifiestos en varios de ellos comprobando que tienda-web y api-reservas conviven con idéntico nombre en namespaces distintos, cada uno con su propia ClusterIP y hablando con su propia base de datos gracias al DNS corto. Trabajas con -n, con -A y fijando el namespace del contexto, con el reflejo de verificar dónde estás antes de cualquier comando destructivo.

Y te llevas las dos advertencias que más incidentes evitan. La primera: un namespace no aísla nada por sí solo. No es frontera de red —lo has demostrado escribiendo en la caché de preproducción desde desarrollo y llamando a producción desde un pod de dev—, no es frontera de permisos, no limita recursos y no separa nodos. Es el ámbito sobre el que se aplican cuotas, RBAC y políticas de red, que llegarán en los módulos 3, 4 y 8. La segunda: kubectl delete namespace se lo lleva todo sin confirmación y sin marcha atrás, motivo por el cual producción real de Rutas Norte vive en un clúster aparte.

Queda un hilo suelto que ha recorrido todo el módulo. Los selectores de los ReplicaSets, los selectores de los Services, la etiqueta entorno de los namespaces, la anotación kubernetes.io/change-cause del historial de revisiones, el pod-template-hash que el Deployment añade solo, las consultas con -l que llevamos usando desde la primera lección... Todo eso son etiquetas y anotaciones, y hasta ahora las hemos ido usando sin sistematizar. En Etiquetas, Selectores y Anotaciones, la lección que cierra el módulo, pondremos orden: sintaxis y restricciones, etiquetas recomendadas por Kubernetes, el esquema de etiquetado definitivo de los seis componentes de Rutas Norte en sus tres entornos, selectores de igualdad y de conjunto, y las consultas del día a día que convierten un clúster grande en algo navegable.

Curso de Kubernetes

Módulo 1: Introducción a Kubernetes

Módulo 2: Componentes Principales de Kubernetes

Módulo 3: Gestión de Configuración y Secretos

Módulo 4: Redes en Kubernetes

Módulo 5: Almacenamiento en Kubernetes

Módulo 6: Conceptos Avanzados de Kubernetes

Módulo 7: Monitoreo y Registro

Módulo 8: Seguridad en Kubernetes

Módulo 9: Escalado y Rendimiento

Módulo 10: Ecosistema y Herramientas de Kubernetes

Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real

Módulo 12: Preparación para la Certificación de Kubernetes

© Copyright 2026. Todos los derechos reservados