3. Despliegue Automatizado

Despliegue Automatizado

En el tema anterior hemos visto como desplegar manualmente en los servidores Apache y Nginx. En este tema vamos a ver como hacer lo mismo pero de forma automatizada. Automatizar tareas es fundamental ya que se minimizan los errores que se cometen. Por otro lado el despliegue puede llegar a hacerse decenas de veces al día, por lo que obviamente debe estar automatizado.

Podemos pensar que instalar (desplegar) decenas de veces al día una aplicación es un poco exagerado pero en sitios web como Amazon ésto si que tiene sentido. Si en la aplicación de amazón hay un error que suele afectar al 0,1% de sus ventas. ¿Cuanto dinero pierden diariamente por tener ese error? Y en este tipo de aplicaciones se suelen estar constantemente realizando cambios y corrección de error , así que ¿porque no desplegarla en cuanto está corregido un error o añadida la nueva funcionalidad? Esto en la literatura informática se llama Continuous delivery

Estructura de carpetas

Para poder automatizar el despliegue , lo primero es tener una estructura definida de nuestro proyecto y de nuestro servidor. Durante el curso vamos a usar este esquema tanto en este módulo como en los otros módulos. El esquema he intentado que sea lo mas fiel posible a la realidad de las empresas aunque he intentado simplificarlo. Sin embargo cada empresa tendrá la estructura que se adecue al proyecto que estén desarrollando.

Estructura de carpetas.

/mi-proyecto
/src //Donde está todo el código fuente de la aplicación
/main
/java
/com
/daw
/peliculas
Main.java
/resources
/static
index.html
/assets
/img
/fonts
/media //Audios y Videos
/vendor //librerías bajadas de internet (también se puede llamar lib)
/jquery
/boostrap
/scss //Código en SASS
/css //código en CSS
/ts //Código en TypeScript
/js //código en JavaScript
/docs //Documentación de la aplicación. Es la documentación con los diagramas UML, el JavaDoc, etc.
/disenyo //Donde están las imágenes en formato Photshop o similares que estamos visualmente diseñando
/db //Ficheros ".sql" de creación de la base de datos, etc. Es todo lo necesario para tener la base de datos preparada desde cero. Incluye insertar datos que siemrpe deben estar , como por ejemplo códigos de provincias. Pero NO las inserciones de los datos del usuario.
/scripts //Scripts varios. Normalmente están relacionados con el despliegue de la aplicación. Es lo mas importante en este módulo de despliegue.
deploy.sh
build.sh
/node_modules //Carpeta que usa node para guardar lo que instalamos de NodeJS
/target //El código "compilado" de la aplicación. Eso incluye transformar de TypeScript a JavaScript o de SASS a CSS,etc.
AppPeliculas-0.0.1-SNAPSHOT.jar
package.json

Hay que fijarse que en git no se debe guardar únicamente la carpeta "src" del código fuente sino que debe guardarse la carpeta "mi-proyecto" completa. Aunque se puede hacer alguna excepción con "dist" o "node-modules". El motivo de guardar la carpeta "mi-proyecto" es debido a que realmente todas esas carpetas son las que forman el proyecto, y no únicamente la carpeta "src".

Carpeta target

La carpeta target es la mas importante para el módulo de despliegue ya que en ella estará el proyecto tal y como se va a desplegar.

En otros IDE's como NetBeans , esta carpeta puede llamarse dist

Podemos pensar que debe ser únicamente una copia de la carpeta "src" ya que el código JavaScript pero si que se pueden hacer muchos cambios en esta carpeta. Veamos algunos ejemplos:

Compilar el código fuente Java a el compilado en los ficheros .class

Minimizar el código: Hacer que ocupe menos el código JavaScript o CSS. Se hace para que las páginas web se carguen antes. Y suele consistir en quitar los comentarios, los retornos de carro, etc.

Ofuscar el código: Para evitar que alguien pueda copiar nuestro código se suele, por ejemplo , cambiar el nombre de las variables por nombres sin sentido. Normalmente se minimiza y se ofusca a la vez.

Transpilar: Es transformar el código fuente de un lenguaje en otro. Se usa:

