DEV Community

Cover image for Service mesh: cómo Envoy gestiona el tráfico entre microservicios
lu1tr0n
lu1tr0n

Posted on Originally published at elsolitario.org

Service mesh: cómo Envoy gestiona el tráfico entre microservicios

Cuando un pod de Kubernetes se cae a mitad de una petición, tu aplicación no tiene por qué enterarse: un proxy que corre al lado de cada contenedor puede reintentar la llamada, abrir un circuit breaker y cifrar la conexión con mTLS antes de que tu código note algo raro. Ese proxy es la pieza central de un service mesh, la capa de infraestructura que separa la lógica de red de la lógica de negocio en arquitecturas de microservicios.

En esta guía vas a ver cómo funciona el patrón sidecar, cómo instalar Istio con Envoy en un clúster real, y en qué casos ese proxy extra no vale la pena.

TL;DR

  • Vas a entender qué hace el patrón sidecar y por qué separa la lógica de red del código de tu aplicación.- Vas a poder instalar Istio con Envoy en un clúster de Kubernetes real usando istioctl.- Vas a saber escribir un VirtualService para repartir tráfico entre dos versiones del mismo servicio (canary release).- Vas a poder activar mTLS automático entre servicios con un PeerAuthentication de pocas líneas.- Vas a configurar un circuit breaker con outlierDetection sin tocar el código de tu servicio.- Vas a distinguir cuándo conviene un service mesh con sidecar y cuándo conviene un enfoque sidecarless con eBPF.- Vas a conocer los comandos exactos para verificar que el sidecar está sincronizado con el control plane.

Qué es un service mesh y por qué importa

Un service mesh es una capa de infraestructura dedicada a manejar la comunicación entre servicios dentro de una arquitectura de microservicios. En vez de que cada servicio implemente su propia lógica de reintentos, balanceo de carga, cifrado y observabilidad, esa responsabilidad se delega a un proxy que corre junto a cada instancia del servicio.

La idea nació de un problema concreto: cuando una empresa pasa de un monolito a decenas o cientos de microservicios, la comunicación entre ellos (el llamado tráfico este-oeste) se vuelve tan compleja como la lógica de negocio misma. Cada equipo termina reimplementando circuit breakers, reintentos con backoff y métricas de latencia en el lenguaje que le toque: Java, Go, Python. Eso duplica trabajo y multiplica bugs.

Un service mesh resuelve esto moviendo esa lógica fuera del proceso de la aplicación y metiéndola en un proxy de red que intercepta cada paquete que entra o sale del contenedor. El código de tu aplicación sigue haciendo llamadas HTTP o gRPC normales; el proxy decide si reintenta, si cifra, si corta el tráfico o si lo manda a una versión distinta del servicio.
Cada pod suma un contenedor Envoy: el sidecar intercepta todo el tráfico de red.

Cómo funciona: el patrón sidecar

El componente central de casi todo service mesh moderno es Envoy, un proxy de alto rendimiento escrito en C++ que Lyft liberó como open source y que hoy es un proyecto graduado de la Cloud Native Computing Foundation. Envoy no se instala como un servicio central: se inyecta como un contenedor adicional, el sidecar, dentro de cada pod de Kubernetes, junto al contenedor de tu aplicación.

Esto significa que un pod con un service mesh activo no tiene un contenedor, tiene dos: tu aplicación y el sidecar de Envoy. Todo el tráfico de red que entra o sale del pod pasa primero por reglas de iptables que Istio configura automáticamente, y esas reglas redirigen el tráfico al puerto donde escucha Envoy antes de dejarlo seguir su camino.

Arquitectónicamente, un service mesh se divide en dos planos: el data plane, formado por todos los proxies Envoy que efectivamente mueven los paquetes, y el control plane, que en Istio se llama Istiod y le dice a cada Envoy qué reglas de enrutamiento, qué certificados y qué políticas de seguridad aplicar. Esa comunicación entre Istiod y los Envoys ocurre por el protocolo xDS, una API de gRPC que originó el propio proyecto Envoy.

Para ilustrar el flujo real de una petición entre dos servicios con mTLS activado:

sequenceDiagram
    participant App1 as App Pedidos
    participant Sc1 as Sidecar Envoy 1
    participant Sc2 as Sidecar Envoy 2
    participant App2 as App Pagos
    App1->>Sc1: peticion HTTP en texto plano
    Sc1->>Sc2: conexion mTLS cifrada
    Sc2->>App2: peticion HTTP en texto plano
    App2-->>Sc2: respuesta
    Sc2-->>Sc1: respuesta cifrada
    Sc1-->>App1: respuesta en texto plano
    Note over Sc1,Sc2: Istiod distribuyo los certificados via xDS
