Saltar al contenido
Computer Science Fundamentals
Código fuente
← Inicio

Balanceo de carga

100 preguntas

Correctas
0
Incorrectas
0
Sin responder
100
Dificultad
Número de preguntas

100 preguntas en este filtro

Resumen teórico

Imagina la cafetería de la escuela con una sola caja y una fila enorme. La fila avanza despacio, los niños se enojan y, si el cajero se enferma y se va, nadie come. Ahora imagina que la cafetería abre cuatro cajas y una maestra se para en la puerta diciendo "tú vas a la caja 1, tú vas a la caja 2...". La fila avanza cuatro veces más rápido y, si un cajero se enferma, la maestra simplemente deja de mandar niños allí. Esa maestra es un balanceador de carga (load balancer).

En internet es igual. Un sitio web o una aplicación es atendido por un programa en una computadora llamada . Un servidor solo puede hacer cierta cantidad de trabajo. Cuando millones de personas piden páginas al mismo tiempo, usamos muchos servidores, y algo tiene que decidir qué servidor responde a cada persona. Ese algo es el balanceador de carga. Te lo encuentras cada vez que abres un sitio grande, una app de video o un juego en línea, aunque nunca lo veas.

En este resumen aprenderás, paso a paso: por qué usamos muchos servidores en lugar de uno gigante, qué es un balanceador de carga y en qué se diferencia de un proxy inverso y de un API gateway, en qué "piso" de la red puede trabajar, cómo elige un servidor (los algoritmos), cómo se da cuenta de que un servidor murió, cómo mantiene a un usuario en el mismo servidor cuando hace falta, cómo maneja el candado secreto de HTTPS, cómo evitar que el propio balanceador tumbe todo y cómo configurarlo en NGINX y en Caddy. Al final podrás responder el quiz de esta área.

El panorama general y las palabras que usamos

Empecemos con algunas palabras que verás todo el tiempo. Un cliente es quien pide algo (tu navegador, la app del celular). Una petición (request) es una pregunta que envía el cliente, como "dame la página de inicio". Un servidor responde. Cuando ponemos muchos servidores iguales detrás de una sola puerta, cada uno se llama back end (o instancia, una copia del mismo programa). La puerta es el balanceador de carga.

            ┌──────────┐      ┌─> back end 1
clientes ─> │ balancea-│ ─────┼─> back end 2
            │   dor    │      └─> back end 3
            └──────────┘
  una dirección       muchas copias iguales
  que ven los         de la aplicación
  clientes
El dibujo más simple del balanceo de carga

Los clientes solo conocen la dirección del balanceador. Nunca saben cuántos back ends existen. Eso es muy útil: podemos agregar o quitar back ends y nadie de afuera lo nota.

Los dos trabajos de un balanceador de carga

  • Repartir el trabajo: para que ningún servidor quede aplastado mientras otros están parados. Esto da más capacidad (más peticiones por segundo).
  • Sobrevivir a las fallas: cuando un servidor muere, dejar de mandarle peticiones. Los usuarios siguen siendo atendidos por los demás. Esto da .
↑ Volver al índice

Escalado horizontal frente a vertical

Tu tienda recibe más clientes de los que puede atender. Hay dos cosas que puedes hacer. Puedes comprar una máquina gigante y rapidísima para reemplazar la vieja. O puedes comprar más máquinas normales y usarlas juntas. Estos dos caminos tienen nombre.

AspectoEscalado vertical (scale up)Escalado horizontal (scale out)
Qué hacesHacer más grande una máquina: más núcleos, más memoria, discos más rápidosAgregar más instancias del mismo servicio
ImagenCambiar al cajero por un supercajeroAbrir más cajas
¿Hay que cambiar la aplicación?Normalmente no, es la forma más simpleSí: debe funcionar con varias copias a la vez
¿Necesita balanceador de carga?No, hay una sola máquinaSí, algo debe repartir las peticiones
LímitesExiste un tamaño máximo, el precio sube rápido cerca de él, cambiar el tamaño muchas veces exige detener el servicio, y sigue siendo UNA máquina (un punto único de falla)No hay un techo de una sola máquina, pero las partes compartidas (como una única base de datos) pueden convertirse en el nuevo límite

Por qué los servicios stateless escalan fácil en horizontal

Un servicio es cuando cualquier copia puede responder cualquier petición. Los datos que deben sobrevivir (sesiones, carritos, pedidos) viven en un almacenamiento compartido, como una base de datos. Entonces el balanceador puede mandar cada petición a cualquier lugar, y las copias pueden aparecer o desaparecer libremente.

Dimensionamiento: N+1

Si una instancia atiende 400 peticiones por segundo y el momento de mayor movimiento trae 1500, necesitas ceil(1500 / 400) = ceil(3,75) = 4 instancias solo para atender el pico (siempre redondeamos hacia arriba). Llama a ese número N. Para seguir atendiendo todo el pico cuando muera una instancia, agregas una de reserva: N + 1 = 5.