TypeScript a JavaScript: TypeScript es un lenguaje con mayor tipado que JavaScript y se suele usar en vez de JavaScript. Como los navegadores no soportan TypeScript hay que convertirlo (transpilarlo) de TypeScript a JavaScript.

SASS a CSS: SASS es un lenguaje similar a CSS pero con mayores funcionalidades. Como los navegadores no soportan SASS hay que convertirlo (transpilarlo) de SASS a CSS. En SASS también se suele decir transformar el código de SASS a CSS en vez de decir "transpilar"

Etc.

Por ello , por ejemplo, en la carpeta "src" estaría el código en TypeScript y en la carpeta "target" estaría en JavaScript correspondiente al TypeScript.

Tareas de Automatización del despliegue

Veamos ahora un ejemplo de como desplegar una aplicación web en Java con SASS , TypeScript y que se despliega en Apache.

Build (en maven se llama package)

Copiar el código de "src" en "target"

Compilar el código Java

Transpilar el código SASS en CSS dentro de la carpeta "target"

Transpilar el código TypeScript en JavaScript dentro de la carpeta "target"

Borrar el código SASS de la carpeta "target"

Borrar el código TypeScript de la carpeta "target"

Generar el .jar

Desplegar

Copiar el .jar de la carpeta "target" al servidor donde se va a ejecutar o copiar el .war en el Tomcat

Ejecutar

Ejecutar el .jar

Para hacer todas esta tareas vamos a usar Scripts del sistema operativo , por ejemplo en Linux se usará "Bash" y los Script de npm que vamos a ver mas adelante.

Transformar SASS en CSS

Instalar SASS

npm install sass -g

Transpilar SASS en CSS

sass ./scss/main.scss ./css/main.css

Transformar TypeScript en JavaScript

Instalar TypeScript

npm install typescript -g

Transpilar TypeScript en JavaScript

tsc --outDir ./js ./ts/*.ts

Generar HTML con los issues

Para generar un HTML a partir de los los de git se puede usar este script llamado generar_html_from_log.sh

Y se ejecuta así:

Un ejemplo similar del servidor Tomcat se puede ver en Apache Tomcat 10 Changelog

Scripts npm

Para hacer las tareas que acabamos de indicar, se harán scripts de Bash que se dejarán en la carpeta "/scripts" sin embargo ahí puede haber gran cantidad de Script o podemos querer usar otra carpeta u otro lenguaje. Para unificar todo esto en los proyectos web, vamos a homogeneizar la ejecución de estos scripts mediante los Scripts de npm.

En los siguientes artículos se explican los scripts npm:

npm scripts

Why npm scripts

NPM is an amazing build tool

Veamos ahora un ejemplo de Script de transformar SASS a CSS.

Crear un Script para node dentro del packaje.json

Ejecutar un Script de node.

npm run compile-scss

¿Que hemos ganado con ésto? Que ahora todas las "tareas" que tenemos que hacer en el proyecto se pueden ver en el "packaje.json" y siempre se ejecutarán como "npm run nombre-tarea"

Ejecutar un Script cuando algo cambia

onchange permite ejecutar un comando cuando cambia algún fichero.

Instalar onchange

npm install onchange -g

Mostrar un mensaje cada vez que cambia algo en un fichero JavaScript

onchange '**/*.js' -- echo Ha cambiado un fichero JavaScript

Maven

Ahora deberemos llamar a nuestro script de npm run build desde maven. Usaremos el plugin de exec-maven-plugin.

Para ello añadiremos el siguiente XML al pom.xml

Para ejecutar Maven usaremos:

Más información:

Introduction to the Build Lifecycle

Copiar ficheros por scp

Para copiar ficheros locales a un servidor remoto se usa la sintaxis:

scp /mi_carpeta_local/mi_fichero mi_usuario_remoto@ip_remota:/mi_carpeta_remota

Ejemplo para copiar el fichero local /home/logongas/app.jar en la máquina remota 192.168.1.1 en la carpeta /opt usando el usuario remoto logongas:

Para copiar ficheros remoto en una carpeta local se usa la sintaxis:

