En esta unidad vamos a abordar el despliegue de aplicaciones escritas en distintos lenguajes de programación y sobre los frameworks web más utilizados en el momento.
TravelRoad
PostgreSQL
Framework Web
Laravel (PHP)
Express (Javascript)
Spring (Java)
Ruby on Rails (Ruby)
Django (Python)
Durante toda la unidad de trabajo vamos a desarrollar una aplicación web muy sencilla denominada TravelRoad que se encargará de acceder a la base de datos y mostrar por pantalla aquellos destinos que ya hemos visitado y aquellos que aún nos quedan por visitar.
Código HTML:
<h1>My Travel Bucket List</h1>
<h2>Places I'd Like to Visit</h2>
<ul>
<li>Tokio</li>
<li>Nairobi</li>
<li>Denver</li>
<li>Moscú</li>
<li>Oslo</li>
<li>Cincinnati</li>
<li>Helsinki</li>
</ul>
<h2>Places I've Already Been To</h2>
<ul>
<li>Budapest</li>
<li>Berlín</li>
<li>Lisboa</li>
<li>Río</li>
</ul>
Esta aplicación nos servirá de punto de partida para desplegar en producción a través de distintas tecnologías.
Para la capa de datos de la aplicación que vamos a desplegar, necesitamos un sistema gestor de bases de datos. Trabajaremos sobre PostgreSQL: "The World's Most Advanced Open Source Relational Database".
Lo primero será actualizar los repositorios:
sdelquin@lemon:~$ sudo apt update
Des:1 http://security.debian.org/debian-security bullseye-security InRelease [48,4 kB]
Obj:2 http://deb.debian.org/debian bullseye InRelease
Des:3 http://deb.debian.org/debian bullseye-updates InRelease [44,1 kB]
Des:4 https://download.docker.com/linux/debian bullseye InRelease [43,3 kB]
Des:5 http://security.debian.org/debian-security bullseye-security/main Sources [167 kB]
Des:6 http://deb.debian.org/debian bullseye-updates/main Sources.diff/Index [15,1 kB]
Des:7 https://download.docker.com/linux/debian bullseye/stable arm64 Packages [13,7 kB]
Des:8 http://security.debian.org/debian-security bullseye-security/main arm64 Packages [191 kB]
Des:9 http://deb.debian.org/debian bullseye-updates/main arm64 Packages.diff/Index [15,1 kB]
Des:10 http://security.debian.org/debian-security bullseye-security/main Translation-en [122 kB]
Des:11 http://deb.debian.org/debian bullseye-updates/main Sources T-2022-10-31-2015.41-F-2022-10-31-2015.41.pdiff [391 B]
Des:11 http://deb.debian.org/debian bullseye-updates/main Sources T-2022-10-31-2015.41-F-2022-10-31-2015.41.pdiff [391 B]
Des:12 http://deb.debian.org/debian bullseye-updates/main arm64 Packages T-2022-10-31-2015.41-F-2022-10-31-2015.41.pdiff [286 B]
Des:12 http://deb.debian.org/debian bullseye-updates/main arm64 Packages T-2022-10-31-2015.41-F-2022-10-31-2015.41.pdiff [286 B]
Des:13 http://nginx.org/packages/debian bullseye InRelease [2.866 B]
Des:14 http://nginx.org/packages/debian bullseye/nginx arm64 Packages [13,2 kB]
Des:15 https://packages.sury.org/php bullseye InRelease [6.841 B]
Des:16 https://packages.sury.org/php bullseye/main arm64 Packages [336 kB]
Des:17 http://packages.microsoft.com/repos/code stable InRelease [10,4 kB]
Des:18 http://packages.microsoft.com/repos/code stable/main arm64 Packages [118 kB]
Des:19 http://packages.microsoft.com/repos/code stable/main amd64 Packages [116 kB]
Des:20 http://packages.microsoft.com/repos/code stable/main armhf Packages [118 kB]
Descargados 1.383 kB en 6s (243 kB/s)
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias... Hecho
Leyendo la información de estado... Hecho
Se pueden actualizar 78 paquetes. Ejecute «apt list --upgradable» para verlos.
A continuación instalaremos algunos paquetes de soporte:
sdelquin@lemon:~$ sudo apt install -y apt-transport-https
[sudo] password for sdelquin:
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias... Hecho
Leyendo la información de estado... Hecho
apt-transport-https ya está en su versión más reciente (2.2.4).
0 actualizados, 0 nuevos se instalarán, 0 para eliminar y 78 no actualizados.
A continuación descargamos la clave de firma para el repositorio oficial de PostgreSQL:
sdelquin@lemon:~$ curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
Añadimos el repositorio oficial de PostgreSQL al sistema:
sdelquin@lemon:~$ echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" \
| sudo tee /etc/apt/sources.list.d/postgresql.list > /dev/null
Ahora volvemos a actualizar la paquetería:
sdelquin@lemon:~$ sudo apt update
Obj:1 http://security.debian.org/debian-security bullseye-security InRelease
Obj:2 http://deb.debian.org/debian bullseye InRelease
Obj:3 http://deb.debian.org/debian bullseye-updates InRelease
Obj:4 https://download.docker.com/linux/debian bullseye InRelease
Obj:5 http://packages.microsoft.com/repos/code stable InRelease
Obj:6 http://nginx.org/packages/debian bullseye InRelease
Des:7 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg InRelease [91,7 kB]
Obj:8 https://packages.sury.org/php bullseye InRelease
Des:9 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 Packages [254 kB]
Descargados 346 kB en 1s (273 kB/s)
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias... Hecho
Leyendo la información de estado... Hecho
Se pueden actualizar 78 paquetes. Ejecute «apt list --upgradable» para verlos.
Lo más probable es que dispongamos de distintas versiones de PostgreSQL. Con el siguiente comando podemos comprobarlo:
sdelquin@lemon:~$ apt-cache search --names-only 'postgresql-[0-9]+$' | sort
postgresql-10 - The World's Most Advanced Open Source Relational Database
postgresql-11 - The World's Most Advanced Open Source Relational Database
postgresql-12 - The World's Most Advanced Open Source Relational Database
postgresql-13 - The World's Most Advanced Open Source Relational Database
postgresql-14 - The World's Most Advanced Open Source Relational Database
postgresql-15 - The World's Most Advanced Open Source Relational Database
Por tanto instalamos la última versión:
sdelquin@lemon:~$ sudo apt install -y postgresql-15
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias... Hecho
Leyendo la información de estado... Hecho
Se instalarán los siguientes paquetes adicionales:
libcommon-sense-perl libjson-perl libjson-xs-perl libpq5 libtypes-serialiser-perl pgdg-keyring
postgresql-client-15 postgresql-client-common postgresql-common sysstat
Paquetes sugeridos:
postgresql-doc-15 isag
Se instalarán los siguientes paquetes NUEVOS:
libcommon-sense-perl libjson-perl libjson-xs-perl libpq5 libtypes-serialiser-perl pgdg-keyring
postgresql-15 postgresql-client-15 postgresql-client-common postgresql-common sysstat
0 actualizados, 11 nuevos se instalarán, 0 para eliminar y 78 no actualizados.
Se necesita descargar 18,6 MB de archivos.
Se utilizarán 63,3 MB de espacio de disco adicional después de esta operación.
Des:1 http://deb.debian.org/debian bullseye/main arm64 libjson-perl all 4.03000-1 [88,6 kB]
Des:2 http://deb.debian.org/debian bullseye/main arm64 libcommon-sense-perl arm64 3.75-1+b4 [24,6 kB]
Des:3 http://deb.debian.org/debian bullseye/main arm64 libtypes-serialiser-perl all 1.01-1 [12,2 kB]
Des:4 http://deb.debian.org/debian bullseye/main arm64 libjson-xs-perl arm64 4.030-1+b1 [93,7 kB]
Des:5 http://deb.debian.org/debian bullseye/main arm64 sysstat arm64 12.5.2-2 [581 kB]
Des:6 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 pgdg-keyring all 2018.2 [10,7 kB]
Des:7 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 postgresql-client-common all 244.pgdg110+1 [92,0 kB]
Des:8 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 postgresql-common all 244.pgdg110+1 [232 kB]
Des:9 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 libpq5 arm64 15.0-1.pgdg110+1 [171 kB]
Des:10 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 postgresql-client-15 arm64 15.0-1.pgdg110+1 [1.609 kB]
Des:11 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 postgresql-15 arm64 15.0-1.pgdg110+1 [15,7 MB]
Descargados 18,6 MB en 3s (6.469 kB/s)
Preconfigurando paquetes ...
Seleccionando el paquete libjson-perl previamente no seleccionado.
(Leyendo la base de datos ... 227992 ficheros o directorios instalados actualmente.)
Preparando para desempaquetar .../00-libjson-perl_4.03000-1_all.deb ...
Desempaquetando libjson-perl (4.03000-1) ...
Seleccionando el paquete pgdg-keyring previamente no seleccionado.
Preparando para desempaquetar .../01-pgdg-keyring_2018.2_all.deb ...
Desempaquetando pgdg-keyring (2018.2) ...
Seleccionando el paquete postgresql-client-common previamente no seleccionado.
Preparando para desempaquetar .../02-postgresql-client-common_244.pgdg110+1_all.deb ...
Desempaquetando postgresql-client-common (244.pgdg110+1) ...
Seleccionando el paquete postgresql-common previamente no seleccionado.
Preparando para desempaquetar .../03-postgresql-common_244.pgdg110+1_all.deb ...
Añadiendo `desviación de /usr/bin/pg_config a /usr/bin/pg_config.libpq-dev por postgresql-common'
Desempaquetando postgresql-common (244.pgdg110+1) ...
Seleccionando el paquete libcommon-sense-perl previamente no seleccionado.
Preparando para desempaquetar .../04-libcommon-sense-perl_3.75-1+b4_arm64.deb ...
Desempaquetando libcommon-sense-perl (3.75-1+b4) ...
Seleccionando el paquete libtypes-serialiser-perl previamente no seleccionado.
Preparando para desempaquetar .../05-libtypes-serialiser-perl_1.01-1_all.deb ...
Desempaquetando libtypes-serialiser-perl (1.01-1) ...
Seleccionando el paquete libjson-xs-perl previamente no seleccionado.
Preparando para desempaquetar .../06-libjson-xs-perl_4.030-1+b1_arm64.deb ...
Desempaquetando libjson-xs-perl (4.030-1+b1) ...
Seleccionando el paquete libpq5:arm64 previamente no seleccionado.
Preparando para desempaquetar .../07-libpq5_15.0-1.pgdg110+1_arm64.deb ...
Desempaquetando libpq5:arm64 (15.0-1.pgdg110+1) ...
Seleccionando el paquete postgresql-client-15 previamente no seleccionado.
Preparando para desempaquetar .../08-postgresql-client-15_15.0-1.pgdg110+1_arm64.deb ...
Desempaquetando postgresql-client-15 (15.0-1.pgdg110+1) ...
Seleccionando el paquete postgresql-15 previamente no seleccionado.
Preparando para desempaquetar .../09-postgresql-15_15.0-1.pgdg110+1_arm64.deb ...
Desempaquetando postgresql-15 (15.0-1.pgdg110+1) ...
Seleccionando el paquete sysstat previamente no seleccionado.
Preparando para desempaquetar .../10-sysstat_12.5.2-2_arm64.deb ...
Desempaquetando sysstat (12.5.2-2) ...
Configurando pgdg-keyring (2018.2) ...
Configurando libpq5:arm64 (15.0-1.pgdg110+1) ...
Configurando libcommon-sense-perl (3.75-1+b4) ...
Configurando libtypes-serialiser-perl (1.01-1) ...
Configurando libjson-perl (4.03000-1) ...
Configurando sysstat (12.5.2-2) ...
Creating config file /etc/default/sysstat with new version
update-alternatives: utilizando /usr/bin/sar.sysstat para proveer /usr/bin/sar (sar) en modo automát
ico
Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-collect.timer → /lib/systemd/syste
m/sysstat-collect.timer.
Created symlink /etc/systemd/system/sysstat.service.wants/sysstat-summary.timer → /lib/systemd/syste
m/sysstat-summary.timer.
Created symlink /etc/systemd/system/multi-user.target.wants/sysstat.service → /lib/systemd/system/sy
sstat.service.
Configurando postgresql-client-common (244.pgdg110+1) ...
Configurando libjson-xs-perl (4.030-1+b1) ...
Configurando postgresql-client-15 (15.0-1.pgdg110+1) ...
update-alternatives: utilizando /usr/share/postgresql/15/man/man1/psql.1.gz para proveer /usr/share/
man/man1/psql.1.gz (psql.1.gz) en modo automático
Configurando postgresql-common (244.pgdg110+1) ...
Añadiendo al usuario postgres al grupo ssl-cert
Creating config file /etc/postgresql-common/createcluster.conf with new version
Building PostgreSQL dictionaries from installed myspell/hunspell packages...
en_us
Removing obsolete dictionary files:
Created symlink /etc/systemd/system/multi-user.target.wants/postgresql.service → /lib/systemd/system
/postgresql.service.
Configurando postgresql-15 (15.0-1.pgdg110+1) ...
Creating new PostgreSQL cluster 15/main ...
/usr/lib/postgresql/15/bin/initdb -D /var/lib/postgresql/15/main --auth-local peer --auth-host scram
-sha-256 --no-instructions
Los archivos de este cluster serán de propiedad del usuario «postgres».
Este usuario también debe ser quien ejecute el proceso servidor.
El cluster será inicializado con configuración regional «es_ES.UTF-8».
La codificación por omisión ha sido por lo tanto definida a «UTF8».
La configuración de búsqueda en texto ha sido definida a «spanish».
Las sumas de verificación en páginas de datos han sido desactivadas.
corrigiendo permisos en el directorio existente /var/lib/postgresql/15/main ... hecho
creando subdirectorios ... hecho
seleccionando implementación de memoria compartida dinámica ... posix
seleccionando el valor para max_connections ... 100
seleccionando el valor para shared_buffers ... 128MB
seleccionando el huso horario por omisión ... Atlantic/Canary
creando archivos de configuración ... hecho
ejecutando script de inicio (bootstrap) ... hecho
realizando inicialización post-bootstrap ... hecho
sincronizando los datos a disco ... hecho
update-alternatives: utilizando /usr/share/postgresql/15/man/man1/postmaster.1.gz para proveer /usr/
share/man/man1/postmaster.1.gz (postmaster.1.gz) en modo automático
Procesando disparadores para man-db (2.9.4-2) ...
Procesando disparadores para libc-bin (2.31-13+deb11u4) ...
Revisamos que la versión instalada sea la correcta:
sdelquin@lemon:~$ psql --version
psql (PostgreSQL) 15.0 (Debian 15.0-1.pgdg110+1)
Tras la instalación, el servicio PostgreSQL se arrancará automáticamente. Podemos comprobarlo de la siguiente manera:
sdelquin@lemon:~$ sudo systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
Active: active (exited) since Tue 2022-11-01 11:03:54 WET; 2min 2s ago
Main PID: 751384 (code=exited, status=0/SUCCESS)
Tasks: 0 (limit: 2251)
Memory: 0B
CPU: 0
CGroup: /system.slice/postgresql.service
nov 01 11:03:54 lemon systemd[1]: Starting PostgreSQL RDBMS...
nov 01 11:03:54 lemon systemd[1]: Finished PostgreSQL RDBMS.
El puerto por defecto en el que trabaja PostgreSQL es el 5432:
sdelquin@lemon:~$ sudo netstat -napt | grep postgres | grep -v tcp6
tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN 23195/postgres
💡 El comando
netstat
se puede instalar junto a otras herramientas de red con:sudo apt install net-tools
.
Ahora vamos a iniciar sesión en el sistema gestor de bases de datos:
sdelquin@lemon:~$ sudo -u postgres psql
psql (15.0 (Debian 15.0-1.pgdg110+1))
Digite «help» para obtener ayuda.
postgres=#
💡 El comando anterior ejecuta la herramienta cliente
psql
con el usuariopostgres
que es el que está habilitado por defecto para acceder a la administración de la base de datos.
Vamos a crear una base de datos y un rol de acceso a la misma:
sdelquin@lemon:~$ sudo -u postgres psql
psql (15.0 (Debian 15.0-1.pgdg110+1))
Digite «help» para obtener ayuda.
postgres=# CREATE USER travelroad_user WITH PASSWORD 'dpl0000';
CREATE ROLE
postgres=# CREATE DATABASE travelroad WITH OWNER travelroad_user;
CREATE DATABASE
postgres=# \q
A continuación accedemos al intérprete PostgreSQL con el nuevo usuario:
sdelquin@lemon:~$ psql -h localhost -U travelroad_user travelroad
Contraseña para usuario travelroad_user:
psql (15.0 (Debian 15.0-1.pgdg110+1))
Conexión SSL (protocolo: TLSv1.3, cifrado: TLS_AES_256_GCM_SHA384, compresión: desactivado)
Digite «help» para obtener ayuda.
travelroad=>
Ahora ya podemos crear la tabla de lugares:
travelroad=> CREATE TABLE places(
id SERIAL PRIMARY KEY,
name VARCHAR(255),
visited BOOLEAN);
CREATE TABLE
Obviamente empezamos con la tabla vacía:
travelroad=> SELECT * FROM places;
id | name | visited
----+------+---------
(0 filas)
Vamos a cargar los datos desde este fichero places.csv a la tabla places
.
Lo primero será descargar el fichero CSV:
sdelquin@lemon:~$ curl -o /tmp/places.csv https://raw.githubusercontent.com/sdelquin/dpl/main/ut4/files/places.csv
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 142 100 142 0 0 402 0 --:--:-- --:--:-- --:--:-- 402
A continuación usaremos la función copy
de PostgreSQL para insertar los datos en la tabla:
sdelquin@lemon:~$ psql -h localhost -U travelroad_user -d travelroad \
-c "\copy places(name, visited) FROM '/tmp/places.csv' DELIMITER ','"
Contraseña para usuario travelroad_user:
COPY 11
Comprobamos que los datos se han insertado de manera adecuada:
sdelquin@lemon:~$ psql -h localhost -U travelroad_user travelroad
Contraseña para usuario travelroad_user:
psql (15.0 (Debian 15.0-1.pgdg110+1))
Conexión SSL (protocolo: TLSv1.3, cifrado: TLS_AES_256_GCM_SHA384, compresión: desactivado)
Digite «help» para obtener ayuda.
travelroad=> SELECT * FROM places;
id | name | visited
----+------------+---------
1 | Tokio | f
2 | Budapest | t
3 | Nairobi | f
4 | Berlín | t
5 | Lisboa | t
6 | Denver | f
7 | Moscú | f
8 | Oslo | f
9 | Río | t
10 | Cincinnati | f
11 | Helsinki | f
(11 filas)
pgAdmin es la plataforma más popular de código abierto para administrar PostgreSQL. Tiene una potente interfaz gráfica que facilita todas las operaciones sobre el servidor de base de datos.
Es un software escrito en Python sobre un framework web denominado Flask.
Lo primero de dodo será instalar Python. Aunque existen paquetes precompilados en la paquetería de los distintos sistemas operativos, vamos a descargar la última versión desde la página oficial y compilar los fuentes para nuestro sistema.
Dado que Python instala ciertas herramientas ejecutables en línea de comandos, es necesario aseguramos que la ruta a estos binarios está en el PATH
:
echo 'export PATH=~/.local/bin:$PATH' >> .bashrc && source .bashrc
Ahora ya se pueden seguir las instrucciones de instalación de Python para realizar la implantación de esta herramienta.
Creamos las carpetas de trabajo con los permisos adecuados:
sdelquin@lemon:~$ sudo mkdir /var/lib/pgadmin
sdelquin@lemon:~$ sudo mkdir /var/log/pgadmin
sdelquin@lemon:~$ sudo chown $USER /var/lib/pgadmin
sdelquin@lemon:~$ sudo chown $USER /var/log/pgadmin
Creamos un entorno virtual de Python (lo activamos) e instalamos el paquete pgadmin4
:
sdelquin@lemon:~$ cd $HOME
sdelquin@lemon:~$ python -m venv pgadmin4
sdelquin@lemon:~$ source pgadmin4/bin/activate
(pgadmin4) sdelquin@lemon:~$ pip install pgadmin4
...
...
...
⚠️ La salida es extensa y puede tardar un poco en terminar ya que requiere de otros paquetes de soporte.
Ahora lanzamos el script de configuración en el que tendremos que dar credenciales para una cuenta "master":
(pgadmin4) sdelquin@lemon:~$ pgadmin4
NOTE: Configuring authentication for SERVER mode.
Enter the email address and password to use for the initial pgAdmin user account:
Email address: sdelqui@gobiernodecanarias.org
Password:
Retype password:
pgAdmin 4 - Application Initialisation
======================================
Starting pgAdmin 4. Please navigate to http://127.0.0.1:5050 in your browser.
2022-12-01 13:37:45,485: WARNING werkzeug: WebSocket transport not available. Install simple-websocket for improved performance.
* Serving Flask app 'pgadmin' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
Aunque este script lanza un servidor de desarrollo en el puerto 5050 no nos interesa de momento ya que queremos desplegar con garantías. Pusamos CTRL-C para detener el proceso.
💡 Para desactivar el entorno virtual podemos ejecutar
deactivate
.
Para poder lanzar el servidor pgAdmin en modo producción y con garantías, necesitaremos hacer uso de un procesador de peticiones WSGI denominado gunicorn
.
Podemos instalarlo como un paquete Python adicional (dentro del entorno virtual):
(pgadmin4) sdelquin@lemon:~$ pip install gunicorn
Collecting gunicorn
Using cached gunicorn-20.1.0-py3-none-any.whl (79 kB)
Requirement already satisfied: setuptools>=3.0 in ./pgadmin4/lib/python3.11/site-packages (from gunicorn) (65.5.0)
Installing collected packages: gunicorn
Successfully installed gunicorn-20.1.0
[notice] A new release of pip available: 22.3 -> 22.3.1
[notice] To update, run: pip install --upgrade pip
Ahora ya estamos en disposición de levantar el servidor pgAdmin utilizando gunicorn
:
(pgadmin4) sdelquin@lemon:~$ gunicorn \
--chdir pgadmin4/lib/python3.11/site-packages/pgadmin4 \
--bind unix:/tmp/pgadmin4.sock pgAdmin4:app
[2022-12-01 13:48:27 +0000] [57576] [INFO] Starting gunicorn 20.1.0
[2022-12-01 13:48:27 +0000] [57576] [INFO] Listening at: unix:/tmp/pgadmin4.sock (57576)
[2022-12-01 13:48:27 +0000] [57576] [INFO] Using worker: sync
[2022-12-01 13:48:27 +0000] [57577] [INFO] Booting worker with pid: 57577
💡 De momento dejamos este proceso corriendo en una terminal, y abirmos otra para seguir trabajando.
Lo que restaría es crear virtual host en Nginx que sirva la aplicación vía web:
sdelquin@lemon:~$ sudo vi /etc/nginx/conf.d/pgadmin.conf
Contenido:
server {
server_name pgadmin.arkania.es;
location / {
proxy_pass http://unix:/tmp/pgadmin4.sock; # socket UNIX
}
}
Recargamos la configuración de Nginx y accedemos vía web a la URL especificada:
💡 Utilizamos las credenciales creadas al lanzar el script de configuración.
Obviamente no es operativo tener que mantener el proceso gunicorn
funcionando en una terminal, por lo que vamos a crear un servicio del sistema.
sdelquin@lemon:~$ sudo vi /etc/systemd/system/pgadmin.service
Contenido:
[Unit]
Description=pgAdmin
[Service]
User=sdelquin
ExecStart=/bin/bash -c '\
source /home/sdelquin/pgadmin4/bin/activate && \
gunicorn --chdir /home/sdelquin/pgadmin4/lib/python3.11/site-packages/pgadmin4 \
--bind unix:/tmp/pgadmin4.sock \
pgAdmin4:app'
Restart=always
[Install]
WantedBy=multi-user.target
A continuación recargamos los servicios para luego levantar pgAdmin y habilitarlo en caso de reinicio del sistema:
sdelquin@lemon:~$ sudo systemctl daemon-reload
sdelquin@lemon:~$ sudo systemctl start pgadmin
sdelquin@lemon:~$ sudo systemctl enable pgadmin
Por último comprobamos que el servicio está funcionando correctamente:
sdelquin@lemon:~$ sudo systemctl is-active pgadmin
active
Cuando conectamos a pgAdmin tenemos la posibilidad de conectar distintos servidores de bases de datos. Procederemos a registrar la base de datos de TravelRoad.
Pulsamos con botón derecho y vamos a Register → Server:
Ahora configuramos el servidor. En primer lugar desde la pestaña General:
Y luego desde la pestaña Connection finalizando con el botón Save
:
Por defecto PostgreSQL sólo permite conexiones desde localhost. Si queremos acceder desde fuera, tendremos que modificar algunas configuraciones.
En primer lugar tendremos que "escuchar" en cualquier IP, no únicamente en localhost (valor por defecto):
sdelquin@lemon:~$ sudo vi /etc/postgresql/15/main/postgresql.conf
Añadir lo siguiente en la línea 64:
listen_addresses = '*'
En segundo lugar tendremos que otorgar permisos. PostgreSQL tiene la capacidad de controlar accesos por:
- Base de datos.
- Usuario.
- IP de origen.
En este ejemplo vamos a permitir el acceso del usuario travelroad_user
a la base de datos travelroad
desde cualquier IP de origen:
sdelquin@lemon:~$ sudo vi /etc/postgresql/15/main/pg_hba.conf
Añadir al final del fichero:
host travelroad travelroad_user 0.0.0.0/0 md5
Una vez hechos estos cambios, debemos reiniciar el servicio PostgreSQL para que los cambios surtan efecto:
sdelquin@lemon:~$ sudo systemctl restart postgresql
Podemos comprobar que el servicio PostgreSQL ya está escuchando en todas las IPs:
sdelquin@lemon:~$ sudo netstat -napt | grep postgres | grep -v tcp6
tcp 0 0 0.0.0.0:5432 0.0.0.0:* LISTEN 23700/postgres
💡
0.0.0.0
significa cualquier IP.
Ahora ya podemos acceder a nuestro servidor PostgreSQL desde cualquier máquina utilizando el nombre de dominio/IP del servidor y las credenciales de acceso.
Un framework para aplicaciones web (o framework web) es un framework diseñado para apoyar el desarrollo de sitios web dinámicos, aplicaciones web y servicios web.
Este tipo de frameworks intenta aliviar el exceso de carga asociado con actividades comunes usadas en desarrollos web. Por ejemplo, muchos framework proporcionan bibliotecas para acceder a bases de datos, estructuras para plantillas y gestión de sesiones, y con frecuencia facilitan la reutilización de código.
Aunque cada framework web tiene sus particularidades, todos comparten una arquitectura común. En el siguiente esquema se muestran los principales componentes que intervienen en la gestión de una petición:
Componente | Cometido |
---|---|
Models | Modelos de datos. Pueden trabajar directamente sobre la base de datos con SQL o bien utilizar un ORM (Object Relational Mapping) para abstraer la información. |
Routes | Rutas. Este componente se encarga de decidir qué controlador debe gestionar cada ruta de la URL de entrada (/hello , /blog , /join , etc.) |
Views | Vistas. Se encargan de presentar la información "renderizando" plantillas con los datos que recibe. Pueden utilizar un motor de plantillas para esta tarea (Template Engine). |
Controllers | Controladores. Bloques de código encargados de llevar a cabo la lógica de negocio teniendo a su disposición los datos y las plantillas. Devuelven la respuesta HTML que se hace llegar al cliente. |
Muchos de los framework web ya traen incorporado un servidor de desarrollo que levanta un servidor web ligero pensado para que podamos desarrollar nuestra aplicación en un entorno local controlado. Algunos puertos habituales del servidor de desarrollo son el 8000, 8080, 3000, 5000, etc. (siempre puertos no privilegiados > 1024).
Usar un servidor de desarrollo en una máquina de producción se considera una mala práctica y está desaconsejado. Para ello disponemos de un servidor de producción tipo Nginx que es capaz de gestionar multitud de peticiones con un comportamiento estable y un alto rendimiento. Trabaja habitualmente en los puertos 80 y 443.
Laravel es un framework de código abierto para desarrollar aplicaciones y servicios web con PHP.
Lo primero que necesitamos es un gestor de dependencias para PHP. Vamos a instalar Composer:
sdelquin@lemon:~$ curl -fsSL https://raw.githubusercontent.com/composer/getcomposer.org/main/web/installer \
| php -- --quiet | sudo mv composer.phar /usr/local/bin/composer
Comprobamos la versión instalada:
sdelquin@lemon:~$ composer --version
Composer version 2.4.4 2022-10-27 14:39:29
Necesitamos ciertos módulos PHP habilitados en el sistema. Para ello instalamos los siguientes paquetes soporte:
sdelquin@lemon:~$ sudo apt install -y php8.2-mbstring php8.2-xml \
php8.2-bcmath php8.2-curl php8.2-pgsql
Paquete | Descripción |
---|---|
mbstring | Gestión de cadenas de caracteres multibyte |
xml | Análisis XML |
bcmath | Operaciones matemáticas de precisión arbitraria |
curl | Cliente de cURL |
pgsql | Herramientas para PostgreSQL |
Ahora ya podemos crear la estructura de nuestra aplicación Laravel. Para ello utilizamos composer
indicando el paquete laravel/laravel junto al nombre de la aplicación:
sdelquin@lemon:~$ composer create-project laravel/laravel travelroad
Vemos que se ha creado una carpeta travelroad
con el andamio (scaffolding) para empezar a trabajar:
sdelquin@lemon:~/travelroad$ ls -l
total 352K
-rw-r--r-- 1 sdelquin sdelquin 263 ene 2 14:45 vite.config.js
drwxr-xr-x 4 sdelquin sdelquin 4,0K ene 2 14:45 tests
drwxr-xr-x 5 sdelquin sdelquin 4,0K ene 2 14:45 storage
drwxr-xr-x 2 sdelquin sdelquin 4,0K ene 2 14:45 routes
drwxr-xr-x 5 sdelquin sdelquin 4,0K ene 2 14:45 resources
-rw-r--r-- 1 sdelquin sdelquin 4,1K ene 2 14:45 README.md
drwxr-xr-x 2 sdelquin sdelquin 4,0K ene 2 14:45 public
-rw-r--r-- 1 sdelquin sdelquin 1,2K ene 2 14:45 phpunit.xml
-rw-r--r-- 1 sdelquin sdelquin 286 ene 2 14:45 package.json
drwxr-xr-x 3 sdelquin sdelquin 4,0K ene 2 14:45 lang
drwxr-xr-x 5 sdelquin sdelquin 4,0K ene 2 14:45 database
drwxr-xr-x 2 sdelquin sdelquin 4,0K ene 2 14:45 config
-rw-r--r-- 1 sdelquin sdelquin 1,8K ene 2 14:45 composer.json
drwxr-xr-x 3 sdelquin sdelquin 4,0K ene 2 14:45 bootstrap
-rwxr-xr-x 1 sdelquin sdelquin 1,7K ene 2 14:45 artisan
drwxr-xr-x 7 sdelquin sdelquin 4,0K ene 2 14:45 app
-rw-r--r-- 1 sdelquin sdelquin 280K ene 10 09:29 composer.lock
drwxr-xr-x 39 sdelquin sdelquin 4,0K ene 10 09:29 vendor
Entramos en la carpeta de trabajo y probamos que se ha instalado correctamente artisan, la interfaz en línea de comandos para Laravel:
sdelquin@lemon:~/travelroad$ ./artisan --version
Laravel Framework 9.38.0
Por defecto se ha creado un fichero de configuración .env
durante el andamiaje. Abrimos este fichero y modificamos ciertos valores para especificar credenciales de acceso:
sdelquin@lemon:~/travelroad$ vi .env
...
APP_NAME=TravelRoad
APP_ENV=development
...
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=travelroad
DB_USERNAME=travelroad_user
DB_PASSWORD=dpl0000
...
Lo primero será fijar los permisos adecuados a los ficheros del proyecto para que los servicios Nginx+PHP-FPM puedan trabajar sin errores de acceso.
Existen un par de carpetas en las que se puede almacenar información. Ajustamos los permisos:
sdelquin@lemon:~/travelroad$ sudo chgrp -R nginx storage bootstrap/cache
sdelquin@lemon:~/travelroad$ sudo chmod -R ug+rwx storage bootstrap/cache
La configuración del virtual host Nginx para nuestra aplicación Laravel la vamos a hacer en un fichero específico:
sdelquin@lemon:~$ sudo vi /etc/nginx/conf.d/travelroad.conf
Contenido:
server {
server_name travelroad;
root /home/sdelquin/travelroad/public;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
💡 Recordar añadir
travelroad
al fichero/etc/hosts
en caso de estar trabajando en local.
Comprobamos la sintaxis del fichero y, si todo ha ido bien, recargamos la configuración Nginx:
sdelquin@lemon:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sdelquin@lemon:~$ sudo systemctl reload nginx
Si ahora abrimos el navegador en http://travelroad veremos una página de inicio (launching) con información general sobre el framework:
sdelquin@lemon:~$ firefox http://travelroad
Nos queda modificar el comportamiento de la aplicación para cargar los datos y mostrarlos en una plantilla.
Lo primero es cambiar el código de la ruta:
sdelquin@lemon:~/travelroad$ vi routes/web.php
Contenido:
<?php
// https://laravel.com/api/6.x/Illuminate/Support/Facades/DB.html
use Illuminate\Support\Facades\DB;
Route::get('/', function () {
$wished = DB::select('select * from places where visited = false');
$visited = DB::select('select * from places where visited = true');
return view('travelroad', ['wished' => $wished, 'visited' => $visited]);
});
Lo segundo es escribir la plantilla que renderiza los datos. Renderizar una plantilla significa sustituir las variables por sus valores y así obtener un HTML final. Utilizaremos Blade como motor de plantillas incluido en Laravel.
sdelquin@lemon:~/travelroad$ vi resources/views/travelroad.blade.php
Contenido:
<html>
<head>
<title>Travel List</title>
</head>
<body>
<h1>My Travel Bucket List</h1>
<h2>Places I'd Like to Visit</h2>
<ul>
@foreach ($wished as $place)
<li>{{ $place->name }}</li>
@endforeach
</ul>
<h2>Places I've Already Been To</h2>
<ul>
@foreach ($visited as $place)
<li>{{ $place->name }}</li>
@endforeach
</ul>
</body>
</html>
Ya podemos abrir el navegador en http://travelroad y comprobar que todo está funcionando correctamente:
sdelquin@lemon:~$ firefox http://travelroad
Hay que tener en cuenta un detalle. La carpeta vendor
está fuera de control de versiones por una entrada que se crea automáticamente un el fichero .gitignore
del "scaffolding" que realiza Laravel:
sdelquin@lemon:~/travelroad$ grep vendor .gitignore
/vendor
Esta carpeta contiene todas las dependencias del proyecto. Por lo tanto, cuando hagamos el despliegue en producción, debemos ejecutar el siguiente comando para crear esta carpeta e instalar todas las dependencias necesarias:
sdelquin@lemon:~/travelroad$ composer install
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
INFO Discovering packages.
laravel/sail .............................................................................. DONE
laravel/sanctum ........................................................................... DONE
laravel/tinker ............................................................................ DONE
nesbot/carbon ............................................................................. DONE
nunomaduro/collision ...................................................................... DONE
nunomaduro/termwind ....................................................................... DONE
spatie/laravel-ignition ................................................................... DONE
81 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
Veamos un ejemplo de script de despliegue para esta aplicación:
sdelquin@lemon:~/travelroad$ vi deploy.sh
Contenido:
#!/bin/bash
ssh arkania "
cd $(dirname $0)
git pull
composer install
"
Damos permisos de ejecución:
sdelquin@lemon:~/travelroad$ chmod +x deploy.sh
💡
deploy.sh
es un fichero que se incluye en el control de versiones.
Express es un framework web mínimo y flexible para Node.js.
Lo primero que debemos instalar es Node.js: un entorno de ejecución para JavaScript construido con V8, motor de JavaScript de Chrome.
Existe un instalador que nos facilita añadir los repositorios oficiales de Node.js. El comando a ejecutar es el siguiente:
sdelquin@lemon:~$ curl -fsSL https://deb.nodesource.com/setup_current.x | sudo -E bash
[sudo] password for sdelquin:
## Installing the NodeSource Node.js 19.x repo...
## Populating apt-get cache...
+ apt-get update
Obj:1 http://deb.debian.org/debian bullseye InRelease
Des:2 https://download.docker.com/linux/debian bullseye InRelease [43,3 kB]
Des:3 http://security.debian.org/debian-security bullseye-security InRelease [48,4 kB]
Obj:4 https://packages.sury.org/php bullseye InRelease
Des:5 http://deb.debian.org/debian bullseye-updates InRelease [44,1 kB]
Des:6 http://nginx.org/packages/debian bullseye InRelease [2.866 B]
Des:7 http://security.debian.org/debian-security bullseye-security/main Sources [167 kB]
Des:8 http://packages.microsoft.com/repos/code stable InRelease [10,4 kB]
Des:9 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg InRelease [91,7 kB]
Des:10 http://security.debian.org/debian-security bullseye-security/main arm64 Packages [192 kB]
Des:11 http://packages.microsoft.com/repos/code stable/main arm64 Packages [119 kB]
Des:12 http://security.debian.org/debian-security bullseye-security/main Translation-en [123 kB]
Des:13 http://packages.microsoft.com/repos/code stable/main armhf Packages [119 kB]
Des:14 http://packages.microsoft.com/repos/code stable/main amd64 Packages [117 kB]
Des:15 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg/main arm64 Packages [256 kB]
Descargados 1.334 kB en 2s (597 kB/s)
Leyendo lista de paquetes... Hecho
## Confirming "bullseye" is supported...
+ curl -sLf -o /dev/null 'https://deb.nodesource.com/node_19.x/dists/bullseye/Release'
## Adding the NodeSource signing key to your keyring...
+ curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | gpg --dearmor | tee /usr/share/keyrings/nodesource.gpg >/dev/null
gpg: AVISO: propiedad insegura del directorio personal '/home/sdelquin/.gnupg'
## Creating apt sources list file for the NodeSource Node.js 19.x repo...
+ echo 'deb [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_19.x bullseye main' > /etc/apt/sources.list.d/nodesource.list
+ echo 'deb-src [signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_19.x bullseye main' >> /etc/apt/sources.list.d/nodesource.list
## Running `apt-get update` for you...
+ apt-get update
Obj:1 http://security.debian.org/debian-security bullseye-security InRelease
Obj:2 http://apt.postgresql.org/pub/repos/apt bullseye-pgdg InRelease
Obj:3 http://deb.debian.org/debian bullseye InRelease
Obj:4 http://deb.debian.org/debian bullseye-updates InRelease
Obj:5 http://packages.microsoft.com/repos/code stable InRelease
Obj:6 https://download.docker.com/linux/debian bullseye InRelease
Obj:7 http://nginx.org/packages/debian bullseye InRelease
Obj:8 https://packages.sury.org/php bullseye InRelease
Des:9 https://deb.nodesource.com/node_19.x bullseye InRelease [4.586 B]
Des:10 https://deb.nodesource.com/node_19.x bullseye/main arm64 Packages [772 B]
Descargados 5.358 B en 1s (4.180 B/s)
Leyendo lista de paquetes... Hecho
## Run `sudo apt-get install -y nodejs` to install Node.js 19.x and npm
## You may also need development tools to build native addons:
sudo apt-get install gcc g++ make
## To install the Yarn package manager, run:
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update && sudo apt-get install yarn
Ahora ya podemos instalar Node.js de forma ordinaria:
sdelquin@lemon:~$ sudo apt install -y nodejs
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias... Hecho
Leyendo la información de estado... Hecho
Se instalarán los siguientes paquetes NUEVOS:
nodejs
0 actualizados, 1 nuevos se instalarán, 0 para eliminar y 78 no actualizados.
Se necesita descargar 28,3 MB de archivos.
Se utilizarán 183 MB de espacio de disco adicional después de esta operación.
Des:1 https://deb.nodesource.com/node_19.x bullseye/main arm64 nodejs arm64 19.0.1-deb-1nodesource1 [28,3 MB]
Descargados 28,3 MB en 4s (7.406 kB/s)
Seleccionando el paquete nodejs previamente no seleccionado.
(Leyendo la base de datos ... 230383 ficheros o directorios instalados actualmente.)
Preparando para desempaquetar .../nodejs_19.0.1-deb-1nodesource1_arm64.deb ...
Desempaquetando nodejs (19.0.1-deb-1nodesource1) ...
Configurando nodejs (19.0.1-deb-1nodesource1) ...
Procesando disparadores para man-db (2.9.4-2) ...
Comprobamos las versiones de Node.js y de npm (sistema de gestión de paquetes para Node.js):
sdelquin@lemon:~$ node --version
v19.0.1
sdelquin@lemon:~$ npm --version
8.19.2
Ahora ya podemos crear la estructura (andamiaje) de nuestra aplicación Express. Para ello utilizamos express-generator
una herramienta que debemos instalar de forma global en el sistema:
sdelquin@lemon:~$ sudo npm install -g express-generator
[sudo] password for sdelquin:
npm WARN deprecated mkdirp@0.5.1: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
added 10 packages, and audited 11 packages in 2s
4 vulnerabilities (1 moderate, 1 high, 2 critical)
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
npm notice
npm notice New patch version of npm available! 8.19.2 -> 8.19.3
npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.19.3
npm notice Run npm install -g npm@8.19.3 to update!
npm notice
Creamos la estructura base de la aplicación indicando que las vistas (plantillas) van a utilizar pug como motor de plantillas:
sdelquin@lemon:~$ express --view=pug travelroad
create : travelroad/
create : travelroad/public/
create : travelroad/public/javascripts/
create : travelroad/public/images/
create : travelroad/public/stylesheets/
create : travelroad/public/stylesheets/style.css
create : travelroad/routes/
create : travelroad/routes/index.js
create : travelroad/routes/users.js
create : travelroad/views/
create : travelroad/views/error.pug
create : travelroad/views/index.pug
create : travelroad/views/layout.pug
create : travelroad/app.js
create : travelroad/package.json
create : travelroad/bin/
create : travelroad/bin/www
change directory:
$ cd travelroad
install dependencies:
$ npm install
run the app:
$ DEBUG=travelroad:* npm start
El comando anterior creará una carpeta travelroad
con la estructura base para poder desarrollar nuestra aplicación web:
sdelquin@lemon:~/travelroad$ ls -l
total 128
-rw-r--r-- 1 sdelquin sdelquin 1074 nov 7 16:46 app.js
drwxr-xr-x 2 sdelquin sdelquin 4096 ene 9 19:42 bin
-rw-r--r-- 1 sdelquin sdelquin 347 nov 7 17:11 package.json
drwxr-xr-x 5 sdelquin sdelquin 4096 nov 7 16:46 public
drwxr-xr-x 2 sdelquin sdelquin 4096 nov 8 16:59 routes
drwxr-xr-x 2 sdelquin sdelquin 4096 nov 8 17:02 views
Tal y como indica la salida del comando, ahora debemos instalar las dependencias:
sdelquin@lemon:~/travelroad$ npm install
npm WARN deprecated core-js@2.6.12: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
added 124 packages, and audited 125 packages in 11s
8 packages are looking for funding
run `npm fund` for details
4 vulnerabilities (2 low, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues, run:
npm audit fix --force
Run `npm audit` for details.
npm install
obtiene las dependencias del fichero package.json
y almacena los paquetes en la carpeta node_modules
.
💡
node_modules
es una carpeta que debe esta fuera de control de versiones.
Ahora podemos probar la aplicación lanzando el servidor de desarrollo:
sdelquin@lemon:~/travelroad$ DEBUG=travelroad:* npm start
> travelroad@0.0.0 start
> node ./bin/www
travelroad:server Listening on port 3000 +0ms
En otra pestaña de terminal, abrimos con el navegador la dirección http://localhost:3000 obteniendo un resultado como éste:
Para poder acceder a la base de datos PostgreSQL necesitamos una dependencia adicional node-postgres. Realizamos la instalación:
sdelquin@lemon:~/travelroad$ npm install pg
added 15 packages, and audited 140 packages in 6s
8 packages are looking for funding
run `npm fund` for details
4 vulnerabilities (2 low, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues, run:
npm audit fix --force
Run `npm audit` for details.
Siempre es interesante guardar las credenciales en un fichero "externo". En este caso vamos a trabajar con un fichero .env
con lo que necesitaremos el paquete dotenv. Lo instalamos:
sdelquin@lemon:~/travelroad$ npm install dotenv
added 1 package, and audited 141 packages in 3s
8 packages are looking for funding
run `npm fund` for details
4 vulnerabilities (2 low, 2 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues, run:
npm audit fix --force
Run `npm audit` for details.
En este fichero hay que guardar la cadena de conexión a la base de datos PostgreSQL:
sdelquin@lemon:~/travelroad$ echo 'PSQL_CONNECTION=postgresql://travelroad_user:dpl0000@localhost:5432/travelroad' > .env
Nos queda modificar el comportamiento de la aplicación para cargar los datos y mostrarlos en una plantilla.
sdelquin@lemon:~/travelroad$ mkdir config && vi config/database.js
Contenido:
const { Pool } = require("pg");
require("dotenv").config();
const connectionString = process.env.PSQL_CONNECTION;
const pool = new Pool({
connectionString,
});
module.exports = {
query: (text, params) => pool.query(text, params),
};
sdelquin@lemon:~/travelroad$ vi routes/index.js
Contenido:
const db = require("../config/database");
let express = require("express");
let router = express.Router();
/* GET home page. */
router.get("/", async function (req, res, next) {
const { rows: wished } = await db.query(
"SELECT * FROM places WHERE visited=false"
);
const { rows: visited } = await db.query(
"SELECT * FROM places WHERE visited=true"
);
res.render("index", { wished, visited });
});
module.exports = router;
sdelquin@lemon:~/travelroad$ vi views/index.pug
Contenido:
block content
h1= "My Travel Bucket List"
h2= "Places I'd Like to Visit"
ul
each place in wished
li= place.name
h2= "Places I've Already Been To"
ul
each place in visited
li= place.name
Volvemos a lanzar la aplicación:
sdelquin@lemon:~/travelroad$ DEBUG=travelroad:* npm start
Y comprobamos que la dirección http://localhost:3000 nos da el resultado que esperábamos:
Vamos a hacer uso de pm2 un gestor de procesos para aplicaciones Node.js en producción.
Lo primero es instalar el paquete de forma global en el sistema:
sdelquin@lemon:~$ sudo npm install -g pm2
[sudo] password for sdelquin:
npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.
added 184 packages, and audited 185 packages in 12s
12 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Ahora ya podemos lanzar un proceso en background con nuestra aplicación. Estando en la carpeta de trabajo ~/travelroad
ejecutamos el siguiente comando:
sdelquin@lemon:~/travelroad$ pm2 start ./bin/www --name travelroad
[PM2] Starting /home/sdelquin/travelroad/bin/www in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name │ mode │ ↺ │ status │ cpu │ memory │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0 │ travelroad │ fork │ 0 │ online │ 0% │ 22.6mb │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
Lo único que nos queda es preparar el virtual host en Nginx para comunicar con el proceso de Node.js:
sdelquin@lemon:~$ sudo vi /etc/nginx/conf.d/travelroad.conf
Contenido:
server {
server_name travelroad;
location / {
proxy_pass http://localhost:3000; # socket TCP
}
}
Recargamos la configuración de Nginx y accedemos a http://travelroad obteniendo el resultado esperado:
Veamos un ejemplo de script de despliegue para esta aplicación:
sdelquin@lemon:~/travelroad$ vi deploy.sh
Contenido:
#!/bin/bash
ssh arkania "
cd $(dirname $0)
git pull
npm install
pm2 restart travelroad --update-env
"
Damos permisos de ejecución:
sdelquin@lemon:~/travelroad$ chmod +x deploy.sh
💡
deploy.sh
es un fichero que se incluye en el control de versiones.
Spring es un framework de código abierto que da soporte para el desarrollo de aplicaciones y páginas webs basadas en Java. Se trata de uno de los entornos más populares y ayuda a crear aplicaciones con un alto rendimiento empleando objetos de Java sencillos.
Lo primero será instalar el Java Development Kit (JDK). Existe una versión "opensource" denominada OpenJDK.
Descargamos el paquete OpenJDK desde su página de descargas:
sdelquin@lemon:~$ curl -O --output-dir /tmp \
https://download.java.net/java/GA/jdk19.0.1/afdd2e245b014143b62ccb916125e3ce/10/GPL/openjdk-19.0.1_linux-x64_bin.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 186M 100 186M 0 0 11.3M 0 0:00:16 0:00:16 --:--:-- 11.6M
Ahora descomprimimos el contenido del paquete en /usr/lib/jvm
:
sdelquin@lemon:~$ sudo tar -xzvf /tmp/openjdk-19.0.1_linux-x64_bin.tar.gz \
--one-top-level=/usr/lib/jvm
💡 Tener en cuenta que la última versión disponible puede variar en el tiempo.
Comprobamos que todo ha ido bien y que el contenido del paquete está disponible:
sdelquin@lemon:~$ ls -l /usr/lib/jvm/jdk-19.0.1/
total 28
drwxr-xr-x 2 root root 4096 nov 11 14:27 bin
drwxr-xr-x 5 root root 4096 nov 11 14:27 conf
drwxr-xr-x 3 root root 4096 nov 11 14:27 include
drwxr-xr-x 2 root root 4096 nov 11 14:27 jmods
drwxr-xr-x 72 root root 4096 nov 11 14:27 legal
drwxr-xr-x 5 root root 4096 nov 11 14:27 lib
-rw-r--r-- 1 10668 10668 1213 sep 14 13:55 release
Necesitamos realizar alguna configuración adicional para que el JDK funcione correctamente.
Por un lado establecer variables de entorno adecuadas a la instalación. Básicamente indicar dónde se encuentran los ejecutables de Java modificando la variable de entorno PATH:
sdelquin@lemon:~$ sudo vi /etc/profile.d/jdk_home.sh
Contenido:
#!/bin/sh
export JAVA_HOME=/usr/lib/jvm/jdk-19.0.1/
export PATH=$JAVA_HOME/bin:$PATH
Por otro lado actualizar las alternativas para los ejecutables:
sdelquin@lemon:~$ sudo update-alternatives --install \
"/usr/bin/java" "java" "/usr/lib/jvm/jdk-19.0.1/bin/java" 0
sdelquin@lemon:~$ sudo update-alternatives --install \
"/usr/bin/javac" "javac" "/usr/lib/jvm/jdk-19.0.1/bin/javac" 0
sdelquin@lemon:~$ sudo update-alternatives --set java \
/usr/lib/jvm/jdk-19.0.1/bin/java
sdelquin@lemon:~$ sudo update-alternatives --set javac \
/usr/lib/jvm/jdk-19.0.1/bin/javac
Por lo tanto disponemos de:
- javac → es el compilador del lenguaje de programación Java.
- java → es la herramienta para ejecutar programas escritos en Java.
Ahora ya podemos comprobar las versiones de las herramientas instaladas:
sdelquin@lemon:~$ java --version
openjdk 19.0.1 2022-10-18
OpenJDK Runtime Environment (build 19.0.1+10-21)
OpenJDK 64-Bit Server VM (build 19.0.1+10-21, mixed mode, sharing)
sdelquin@lemon:~$ javac --version
javac 19.0.1
⚠️ En este punto se debe cerrar la sesión y volver a abrirla para que los cambios se apliquen correctamente.
SDKMAN es una herramienta que permite gestionar versiones de kits de desarrollo de software (entre ellos Java).
Para su instalación debemos comprobar que tenemos el paquete zip
instalado en el sistema:
sdelquin@lemon:~$ sudo apt install -y zip
Ahora ejecutamos el siguiente script de instalación:
sdelquin@lemon:~$ curl -s https://get.sdkman.io | bash
A continuación activamos el punto de entrada de la siguiente manera:
sdelquin@lemon:~$ source "$HOME/.sdkman/bin/sdkman-init.sh"
Ya deberíamos de tener la instalación completada. Comprobamos la versión de la herramienta:
sdelquin@lemon:~$ sdk version
SDKMAN 5.16.0
Dentro de Spring, existe un subproyecto denominado Spring Boot que facilita la preparación de aplicaciones Spring para ponerlas en producción. Utilizaremos esta herramienta durante el despliegue.
Para instalarla hacemos uso de SDKMAN:
sdelquin@lemon:~$ sdk install springboot
Downloading: springboot 2.7.5
In progress...
########################################## 100,0%
Installing: springboot 2.7.5
Done installing!
Setting springboot 2.7.5 as default.
Comprobamos la versión instalada:
sdelquin@lemon:~$ spring --version
Spring CLI v2.7.5
Maven es una herramienta enfocada a la construcción de proyectos Java que permite la gestión de dependencias. Su instalación también es a través de SDKMAN:
sdelquin@lemon:~$ sdk install maven
Downloading: maven 3.8.6
In progress...
########################################## 100,0%
Installing: maven 3.8.6
Done installing!
Setting maven 3.8.6 as default.
Comprobamos la versión instalada:
sdelquin@lemon:~$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: /home/sdelquin/.sdkman/candidates/maven/current
Java version: 19.0.1, vendor: Oracle Corporation, runtime: /usr/lib/jvm/jdk-19.0.1
Default locale: es_ES, platform encoding: UTF-8
OS name: "linux", version: "5.10.0-18-arm64", arch: "aarch64", family: "unix"
💡 Existen otras herramientas para construcción de proyectos Java. Aquí podemos citar Gradle.
Creamos la estructura base del proyecto utilizando la herramienta en línea de comandos para Spring Boot:
sdelquin@lemon:~$ spring init \
--build=maven \
--dependencies=web \
--group=edu.dpl \
--name=travelroad \
--description=TravelRoad \
travelroad
Using service at https://start.spring.io
Project extracted to '/home/sdelquin/travelroad'
Listamos el contenido de la carpeta de trabajo:
sdelquin@lemon:~$ tree travelroad
travelroad
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── edu
│ │ └── dpl
│ │ └── travelroad
│ │ └── TravelroadApplication.java
│ └── resources
│ ├── application.properties
│ ├── static
│ └── templates
└── test
└── java
└── edu
└── dpl
└── travelroad
└── TravelroadApplicationTests.java
14 directories, 7 files
Tendremos que adaptar un poco la estructura inicial del proyecto para cumplir con el objetivo de la aplicación que tenemos que desarrollar.
Dentro de la carpeta src/main
tendremos que organizar los distintos módulos (controlador, modelo y plantilla) que componen la aplicación de la siguiente manera:
sdelquin@lemon:~/travelroad$ mkdir -p src/main/java/edu/dpl/travelroad/controllers
sdelquin@lemon:~/travelroad$ touch src/main/java/edu/dpl/travelroad/controllers/HomeController.java
sdelquin@lemon:~/travelroad$ mkdir -p src/main/java/edu/dpl/travelroad/models
sdelquin@lemon:~/travelroad$ touch src/main/java/edu/dpl/travelroad/models/Place.java
sdelquin@lemon:~/travelroad$ mkdir -p src/main/java/edu/dpl/travelroad/repositories
sdelquin@lemon:~/travelroad$ touch src/main/java/edu/dpl/travelroad/repositories/PlaceRepository.java
sdelquin@lemon:~/travelroad$ touch src/main/resources/templates/home.html
Tras esto, la estructura del proyecto quedaría tal que así:
sdelquin@lemon:~/travelroad$ tree src/main
src/main
├── java
│ └── edu
│ └── dpl
│ └── travelroad
│ ├── controllers
│ │ └── HomeController.java
│ ├── models
│ │ └── Place.java
│ ├── repositories
│ │ └── PlaceRepository.java
│ └── TravelroadApplication.java
└── resources
├── application.properties
├── static
└── templates
└── home.html
10 directories, 6 files
sdelquin@lemon:~/travelroad$ vi src/main/java/edu/dpl/travelroad/controllers/HomeController.java
Contenido:
package edu.dpl.travelroad.controllers;
import edu.dpl.travelroad.models.Place;
import edu.dpl.travelroad.repositories.PlaceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
private final PlaceRepository placeRepository;
@Autowired
public HomeController(PlaceRepository placeRepository) {
this.placeRepository = placeRepository;
}
@GetMapping("/")
public String home(Model model) {
model.addAttribute("wished", placeRepository.findByVisited(false));
model.addAttribute("visited", placeRepository.findByVisited(true));
return "home"; // home.html
}
}
sdelquin@lemon:~/travelroad$ vi src/main/java/edu/dpl/travelroad/models/Place.java
Contenido:
package edu.dpl.travelroad.models;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "places")
public class Place {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Boolean visited;
public Place() {
}
public Place(Long id, String name, Boolean visited) {
this.id = id;
this.name = name;
this.visited = visited;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getVisited() {
return visited;
}
public void setVisited(Boolean visited) {
this.visited = visited;
}
}
sdelquin@lemon:~/travelroad$ vi src/main/java/edu/dpl/travelroad/repositories/PlaceRepository.java
Contenido:
package edu.dpl.travelroad.repositories;
import edu.dpl.travelroad.models.Place;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.Query;
@Repository
public interface PlaceRepository extends CrudRepository<Place, Long> {
@Query("SELECT p FROM Place p WHERE p.visited = ?1")
List<Place> findByVisited(Boolean visited);
}
Para la plantilla vamos a utilizar Thymeleaf un motor de plantillas moderno para Java:
sdelquin@lemon:~/travelroad$ vi src/main/resources/templates/home.html
Contenido:
<!DOCTYPE HTML>
<html>
<head>
<title>My Travel Bucket List</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<h1>My Travel Bucket List</h1>
<h2>Places I'd Like to Visit</h2>
<ul th:each="place : ${wished}">
<li th:text="${place.name}"></li>
</ul>
<h2>Places I've Already Been To</h2>
<ul th:each="place : ${visited}">
<li th:text="${place.name}"></li>
</ul>
</body>
</html>
Maven es un gestor de dependencias. Por tanto debemos definir estas dependencias en un fichero XML:
sdelquin@lemon:~/travelroad$ vi pom.xml
Contenido:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>edu.dpl</groupId>
<artifactId>travelroad</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>travelroad</name>
<description>TravelRoad</description>
<properties>
<java.version>19</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Listado de dependencias:
Dependencia | Responsabilidad |
---|---|
spring-boot-starter-web | Andamiaje de la aplicación web. |
spring-boot-starter-test | Andamiaje de tests para la aplicación web. |
spring-boot-starter-thymeleaf | Motor de plantillas. |
spring-boot-starter-data-jpa | Capa de persistencia Java. |
postgresql | Driver para conectar a bases de datos PostgreSQL (JDBC). |
spring-boot-maven-plugin | Plugin de Spring Boot para usar Maven. |
sdelquin@lemon:~/travelroad$ vi src/main/resources/application.properties
Contenido:
spring.datasource.url=jdbc:postgresql://localhost:5432/travelroad
spring.datasource.username=travelroad_user
spring.datasource.password=dpl0000
Para poner en funcionamiento el proyecto necesitamos dos fases que se llevarán a cabo mediante Maven:
- Compilación.
- Empaquetado.
La herramienta que usamos para ello es Maven, pero en el propio andamiaje de la aplicación ya se incluye un Maven Wrapper denominado mvnw
que incluye todo lo necesario para poder construir el proyecto.
Para llevar a cabo la compilación del proyecto ejecutamos lo siguiente:
sdelquin@lemon:~/travelroad$ ./mvnw compile
Para llevar a cabo el empaquetado del proyecto ejecutamos lo siguiente:
sdelquin@lemon:~/travelroad$ ./mvnw package
Tras esto, obtendremos un archivo JAR (Java ARchive) en la ruta:
sdelquin@lemon:~/travelroad$ ls target/travelroad-0.0.1-SNAPSHOT.jar
target/travelroad-0.0.1-SNAPSHOT.jar
sdelquin@lemon:~/travelroad$ file target/travelroad-0.0.1-SNAPSHOT.jar
target/travelroad-0.0.1-SNAPSHOT.jar: Java archive data (JAR)
💡 El fichero generado tiene la versión
0.0.1-SNAPSHOT
. Esto se puede cambiar modificando la etiqueta<version>
del ficheropom.xml
.
Una forma de lanzar la aplicación es correr el fichero JAR generado:
sdelquin@lemon:~/travelroad$ java -jar target/travelroad-0.0.1-SNAPSHOT.jar
Dentro del empaquetado también se incluye Tomcat un servidor de aplicaciones para Java que se puede usar perfectamente en producción.
Esto nos permitirá acceder a http://localhost:8080 y comprobar que la aplicación funciona correctamente.
💡 Tener en cuenta que la carpeta
target
no debe estar dentro de control de versiones.
De cara a simplificar el proceso de despliegue en el entorno de producción, podemos disponer de un script que realice los pasos del proceso de construcción:
sdelquin@lemon:~/travelroad$ vi run.sh
Contenido:
#!/bin/bash
cd $(dirname $0)
./mvnw package # el empaquetado ya incluye la compilación
# ↓ Último fichero JAR generado
JAR=`ls target/*.jar -t | head -1`
/usr/bin/java -jar $JAR
Asignamos permisos de ejecución:
sdelquin@lemon:~/travelroad$ chmod +x run.sh
A continuación creamos un fichero de servicio (de usuario) para gestionarlo mediante systemd:
sdelquin@lemon:~$ mkdir -p ~/.config/systemd/user
sdelquin@lemon:~$ vi ~/.config/systemd/user/travelroad.service
Contenido:
[Unit]
Description=Spring Boot TravelRoad
[Service]
Type=simple
StandardOutput=journal
ExecStart=/home/sdelquin/travelroad/run.sh
[Install]
WantedBy=default.target
💡 Ojo modificar el campo
ExecStart
con la ruta correcta al script de arranque.
Habilitamos el servicio para que se arranque automáticamente:
sdelquin@lemon:~$ systemctl --user enable travelroad.service
Created symlink /home/sdelquin/.config/systemd/user/default.target.wants/travelroad.service → /home/sdelquin/.config/systemd/user/travelroad.service.
Iniciamos el servicio para comprobar su funcionamiento:
sdelquin@lemon:~$ systemctl --user start travelroad.service
Podemos comprobar el estado del servicio (tener en cuenta que puede tardar algún tiempo):
sdelquin@lemon:~$ systemctl --user status travelroad.service
● travelroad.service - Spring Boot TravelRoad
Loaded: loaded (/home/sdelquin/.config/systemd/user/travelroad.service; enabled; vendor preset>
Active: active (running) since Mon 2023-01-30 19:22:53 WET; 1min 2s ago
Main PID: 101634 (run.sh)
Tasks: 37 (limit: 2251)
Memory: 387.5M
CPU: 13.715s
CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/travelroad.service
├─101634 /bin/bash /home/sdelquin/travelroad/run.sh
└─101763 /usr/bin/java -jar target/travelroad-0.0.1-SNAPSHOT.jar
...
Lo último que nos queda es configurar el host virtual en Nginx para dar servicio a las peticiones:
sdelquin@lemon:~$ sudo vi /etc/nginx/conf.d/travelroad.conf
Contenido:
server {
server_name travelroad;
location / {
proxy_pass http://localhost:8080; # socket TCP
}
}
Recargamos la configuración del servidor web para que los cambios surtan efecto:
sdelquin@lemon:~$ sudo systemctl reload nginx
Con todo esto hecho, ya podemos probar el acceso a la aplicación web visitando http://travelroad:
Veamos un ejemplo de script de despliegue para esta aplicación:
sdelquin@lemon:~/travelroad$ vi deploy.sh
Contenido:
#!/bin/bash
ssh arkania "
cd $(dirname $0)
git pull
systemctl --user restart travelroad.service
"
Finalizamos dando permisos de ejecución:
sdelquin@lemon:~/travelroad$ chmod +x deploy.sh
💡
deploy.sh
es un fichero que se incluye en el control de versiones.
Ruby on Rails también conocido como RoR o Rails, es un framework de aplicaciones web de código abierto escrito en el lenguaje de programación Ruby, siguiendo el paradigma del patrón Modelo Vista Controlador.
Lo primero que necesitaremos será instalar Ruby Version Manager (RVM) una herramienta en línea de comandos que permite instalar, gestionar y trabajar con múltiples entornos Ruby.
Añadimos las claves GPG:
sdelquin@lemon:~$ gpg2 --recv-keys \
409B6B1796C275462A1703113804BB82D39DC0E3 \
7D2BAF1CF37B13E2069D6956105BD0E739499BDB
Ahora lanzamos el script de instalación:
sdelquin@lemon:~$ curl -sSL https://get.rvm.io | bash -s stable
Downloading https://github.com/rvm/rvm/archive/1.29.12.tar.gz
Downloading https://github.com/rvm/rvm/releases/download/1.29.12/1.29.12.tar.gz.asc
gpg: Firmado el vie 15 ene 2021 18:46:22 WET
gpg: usando RSA clave 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
gpg: Firma correcta de "Piotr Kuczynski <piotr.kuczynski@gmail.com>" [desconocido]
gpg: ATENCIÓN: ¡Esta clave no está certificada por una firma de confianza!
gpg: No hay indicios de que la firma pertenezca al propietario.
Huellas dactilares de la clave primaria: 7D2B AF1C F37B 13E2 069D 6956 105B D0E7 3949 9BDB
GPG verified '/home/sdelquin/.rvm/archives/rvm-1.29.12.tgz'
Installing RVM to /home/sdelquin/.rvm/
Adding rvm PATH line to /home/sdelquin/.profile /home/sdelquin/.mkshrc /home/sdelquin/.bashrc /home/sdelquin/.zshrc.
Adding rvm loading line to /home/sdelquin/.profile /home/sdelquin/.bash_profile /home/sdelquin/.zlogin.
Installation of RVM in /home/sdelquin/.rvm/ is almost complete:
* To start using RVM you need to run `source /home/sdelquin/.rvm/scripts/rvm`
in all your open shell windows, in rare cases you need to reopen all shell windows.
Thanks for installing RVM 🙏
Please consider donating to our open collective to help us maintain RVM.
👉 Donate: https://opencollective.com/rvm/donate
Abrimos una nueva pestaña (o volvemos a logearnos si es en producción) para que los cambios se reflejen y podamos probar RVM:
sdelquin@lemon:~$ rvm --version
rvm 1.29.12 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [https://rvm.io]
Ahora procedemos a instalar Ruby usando RVM:
sdelquin@lemon:~$ rvm install ruby
Searching for binary rubies, this might take some time.
No binary rubies available for: debian/11/arm64/ruby-3.0.0.
Continuing with compilation. Please read 'rvm help mount' to get more information on binary rubies.
Checking requirements for debian.
Installing requirements for debian.
Updating systemsdelquin password required for 'apt-get --quiet --yes update':
.....
Installing required packages: gawk, bison, libyaml-dev, sqlite3, libgmp-dev........
Requirements installation successful.
Installing Ruby from source to: /home/sdelquin/.rvm/rubies/ruby-3.0.0, this may take a while depending on your cpu(s)...
ruby-3.0.0 - #downloading ruby-3.0.0, this may take a while depending on your connection...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 18.6M 100 18.6M 0 0 22.9M 0 --:--:-- --:--:-- --:--:-- 22.9M
ruby-3.0.0 - #extracting ruby-3.0.0 to /home/sdelquin/.rvm/src/ruby-3.0.0.....
ruby-3.0.0 - #configuring........................................................................
ruby-3.0.0 - #post-configuration..
ruby-3.0.0 - #compiling..........................................................................................
ruby-3.0.0 - #installing.....................
ruby-3.0.0 - #making binaries executable...
Installed rubygems 3.2.3 is newer than 3.0.9 provided with installed ruby, skipping installation, use --force to force installation.
ruby-3.0.0 - #gemset created /home/sdelquin/.rvm/gems/ruby-3.0.0@global
ruby-3.0.0 - #importing gemset /home/sdelquin/.rvm/gemsets/global.gems..........................................................
ruby-3.0.0 - #generating global wrappers........
ruby-3.0.0 - #gemset created /home/sdelquin/.rvm/gems/ruby-3.0.0
ruby-3.0.0 - #importing gemsetfile /home/sdelquin/.rvm/gemsets/default.gems evaluated to empty gem list
ruby-3.0.0 - #generating default wrappers........
ruby-3.0.0 - #adjusting #shebangs for (gem irb erb ri rdoc testrb rake).
Install of ruby-3.0.0 - #complete
Ruby was built without documentation, to build it run: rvm docs generate-ri
Tenemos que establecer la versión por defecto de Ruby que vamos a utilizar:
sdelquin@lemon:~$ bash --login
sdelquin@lemon:~$ rvm --default use 3.0.0
Using /home/sdelquin/.rvm/gems/ruby-3.0.0
sdelquin@lemon:~$ echo "source ~/.rvm/scripts/rvm" >> .bashrc
Comprobamos tanto la versión de Ruby como la versión de la herramienta gem
que se utiliza para instalar paquetes (gemas):
sdelquin@lemon:~$ ruby --version
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [aarch64-linux]
sdelquin@lemon:~$ gem --version
3.2.3
Estamos ya en disposición de instalar Ruby on Rails:
sdelquin@lemon:~$ gem install rails
Comprobamos ahora la versión instalada de Ruby on Rails:
sdelquin@lemon:~$ rails --version
Rails 7.0.4
Necesitamos instalar algún paquete en el sistema para dar soporte a los pasos posteriores. En este caso nos hace falta libpq una librería de sistema para acceso a bases de datos PostgreSQL:
$ sudo apt install -y libpq-dev
Rails nos provee de un subcomando para crear una nueva aplicación (andamiaje). Incluimos el nombre de la aplicación y el tipo de base de datos que vamos a utilizar:
sdelquin@lemon:~$ rails new travelroad -G --database=postgresql
La estructura de carpetas y ficheros queda tal que así:
sdelquin@lemon:~$ ls -l travelroad
total 68
drwxr-xr-x 11 sdelquin sdelquin 4096 feb 3 15:23 app
drwxr-xr-x 2 sdelquin sdelquin 4096 feb 3 15:23 bin
drwxr-xr-x 5 sdelquin sdelquin 4096 feb 3 15:23 config
-rw-r--r-- 1 sdelquin sdelquin 160 feb 3 15:22 config.ru
drwxr-xr-x 2 sdelquin sdelquin 4096 feb 3 15:22 db
-rw-r--r-- 1 sdelquin sdelquin 2282 feb 3 15:22 Gemfile
-rw-r--r-- 1 sdelquin sdelquin 5520 feb 3 15:23 Gemfile.lock
drwxr-xr-x 4 sdelquin sdelquin 4096 feb 3 15:22 lib
drwxr-xr-x 2 sdelquin sdelquin 4096 feb 3 15:23 log
drwxr-xr-x 2 sdelquin sdelquin 4096 feb 3 15:22 public
-rw-r--r-- 1 sdelquin sdelquin 227 feb 3 15:22 Rakefile
-rw-r--r-- 1 sdelquin sdelquin 374 feb 3 15:22 README.md
drwxr-xr-x 2 sdelquin sdelquin 4096 feb 3 15:22 storage
drwxr-xr-x 10 sdelquin sdelquin 4096 feb 3 15:22 test
drwxr-xr-x 5 sdelquin sdelquin 4096 feb 3 15:23 tmp
drwxr-xr-x 3 sdelquin sdelquin 4096 feb 3 15:23 vendor
💡 La opción
-G
deshabilita la creación de un repositorio git.
Para que la conexión a la base de datos funcione correctamente debemos establecer las credenciales de inicio de sesión en PostgreSQL. Para ello utilizamos uno de los ficheros que provee Ruby on Rails en su andamiaje:
sdelquin@lemon:~/travelroad$ vi config/database.yml
Contenido:
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
username: travelroad_user
password: dpl0000
database: travelroad
host: localhost
port: 5432
production:
<<: *default
database: travelroad
username: travelroad_user
password: <%= ENV["TRAVELROAD_DATABASE_PASSWORD"] %>
host: localhost
port: 5432
test:
<<: *default
database: travelroad_test
Ruby on Rails trae incorporado el servidor de aplicación Puma que lo podemos lanzar en modo "desarrollo" para comprobar que todo esté funcionando correctamente:
sdelquin@lemon:~/travelroad$ bin/rails server
=> Booting Puma
=> Rails 7.0.4 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 3.0.0-p0) ("Birdie's Version")
* Min threads: 5
* Max threads: 5
* Environment: development
* PID: 5193
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
Si abrimos un navegador en http://localhost:3000 obtendremos una pantalla similar a la siguiente:
Lo primero es editar el fichero de rutas y modificarlo convenientemente:
sdelquin@lemon:~/travelroad$ vi config/routes.rb
Contenido:
Rails.application.routes.draw do
get "/", to: "places#index"
end
Para crear el código base del modelo utilizamos la herramienta que nos proporciona Ruby on Rails:
sdelquin@lemon:~/travelroad$ bin/rails generate model Place
invoke active_record
create db/migrate/20221114114713_create_places.rb
create app/models/place.rb
invoke test_unit
create test/models/place_test.rb
create test/fixtures/places.yml
En la salida del comando anterior podemos ver un fichero de migraciones en la carpeta migrate
. Las migraciones incluyen los cambios a aplicar en la base de datos cuando creamos o modificamos cualquier modelo (de datos).
Para crear el código base del controlador utilizamos la herramienta que nos proporciona Ruby on Rails:
sdelquin@lemon:~/travelroad$ bin/rails generate controller Places index
create app/controllers/places_controller.rb
invoke erb
create app/views/places
create app/views/places/index.html.erb
invoke test_unit
create test/controllers/places_controller_test.rb
invoke helper
create app/helpers/places_helper.rb
invoke test_unit
Editamos el controlador:
sdelquin@lemon:~/travelroad$ vi app/controllers/places_controller.rb
Contenido:
class PlacesController < ApplicationController
def index
@wished = Place.where(visited: false)
@visited = Place.where(visited: true)
end
end
Para la vista vamos a utilizar ERB un motor de plantillas sencillo pero potente para Ruby.
Editamos el fichero de la vista:
sdelquin@lemon:~/travelroad$ vi app/views/places/index.html.erb
Contenido:
<h1>My Travel Bucket List</h1>
<h2>Places I'd Like to Visit</h2>
<ul>
<% @wished.each do |place| %>
<li><%= place.name %></li>
<% end %>
</ul>
<h2>Places I've Already Been To</h2>
<ul>
<% @visited.each do |place| %>
<li><%= place.name %></li>
<% end %>
</ul>
Dado que estamos trabajando con una base de datos y una tabla ya creadas, no nos interesa aplicar las migraciones que nos sugiere Ruby on Rails. Para no obtener un error, deshabilitamos esa opción en otro fichero de configuración del entorno de desarrollo:
sdelquin@lemon:~/travelroad$ vi config/environments/development.rb
Tocamos sólo la siguiente línea:
config.active_record.migration_error = false
Ahora ya estamos en disposición de levantar el servidor de desarrollo y comprobar que todo funciona correctamente:
sdelquin@lemon:~/travelroad$ bin/rails server
=> Booting Puma
=> Rails 7.0.4 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 3.0.0-p0) ("Birdie's Version")
* Min threads: 5
* Max threads: 5
* Environment: development
* PID: 66801
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop
Al acceder a http://localhost:3000 podemos ver que obtenemos el resultado esperado:
Para poder conectar Nginx con Ruby on Rails necesitamos hacer uso del módulo Passenger.
Antes de nada debemos intalar la herramienta en sí:
sdelquin@lemon:~$ gem install passenger
Fetching passenger-6.0.15.gem
Building native extensions. This could take a while...
Successfully installed passenger-6.0.15
Parsing documentation for passenger-6.0.15
Installing ri documentation for passenger-6.0.15
Done installing documentation for passenger after 17 seconds
1 gem installed
Comprobamos la versión instalada:
sdelquin@lemon:~$ passenger --version
Phusion Passenger(R) 6.0.15
Aunque la documentación oficial se enfoca más en compilar un "nuevo" Nginx incluyendo el módulo passenger
de forma estática, en nuestro caso vamos a incluirlo de forma dinámica al Nginx "existente".
Lo primero será instalar las dependencias que tiene este módulo de cara al proceso de compilación:
sdelquin@lemon:~$ sudo apt install -y libcurl4-openssl-dev libpcre3-dev
A continuación descargamos el código fuente de Nginx con la misma versión que el Nginx que tenemos funcionando en el sistema:
sdelquin@lemon:~$ curl -sL \
https://nginx.org/download/nginx-$(/sbin/nginx -v |& cut -d '/' -f2).tar.gz |
tar xvz -C /tmp
Nos movemos a la carpeta descargada:
sdelquin@lemon:~$ cd /tmp/nginx-$(/sbin/nginx -v |& cut -d '/' -f2)/
Realizamos la configuración:
sdelquin@lemon:/tmp/nginx-1.22.0$ ./configure \
--add-dynamic-module=$(passenger-config --nginx-addon-dir) \
--with-compat
Y construimos el módulo dinámico:
sdelquin@lemon:/tmp/nginx-1.22.0$ make modules
Como resultado de este proceso obtendremos un fichero ngx_http_passenger_module.so
que copiaremos a la carpeta desde la que se cargan los módulos en Nginx:
sdelquin@lemon:/tmp/nginx-1.22.0$ sudo cp objs/ngx_http_passenger_module.so \
/etc/nginx/modules
Ya por último, para cargar el módulo en Nginx, tocamos la configuración del servicio, añadiendo la siguiente línea a /etc/nginx/nginx.conf
:
...
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
# ↓ Añadir esta línea
load_module /etc/nginx/modules/ngx_http_passenger_module.so;
# ↑ Añadir esta línea
events {
worker_connections 1024;
}
...
También es necesario incluir en el fichero anterior, la ruta raíz de passenger
. La podemos encontrar con el comando:
sdelquin@lemon:~$ passenger-config --root
/home/sdelquin/.rvm/gems/ruby-3.0.0/gems/passenger-6.0.15
Añadimos ahora esta ruta al fichero /etc/nginx/nginx.conf
dentro del bloque http { ... }
y antes de la línea include /etc/nginx/conf.d/*.conf;
:
...
http {
...
#gzip on;
# ↓ Añadir esta línea
passenger_root /home/sdelquin/.rvm/gems/ruby-3.0.0/gems/passenger-6.0.15;
# ↑ Añadir esta línea
include /etc/nginx/conf.d/*.conf;
}
Comprobamos que la sintaxis sea correcta y reiniciamos el servidor web:
sdelquin@lemon:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sdelquin@lemon:~$ sudo systemctl restart nginx
Creamos un nuevo fichero de configuración para dar servicio a este escenario:
sdelquin@lemon:~$ sudo vi /etc/nginx/conf.d/travelroad.conf
Contenido:
server {
server_name travelroad;
root /home/sdelquin/travelroad/public;
passenger_enabled on;
passenger_app_env production;
passenger_env_var TRAVELROAD_DATABASE_PASSWORD dpl0000;
}
Es el momento de generar los assets de la aplicación (CSS, Javascript, ...):
sdelquin@lemon:~/travelroad$ bin/rails assets:precompile
💡 El comando anterior genera los assets en la carpeta
public/assets
. Esta carpeta no debería estar dentro del control de versiones.
Tras todos estos cambios, nos aseguramos de reiniciar (mejor que recargar por la cantidad de modificaciones/módulos que hemos tocado) el servidor web Nginx:
sdelquin@lemon:~$ sudo systemctl restart nginx
Y finalmente accedemos a http://travelroad comprobando que es el resultado esperado:
Veamos un ejemplo de script de despliegue para esta aplicación:
sdelquin@lemon:~/travelroad$ vi deploy.sh
Contenido:
#!/bin/bash
ssh arkania "
cd $(dirname $0)
git pull
bin/rails assets:precompile
passenger-config restart-app .
"
Damos permisos de ejecución:
sdelquin@lemon:~/travelroad$ chmod +x deploy.sh
💡
deploy.sh
es un fichero que se incluye en el control de versiones.
Django es un framework para desarrollo web de código abierto, escrito en Python, que respeta el patrón de diseño conocido como modelo–vista–controlador (MVC).
Lo primero de todo será instalar el lenguaje de programación y las herramientas Python para poder trabajar en el desarrollo de la aplicación web. Para ello basta con seguir estas instrucciones.
💡 Recordar que se debe añadir la ruta
~/.local/bin
alPATH
para que los binarios instalados por paquetes de Python se puedan localizar fácilmente.
Habitualmente en Python trabajamos con entornos virtuales lo que nos permite aislar las dependencias de nuestro proyecto con respecto a otros proyectos o al sistema operativo.
Para crear (y activar) un entorno virtual ejecutamos lo siguiente:
sdelquin@lemon:~/travelroad$ python -m venv --prompt travelroad .venv
sdelquin@lemon:~/travelroad$ source .venv/bin/activate
(travelroad) sdelquin@lemon:~/travelroad$
💡 Cuando activamos un entorno virtual el prompt se modifica y aparece entre paréntesis el nombre del entorno virtual.
Los nuevos paquetes que instalemos dentro del entorno virtual se almacenarán en la carpeta .venv
:
(travelroad) sdelquin@lemon:~/travelroad$ ls -l .venv
total 16
drwxr-xr-x 2 sdelquin sdelquin 4096 nov 15 10:14 bin
drwxr-xr-x 3 sdelquin sdelquin 4096 nov 15 10:14 include
drwxr-xr-x 3 sdelquin sdelquin 4096 nov 15 10:14 lib
lrwxrwxrwx 1 sdelquin sdelquin 3 nov 15 10:14 lib64 -> lib
-rw-r--r-- 1 sdelquin sdelquin 219 nov 15 10:14 pyvenv.cfg
La carpeta del entorno virtual .venv
no debe estar en el control de versiones, por lo que se recomienda incluirla en el fichero .gitignore
.
💡 Para desactivar el entorno virtual podemos ejecutar
deactivate
.
Para instalar Django (y sus dependencias) basta con utilizar la herramienta de gestión de paquetes en Python denominada pip (Package Installer for Python):
(travelroad) sdelquin@lemon:~/travelroad$ pip install django
Collecting django
Downloading Django-4.1.3-py3-none-any.whl (8.1 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.1/8.1 MB 5.1 MB/s eta 0:00:00
Collecting asgiref<4,>=3.5.2
Downloading asgiref-3.5.2-py3-none-any.whl (22 kB)
Collecting sqlparse>=0.2.2
Downloading sqlparse-0.4.3-py3-none-any.whl (42 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 42.8/42.8 kB 8.3 MB/s eta 0:00:00
Installing collected packages: sqlparse, asgiref, django
Successfully installed asgiref-3.5.2 django-4.1.3 sqlparse-0.4.3
Podemos comprobar la versión instalada de Django con el siguiente comando:
(travelroad) sdelquin@lemon:~/travelroad$ python -m django --version
4.1.3
Django proporciona la herramienta django-admin para crear la estructura base del proyecto:
(travelroad) sdelquin@lemon:~/travelroad$ django-admin startproject main .
Comprobamos el contenido de la carpeta de trabajo:
(travelroad) sdelquin@lemon:~/travelroad$ tree
.
├── manage.py
└── main
├── asgi.py
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
1 directory, 6 files
💡 A diferencia de Ruby on Rails, Django sigue un patrón "minimalista" donde el andamiaje es modesto y con muy pocos ficheros iniciales.
Podemos lanzar el servidor de desarrollo con la herramienta manage.py que ya viene incluida en el andamiaje del proyecto:
(travelroad) sdelquin@lemon:~/travelroad$ ./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
November 15, 2022 - 10:11:09
Django version 4.1.3, using settings 'main.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Ahora si accedemos a http://localhost:8000 tendremos la pantalla de bienvenida de un proyecto base Django:
Ahora ya estamos en disposición de empezar a montar las distintas partes de nuestra aplicación web. Django sigue el patrón MTV (Model-Template-View) que es análogo al modelo MVC. Funciona de la siguiente manera:
Un proyecto Django está formado por "aplicaciones". Lo primero será crear nuestra primera aplicación:
(travelroad) sdelquin@lemon:~/travelroad$ ./manage.py startapp places
A través de este comando se ha creado una carpeta para alojar la aplicación places
con el siguiente contenido:
(travelroad) sdelquin@lemon:~/travelroad$ ls -l places
total 24
-rw-r--r-- 1 sdelquin sdelquin 63 nov 15 10:19 admin.py
-rw-r--r-- 1 sdelquin sdelquin 144 nov 15 10:19 apps.py
-rw-r--r-- 1 sdelquin sdelquin 0 nov 15 10:19 __init__.py
drwxr-xr-x 2 sdelquin sdelquin 4096 nov 15 10:19 migrations
-rw-r--r-- 1 sdelquin sdelquin 57 nov 15 10:19 models.py
-rw-r--r-- 1 sdelquin sdelquin 60 nov 15 10:19 tests.py
-rw-r--r-- 1 sdelquin sdelquin 63 nov 15 10:19 views.py
Hemos de activar esta aplicación para que Django sea consciente de que existe. Para ello añadimos esta línea en el fichero main/settings.py
:
(travelroad) sdelquin@lemon:~/travelroad$ vi main/settings.py
Contenido:
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Nueva línea ↓
'places.apps.PlacesConfig',
]
...
Antes de nada debemos instalar un paquete de soporte denominado psycopg que viene a ser un driver para conectar Python con bases de datos PostgreSQL:
(travelroad) sdelquin@lemon:~/travelroad$ pip install psycopg2
Collecting psycopg2
Downloading psycopg2-2.9.5.tar.gz (384 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 384.3/384.3 kB 4.1 MB/s eta 0:00:00
Preparing metadata (setup.py) ... done
Building wheels for collected packages: psycopg2
Building wheel for psycopg2 (setup.py) ... done
Created wheel for psycopg2: filename=psycopg2-2.9.5-cp311-cp311-linux_aarch64.whl size=490810 sha256=fb4ced65205db48f43764e3023f3dfc013a00c9e4b33f7d94db4042bbe7b4be1
Stored in directory: /tmp/pip-ephem-wheel-cache-srnl4xia/wheels/f9/08/b1/dddce0df8eee727ef4a56fb0da4f0230de9e127e5f234881d4
Successfully built psycopg2
Installing collected packages: psycopg2
Successfully installed psycopg2-2.9.5
Hay que establecer las credenciales de acceso a la base de datos:
(travelroad) sdelquin@lemon:~/travelroad$ vi main/settings.py
Dejar la sección DATABASES
tal que así:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'travelroad',
'USER': 'travelroad_user',
'PASSWORD': 'dpl0000',
'HOST': 'localhost',
'PORT': 5432,
}
}
Django proporciona el subcomando check dentro de manage.py
para comprobar que todo esté correcto:
(travelroad) sdelquin@lemon:~/travelroad$ ./manage.py check
System check identified no issues (0 silenced).
En Django existe un ORM que permite mapear clases escritas en Python con entidades relacionales de la base de datos (PostgreSQL en este caso).
Vamos a escribir nuestro modelo de lugares:
(travelroad) sdelquin@lemon:~/travelroad$ vi places/models.py
Contenido:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=255)
visited = models.BooleanField()
class Meta:
# ↓ necesario porque ya partimos de una tabla creada ↓
db_table = "places"
def __str__(self):
return self.name
💡 Django añade automáticamente por defecto a todos sus modelos una clave primaria
id
que es única y autoincremental.
Creamos la vista que gestionará las peticiones a la página principal:
(travelroad) sdelquin@lemon:~/travelroad$ vi places/views.py
Contenido:
from django.http import HttpResponse
from django.template import loader
from .models import Place
def index(request):
wished = Place.objects.filter(visited=False)
visited = Place.objects.filter(visited=True)
template = loader.get_template('places/index.html')
context = {
'wished': wished,
'visited': visited,
}
return HttpResponse(template.render(context, request))
A continuación creamos la plantilla:
(travelroad) sdelquin@lemon:~/travelroad$ mkdir -p places/templates/places
(travelroad) sdelquin@lemon:~/travelroad$ vi places/templates/places/index.html
Contenido:
<h1>My Travel Bucket List</h1>
<h2>Places I'd Like to Visit</h2>
<ul>
{% for place in wished %}
<li>{{ place }}</li>
{% endfor %}
</ul>
<h2>Places I've Already Been To</h2>
<ul>
{% for place in visited %}
<li>{{ place }}</li>
{% endfor %}
</ul>
Es necesario vincular cada URL con la vista que la gestionará.
Para ello, lo primero es crear el fichero de URLs para la "aplicación" places
:
(travelroad) sdelquin@lemon:~/travelroad$ vi places/urls.py
Contenido:
from django.urls import path
from . import views
app_name = 'places'
urlpatterns = [
path('', views.index, name='index'),
]
Y ahora enlazamos estas URLs desde el fichero principal:
(travelroad) sdelquin@lemon:~/travelroad$ vi main/urls.py
Contenido:
from django.contrib import admin
from django.urls import path
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls),
# NUEVA LÍNEA ↓
path('', include('places.urls', 'places')),
]
Con todo esto ya estamos en disposición de probar nuestra aplicación en un entorno de desarrollo (local):
(travelroad) sdelquin@lemon:~/travelroad$ ./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
November 21, 2022 - 15:40:01
Django version 4.1.3, using settings 'main.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Si accedemos a http://localhost:8000 podemos observar el resultado esperado:
Queremos que las credenciales a la base de datos sea un elemento configurable en función del entorno en el que estemos trabajando.
Esto lo podemos conseguir (entre otros) mediante un paquete de Python denominado prettyconf que sirve para cargar variables de entorno mediate un fichero de configuración.
Realizamos la instalación del paquete (ojo tener activo el entorno virtual Python):
(travelroad) sdelquin@lemon:~/travelroad$ pip install prettyconf
Collecting prettyconf
Using cached prettyconf-2.2.1.tar.gz (11 kB)
Preparing metadata (setup.py) ... done
Building wheels for collected packages: prettyconf
Building wheel for prettyconf (setup.py) ... done
Created wheel for prettyconf: filename=prettyconf-2.2.1-py2.py3-none-any.whl size=9797 sha256=300e63f4a8afcfc43446bef750e270e9a2a070a28dc9e27c66c22276779bd65c
Stored in directory: /home/sdelquin/.cache/pip/wheels/58/75/59/2aa05b767025506114499da0585a6004bd1b8171e0b141c577
Successfully built prettyconf
Installing collected packages: prettyconf
Successfully installed prettyconf-2.2.1
Modificamos las siguientes líneas del fichero settings.py
para aprovechar las funcionalidades de este paquete:
(travelroad) sdelquin@lemon:~/travelroad$ vi main/settings.py
Contenido:
...
from pathlib import Path
# ↓ Nueva línea
from prettyconf import config
# ↑ Nueva línea
...
DEBUG = config('DEBUG', default=True, cast=config.boolean)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=config.list)
...
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME', default='travelroad'),
'USER': config('DB_USERNAME', default='travelroad_user'),
'PASSWORD': config('DB_PASSWORD', default='dpl0000'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default=5432, cast=int)
}
}
...
Comprobamos que todo sigue estando correcto:
(travelroad) sdelquin@lemon:~/travelroad$ ./manage.py check
System check identified no issues (0 silenced).
De cara a que el proyecto pueda "reproducirse" en cualquier entorno virtual necesitamos especificar los requerimientos (dependencias) en un fichero requirements.txt
.
Lo creamos y añadimos los paquetes utilizados:
(travelroad) sdelquin@lemon:~/travelroad$ vi requirements.txt
Contenido:
django
psycopg2
prettyconf
Cuando nos vayamos al entorno de producción hay que realizar una serie de pasos:
- Clonar el repositorio git.
- Crear el entorno virtual (tal y como se ha visto).
- Instalar las dependencias.
- Fijar parámetros para el entorno de producción.
- Montar el servidor de aplicación.
- Configurar el virtual host para Nginx.
- Preparar el script de despliegue.
Una vez clonado el repositorio y con el entorno virtual creado y activo, ejecutamos el siguiente comando para instalar las dependencias del proyecto:
(travelroad) sdelquin@lemon:~/travelroad$ pip install -r requirements.txt
Necesitamos un fichero .env
que contendrá las configuraciones concretas para el entorno de producción:
(travelroad) sdelquin@lemon:~/travelroad$ vi .env
Contenido:
DEBUG=0
# ↓ Dominio en el que se va a desplegar la aplicación
ALLOWED_HOSTS=travelroad.dpl.arkania.es
#DB_PASSWORD='supersecret'
💡 Tener en cuenta que el fichero
.env
hay que dejarlo fuera de control de versiones.
Existen múltiples alternativas para el despliegue de una aplicación Django y un amplio abanico de servidores de aplicación.
En este caso vamos a elegir gunicorn como servidor WSGI (Web Server Gateway Interface) para Python.
La instalación de gunicorn
es muy sencilla ya que se trata de un paquete del ecosistema Python:
(travelroad) sdelquin@lemon:~/travelroad$ pip install gunicorn
Collecting gunicorn
Using cached gunicorn-20.1.0-py3-none-any.whl (79 kB)
Requirement already satisfied: setuptools>=3.0 in ./.venv/lib/python3.11/site-packages (from gunicorn) (65.5.0)
Installing collected packages: gunicorn
Successfully installed gunicorn-20.1.0
Una vez instalado, tenemos a nuestro alcance un script de gestión que permite lanzar el servidor:
(travelroad) sdelquin@lemon:~/travelroad$ gunicorn main.wsgi:application
[2022-11-21 16:13:06 +0000] [280469] [INFO] Starting gunicorn 20.1.0
[2022-11-21 16:13:06 +0000] [280469] [INFO] Listening at: http://127.0.0.1:8000 (280469)
[2022-11-21 16:13:06 +0000] [280469] [INFO] Using worker: sync
[2022-11-21 16:13:06 +0000] [280470] [INFO] Booting worker with pid: 280470
Dado que el servidor WSGI debemos mantenerlo activo y con la posibilidad de gestionarlo (arrancar, parar, etc.) hemos de buscar alguna herramienta que nos ofrezca estas posibilidades.
Una alternativa es usar servicios systemd, como hemos visto anteriormente (Java Spring).
Pero en esta ocasión vamos a usar Supervisor que es un sistema cliente/servidor que permite monitorizar y controlar procesos en sistemas Linux/UNIX... ¡Y además está escrito en Python!
Para instalarlo ejecutamos el siguiente comando:
sdelquin@lemon:~$ sudo apt install -y supervisor
[sudo] password for sdelquin:
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias... Hecho
Leyendo la información de estado... Hecho
Paquetes sugeridos:
supervisor-doc
Se instalarán los siguientes paquetes NUEVOS:
supervisor
0 actualizados, 1 nuevos se instalarán, 0 para eliminar y 88 no actualizados.
Se necesita descargar 309 kB de archivos.
Se utilizarán 1.738 kB de espacio de disco adicional después de esta operación.
Des:1 http://deb.debian.org/debian bullseye/main arm64 supervisor all 4.2.2-2 [309 kB]
Descargados 309 kB en 0s (636 kB/s)
Seleccionando el paquete supervisor previamente no seleccionado.
(Leyendo la base de datos ... 235947 ficheros o directorios instalados actualmente.)
Preparando para desempaquetar .../supervisor_4.2.2-2_all.deb ...
Desempaquetando supervisor (4.2.2-2) ...
Configurando supervisor (4.2.2-2) ...
Created symlink /etc/systemd/system/multi-user.target.wants/supervisor.service → /lib/systemd/system/supervisor.service.
Procesando disparadores para man-db (2.9.4-2) ...
Podemos comprobar que el servicio está levantado y funcionando:
sdelquin@lemon:~$ sudo systemctl status supervisor
● supervisor.service - Supervisor process control system for UNIX
Loaded: loaded (/lib/systemd/system/supervisor.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2022-11-21 16:25:09 WET; 56s ago
Docs: http://supervisord.org
Main PID: 282865 (supervisord)
Tasks: 1 (limit: 2251)
Memory: 16.0M
CPU: 92ms
CGroup: /system.slice/supervisor.service
└─282865 /usr/bin/python3 /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf
nov 21 16:25:09 lemon systemd[1]: Started Supervisor process control system for UNIX.
nov 21 16:25:10 lemon supervisord[282865]: 2022-11-21 16:25:10,061 CRIT Supervisor is running as ro>
nov 21 16:25:10 lemon supervisord[282865]: 2022-11-21 16:25:10,061 WARN No file matches via include>
nov 21 16:25:10 lemon supervisord[282865]: 2022-11-21 16:25:10,062 INFO RPC interface 'supervisor' >
nov 21 16:25:10 lemon supervisord[282865]: 2022-11-21 16:25:10,063 CRIT Server 'unix_http_server' r>
nov 21 16:25:10 lemon supervisord[282865]: 2022-11-21 16:25:10,063 INFO supervisord started with pi>
Supervisor viene con la herramienta supervisorctl
, pero inicialmente un usuario "ordinario" no tiene permisos para usarla:
sdelquin@lemon:~$ supervisorctl status
error: <class 'PermissionError'>, [Errno 13] Permission denied: file: /usr/lib/python3/dist-packages/supervisor/xmlrpc.py line: 560
Para que un usuario no privilegiado pueda usar el servicio, la estrategia a seguir es añadir un grupo supervisor
con permisos para ello, y luego unir al usuario a dicho grupo.
sdelquin@lemon:~$ sudo groupadd supervisor
Editamos la configuración de Supervisor:
sdelquin@lemon:~$ sudo vi /etc/supervisor/supervisord.conf
Cambiar (y añadir) lo siguiente a partir de la línea 5:
...
chmod=0770 ; socket file mode (default 0700)
chown=root:supervisor ; grupo 'supervisor' para usuarios no privilegiados
...
Reiniciamos el servicio para que surtan efectos los cambios realizados:
sdelquin@lemon:~$ sudo systemctl restart supervisor
Ahora añadimos el usuario al grupo creado:
sdelquin@lemon:~$ sudo usermod -a -G supervisor sdelquin
Añadiendo al usuario `sdelquin' al grupo `supervisor' ...
Añadiendo al usuario sdelquin al grupo supervisor
Hecho.
Para que el cambio de grupo sea efectivo, HABRÁ QUE SALIR Y VOLVER A ENTRAR EN LA SESIÓN.
Una vez de vuelta en la sesión podemos comprobar que ya no se produce ningún error al lanzar el controlador de supervisor con nuestro usuario habitual:
sdelquin@lemon:~$ supervisorctl help
default commands (type help <topic>):
=====================================
add exit open reload restart start tail
avail fg pid remove shutdown status update
clear maintail quit reread signal stop version
Aunque no es totalmente obligatorio, sí puede ser de utilidad que tengamos un script de servicio para nuestra aplicación que se encargue de levantar gunicorn
:
(travelroad) sdelquin@lemon:~/travelroad$ vi run.sh
Contenido:
#!/bin/bash
cd $(dirname $0)
source .venv/bin/activate
gunicorn -b unix:/tmp/travelroad.sock main.wsgi:application
Damos permisos de ejecución:
(travelroad) sdelquin@lemon:~/travelroad$ chmod +x run.sh
Lo que nos queda es crear la configuración de un proceso supervisor que lance nuestro servicio WSGI como servidor de aplicación para la aplicación Django.
sdelquin@lemon:~$ sudo vi /etc/supervisor/conf.d/travelroad.conf
Contenido:
[program:travelroad]
user = sdelquin
command = /home/sdelquin/travelroad/run.sh
autostart = true
autorestart = true
stopsignal = INT
killasgroup = true
stderr_logfile = /var/log/supervisor/travelroad.err.log
stdout_logfile = /var/log/supervisor/travelroad.out.log
Ahora ya podemos añadir este proceso:
sdelquin@lemon:~$ supervisorctl reread
travelroad: available
sdelquin@lemon:~$ supervisorctl add travelroad
travelroad: added process group
sdelquin@lemon:~$ supervisorctl status
travelroad RUNNING pid 6018, uptime 0:00:23
Por último nos queda configurar el virtual host para derivar las peticiones al servidor WSGI:
sdelquin@lemon:~$ sudo vi /etc/nginx/conf.d/travelroad.conf
Contenido:
server {
server_name travelroad;
location / {
include proxy_params;
proxy_pass http://unix:/tmp/travelroad.sock; # socket UNIX
}
}
💡 Tener en cuenta que la ruta del socket tiene que coincidir con el script de servicio
run.sh
.
Recargamos la configuración de Nginx para que los cambios surtan efecto:
sdelquin@lemon:~$ sudo systemctl reload nginx
Ya podemos acceder a http://travelroad (o el dominio de producción que corresponda) obteniendo el resultado esperado:
Tener en cuenta que cuando actualicemos el código de la aplicación será necesario recargar el script de servicio para que gunicorn
vuelva a servir la aplicación con los cambios realizados:
sdelquin@lemon:~$ supervisorctl restart travelroad
travelroad: stopped
travelroad: started
Veamos un ejemplo de script de despliegue para esta aplicación:
sdelquin@lemon:~/travelroad$ vi deploy.sh
Contenido:
#!/bin/bash
ssh arkania "
cd $(dirname $0)
git pull
source .venv/bin/activate
pip install -r requirements.txt
# python manage.py migrate
# python manage.py collectstatic --no-input
supervisorctl restart travelroad
"
Damos permisos de ejecución:
sdelquin@lemon:~/travelroad$ chmod +x deploy.sh
💡
deploy.sh
es un fichero que se incluye en el control de versiones.