Imagina que tienes 40 microservicios hablando entre ellos en texto plano. De repente, un equipo te pide hacer un canary deployment del 5% para una nueva versión; otro pregunta por qué el proceso de checkout tarda 300 ms; y el equipo de seguridad exige cifrado en tránsito dentro de todo el clúster.
¿Cómo lo resuelves? Podrías intentar meter lógica de red en cada librería de cada servicio (y repetirlo para Go, Python y Java…), o puedes mover toda esa responsabilidad a la capa de red.
Eso es, en esencia, un Service Mesh. Sin embargo, también es la pieza de infraestructura que más veces he visto instalar en entornos de producción sin que nadie sepa exactamente qué problema está resolviendo. En esta guía vamos a comparar las tres opciones serias que quedan en 2026 y, lo más importante, dedicaremos una sección entera a la pregunta más rentable que te puedes hacer: cuándo NO instalarlo.
🎯 Qué problema resuelve realmente
Un service mesh intercepta el tráfico entre pods y aplica políticas sin que tengas que tocar ni una sola línea de código de tu aplicación. Básicamente, estas cuatro capacidades son las que justifican su existencia:
mTLS automático
Cifrado y autenticación mutua entre todos los pods, con rotación de certificados gestionada automáticamente. Sin un mesh, cada servicio necesitaría su propia gestión de certificados. Ojo: esto complementa —no sustituye— a las NetworkPolicies y RBAC.
Observabilidad L7
Obtienes las golden signals (tasa de peticiones, errores, latencia p50/p95/p99) por cada par origen-destino sin instrumentar la app. Mientras que una NetworkPolicy solo ve IPs y puertos, un mesh entiende códigos HTTP y rutas.
Traffic splitting
Te permite enviar, por ejemplo, el 5% del tráfico a una versión nueva. Sin un mesh, esto es un juego de azar con las réplicas de tus Deployments (si tienes 3 pods, lo mínimo que puedes enviar es un 33%).
Retries, timeouts y circuit breaking
Reintentos inteligentes, timeouts por ruta y eyección de instancias que fallan, aplicados de forma consistente en todos los lenguajes de tu stack.
Lo que un service mesh NO resuelve
No arregla una aplicación lenta, no sustituye a un balanceador de carga de entrada, no elimina la necesidad de trazas distribuidas con contexto de negocio (el mesh propaga headers, pero tu app debe reenviarlos) y no compensa una arquitectura de microservicios mal diseñada.
🏗️ Arquitectura: sidecar vs sidecarless
La gran diferencia técnica entre las tres opciones actuales es, fundamentalmente, dónde vive el proxy.

