Soporte de Web Sockets en Apache Web Server (httpd) sobre un contenedor Docker

0
13734

En este tutorial vamos a probar el soporte de web sockets en un Apache Web Server «dockerizado».

0. Índice de contenidos.


1. Introducción

En tutoriales anteriores hemos visto como podemos implementar una conexión bidireccional entre cliente y servidor
estableciendo un canal de comunicación en tiempo real con el soporte de Web Sockets y:

En todos esos ejemplos el servidor estaba directamente expuesto al cliente pero ¿y si el servidor está fuera de la DMZ
y tenemos un proxy inverso delante?, y ¿si necesitamos montar nuestra aplicación en cluster activo/activo y queremos dar
soporte de balanceo a las peticiones vía Web Sockets?

En este tutorial vamos a configurar httpd (Apache Web Server) para dar soporte y hacer pruebas de redirección de peticiones ws (Web Sockets) con balanceo de carga, inicialmente en un entorno no productivo.

Remarco que las pruebas las vamos a ejecutar en un entorno no productivo porque además vamos a hacerlas en un contenedor docker de httpd, que nos va a servir
«para cacharrear» sin tener ninguna intención inicial de guardar o externalizar esa configuración para su uso en otros entornos, aunque como hemos
visto recientemente en el tutorial de introducción a docker no sería muy complejo hacerlo.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.3 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Mavericks 10.9.4
  • Boot2docker 1.6.2
  • Apache Web Server httpd 2.4.12
  • Apache Tomcat 8.0.20


3. Httpd con Docker.

Asumiendo que tenemos docker o boot2docker instalado, en función del SO en el que estemos trabajando, la instalación de la imagen oficial de
httpd pasa por la ejecución del siguiente comando:

docker pull httpd

Con ello nos bajaremos la última versión publicada de la imagen que, a día de hoy, incorpora un httpd 2.4.12.

Podemos comprobar la descarga de la imagen ejecutando el comando de listado de imágenes:

bash-3.2$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
httpd               latest              de94ed779434        4 weeks ago         161.8 MB

El siguiente paso es arrancar un contenedor basado en esa imagen, proporcionando un nombre descriptivo y, como es posible
que tengamos un apache en local corriendo, mapeando el puerto local 9091 contra el puerto 80 del contenedor

bash-3.2$ docker run -d -p 9091:80  --name httpd-ws httpd
12820527d2e7dc758ab6b00638ceb18d29ff166ef6aa60a6709f9bc216f618a9

Usando boot2docker tenemos que lanzar el siguiente comando para comprobar la ip en la que se ha levantado el contenedor.

MacBook-Pro-de-jmsanchez:~ jmsanchez$ boot2docker ip
192.168.59.103

Ahora podemos acceder a través del navegador para comprobar que se encuentra levantado:

httpd-websockets-docker-00

Con el siguiente comando podemos listar los contenedores creados basados en las imágenes y comprobar el estado de los mismos

bash-3.2$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                  PORTS                  NAMES
12820527d2e7        httpd:latest        "httpd-foreground"   2 days ago          Up 53 minutes           0.0.0.0:9091->80/tcp   httpd-ws            
0f792a9c397a        httpd:latest        "httpd-foreground"   10 days ago         Exited (0) 7 days ago                          loving_swartz 

Parece que lo tenemos listo para empezar «a cacharrear» pero en este punto, tenemos un primer problema,
la imagen oficial de Apache Web Server al arrancar levanta el proceso httpd, si accedemos al contenedor
y paramos el proceso httpd también se para el contenedor.
La recomendación en este punto es construir nuestra propia imagen basada en la de httpd proporcionando
una configuración externa, bien basándonos en un Dockerfile o mapeando un directorio local contra el
directorio de configuración de httpd en el contenedor; pero no vamos a invertir inicialmente tiempo en esto,
nuestro objetivo es disponer de un apache 2.4 para hacer pruebas de configuración no preparar un entorno de producción.

A continuación paramos el contenedor:

bash-3.2$ docker stop 12820527d2e7
12820527d2e7

Y lo borramos:

bash-3.2$ docker rm 12820527d2e7
12820527d2e7

Al comando inicial con el que creamos el contenedor basándonos en la imagen vamos a añadirle los siguientes parámetros

  • -it: para acceder por consola a la shell del contenedor,
  • -p: por cambiar el puerto inicial que proponíamos al 81,
  • el comando bash al final para que no se levante el proceso establecido por defecto para la imagen, sino que simplemente permita el acceso a la consola,
docker run -it --name httpd-ws -p 81:80 httpd bash

Una vez ejecutado, podemos lenvantar manualmente el Apache Web Server:

root@7329fc1203d5:/usr/local/apache2/bin# httpd -k start

Si, por cualquier motivo paramos el contenedor siempre podemos volver a levantarlo manualmente

bash-3.2$ docker start httpd-ws

Y podemos «engancharnos» a la consola de bash del contendor con el siguiente comando (dos veces INTRO):

bash-3.2$ docker attach httpd-ws

Ya podemos parar y arrancar tanto el contendor como el httpd que nos proporciona internamente el mismo, si bien, nos
encontramos con un segundo problema, no tenemos un editor que nos permita modificar fichero alguno desde el contenedor;
las imágenes están pensadas para consumir pocos recursos y ocupar poco espacio con lo que tienen lo mínimo imprescindible
para que después las amplies; ya digo que lo normal es externalizar la configuración de los procesos, en este caso httpd,
en ficheros externos al contenedor para que levantar uno o varios dockers por entorno sea homogéneo, pero para esta prueba
no vamos a hacer eso, vamos a instalar un editor en el contenedor.

La imagen se ha generado en base una debian con lo que vamos a ejecutar los siguientes comandos:


apt-get update
...

apt-get install vim
...

Hay que tener en cuenta que todo lo que hemos hecho se perderá si borramos el contenedor y, a partir de aquí,
podemos ejecutar el siguiente comando:

root@7329fc1203d5:/usr/local/apache2/htdocs# vi index.html 

para hacer una prueba rápida de edición de la página de indice de Apache.

<html><body><h1>It works! inside Docker!!!</h1></body></html>

Si levantamos el proceso httpd:

root@7329fc1203d5:/usr/local/apache2/bin# httpd -k start

y accedemos a través del navegador al puerto configurado veremos los cambios

httpd-websockets-docker-01

Aparte de cacharrear en este punto ya hemos aprendido o consoliado conceptos sobre docker.


4. Web sockets httpd proxy.

Vamos a configurar el soporte de web sockets en Apache Web Server y lo primero que debemos hacer es habilitar
los módulos correspondientes en la configuración de httpd para que los soporte. Accediendo al fichero de configuración

root@7329fc1203d5:/usr/local/apache2# vi conf/httpd.conf 

Habilitaremos los siguientes módulos:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_express_module modules/mod_proxy_express.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so

Y, para probar, en cualquier punto del mismo fichero, puede ser después de la declaración del primer <Directory />
podemos añadir la siguiente configuración

ProxyPass        /tnt-labs/rest/notifications/info  http://172.20.10.3:8080/tnt-labs/rest/notifications/info
ProxyPassReverse /tnt-labs/rest/notifications/info  http://172.20.10.3:8080/tnt-labs/rest/notifications/info

ProxyPass        /tnt-labs/rest/notifications  ws://172.20.10.3:8080/tnt-labs/rest/notifications
ProxyPassReverse /tnt-labs/rest/notifications  ws://172.20.10.3:8080/tnt-labs/rest/notifications

ProxyPass        /tnt-labs http://172.20.10.3:8080/tnt-labs
ProxyPassReverse /tnt-labs http://172.20.10.3:8080/tnt-labs

La IP 172.20.10.3 es la de mi máquina local que está visible para el contenedor.

El orden en el que declaramos las URLs de mapeo es importante puesto que debemos declararlas de mayor a menor granularidad y, por regla general las que mapean el protocolo ws serán más granulares y, como consecuencia, las primeras en declararse.

