Windows Azure Session Affinity en Java/Tomcat con HTTPS/SSL #windowsazure

Introducción

Muchos desarrolladores de JAVA que desarrollan en eclipse están utilizando Windows Azure para hacer despliegue de sus aplicaciones en la Nube. Optan por Azure en el modelo de plataforma como servicio (PaaS) que les brinda muchas ventajas como por ejemplo elasticidad horizontal para responder a demanda variable, control automático de la plataforma basado en monitoreo de las aplicaciones, altoa disponibilidad de 99,95% asegurada,  etc.

Las aplicaciones desarrolladas para correr en PaaS siguen ciertas directivas de diseño para aprovechar al máximo las capacidades de la plataforma. Una de esas directivas es no utilizar estado en memoria, porque eso genera dependencia del cliente (Browser en una aplicación Web) con la instancia del servicio que lo está atendiendo. La razón por la cual se busca que esta dependencia no exista es para permitir escalar horizontalmente sin problema a las aplicaciones, dando la potencialidad de convertirse en aplicaciones que no tendrán problemas de desempeño si se ven enfrentados a picos de utilización.

Históricamente los desarrolladores Web han utilizado variables de sesión almacenadas en memoria para conservar estados.  Con eso se les simplificaba el trabajo ya que podía almacenar cualquier información que considerara parte de la sesión del usuario en memoria y acceder a ella. Esto generaba un problema cuando las aplicaciones web son desplegadas en una granja de servidores constituida por más de una instancia del servidor Web y un balanceador de carga.

Para Ilustrar el problema veamos el siguiente escenario. Una aplicación web con estado en memoria que es desplegada en dos instancias coordinadas por un balanceador de carga (NLB) como se muestra en el diagrama 1. La idea es que el usuario ingresa su nombre de usuario / contraseña para inicia una sesión (Mensaje 1), esa petición es enviada a la instancia cero del servicio web y esa instancia crea la sesión en memoria de ese servidor. Lugo, el cliente vuelve ha hacer un requerimiento donde le pregunta al servidor web los datos de su sesión (Mensaje 2), esa petición es enviada a la instancia uno servicio web quien la busca en memoria y por supuesto no la encuentra ya que esa sesión fue creada en la instancia cero lo que produce un error!

1

Una solución simple para este problema es configurar el balanceador para que genere afinidad entre el cliente y la instancia de servicio que lo está atendiendo, comúnmente esto se llama Sticky Session. Esta configuración lo que hace es que la primera vez que un cliente se conecta lo envía a una instancia de servicio y almacena esa información (cliente/instancia) en memoria del balanceador. Entonces cuando el balanceador recibe la siguiente llamada de un cliente ya conocido lo envía siempre a la misma instancia, como se muestra en el diagrama 2.

2

Esta solución, muy simple de configurar, tiene la desventaja de que si la instancia cero de nuestro ejemplo cae, entonces todos los clientes que tienen afinidad con esa instancia caen también con ella aunque el servicio tenga otras instancias funcionando.

La solución más correcta en aplicaciones que corren en PaaS es que esa sesión sea almacenada en memoria compartida por todas las instancias del servicio web ya que así no se produce este error ya que todas las instancias tienen acceso a los mismos datos. En Windows Azure para esto se implementa memcache. Para más detalles de esto en JAVA, pueden ver el tutorial How to Use Caching.

Ahora, muchas aplicaciones JAVA ya fueron construidas con sesiones en memoria y vienen de despliegues tradicionales en Datacenters en los cuales los balanceadores de carga fueron configurados con Sticky session. Para hacer mas simple la migración de esas aplicaciones a Windows Azure, en el Plugin de Eclipse se incorporó la opción de configurar Session Affinity para evitar tener que modificar el código de las aplicaciones para que usen memoria compartida. La figura 3 muestra la pantalla de configuración de Session Affinity en Eclipse.

3

Escenario

El escenario que vamos a utilizar es una página Web desarrollada en JSP que es hospedada en el servidor web Tomcat. Un escenario muy común en el mundo JAVA. Esta aplicación será desplegada en Windows Azure utilizando Worker Role, con la opción de Session Affinity sobre HTTPS. La figura 4 muestra el escenario de este artículo.

4

Una sorpresa común para los desarrolladores de JAVA que usan Eclipse y el plugin de Azure es que en el despliegue de su solución se utiliza Internet Information Services (IIS) con Application Request Routing (ARR). ARR es el responsable de mantener la afinidad de la sesión, sin intervención alguna de Tomcat. Este elemento es quien identifica a los clientes y crea la afinidad entre cada cliente y una de las instancias de Tomcat. Esto permite que cada vez que llega un requerimiento desde el cliente ARR lo envía siempre a la misma instancia de Tomcat permitiendo así que las variables de sesión en memoria funcionen correctamente.

