Desplegando una aplicación de Spring Boot en Amazon EC2 y asignándole un subdominio

Spring Boot ha sido uno de los proyectos que han contribuido a agilizar el desarrollo de aplicaciones web en Java. De la forma tradicional el siquiera tener una aplicación web con un “Hola mundo” corriendo involucra el descargar el servidor de aplicaciones de nuestra preferencia, configurar el proyecto para desplegar en dicho servidor, agregar un archivo de configuración necesaria en un XML y en algunos casos el definir las conexiones a las bases de datos en el servidor de aplicaciones. Spring Boot, por otro lado nos ahorra toda esta talacha de varias formas, lo más relevante es la inclusión de un servidor de Tomcat embebido, lo cual nos permite el tener una aplicación web corriendo en cuestión de minutos y únicamente siendo indispensable el tener Java y una herramienta de construcción (Maven o Gradle) instalados

. Aquí les dejo un ejemplo: https://spring.io/guides/gs/spring-boot/

Otra de las ventajas de Spring Boot es lo sencillo que es el desplegar una aplicación web en cualquier lugar. El construir un proyecto de Spring Boot con los plugins de Maven genera un archivo ejecutable que es posible correr en cualquier computadora con la versión de Java con la que se compiló el proyecto. Al tener el servidor de Tomcat embebido el abrir el ejecutable levanta este servidor y monta la aplicación, la cual se hace disponible por defecto en el puerto 8080 (http://localhost:8080). Si a esto le añadimos el soporte de una base de datos en memoria con H2 con la siguiente entrada

  1. <dependency>
  2.         <groupId>com.h2database</groupId>
  3.         <artifactId>h2</artifactId>
  4. </dependency>

Podríamos tener una aplicación que literalmente se la podemos pasar a una persona y sin necesidad de configuración alguna y hasta sin necesidad de tener acceso a Internet puede probarla.

Hay una infinidad de otras ventajas que trae consigo Spring Boot, pero me concentraré en una que me sorprendió al verla en acción y es la facilidad en la que uno puede hacer un despliegue en una instancia de Amazon EC2.

A continuación veremos en un caso práctico cómo desplegar una aplicación empaquetada de Spring Boot a una instancia de Amazon EC2, registrarla como un servicio y al final asociarla a un subdominio de un dominio existente, todo esto tomando en cuenta la siguiente configuración:

  • Sistema operativo: Amazon Linux
  • Base de datos: MySQL 5
  • Java 8
  • Maven
  • Spring Data JPA y Hibernate en la capa de acceso a datos

El proceso involucra las siguientes fases:

  • Fase 1: Preparaciones del proyecto - Corresponde a las configuraciones necesarias para distinguir entre los diferentes tipos de ambientes.
  • Fase 2: Preparación de la instancia de Amazon EC2 - Una vez que hayamos creado nuestra instancia de Amazon EC2 necesitamos instalar el software necesario para el proyecto.
  • Fase 3: Subida de la aplicación - Empaquetada nuestra aplicación la subiremos a la instancia de Amazon y la registraremos como un servicio.
  • Fase 4: Registro de un subdominio - Si tenemos un dominio existente podemos crear un subdominio con el cual podamos acceder a la aplicación web.

Fase 1: Preparaciones del proyecto

Configuración de perfiles

Lo primero que es extremadamente recomendable (para mí necesario) es el separar la configuración en un perfil de desarrollo y un perfil de despliegue como mínimo. Spring y Spring Boot facilitan esto de dos formas, la primera es un archivo “application.properties” que viene en el esqueleto de Spring Boot en donde se pueden especificar propiedades de configuración como el puerto usado, nivel de logging, JDBC, etc.

Se pueden definir configuraciones específicas para un perfil creando un archivo properties con el nombre “application”, un guión (-) y el nombre de perfil, por ejemplo:

application-prod.properties : Para el perfil con el nombre “prod” (producción)

application-dev.properties : Para el perfil con el nombre “dev” (desarrollo)

Y claro, en application.properties tendremos las propiedades que serían comunes entre ambos. Ahora si ejecutamos la aplicación generada con el parámetro

  1. -Dspring.profile.active=prod
  2.  
  1. #Ejemplo:
  2. $ java -jar miAplicacion.jar -Dspring.profile.active=prod

Spring cargará las propiedades del archivo application-prod.properties.

De forma similar, podemos discriminar qué Beans de Spring son cargados dependiendo del perfil especificado. Esto se hace únicamente añadiendo la anotación:

  1. @Profile("prod")

Al Bean que va a ser discriminado. Esto es útil por ejemplo para quitar la configuración de H2 en el perfil de producción o la inyección de datos de ejemplo.

Definición de conexiones con la base de datos

Idealmente en el ambiente de desarrollo estaremos usando una base de datos en memoria H2. En la configuración de producción estaremos usando el JDBC de MySQL, por lo que en application-prod.properties especificaremos:

spring.datasource.url=jdbc:mysql://{host}/{esquema}
spring.datasource.username={nombre de usuario de la base de datos}
spring.datasource.password={password del usuario de la base de datos}
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Adicionalmente es recomendable añadir:

spring.datasource.testOnBorrow=true
spring.datasource.validationQuery=SELECT 1

Si no incluimos esto anterior la conexión a la base de datos se “muere” después de un determinado periodo de inactividad (no sé el dato exacto pero es menos de 24 horas). Estas propiedades indican que si fracasa el query de validación (SELECT 1) antes de una consulta entonces se destruye la conexión con la base de datos y se crea una nueva.

Ya por último podemos indicar si Hibernate debe de hacer alguna acción con el esquema al iniciar nuestra aplicación con la propiedad:

spring.jpa.hibernate.ddl-auto

La cual puede tener los siguientes valores:

  • update: Actualiza el esquema (ALTER TABLE)
  • create: Elimina el esquema anterior (DROP TABLE) y lo vuelve a crear (CREATE TABLE)
  • create-drop: Elimina el esquema al terminar la aplicación y lo crea al iniciar la aplicación

Es importante mencionar que esto anterior es útil para la realización de pruebas o para la creación inicial de la base de datos y es mejor eliminar esta configuración en producción por el cáracter destructivo que representan estas acciones.

Plugin de despliegue

Para poder hacer uso de la funcionalidad de registrar la aplicación como un servicio tenemos que añadir el siguiente plugin de construcción de Spring Boot 1.3:

  1.         <build>
  2.                 <plugins>
  3.                         <plugin>
  4.                                 <groupId>org.springframework.boot</groupId>
  5.                                 <artifactId>spring-boot-maven-plugin</artifactId>
  6.                                 <version>1.3.0.M2</version>
  7.                                 <configuration>
  8.                                         <executable>true</executable>
  9.                                 </configuration>
  10.  
  11.                         </plugin>
  12.                 </plugins>
  13.         </build>

Hasta esta fecha que Spring Boot 1.3 no ha sido liberado es necesario incluir en el archivo de dependencias el repositorio de milestones de Spring para que obtenga la versión del último milestone indicado anteriormente:

  1.         <pluginRepositories>
  2.                 <pluginRepository>
  3.                         <id>spring-libs-milestones</id>
  4.                         <url>http://repo.spring.io/libs-milestone</url>
  5.                 </pluginRepository>
  6.         </pluginRepositories>

Compilación

Por último empaquetamos nuestra aplicación con

  1. $ mvn clean package

Fase 2: Preparación de la instancia de Amazon EC2

Una vez que hayamos creado la instancia de Amazon Linux en Amazon EC2 y creado nuestro grupo de seguridad para que podamos entrar vía SSH, iniciaremos la instalación del software necesario. Primero nos aseguramos que tengamos actualizados todos los repositorios de yum con

  1.  
  2. $ sudo yum update -y

Instalamos los siguientes paquetes:

  • MySQL 5.5
  • Apache HTTP Server: Servidor HTTP que funciona out-of-the-box y que lo usaremos como proxy.
  1. $ sudo yum install -y httpd24 mysql55-server

Desinstalamos la versión de Java 7 que viene preinstalada e instalamos la versión 8:

  1. $ sudo yum remove java-1.7.0-openjdk
  2. $ sudo yum install java-1.8.0

Inicializamos el servidor de Apache:

  1. $ sudo service httpd start

Lo hacemos que inicie junto con el inicio el sistema:

  1. $ sudo chkconfig httpd on

Verificamos que todo se encuentre correctamente ingresando a la URL de nuestra instancia de Amazon EC2, lo que nos debe de responder con una landing page del servidor.

landingpage

Iniciamos el servicio de MySQL:

  1.  
  2. $ sudo service mysqld start

Configuramos el servidor de MySQL con el siguiente comando:

  1. $ sudo mysql_secure_installation

Esto nos hará las siguientes preguntas:

  • Primero es el password de la cuenta de root. Al inicio la cuenta root de MySQL no cuenta con un password por lo que damos Enter. Luego nos pedirá si le queremos establecer un password, lo hacemos.
  • Eliminar las cuentas de usuario anónimas
  • Deshabilitar login de root de forma remota
  • Eliminar las base de datos de prueba
  • Recargar los privilegios y guardar los cambios

De preferencia es mejor contestar afirmativo a las preguntas anteriores.

Registramos ahora a MySQL para que inicie junto con el inicio del sistema:

  1. $ sudo chkconfig mysqld on

Con esto ya tenemos la instancia lista para nuestra aplicación.

Fase 3: Subida de la aplicación

Aquí entra una caja negra en el proceso. Lo importante es que el JAR o WAR empaquetado se suba al servidor, ya sea por medio de algo tan sencillo como una transferencia SFTP o algo más sofisticado como parte de un Job de Jenkins. En fin, una vez subido es posible registrarlo como un servicio por medio de crear una liga simbólica a init.d, suponiendo que nuestra aplicación está en una carpeta /jar dentro de la carpeta home de ec2-user, el comando sería el siguiente:

  1. $ sudo ln -s /home/ec2-user/jar/miapp.jar /etc/init.d/miapp

Con esto podemos iniciar la aplicación con:

  1. $ sudo service miapp start

Sin embargo, nunca indicamos qué perfil usar, por lo que la aplicación no se comportará como deseamos. Para indicar el perfil hay que crear un archivo de configuración con terminación .conf que tenga el mismo nombre que el ejecutable, por ejemplo miapp.conf

En este archivo agregamos la siguiente línea que indica los parámetros adicionales que serán añadidos a la ejecución del programa y especificaremos el perfil correspondiente que queremos usar en Amazon

  1. JAVA_OPTS=-Dspring.profiles.active=prod

Por último es recomendable el registrar la aplicación para que se inicie con el inicio del sistema, esto se hace de la misma forma que el servidor de Apache y MySQL

  1.  
  2. $ sudo chkconfig miapp on

Los logs de la aplicación en este modo se estarán registrando en el archivo

/var/log/miapp.log

Última fase: Registro de un subdominio

En esta última sección quiero indicarles cómo de la forma que hemos configurado todo es posible usar un dominio del que tengamos control para generar un subdominio que apunte a nuestra aplicación de Spring Boot. Para esta última parte es necesario el tener un dominio registrado y acceso a su configuración DNS.

Primero es necesario que registremos una nueva “Elastic IP” y asignarla a nuestra instancia para tener una IP fija.

Después usando esta IP creamos un nuevo registro en nuestro DNS con la siguiente configuración:

  • Name: subdominio.dominio.com
  • TTL: 60
  • Class: IN
  • Type: A
  • Record: {IP fija de la instancia}

Esto hará que el DNS redirija a la instancia cuando se ingrese el subdomino indicado. Ahora es necesario configurar la instancia para redirigir las peticiones al lugar adecuado. Afortunadamente el servidor HTTP de Apache cuenta con una amplia gama de funcionalidades entre las cuales está el poder ser usado como proxy inverso. Podemos indicarle que si llega una petición de un cierto subdominio, se redirija a una determinada dirección. En el caso concreto de lo que queremos, necesitamos redirigir a la dirección local de la instancia de Tomcat lanzada de Spring Boot, que por defecto es ejecutada en el puerto 8080, esto se hace añadiendo la siguiente entrada de hosts virtuales al archivo de configuración alojado en:  /etc/httpd/conf/httpd.conf :

  1.  
  2. <VirtualHost *:80>
  3.     ServerName dominio.com
  4.     ServerAlias subdominio.dominio.com
  5.     ProxyRequests Off
  6.     ProxyPass /  http://localhost:8080/
  7.     ProxyPassReverse / http://localhost:8080/
  8. </VirtualHost>

Al final el flujo quedaría de la siguiente forma:

flujo

Con esto ya tenemos una aplicación de Spring Boot desplegada completamente y disponible en subdominio.dominio.com. Cabe mencionar que esto es únicamente una forma de varias de alojar una aplicación de Spring Boot y es la que hallé más sencilla y suficientemente controlada, sin embargo, me gustaría conocer si ven mejoras a este proceso y de qué forma y en dónde despliegan ustedes sus aplicaciones de Spring Boot.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de Nopalin

Es necesario apache?

Muy buen aporte, solo tengo una duda, ¿cual es la ventaja de usar apache como proxy? al final de cuentas tanto apache como la aplicacion en springboot corren en el mismo server, solo cambias el puerto del tomcat al 80 y ya esta el acceso directo. te compro el proxy cuando la aplicación en spring boot correrá en un server privado a donde solo el equipo proxy tiene acceso, pero en este escenario creo que esta demás, no?

Imagen de ezamudio

Apache

Apache aguanta mejor los ataques en caso que te quieran hacer DDoS o estar picándole a ver por dónde pueden crackear tus apps/servicios o cómo tirarlos.

Imagen de Nopalin

Sin seguridad

Bueno bueno, era más que obvio la respuesta de que apache es mejor como punto de entrada para la seguridad, pero quien decida atacarte, con un simple portscan se dará cuenta que en el puerto 8080 corre otro servicio y que seguramente sera un java-based por las urls que se generan.

La finalidad del post era hacer promoción a spring boot, el cual no requiere apache, por eso creo que está demás agregarlo. Y si lo agrega con el pretexto de seguridad o escalabilidad... bueeeeno es ya todo otro tema, no?

Imagen de arterzatij

bueeeeno es ya todo otro

bueeeeno es ya todo otro tema, no?

Claro!

Pero así nacen las discusiones para mejorar los aspectos de hacer un buen software.

con un simple portscan se dará cuenta que en el puerto 8080 corre otro servicio y que seguramente sera un java-based por las urls que se generan.

No necesariamente dejas el puerto abierto. Aunque eso ya es seguridad en redes y como configuras las instancias en amazon. Yo en lo particular todo "Java" lo tengo en una VPN y el único puerto de salida es apache es decir el 80. Así que aunque te hagan un escaneo no verán abierto los demás puertos.

La finalidad del post era hacer promoción a spring boot, el cual no requiere apache, por eso creo que está demás agregarlo

En si, muchos contenedores no ocupan un http server pero son buenas practicas, muchas ya en la liga, otras como un hilo que se levanto aquí. Para cuando hacen mantenimientos y necesitas bajar la aplicación, en apache solo configuras un redirect a "estamos en mantenimiento" y la URL sigue dando respuestas.

Pero en definitiva, todo depende de donde estará tu app deployada y si tienes control del trafico hacia tu app :)