Las pruebas las estamos haciendo con el proyecto montado con el soporte de spring-messaging, que expone un primer servicio /notifications/info por http que devuelve la información del Web Socket, como la URL es más granular debe declararse el primero.

Si ahora reiniciamos el proceso, podríamos comprobar que ya no es necesario acceder a la aplicación por el puerto del Apache Tomcat, puesto que está correctamente mapeado por el Apache Web Server.

root@7329fc1203d5:/usr/local/apache2# bin/httpd -k restart

Si no funcionase correctamente, una manera de comprobar que los módulos que están cargados es ejecutando el siguiente comando:

root@7329fc1203d5:/usr/local/apache2# bin/apachectl -t -D DUMP_MODULES | grep proxy
 proxy_module (shared)
 proxy_connect_module (shared)
 proxy_http_module (shared)
 proxy_wstunnel_module (shared)
 proxy_ajp_module (shared)
 proxy_balancer_module (shared)
 proxy_express_module (shared)

5. Web sockets en cluster con htttp.

Ahora vamos a suponer que tenemos la aplicación levantada en dos tomcats, en la misma máquina con distintos puertos.

Lo primero que vamos a hacer es habilitar el monitor del balanceador, podriamos no hacerlo pero buena gana de ponernos una venda en los ojos

<Location /balancer-manager>
SetHandler balancer-manager

Order Deny,Allow
Allow from all
</Location>

ProxyRequests Off
ProxyPass /balancer-manager !

A continuación vamos a configurar tres balanceadores con sus correspondientes URLs de mapeo que
coinciden tanto en orden como en contenido con lo configurado anteriormente, solo que ahora redirigen a los balanceadores:

<Proxy balancer://tnt-cluster-rest-info>
BalancerMember http://172.20.10.3:8080/tnt-labs/rest/notifications/info
BalancerMember http://172.20.10.3:8081/tnt-labs/rest/notifications/info
</Proxy>

<Proxy balancer://tnt-cluster-http>
BalancerMember http://172.20.10.3:8080/tnt-labs
BalancerMember http://172.20.10.3:8081/tnt-labs
</Proxy>

<Proxy balancer://tnt-cluster-ws>
BalancerMember ws://172.20.10.3:8080/tnt-labs/rest/notifications
BalancerMember ws://172.20.10.3:8081/tnt-labs/rest/notifications
</Proxy>


ProxyPass        /tnt-labs/rest/notifications/info  balancer://tnt-cluster-rest-info
ProxyPassReverse /tnt-labs/rest/notifications/info  balancer://tnt-cluster-rest-info

ProxyPass        /tnt-labs/rest/notifications  balancer://tnt-cluster-ws
ProxyPassReverse /tnt-labs/rest/notifications  balancer://tnt-cluster-ws

ProxyPass        /tnt-labs balancer://tnt-cluster-http
ProxyPassReverse /tnt-labs balancer://tnt-cluster-http

Si realizamos varias peticiones desde distintos clientes podemos comprobar,
accediendo al monitor del balanceador en la URL http://192.168.59.103:81/balancer-manager, como las peticiones
se reparten entre los distintos tomcats.

httpd-websockets-docker-02

6. Referencias.


7. Conclusiones.

Debemos tener en cuenta que, con lo visto, un cliente a través del Apache Web Server está conectado a
un Web Socket de un Apache Tomcat, la carga está repartida entre los distintos servidores pero
simplemente con la configuración anterior las notificaciones desde los servidores a los clientes tendrían que
ejecutarse desde todos los nodos del balanceador, no hemos configurado una replica de la sesión de web sockets
entre los distintos nodos del cluster. ¿Será el contenido de otra entrada en el blog?

Sin necesidad de mantener una réplica de sessiones y disponiendo un repositorio de información común, como podría ser:

  • una base de datos, cada nodo podría «monitorizar» una tabla de notificaciones para remitirlas a todos sus clientes conectados, o
  • una cola de mensajería como se plantea en esta entrada de activemq,
    de tal modo que nodo podría suscribirse a un topic de notificaciones y despachar el mensaje recibido de la cola, a sus clientes conectados.

Un saludo.

Jose

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad