En este tutorial vamos a probar el soporte de web sockets en un Apache Web Server «dockerizado».
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Httpd con Docker.
- 4. Web sockets httpd proxy.
- 5. Web sockets en cluster con htttp.
- 6. Referencias.
- 7. Conclusiones.
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:
- la implementación de Apache Tomcat,
- haciendo uso de la librería spring-websockets y
- con el soporte de mensajería de ActiveMQ
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:
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
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.
6. Referencias.
- https://registry.hub.docker.com/_/httpd/
- http://httpd.apache.org/docs/2.4/mod/mod_proxy_wstunnel.html
- http://docs.spring.io/spring-session/docs/current/reference/html5/
- http://assets.spring.io/wp/WebSocketBlogPost.html
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