Aplicaciones de una sola página con Vue.js y Flask: implementación

Bienvenido a la séptima y última entrega de esta serie de tutoriales de varias partes sobre el desarrollo web full-stack con Vue.js y Flask. En este post estaré de...

Implementación en un servidor privado virtual

Bienvenido a la séptima y última entrega de esta serie de tutoriales de varias partes sobre el desarrollo web full-stack con Vue.js y Flask. En esta publicación, demostraré cómo implementar la aplicación creada a lo largo de esta serie.

El código de esta publicación se puede encontrar en mi cuenta de GitHub en la rama Séptimo Post.

Contenido de la serie

  1. Configuración y familiarización con VueJS
  2. Navegación por el enrutador Vue
  3. Gestión de estados con Vuex
  4. API RESTful con Flask
  5. Integración AJAX con API REST
  6. Autenticación JWT
  7. Despliegue a un Servidor Privado Virtual (usted está aquí)

Resumen de las tecnologías

Este tutorial cubrirá varias tecnologías necesarias para implementar una aplicación Flask REST API y Vue.js SPA distribuida de varios niveles. A continuación he enumerado las tecnologías y sus usos:

  • Ubuntu LTS 16.04: servidor host para ejecutar varias aplicaciones y servidores
  • uWSGI: servidor contenedor de Webserver Gateway Interface (WSGI) para ejecutar aplicaciones Python (Flask en este caso)
  • Nginx: servidor web HTTP sin bloqueo de alto rendimiento capaz de realizar proxy inverso a uWSGI
  • Node.js/NPM: entorno Javascript para construir la aplicación Vue.js SPA

Preparar el código para la implementación {#preparar el código para la implementación}

Hay un par de cambios que deben realizarse en el código para que sea más fácil de mantener una vez que la aplicación se haya implementado en mi entorno de producción.

Por ejemplo, en api/index.js de la aplicación survey-spa Vue.js, codifiqué una variable llamada API_URL para apuntar al servidor de desarrollo http://127.0.0.1:5000/api. Al hacer esto, tendré que recordar cambiar esto a la dirección IP del servidor de producción cada vez que necesite implementar.

La experiencia me ha enseñado que siempre habrá cambios en la aplicación que requerirán implementaciones futuras en las que es probable que me olvide de actualizar esta dirección IP. Un mejor enfoque es eliminar el riesgo de que me olvide de actualizar esto y, en su lugar, utilizar configuraciones en el proceso de compilación para manejar esto por mí, lo que resulta en menos de lo que tengo que recordar (es decir, se necesitan menos pasos) durante la implementación. Esto reduce significativamente el riesgo de una implementación fallida en futuras actualizaciones.

Logro esto moviéndome al directorio survey-spa/config y modificando los archivos dev.env.js y prod.env.js definiendo una variable llamada API_URL a la que se le asigna un valor de http://localhost: 5000/api para dev y http://${process.env.BASE_URL}/api para prod como se muestra a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// dev.env.js

'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  NODE_ENV: '"development"',
  API_URL: JSON.stringify(`http://localhost:5000/api`)
})
1
2
3
4
5
6
// prod.env.js
'use strict'
module.exports = {
  NODE_ENV: '"production"',
  API_URL: JSON.stringify(`http://${process.env.BASE_URL}/api`)
}

Nota: el valor de process.env.BASE_URL es una variable de entorno que agregaré al usuario .bash_profile del servidor Ubuntu y lo configuraré igual a la dirección IP del servidor.

Luego, en api/index.js, modifico la línea const API_URL = 'http://127.0.0.1:5000/api' y la establezco igual a process.env.API_URL.

A continuación, en la aplicación Flask, necesito agregar un nuevo módulo llamado wsgi.py para que sirva como punto de entrada a la API REST de Flask. El módulo wsgi.py se parece bastante al módulo appserver.py excepto que no tiene ninguna llamada al método run(...) del objeto de la aplicación. Esto se debe a que el objeto de la aplicación servirá como un invocable para que el servidor contenedor uwsgi se ejecute utilizando su protocolo binario rápido en lugar del servidor de desarrollo normal que se crea cuando se llama a app.run(...).