La página JSP de nuestra aplicación web nos permitirá identificar que instancia de Tomcat nos está respondiendo, que instancia de ARR nos esta rutenado e ver los identificadores de sesión de Tomcat y el identificador de ruteo de ARR como se muestra en la figura 5.

5

Implementación paso a paso

  1. Crear un proyecto tipo Dynamic Web Project en Eclipse

Utilizando Eclipse creamos un nuevo proyecto web dinámico con el nombre de HolaMundov2 como se muestra en la figura 6.

6

2. Crear una página JSP en la carpeta WebContent

Ahora, debemos crear la página JSP que utilizaremos en nuestra aplicación la cual llamaremos index.jsp como se muestra en la figura 7.

7

3. Modificar la página JSP

Reemplazamos el código que viene por defecto en la página por el siguiente código.

<%@page import="java.net.InetAddress"%>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Hola Mundo V2 JSP</title>
</head>
<body>
	<%
	java.net.InetAddress ip = java.net.InetAddress.getLocalHost();
	ip = InetAddress.getLocalHost();
	System.out.println("Current Server IP address : " + ip.getHostAddress());
	String hostName=request.getServerName();
	String x=(String)session.getId();
	if ( x  == null ) {
			x="";
		}
    Integer count = (Integer)session.getAttribute("COUNT");
	// If COUNT is not found, create it and add it to the session
    if ( count == null ) {
      count = new Integer(1);
      session.setAttribute("COUNT", count);
    }
    else {
      count = new Integer(count.intValue() + 1);
      session.setAttribute("COUNT", count);
    }
	%>
	<br>IP TOMCAT Server: <b><%=ip.getHostAddress() %></b><br>
	Cloud Services: <b><%=hostName%></b>
	<br>
	Session ID: <B><%=x  %></B>
	<br>Session Counter: <b><%=count%></B> times.

	<script language="JavaScript">
		var req = new XMLHttpRequest();
		req.open('GET', document.location + 'lala.jps', false);
		req.send(null);
		var headers = req.getAllResponseHeaders().toLowerCase();
		document.write('<p><b>HTTP Headers</b>: '  + headers);
	</script>

	<%
	   Cookie cookie = null;
	   Cookie[] cookies = null;
	   // Get an array of Cookies associated with this domain
	   cookies = request.getCookies();
	   if( cookies != null ){
	      out.println("<h2> Found Cookies Name and Value</h2>");
	      for (int i = 0; i < cookies.length; i++){
	         cookie = cookies[i];
	         out.print("Name : " + cookie.getName( ) + ",  ");
	         out.print("Value: " + cookie.getValue( )+" <br/>");
	      }
	  }else{
	      out.println("<h2>No cookies founds</h2>");
	  }
%>
</body>
</html>

4. Ejecutar en el ambiente local
Para poder ejecutar localmente la página JSP debemos definir un nuevo servidor Tomcat 7 como se muestra en la figura 8. Como segundo paso del asistente, agregamos nuestra aplicación.

8

Una vez creado el servidor podemos ejecutar localmente el proyecto obteniendo como respuesta la página mostrada en la figura 9 en el mismo ambiente de Eclipse. En este momento estamos listos para hacer el despliegue en Windows Azure.

9

5. Crear un proyecto tipo Windows Azure Deployment Project
Ya tenemos funcionando nuestra aplicación de prueba en el ambiente local, por lo que ahora debemos hacer el proyecto de despliegue. Este proyecto lo que hace es crear un paquete de instalación y un archivo de configuración de Azure.  Dentro del paquete van utilitarios que instalaran y configuraran los diferentes componentes necesarios para que esta aplicación funcione correctamente, como por ejemplo Java Runtime Environment (JRE), el servidio de Tomcat, etc.

Al crear el proyecto, seguimos el asistente como se muestra en las siguientes imágenes.

Primero le asignamos un nombre al proyecto de despliegue.

10

Segundo, incluimos el JDK en el proyecto.

11

Tercero, incluimos el servidor Tomcat.

12

Cuarto, Incluimos nuestra aplicación (WAR)

13

Por último, debemos seleccionar la opción de Session Affinity para que se configure la afinidad entre clientes y una de las instancias de Tomcat.

14

6. Cambio de configuración de Puertos para soportar SSL

