Configurar Nginx como balanceador de carga

Publicado por pico.dev el , actualizado el .
blog-stack planeta-codigo planeta-linux software software-libre
Comentarios

Para escalar horizontalmente los servidores de aplicaciones, aumentar el rendimiento, disminuir la latencia, conseguir tolerancia a fallos y aumentar la disponibilidad podemos usar el servidor web Nginx como balanceador de carga entre varios servidores de aplicaciones. En este ejemplo muestro la configuración necesaria para añadir la funcionalidad de balanceador de carga a Nginx entre varios servidores de aplicaciones Tomcat usando además Docker.

Nginx

Un balanceador de carga distribuye las peticiones que llegan al servidor entre varios servidores para que las atiendan consiguiendo optimizar el uso de los recursos, aumentar el número de peticiones atendidas por unidad de tiempo, reducir la latencia y proporcionar tolerancia a fallos. Escalar un servidor que deba procesar un gran número de peticiones llegado un límite es más barato escalar horizontalmente añadiendo más servidores que escalar verticalmente usando servidores más potentes.

Distribuir las peticiones ha de hacerse eficientemente para que un servidor no reciba todas las peticiones pesadas y se sature mientras hay servidores que tienen poca carga. Usando varios servidores para atender las peticiones evita que haya un único punto de fallo en la aplicación proporcionando tolerancia a fallos, si un servidor sufre un fallo el resto de servidores se encargarán de atender las peticiones. Una mejor tolerancia a fallos aumentará la disponibilidad del servicio sin que haya caídas de servicio.

Hay soluciones específicas para balancear la carga como HAProxy pero para los casos sencillos que son los más habituales y para no añadir una pieza más a la arquitectura de la aplicación Nginx es capaz de hacer las funciones de balanceo de carga entre varios servidores de aplicaciones.

Hay tres estrategias para balancear o distribuir la carga:

  • round-robin: las peticiones son distribuidas entre los servidores de forma cíclica. Cabe la posibilidad de que las peticiones más pesadas sean procesadas por el mismo servidor, distribuye las peticiones de forma ecuánime pero la carga no.
  • least-connected: la siguiente petición es atendida por el servidor con menos conexiones activas.
  • ip-hash: se selecciona el servidor que atenderá la petición en base a algún dato como la dirección IP, de esta forma todas las peticiones de un usuario son atendidas por el mismo servidor.

Esta es la configuración básica con la estrategia round-robin. Los servidores balanceados se definen con la directiva upstream a los que se hace de proxy inverso con la directiva proxy_pass.

Para usar la estrategia least-coneccted hay que indicar la directiva least_conn en la directiva upstream.

Hay que tener en cuenta que en las estrategias round-robin y least-conected cada petición probablemente sea atendida por un servidor diferente de modo que si los servidores no comparten las sesiones se producirán comportamientos erráticos. Usando la estrategia ip_hash se usará la dirección IP para redirigir todas las peticiones al mismo servidor que se conoce como sticky session.

Para que los servidores compartan la sesión y evitar usar sticky session podemos usar Redis como sistema de información para guardar las sesiones de los servidores, si un servidor de aplicaciones deja de funcionar las sesiones que mantuviese no se perderán y las peticiones podrán ser atendida por cualquier servidor. Si hay un servidor que queremos procese más peticiones porque tiene más capacidad podemos dar más peso a este. En esta configuración de cada 5 peticiones 3 serán atendidas por el servidor app1, 1 por el app2 y otra por app3.

Cuando un servidor falla al servir una petición Nginx lo marca como en estado erróneo y deja de enviarle peticiones, los chequeos de salud se hacen de forma pasiva según el resultado de las peticiones que se envían. Con max_fails se establece el máximo número de fallos antes de considerar un servidor con estado erróneo, tiene un valor por defecto de 1. Con fail_timeout se establece el tiempo que un servidor se considera que está en estado erróneo antes de enviar una nueva petición, si enviada una nueva petición responde correctamente se vuelve a considerar en estado correcto. Con la directiva health_check se puede configurar las comprobaciones de estado que hace Nginx para determinar si el servidor de aplicaciones está funcionando correctamente.

Si queremos que el cliente conozca que servidor atendió la petición podemos añadir la directiva add_header usando una de las variables añadidas por el módulo ngx_http_upstream, nos servirá para depurar la aplicación en tiempo de desarrollo.

Nginx balanceando la carga entre 3 servidores de aplicaciones Tomcat

En el ejemplo de configuración usaré Docker para crear un servidor web Nginx que haga de balanceador de carga entre tres servidores de aplicaciones Tomcat. Con Docker hacer esta prueba es mucho más sencilla que instalar tres Tomcats y un servidor Nginx a travbés de los paquetes del sistema o descargando binarios, puedes leer los artículos de la serie Docker que he escrito para conocer como usarlo y que ofrece esta útil herramienta. El archivo de docker-compose.yml completo es el siguiente:

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando docker-compose up.

Yo apoyo al software libre