1
2
3
4
# backend/wsgi.py

from surveyapi.application import create_app
app = create_app()

Con esto terminado, puedo enviar mis cambios al control de versiones y saltar a mi servidor de producción para desplegar el proyecto y configurar los programas que usaré para ejecutar la aplicación en el servidor de producción.

Preparando el servidor Ubuntu

A continuación, accederé a mi servidor privado virtual Ubuntu de producción, que podría estar alojado en uno de los muchos servicios en la nube, como AWS, DigitalOcean, Linode, etc., y comenzaré a instalar todas las ventajas que enumeré en Descripción general de la sección Tecnologías.

1
2
$ apt-get update
$ apt-get install python3-pip python3-dev python3-venv nginx nodejs npm

Con esas instalaciones fuera del camino, ahora puedo crear un usuario llamado "survey" para ejecutar la aplicación y alojar el código.

1
2
3
4
$ adduser survey
$ usermod -aG sudo survey
$ su survey
$ cd

Ahora debería estar en el directorio de inicio del usuario "survey" en /home/survey.

Con el usuario de la encuesta creado, puedo actualizar el archivo .bash_profile para que contenga la dirección IP de mi servidor de producción agregando esta línea al final del archivo. Tenga en cuenta que 123.45.67.89 representa una dirección IP falsa de mi servidor. Reemplácelo con su verdadera dirección IP si está siguiendo.

1
export BASE_URL=123.45.67.89

A continuación, quiero decirle al firewall (ufw) que OpenSSH es aceptable y habilitarlo.

1
2
$ sudo ufw allow OpenSSH
$ sudo ufw enable

Una vez hecho esto, ahora clonaré el repositorio en el servidor para poder compilarlo e implementarlo.

1
$ git clone https://github.com/amcquistan/flask-vuejs-survey.git

Ahora voy a usar el cd de Flass-vuejs-survey/frontend/survey-spa e instalaré las dependencias de la interfaz y compilaré la aplicación de producción.

1
2
3
$ cd flask-vuejs-survey/frontend/survey-spa
$ npm install
$ npm run build

Esto crea un nuevo directorio llamado "dist", que contendrá una página index.html y un directorio llamado "static" que contiene todos los archivos CSS y JavaScript compilados. Estos son los que tendré en el servidor Nginx para constituir la aplicación frontal del SPA.

A continuación, crearé un entorno virtual en el directorio /home/survey para que un intérprete Python3 aislado ejecute la aplicación Python. Una vez instalado, lo activo y me muevo al directorio del proyecto backend para instalar sus paquetes de dependencia especificados en el archivo requirements.txt.

1
2
3
4
$ python3 -m venv venv
$ source venv/bin/activate
(venv) $ cd flask-vuejs-survey/backend
(venv) $ pip install -r requirements.txt

Ahora puedo inicializar la base de datos sqlite y ejecutar las migraciones para crear las distintas tablas de base de datos requeridas por la API REST.

1
(venv) $ python manage.py db upgrade

En este punto, me gustaría iniciar el servidor de desarrollo de Flask para asegurarme de que todo funciona como se esperaba. Antes de hacerlo, necesito decirle al servicio ufw que permita el tráfico en el puerto 5000.

1
2
(venv) $ sudo ufw allow 5000
(venv) $ python appserver.py

En un navegador, ahora puedo ir a http://123.45.67.89:5000/api/surveys/ y debería ver una respuesta JSON simple de [] porque todavía no hay encuestas en esta base de datos, pero esto sí indicar que se realizó una solicitud exitosa. Además, en la terminal conectada al servidor debe haber un mensaje registrado para la solicitud GET emitida desde mi navegador.

Tecleo Ctrl+C en la terminal para eliminar el servidor de desarrollo de Flask y paso a configurar uwsgi para controlar la ejecución de mi API REST de Flask. Si se pregunta de dónde proviene uwsgi, se especifica como un requisito en el archivo requirements.txt con el que instalé pip anteriormente.

Configuración del servidor de contenedores uWSGI

Similar a lo que acabo de hacer con el servidor de desarrollo Flask, ahora probaré que el servidor uWSGI puede servir la aplicación de la siguiente manera.

1
(venv) $ uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app

Nuevamente, ir a mi navegador y actualizar la misma solicitud que hice anteriormente debería devolver una respuesta de matriz JSON vacía. Una vez satisfecho con mi progreso, puedo volver a pulsar Ctrl+C en la terminal y continuar.

Hay dos pasos más que me gustaría realizar para completar la configuración del servidor de contenedores uWSGI. Un paso es crear un archivo de configuración que uWSGI leerá y que reemplazará muchos de los indicadores y argumentos de la línea de comandos que usé anteriormente. El segundo paso es crear un archivo de servicio systemd para administrar el servidor contenedor uWSGI como un servicio como muchos de los otros que ya se ejecutan en el servidor Ubuntu.

En el directorio back-end creo un archivo llamado Surveyapi.ini y lo lleno con lo siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[uwsgi]
module = wsgi:app

master = true
processes = 4

socket = myproject.sock
chmod-socket = 660
vacuum = true

die-on-term = true

Este archivo de configuración le permite a uWSGI saber que el invocable es el objeto de la aplicación dentro del módulo wsgi.py. También le dice que genere y use cuatro procesos para manejar las solicitudes de aplicaciones comunicadas a través de un archivo de socket llamado Surveyapi.sock que tiene un permiso lo suficientemente flexible como para permitir que el servidor web Nginx lea y escriba desde él. Los ajustes de vacío y muerte a término son para garantizar una limpieza adecuada.

Para el archivo de servicio systemd, necesito crear un archivo llamado surveyapi.service en el directorio /etc/systemd/system y agregar algunos descriptores más comandos de acceso, escritura y ejecución como este:

1
(venv) $ sudo nano /etc/systemd/system/surveyapi.service

Luego llénalo con lo siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[Unit]
Description=uWSGI Python container server
After=network.target

[Service]
User=survey
Group=www-data
WorkingDirectory=/home/survey/flask-vuejs-survey/backend
Environment="PATH=/home/survey/venv/bin"
ExecStart=/home/survey/venv/bin/uwsgi --ini surveyapi.ini

[Install]
WantedBy=multi-user.target

Ahora puedo iniciar el servicio y verificar su estado y asegurarme de que el directorio back-end ahora contiene Surveyapi.sock.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
(venv) $ sudo systemctl start surveyapi
(venv) $ sudo systemctl status surveyapi
   Loaded: loaded (/etc/systemd/system/surveyapi.service; disabled; vendor preset: enabled)
   Active: active (running) since Mon 2018-04-23 19:23:01 UTC; 2min 28s ago
 Main PID: 11221 (uwsgi)
    Tasks: 6
   Memory: 28.1M
      CPU: 384ms
   CGroup: /system.slice/surveyapi.service
           ├─11221 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
           ├─11226 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
           ├─11227 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
           ├─11228 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
           ├─11229 /home/survey/venv/bin/uwsgi --ini surveyapi.ini
           └─11230 /home/survey/venv/bin/uwsgi --ini surveyapi.ini

Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: mapped 437520 bytes (427 KB) for 5 cores
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: *** Operational MODE: preforking ***
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x8b4c30 pid: 112
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: *** uWSGI is running in multiple interpreter mode ***
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI master process (pid: 11221)
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI worker 1 (pid: 11226, cores: 1)
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI worker 2 (pid: 11227, cores: 1)
Apr 23 19:23:01 ubuntu-s-1vcpu-2gb-sfo2-01 uwsgi[11221]: spawned uWSGI worker 3 (pid: 11228, cores: 1)
lines 1-23
(venv) $ ls -l /home/survey/flask-vuejs-survey/backend
-rw-rw-r-- 1 survey survey     201 Apr 23 18:18 appserver.py
-rw-rw-r-- 1 survey survey     745 Apr 23 17:55 manage.py
drwxrwxr-x 4 survey survey    4096 Apr 23 18:06 migrations
drwxrwxr-x 2 survey survey    4096 Apr 23 18:52 __pycache__
-rw-rw-r-- 1 survey survey     397 Apr 23 18:46 requirements.txt
drwxrwxr-x 3 survey survey    4096 Apr 23 18:06 surveyapi
-rw-rw-r-- 1 survey survey     133 Apr 23 19:04 surveyapi.ini
srw-rw---- 1 survey www-data     0 Apr 23 19:23 surveyapi.sock
-rw-r--r-- 1 survey survey   10240 Apr 23 18:19 survey.db
-rw-rw-r-- 1 survey survey      84 Apr 23 18:42 wsgi.py

¡Excelente! Lo último que debo hacer es habilitar el inicio automático cada vez que se inicie el sistema para garantizar que la aplicación esté siempre activa.

1
(venv) $ sudo systemctl enable surveyapi

Configuración de Nginx {#configuración de Nginx}

Utilizaré Nginx para servir contenido estático como HTML, CSS y JavaScript, así como para revertir las llamadas de la API REST del proxy a la aplicación Flask/uWSGI. Para configurar nginx para lograr estas cosas, necesitaré crear un archivo de configuración que defina cómo administrar estas diversas solicitudes.

En /etc/nginx/sites-available, crearé un archivo llamado encuesta que contendrá lo siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
server {
    listen 80;
    server_name 123.45.67.89;

    location /api {
        include uwsgi_params;
        uwsgi_pass unix:/home/survey/flask-vuejs-survey/backend/surveyapi.sock;
    }

  location / {
    root /home/survey/flask-vuejs-survey/frontend/survey-spa/dist;
    try_files $uri $uri/ /index.html;
  }
}

Este archivo crea una nueva configuración de bloque de servidor que dice escuchar la dirección IP 123.45.67.89 en el puerto HTTP estándar de 80. Luego dice que busque cualquier ruta URI que comience con /api y revierta el proxy al servidor Flask / uWSGI REST API usando el archivo de socket previamente definido. Por último, la configuración dice que atrape todo lo demás debajo de / y sirva el archivo index.html en el directorio dist creado cuando construí la aplicación SPA front-end de Vue.js antes.

Con este archivo de configuración creado, necesito que Nginx sepa que es un sitio disponible creando un enlace simbólico al directorio /etc/nginx/sites-enabled de la siguiente manera:

1
$ sudo ln -s /etc/nginx/sites-available/survey /etc/nginx/sites-enabled 

Para permitir el tráfico a través del puerto HTTP y vincularlo a Nginx, emitiré la siguiente actualización de ufw y cerraré el puerto 5000 abierto anteriormente.

1
2
$ sudo ufw delete allow 5000
$ sudo ufw allow 'Nginx Full'

Siguiendo este comando, necesitaré reiniciar el servicio Nginx para que las actualizaciones surtan efecto.

1
$ sudo systemctl restart nginx

Ahora puedo volver a mi navegador y visitar http://123.454.67.89 y se me presenta la aplicación de encuestas que he mostrado en artículos anteriores.

Conclusión

Bueno, esta es la publicación final de esta serie de tutoriales de varias partes sobre cómo utilizar Flask y Vue.js para crear una aplicación SPA habilitada para API REST. He intentado cubrir la mayoría de los temas importantes que son comunes a muchos casos de uso de aplicaciones web, asumiendo muy poco conocimiento previo de las tecnologías Flask y Vue.js utilizadas.

Les agradezco por seguir esta serie y, por favor, no duden en comentar o criticar a continuación.