Configurar Nginx como balanceador de carga

por | 24 enero, 2022

Escrito por picodotdev el 08/07/2016, actualizado el 09/07/2016.
planeta-codigo software software-libre web
Enlace permanente 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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14upstream app {     server app1:8080;     server app2:8080;     server app3:8080; } server {     listen 80;     location / {         proxy_pass http://app;         add_header X-Upstream $upstream_addr;     } }

nginx.conf

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

1 2 3 4 5 6upstream app {     least_conn;     server app1:8080;     server app2:8080;     server app3:8080; }

nginx-least_conn.conf

Hay que tener en cuenta que en las estrategias round-robin y least-connected 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.

1 2 3 4 5 6upstream app {     ip_hash;     server app1:8080;     server app2:8080;     server app3:8080; }

nginx-ip_hash.conf

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.

1 2 3 4 5upstream app {     server app1:8080 weight=3;     server app2:8080;     server app3:8080; }

nginx-weight.conf

Cuando un servidor falla al servir una petición Nginx lo marca como en estado erróneo y deja de enviarle peticiones, las comprobaciones 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.

1 2 3 4 5upstream app {     server app1:8080 max_fails=5 fail_timeout=30s;     server app2:8080 max_fails=1 fail_timeout=60s;     server app3:8080; }

nginx-misc.conf

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.

Servidor balanceado 172.17.0.2:8080
Servidor balanceado 172.17.0.4:8080
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 travé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:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19app1: image: tomcat app2: image: tomcat app3: image: tomcat nginx: image: nginx:alpine volumes:     - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro links:     - app1     - app2     - app3 ports:     - "80:80"

docker-compose.yml