La reserva sale más barata cuando las máquinas son pequeñas. Para sobrevivir a una falla con 2 máquinas grandes, cada una capaz de cargar el 100% del pico, pagas el 200% del pico. Con 8 máquinas pequeñas, cada una cargando 1/7 del pico, pagas 8/7, cerca del 114%. Una reserva entre 8 es una fracción menor que una reserva entre 2.

Capacidad que debes comprar para sobrevivir a la pérdida de una máquina (pico = 100)
2 máquinas grandes
200 % del pico
8 máquinas pequeñas
114.3 % del pico

Elasticidad y sus trampas

Con el escalado horizontal puedes encender instancias en la hora pico y apagarlas en las horas tranquilas, una por una, mientras las demás siguen trabajando. Esto se llama . Una máquina escalada en vertical queda dimensionada para el pico todo el día, o tiene que detenerse en cada cambio de tamaño.

↑ Volver al índice

Proxy inverso, balanceador de carga y API gateway

Un se coloca entre dos partes. La pregunta es: ¿para quién trabaja?

TipoDónde estáA quién representa
Proxy directo (forward proxy)Junto a los clientes (por ejemplo, una red de empresa donde todos salen a internet a través de él)A los clientes. El servidor no ve quién está detrás
Proxy inverso (reverse proxy)Junto a los servidoresA los servidores. El cliente habla solo con él y no necesita saber que existe

Un proxy inverso es un lugar natural para hacer trabajos que toda petición necesita: TLS (el candado de HTTPS), caché, compresión, esconder la dirección real del origen y retener a los clientes lentos para que la aplicación quede libre para la siguiente petición. Por eso un proxy inverso es útil incluso con UN solo servidor detrás. Lo que no puede dar es redundancia, que requiere más de un servidor.

Un balanceador de carga de capa 7 es un proxy inverso que además decide cuál de varias copias equivalentes atiende cada petición. Por eso NGINX y Caddy cumplen los dos papeles con la misma configuración.

Un API gateway es un proxy inverso especializado en APIs. En un solo punto aplica reglas comunes a todas las rutas: quién puede llamar (autenticación), con qué frecuencia (rate limiting), a qué servicio va cada ruta y cómo se adaptan las peticiones o respuestas. La lógica de negocio queda en los servicios.

                        ┌─> réplica 1 de pedidos
               /orders  │   (balanceador de pedidos)
 clientes ─> API ───────┼─> réplica 2 de pedidos
            gateway     │
               /users   ├─> réplica 1 de usuarios
                        │   (balanceador de usuarios)
                        └─> réplica 2 de usuarios
Dos decisiones diferentes

Qué cambia cuando hay un proxy en el medio

El proxy mantiene dos conexiones separadas: una con el cliente, otra con el back end. Esto trae tres consecuencias prácticas.

  • La aplicación ve la dirección del proxy, no la del cliente. El proxy pasa la dirección del cliente en una cabecera llamada X-Forwarded-For. Es una lista que crece hacia la derecha: cada proxy agrega la dirección de quien le entregó la petición. Con saltos honestos, X-Forwarded-For: 203.0.113.7, 10.0.0.5, 10.0.0.9 significa que el cliente es 203.0.113.7 (el primero), luego vino el proxy 10.0.0.5, y 10.0.0.9 es otro salto. El último proxy no aparece en la lista, porque es el par de la conexión que ve la aplicación.
  • Nunca confíes ciegamente en esa cabecera. Cualquier cliente puede enviar X-Forwarded-For con un valor inventado, y los proxies agregan al FINAL. Así que la entrada más a la izquierda es la que controla el cliente. Para límites de tasa o control de acceso, lee de derecha a izquierda, salta las direcciones de tus propios proxies de confianza y quédate con la primera que sobre.
  • Las cabeceras hop-by-hop se eliminan. Algunas cabeceras (Connection, Keep-Alive, Transfer-Encoding, Upgrade) valen solo para una conexión entre dos vecinos, a diferencia de las cabeceras end-to-end, que viajan del cliente al origen. Como el proxy tiene dos conexiones independientes, descarta las hop-by-hop que recibió y crea las que sirven para la otra conexión.
↑ Volver al índice

Capa 4 frente a capa 7

Describimos las redes como un edificio con pisos, llamados capas. El tiene siete: física, enlace de datos, red, transporte, sesión, presentación y aplicación. Aquí solo importan dos. La capa 4 es el piso del transporte, donde viven TCP y UDP, con direcciones y puertos. La capa 7 es el piso de la aplicación, donde vive HTTP, con URLs, cabeceras y cookies. "L4" y "L7" dicen hasta qué piso lee el tráfico un balanceador antes de decidir.

AspectoCapa 4Capa 7
VeDirecciones y puertos de origen y destinoEl mensaje HTTP completo: ruta, cabecera Host, cookies, otras cabeceras
Unidad de trabajoLa conexión: una decisión y luego solo reenvía bytesLa petición: cada petición se lee, se interpreta y se envía de nuevo
ConexionesReenvía los bytes de una conexiónTermina la conexión TCP del cliente y abre (o reutiliza) otra hacia el back end, así que hay dos conexiones
ProtocolosCualquiera (no le importa lo que hay dentro)Los que entiende, normalmente HTTP
Costo por peticiónMenorMayor (más CPU y memoria)
Reglas de enrutamientoPobres: solo dirección y puertoRicas: por ruta, host, cabecera, cookie

La regla para elegir es simple: mira qué necesita tu regla de enrutamiento. Separar servicios por direcciones o puertos distintos funciona en la capa 4. Si la diferencia está en la ruta de la URL (/api/* aquí, /static/* allá, mismo host y mismo puerto), en una cabecera o en una cookie, esa información solo aparece después de leer el HTTP, así que necesitas al menos la capa 7.

La trampa de la conexión larga

Un balanceador de capa 4 elige una vez por conexión. Si los clientes mantienen una conexión abierta y mandan miles de peticiones por ella (keep-alive de HTTP/1.1), todas van al mismo back end, y las conexiones con volúmenes distintos producen carga despareja. Lo mismo ocurre, con más fuerza, con HTTP/2 y gRPC, que multiplexan muchas llamadas en una sola conexión. Un cliente muy activo puede sobrecargar un back end mientras otros dos están parados. La solución es balancear en la unidad correcta, la petición o el stream: un proxy de capa 7, o un cliente que abre conexiones a varios back ends y se va alternando entre ellas.

capa 4:  [conn A: req req req req req ...] -> back end 1
         [conn B: req ................] -> back end 2

capa 7:  req -> back end 1
         req -> back end 2
         req -> back end 3   (una elección por petición)
La capa 4 ve una conexión, la capa 7 ve muchas peticiones

Con HTTPS, un balanceador de capa 4 puede trabajar en passthrough: reenvía los bytes cifrados y el túnel secreto va del cliente al back end. Todavía ve lo que está fuera del cifrado (direcciones, puertos, cuándo se abren y cierran las conexiones), pero la ruta, las cabeceras y las cookies le son invisibles.

↑ Volver al índice

Algoritmos de balanceo

Un de balanceo responde una pregunta: ¿qué back end recibe la siguiente petición? Conozcamos los más comunes.

Round robin

Los back ends reciben peticiones por turnos, uno después del otro, y cuando la lista termina vuelve a empezar, como repartir cartas alrededor de una mesa. Ignora la carga y la velocidad de cada servidor. Con los back ends A, B, C empezando por A, la petición número k va a la posición (k − 1) mod 3 de la lista, contada desde cero. Para k = 7, (7 − 1) mod 3 = 0, así que la 7.ª petición va a A.

Round robin ponderado

Cada back end tiene un peso, un número escrito en la configuración. La parte de un servidor es su peso dividido entre la suma de todos los pesos. Con pesos 3, 2 y 1, la suma es 6, así que las partes son 3/6, 2/6 y 1/6. De 600 peticiones: 300, 200 y 100.

Pesos 3, 2 y 1: cómo se reparten 600 peticiones
servidor de peso 3
300 peticiones
servidor de peso 2
200 peticiones
servidor de peso 1
100 peticiones

Los pesos son estáticos: son una suposición hecha de antemano y no cambian solos cuando un servidor se vuelve más lento.

Menos conexiones (least connections)

El balanceador mantiene un contador para cada back end: suma 1 cuando envía una petición y resta 1 cuando esta termina. La siguiente petición va al back end con menos peticiones en curso. Es una estimación simple de qué tan ocupado está cada servidor. Le gana claramente al round robin cuando las peticiones tienen duraciones muy distintas, porque el round robin iguala el número de peticiones enviadas, no el trabajo.

El least connections ponderado divide las conexiones activas entre el peso y elige el menor resultado. Ejemplo: A tiene 12 conexiones y peso 1, B tiene 20 y peso 4, C tiene 9 y peso 1. Las razones son 12, 5 y 9, así que se elige B: un servidor de peso 4 aguanta cuatro veces más.

Un servidor lento, dos algoritmos

Toma tres back ends: dos responden en 10 ms y uno en 40 ms, y un número fijo de clientes manda la siguiente petición apenas termina la anterior. Con round robin, cada back end recibe un tercio de las peticiones. Pero el lento necesita cuatro veces más tiempo por petición, así que las peticiones se acumulan en él. Con least connections, cada back end mantiene el mismo número de peticiones en curso. Un back end que mantiene cierto número de peticiones las termina a una tasa de (en curso) dividido entre (tiempo por petición), así que las tasas quedan en la proporción 1/40 : 1/10 : 1/10 = 1 : 4 : 4. Eso suma 9 partes, así que el back end lento recibe 1/9 de las peticiones, cerca del 11%, y cada rápido 4/9, cerca del 44%.

Parte de las peticiones que recibe el back end lento
round robin
33.3 %
least connections
11.1 %

Aleatorio y el poder de dos opciones

El aleatorio (random) elige un servidor al azar para cada petición. Con pocas peticiones el reparto parece despareja, pero con muchas cada uno de N servidores iguales recibe cerca de 1/N. No necesita contador ni posición, así que varios balanceadores pueden aplicarlo sin hablar entre sí. El poder de dos opciones (random two en NGINX, random_choose 2 en Caddy) sortea dos servidores y se queda con el menos cargado de los dos. Es mucho más equilibrado que el aleatorio simple y cuesta solo una comparación. También reduce el "efecto manada", en que varios balanceadores mandan todo al único servidor que parece el más libre.

Hash: IP hash y URL hash

Un de una clave elige el servidor. En el IP hash la clave es la dirección del cliente, así que el mismo cliente vuelve al mismo servidor sin ninguna tabla (esto vale mientras el conjunto de servidores no cambie). En el URL hash la clave es la dirección pedida, así que la misma URL siempre va al mismo servidor. Es lo mejor para cachés: cada nodo guarda respuestas de las URLs que atiende, y la tasa de aciertos sube.

Dos trampas más de least connections

  • Contadores no compartidos. Si dos procesos de balanceador mantienen contadores separados, cada uno decide con la mitad del cuadro y las decisiones pueden acumularse en el mismo back end. El estado debe compartirse (una zona de memoria compartida), o se usa una política que tolere información parcial.
  • El servidor nuevo y vacío. Un servidor recién llegado tiene cero conexiones, así que en un pool ocupado atrae casi todo el tráfico nuevo de golpe, cuando todavía está frío (cachés vacíos, ninguna conexión abierta). Un slow start aumenta su parte poco a poco.
AlgoritmoDecide porMejor cuandoPunto débil
Round robinUn contador (turnos)Servidores iguales, peticiones parecidasCiego a la carga y a la velocidad
Round robin ponderadoContador y pesosServidores de distinto tamañoLos pesos son suposiciones fijas
Least connectionsPeticiones en cursoPeticiones con duraciones muy distintasNecesita contadores compartidos, los servidores nuevos se inundan
Aleatorio / dos opcionesAzar (y una comparación)Varios balanceadores sin coordinaciónDesigualdad de corto plazo (aleatorio simple)
IP hash / URL hashUn hash de una claveAfinidad, cachésDesparejo cuando las claves se concentran
↑ Volver al índice

Sesiones fijas (sticky sessions)

A veces el usuario debe volver al mismo back end. La afinidad de sesión (session affinity, también llamada sticky session) es la regla por la cual el balanceador manda todas las peticiones de un cliente al mismo back end, en lugar de elegir de nuevo cada vez. El balanceador reconoce al cliente por una cookie o por la dirección IP.

¿Por qué alguien la necesitaría? Por el estado guardado dentro de una instancia. Si tu inicio de sesión o tu carrito de compras vive solo en la memoria del proceso que te atendió, una petición enviada a otra instancia no encuentra nada. Atar al cliente a la instancia rodea el problema, pero no lo resuelve.

Dos formas de reconocer al cliente

AspectoPor cookiePor dirección IP
CómoEl balanceador elige un back end en la primera petición y agrega a la respuesta una cookie que identifica la elección. El navegador devuelve la cookie cada vez, y el balanceador repite el destino. El estado vive en el cliente, no en una tabla del balanceadorUn hash de la dirección del cliente elige el back end (ip_hash en NGINX, client_ip_hash en Caddy)
¿Necesita capa 7?Sí (lee y escribe cabeceras HTTP)No
Punto débilLa cookie es un dato controlado por el clienteSupone que una dirección = un usuario, lo que falla en ambos sentidos: el mismo usuario cambia de dirección al cambiar de red, y muchos usuarios detrás de un NAT parecen uno solo

Lo que la afinidad no da

  • No protege el estado. Si la sesión existe solo en la memoria de una instancia y esa instancia se cae, la sesión muere. El failover solo asegura que el usuario llegue a un servidor que no lo conoce.
  • Perjudica el autoscaling. Las instancias sobrecargadas conservan todas las sesiones que ya tenían, y las instancias nuevas reciben solo clientes que todavía no están atados. Cuanto más largas las sesiones, más lenta la redistribución. Los servicios stateless se reequilibran en la siguiente petición.
  • Puede desbalancear la carga cuando se comparten direcciones (ver IP hash).

La cura de verdad: quitar el estado

La afinidad solo hace falta mientras el estado de la sesión vive dentro de una instancia. Saca el estado de allí, ya sea a un almacenamiento compartido que todas las instancias consultan, o a un token firmado que el cliente envía en cada petición, y las instancias pasan a ser stateless. El balanceador puede elegir cualquiera, y las instancias pueden caerse o agregarse sin perder sesiones.

↑ Volver al índice

Consistent hashing

Usar el hash de una clave para elegir un servidor tiene un gran problema. La receta simple es hash(clave) mod N, donde N es el número de servidores (mod es el resto de la división). Si N cambia, la respuesta para casi todas las claves cambia.

Compruébalo con números. Claves 0, 1, 2, ... con clave mod N. Al pasar de N = 4 a N = 5, una clave conserva su servidor solo si el resto antiguo y el nuevo son iguales. El patrón se repite cada 20 claves, y los restos coinciden solo para las claves 0, 1, 2 y 3, es decir, 4 de 20. Así que el 80% de las claves cambia de servidor. Para un caché, eso significa perder casi todo el contenido caliente de una sola vez.

El consistent hashing se inventó para que un cambio en el conjunto de servidores mueva solo una pequeña fracción de las claves, cerca de 1/N.

              0
        S1 *       * k1
     *               *
   k2 *               * S2
     *               *
        S3 *       * k3

 una clave pertenece al primer servidor
 que se encuentra avanzando en sentido
 horario desde su posición
El anillo (la salida del hash se trata como un círculo)

La misma función de hash da una posición en el círculo a cada servidor y a cada clave. En el diseño clásico, cada clave pertenece al primer servidor que se encuentra avanzando en sentido horario desde la posición de la clave. La regla depende solo de posiciones, así que cuando un servidor entra o sale, solo las claves de un tramo del anillo cambian de dueño.

Ejemplo: 4 servidores iguales se reparten las claves por igual, y entra un 5.º servidor igual. Las únicas claves que se mueven son las que el nuevo servidor ahora posee: 1/5. Cada servidor antiguo pasa de 1/4 a 1/5, cediendo 1/20 del total, y 4 × 1/20 = 1/5.

Claves que cambian de servidor al pasar de 4 a 5 servidores
clave mod N
80 %
consistent hashing
20 %

Nodos virtuales

Si cada servidor tiene solo UNA posición en el anillo, los tramos (arcos) pueden ser de tamaños muy distintos, y cuando un servidor sale, su vecino en sentido horario hereda toda su parte. Ese vecino pasa a cargar dos partes y, si también falla, pasa tres al siguiente: una cascada de fallas.

La solución son los nodos virtuales (virtual nodes): cada servidor aparece decenas o cientos de veces en el anillo, en muchas posiciones. Así los arcos se vuelven uniformes, un servidor más grande puede recibir más posiciones (más parte), y cuando un servidor sale, sus claves se reparten entre muchos otros en lugar de uno solo.

↑ Volver al índice

Health checks y failover

Un balanceador que sigue mandando peticiones a un servidor muerto es peor que ningún balanceador. Por eso debe averiguar quién está vivo. Esto es un health check (verificación de salud). Cuando descubre un problema y pasa el trabajo a los demás, eso es failover: la transferencia automática del trabajo de un componente que falló a uno sano. El failover necesita dos cosas: redundancia (otro componente puede tomar el relevo) y detección (algo nota la falla).

AspectoVerificación activaVerificación pasiva
Qué haceEl balanceador envía sus propias peticiones de prueba (probes) a intervalos fijos, por ejemplo a /healthEl balanceador no envía probes. Observa las peticiones reales y cuenta fallas (errores de conexión, timeouts)
¿Necesita tráfico?No, funciona incluso sin clientesSí, necesita peticiones reales para aprender
Costo de descubrir un servidor muertoSolo tráfico de pruebaLas primeras peticiones reales enviadas al servidor muerto fallan o esperan un timeout, así que usuarios reales pagan
EjemploCaddy: health_uri, health_intervalNGINX: max_fails dentro de fail_timeout

Las dos se complementan. La pasiva es inmediata, pero necesita tráfico. La activa funciona sin tráfico y es la única que puede traer un servidor de vuelta, porque nadie manda peticiones reales a un servidor que está fuera.

Un back end marcado como no saludable (unhealthy) no se borra. El balanceador solo deja de elegirlo para nuevas peticiones y reparte el tráfico entre los demás. Cuando la verificación vuelve a pasar, regresa a la rotación sin ningún paso manual.

¿En qué capa verificas?

Una verificación solo detecta problemas en la capa donde trabaja. Abrir una conexión TCP prueba que algún proceso está escuchando en el puerto, nada más. Si la aplicación responde estado HTTP 500 a todo, una verificación TCP dice "sano". Para notar errores de la aplicación, el probe debe hacer una petición HTTP y comprobar el código de estado (y el cuerpo cuando haga falta).

¿Cuánto tarda en retirarse un servidor muerto?

En el peor caso, tiempo de detección = intervalo entre probes × número de fallas exigidas + timeout del probe. Si los probes salen cada 2 s, se exigen tres fallas consecutivas y el timeout es mínimo, un servidor que muere justo después de un probe bueno se retira 2 × 3 = 6 segundos después. Durante esos 6 segundos todavía recibe peticiones. Los intervalos cortos detectan más rápido, pero aumentan las falsas alarmas: ese es el equilibrio que hay que buscar.

Los umbrales también dan a la decisión una histéresis (un retraso que evita cambiar de decisión con demasiada facilidad). Si un servidor que alterna entre funcionar y fallar pudiera entrar y salir del pool en cada probe, cada entrada costaría peticiones de clientes. Exigir varios éxitos consecutivos antes de volver cambia un regreso más lento por estabilidad.

Reintentar o no reintentar

Cuando un intento falla, ¿puede el balanceador enviar la misma petición a otro back end? Depende de lo que vio el primer back end.

Qué pasó¿El back end vio la petición?¿Reintentar en otro back end?
Conexión rechazada o timeout de conexiónNoSiempre es seguro, cualquier método
La petición se envió y luego la conexión se cayó o no llegó respuestaQuizá, y quizá se ejecutóSolo para peticiones , como GET, HEAD y OPTIONS

Liveness, readiness y verificaciones superficiales

  • Liveness pregunta: ¿el proceso está atascado y necesita reiniciarse? Readiness pregunta: ¿puede recibir tráfico ahora? Una instancia que todavía está cargando cachés y abriendo conexiones a la base de datos está viva, pero no lista. El balanceador debe saltársela, y nadie debe reiniciarla.
  • Una verificación profunda (que prueba dependencias compartidas, como ejecutar una consulta en la base de datos compartida) hace que todos los back ends fallen juntos cuando falla la dependencia. Quitarlos a todos no ayuda, y además tumba rutas que seguirían funcionando (como páginas en caché). La verificación que usa el balanceador debe ser superficial: ¿ESTA instancia puede atender? Vigila las dependencias por otros medios.

Retirar un servidor sin perder peticiones

El connection draining lo hace en dos pasos. Primero, el balanceador deja de elegir la instancia para nuevas peticiones. Luego espera a que terminen las peticiones en curso, hasta un plazo límite. Solo entonces se detiene el proceso. Los reintentos no reemplazan al draining, porque las peticiones no idempotentes no se pueden repetir con seguridad.

↑ Volver al índice

Alta disponibilidad y puntos únicos de falla

Un punto único de falla (SPOF, single point of failure) es cualquier parte del sistema que no tiene sustituto: si se detiene, todo se detiene, por muy redundantes que sean las demás partes. La pregunta útil para cada componente es: ¿qué pasa si solo falla este? Alta disponibilidad es el trabajo de encontrar los SPOF y duplicarlos.

Aquí está la sorpresa: cinco servidores de aplicación detrás de UN balanceador de carga. El balanceador elimina el SPOF de la capa de aplicación, pero si está solo se convierte en un nuevo SPOF. Por eso los balanceadores se despliegan en pares o grupos, con una forma de hacer que el tráfico llegue al sano, como una que cambia de dueño, o el DNS. La redundancia en una capa no compensa su ausencia en otra.

Activo-pasivo y activo-activo

AspectoActivo-pasivoActivo-activo
Quién atiendeUn nodo atiende todo el tráfico, el otro espera en standbyLos dos atienden al mismo tiempo
Cuando uno fallaEl standby toma el relevoEl otro absorbe toda la carga
RecursosEl standby está ociosoMejor aprovechamiento de los recursos
El inconvenienteEl cambio toma algo de tiempoCada nodo necesita margen suficiente para cargar todo solo

La aritmética de la disponibilidad

En paralelo (componentes redundantes, el conjunto funciona mientras al menos uno esté en pie): el conjunto solo falla si TODOS fallan juntos. Con fallas independientes, la indisponibilidad del conjunto es el producto de las indisponibilidades. Dos balanceadores de 99% cada uno: indisponibilidad 0,01 × 0,01 = 0,0001, así que disponibilidad 1 − 0,0001 = 99,99%.

En serie (ambas partes son necesarias): la disponibilidad del conjunto es el producto de las disponibilidades. El par de 99,99% delante de UNA base de datos de 99,9%: 0,9999 × 0,999 = 0,9989001, cerca de 99,89%. Nunca supera a la parte más débil. Agregar aún más balanceadores casi no ayuda. La ganancia está en hacer redundante la base de datos.

Disponibilidad de los ejemplos
un balanceador
99 %
par de balanceadores
99.99 %
par + una base de datos
99.89 %

Split brain

Dos balanceadores en un par activo-pasivo se vigilan con un heartbeat (un mensaje regular de "estoy vivo"). Supón que el enlace entre ellos se rompe mientras ambos están sanos. Ninguno puede distinguir la muerte de su par de una falla del enlace, pues en ambos casos el heartbeat se detiene. Los dos pueden decidir "yo debo ser el activo" y pelear por el mismo recurso, como la dirección IP virtual. Esto es el split brain (cerebro dividido). Para evitarlo necesitas información de fuera del enlace: un quórum con un tercer participante (un testigo) que decida quién puede seguir activo, o fencing, que garantiza que el otro nodo realmente fue apagado.

↑ Volver al índice

Terminación TLS

crea un túnel secreto entre dos extremos. Quien es el extremo del túnel guarda el certificado (la cédula de identidad del sitio) y la clave privada (el secreto que prueba que esa identidad es suya). Terminar el TLS es ser ese extremo: descifras los datos. ¿Dónde debe terminar el túnel cuando hay un balanceador? Hay tres formas.

ModoQué ocurre¿Lee el HTTP (reglas de capa 7)?¿Tráfico interno cifrado?¿Dónde están certificado y clave?
PassthroughEl balanceador (capa 4) no descifra. El túnel va del cliente al back endNoSí, de extremo a extremoEn cada back end (distribuir y renovar en cada uno)
Terminación (simple)El balanceador descifra y luego habla HTTP plano con los back endsSíNo, el tramo interno va en claroSolo en el balanceador
RecifradoEl balanceador descifra, inspecciona y luego abre una nueva conexión TLS hacia el back endSíSíEn el balanceador y en los back ends
passthrough:  cliente ════════════════════> back end

terminación:  cliente ═══════> balanceador ───> back end
               (HTTPS)                (HTTP plano)

recifrado:    cliente ═══════> balanceador ═══> back end
               (HTTPS)                (nuevo HTTPS)
Dónde termina el túnel

El principal beneficio de terminar en el balanceador es operativo: el TLS (certificados, claves, versiones, cifrados) se configura en un solo lugar. Los back ends se vuelven más simples y gastan su CPU en la aplicación. El precio es que el tramo interno deja de estar cifrado, a menos que recifres.

Consecuencias que debes conocer

  • El esquema se pierde. Después de la terminación, la aplicación ve solo la conexión interna, que es HTTP. Si los usuarios navegan por https:// pero la aplicación arma redirecciones que empiezan con http://, esa es la causa. El proxy informa el esquema original en la cabecera X-Forwarded-Proto, y la aplicación debe usarla. Como cualquier cliente puede enviar esa cabecera, acéptala solo de proxies de confianza.
  • Cifrar no es autenticar. Si NGINX abre proxy_pass https://backend; sin ninguna directiva proxy_ssl_*, no verifica el certificado del back end. Alguien dentro de la red que se haga pasar por el back end recibe tráfico que puede descifrar. Con recifrado debes activar la verificación explícitamente (proxy_ssl_verify on;) y dar la lista de autoridades certificadoras aceptadas.
↑ Volver al índice

Configuración de NGINX

A NGINX (aquí, la versión open source 1.30) se le dice qué hacer mediante un archivo de configuración. Para balancear haces dos cosas: declaras un grupo de servidores con nombre y apuntas una directiva de proxy a ese nombre.

upstream api {
    server app1:3000;
    server app2:3000;
    server app3:3000;
}

server {
    location /api/ {
        proxy_pass http://api/;
        proxy_set_header Host $host;
    }
}
Un grupo y un location que lo usa
  • upstream api { ... } declara un grupo de servidores y le da un nombre. El grupo solo no recibe tráfico.
  • proxy_pass dentro de un location lo convierte en un proxy inverso. Cuando el nombre en la URL es un grupo upstream, el destino se elige entre los servidores del grupo con el método de balanceo.
  • El método por defecto es el round robin ponderado, y cada servidor tiene peso 1 a menos que escribas weight. Los otros métodos (least_conn, ip_hash, hash, random) solo se aplican cuando escribes la directiva dentro del bloque. En ip_hash, para IPv4 la clave son solo los tres primeros octetos de la dirección, así que 192.168.1.10 y 192.168.1.200 (misma red /24) van al mismo servidor, y para IPv6 es la dirección completa.

Parámetros del servidor

ParámetroSignificadoPor defecto
weight=3Proporción entre los servidores en la rotación1
max_fails=2Cuántos intentos fallidos, dentro de fail_timeout, hacen que el servidor se considere no disponible1
fail_timeout=15sA la vez la ventana de conteo Y el tiempo que el servidor permanece no disponible10s
backupUn servidor de reserva: queda fuera de la rotación normal y solo se usa cuando los servidores primarios no están disponiblesno es backup

Así, un grupo con server a weight=3;, server b; y server c backup; manda tráfico a a y b en la proporción 3 a 1, y c solo recibe peticiones cuando a y b no están disponibles.

Verificaciones pasivas y reintentos

NGINX open source tiene solo verificación pasiva (el health_check activo pertenece a la versión comercial). Juzga a un servidor por las peticiones reales. Cuando se alcanza max_fails dentro de fail_timeout, el servidor se salta durante fail_timeout, y después se usa una petición real de un cliente para probarlo de nuevo. Mientras el servidor sigue muerto, este ciclo se repite. Mientras tanto, proxy_next_upstream (por defecto error timeout) pasa la petición fallida a otro servidor. Las peticiones no idempotentes (POST, LOCK, PATCH) ya enviadas quedan fuera de este reintento, salvo que agregues non_idempotent.

Dos detalles que muerden

  • La barra final de proxy_pass. Si proxy_pass tiene una parte de URI después del nombre del destino (incluso una sola /), NGINX reemplaza la parte de la petición que coincidió con el location por esa URI. Con location /api/ { proxy_pass http://backend/; }, una petición a /api/users llega al back end como /users. Sin la barra (http://backend) la ruta llega como /api/users.
  • La cabecera Host. NGINX reescribe Host con $proxy_host, el destino escrito en proxy_pass. Una aplicación que usa Host para elegir el sitio o armar enlaces vería un nombre interno. proxy_set_header Host $host; pasa el nombre que el cliente realmente pidió.
↑ Volver al índice

Configuración de Caddy

Caddy (versión 2.11) hace el mismo trabajo con un archivo más corto, llamado Caddyfile. No hay un bloque aparte para declarar el grupo: la directiva reverse_proxy es proxy inverso y balanceador de carga a la vez. Listas las direcciones en la misma línea, y las opciones van en un bloque entre llaves.

example.com {
    reverse_proxy app1:3000 app2:3000 app3:3000 {
        lb_policy round_robin
        health_uri /health
        health_interval 5s
        lb_try_duration 5s
    }
}
Un Caddyfile con balanceo, health check y reintentos

HTTPS automático

Cuando la dirección de un sitio es un nombre de dominio público (como example.com arriba), Caddy activa HTTPS sin ninguna configuración extra. Obtiene el certificado de una autoridad certificadora usando ACME, lo renueva automáticamente y redirige HTTP a HTTPS. Como balanceador de carga, eso es terminación TLS sin administrar archivos de certificados.

Valores por defecto distintos a los de NGINX

IdeaNGINX (open source)Caddy
Grupo de back endsupstream nombre { server ...; } más proxy_passreverse_proxy a b c
Algoritmo por defectoRound robin ponderadorandom
Otras políticasleast_conn, ip_hash, hash, random twolb_policy round_robin, least_conn, client_ip_hash, cookie, random_choose 2
Verificación activaNo (solo en la versión comercial)health_uri (apagada hasta que la escribas); health_interval por defecto 30s, health_timeout por defecto 5s
Verificación pasivaActivada: max_fails=1, fail_timeout=10sApagada hasta que fail_duration sea mayor que 0
Reintento tras un intento fallidoActivado: proxy_next_upstream error timeoutApagado hasta definir lb_try_duration (o lb_retries); lb_try_interval es la pausa, 250 ms por defecto

lb_try_duration 5s le dice a Caddy que siga eligiendo upstreams e intentando de nuevo hasta por 5 segundos. Eso oculta al cliente la pérdida de un back end, a cambio de algo de espera. En las verificaciones activas, health_uri hace que el propio proxy envíe peticiones a esa ruta en cada upstream, haya o no tráfico de clientes. Un upstream que falla sale de la selección y regresa cuando el probe vuelve a tener éxito.

↑ Volver al índice

Juntando todo

Ahora podemos leer la historia completa. Un servidor no basta, así que escalamos en horizontal (más instancias) y ponemos un balanceador de carga delante. El balanceador es un proxy inverso que decide qué copia igual atiende cada petición. En la capa 4 decide por conexión, en la capa 7 por petición, con reglas más ricas. Elige con un algoritmo, evita los servidores muertos gracias a los health checks, termina el TLS en un solo lugar y necesita duplicarse para no ser él mismo un punto único de falla.

Si necesitas...Mira...
Más capacidadEscalado horizontal, dimensionamiento N+1, encontrar el cuello de botella real
Enrutar por ruta, host o cookieBalanceador de capa 7 o API gateway
Balancear gRPC u otras conexiones largasProxy de capa 7 o un cliente que reparta las llamadas
Reparto igual con servidores desigualesPesos, o least connections
Un acierto de caché en el mismo nodoURL hash, consistent hashing
Mantener a un usuario en un servidorAfinidad por cookie (pero prefiere estado compartido o token firmado)
Sobrevivir a un servidor muertoHealth checks, reintentos solo para peticiones idempotentes, draining
Sobrevivir a un balanceador muertoUn par con IP virtual, quórum o fencing
HTTPS en un solo lugarTerminación TLS, con X-Forwarded-Proto

Errores comunes que debes evitar

  • Creerle ciegamente a X-Forwarded-For: léelo desde la derecha, saltando los proxies de confianza.
  • Agregar instancias cuando la base de datos es el verdadero límite, o cuando los pools de conexiones no van a caber.
  • Usar round robin simple con servidores de velocidades distintas.
  • Usar IP hash cuando muchos usuarios comparten una dirección.
  • Confiar en las sticky sessions para proteger el estado: usa almacenamiento compartido o tokens.
  • Esperar que Caddy reintente y verifique la salud sin configuración, o que NGINX open source haga verificaciones activas.
  • Reintentar un POST que quizá ya se ejecutó.
  • Olvidar que un balanceador de carga solo es un SPOF, y que las partes dependientes en serie limitan la disponibilidad.
  • Cifrar hasta el back end pero no verificar su certificado.
↑ Volver al índice