Pasos para desplegar aplicación Flask con mod_wsgi y Apache.

Apache es el servidor web por excelencia, para desplegar aplicaciones y paginas web. Sin embargo, su funcionamiento en otros stacks, como por ejemplo Python Flask, se comporta bastante diferente. Sin embargo, existen varias formas de usarlo y las útil que encontre, que no requiere demasiadas configuraciones, es usar el módulo WSGI que este servidor posee, lo que permite conectar la aplicación desde donde esté ubicada e interpretarla para que sea usable en producción (y también desarrollo).

Ambiente.

Requerimientos.

  1. Sistema Operativo: Ubuntu 24

  2. Version de Apache: 2.4.58-1ubuntu8.6

  3. Versión de Flask: 3.1.1

  4. Base de Datos: PostgreSQL 16.9

  5. Ruta de la aplicación: /home/user/flask

Configuración.

Vamos a preparar el entorno para habilitar Apache. Para esto, requerimos instalar lo siguiente.

En primer lugar, hay que instalar mod_wsgi en el ambiente. Esto debe hacerse con el entorno virtual activado (Despues se desactiva, ya que apache tomara el mando y desplegará la aplicación).

# Activa el entorno virtual
source venv/bin/activate
# Descargar paquete mod_wsgi
pip install mod_wsgi
# Desactivar el entorno virtual
deactivate

Lo siguiente es instalar el paquete mod_wsgi en Apache y habilitarlo

sudo apt install libapache2-mod-wsgi-py3
# Habilitar 
sudo a2enmod wsgi
# Reiniciar apache
sudo systemctl restart apache2
# Verificar
apache2ctl -M | grep wsgi
# La salida debe ser algo como esto
 wsgi_module (shared)

El próximo paso, es crear el archivo app.wsgi, dentro del directorio donde se encuentra la aplicacion (/home/user/flask) que será el que apache utilizará para conectar con la aplicación.

vim app.wsgi

El archivo debe llevar lo siguiente.

import sys
sys.path.insert(0,'/home/user/flask') # Aqui va la ruta de la aplicacion
from flaskapp import app as application # El nombre de la aplicacion, debe coincidir con el de la app.py, en este caso flaskapp.py

Lo siguiente es crear el VirtualHost en la configuración de Apache. Dependiendo de la distribución, se encuentra en distintos lugars. En el caso de Ubuntu, por lo general está en /etc/apache2/site-avalaibles/. Debe llevar lo siguiente.

sudo vim /etc/apache2/sites-available/flask.conf
<VirtualHost *:5000>
    ServerAdmin webmaster@localhost

    WSGIDaemonProcess flaskapp user=user group=www-data threads=5 python-home=/home/user/flaskapp/venv
    WSGIScriptAlias / /home/user/flaskapp/flaskapp.wsgi
    WSGIProcessGroup flaskapp

    <Directory /home/user/flaskapp>
        Require all granted
    </Directory>

    Alias /static/ /home/user/flaskapp/static/
    <Directory /home/user/flaskapp/static/>
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/flaskapp-error.log
    CustomLog ${APACHE_LOG_DIR}/flaskapp-access.log combined
</VirtualHost>

Este archivo contiene lo siguiente:

En encabezado, Virtualhost *:5000 que habilita el puerto 5000, aunque puede ser cualquiera que deseen utilizar. Se debe comprobar si el puerto objetivo esta abierto para escuchas con netstat o ss.

# Net stats
sudo netstats -tulnp|grep *:5000 # Puede ser cualquier otro puerto
# SS
sudo ss -tulnp|grep *:5000

En WSGIDaemonProcess, se debe agregar lo siguiente.

  • el nombre de la aplicación, en este caso flaskapp

  • el usuario dueño del directorio.

  • el grupo al que pertenece, en este caso debe ser si o si www-data

  • en la ruta python-home, se agrega la ruta donde se encuentra la aplicación. Debe apuntar al directorio venv que es el que activa el entorno virtual y guarda las dependencias.

En WSGIScriptAlias, apuntamos a la ruta de nuestra app.wsgi, que es el puente entre Apache y la aplicacion.