scp mi_usuario_remoto@ip_remota:/mi_carpeta_remota/mi_fichero /mi_carpeta_local

Ejemplo para copiar el fichero remoto app.jar que está en /opt en la máquina remota 192.168.1.1 y guardarlo en nuestra carpeta local /home/logongas usando el usuario remoto logongas:

Ejecutar ordenes en servidores remotos

Para ejecutar ordenes en ordenadores remoto se usa la sintaxis:

ssh mi_usuario_remoto@ip_remota "comando"

Ejemplo de crear el directorio /home/logongas/miapp en la máquina remota 192.168.1.1 usando el usuario remoto logongas:

Scripts necesarios

Veamos ahora los script que deberíamos siempre tener en cualquier proyecto.

build: Compila el código de cliente de src y lo deja en tarjet. Eso incluye compilar TypeScript, el SASS, etc.

npm run build

deploy: Despliega la aplicación en un servidor

npm run deploy

Por lo tanto todo queda de la siguiente forma:

Para compilar TODO el código:

Para desplegara la aplicación:

Variables de entorno

Es posible personalizar los scripts en Bash usando variables de entorno.

Ejemplo de declaración de variables

Para usar las variables se usa ${} y dentro de las llaves el nombre de la variable. Tambien se puede usar solo $ delante del nombre de la variable.

Ejemplo de uso de las variables

Podemos tener en un fichero externo las variables.

Ejemplo de config.env

Para cargar un fichero de variables se usa el comando source.

Ejemplo de cargar las variables de config.env

Parámetros del Script

Los Script de bash pueden tener parámetros, para acceder a ellos se usa $1, $2, $3, etc. siendo cada uno de ellos la posición del parámetro.

Ejemplo de script con 1 parámetro

Continuando con el ejemplo se usar $1 para acceder al parámetro

Interpretar las Variables de Bash

Al pasar argumentos a un script se puede hacer de varias maneras.

Imaginemos el script test.sh, que muestra por pantalla el primer argumento

Y ahora lo vamos a ejecutar de 3 formas distintas:

¿Va a mostrar siempre Hola?

Veamos el resultado:

¿Qué es lo que ha pasado?

Parametros	Resultados	Explicación
$a $b	Hola	Lo toma como 2 argumentos separados y solo muestra Hola porque solo se muestra el primer argumento
'$a $b'	$a $b	Todo ahora es un único argumento, pero con la peculiaridad de que no interpreta las variables (y otras cosas) de BASH
"$a $b"	Hola Adios	Todo ahora es un único argumento, pero si que se interpretan las variables de bash
"$a \$b"	Hola $b	Todo ahora es un único argumento, pero si que se interpretan las variables de bash aunque no se evalúa la variable $b ya que está escapada con \.

Instalar Java JRE

Vamos a ver como instalar y usar el JRE:

Para instalar el JRE:

Descargar el fichero tar.gz desde Temurin download

Descomprime el fichero tar.gz en la carpeta actual y se creará una carpeta con el JRE:

Comprobar que todo está instalado:

Crea la variable de entorno JAVA_HOME:

Muestra la versión de java con:

Ejecutar un jar:

El nombre del fichero OpenJDK21U-jre_x64_linux_hotspot_21.0.5_11.tar.gz y de la carpeta jdk-21.0.5+11-jre será distintas según la versión de java que estés usando.
Si en GitHub no deja subir el OpenJDK21U-jre_x64_linux_hotspot_21.0.5_11.tar.gz porque es un fichero demasiado grande, se puede partir en pequeños ficheros. Para dividir un fichero en trozos se usa esta orden: Que generará una lista de ficheros del estilo: nombre_fichero_grande.trozo_aa nombre_fichero_grande.trozo_ab nombre_fichero_grande.trozo_ac ... Etc. Para unir los trozos se usará la orden:

Cortafuegos

Para que funcione la aplicación, es necesario abrir los puertos. Para ello se usa la orden ufw allow numero_puerto.

Ejemplo para abrir el puerto 80

Para permitir que un usuario pueda gestionar el cortafuegos se debe ejecutar la orden:

Y añadir una linea.

Ejemplo a añadir para el usuario logongas

