Políticas de red de Kubernetes: proteger el tráfico entre pods
Domina las políticas de red de Kubernetes para controlar el tráfico entre pods, aplicar microsegmentación y crear una defensa en profundidad.

De forma predeterminada, todos los pods de un clúster de Kubernetes pueden comunicarse entre sí. Este modelo de red plano resulta cómodo durante el desarrollo, pero es peligroso en producción. Un pod comprometido puede acceder a tu base de datos, a las APIs internas y al plano de control sin ninguna restricción.
Las políticas de red son firewalls nativos de Kubernetes que controlan qué pods pueden comunicarse entre sí. Implementan la microsegmentación: la práctica de definir reglas de tráfico granulares entre cargas de trabajo en lugar de depender únicamente de la seguridad perimetral.
Cómo entender el comportamiento de red predeterminado
Antes de aplicar cualquier NetworkPolicy, Kubernetes permite todo el tráfico de Ingress y de Egress entre pods. En el momento en que aplicas una política a un pod, ese pod pasa de «permitir todo» a «denegar por defecto» en la dirección especificada.
# ❌ No network policies: every pod can reach everything
# Pod A (frontend) → Pod B (api) ✓
# Pod A (frontend) → Pod C (database) ✓ ← This shouldn't happen
# Pod B (api) → Pod C (database) ✓
# Pod D (compromised) → Pod C (database) ✓ ← Dangerous# ✅ Default deny: block everything, then allow explicitly
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Applies to ALL pods in namespace
policyTypes:
- Ingress
- EgressEsta única política bloquea de inmediato todo el namespace. Ningún pod puede recibir ni enviar tráfico hasta que crees reglas de permiso. Empieza por aquí y ve construyendo desde cero: es mucho más seguro que partir de una configuración abierta e intentar cerrar huecos después.
Cómo crear reglas de permiso para patrones comunes
El patrón más común es una aplicación de tres niveles: el frontend se comunica con la API, y la API se comunica con la base de datos. Cada nivel solo debería poder alcanzar a su vecino inmediato.
# Allow frontend pods to receive traffic from ingress controller
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: frontend
tier: web
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-system
podSelector:
matchLabels:
app: nginx-ingress
ports:
- protocol: TCP
port: 3000# Allow API pods to receive traffic ONLY from frontend pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
tier: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
tier: web
ports:
- protocol: TCP
port: 8080# Allow database pods to receive traffic ONLY from API pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-to-database
namespace: production
spec:
podSelector:
matchLabels:
app: postgres
tier: database
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: api-server
tier: backend
ports:
- protocol: TCP
port: 5432Ahora, si el pod del frontend se ve comprometido, el atacante puede llegar a la API, pero no directamente a la base de datos. Cada salto requiere un compromiso adicional, lo que aumenta drásticamente la dificultad de cualquier movimiento lateral.
Control del tráfico de Egress
Las políticas de Ingress protegen a los pods de conexiones entrantes no deseadas. Las políticas de Egress evitan que los pods realicen conexiones salientes no deseadas, algo igual de importante para prevenir la exfiltración de datos.
# ❌ Pod can reach any external IP (data exfiltration risk)
# A compromised pod could send data to attacker-controlled servers
# ✅ Restrict API server egress to known destinations
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-server-egress
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Egress
egress:
# Allow DNS resolution
- to:
- namespaceSelector:
matchLabels:
name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# Allow connection to database
- to:
- podSelector:
matchLabels:
app: postgres
tier: database
ports:
- protocol: TCP
port: 5432
# Allow connection to Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
# Allow connection to external payment API
- to:
- ipBlock:
cidr: 203.0.113.0/24 # Payment provider IP range
ports:
- protocol: TCP
port: 443Incluye siempre la resolución DNS en tus reglas de Egress. Sin ella, los pods no podrán resolver ningún nombre de host, ni siquiera los nombres de servicios internos. Este es el error más común al implementar políticas de Egress.
Pruebas y validación de políticas
Las políticas de red son notoriamente difíciles de probar. Una política mal configurada puede romper tu aplicación de forma silenciosa. Usa herramientas y patrones de prueba dedicados.
// Policy validation script
interface PolicyTestCase {
name: string;
source: { namespace: string; labels: Record<string, string> };
destination: { namespace: string; labels: Record<string, string>; port: number };
expected: "allow" | "deny";
}
const testCases: PolicyTestCase[] = [
{
name: "Frontend can reach API",
source: {
namespace: "production",
labels: { app: "frontend", tier: "web" },
},
destination: {
namespace: "production",
labels: { app: "api-server", tier: "backend" },
port: 8080,
},
expected: "allow",
},
{
name: "Frontend cannot reach database directly",
source: {
namespace: "production",
labels: { app: "frontend", tier: "web" },
},
destination: {
namespace: "production",
labels: { app: "postgres", tier: "database" },
port: 5432,
},
expected: "deny",
},
{
name: "API can reach database",
source: {
namespace: "production",
labels: { app: "api-server", tier: "backend" },
},
destination: {
namespace: "production",
labels: { app: "postgres", tier: "database" },
port: 5432,
},
expected: "allow",
},
{
name: "Random pod cannot reach database",
source: {
namespace: "production",
labels: { app: "debug-pod" },
},
destination: {
namespace: "production",
labels: { app: "postgres", tier: "database" },
port: 5432,
},
expected: "deny",
},
];
async function runPolicyTests(
tests: PolicyTestCase[]
): Promise<{ passed: number; failed: number; results: string[] }> {
let passed = 0;
let failed = 0;
const results: string[] = [];
for (const test of tests) {
const actual = await probeConnection(
test.source,
test.destination
);
if (actual === test.expected) {
passed++;
results.push(`✅ ${test.name}: ${actual} (expected ${test.expected})`);
} else {
failed++;
results.push(`❌ ${test.name}: ${actual} (expected ${test.expected})`);
}
}
return { passed, failed, results };
}
async function probeConnection(
source: PolicyTestCase["source"],
destination: PolicyTestCase["destination"]
): Promise<"allow" | "deny"> {
// In practice: kubectl run a temp pod with source labels,
// attempt connection to destination, check exit code
return "allow"; // Placeholder
}Ejecuta estas pruebas en tu pipeline de CI contra un clúster de staging con las mismas políticas aplicadas. Funcionan tanto como documentación como pruebas de regresión: si alguien modifica una política, la suite de pruebas detecta los cambios no deseados.
Políticas entre namespaces
Los microservicios suelen repartirse en varios namespaces. Las políticas de red admiten reglas entre namespaces mediante selectores de namespace, pero la sintaxis exige mucha atención.
# Allow monitoring namespace to scrape metrics from all production pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-monitoring-scrape
namespace: production
spec:
podSelector: {} # All pods in production
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9090 # Metrics port# Allow production pods to send logs to logging namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-logging-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
name: logging
podSelector:
matchLabels:
app: fluentd
ports:
- protocol: TCP
port: 24224Etiqueta tus namespaces de forma coherente. La etiqueta name: monitoring en el namespace de monitoreo es lo que permite que funcione el selector entre namespaces. Sin las etiquetas de namespace correctas, las políticas entre namespaces fallan en encontrar coincidencias de forma silenciosa.
Depuración de problemas de conectividad
Cuando una política de red bloquea tráfico de forma inesperada, depurar el problema puede ser frustrante. Un enfoque sistemático ahorra horas de conjeturas.
# Step 1: Verify which policies apply to the target pod
kubectl get networkpolicy -n production -o wide
# Step 2: Check pod labels match policy selectors
kubectl get pod api-server-abc123 -n production --show-labels
# Step 3: Test connectivity from source pod
kubectl exec -it frontend-xyz789 -n production -- \
wget --timeout=3 -qO- http://api-server:8080/health
# Step 4: Check if CNI plugin supports network policies
# (Not all do! Flannel without Calico won't enforce policies)
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave'// Automated connectivity diagnostic
interface ConnectivityDiagnostic {
sourcePopod: string;
targetPod: string;
port: number;
policiesApplied: string[];
cniSupportsPolicy: boolean;
namespaceLabelsCorrect: boolean;
podLabelsMatch: boolean;
portInPolicy: boolean;
}
function diagnoseBlockedTraffic(
diag: ConnectivityDiagnostic
): string[] {
const suggestions: string[] = [];
if (!diag.cniSupportsPolicy) {
suggestions.push(
"CNI plugin does not support NetworkPolicy. " +
"Install Calico, Cilium, or another policy-aware CNI."
);
}
if (diag.policiesApplied.length === 0) {
suggestions.push(
"No policies found for target pod. " +
"Check if default-deny exists in this namespace."
);
}
if (!diag.podLabelsMatch) {
suggestions.push(
"Source pod labels don't match any ingress 'from' selector. " +
"Verify labels on both pods and in the policy."
);
}
if (!diag.portInPolicy) {
suggestions.push(
`Port ${diag.port} is not listed in any matching policy's port list.`
);
}
return suggestions;
}Conclusiones clave
Las políticas de red transforman la seguridad de Kubernetes: pasan de una red plana y abierta a una arquitectura correctamente segmentada. El principio más importante es la denegación por defecto: empieza bloqueando todo y luego crea reglas de permiso explícitas para los patrones de tráfico conocidos. Este enfoque garantiza que los nuevos despliegues sean seguros por defecto en lugar de quedar expuestos por accidente.
Los errores más comunes son predecibles: olvidar el DNS en las reglas de Egress, no etiquetar los namespaces para las políticas entre namespaces, y desplegar en un clúster cuyo CNI no admite políticas. Prueba tus políticas en staging con sondas de conectividad automatizadas, y trátalas como código: versionadas, revisadas y desplegadas mediante CI junto con las cargas de trabajo que protegen.