Por ultimo en Alias, se apunta al directorio static, el cual almacena los archivos estáticos como imágenes, hojas de estilo y scripts de Javascript que la web utiliza.

Una vez realizado esto, se deba aplicar el comando a2ensite flaskapp, para que agregue el nuevo archivo .conf se vuelve a reinicar Apache, para que tome los cambios.

Se debe asegurar que el puerto sea escuchado. Para eso, abrimos /etc/apache2/ports.conf y agregamos la siguiente linea

Listen 5000

Se agrega la aplicación al entorno.

# Añade la aplicacion al entorno de apache
sudo a2ensite flaskapp

Ahora, hay que considerar los permisos del directorio. Si estan trabajando con git, pueden tener ciertas dificultades, si el usuario que administra el control de versiones no es el duelo del directorio. Lo mejor en este caso, es agregarlo al grupo www-data.

# Se agrega 
sudo chown -R user:www-data /home/user/pyerp
# Se dan permisos de lectura y ejecucion
sudo chmod -R 755 /home/user/flaskapp

Por último, se recarga Apache.

sudo systemctl reload apache2

Con esto, ya estamos listos para el despliegue, salvo una cosa.

Normalmente, Apache, toma en consideración la ruta absoluta de los archivos, por lo que es imperativo, realizar ciertos cambios en la configuración misma de la aplicación. En este caso, la aplicación tiene dos archivos que hacen función de eso: config.ini y config.py. En este últimos nos centraremos.

Para empezar, la configuración básica es la siguiente. Definimos en el .ini, los parámetros que serán llamados:

# En este archivo, se agregan los parámetros. Pueden ser varios, los cuales se pueden llamar en constantes dentro de cualquier otro archivo de la aplicacion.
[postgresql]
HOSTNAME = localhost
PORT = 5432
USERNAME = usuario
PASSWORD = clave
DATABASE = basededatos

Luego tenemos el .py que lo llama.

from configparser import ConfigParser

def DBconfig(filename='config.ini', section='postgresql'):
    parser = ConfigParser()
    parser.read(filename)
    db = {}

    if section in parser:
        for key in parser[section]:
            db[key.upper()] = parser[section][key]
    else:
        raise Exception(f'La seccion {section} no existe en el archivo {filename}')
    return db 

if __name__ == '__main__':
    DBconfig()

La función de este ultimo archivo, es llamar a config.ini y cargar dentro de la aplicación, los parámetros que son leídos por cada sección. Normalmente, se escribe como en el ejemplo de arriba, pero Apache puede que no encuentre la ruta y vaya hacia otro lado.

Para asegurar que eso no pase, se agrega lo siguiente

def get_path(filename):
    return os.path.join(os.path.dirname(__file__), filename)

Esta función, nos trae la ruta absoluta, donde se encuentra el archivo config.ini, lo que hará que Apache lo encuentre sin problemas. El archivo entonces quedará asi.

import os # importamos las funcionalidades que usa el sistema operativo.
from configparser import ConfigParser

# Generamos la ruta absoluta, basada en la del directorio de la aplicacion
def get_path(filename):
   return os.path.join(os.path.dirname(__file__), filename)

def DBconfig(filename='config.ini', section='postgresql'):
   parser = ConfigParser()
   # aqui llamamos a la funcion que trae la ruta.
   parser.read(get_path(filename))

   db = {}
   if section in parser:
       for key in parser[section]:
           db[key.upper()] = parser[section][key]
   else:
       raise Exception(f'La seccion {section} no existe en el archivo {filename}')
   return db

Consideraciones finales.

Este ejemplo, se hizo en un ambiente de desarrollo. Para desplegar en producción, hay otros detalles adicionales que se deben considerar. Aun así está configuración mínima, permite desplegar una aplicación, rápidamente y con mayor eficiencia.

Existen otras herramientas como gunicorn, que realiza la misma función, pero en este post, quise dar un ejemplo de algo que ya existe y que además, es el más masivo en su uso.

Espero que les sirva. Saludos.

0
Subscribe to my newsletter

Read articles from Hermann Pollack (hpollack95) directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hermann Pollack (hpollack95)
Hermann Pollack (hpollack95)