Enter fullscreen mode Exit fullscreen mode

Ni la app de Pedidos ni la de Pagos escriben una sola línea de código para el cifrado: los dos sidecars negocian el mTLS entre ellos de forma transparente.

Ejemplos prácticos: instalar Istio y ver el sidecar en acción

El primer paso siempre es tener el control plane corriendo en el clúster. Esto instala Istiod y habilita la inyección automática de sidecars:

curl -L https://istio.io/downloadIstio | sh -
cd istio-*
export PATH=$PWD/bin:$PATH
istioctl install --set profile=demo -y
kubectl label namespace default istio-injection=enabled
Enter fullscreen mode Exit fullscreen mode

El comando istioctl install despliega Istiod con el perfil demo (pensado para pruebas, con todos los componentes activos). El kubectl label marca el namespace default para que cada pod nuevo reciba automáticamente el contenedor sidecar sin que edites un solo manifiesto de deployment.

Con el mesh activo, el siguiente paso típico es repartir tráfico entre dos versiones del mismo servicio para hacer un canary release. Esto se define con un VirtualService y un DestinationRule:

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: pagos-api
spec:
  host: pagos-api
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2
---
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
  name: pagos-api
spec:
  hosts:
    - pagos-api
  http:
    - route:
        - destination:
            host: pagos-api
            subset: v1
          weight: 90
        - destination:
            host: pagos-api
            subset: v2
          weight: 10
Enter fullscreen mode Exit fullscreen mode

El DestinationRule define qué pods pertenecen a cada versión según sus labels; el VirtualService reparte el 90% del tráfico a v1 (la versión estable) y el 10% a v2 (la versión candidata). Si v2 empieza a fallar, cambiás el peso a 0 sin reiniciar ningún pod y sin que el cliente note nada distinto.

Para activar mTLS estricto entre todos los servicios del namespace istio-system (y por extensión, del clúster), el objeto es un PeerAuthentication:

apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
Enter fullscreen mode Exit fullscreen mode

Con mode: STRICT, cada sidecar rechaza conexiones que no vengan cifradas con el certificado que Istiod emitió para ese servicio. Los certificados se rotan automáticamente sin que ningún desarrollador tenga que generar ni copiar un archivo .pem.

Circuit breakers y reintentos: resiliencia sin código extra

Además de enrutar tráfico, cada sidecar puede aplicar políticas de resiliencia que tradicionalmente vivían en librerías como Hystrix o resilience4j, pero ahora corren fuera del proceso de la aplicación. Esto se configura en el mismo DestinationRule que ya usaste para el canary:

apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
  name: pagos-api-resiliencia
spec:
  host: pagos-api
  trafficPolicy:
    connectionPool:
      http:
        http1MaxPendingRequests: 50
        maxRequestsPerConnection: 10
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 60s
      maxEjectionPercent: 50
Enter fullscreen mode Exit fullscreen mode

El bloque outlierDetection es el circuit breaker: si un pod de pagos-api devuelve 5 errores 5xx seguidos en una ventana de 30 segundos, Envoy lo saca del balanceo de carga por 60 segundos, sin que el cliente que llama a pagos-api tenga que implementar esa lógica. El connectionPool limita cuántas peticiones pendientes y por conexión acepta cada pod, evitando que un servicio lento tumbe en cascada a los que dependen de él.

Esto reemplaza patrones que antes requerían una librería específica por lenguaje: un servicio en Go y otro en Java pueden compartir exactamente la misma política de circuit breaking, porque la aplica el sidecar y no el runtime de cada lenguaje.

Observabilidad: métricas, trazas y logs sin instrumentar código

Como cada Envoy ve absolutamente todo el tráfico que entra y sale de su pod, expone automáticamente métricas en formato Prometheus: tasa de peticiones, latencia por percentil, códigos de respuesta y tamaño de payload, todo etiquetado por servicio, versión y namespace. Estas métricas se consultan con una petición simple:

kubectl exec pagos-api-7d4f9c-x2k1p -c istio-proxy -- \
  curl -s localhost:15000/stats/prometheus | grep istio_requests_total
Enter fullscreen mode Exit fullscreen mode

Ese comando confirma, sin adivinar, que el sidecar está contando peticiones reales: si el contador istio_requests_total sube con cada llamada, el mesh está activo y midiendo tráfico. Sumado a Grafana para dashboards y Jaeger para trazas distribuidas, un equipo obtiene observabilidad de extremo a extremo sin agregar una sola librería de instrumentación al código de sus servicios; solo necesita propagar las cabeceras de trazado (x-request-id, x-b3-traceid) entre llamadas salientes.