Conexión con claves pública/privada

Para conectarse a un servidor, ya no se usan contraseñas sino que se crear pares de claves (una pública y otra privada).

Para generar el par de claves se usa la orden ssh-keygen.

Generar el par de clave con formato RSA con una logitud de 4096 bits, con el correo juan@alumno.fpmislata.com y que se genera el par de claves en los ficheros $HOME/.ssh/id_rsa y $HOME/.ssh/id_rsa.pub

Esa orden genera 2 ficheros:

$HOME/.ssh/id_rsa: Es la clave privada

$HOME/.ssh/id_rsa.pub: Es la clave pública que hay que copiar en el servidor al que nos queremos conectar

Ejemplo de Contenido de los ficheros

Ahora tenemos que ir al servidor remoto al que nos queremos conectar e ir a la carpeta del usuario al que nos queremos conectar. Y añadir al fichero $HOME/.ssh/authorized_keys el contenido de nuestra clave publica

En el siguiente ejemplo vemos que ya existe una clave y añadimos la nueva que acabamos de crear.

Por último para conectarnos al servidor con ssh o scp deberemos indicar el fichero de la clave privada y para ello se usará el parámetro -i.

Ejemplo con ssh indicado que la clave privada a usar está en $HOME/.ssh/id_rsa

Ejemplo con scp indicado que la clave privada a usar está en $HOME/.ssh/id_rsa

Si guardamos nuestro par de claves con los nombres de $HOME/.ssh/id_rsa y $HOME/.ssh/id_rsa.pub se usarán por defecto al conectarnos a cualquier servidor y no hará falta indicar que las usemos.Por otro lado en el fichero $HOME/.ssh/config podemos especificar para cada servidor la clave que queremos usar. Por lo tanto las 3 reglas (en orden de prioridad) para saber que clave privada usar son: Se indica en el propio comando ssh o scp con -i Se indica en el fichero $HOME/.ssh/config para cada servidor Se usa $HOME/.ssh/id_rsa La clave añadida con ssh-agent (No explicada)

Ejercicios

Ejercicio 1.A

Crea un Script en bash llamado "build.sh" que:

Compile el SASS de src en la carpeta css de src

Compile el SASS de tarjet en la carpeta css de tarjet pero sin poner espacio

Se ejecute el Script "build.sh"

Haz que se ejecute npm run build cuando se lance install en Maven. Es decir al ejecutar

Ejercicio 1.B

Modifica el proyecto anterior , añadiendo un Script en bash llamado "deploy.sh" que:

Ejecute mvn clean install

Copie el jar a una máquina remota en Linux

Ejecute el jar en dicha máquina remota en Linux

Ejercicio 1.C

Modifica el proyecto anterior , crean en el script deploy.sh las siguientes variables y úsalas:

IP_DEPLOY_SERVER

USER_NAME_DEPLOY_SERVER

APP_PORT

Puedes crear más variables si las necesitas.

Ahora extrae esas variables al fichero pruebas.env y carga esas variables usando:

Para indicar en Spring el puerto a usar en la aplicación se pasa el valor por la línea de comandos con la opción -D y definiendo la variable server.port

Ejercicio 1.D

Teniendo 3 servidores distintos donde desplegar:

pruebas

preproduccion

produccion

Cada entorno tiene una IP, un usuario y un puerto distinto.

Crea 3 ficheros de variables para cada entorno en la carpeta entornos:

pruebas.env

preproduccion.env

produccion.env

Ahora crea los siguientes scripts en el package.json

Y modifica deploy.sh de forma que el nombre del fichero de configuración sea el primer argumento del script

Por último despliega la aplicación en cada uno de los entornos ejecutando

En el entorno de pruebas:

En el entorno de preproducción:

En el entorno de producción:

Ejercicio 1.E

Modifica el script de despliegue de forma que antes de ejecutar mvn clean build se cambie a la rama correspondiente de git para que se compile la versión correcta.

Entorno	Rama de Git a compilar
Pruebas	develop
Preproduccion	release
Produccion	master

Para ello modifica los ficheros .env añadiendo la varaible RAMA_GIT para indicar a que rama de debe mover el script