Istio — Envoy, dos modos
- Sidecar: Un contenedor Envoy inyectado en cada pod. Tienes máxima potencia (todo el filtro L7 de Envoy), pero también el máximo coste: un proxy por cada pod.
- Ambient mode: La respuesta de Istio al coste del sidecar. Aquí no hay sidecars; un agente Rust llamado
ztunnelgestiona el mTLS y L4 a nivel de nodo, y los waypoint proxies (Envoy) se despliegan solo donde realmente necesitas capa L7. - Plano de control:
istiod.
Linkerd — micro-proxy en Rust
- Usa sidecars, pero con un proxy propio escrito en Rust (
linkerd2-proxy) en lugar de Envoy. Su filosofía es: «menos es más». Menos superficie de ataque, menos memoria y menos configuración que romper. - No te llena de CRDs masivos; usa el estándar
HTTPRoutede la Gateway API para el routing. - Plano de control:
linkerd-destination,linkerd-identity,linkerd-proxy-injector.
Cilium Service Mesh — eBPF primero
- Su datapath es eBPF directamente en el kernel: no hay proxy en el camino para tráfico L3/L4. Como Cilium suele ser ya tu CNI, el mesh no añade una capa extra de complejidad.
- Ofrece cifrado con WireGuard o IPsec a nivel de nodo y autenticación basada en SPIFFE/SPIRE.
- Si necesitas L7 (routing HTTP), delega el trabajo a un Envoy por nodo, activándolo solo cuando la política lo requiere.
Cilium no es un service mesh completo
Cilium es increíble en L3/L4, cifrado y observabilidad (Hubble), pero no tiene el equivalente a retries con presupuesto, circuit breaking por outlier detection ni traffic splitting interno (pod a pod) al nivel de Istio o Linkerd. Su traffic splitting pasa por la Gateway API (el borde). Si buscas canary entre servicios internos, esto es un factor decisivo.
🚀 Instalación real
Si estás listo para probarlos, aquí tienes los comandos clave para cada uno.
Istio — ambient mode (recomendado en despliegues nuevos)
# Instalar Istio con el perfil ambient
istioctl install --set profile=ambient --skip-confirmation
# Enrolar un namespace en ambient (el CNI plugin usará ztunnel
# para los pods nuevos y los que se reinicien)
kubectl label namespace mi-app istio.io/dataplane-mode=ambient
# Verificar que los workloads están enrolados
istioctl ztunnel-config workloads
Istio — modo sidecar
istioctl install --skip-confirmation
# Habilitar inyección de sidecar por namespace
kubectl label --overwrite namespace mi-app istio-injection=enabled
# Los pods existentes NO se inyectan solos: hay que reiniciarlos
kubectl rollout restart deployment -n mi-app
Migración de sidecar a ambient
Al pasar un namespace a ambient, recuerda quitar la etiqueta de inyección (istio-injection o la de revisión) y añadir istio.io/dataplane-mode=ambient. Si dejas ambas, acabarás con un sidecar y un ztunnel funcionando a la vez.
Linkerd
# Comprobar el CLI (la versión de servidor aparecerá tras instalar el control plane)
linkerd version
# 1) CRDs, 2) control plane
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
# Validar la instalación completa
linkerd check
Para la inyección por namespace, usa esta anotación:
apiVersion: v1
kind: Namespace
metadata:
name: mi-app
annotations:
linkerd.io/inject: enabled
Cilium Service Mesh
# Gateway API (requiere kube-proxy replacement)
cilium install \
--set kubeProxyReplacement=true \
--set gatewayAPI.enabled=true
# Mutual authentication basada en SPIRE
cilium install \
--set authentication.mutual.spire.enabled=true \
--set authentication.mutual.spire.install.enabled=true
# Balanceo L7 con Envoy
cilium install \
--set kubeProxyReplacement=true \
--set envoyConfig.enabled=true \
--set loadBalancer.l7.backend=envoy
# Observabilidad
cilium hubble enable
🔐 mTLS y políticas de autorización
Una vez instalado, ¿cómo aplicamos la seguridad? Cada uno tiene su forma de entender el mundo.
Istio
Para aplicar mTLS estricto en todo un namespace:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: mi-app
spec:
mtls:
mode: STRICT
Y para aislar un namespace (que solo acepte tráfico de su propio entorno):
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: mi-app-isolation
namespace: mi-app
spec:
action: ALLOW
rules:
- from:
- source:
namespaces: ["mi-app"]
Migra a STRICT con PERMISSIVE de por medio
Nunca apliques STRICT directamente sobre un namespace con clientes que aún no tengan mesh; cortarás el tráfico al instante. Usa primero el modo PERMISSIVE para verificar en tus métricas que todo va cifrado y, una vez confirmado, endurece la política.
Linkerd
Aquí la vida es más fácil: el mTLS entre pods inyectados está activo por defecto. Las políticas se gestionan por ruta:
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: authors-get-policy
namespace: booksapp
spec:
targetRef:
group: policy.linkerd.io
kind: HTTPRoute
name: authors-get-route
requiredAuthenticationRefs:
- name: authors-get-authn
kind: MeshTLSAuthentication
group: policy.linkerd.io
---
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: authors-get-authn
namespace: booksapp
spec:
identities:
- "books.booksapp.serviceaccount.identity.linkerd.cluster.local"
- "webapp.booksapp.serviceaccount.identity.linkerd.cluster.local"
Cilium
En Cilium, la autenticación mutua vive dentro de la propia CiliumNetworkPolicy:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-requiere-auth
namespace: mi-app
spec:
endpointSelector:
matchLabels:
app: api
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
authentication:
mode: "required"
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/v1/.*"
Con mode: "required", Cilium solo permitirá la conexión si ambos workloads se han autenticado mediante SPIRE.
🔀 Canary y traffic splitting
¿Cómo gestionamos el despliegue gradual de nuevas versiones?
Istio — VirtualService con pesos
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews-route
spec:
hosts:
- reviews.prod.svc.cluster.local
http:
- route:
- destination:
host: reviews.prod.svc.cluster.local
subset: v1
weight: 75
- destination:
host: reviews.prod.svc.cluster.local
subset: v2
weight: 25
Linkerd — Gateway API HTTPRoute
apiVersion: policy.linkerd.io/v1beta2
kind: HTTPRoute
metadata:
name: bb-route
namespace: traffic-shift-demo
spec:
parentRefs:
- name: bb
kind: Service
group: core
port: 8080
rules:
- backendRefs:
- name: bb
port: 8080
weight: 90
- name: bb-v2
port: 8080
weight: 10
Cilium — Gateway API en el borde
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: echo-route
spec:
parentRefs:
- name: cilium-gw
hostnames:
- "*"
rules:
- matches:
- path:
type: PathPrefix
value: /echo
backendRefs:
- name: echo-1
port: 8080
weight: 99
- name: echo-2
port: 8090
weight: 1
No shiftees tráfico a mano
Editar pesos manualmente en producción es la receta perfecta para un incidente un viernes por la tarde. Tanto Istio como Linkerd se integran con Flagger, que automatiza el proceso de canary basándose en métricas reales y hace rollback automático si algo falla.
📊 Comparativa
Para que lo veas de un vistazo:
| Aspecto | Istio (ambient) | Istio (sidecar) | Linkerd | Cilium |
|---|---|---|---|---|
| Datapath | ztunnel (Rust) + waypoint Envoy | Envoy por pod | Micro-proxy Rust por pod | eBPF + Envoy por nodo |
| mTLS por workload | ✅ | ✅ | ✅ (por defecto) | ⚠️ SPIFFE auth + WireGuard/IPsec |
| Traffic split interno | ✅ | ✅ | ✅ | ❌ solo vía gateway |
| Retries / circuit breaking | ✅ (waypoint) | ✅ completo | ✅ básico | ❌ |
| Observabilidad | Prometheus + Kiali | Prometheus + Kiali | linkerd viz |
Hubble |
| Multi-cluster | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ Cluster Mesh |
| Superficie de config | Alta | Muy alta | Baja | Media |
| Curva de aprendizaje | ⭐⭐ dura | ⭐ muy dura | ⭐⭐⭐⭐⭐ suave | ⭐⭐⭐ (asume Cilium CNI) |
| Requisito de CNI | Cualquiera | Cualquiera | Cualquiera | Cilium obligatorio |
| Gobernanza | CNCF Graduated | CNCF Graduated | CNCF Graduated | CNCF Graduated |
⚡ Overhead: órdenes de magnitud, no promesas
Sobre estas cifras
Las siguientes cifras son órdenes de magnitud basadas en benchmarks del Service Mesh Performance de la CNCF. No son mediciones propias. El overhead real dependerá de tu RPS, el tamaño de tus payloads y de si tienes filtros L7 activos. Mide siempre en tu propio entorno.
| Modelo | Latencia añadida (p99, orden) | CPU/memoria por pod (orden) |
|---|---|---|
| Istio sidecar (Envoy) | Unidades de ms (~2-5 ms) | ~50-100 MB y decenas de milicores por pod |
| Istio ambient (solo ztunnel, L4) | Menos de 1 ms | Coste por nodo, no por pod |
| Istio ambient + waypoint L7 | Unidades de ms en el salto L7 | Coste por namespace/servicio |
| Linkerd micro-proxy | Sub-milisegundo a ~1 ms | ~10-20 MB por pod |
| Cilium eBPF (L4) | Prácticamente nulo (sin proxy) | Coste por nodo (agente) |
| Cilium con Envoy L7 | Unidades de ms en flujos L7 | Coste por nodo |
Lo que sí podemos concluir con seguridad es esto:
1. El modelo por pod no escala bien. 1.000 pods con Envoy son 1.000 proxies consumiendo memoria base. Ambient y eBPF mueven ese coste al nivel del nodo.
2. L7 tiene un precio. Parsear HTTP siempre será más caro que reenviar bytes. Si solo necesitas mTLS y L4, no pagues por L7.
3. La latencia rara vez es el problema real. El verdadero cuello de botella suele ser el consumo de recursos y la carga operativa.
🔭 Observabilidad
Los tres se llevan de maravilla con Prometheus y encajan en cualquier stack de observabilidad.
# Istio: dashboard Kiali (topología + tráfico en vivo)
istioctl dashboard kiali
# Linkerd: extensión viz, métricas golden signals en vivo
linkerd viz install | kubectl apply -f -
linkerd viz dashboard
# Cilium: flujos L3/L4/L7 con Hubble
cilium hubble enable
hubble observe --namespace mi-app --protocol http
Métricas != trazas
Ningún mesh genera trazas distribuidas completas por sí solo. Propagan los headers, pero si tu aplicación no reenvía los headers traceparent / b3 entre peticiones, las trazas saldrán rotas. Ese es un trabajo de la aplicación, no del mesh.
🛑 Cuándo NO necesitas un service mesh
Esta es la sección más importante de esta guía. Un service mesh es una base de datos de configuración de red distribuida delante de todo tu tráfico; si falla, falla todo.
No lo instales si:
- Tienes menos de ~10 servicios. Con 5 servicios te conoces todas las llamadas de memoria. El mesh te dará más dolores de cabeza que soluciones.
- Todo tu stack usa un solo lenguaje. Si todo es Go o todo es Java, una librería compartida (o gRPC con interceptores) te da lo mismo con mucha menos maquinaria.
- Tu problema es de entrada, no de «este-oeste». Si solo necesitas TLS y routing por host, un Ingress Controller o Gateway API es suficiente.
- Solo necesitas «cifrado en tránsito» sin identidad por workload. WireGuard a nivel de CNI (como Cilium) lo cubre con un coste ínfimo. Si el auditor pide identidad criptográfica por servicio, entonces sí ve a por el mTLS del mesh.
- Tu segmentación se resuelve con NetworkPolicies. «Frontend habla con API» es una NetworkPolicy, no requiere un mesh.
- No tienes un equipo de plataforma. Un mesh requiere alguien que entienda su modelo de datos y sepa depurarlo a las 3 de la mañana. Sin ese dueño, el mesh es deuda técnica con un dashboard bonito.
- Aún no tienes métricas básicas. Si ni siquiera tienes Prometheus funcionando, estás intentando construir la casa empezando por el tejado.
El coste oculto: depuración
Con un mesh, un error 503 puede venir de la app, del proxy origen, del proxy destino, de un timeout de política, de un certificado caducado o de una política de autorización mal escrita. La superficie de depuración se multiplica exponencialmente.
🧭 Guía de decisión
Para terminar, te dejo este resumen rápido:
- Ya usas Cilium y solo quieres mTLS + observabilidad L4/L7 y Gateway API $\rightarrow$ Cilium Service Mesh. Sin componentes nuevos y coste casi nulo.
- Quieres mTLS y golden signals mañana, con el mínimo de conceptos nuevos $\rightarrow$ Linkerd. Es el mesh que se instala en una tarde y no te despierta por la noche.
- Necesitas routing L7 complejo, multi-cluster o políticas de autorización muy finas $\rightarrow$ Istio en ambient mode. Potencia máxima sin el impuesto del sidecar.
- Tienes una inversión enorme en filtros Envoy a medida $\rightarrow$ Istio sidecar, hasta que el modo ambient esté maduro para tu caso.
- Ninguna de las anteriores encaja $\rightarrow$ Probablemente no necesites un service mesh todavía. Vuelve cuando el problema sea real.
🔗 Enlaces relacionados
- Kubernetes – Orquestación de contenedores
- Probes en Kubernetes
- Seguridad en Kubernetes: RBAC y NetworkPolicies
- Comparación de balanceadores de carga
- Stack de observabilidad
- Comparación VPN Overlay