💡 Tip: si tu app hace varias llamadas salientes dentro de una misma petición entrante, tenés que reenviar manualmente las cabeceras x-b3-* que llegan; el sidecar no puede inventar el contexto de traza que tu propio código no propaga.

Cómo empezar: de cero a tu primer mesh en Kubernetes

Para adoptar un service mesh sin romper nada en producción, el orden recomendado es este:

  • Instalar Istio en un clúster de staging con el perfil demo, como en el bloque anterior.- Etiquetar solo un namespace de prueba con istio-injection=enabled, nunca todo el clúster de una sola vez.- Redeployar los pods de ese namespace (kubectl rollout restart deployment) para que reciban el sidecar: la inyección solo ocurre en la creación del pod.- Verificar que el sidecar está corriendo con kubectl get pod <nombre> -o jsonpath='{.spec.containers[*].name}': deberías ver istio-proxy junto al contenedor de tu app.- Confirmar que el sidecar está sincronizado con el control plane con istioctl proxy-status: la columna SYNCED debe decir SYNCED para cada proxy, no STALE.- Activar mTLS en PERMISSIVE primero (acepta tráfico cifrado y sin cifrar a la vez) y recién después pasar a STRICT, cuando confirmes que ningún servicio quedó fuera del mesh.

⚠️ Ojo: si activás STRICT antes de que todos los pods tengan el sidecar inyectado, esos servicios sin sidecar quedan incomunicados de inmediato: no pueden hablar mTLS porque no tienen con qué.
istioctl proxy-status es el primer comando a correr cuando algo no enruta.

Casos de uso reales

Los equipos que adoptan un service mesh casi siempre lo hacen por una de estas cuatro razones:

  • Seguridad zero-trust: cifrar todo el tráfico interno con mTLS sin pedirle a cada equipo que maneje certificados manualmente.- Despliegues progresivos: canary releases y blue-green deployments a nivel de tráfico, usando pesos de enrutamiento como el ejemplo de pagos-api.- Resiliencia: circuit breakers, reintentos con backoff exponencial y timeouts configurados de forma centralizada, en vez de que cada microservicio traiga su propia librería.- Observabilidad uniforme: como todo el tráfico pasa por Envoy, las métricas de latencia, tasa de error y trazas distribuidas salen gratis, sin instrumentar cada servicio a mano.

Un ejemplo típico: un equipo de pagos quiere lanzar una nueva versión de su servicio, pero no confía en probarla contra el 100% del tráfico real. Con el VirtualService de la sección anterior, exponen la v2 solo al 10% del tráfico, miran las métricas de error en Prometheus, y si todo está bien, suben el peso gradualmente hasta el 100%. Si algo falla, bajan el peso a 0 en segundos, sin rollback de deployment.

stateDiagram-v2
    [*] --> V1_100
    V1_100 --> V1_90_V2_10 : iniciar canary
    V1_90_V2_10 --> V1_50_V2_50 : metricas ok
    V1_50_V2_50 --> V2_100 : promocion completa
    V1_90_V2_10 --> V1_100 : rollback por error rate
    V1_50_V2_50 --> V1_100 : rollback por error rate
Enter fullscreen mode Exit fullscreen mode

Errores comunes y gotchas

  • Orden de arranque: el sidecar de Envoy debe estar listo antes de que la app empiece a hacer llamadas de red, porque las reglas de iptables ya están activas. Istio resuelve esto con un init-container, pero con un readinessProbe mal configurado el pod puede quedar en CrashLoopBackOff los primeros segundos.- Consumo de memoria y CPU: cada sidecar es un proceso más por pod. En un clúster con cientos de pods, eso se traduce en un costo de infraestructura no trivial que hay que sumar antes de decidir adoptar un mesh.- Latencia agregada: cada petición ahora pasa por dos proxies extra (el del cliente y el del servidor) antes de llegar a destino. Hay que medirlo con istioctl proxy-config antes de asumir que no importa.- Namespaces sin etiquetar: es fácil olvidar etiquetar un namespace nuevo con istio-injection=enabled y terminar con servicios que quedan fuera del mesh sin que nadie lo note hasta que falla el mTLS estricto.- Confundir el service mesh con un API Gateway: el mesh gestiona tráfico este-oeste (entre servicios internos); un API Gateway gestiona tráfico norte-sur (entre el cliente externo y el clúster). Son piezas complementarias, no sustitutas.

Comparativa con alternativas

