¿Cómo funciona el descubrimiento de servicios — descubrimiento del lado del cliente vs. del lado del servidor?
Todo despliegue dinámico necesita responder '¿a qué dirección llamo ahora mismo?' — la decisión de diseño interesante es quién consulta el registro y dónde ocurre el balanceo de carga.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo funciona el descubrimiento de servicios — descubrimiento del lado del cliente vs. del lado del servidor?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#El problema: nada se queda donde lo dejaste
En un despliegue estático, "el servicio de inventario" vive para siempre en una IP fija, así que la escribes a mano en la configuración. En uno dinámico —contenedores reprogramados por un orquestador, instancias que escalan hacia arriba y hacia abajo, despliegues canary desviando tráfico— el conjunto de direcciones sanas detrás de "el servicio de inventario" cambia cada pocos minutos, a veces cada pocos segundos. El descubrimiento de servicios es el mecanismo que responde "¿a qué direcciones puedo enviarle esta petición ahora mismo?" sin que un humano edite un archivo de configuración cada vez que algo se reinicia.
Todo sistema de descubrimiento necesita tres piezas: un registro de servicios (el directorio de quién está vivo y dónde), un mecanismo de registro (cómo entra una instancia a ese directorio) y una verificación de salud (cómo sale del directorio una instancia muerta o que dejó de responder, antes de que cada cliente lo aprenda por las malas). La decisión de diseño interesante es quién consulta el registro y dónde se toma la decisión de balanceo de carga — y esa es exactamente la línea entre el descubrimiento del lado del cliente y el del lado del servidor.
#Descubrimiento del lado del cliente
El servicio que llama incorpora un cliente consciente del descubrimiento — históricamente una librería como Netflix Ribbon junto con un registro Eureka, aunque la misma forma aparece en cualquier lugar donde una librería cliente cachea el registro y balancea la carga por su cuenta. La instancia se registra a sí misma al arrancar (auto-registro) y envía latidos periódicos para probar que sigue viva. El cliente que llama le pregunta directamente al registro la lista actual de instancias sanas (a menudo cacheándola localmente y refrescándola cada cierto tiempo en lugar de consultar el registro en cada petición), y luego aplica su propio algoritmo de balanceo —round robin, ponderado, consciente de la zona, basado en latencia— para elegir una y llamarla directamente.
La ventaja es un salto de red menos y mucha flexibilidad: el cliente puede tomar decisiones de enrutamiento con información que un balanceador genérico no tiene (en qué zona estoy, qué instancia me respondió más rápido la última vez). La desventaja es que cada servicio que llama, en cada lenguaje que use tu organización, necesita una librería cliente de descubrimiento compatible que hable con la misma API de registro —doloroso en un entorno políglota— y cada cliente ahora carga lógica de descubrimiento, caché y balanceo que hay que implementar bien y mantener consistente en todas partes.
#Descubrimiento del lado del servidor
El servicio que llama ni siquiera sabe que el registro existe. Llama a una única dirección estable —un balanceador de carga, un API gateway, o un nombre DNS— y es ese intermediario quien consulta el registro (o mantiene su propia vista de él) y enruta la petición hacia una instancia sana. Así funciona un montaje clásico de AWS (los clientes llaman a un Elastic Load Balancer; el target group del ELB se mantiene sincronizado con las tareas ECS sanas) y, más relevante hoy, así funciona Kubernetes por defecto: un cliente llama al nombre DNS estable o al ClusterIP de un Service, el plano de control de Kubernetes mantiene los Endpoints de ese Service sincronizados con los Pods que en ese momento pasan su readiness probe, y kube-proxy programa la red del nodo (iptables/IPVS) para enrutar las conexiones hacia una de esas IPs de Pod sanas — así que en la práctica muchas veces no hay ningún salto extra en espacio de usuario, solo enrutamiento de conexiones a nivel de kernel.
La ventaja es que el cliente que llama es tonto por diseño: sin librería, sin dependencia específica del lenguaje hacia la API del registro, funciona igual para un script de bash que para un servicio Java de quince años. La lógica de descubrimiento y balanceo vive en un solo lugar y puede cambiar sin tocar a ningún llamador. La desventaja es que el balanceador de carga o la capa de red de la plataforma se convierte en infraestructura crítica y compartida: si es un salto de proxy literal, añade latencia y se convierte en algo que hay que escalar y operar; si es basado en DNS, heredas las rarezas de caché y TTL de DNS (un resolutor que ignora los TTL seguirá enviando tráfico a una instancia que ya desapareció).
#Auto-registro vs. registro por terceros
Ortogonal a la distinción cliente-servidor está quién escribe en el registro. En el auto-registro, el propio código de la instancia llama al registro al arrancar y envía sus propios latidos — sencillo, pero acopla tu código de aplicación a la API del registro y a implementar bien la lógica de apagado y latido en cada servicio. En el registro por terceros, algo externo a la aplicación lo hace: un sidecar, o la propia plataforma. Kubernetes es el ejemplo más limpio — tu aplicación ni se entera de que Kubernetes existe; el kubelet reporta la disponibilidad del Pod, y el plano de control actualiza los Endpoints en consecuencia. Hoy en día se prefiere generalmente el registro por terceros porque saca por completo el descubrimiento de la lógica de negocio.
#Eligiendo uno
| Lado del cliente | Lado del servidor | |
|---|---|---|
| Salto de red extra | No (el cliente llama directo a la instancia) | A menudo sí (salvo que sea a nivel de kernel, como kube-proxy) |
| Complejidad del cliente | Mayor — necesita librería consciente del descubrimiento | Menor — el cliente solo llama a una dirección estable |
| Amigable con entornos políglotas | Pobre — cada lenguaje necesita un cliente compatible | Excelente — cualquier cliente funciona |
| Dónde vive la lógica de balanceo | Distribuida en cada cliente | Centralizada, fácil de cambiar globalmente |
| Ejemplos típicos | Netflix Eureka + Ribbon | Kubernetes Services, AWS ELB, la mayoría de service meshes modernos |
La mayoría de los equipos que construyen sobre Kubernetes hoy obtienen descubrimiento del lado del servidor gratis y nunca piensan en ello — lo cual es en sí misma evidencia de por qué ganó ese enfoque: elimina toda una categoría de complejidad de cliente por lenguaje que la pila de la era Netflix tenía que resolver explícitamente.
:::danger Las entradas obsoletas son peores que no tener registro Un registro que solo elimina instancias mediante un TTL lento (por ejemplo, "expira tras 90 segundos sin latido") seguirá enrutando tráfico hacia una instancia muerta durante toda esa ventana, en cada cliente que cacheó su listado. Combina la expiración basada en latidos con verificaciones de salud activas, y mantén el TTL de caché en el cliente (o balanceador) corto en relación con la rapidez con la que realmente mueren las instancias en tu entorno — un contenedor que el kernel mata por OOM no tiene oportunidad de darse de baja con elegancia. :::
:::note El service mesh difumina la línea, a propósito Un sidecar de malla (Envoy en Istio/Linkerd) es técnicamente un punto de descubrimiento del lado del servidor ubicado junto a cada cliente, así que obtiene el beneficio de "sin librería cliente por lenguaje" del descubrimiento del lado del servidor, evitando a la vez un balanceador central compartido y potencialmente cuello de botella — cada salto solo pasa por el proxy local. Vale la pena nombrarlo como una tercera opción una vez que un equipo ha superado el "elige uno". :::