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