Una vez terminado el asistente Eclipse crea un proyecto de despliegue que tiene dos EndPoints  definidos como se muestra en la imagen 15. EL llamado “http” tiene el puerto publico 80 y privado 31221 mientras el llamado “http_SESSION_AFFINITY” solo el privado 8080. La idea de esta configuración es que las llamadas http:80 son recibidas por el balanceador del Cloud Services y enviados al puerto 31221 donde IIS ARR lo recibe y lo redirige a alguna de las instancias de Tomcat que está escuchando en el puerto 8080.

15

Ahora como nosotros queremos que esto funcione sobre HTTP:443 debemos modificar la configuración por defecto de los EndPoints como se muestra en la imagen 16.

16

7. Instalar el Certificado a utilizar en el Cloud Services

Para poder utilizar HTTPS necesitamos un certificado que será utilizado para cifrar la comunicación entre el navegador y el servidor Web. Para esto debemos subir el certificado a nuestro Cloud Services, como se muestra en la imagen 17.

17

En este ejemplo yo estoy utilizando un certificado autogenerado para hacer las pruebas, el cual tiene el Thumbprint 09D7DF2A1779D027CB52DE223FA30C579182109B como se muestra en la siguiente imagen.

18

8. Incluir la referencia al certificado en ServiceDefinition.csdef

LA definición de los servicios de un Cloud Services se hace en el archivo XML llamado ServiceDefinition.csdef. Este archivo describe cada Rol y sus características. Aquí vamos a agregar la referencia al certificado que usaremos, en este caso lo he llamado “web.ssl”. Para esto debemos agregar los siguientes tag como miembro del tag WorkerRole.

<Certificates>
      <Certificate name="web.ssl" storeLocation="LocalMachine" storeName="My"/>
</Certificates>

9. Incluir la referencia al certificado en ServiceConfiguration.cscfg
La configuración del servicio que brinda un Cloud Services se hace en el archivo XML llamado ServiceConfiguration.cscfg, Aquí vamos a agregar la referencia al certificado y su Thumbprint.

<Certificates>
<Certificate name="web.ssl" thumbprint="09D7DF2A1779D027CB52DE223FA30C579182109B" thumbprintAlgorithm="sha1"/>
</Certificates>

10.Definir el número de instancias del servicio
En este mismo archivo vamos a aumentar las instancias del servicio de 1 a 2, para poder generar el escenario mostrado en la figura 4. Para eso modificamos el siguiente TAG en el archivo ServiceConfiguration.cscfg

<Instances count="2"/>

11. Script de configuración Automática de IIS ARR

Dado que el Plugin de Eclipse para Windows Azure, al configurar Session Affinity lo hace sobre HTTP y no sobre HTTPS, nosotros debemos modificar la configuración para que así si funcione el servicio sobre HTTPS. Para lograr este cambio de configuración vamos a utilizar un script llamado stratup.cmd que permite ejecutar comandos destinados a configuraciones especiales antes que el servicio inicie.

Lo que necesitamos hacer en este script sobre IIS es:

  • Borrar el Binding HTTP que tiene el sitio web por defecto
  • Crear el Binding HTTPS para el sitio web por defecto en el puerto 31221
  • Agregar el certificado al puerto 31221
  • Agregar un HTTP Header para poder identificar que instancia de ARR nos esta direccionando (opcional)
  • Reiniciar el servicio IIS para que todas las configuraciones se apliquen

Todo esto debe hacerse de manera automática cada vez que una instancia de se crea, por eso lo incluimos en el script stratup.cmd.  Reemplazamos el contenido original de dicho archivo con el siguiente código.

echo Hello World!
:: Script Variables
set vcerthash=09D7DF2A1779D027CB52DE223FA30C579182109B

:: get ipv4
ipconfig | findstr IPv4 > ipadd.txt
for /F "tokens=14" %%i in (ipadd.txt) do (
	@echo %%i
	set varip=%%i
)
del ipadd.txt /Q
set varip=%varip: =%
:: Move to directory
cd d:\windows\System32\inetsrv > logArrSetup.txt
d:
: Delete HTTP Binding
appcmd set site /site.name: "Default Web Site"  /-bindings.[protocol='http',bindingInformation='%varip%:31221:'] >> logArrSetup.txt
:Create HTTPS Binding
appcmd set site /site.name: "Default Web Site"  /+bindings.[protocol='https',bindingInformation='%varip%:31221:'] >> logArrSetup.txt
:Add Certificate to HTTPS Binding port
netsh http add sslcert ipport=0.0.0.0:31221 certhash=%vcerthash% appid={4dc3e181-e14b-4a21-b022-59fc669b0914} >> logArrSetup.txt
:Add HTTP Header response to track who instance of ARR is reciving the request
appcmd set config /section:httpProtocol /+customHeaders.[name='X-JPG-ARR',value='%varip%']  >> logArrSetup.txt
:Reset IIS Services to update de configuration Changes
iisreset >> logArrSetup.txt