Ejercicio 1.F

Cambia ahora el script de despliegue para que en la máquina destino se copie tambien el JDK y se use ese JDK de has copiado.

Para ello tendrás que:

Tener en local el JDK comprimido en la carpeta dependencies

Copiar el fichero del JDK en la máquina desino

Descomprimir el fichero en la máquina desino

Ejecutar el jar con el jdk que has copiado.

Ejercicio 1.G

Cambia ahora el script de despliegue para que en la máquina destino el cortafuegos abra el puerto que esté usando la app.

Ejercicio 1.H

Crea un par de claves publica/privada para acceder al servidor con esas claves en vez de con una contraseña. Es decir que tanto las órdenes de ssh como las de scp usen la clave privada en vez de una contraseña.

Ejercicio 1.I

Ahora vamos a separar el proyecto en 2 repositorios.

El proyecto de Java con todo el código excepto la parte de deploy. Este proyecto lo vamos a llamar Facturas

Otro proyecto con solo la parte necesaria para desplegar. Este otro proyecto lo vamos a llamar DesplegarFacturas

Ahora deberás hacer lo siguiente:

Crear 2 repositorios en GitHub para guardar los 2 proyectos.

Facturas

DesplegarFacturas

En el proyecto de desplegar:

Crea un nuevo fichero de variables de entorno con la URL del proyecto de Facturas. Este fichero lo llamarás app.env y la variable se llamará URL_SOURCE_GIT

En el script de deploy.sh:

Borrar la carpeta ../Facturas

Descargar el código fuente de la app en ../Facturas desde la URL ${URL_SOURCE_GIT}

Cambiar a la rama de git adecuada

Generar el AppFacturas-0.0.1-SNAPSHOT.jar (incluyendo generar el CSS)

Copiar los siguientes ficheros en el servidor de despliegue

OpenJDK21U-jre_x64_linux_hotspot_21.0.5_11.tar.gz

AppFacturas-0.0.1-SNAPSHOT.jar

server.sh

Borrar la carpeta ../Facturas

Ejecutar en el servidor el script server.sh el cual hace lo siguiente:

Descomprimir el OpenJDK21U-jre_x64_linux_hotspot_21.0.5_11.tar.gz

Abrir el puerto del cortafuegos

Crear la variable de entorno JAVA_HOME

Ejecutar el AppFacturas-0.0.1-SNAPSHOT.jar indicando el puerto con la variable server.port

Con todo ésto lo que hemos conseguido es que descargando únicamente el proyecto DesplegarFacturas y ejecutando npm run deploy:nombre_entorno podamos desplegar la aplicación.

Es decir que estará todo totalmente automatizado a excepción de las 2 siguiente órdenes:

git clone http://github.com/usuariogit/DesplegarFacturas.git

npm run deploy:nombre_entorno.

Aunque realmente aun no lo hemos automatizado todo ya que: Para ejecutar npm nos haría falta NodeJS Para compilar el código Java nos haría falta Maven Para compilar el código Java nos haría falta el JDK Para SASS nos haría falta el compilador de SASS Para TypeScript nos haría falta ek compilador de TypeScript Etc.

Veamos ahora un esquema de como quedarían los directorios más importante de cada máquina.

Máquina local desde la que se despliega

/Facturas
/src
/main
/java
/resources
/static
index.html
/scss
main.scss
/css
main.css
/scripts
build.sh
/target
AppFacturas-0.0.1-SNAPSHOT.jar
package.json
pom.xml
/DesplegarFacturas
/certificados
id_rsa
id_rsa.pub
/global
app.env
/entornos
pruebas.env
preproduccion.env
produccion.env
/dependencies
OpenJDK21U-jre_x64_linux_hotspot_21.0.5_11.tar.gz
/scripts
deploy.sh
server.sh
package.json

Maquina remota en la que se despliega

/home/alumno
/.ssh
authorized_keys
/nombreAlumno
/jdk-21.0.5+11-jre
/bin
java
server.sh
OpenJDK21U-jre_x64_linux_hotspot_21.0.5_11.tar.gz
AppFacturas-0.0.1-SNAPSHOT.jar