Introducción
En esta segunda parte del artículo vamos a completar el escenario propuesto anteriormente. La primera parte pueden encontrarla aquí.
El escenario consta de dos máquinas virtuales (ARR1 y ARR2) que actuarán como balanceadores de carga de 3 instancias de la aplicación Web WebAppStateFull. Los Aplication Request Routing (ARR) están expuestos a internet, es decir los clientes hacen sus llamadas HTTTP al puerto 80 del Cloud Services llamado demoarrblog, y estos redirigen las llamadas a los servidores Web que se encuentran en el Cloud Service jpggArrInterno. La gracia es que esas llamadas son a través de la red virtual VirtualNetworkArr que une los dos Cloud Services con direccionamiento privado. Esto quiere decir que los ARR llaman directamente a las direcciones privadas de los Web Servers sin necesidad que estos últimos expongan un EndPoint público, lo que tiene como segunda derivada, mayor seguridad.
Este escenario tiene sentido cuando las aplicaciones Web son StateFul y no tenemos opción de cambiarlas a StateLess. Como siempre, debemos notar que staless permite escalar más fácil a las aplicaciones PaaS por lo que es la primera opción al momento de diseñar una aplicación. La aplicación del ejemplo es StateFul, por lo cual cuando es instalada en una granja de servidores como en este caso, es necesario que los balanceadores tenga la característica de afinidad para que no se produzcan errores al usar la aplicación porque el usuario es enviado a otro servidor de la granja en el cual su sesión no existe.
Estas dos cosas vamos a probar en este artículo, balanceo de carga y afinidad en
Windows Azure. Es pre requisito para seguir el paso a paso haber realizado la
configuración de la primera parte del artículo, la cual se encuentra aquí.
Primer paso: crear Proyecto Web staful
Utilizando Visual Studio, creamos un proyecto ASP.NET 4.5 vacio como se muestra en la siguiente pantalla. Esta será nuestra aplicación de Web StateFul la cual llamaremos WebAppStateFul.
A este proyecto Web vacío le agregamos una página llamada default.aspx, en la cual agregamos los controlesque aparecen a continuación.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="default.aspx.cs" Inherits="WebAppStateFul._default" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> </head> <body> <form id="form1" runat="server"> <div> Nombre Servidor: <asp:Label ID="lbNombreServer" runat="server"></asp:Label> <br /> ¿Es una nueva sessión? <asp:Label ID="lbNuevaSession" runat="server" Text="Label"></asp:Label> <br /> LLave de sessión: <asp:Label ID="lbSessionLLave" runat="server"></asp:Label> <br /> Contador de request por sessión: <asp:Label ID="lbNRequest" runat="server"></asp:Label> <br /> <br /> <strong>Lista de Variables de Session</strong><br /> <asp:GridView ID="gvListaGalletas" runat="server" CellPadding="4" ForeColor="#333333" GridLines="None" Width="681px"> <AlternatingRowStyle BackColor="White" /> <EditRowStyle BackColor="#2461BF" /> <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" /> <RowStyle BackColor="#EFF3FB" /> <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" /> <SortedAscendingCellStyle BackColor="#F5F7FB" /> <SortedAscendingHeaderStyle BackColor="#6D95E1" /> <SortedDescendingCellStyle BackColor="#E9EBEF" /> <SortedDescendingHeaderStyle BackColor="#4870BE" /> </asp:GridView> <br /> <strong>Lista de cookies</strong><br /> <asp:GridView ID="gvListaCookies" runat="server" CellPadding="4" ForeColor="#333333" GridLines="None"> <AlternatingRowStyle BackColor="White" /> <EditRowStyle BackColor="#7C6F57" /> <FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" /> <HeaderStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" /> <PagerStyle BackColor="#666666" ForeColor="White" HorizontalAlign="Center" /> <RowStyle BackColor="#E3EAEB" /> <SelectedRowStyle BackColor="#C5BBAF" Font-Bold="True" ForeColor="#333333" /> <SortedAscendingCellStyle BackColor="#F8FAFA" /> <SortedAscendingHeaderStyle BackColor="#246B61" /> <SortedDescendingCellStyle BackColor="#D4DFE1" /> <SortedDescendingHeaderStyle BackColor="#15524A" /> </asp:GridView> <br /> <strong>HTTP Header</strong><br /> <asp:GridView ID="gvHttpHeader" runat="server" BackColor="#CCCCCC" BorderColor="#999999" BorderStyle="Solid" BorderWidth="3px" CellPadding="4" CellSpacing="2" ForeColor="Black"> <FooterStyle BackColor="#CCCCCC" /> <HeaderStyle BackColor="Black" Font-Bold="True" ForeColor="White" /> <PagerStyle BackColor="#CCCCCC" ForeColor="Black" HorizontalAlign="Left" /> <RowStyle BackColor="White" /> <SelectedRowStyle BackColor="#000099" Font-Bold="True" ForeColor="White" /> <SortedAscendingCellStyle BackColor="#F1F1F1" /> <SortedAscendingHeaderStyle BackColor="#808080" /> <SortedDescendingCellStyle BackColor="#CAC9C9" /> <SortedDescendingHeaderStyle BackColor="#383838" /> </asp:GridView> </div> </form> </body> </html>
Luego agregamos el código C# que tiene la lógica de la aplicación.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Collections; namespace WebAppStateFul { public partial class _default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { lbNombreServer.Text = System.Environment.MachineName; if (Session["llave"] == null) { //Nueva session lbNuevaSession.Text = "Si"; Session["llave"]= string.Format("{0}_{1}_{2}", System.Environment.MachineName, DateTime.Now.ToShortTimeString(), Request.Browser.Browser.ToString()); Session["nRequest"] = 0; } else { lbNuevaSession.Text = "NO"; Session["nRequest"] = (int)Session["nRequest"] + 1; } lbSessionLLave.Text = (string)Session["llave"]; lbNRequest.Text = ((int)Session["nRequest"]).ToString(); ArrayList listaVarSession = new ArrayList(); for (int i = 0; i < Session.Count; i++) { listaVarSession.Add(Session[i]); } gvListaGalletas.DataSource = listaVarSession; gvListaGalletas.DataBind(); //gvListaCookies ArrayList colCookies = new ArrayList(); for (int i = 0; i < Request.Cookies.Count; i++) { colCookies.Add(Request.Cookies[i]); } gvListaCookies.DataSource = colCookies; gvListaCookies.DataBind(); //http header ArrayList httpLista = new ArrayList(); for (int i = 0; i < Request.Headers.Count; i++) { httpLista.Add(Request.Headers.GetKey(i) +"="+ Request.Headers[i]); } gvHttpHeader.DataSource = httpLista; gvHttpHeader.DataBind(); } } }
Esta aplicación captura el nombre físico de la instancia de la aplicación Web donde se está ejecutando el requerimiento dentro de la Server Farm. Lugo, si es el primer llamado de ese cliente a ese servidor crear una variable de sesión llamada llave para identificar si el servidor reconoce o no al cliente. Por último, en otra variable de sesión lleva la cuenta de los requerimientos que cada cliente lleva en ese servidor. Esto es una aplicación StateFul porque si el cliente es enviado a otro servidor se pierde la cuenta.
La siguiente imagen muestra lo que la página produce después de cargarla un par de veces.
Segundo paso: Proyecto Cloud
Ya tenemos una aplicación Web lista, la que usaremos como ejemplo nuestro Web Site Stateful. Ahora, para poder publicar la aplicación en Azure. Para esto, tenemos que agregar un nuevo proyecto del tipo Cloud vacío, como se muestra en el siguiente diálogo.
Para incluir el proyecto Web existente a la solución Cloud, debemos agregar un Web Role de una solución existente como se muestra a continuación.
En este punto ya tenemos un proyecto Cloud y podemos probarlo utilizando el emulador local. Para esto, configuramos el proyecto CloudServiceWeb como Set As Startup Prjoject y luego utilizando F5 ejecutamos la aplicación.
Podemos ver en Windows Azure Compute Emulator que la aplicación se ejecuta en una instancia y en el Browser los datos del servidor donde se ejecuta y variables de sesión.
Para el escenario que vamos a montar en Azure, vamos a utilizar 3 instancias, lo que nos va a ayudar a ver claramente cómo el servicio ARR realiza el balanceo de Carga. Para configura la cantidad de instancias, vamos a la configuración del rol y cambiamos la cantidad de instancias de 1 a 3.
El Segundo cambio que debemos hacer es configurar el EndPoint externo en el puerto 80, así podrán los clientes acceder al servicio una vez publicado en la Nube.
La ultima configuración que hacemos es la red virtual (virtualNetwordArr) y sub red (servidoresWe) donde las instancias de servicio Web se van a desplegar. Esto se configura manualmente en el archivo XML ServiceConfiguration.Cloud.cscfg
<?xml version="1.0" encoding="utf-8"?> <ServiceConfiguration serviceName="cloudServiceWeb" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="3" osVersion="*" schemaVersion="2013-03.2.0"> <Role name="WebAppStateFul"> <Instances count="3" /> <ConfigurationSettings> <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" value="UseDevelopmentStorage=true" /> </ConfigurationSettings> </Role> <NetworkConfiguration> <VirtualNetworkSite name="virtualNetwordArr" /> <AddressAssignments> <InstanceAddress roleName="WebAppStateFul"> <Subnets> <Subnet name="servidoresWe" /> </Subnets> </InstanceAddress> </AddressAssignments> </NetworkConfiguration> </ServiceConfiguration>
Por último, podemos hacer el despliegue en la nube siguiendo el asistente de publicación del proyecto Cloud.
Una vez terminado el despliegue de la solución podemos revisar en el portal de administración de Azure los recursos que la red virtual virtualnetwordarr tiene desplegados. En la lista de recursos vemos las 3 instancias de nuestra aplicación junto con la máquina virtual que tiene el servicio de ARR.
Tercer paso: Pruebas del proyecto Cloud
Para probar el servicio, utilizamos un par de navegadores diferentes y vamos a la url http://demoarrblog.cloudapp.net/ del Cloud Service donde hicimos el deploy de la aplicación Web. Como configuramos un EndPoint externo en el puerto 80, accedemos directamente como muestra la siguiente imagen. Podemos ver que cada cliente es enviado a diferentes instancias de nuestra aplicación. Este balanceo es el que hace Windows Azure de manera automática, del cual no tenemos control por ahora.
Cuarto paso: Configuración ARR
Una vez probada la aplicación Web y entendido el comportamiento del balanceador de carga por defecto, vamos a configurar el servicio Aplication Request Routing ARR en la máquina virtual para tomar control de la forma como balanceamos la carga de usuarios y la afinidad de los mismos con la primera instancia con la que se conectaron. El escenario se muestra en la siguiente figura.
La idea es que el cliente llama al servicio a la URL del Cloud Service del ARR en la URL http://demoarrblog.cloudapp.net/ y ahí el ARR toma control del requerimiento y lo redirige hacia una de las instancias del servicio Web.
Las instancias del servicio web WebAppStateFul sólo tienen que recibir requerimientos desde el servicio de ARR por lo cual no es necesario que exponga un EndPoint público. El ARR llamará directamente al servicio utilizando el direccionamiento privado 10.0.0.x de la sub red servidoreswe En este caso, vamos a publicar un EndPoint interno porque los clientes finales no van a acceder directamente a este servicio sino que lo harán a través de los servidores ARR. Para que sea más claro, vamos a utilizar el puerto 90 y no el 80 para publicar el servicio.
Volvemos a hacer deploy del servicio con la nueva configuración.
Una vez actualizada la configuración del Cloud Servicies, tenemos que configurar el ARR para que diriga los requerimientos que recibe hace los servicios Web. Nos conectamos a la maquina vitual utilizando RDP y podemos comprobar que tenemos conectividad con las instancias de la aplicación web utilizando direccionamiento privado. Para validar podemos hacer ping al 10.0.0.12 y usando el navegador podemos cargar http://10.0.0.12:90
La configuración del servicio ARR se hace en Internet information Server Manager. Al igual como lo hicimos en la primera parte del artículo, vamos a configurar una granja de servidores. En la misma granja antes creada, borramos los servidores que contiene y agregamos las tres instancias de la aplicación Web:
- 10.0.0.12 puerto 90
- 10.0.0.13 puerto 90
- 10.0.0.14 puerto 90
Para la primera prueba no utilizaremos Server Affinity para así observar cómo funciona al balanceador. Para esto, desmarcamos la opción Client Affinity de la granja de servidores. Esto significa que vamos dejar que los clientes fluyan entre todos los servidores de la granja.
ARR permite diferentes criterios de balanceo, nosotros utilizaremos Weighted Round Robin con una distribución de carga custom, que dejaremos en 33.33% para que todos los servidores reciban la misma cantidad de requerimientos de clientes.
Quinto paso: Pruebas de balanceo sin afinidad
Una vez realizada la configuración del ARR podemos probar utilizando un solo cliente. Utilizando el browser cargarnos la URL del Cloud Services de la máquina virtual con ARR y obtenemos el siguiente resultado en la primera llamada. Nos responde el servidor RD00155D53D6C8 y reconoce que la sesi[on es nueva, es decir es el primer llamado.
Ahora, realizamos llamados sucesivos y obtenemos las respuestas que se muestran en la siguiente tabla. El comportamiento es exactamente el esperado, el cliente es dirigido a cada uno de los 3 servidores que forman la Web Farm de manera secuencial lo que crea 3 sesiones diferentes para el cliente, una en cada instancia de la aplicación. Esto es el problema para las aplicaciones StateFul ya que el cliente en rigor es solo uno por lo que debería tener solo una sesión independiente de la cantidad de instancias de la granja.
# Request | Nombre Servidor | ¿Es una nueva sesión? | Llave de sesión | Contador de request por sesión |
1 |
RD00155D53D6C8 |
Sí |
RD00155D53D6C8_9:02 PM_Firefox |
0 |
2 |
RD00155D53D115 |
Sí |
RD00155D53D115_9:03 PM_Firefox |
0 |
3 |
RD00155D53C1CB |
Sí |
RD00155D53C1CB_9:03 PM_Firefox |
0 |
4 |
RD00155D53D6C8 |
No |
RD00155D53D6C8_9:02 PM_Firefox |
1 |
5 |
RD00155D53D115 |
No |
RD00155D53D115_9:03 PM_Firefox |
1 |
6 |
RD00155D53C1CB |
No |
RD00155D53C1CB_9:03 PM_Firefox |
1 |
7 |
RD00155D53D6C8 |
No |
RD00155D53D6C8_9:02 PM_Firefox |
2 |
Para solucionar el problema de las múltiples sesiones debido a múltiples instancias en una granja de servidores tenemos dos opciones. Primero, y la más recomendada, modificar la aplicación para que sea StateLess. Si esto no se puede hacer, tenemos la segunda opción que es crear afinidad entre el cliente y la instancia del servidor que lo atiende primero.
ARR nos permite crear afinidad de una manera muy simple. Configuramos en Server Affinitty la opción de Client Affinity y con esto ARR agrega una cookie en el cliente que mamamos en este caso ARRAffinity. Esta cookie almacena la instancia que ese cliente está utilizando, lo que le permite al ARR desde el segundo Request en adelante seguir retueando al cliente con la primera instancia que sirvió a ese cliente. De esa forma la sesión del cliente se mantiene con ese servidor y no tenemos problema con la aplicación StateFul.
La configuración la realizamos a nivel de Server Farm, en la opción de Server Afinnity como muestra la siguiente figura.
Sexto paso: Pruebas de balanceo con afinidad
Una vez configurado ARR con afinidad podemos probar el comportamiento de la aplicación. Para esto abrimos dos nuevas instancias del navegador y hacemos el primer request. Luego comenzamos a recargar la aplicación y las respuestas se muestran en las siguientes capturas de pantalla.
Primer request de ambos navegadores.
Segundo request de ambos navegadores.
Tercer request de ambos navegadores.
Las respuestas que obsérvanos nos muestran que el primer cliente (Internet Explorer) fue ruteado por el ARR la instancia RD00155D53D6C8 en el primer llamado. El segundo cliente (FireFox) a su vez fue enviado a la instancia RD00155D53D6C8. Todas las siguientes peticiones de cada cliente, se mantienen en la misma instancia! Esto es afinidad funcionando.
Pueden ver también que la tabla donde aparecen las cookies, aparece la cookie llamada ARRAfinnity, llave con al cual ARR logra la afinidad con el servidor.
Séptimo paso: Agregar Segundo ARR
Todo sistema que busca tener alta disponibilidad requiere como base no tener puntos únicos de falla, en el ejemplo hasta ahora desarrollado la aplicación Web tiene 3 instancias por lo cual no debemos preocuparnos por la falla en una de ellas. Si hay una falla en una instancia, los clientes que tienen afinidad establecida con esa instancia van a experimentar un error de conexión (la instancia esta caída) y el ARR los enviará a conectarse a otra instancia donde crearan una nueva sesión.
El punto único de falla, hasta ahora, en el ejemplo es el ARR ya que sólo tenemos una instancia. Queremos llegar a la siguiente configuración.
Para completar este escenario, debemos crear un segundo Windows Server 2012 y configurar el servicio de ARR en esa nueva máquina virtual. Para hacer esto, seguimos los mismos pasos explicados en detalle en la primera parte del artículo, específicamente en los pasos Crear un servidor Windows Server 2012 , Configuración de IIS en Windows Server e Instalación de Instalación de Aplication Request Routing (ARR). Esto se encuentra en este link.
Las consideraciones que debemos tener para crear esta segunda máquina virtual son el nombre (ARR2) y ubicarla en el mismo Cloud Service que la primera, como se muestra en la siguiente captura de pantalla.
Una vez Instalado ARR, se configura la granja de servidores con las 3 instancias de la aplicación Web de la misma manera como se hizo con el primer ARR. Esto incluye configurar Server Affinity además de los 3 servidores de la granja y el criterio de balanceo de carga.
Por último, debemos configurar el Endpoint público de la máquina virtual en el
puerto 80. Como en el Cloud Service el servicio Web se va a balancear entre las
dos máquinas virtuales, se crea un EndPoint del tipo Load-Balance como se
muestra a continuación.
El puerto a balancear es el 80 (externo) y el puerto de la máquina virtual (interno) también es el 80.
Una vez creado el EndPoint podemos ver en la configuración de la máquina virtual que el puerto ha quedado configurado y balanceado. Con esto eliminamos el punto único de falla de la arquitectura de ejemplo.
Octavo paso: Probar la configuración
Nuevamente utilizamos 3 clientes para realizar las pruebas. Los 3 clientes se conectan a dos servidores, lo cual podría pensarse que es un error pero no lo es ya que cada ARR tiene su propio registro de balanceo, por lo cual, ARR1 puede haber enviado el primer request que recibió a RD00155D53C1D5 y ARR2 al recibir su primer request puede hace lo mismo. Por eso ese servidor aparece dos veces. Ahora, si se fijan el tercer cliente va a otro servidor. Si recargamos las páginas de los 3 clientes observamos que mantienen afinidad con el servidor que los atendió primero, por lo que hemos comprobado que tenemos listo la configuración de nuestro ejemplo.
Conclusiones
En estos dos artículos hemos desarrollado un escenario donde podemos tomar control del método con que se balancea la carga entre las múltiples instancias de una aplicación Web.
Además, para las aplicaciones StateFul que no podemos modificar, aprendimos a configurar la característica de Server Afinnity de ARR que permite asegurar que el cliente se mantendrá conectado a la instancia de la aplicación que lo atendió en el primer llamado que es en la cual creo su sesión.
Como corolario al ejercicio, tuvimos la necesidad de conectar un Cloud Service IaaS, donde están contenidas las máquinas virtuales de ARR, con un Cloud Services de PaaS. En este último se desplegaron las instancias de la aplicación Web. Esto lo logramos utilizando redes virtuales, que son la vía de comunicación interna/ directa entre Cloud Services independiente si son PaaS o IaaS. Esto es una de las grandes ventajas de la plataforma Azure que permite construir sistemas hibridos combinando la potencia de PaaS con la flexibilidad de IaaS en nuestros sistemas.
Links relacionados
- Cómo usar Custom Load Balance y Affinity en Windows Azure #windowsazure
- Windows Azure: ASP.NET en Cloud Service utilizando MongoDB
- Cómo respaldar Azure SQL Database usando un Worker Role
- Windows Azure Media Services: Publicar videos para IOS y Windows Phone
- ¿Cómo ejecutar Apache SOLR en Windows Azure?
Pingback: Windows Azure Session Affinity en Java/Tomcat con HTTPS/SSL #windowsazure | ..:: Liarjo of Locksley ::..