Imagen de Nopalin

Apache es el servidor http

Apache es el servidor http mas conocido del mundo, y de los programadores web es el más utilizado. Sin embargo eso no lo convierte en el mejor.

Jetty es una buena alternativa y al ser 100%, nos brinda un mejor panorama para escalar aplicaciones java.

https://wiki.eclipse.org/Jetty/Tutorial/Apache

Imagen de arterzatij

Apache es el servidor http

Apache es el servidor http mas conocido del mundo, y de los programadores web es el más utilizado. Sin embargo eso no lo convierte en el mejor.

Asi es, es el mas conocido, pero no el único de hecho hay mas comparison-of-web-servers.

Pero pues muchas veces, como lo mencione dependera de donde despliegues tu app. A veces no tienes control del software que instalaras en otras si.

Graficas de comparacion

Imagen de ezamudio

nginx

Otra opción es nginx.

Tal vez ya es cuestión de gustos; yo prefiero tener un web server dedicado (me refiero al proceso) y definir ahí proxies a las aplicaciones necesarias. Hay varias razones para esto:

  • Se pueden tener varias aplicaciones, cada una en su propio contenedor (sea jetty, tomcat, jboss, o incluso standalone) y sólo es cuestión de agregar el proxy adecuado en el web server.
  • Se pueden tener aplicaciones que no sean visibles desde internet, sino sólo por un túnel o algún otro puerto que no sea el 80, para staging/pruebas/uso interno/etc.
  • Las aplicaciones de administración de tomcat o el contenedor que sea, no quedan expuestas a internet.
  • Se pueden definir caches para recursos estáticos como imágenes, etc y que esos los despache el web server, que es muy bueno para despachar contenido estático.
  • Los contenedores de aplicaciones pueden incluso estar en otros equipos. Puedes armar una DMZ.

Muchas gracias. Cierto, en el

Muchas gracias. Cierto, en el caso más sencillo podríamos decir que está demás, si el servidor ya tiene un dominio asociado entonces sólo hay que configurar que el puerto de ejecución sea el 80 y listo.

Sin embargo el caso de uso para el que he ocupado esta solución es para proyectos en donde no es necesario registrar un nuevo dominio o simplemente cuando queremos darle acceso a una versión de prueba del sitio a un cliente, en lugar de darles la URL larga que genera Amazon les damos una URL más "amigable". Como en mi empresa ya tenemos un dominio registrado simplemente le asignamos el subdominio de esta forma a la aplicación particular. Por ejemplo, nosotros tenemos un servidor con recursos limitados para demostraciones con clientes que tiene en este momento dos aplicaciones corriendo: 1 en PHP y 1 en Spring Boot, cada una con su respectivo subdominio.

Por supuesto creo que es cuestión de preferencia de si quieres usar Apache HTTP, Tomcat, Jetty o algo más para esto mismo.

Imagen de ezamudio

ssl

Otra razón para usar apache o nginx: SSL. Es más rápido el SSL en estos web servers que en tomcat/jboss/jetty/glassfish/etc.