Para efectos de análisis, todos los comandos tienen como salida el archivo logArrSetup.txt. En este archivo vamos a poder ver el resultado de las configuraciones aplicadas en cada instancia de servicio.

12. Publicación en Azure

En este momento ya estamos listos para realizar el despliegue de nuestra aplicación de pruebas. Para eso utilizamos la opción de publicación que nos brinda el Plugin de Eclipse, que inicia el dialogo que se muestra en la siguiente imagen.

Es importante completar la información de Remote Access para que en pasos posteriores podamos conectarnos a las instancias y revisar la configuración.

Una vez terminado de hacer el deploy, obtenemos el esperado mensaje “Running” como se muestra en la siguiente imagen.

20

Prueba de Afinidad de sesión

Ahora, debemos hacer la prueba de afinidad de sesión. Para ello, vamos a ejecutar el siguiente comando

iexplore -private https://tomcatssl.cloudapp.net/HolaMundov2/

Esto lo que hace es abrir un browser en modo privado y carga mi página JSP usando HTTPS como queremos. Al ejecutarlo recibo el mensaje advertencia que el certificado no está emitido por una entidad certificadora reconocida y que la dirección del certificado no corresponde. Esto está bien ya que yo generé ese certificado.

21

Selecciono la opción Continue to this website (not recommended) y me muestra la respuesta de mi página. Recargo la página unas 3 veces y obtengo lo que se muestra en la siguiente pantalla.

22

Primero, podemos ver que la dirección de la instancia de Tomcat con la que se generó afinidad es 10.146.226.34 y la cookie de identificación de sesión es DE9EF2B35416C64B4CB0165F4A6A9C4E. Estos dos datos no deben cambiar nunca ya que todas las futuras peticiones que haga al servidor con este browser van a ser redireccionadas a esa instancia de Tomcat.

Este requerimiento, el balanceador de carga lo envío al IIS ARR  x-jpg-arr: 10.146.226.34 cómo podemos ver en el encabezado de HTTP.

Ahora, recargo la página y obtengo lo siguiente.

23

Se puede ver que la instancia de Tomcat sigue siendo la misma 10.146.226.34, el identificador de sesión también se mantiene  DE9EF2B35416C64B4CB0165F4A6A9C4E pero la instancia de IIS ARR cambío ya que ahora es la x-jpg-arr: 10.175.104.24. Esto quiere decir que el balanceador de carga envío mi nuevo requerimiento a la otra instancia del servicio que tiene la dirección 10.175.104.24 y aquella reconoció que yo ya tengo afinidad con la instancia de Tomcat  10.146.226.34 por lo que redirigió mi llamada a esa instancia.

¿Cómo supo IIS ARR que ya tengo afinidad con un Tomcat?

Bueno, muy simple. IIS ARR creó en la primera llamada una cookie llamada ARRWAP4EJ la cual contiene la información de afinidad de sesión para este navegador. EL valor de esa cookie tampoco cambia en las diferentes llamadas, en mi caso el valor es  5855a053ea34b597db38b95ce14626517935371c9408359ab695f0f1b114977f.  Con esa cookie ARR puede saber dónde enviar este y los próximos requerimientos.

EL comportamiento de las dos llamadas se muestra en el siguiente diagrama de secuencia.

24

Es importante notar que la comunicación entre el ARR y Tomcat es HTTP no HTTPS por lo que el certificado digital sólo debe ser instalado en IIS.

Código fuente

El código fuente se puede descargar desde Github

https://github.com/liarjo/TomcatSSL/tree/master/TomcatSSLSessionAffinity

Conclusiones

Es perfectamente posible configurar de manera automática Session Affinity sobre HTTPS cuando se desarrolla en Java con Eclipse. Solo se debe entender cómo funciona  IIS ARR, el balanceador de Carga y la configuración de IIS a través de comandos como lo hicimos en este ejemplo.

Links Relacionados

Un pensamiento en “Windows Azure Session Affinity en Java/Tomcat con HTTPS/SSL #windowsazure

  1. Pingback: Windows Azure Session Affinity in Java | bits by gil

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s