Cuando montamos un servidor web y los servicios que proporcionamos se hacen mayores (mayor cantidad de usuarios accede a ellos), inevitablemente debemos seguir una maniobra de escalado para dichos servicios. Tenemos muchas opciones, podemos tener servidores dedicados, o como primer paso utilizar servidores VPS como pueden ser DigitalOcean o Amazon AWS. En DigitalOcean, como muchos otros, tendrás que montarte tú todo, es decir, montar tu servidor e instalar HAProxy, Nginx, Apache (o el que quieras) en modo balanceador de carga, de modo que cada petición que entre será desviada a algún nodo web que tengamos conectado.
El caso de Amazon AWS es especial, porque Amazon nos proporciona servicios configurables y se adaptan a muchas de las situaciones que se nos puedan presentar. Está bien tener una alternativa para no depender siempre ellos, pero por un lado nos ahorra tiempo y mantenimiento (una máquina más implica monitorización, actualizaciones, etc; por otro lado, muchas veces utilizar estos servicios de Amazon nos termina saliendo más barato que crear una nueva instancia para este tipo de aplicaciones. Por eso, vamos a utilizar el Elastic Load Balancer de Amazon para los ejemplos.
¿Qué necesitamos?
Vamos a utilizar instancias muy pequeñas y sencillas para los ejemplos. Normalmente, cuando la aplicación web crezca, tendremos instancias más potentes. Sólo tenemos que analizar el coste por usuario y el rendimiento. Aproximadamente es como decir:
- Mi previsión es de 100.000 (cien mil) usuarios/día. Algo más de 4.000 por hora
- Normalmente tengo unos picos de 5.000 usuarios
- Tengo tres tipos de instancia probados:
- con el pequeño que cuesta 1€/día puedo atender a 3000 usuarios/hora
- con uno un poco más grande, que cuesta 2€/día puedo atender a 5000 usuarios/hora
- con el más grande, 3€/día puedo atender a 10000 usuarios/hora
Podemos contratar el pequeño, pero no nos va a llegar, debemos tener por lo menos dos pequeños para atender hasta 6000 usuarios al día, tendremos margen, porque nuestros picos de visitas son de 5000. Y si se cae uno de los nodos, al menos no nos quedamos sin servicio.
Podemos contratar el segundo, 2€/día , nos saldría igual que la configruación anterior, pero si vienen más de 5000 usuarios el servicio se verá afectado. Además, si el nodo se cae, estamos perdidos.
El tercero de 3€ al día, ni lo pensamos porque por ahora es matar moscas a cañonazos.
Configuración del VPC
Amazon AWS nos permite configurar al máximo la red a la que conectamos las instancias y los recursos. con Virtual Private Cloud. Por lo que vamos a empezar por ahí. Dentro de nuestro panel AWS nos vamos a VPC y creamos uno para nuestras pruebas:
.
En Name, damos un nombre identificativo a nuestra red.
En CIDR, especificamos el rango de direcciones.
En Tenancy, diremos si la red utilizada tendrá un hardware dedicado o no. (Un hardware dedicado aumenta notablemente el rendimiento, pero también aumenta mucho los costes y muchas veces con Default es suficiente).
Tras ello, creamos la subred. Nos vamos a Subnet y Create Subnet, introduciendo la siguiente información:
Seleccionamos el VPC al que vamos a aplicar esta subred y el bloque de IPs que utilizaremos.
Es común en muchas configuraciones crear varias subredes, una pública y otra privada por aquello de separar la red con conexión a Internet de la red privada que no tendrá conexión directa y utilizarán nuestros servicios internos para comunicarse. Así, al separar las redes dificultamos el acceso desde Internet a, por ejemplo, nuestro servidor de base de datos (al que sólo podrá conectarse nuestro servidor web).
Ahora procedemos a la creación de una puerta de enlace con Internet para nuestro VPC público. Para ello vamos a Internet Gateway y creamos uno:
.
Esto, podemos considerarlo como un cable con Internet. Ahora damos con el botón derecho a la puerta de enlace en el listado y pulsamos sobre Attach to VPC donde seleccionamos nuestra VPC y aceptamos.
Ahora, seguramente se haya creado una table de enrutamiento (Route Table), nos vamos a esa sección y lo primero es darle nombre (porque por defecto Amazon las crea sin nombre). Este paso es importante porque si hemos creado varias y no tienen nombre ya no sabremos cuál es cuál, o al menos será muy complicado.
Entramos en la tabla de enrutamiento y veremos que en la pestaña Routes, podemos ver a todos los que están dentro de 10.0.0.0/16 (nuestro CIDR), ahora especificaremos que los que están fuera (el resto de IPs) serán accesibles a través de nuestra puerta de enlace. Editaremos las rutas y añadiremos nuestra Gateway para 0.0.0.0/0:
.
Ahora, si volvemos a Subnet, podremos ver esta ruta también.
Configuración del Security Group
El siguiente paso es configurar el Security Group. Para ello, en la misma página, a la izquierda, vemos Security Groups, pinchamos ahí y creamos un nuevo Grupo de Seguridad con esta configuración:
Luego cambiamos las reglas de tráfico entrante (Inbound) a las siguientes:
Con esto aceptaremos todo el tráfico entrante local, y desde Internet sólo podrán accedernos por HTTP/HTTPs/SSH. SSH podemos eliminarlo cuando ya no lo estemos utilizando, y sólo podremos entrar a las instancias por HTTP/HTTPs y activarlo cuando lo necesitemos. O si preferimos, dejar este Security Group para las instancias y crear uno sólo con HTTP/HTTPS para el balanceador de carga.
Configuración de las instancias
Podemos crear las instancias como queramos. Yo he preferido instalar Debian Jessie en instancias de tipo Micro. Luego configurarlas de la siguiente manera:
Es importante seleccionar el VPC. E intentamos que no se asigne automáticamente una dirección IP a la instancia (para mantenerla privada). Si es necesario, para administración y configuración, reservaremos una dirección IP y la engancharemos a la instancia (esta configuración no podremos cambiarla en el futuro)
Tras ello añadimos almacenamiento (para probar con el disco duro que viene sugerido o un SSD pequeño nos basta), pero el disco ya depende de lo grande de nuestra aplicación web (teniendo en cuenta que en este espacio debe caber todo el sistema operativo y los programas.
Avanzando llegaremos hasta la pregunta del Security Group. Podemos crearlo aquí, o podemos utilizar uno que ya hayamos creado:
Creamos dos instancias de la misma manera. Ahora creamos una dirección IP. Dentro de VPC, vamos a Elastic IP y damos en Allocate New Address. Tenemos que tener cuidado porque Amazon nos cobrará por las IPs que tengamos reservadas y no estén asignadas, así que una vez que tengamos la IP en nuestro poder, la asignaremos a una instancia para instalar software:
Para acceder a la instancia, el usuario de administración es admin por lo que tendremos que acceder a:
Y aceptar la autenticidad de clave.
Para probar, instalaremos Apache:
sudo apt-get install apache2
Activamos SSL para HTTPS (y ya lo tenemos para luego)
sudo a2ensite default-ssl
sudo service apache2 restart
Tras eso, vamos a cambiar un poco la web que trae el servidor por defecto. Que estará en /var/www/html/index.html , y donde dice " Apache2 Debian Default Page " pondremos "Servidor 1″.
Guardamos todo y accedemos a la IP del servidor tanto con HTTP como HTTPs y veremos algo parecido a esto (tanto para HTTP como para HTTPS, en HTTPS tendremos que aceptar el certificado, ya que es un autofirmado y no es válido):
.
A continuación desasignamos la dirección IP anterior, la asignamos a la segunda instancia y repetimos la operación. Cuando estemos editando /var/www/html/index.html pondremos Servidor 2, para diferenciar uno de otro.
Creando el balanceador de carga
Aunque este año Amazon ha creado un balanceador de carga de aplicaciones ( Application Load Balancer), no es aplicable en todos los casos, sólo acepta protocolo HTTP(S). Aunque tiene sus beneficios también tiene sus restricciones. Por ahora vamos a utilizar el Classic Load Balancer. Por lo que en la sección de EC2 vamos a Load Balancers y creamos uno que configuraremos así:
En esta pantalla le damos nombre al Load Balancer y le decimos el VPC con el que estará conectado (más abajo, no se ve en la imagen, seleccionaremos la subred). Luego seleccionamos los servicios que tendrá abiertos el balanceador de carga. En este caso HTTP en el 80 y HTTPS en el puerto 443. Estos serán los puertos aceptados, con los que los usuarios se conectarán a nosotros. Internamente el balanceador de carga hará peticiones a las instancias que tenga detrás por el puerto 80.
Como vemos en la imagen, he seleccionado que tanto para peticiones HTTP y HTTPS se acceda a las instancias por el puerto 80. Esto significa que la conexión segura la estará haciendo el balanceador de carga y nuestra instancia nunca tendrá que lidiar con certificados.
Si utilizamos esta configuración los certificados deberemos subirlos a Amazon, así como seleccionar una configuración para los mismos. Amazon tiene varias configuraciones por defecto muy buenas. Eso sí, el balanceador de carga de Amazon no acepta SNI, por lo que sólo es válido un certificado por balanceador de carga, lo que limita mucho las posibilidades si tenemos varias aplicaciones alojadas en el mismo servidor y todas queremos balancearlas por igual. (Comprar un balanceador por certificado que queramos usar nos puede salir muy caro, por lo que más adelante veremos la solución).
Por supuesto podremos también hacer que el servicio HTTPS de nuestro balanceador acceda internamente por HTTPS a nuestros servidores web. Pero el certificado que tengamos instalado en nuestro servidor web poco importa, porque el que de verdad verá el usuario será el que esté configurado en el balanceador.
Tras configurar el Security Group (que podíamos tenerlo de antes), veremos el tema del estado de salud de las instancias:
Esto será una señal periódica que emitirá el balanceador a nuestras instancias para comprobar que están bien, es decir, que responden correctamente. Según esta configuración, cada 30 segundos accederá por HTTP (puerto 80) GET a /index.html . Si la intancia devuelve una página correcta (estado 200 OK), todo va bien, si no (estado >=500) o tiene que esperar más de 5 segundos la espera, la instancia figurará como no saludable.
Si transcurridos dos intentos figura como no saludable, el balanceador no tendrá en cuenta esta instancia (aunque seguirá haciéndole PING) hasta que durante 10 pings devuelve un 200, cuando será marcada como saludable y de nuevo sea accesible. (Más adelante veremos qué podemos hacer con esto). A mí me gusta bajar un poco los valores de intervalo y timeout, a 18s y 3s respectivamente. Más pruebas para las instancias, consume ancho de banda y algunos recursos, pero me da más seguridad.
Luego añadimos las instancias, damos nombre al balanceador y listo.
¿Cómo accedemos a los contenidos?
Debemos mirar en la ventana de los balanceadores de carga la información del balanceador actual, y quedarnos con el DNS Name.
Sólo debemos copiar y pegar en el navegador y acceder. Cuando accedamos a la web, y actualicemos varias veces, debemos ver cómo el Servidor 1 y el Servidor 2 van cambiando cada vez que accedemos a uno. Ya tenemos aquí la magia del balanceador.
Consejos sobre el balanceador
Si estamos utilizando conexiones HTTP/HTTPS muchas veces nos interesará que los usuarios que llegan por primera vez a un servidor se queden en él si es posible. Es decir, si un usuario viene y contacta con Servidor 1, que siempre que haga peticiones acceda a Servidor 1 siempre que sea posible. Eso lo conseguimos editando la configuración de puertos (Port Configuration) del balanceador y pulsando sobre Edit Stickness debajo del puerto.
Conocer la IP de nuestros visitantes
El balanceador de carga, como hemos visto, cuando alguien se conecta al Load Balancer, éste hace una conexión al servidor web, por lo que el servidor web recibirá el visitante desde la IP del balanceador de carga. Esto hace que sea una mala idea restringir el número de conexiones por segundo basándonos en la IP. Pero claro, muchas aplicaciones requieren conocer el país del visitante o la IP para almacenarla o por labores de monitorización. En principio la IP la veremos en la cabecera X-Forwarded-For, ya que nuestro balanceador de carga (como casi todos los del mercado) nos envía esa información. Aunque en los logs de Apache la IP que figurará será la del balanceador. Si queremos que salga la dirección correcta debemos editar /etc/apache2/apache2.conf y cambiar el LogFormat que estemos utilizando (en mi caso combined) por algo como lo siguiente:
LogFormat "%{X-Forwarded-For}i :: %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
La cadena %{X-Forwarded-For}i cogerá esa cabecera y la plasmará en el log en la posición indicada.
Utilizando mi propio dominio
Lo más fácil es utilizar Amazon Route53 para el dominio. Aunque eso implica cambiar de proveedor, subir registros DNS y más. Pero podemos hacer enlaces a servicios de Amazon sin preocuparnos. En este caso haciendo un Alias:
.
Pero si queremos utilizar nuestro proveedor habitual debemos crear un registro CNAME que apunte nuestro dominio o subdominio al host que nos da Amazon. Aquello que metimos en el navegador para probar el balanceador de carga. Aunque adelanto, no a todos los proveedores les gusta que le metas ese host, muchos lo dan como incorrecto y dan problemas.
Balanceando la carga de otros servicios
El Classic Load Balancer permite también balancear la carga de cualquier servicio accesible por TCP. Por ejemplo algún servicio de Streaming que tengamos corriendo en una instancia
¿Certificados en los servidores o en el balanceador?
Es un tema controvertido. Esto se nos presentará cuando necesitemos activar seguridad para Amazon AWS y ofrezcamos HTTPS a nuestros visitantes. Todo tiene sus pros y sus contras. Por ejemplo, si los certificados los almacenamos en el balanceador de Amazon:
- BIEN. Configuras una sola vez los certificados y la seguridad del servidor. Por ejemplo, si tienes que renovar certificados, sólo hay que instalar los nuevos en el balanceador y listo. No hay que preocuparse por nada más.
- BIEN. Permite hacer las peticiones HTTPS al puerto HTTP de las instancias, por lo que sólo tienes que abrir un puerto y no hay pérdidas de velocidad.
- BIEN. Permite que el balanceador recuerde a qué instancia ha dirigido la última petición a través de una cookie.
- MAL. Sólo un certificado por balanceador. Si ofreces varios servicios en tus servidores, todos comparten certificado, por lo que, o contratas uno para varios dominios o un Wildcard, o te puedes poner a llorar por la próxima factura de los Load Balancers.
- MAL. Si utilizar Let's Encrypt tienes que currar un poco para activar las renovaciones automáticas de certificados. Se puede, si buscas en GitHub hay scripts para hacerlo, pero hay que trabajar un poco más, ya que el certificado hay que instalarlo en el balanceador.
- DEPENDE. Confías en Amazon para almacenar tus certificados y claves privadas. Depende, porque hay gene a la que le gusta y gente a la que no. Confías también en Amazon en lo en serio que se toma la seguridad en sus servicios.
Por contra, otro tipo de configuración puede ser que los certificados se queden en las instancias, y el balanceador sólo reparta tráfico:
- BIEN. Puedes utilizar varios certificados por balanceador. Eso sí, te lo tienes que configurar tú.
- BIEN. Más control por tu parte sobre la configuración, la redirección es ligeramente más rápida porque no hace tantas operaciones como intermediario.
- BIEN. Las claves privadas las almacenas tú, no tienes que mandárselas a Amazon.
- MAL. Cuando toque renovar certificados hay que renovarlos todos en todas las intancias, aunque podemos automatizarlo.
- MAL. El filtrado de las peticiones corre de tu cuenta. Amazon puede filtrar muchas peticiones, pero las peticiones mal formadas que pueden dar lugar a ataques es tu trabajo.
- MAL. Por la naturaleza de la configuración (que veremos más adelante), extraer la IP de los visitantes es duro, muy duro (bueno, no tanto, te lo explico más abajo).
- DEPENDE. La seguridad corre de tu cuenta. Si no actualizas, parchaeas o no configuras correctamente los servidores, los ataques que te lluevan serán culpa tuya (y no puedes echárselas al hermanito Amazon).
Una vez analizado esto, vemos que el caso de arriba ya lo tenemos. Nos falta el caso de abajo, es decir, certificados en las instancias. Para ello, lo principal que tenemos que hacer es modificar los Listeners de nuestro balanceador de la siguiente manera:
Ahora, en lugar de hacer redirecciones HTTP, estamos haciéndolas TCP. Es decir, en lugar de hacer que el balanceador trabaje en la capa de aplicación (HTTP), trabajará en la capa de transporte (TCP) y nos pasará el tráfico sin analizar la petición que es. Será una desviación del tráfico directamente a nuestro servidor, y es la razón por la que nosotros tenemos que realizar ahora el filtrado de la misma.
Sólo cambiando esto, nos funciona, aunque encontramos un problema. Ahora sí que no hay forma de que el balanceador nos pase la IP que originó la petición.
Proxy Mode - Para conocer la IP que originó todo
Mantengo poco tiempo la intriga. El objetivo es que las instancias web conozcan la IP de los clientes que se conectan. Y para hacerlo tenemos que utilizar Proxy Protocol. Lo que esto hace es añadir una cabecera antes de enviar los datos de petición, algo como esto:
PROXY TCPx [IP Origen] [IP Destino] [Puerto origen] [Puerto destino]
Donde TCPx puede ser TCP4 (para IPv4) o TCP6 (para IPv6). en esta cadena especificaremos también IPs y puertos, información útil para hacer las peticiones. En ocasiones veremos PROXY UNKNOWN cuando no se pueda encontrar el origen. Para ello tenemos que hacer varias cosas tanto en Amazon como en nuestros servidores. Podemos encontrar más información sobre el Proxy Protocol aquí.
En el lado de Amazon
Para decirle al balanceador de carga que queremos utilizar este protocolo, primero debemos instalar las utilidades de consola de AWS (en nuestro ordenador o en otra instancia de Amazon). En Ubuntu y derivadas podemos hacer lo siguiente:
sudo apt-get install awscli
Necesitaremos una clave API que podemos obtener desde la consola de Amazon. Cuando esté configurado debemos hacer:
Donde [testinglb] es el nombre de mi Load Balancer. Y region sólo se indicará si no hemos configurado antes una región por defecto.
En el lado de los servidores
Por un lado, si utilizamos Nginx como servidor web, podemos seguir las instrucciones que se muestran aquí. Pero si utilizamos Apache, esto se complica un poco más. Necesitamos un módulo extra que aún no ha sido incorporado al núcleo de Apache, por lo que debemos ir al GitHub del autor (mod_myfixip). Y compilar mod_myfixip. Para ello, tenemos que tener instalado el paquete de desarrollo de Apache (apache2-dev en mi caso, puede variar en otras distribuciones), descargar el fichero mod_myfixip.c y hacer lo siguiente:
Ya tenemos el módulo, ahora debemos crear una configuración para cargarlo y configurarlo. Crearemos los archivos
/etc/apache2/mods-available/mod_myfixip.load
LoadModule myfixip_module /usr/local/lib/apache2/modules/mod_myfixip.so
/etc/apache2/mods-available/mod_myfixip.conf (ojo, debemos incluir la IP del balanceador de carga, o una máscara como en este caso)
<IfModule mod_myfixip.c>
RewriteIPResetHeader off
RewriteIPAllow 10.0.1.0/24 127.0.0.1
</IfModule>
Tras ello, en los VirtualHosts que utilicen SSL debemos incluir lo siguiente:
Cuando reiniciemos el servidor, todo debe funcionar bien. E incluso en los logs veremos la IP de origen de los clientes.
Manipulando el health check
Este indicador nos ayuda a comunicarle al balanceador de carga la disponibilidad de un nodo determinado. Debe ser un control muy rápido que dé una respuesta concisa (un sí, que es un código 200; o un no, cualquier código >=500). Pero claro, muchas veces el nodo está en pie, pero realizando tareas de mantenimiento, o hemos visto que la carga del servidor está subiendo mucho y no queremos atender peticiones en un tiempo. Entonces podemos crear un pequeño script que controle esto. Por ejemplo, podemos crear un script en PHP, que ejecutará el servidor web. Un script de ejemplo es este:
En este caso vemos que si el archivo /var/www/maintenance existe (archivo que podemos colocar con el comando touch), directamente devolveremos un código 500 indicando que el nodo no está operativo. Si por ejemplo, la carga del sistema es mayor a 3 ($load_limit), también devolveremos un error indicando que el nodo no está operativo en este momento. (Sólo si el nodo actual no es el principal, $main_node=0).
El motivo de restringir la carga sólo a nodos que no son el principal es para que siempre, al menos haya un nodo activo, aunque rinda muy mal. Deberemos implementar nuestro sistema de notificaciones para ver si algún nodo necesita atención humana.
Próxima semana
Aunque muchos estemos metidos en plenas vacaciones de navidad. El día 26 de diciembre es laborable y si no piensas tocar el ordenador esos días, cuando vuelvas de vacaciones será un buen momento para configurar el auto escalado de máquinas dentro de Amazon AWS.