--- title: "Cómo aislar pods con políticas de red de Kubernetes" description: "Aplica NetworkPolicies en Kubernetes para denegar el tráfico por defecto, permitir solo las conexiones necesarias entre pods y namespaces y probar cada regla." category: "Kubernetes Avanzado" language: es source: https://cubepath.com/es/docs/kubernetes-avanzado/politicas-de-red-de-kubernetes updated: 2026-09-25 --- # Cómo aislar pods con políticas de red de Kubernetes Por defecto, en Kubernetes cualquier pod puede conectarse a cualquier otro pod del clúster y a Internet. Las NetworkPolicies permiten restringir ese tráfico a nivel de pod, seleccionando por etiquetas qué conexiones de entrada (ingress) y de salida (egress) están permitidas. En este tutorial desplegarás una pequeña aplicación de prueba, aplicarás una política de denegación por defecto y abrirás solo el tráfico necesario, comprobando cada regla con pruebas de conexión reales. ## Requisitos previos Para seguir esta guía necesitas: - Un clúster de Kubernetes con un plugin de red (CNI) que aplique NetworkPolicies, como Calico, Cilium o el controlador integrado de k3s. **Flannel por sí solo no las aplica**: las políticas se crean sin error pero no tienen ningún efecto. - `kubectl` configurado con permisos para crear namespaces, pods y NetworkPolicies, desde una máquina con Ubuntu 24.04 o cualquier otro sistema. Comprueba qué CNI usa tu clúster mirando los pods de `kube-system`: ```bash kubectl get pods -n kube-system -o wide | grep -Ei 'calico|cilium|flannel|kube-router' ``` Si ves pods de `calico-node` o `cilium`, el clúster aplica políticas. Si solo ves `kube-flannel`, instala Calico o Cilium antes de continuar. ## Cómo funcionan las NetworkPolicies Tres reglas explican casi todo su comportamiento: - Un pod que no está seleccionado por ninguna política acepta todo el tráfico. - En cuanto una política con `policyTypes: [Ingress]` selecciona un pod, ese pod solo acepta el tráfico de entrada que alguna política permita explícitamente. Lo mismo ocurre con `Egress` para el tráfico de salida. - Las políticas son aditivas: no hay reglas de denegación, y si varias políticas seleccionan un pod, se suma todo lo que permiten. Las respuestas de una conexión permitida siempre se aceptan, así que no necesitas reglas para el tráfico de retorno. Los orígenes y destinos se definen con `podSelector`, `namespaceSelector` o `ipBlock` (rangos CIDR). ## Paso 1: Desplegar la aplicación de prueba Crea un namespace `demo` con dos componentes: un `backend` (Nginx expuesto con un Service) y un `frontend` que será el único autorizado a hablar con él. Crea también un pod `intruso` para comprobar que el resto del tráfico se bloquea: ```bash kubectl create namespace demo kubectl -n demo create deployment backend --image=nginx:1.29 --port=80 kubectl -n demo expose deployment backend --port=80 kubectl -n demo run frontend --image=busybox:1.37 --labels=app=frontend -- sleep 1d kubectl -n demo run intruso --image=busybox:1.37 --labels=app=intruso -- sleep 1d ``` `kubectl create deployment` etiqueta automáticamente los pods con `app=backend`. Espera a que todo esté listo: ```bash kubectl -n demo get pods --show-labels ``` ```output NAME READY STATUS RESTARTS AGE LABELS backend-7c9f8d6b5d-q2x7m 1/1 Running 0 40s app=backend,pod-template-hash=7c9f8d6b5d frontend 1/1 Running 0 35s app=frontend intruso 1/1 Running 0 33s app=intruso ``` Comprueba que, sin políticas, ambos pods llegan al backend: ```bash kubectl -n demo exec frontend -- wget -qO- -T 3 http://backend | grep -o '.*' kubectl -n demo exec intruso -- wget -qO- -T 3 http://backend | grep -o '.*' ``` ```output Welcome to nginx! Welcome to nginx! ``` ## Paso 2: Denegar todo el tráfico de entrada por defecto La base de un modelo de mínimo privilegio es una política que selecciona todos los pods del namespace (`podSelector: {}`) y no permite ninguna entrada: ```bash nano default-deny-ingress.yaml ``` ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-ingress namespace: demo spec: podSelector: {} policyTypes: - Ingress ``` Aplícala y repite la prueba desde el frontend: ```bash kubectl apply -f default-deny-ingress.yaml kubectl -n demo exec frontend -- wget -qO- -T 3 http://backend ``` ```output wget: download timed out command terminated with exit code 1 ``` Ahora ningún pod del namespace acepta conexiones entrantes. ## Paso 3: Permitir solo el tráfico del frontend al backend Crea una política que seleccione los pods `app=backend` y acepte conexiones al puerto 80 solo desde pods `app=frontend` del mismo namespace: ```bash nano allow-frontend-to-backend.yaml ``` ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-to-backend namespace: demo spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 80 ``` El puerto indicado es el del contenedor (`targetPort`), no el del Service. Aplícala y comprueba ambos orígenes: ```bash kubectl apply -f allow-frontend-to-backend.yaml kubectl -n demo exec frontend -- wget -qO- -T 3 http://backend | grep -o '.*' kubectl -n demo exec intruso -- wget -qO- -T 3 http://backend ``` ```output Welcome to nginx! wget: download timed out command terminated with exit code 1 ``` El frontend vuelve a tener acceso y el intruso sigue bloqueado. ## Paso 4: Permitir tráfico desde otro namespace Es habitual que un controlador de Ingress o una herramienta de monitorización de otro namespace necesite llegar a tus pods. Para seleccionar un namespace usa la etiqueta `kubernetes.io/metadata.name`, que Kubernetes añade automáticamente a todos los namespaces con su nombre. Crea un namespace `monitoring` con un pod de prueba: ```bash kubectl create namespace monitoring kubectl -n monitoring run probe --image=busybox:1.37 --labels=app=probe -- sleep 1d ``` Añade una política que permita a cualquier pod de `monitoring` conectar con el backend: ```bash nano allow-monitoring.yaml ``` ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-monitoring namespace: demo spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: monitoring ports: - protocol: TCP port: 80 ``` ```bash kubectl apply -f allow-monitoring.yaml kubectl -n monitoring exec probe -- wget -qO- -T 3 http://backend.demo.svc.cluster.local | grep -o '.*' ``` ```output Welcome to nginx! ``` > Importante: la indentación cambia el significado. Si `namespaceSelector` y `podSelector` van en el **mismo** elemento de la lista `from` (sin un guion delante del segundo), se exigen ambas condiciones: pods con esa etiqueta dentro de ese namespace. Si cada uno lleva su propio guion, son dos orígenes alternativos y basta con cumplir uno. ## Paso 5: Restringir el tráfico de salida Limitar la salida impide que un pod comprometido se conecte a otros servicios internos o descargue herramientas de Internet. Aplica una denegación de salida por defecto a todo el namespace, pero permite las consultas DNS a CoreDNS; sin ellas, ningún pod podría resolver nombres de Service: ```bash nano default-deny-egress.yaml ``` ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-egress namespace: demo spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53 - protocol: TCP port: 53 ``` CoreDNS lleva la etiqueta `k8s-app=kube-dns` en kubeadm, k3s y la mayoría de distribuciones; compruébalo con `kubectl -n kube-system get pods --show-labels | grep dns`. Después, permite de forma explícita la salida del frontend hacia el backend: ```bash nano allow-frontend-egress.yaml ``` ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-frontend-egress namespace: demo spec: podSelector: matchLabels: app: frontend policyTypes: - Egress egress: - to: - podSelector: matchLabels: app: backend ports: - protocol: TCP port: 80 ``` Aplica ambas y comprueba que el frontend sigue llegando al backend, pero ya no puede salir a Internet: ```bash kubectl apply -f default-deny-egress.yaml -f allow-frontend-egress.yaml kubectl -n demo exec frontend -- wget -qO- -T 3 http://backend | grep -o '.*' kubectl -n demo exec frontend -- wget -qO- -T 3 http://example.com ``` ```output Welcome to nginx! wget: download timed out command terminated with exit code 1 ``` Si un pod necesita llegar a un servicio externo concreto, añade una regla de salida con `ipBlock` y su rango, por ejemplo `cidr: 203.0.113.0/24`, y el puerto correspondiente. ## Paso 6: Revisar las políticas aplicadas Lista las políticas del namespace y consulta el detalle de una de ellas: ```bash kubectl -n demo get networkpolicy kubectl -n demo describe networkpolicy allow-frontend-to-backend ``` ```output NAME POD-SELECTOR AGE allow-frontend-egress app=frontend 2m allow-frontend-to-backend app=backend 9m allow-monitoring app=backend 6m default-deny-egress 2m default-deny-ingress 11m ``` `` en `POD-SELECTOR` significa que la política selecciona todos los pods del namespace. ## Funciones extra de Calico y Cilium La API estándar `NetworkPolicy` funciona igual con cualquier CNI compatible y es la que conviene usar por defecto. Calico y Cilium añaden sus propios recursos para casos que la API estándar no cubre: - **Calico** ofrece `GlobalNetworkPolicy` (políticas para todo el clúster, con orden de evaluación y reglas de denegación explícitas), que se gestionan con `calicoctl` o con el API server de Calico. - **Cilium** ofrece `CiliumNetworkPolicy`, con reglas de capa 7 (métodos y rutas HTTP) y filtrado de salida por nombre de dominio. Por ejemplo, con Cilium puedes permitir que el frontend solo salga a `api.github.com` por HTTPS. La primera regla permite el DNS a través del proxy de Cilium, que es lo que le permite conocer las IPs de ese nombre: ```yaml apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: frontend-to-github namespace: demo spec: endpointSelector: matchLabels: app: frontend egress: - toEndpoints: - matchLabels: k8s:io.kubernetes.pod.namespace: kube-system k8s:k8s-app: kube-dns toPorts: - ports: - port: "53" protocol: ANY rules: dns: - matchPattern: "*" - toFQDNs: - matchName: api.github.com toPorts: - ports: - port: "443" protocol: TCP ``` Este recurso solo existe en clústeres con Cilium; en cualquier otro CNI, `kubectl apply` fallará porque el tipo no está definido. ## Solución de problemas **La política se crea pero no bloquea nada.** El CNI no aplica NetworkPolicies (caso típico de Flannel). Comprueba el CNI como en los requisitos previos. **Tras la denegación de salida, los pods no resuelven nombres.** La regla de DNS no coincide con los pods de CoreDNS. Revisa sus etiquetas y namespace con `kubectl -n kube-system get pods --show-labels` y ajusta el selector. **Un origen permitido sigue bloqueado.** Comprueba que las etiquetas del pod coinciden exactamente con `kubectl get pods --show-labels`, que el puerto de la regla es el del contenedor y no el del Service, y que, si limitaste la salida, el pod de origen también tiene una regla de egress hacia el destino. Una conexión necesita estar permitida en la salida del origen y en la entrada del destino. **Las sondas de salud fallan después de aplicar la denegación de entrada.** En la mayoría de CNI el tráfico del kubelet del propio nodo está permitido, pero no en todos. Si tus sondas empiezan a fallar, consulta la documentación de tu CNI sobre el tráfico desde el host. Para limpiar el entorno de pruebas: ```bash kubectl delete namespace demo monitoring ``` ## Conclusión Has aplicado una denegación por defecto de entrada y salida en un namespace, permitido solo las conexiones necesarias entre pods y desde otro namespace, y comprobado cada regla con pruebas reales. Como siguientes pasos puedes añadir una política `default-deny-ingress` a cada namespace de aplicación como parte de su plantilla, combinar estas políticas con RBAC para limitar quién puede modificarlas, o usar las herramientas de observabilidad de tu CNI (como Hubble en Cilium) para ver qué flujos se permiten y cuáles se descartan.