OpciónCuándo usarlaVentajaLimitaciónIstio + EnvoyClústeres grandes que necesitan mTLS, canary y observabilidad completaEcosistema más maduro, control granular vía CRDsCurva de aprendizaje alta, un sidecar por podLinkerdEquipos que quieren mTLS y métricas básicas sin configurar muchoProxy propio en Rust, mucho más liviano que EnvoyMenos funciones de enrutamiento avanzado que IstioConsul ConnectEmpresas que ya usan Consul para service discoverySe integra con infraestructura no Kubernetes (VMs, bare metal)Ecosistema de observabilidad menos rico que IstioCilium (modo sidecarless)Clústeres donde el overhead de un sidecar por pod es inaceptableUsa eBPF en el kernel, sin contenedor extra por podRequiere kernel Linux moderno y menos madurez en mTLS de capa 7

Profundizando: xDS, Ambient Mesh y el futuro sin sidecar

El protocolo que conecta el control plane con cada Envoy se llama xDS (discovery service), y en realidad es una familia de APIs: LDS para listeners, RDS para rutas, CDS para clusters (grupos de endpoints) y EDS para los endpoints individuales. Cada vez que Istiod detecta un cambio (un pod nuevo, un VirtualService editado), empuja la actualización a todos los Envoys afectados por gRPC streaming, sin que el proxy tenga que hacer polling.

flowchart TD
    A["kubectl apply VirtualService"] --> B["Istiod (control plane)"]
    B -- "xDS por gRPC" --> C["Envoy sidecar Pod A"]
    B -- "xDS por gRPC" --> D["Envoy sidecar Pod B"]
    subgraph DataPlane
    C
    D
    end
Enter fullscreen mode Exit fullscreen mode

El costo del sidecar (un contenedor por pod, con su propio consumo de memoria) fue la crítica más constante contra los service mesh desde que existen. La respuesta más reciente de la comunidad de Istio es Ambient Mesh, un modo que saca el proxy L7 del pod y lo mueve a un proxy compartido por nodo, reservando el sidecar solo para los casos que necesitan mTLS o políticas L7 avanzadas. Cilium, por su parte, resuelve el mismo problema desde otro ángulo: en vez de un proxy de usuario, usa programas eBPF que corren directamente en el kernel de Linux para aplicar políticas de red sin el overhead de un proceso extra por pod.

Ninguno de los dos enfoques elimina la complejidad del problema, solo la reubica: cambiás un contenedor más por pod por confiar en un proxy compartido por nodo, o por depender de una versión de kernel específica para tus programas eBPF. Elegir entre sidecar, ambient o eBPF sigue siendo, en 2026, una decisión de compromisos y no una respuesta única.

💭 Clave: un service mesh no reemplaza la necesidad de diseñar bien tus microservicios: mueve la complejidad de red fuera del código, pero la complejidad operativa (certificados, versiones, upgrades del control plane) sigue ahí, solo que ahora la maneja el equipo de plataforma en vez de cada equipo de producto.

📖 Resumen en Telegram: Ver resumen

Tu próximo paso: instalá Istio en un clúster local con minikube o kind, etiquetá un namespace de prueba y corré istioctl proxy-status para ver tu primer sidecar sincronizado con el control plane.

Preguntas frecuentes

¿Un service mesh reemplaza a un API Gateway?

No. El service mesh gestiona el tráfico este-oeste entre microservicios internos; el API Gateway gestiona el tráfico norte-sur entre clientes externos y el clúster. Muchas arquitecturas usan ambos a la vez, y de hecho Istio puede actuar como gateway de entrada además de mesh interno.

¿Necesito Kubernetes para usar un service mesh?

No estrictamente: Consul Connect, por ejemplo, funciona también con máquinas virtuales y bare metal. Pero la enorme mayoría de las implementaciones de Istio y Linkerd asumen Kubernetes como plataforma base.

¿Cuánta latencia agrega un sidecar de Envoy?

Depende de la carga y la configuración, pero la forma correcta de saberlo en tu caso es medir con istioctl proxy-config y comparar percentiles de latencia con y sin el sidecar activo, en vez de asumir un número genérico.

¿Qué pasa si el control plane (Istiod) se cae?

Los sidecars siguen funcionando con la última configuración que recibieron: xDS no es una dependencia síncrona para cada petición. Lo que se pierde temporalmente es la capacidad de aplicar cambios nuevos de enrutamiento o seguridad hasta que Istiod vuelva.

¿Vale la pena un service mesh para un proyecto chico?

Rara vez. Si tenés menos de una decena de servicios y no necesitás mTLS obligatorio ni canary releases automatizados, el overhead operativo de mantener Istiod y los sidecars suele superar el beneficio.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Top comments (0)