====== Despliegue de aplicaciones web ======
{{docker.png }}
Aqui vamos a ver como poner en marcha una aplicación web hecha con Spring Boot como paquete jar, de forma que no necesitemos de un servidor de aplicaciones para desplegar y ponerla en marcha.
También veremos cómo desplegar todo el entorno localmente usando Docker, asi como usando AWS o y también directamente en un servidor.
===== Crear una imagen docker de la API =====
==== Empaquetar la aplicación ====
El primer paso será empaquetar la aplicación en formato jar
santi@zenbook:$ mvn clean package
Eso hará que se genera un fichero jar del estilo a //myapi-0.1.jar// en la carpeta //target// del proyecto, dependiendo de como hayamos definido las propiedades //artifactId// y //version// en el fichero //pom.xml//. Será el fichero que tendremos que llevar a la máquina donde queramos desplegar la aplicación. Hay que tener en cuenta que necesitaremos instalar el JDK en esa máquina para que pueda ejecutarla.
En este momento la aplicación ya se encuentra empaqueta y lista para ser ejecutada como si se tratara de una simple aplicación Java:
santi@zenbook:$ java -jar myapi-0.1.jar
==== Crear la imagen Docker ====
También podemos desplegar y poner en marcha nuestra API usando un contenedor docker. De esta forma podemos elegir el entorno sobre el que queremos ejecutarla y hacer que éste se asemeje a aquel donde realmente vayamos a desplegar nuestro desarrollo en el entorno de producción.
Creamos el fichero Dockerfile en la carpeta del proyecto. De acuerdo a nuestro proyecto y su fichero ''pom.xml'', nuestro artifactId es ''myapi'' y la versión ''0.1'', por lo que el paquete creado tras ejecutar ''mvn clean package'' será ''myapi-0.1.jar'' y estará ubicado en la carpeta ''target'' que se habrá creado en nuestro proyecto.
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY target/myapi-0.1.jar app.jar
ENTRYPOINT ["java","-jar","app.jar"]
Para crear la imagen, abrimos una consola y lanzamos el siguiente comando. La imagen se creará con el nombre ''myapi''.
santi@zenbook:$ docker build -t myapi .
==== Crear un proyecto Docker Compose para pruebas en local ====
Nuestra API está realmente compuesta de dos partes: La propia API escrita en Java con Spring Boot y la base de datos MongoDB que utilizamos para almacenar/consultar la información. Asi, necesitaremos crear un proyecto docker compose con dos servicios (API + Bases de Datos) para lanzarlo en nuestro equipo y, por ejemplo, hacer pruebas.
Para eso, el primer paso será crear el fichero ''docker-compose.yaml'' donde se definen ambos servicios. EN el caso de la API partiremos de la imagen que ya hemos creado anteriormente.
En cualquier caso, antes de eso, crearemos un fichero ''.env'' para almacenar alli las variables de entorno que necesitemos para configurar nuestro proyecto docker compose.
=== Ejemplo usando MariaDB como base de datos ===
MARIADB_HOST: localhost
MARIADB_DATABASE: mydatabase
MARIADB_USER: myuser
MARIADB_PASSWORD: myp4ssw0rd
MARIADB_ROOT_PASSWORD: myr00tp4ssw0rd
SPRING_PORT=8080
Y ahora el fichero ''docker-compose.yaml'':
version: "3.4"
name: myapi-mariadb
services:
mariadb:
image: mariadb-11.2.6
container_name: mariadb
restart: unless-stopped
env_file: ./.env
ports:
- $MARIADB_PORT:$MARIADB_PORT
volumes:
- db:/data/db
app:
image: myapi
container_name: myapi
env_file: ./.env
ports:
- $SPRING_PORT:$SPRING_PORT
environment:
SPRING_APPLICATION_JSON: '{
"spring.datasource.url" : "jdbc:mysql://$MARIADB_HOST:$MARIADB_PORT/$MARIADB_DATABASE"
"spring.datasource.username" : "$MARIADB_USERNAME"
"spring.datasource.password" : "$MARIADB_PASSWORD"
}'
depends_on:
- mariadb
restart: on-failure
volumes:
db:
=== Ejemplo usando MongoDB como Base de datos (para APIs reactivas) ===
MONGODB_DATABASE=myapi
MONGODB_PORT=27017
SPRING_PORT=8080
Y ahora el fichero ''docker-compose.yaml'':
version: "3.4"
name: myapi-mongodb
services:
mongodb:
image: mongo:7.0.7
container_name: mongodb
restart: unless-stopped
env_file: ./.env
ports:
- $MONGODB_PORT:$MONGODB_PORT
volumes:
- db:/data/db
app:
image: myapi
container_name: myapi
env_file: ./.env
ports:
- $SPRING_PORT:$SPRING_PORT
environment:
SPRING_APPLICATION_JSON: '{
"spring.data.mongodb.uri" : "mongodb://mongodb:$MONGODB_PORT/$MONGODB_DATABASE?authSource=admin"
}'
depends_on:
- mongodb
restart: on-failure
volumes:
db:
Ahora podemos lanzar nuestra API junto con su base de datos ejecutando el siguiente comando:
santi@zenbook:$ docker compose up -d
Hemos mapeado los puertos de ambos contenedores a nuestro equipo local usando los mismos valores, por lo que podemos lanzar peticiones a la API haciéndolas directamente a ''http://localhost:8080'' y también podemos conectarnos a MongoDB, por por ejemplo con Studio 3T, conectándonos a ''localhost:27017''
===== Crear un entorno de pruebas con docker compose =====
===== Desplegar en AWS =====
{{ aws.png?200 }}
==== Iniciar una instancia EC2 en AWS =====
* Accedemos a la AWS Management Console
{{ aws_management_console.png?500 }}
* Accedemos al servicio EC2
{{ aws_ec2.png?500 }}
* Accedemos a la opción ''Instances''
{{ aws_instances.png?500500 }}
* Desde alli pulsamos en ''Launch Instance'' para lanzar una nueva instancia de EC2
* Una vez lanzada la instancia se nos descargará el fichero PEM que nos hará de certificado cuando queramos conectar con ella a través de SSH. En nuestro caso ese fichero se llamará ''cities.pem''
==== Conectar con SSH a una instancia EC2 de AWS ====
Encontraremos las instrucciones para conectar pulsando en el botón ''Connect'' una vez hayamos seleccionado la instancia EC2 con la que queremos conectar.
{{ aws_connect.png?500 }}
Si queremos conectar mediante SSH, las instrucciones están en la pestaña que dice ''SSH Client''. Será algo como esto:
ssh -i "cities.pem" ec2-user@ec2-94-12-3-207.compute-1.amazonaws.com
==== Preparar la instancia EC2 para el despliegue ====
Una vez que hayamos logrado conectar con la instancia EC2, el primer caso será instalar todo lo necesario. En nuestro caso será:
* Docker
* Docker compose
* Node.js
* Git (para clonar el repositorio con nuestro código)
=== Instalar docker y docker-compose ===
santi@zenbook:$ sudo yum install docker
santi@zenbook:$ curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o docker-compose
santi@zenbook:$ chown ec2-user.ec2-user docker-compose
santi@zenbook:$ cp docker-compose /usr/local/bin
En este punto deberiamos ser capaces de ejecutar los comandos ''docker'' y ''docker-compose'' como superusuario:
santi@zenbook:$ sudo docker
santi@zenbook:$ sudo docker-compose
=== Instalar nodejs ===
santi@zenbook:$ yum install nodejs
=== Clonar el repositorio ===
santi@zenbook:$ sudo yum install git
santi@zenbook:$ git clone https://github.com/codeandcoke/myproject
==== Lanzar la aplicación ====
santi@zenbook:~/myproject$ sudo docker-compose -f docker-compose.yaml up -d
Y ya tendremos, por ejemplo, el endpoint ''GET /cities'' disponible en http://ec2-94-12-3-207.compute-1.amazonaws.com:8080/cars
===== Desplegar la API en un servidor =====
Asi, si suponemos que nuestra máquina remota ya está en marcha y tiene asociado un dominio como //codeandcoke.com//, tendremos que copiar ese fichero jar (únicamente) a dicha máquina. Tendremos que asegurarnos de que nuestro usuario en la máquina tenga permisos sobre la carpeta que lo copiemos (en mi caso lo dejaré en la carpeta ''/opt'')
santi@zenbook:$ scp target/myapi-0.1.jar santi@codeandcoke.com:/opt
> También podemos subirla al servidor usando cualquier aplicación de transferencia de ficheros del estilo a [[https://filezilla-project.org/|Filezilla]] o bien [[https://winscp.net/eng/download.php|WinSCP]]
==== Ejecutar la aplicación en el servidor ====
Ahora, iniciaremos sesión en la máquina remota para lanzar la aplicación:
santi@zenbook:$ ssh santi@codeandcoke.com
santi@codeandcoke.com:$ cd /opt
santi@codeandcoke.com:$ nohup java -jar myapi-0.1.jar > myapi.log 2>&1 &
* Utlizamos el comando ''nohup'' para evitar que la aplicación se detenga al cerrar la sesión de terminal desde donde la lanzamos
* ''java -jar myapi-0.1.jar'' lanza la aplicación
* ''> myapi.log'' redirige toda la salida a un fichero que nos servirá de log de la aplicación
* ''2>&1 &'' redirige toda la salida de error a la salida estándar (que irá al fichero log) y lo ejecuta en segundo plano, para dejar libre la terminal durante el resto de la sesión
También podemos prepararnos un script bash para evitar que escribir ese comando cada vez que queramos desplegar una nueva versión (puesto que además tendremos que detener la existente)
==== Acceder a la aplicación ====
Suponiendo que nuestra aplicación escucha en el puerto 8080, podremos comprobar que funciona sin problemas si accedemos, desde nuestro navegador a http://codeandcoke.com:8080
==== Modificar el puerto de acceso ====
El siguiente paso interesante sería evitar que el usuario tuviera que especificar el puerto a la hora de acceder a la aplicación desde el navegador. Eso podría evitarse, por ejemplo, modificando el puerto en el que ésta escucha (y pasarlo al 80). Esto no es ningún problema si la aplicación "vive" sola en la máquina remota.
==== Configurar Apache como Proxy para la aplicación ====
Si por el contrario tenemos algún servidor Apache funcionando, tendremos el puerto 80 ya ocupado. Si ocurre esto, podemos dejar que la aplicación siga escuchando en el puerto //8080// (o cualquier otro) y hacer que sea Apache quien nos haga de proxy. Podríamos definir un host virtual para el dominio ''codeandcoke.com'' indicando a Apache que lo que tiene que hacer es llevar a los usuarios al puerto 8080 que es donde está la aplicación. Asi, los usuarios accederán a http://codeandcoke.com para acceder a nuestra aplicación.
Para eso, definimos el host virtual en Apache de forma habitual:
ServerAdmin info@codeandcoke.com
ServerName codeandcoke.com
ServerAlias www.codeandcoke.com
ErrorLog "codeandcoke.com-error.log"
CustomLog "codeandcoke.com-access.log" combined
Y añadimos un par de líneas más para indicar que tiene que hacer de proxy con una aplicación ya desplegada:
. . .
ProxyPass / ajp://localhost:8080/
ProxyPassReverse / ajp://localhost:8080/
Quedando la configuración del sitio virtual:
ServerName codeandcoke.com
ServerAlias www.codeandcoke.com
ErrorLog "codeandcoke.com-error.log"
CustomLog "codeandcoke.com-access.log" combined
ProxyPass / ajp://localhost:8080/
ProxyPassReverse / ajp://localhost:8080/
Puede que tengamos que activar el módulo AJP de Apache si no lo está ya:
santi@zenbook:$ sudo a2enmod proxy_ajp
. . .
santi@zenbook:$ sudo service apache2 restart
. . .
> Puedes ver [[https://despliegue.codeandcoke.com/doku.php?id=apuntes:servidores_web|aqui]] más información sobre cómo configurar Apache
> Si estamos desplegando la aplicación como paquete //war// con un Apache Tomcat, necesitaremos configurar tanto Apache para que haga de Proxy como Tomcat para activar el protocolo AJP. Puedes ver cómo hacerlo [[https://despliegue.codeandcoke.com/doku.php?id=apuntes:servidores_aplicaciones#como_configurar_el_proxy_ajp_para_conectar_tomcat_con_apache|aqui]]
----
(c) 2021-2023 Santiago Faci