Patrones de microservicios
Los patrones que se preguntan (API gateway, descubrimiento de servicios, circuit breaker, service mesh y SAGA) y cómo se lleva a producción y se vigila una arquitectura repartida en decenas de servicios.
API gateway: la puerta de entrada
Si una aplicación se ha partido en cuarenta servicios, el cliente no puede tener que conocerlos a los cuarenta. El API gateway es el patrón que lo evita: una puerta única de entrada que aísla los servicios del exterior, recibe todas las peticiones y las encamina al servicio que corresponda, ocultando la complejidad de lo que hay detrás. Funciona como un proxy inverso que centraliza toda la lógica de acceso.
- Encaminamiento: decide a qué servicio interno le toca cada petición que llega.
- Autenticación y autorización en un solo sitio, en vez de repetidas en cada servicio.
- Limitación de tasa y control de cuotas, para que un cliente no sature el sistema.
- Terminación de TLS y traducción de protocolo entre lo que habla el cliente y lo que hablan los servicios.
- Agregación: componer en una sola respuesta datos que vienen de varios servicios, para ahorrarle viajes a un cliente móvil.
Los productos que popularizaron el patrón salieron de Netflix, que liberó su pila de microservicios. Zuul fue el primero en hacer de proxy inverso canalizando las peticiones, y se replica en muchas instancias precisamente porque un punto único de entrada es también un punto único de fallo si no se replica.
Un API gateway centraliza el acceso, no la lógica de negocio. En cuanto empieza a tomar decisiones propias del dominio deja de ser una puerta y se convierte en el monolito que se quería evitar.
Para el examen
Qué hace: enruta, autentica, limita peticiones y agrega respuestas
Su riesgo: si no se replica, se convierte en punto único de fallo
Descubrimiento de servicios y balanceo
En una arquitectura donde las instancias se crean y se destruyen solas, nadie puede tener escrita la dirección de un servicio en su configuración. El descubrimiento de servicios resuelve eso: existe un registro donde cada instancia se da de alta al arrancar diciendo quién es y dónde está, y va confirmando periódicamente que sigue viva. Quien necesita llamar a un servicio pregunta al registro por su nombre lógico y obtiene la lista de instancias disponibles en ese momento.
- La instancia arranca y se registra con su nombre lógico, su dirección y su puerto.
- Envía señales periódicas de que sigue en pie. Si dejan de llegar, el registro la da de baja.
- Quien llama pregunta por el nombre lógico, no por una dirección.
- El registro devuelve las instancias vivas, y el cliente o un balanceador reparte las llamadas entre ellas.
El producto de referencia en el ecosistema de Netflix es Eureka, el servidor donde los servicios se registran con sus metadatos, acompañado de Ribbon para repartir las peticiones entre las instancias disponibles. En un clúster de Kubernetes esta función viene de serie: el objeto Service es a la vez registro y balanceador, y los pods se resuelven por nombre sin que haya que instalar nada.
El descubrimiento de servicios es lo que permite escalar sin reconfigurar nada. Añadir una instancia consiste en arrancarla: ella sola se da de alta y empieza a recibir tráfico.
Para el examen
Qué sustituye: la lista fija de direcciones
Cómo funciona: se pregunta al registro dónde está el servicio
Por qué hace falta: las instancias nacen y mueren continuamente
Circuit breaker: cortar antes de que se propague el fallo
Cuando un servicio llama a otro y ese a otro, un fallo en el último tiene una forma muy desagradable de propagarse: quienes lo llaman se quedan esperando, agotan sus hilos y sus conexiones esperando, y acaban cayendo también. Es el fallo en cascada, y el patrón que lo corta se llama circuit breaker, cortacircuitos, por analogía directa con el interruptor automático de un cuadro eléctrico.
El cortacircuitos se coloca en quien llama y vigila cómo van las llamadas a un destino concreto. Mientras van bien, no hace nada. Cuando acumula demasiados fallos, corta: deja de intentarlo y devuelve el error de inmediato, sin salir a la red. Con eso consigue dos cosas a la vez: que el servicio que llama no se quede colgado, y que el servicio caído no reciba una avalancha de peticiones justo mientras intenta recuperarse.
| Estado | Qué ocurre |
|---|---|
| Cerrado | Estado normal. El circuito conduce y las llamadas pasan. Se van contando los fallos. |
| Abierto | Se ha superado el umbral de fallos. Las llamadas se rechazan al instante, sin salir a la red. |
| Semiabierto | Pasado un tiempo, se dejan pasar unas pocas llamadas de prueba. Si van bien, vuelve a cerrado; si fallan, vuelve a abierto. |
Los nombres de los estados se confunden constantemente, y en algunos apuntes aparecen invertidos. La forma segura de recordarlo es no pensar en puertas sino en electricidad: un circuito CERRADO deja pasar la corriente, y por tanto las llamadas; un circuito ABIERTO la corta. El cortacircuitos se completa con una política de reintentos y de tiempo de espera, y con una política de recuperación que es la que decide cuándo probar de nuevo.
Cerrado es el estado bueno y abierto el malo. Es al revés de lo que sugiere la intuición de una puerta, y es exactamente lo que se pregunta.
Para el examen
Cerrado: deja pasar el tráfico
Abierto: corta, tras acumular fallos
Semiabierto: prueba si el servicio se ha recuperado
Service mesh y el patrón sidecar
Los patrones anteriores tienen un problema común: hay que programarlos. El cortacircuitos, los reintentos, el descubrimiento y el cifrado acaban escritos como bibliotecas dentro de cada servicio, lo que ata cada servicio a un lenguaje y obliga a repetir el mismo trabajo en todos. El service mesh, o malla de servicios, es la respuesta a eso: saca toda esa fontanería fuera del código y la baja a la infraestructura.
El mecanismo con el que lo consigue es el patrón sidecar: junto a cada servicio se despliega un proxy, en el mismo pod, y todo el tráfico que entra y sale del servicio pasa por él. El servicio cree que llama directamente a su destino, pero quien realmente hace la llamada es el proxy, que aplica el descubrimiento, el balanceo, los reintentos, el cortacircuitos, el cifrado y la recogida de métricas sin que el código se entere.
| Plano | Qué es | Producto de referencia |
|---|---|---|
| Plano de datos | El conjunto de proxies sidecar, que son los que transportan y controlan el tráfico entre servicios. | Envoy |
| Plano de control | El punto central que configura a todos los proxies, comprueba las políticas de seguridad y recoge las métricas. | Istio, Linkerd |
- Abstracción: los desarrolladores dejan de ocuparse del descubrimiento, del balanceo y de la recuperación ante fallos.
- Centralización: un único punto desde el que gobernar el comportamiento de todos los servicios de la aplicación.
- Trazabilidad: cada petición recibe un identificador único que permite seguir su recorrido por todos los servicios y analizar dónde se originó un problema. Productos como Jaeger o Spring Cloud Sleuth.
- Observabilidad: salud, topología, métricas y cuadros de mando de lo que está pasando, con Prometheus, Grafana, Elasticsearch o Kiali.
- Seguridad: cifrado del tráfico entre servicios, autenticación y autorización, con productos de gestión de identidad como Keycloak.
Conviene situarlo frente a algo ya conocido: el ESB de una arquitectura SOA también centralizaba la comunicación, pero lo hacía en un bus único por el que pasaba todo. La malla no tiene bus: tiene un proxy por servicio, y lo único centralizado es la configuración de esos proxies. Cambia el punto único de fallo por una malla distribuida.
El sidecar convierte en configuración de infraestructura lo que antes era código repetido en cada servicio. Ese es el motivo por el que se adopta, más que ninguna funcionalidad concreta.
Para el examen
Sidecar: un contenedor auxiliar en el mismo pod
Qué saca de la aplicación la malla: cifrado, reintentos y observabilidad
Datos distribuidos: CQRS, 2PC y SAGA
Si cada microservicio tiene su propia base de datos, deja de existir la transacción que abarcaba a todo el sistema. Conviene precisar el matiz, porque muchos apuntes lo dicen mal: cada servicio sigue siendo perfectamente ACID dentro de su base. Lo que se ha perdido es la transacción ACID que cruce varios servicios, y para operaciones que tocan a varios hacen falta patrones específicos.
| Patrón | Qué resuelve | Coste |
|---|---|---|
| CQRS | Separa la responsabilidad de consultar de la de modificar. Como la inmensa mayoría de las operaciones son lecturas, el servicio se parte en dos: uno para las consultas y otro para las creaciones, modificaciones y borrados, cada uno optimizado y escalado por su cuenta. | Dos modelos que mantener y un desfase entre escribir y ver el cambio reflejado. |
| 2PC | Confirmación en dos fases: un coordinador pregunta primero a todos los participantes si pueden confirmar (fase de votación) y, solo si todos dicen que sí, les ordena confirmar (fase de confirmación). | Muy caro: bloquea recursos en todos los participantes mientras dura, y por eso apenas se usa en microservicios. |
| SAGA | Coordina un cambio que afecta a varios servicios sin bloquear a nadie: la transacción larga se parte en una secuencia de transacciones locales independientes, cada una en su servicio. | Hay que escribir una transacción compensatoria por cada paso. |
SAGA es la que hay que entender bien. Como cada paso ya se ha confirmado en su propia base de datos, no se puede deshacer con un rollback: lo que se hace es ejecutar una transacción compensatoria, una operación que cancela el efecto de la anterior. Si al matricular a un alumno se le cobra, se le da de alta en el curso y se le crea el calendario, y este último paso falla, no se revierte nada: se emite una devolución y una baja, que son las compensaciones de los dos pasos anteriores.
| Gobierno de la SAGA | Cómo funciona |
|---|---|
| Orquestación | Un componente central dirige la secuencia: llama a cada servicio en orden y decide qué compensar si algo falla. Es más fácil de seguir y de depurar. |
| Coreografía | No hay director: cada servicio reacciona a los eventos que publican los demás. Acopla menos, pero el recorrido completo no está escrito en ningún sitio. |
La diferencia que se pregunta: 2PC bloquea recursos y garantiza atomicidad inmediata; SAGA no bloquea nada y llega a un estado coherente al final, deshaciendo con compensaciones lo que ya estaba confirmado.
Para el examen
SAGA: cadena de pasos con sus compensaciones
A qué sustituye: a la transacción distribuida
2PC: bloquea a todos los participantes; no encaja en microservicios
Modos de despliegue y escalado
Un microservicio se puede llevar a producción de muchas formas, y la lista ordenada de menor a mayor delegación es materia de examen. Cuanto más abajo, menos infraestructura hay que administrar y menos control se conserva.
| Modo | Dónde se ejecuta la instancia |
|---|---|
| Máquina física | Directamente sobre el servidor, sin virtualización de ningún tipo. |
| Máquina virtual | Sobre una máquina virtual, con su sistema operativo invitado. |
| Contenedor | En un contenedor aislado, sobre un anfitrión físico o virtual, normalmente bajo un orquestador. |
| Contenedor de aplicaciones | Dentro de un servidor de aplicaciones que administra varias, como Tomcat o WebLogic. |
| PaaS | En instancias administradas por el proveedor, que abstrae toda la capa inferior. Heroku, App Engine, Beanstalk. |
| FaaS | Como una función sin servidor, que solo existe mientras se ejecuta. AWS Lambda, Google Cloud Functions, Azure Functions. |
| KaaS | En un clúster de Kubernetes administrado por el proveedor de nube. EKS, GKE, AKS. |
Sea cual sea el modo, hay cuatro exigencias que la arquitectura impone al despliegue: que cada servicio se ejecute aislado y con sus propios recursos, para que no perjudique a sus vecinos; que todo esté automatizado, porque con decenas de servicios el despliegue manual es inviable y de ahí la importancia de la integración y entrega continuas; que la infraestructura se describa como código y se guarde en control de versiones; y que los despliegues no interrumpan el servicio.
El despliegue sin interrupción se apoya en el mismo principio de estado deseado que usa el orquestador: se declara cómo debe quedar el sistema y la plataforma se encarga de llegar hasta ahí, levantando instancias nuevas antes de retirar las viejas y volviendo atrás si detecta que algo va mal.
| Forma de escalar | En qué consiste |
|---|---|
| Vertical | Dar más recursos a la misma instancia: más CPU, más memoria, más disco, más ancho de banda. Tiene un techo físico. |
| Horizontal | Añadir instancias: más pods o más servicios a demanda, y retirarlos cuando la demanda baja. Es el escalado natural aquí. |
| Partición por datos | Repartir la carga dividiendo los datos por alguna dimensión, como la geografía o el rango de identificadores. |
| Descomposición funcional | Extraer a un servicio propio la funcionalidad que más se demanda, para poder escalarla sola. |
El escalado horizontal es el que da sentido a esta arquitectura: solo crece el servicio que se satura. En un monolito habría que replicar la aplicación entera para conseguir lo mismo.
Para el examen
Escalado horizontal: añadir instancias
Escalado vertical: agrandar la máquina
Azul-verde: dos entornos y conmutación
Canario: suelta el cambio a una parte del tráfico
Resiliencia, observabilidad y seguridad en producción
En una arquitectura repartida hay que dar por hecho que las llamadas van a fallar, y diseñar para que ese fallo no se convierta en una caída. Estas son las piezas que se combinan para conseguirlo.
| Mecanismo | Qué hace |
|---|---|
| Tiempo de espera | Fija cuánto se espera a una respuesta antes de darla por perdida. Sin él, un servicio lento bloquea al que le llama indefinidamente. |
| Reintentos | Vuelve a intentarlo, pero no de inmediato: se espera cada vez más (retroceso exponencial) y se limita el número de intentos. |
| Circuit breaker | Deja de llamar a un destino que está fallando y reintenta pasado un tiempo. |
| Bulkhead | Mamparo. No está en quien llama, sino entre los servicios: reparte los recursos en compartimentos estancos con cuotas de CPU, de memoria o de peticiones, para que la saturación de una parte no hunda el resto. |
| Idempotencia | Que repetir una operación produzca el mismo resultado que ejecutarla una sola vez. Es imprescindible porque con reintentos y con colas los mensajes se duplican. |
El nombre de bulkhead viene de los mamparos estancos de un barco: si se inunda un compartimento, el resto sigue a flote. La idempotencia, por su parte, es lo que permite que un consumidor que recibe dos veces el mismo evento no cobre dos veces la misma matrícula, y por eso los eventos llevan identificador y los consumidores llevan cuenta de cuáles han procesado ya.
La otra mitad del trabajo es enterarse de lo que está pasando, y en un sistema con cuarenta servicios eso no se consigue mirando registros a mano. La observabilidad se apoya en cuatro piezas: agregación de registros de todos los servicios en un solo sitio, agregación de métricas tanto técnicas como de negocio, trazas distribuidas que siguen una misma petición a través de todos los servicios por los que pasa, y alertas que avisen sin llegar a la fatiga de alerta, que es el punto en el que nadie las mira ya.
- Pruebas unitarias, importantes sobre todo en entornos de integración continua.
- Pruebas de servicio, que ejercitan un microservicio entero por su interfaz sustituyendo a sus vecinos por dobles.
- Pruebas de extremo a extremo, que recorren el sistema completo y son las más costosas de mantener.
- Pruebas en producción: transacciones sintéticas, despliegues canario, pruebas A/B, ejecuciones en paralelo e ingeniería del caos.
En seguridad, la malla de servicios resuelve buena parte del problema, pero no exime de tenerlo presente desde el principio del desarrollo. Lo propio de esta arquitectura es el mínimo privilegio con alcances acotados, es decir, que cada servicio se identifique con su propia cuenta y que esa cuenta solo pueda llegar a lo que necesita; y sacar las credenciales del código para llevarlas al entorno o a un gestor de secretos. Las piezas concretas de autenticación entre servicios, OAuth 2.0, TLS y los tokens JWT, se estudian en el tema de servicios web del bloque de Desarrollo.
Idempotencia y reintentos son inseparables: reintentar una operación que no es idempotente es la forma más rápida de duplicar cobros, matrículas o correos.
Para el examen
Los tres pilares: métricas, registros y trazas distribuidas
Sin la traza: no se sabe en qué salto se perdió la petición