Tutorial configurando vagrant para una aplicación 2526

De Wiki de EGC
Revisión del 16:54 17 nov 2025 de Brgutierrez (discusión | contribuciones) (Página creada con « = Despliegue de una Aplicación Python en Vagrant = == Introducción == Este tutorial te guiará en el despliegue de una aplicación Flask utilizando Vagrant, Ansible y S...»)
(dif) ← Revisión anterior | Revisión actual (dif) | Revisión siguiente → (dif)
Saltar a: navegación, buscar

Despliegue de una Aplicación Python en Vagrant

Introducción

Este tutorial te guiará en el despliegue de una aplicación Flask utilizando Vagrant, Ansible y Shell scripts. La finalidad es entender cómo automatizar la configuración y despliegue de aplicaciones en entornos virtuales reproducibles, como los que proporciona Vagrant.

¿Por qué utilizamos Vagrant?

Vagrant es una herramienta de administración de entornos virtuales que permite definir en un archivo (llamado `Vagrantfile`) las especificaciones necesarias para crear, configurar y aprovisionar una máquina virtual de manera rápida y consistente. Vagrant es especialmente útil en desarrollo y pruebas, ya que nos permite configurar un entorno de desarrollo idéntico al entorno de producción en cuestión de minutos y garantiza que cada miembro del equipo de desarrollo trabaje en un entorno coherente.

Con Vagrant, evitamos el clásico problema de "en mi máquina funciona" al tener un entorno idéntico en cada ejecución, independientemente de la máquina en la que se esté corriendo el proyecto. En este tutorial, usaremos Vagrant para crear una máquina virtual de Linux donde desplegaremos nuestra aplicación de Flask.

¿Qué es el Aprovisionamiento?

Aprovisionar, en el contexto de máquinas virtuales y Vagrant, se refiere al proceso de instalar y configurar el software necesario en una máquina virtual para que pueda ejecutar una aplicación. Cuando aprovisionamos una máquina, estamos automatizando la instalación de dependencias, configuraciones de sistema, servicios necesarios (como bases de datos), y otras tareas de configuración que preparan el entorno para ejecutar la aplicación.

Métodos de Aprovisionamiento

En este tutorial veremos dos métodos de aprovisionamiento:

1. Mediante un script de shell: Usando un archivo `.sh` (como `provision.sh`), que contiene comandos de terminal para instalar y configurar los componentes necesarios.

2. Mediante Ansible: Usando un archivo de configuración llamado `playbook.yml`, que describe de manera declarativa lo que queremos instalar y configurar en la máquina virtual.

¿Por qué utilizar Ansible para el Aprovisionamiento?

Ansible es una herramienta de automatización de TI que permite gestionar la configuración, el despliegue y la orquestación de sistemas. Comparado con los scripts de shell, Ansible ofrece varias ventajas:

  • Declarativo y legible: Un playbook de Ansible describe el estado deseado del sistema (qué queremos conseguir), en lugar de los pasos para llegar a ese estado (cómo hacerlo), lo cual hace que sea más legible y fácil de mantener.
  • Idempotencia: Ansible ejecuta las tareas de manera idempotente, lo que significa que si ejecutamos el playbook varias veces, el sistema solo aplicará los cambios necesarios, evitando que se realicen operaciones innecesarias.
  • Escalabilidad y organización: Ansible permite crear playbooks para gestionar múltiples servidores de manera centralizada, lo cual es esencial en ambientes de producción donde hay decenas o cientos de servidores.
  • Ecosistema y comunidad: Ansible es una herramienta popular con una gran comunidad de usuarios y módulos preexistentes para una amplia variedad de tareas, lo que facilita extender la funcionalidad y resolver problemas comunes rápidamente.

¿Cuándo es mejor utilizar un script de shell y cuándo Ansible?

Para proyectos pequeños o configuraciones rápidas, un script de shell puede ser suficiente. Sin embargo, para entornos más complejos, donde la repetición, escalabilidad y legibilidad son factores importantes, Ansible es preferible debido a su idempotencia, claridad y modularidad.

En este tutorial, vamos a provisionar un entorno para nuestra aplicación Flask de ambas maneras, para que puedas comparar los métodos y comprender en qué casos uno puede ser más ventajoso que el otro.

Nuestra Aplicación: Gestión de Tareas

La aplicación que desplegaremos es una sencilla aplicación de gestión de tareas, utilizada previamente en otras prácticas. Esta aplicación está basada en Flask, un framework de desarrollo web en Python, y ahora utilizará una base de datos MariaDB para almacenar información. El propósito de este tutorial es aprender a desplegar la aplicación en un entorno controlado y reproducible usando Vagrant y los métodos de aprovisionamiento mencionados anteriormente.

Estructura del Proyecto

Para esta práctica, el proyecto debería presentar la siguiente estructura de archivos:

flask_testing_project/
├── .env                                      
├── config.py               
├── app/                   
│   ├── __init__.py
│   ├── app.py          
│   ├── models.py          
│   └── routes.py           
└── templates/
    └── tasks.html      
 

Y el siguiente contenido:

Código .env:

El archivo .env es un estándar para almacenar configuraciones que varían entre entornos (desarrollo, pruebas, producción) y que no deben ser hardcodeadas o subidas a repositorios de código (como contraseñas). El paquete python-dotenv cargará estas variables en el entorno de la aplicación al inicio.

Finalidad: Centralizar todas las credenciales de MariaDB y los secretos de Flask, facilitando que tanto el script de aprovisionamiento de Vagrant como la aplicación Flask accedan a ellas.

# .env file (Usado en Local y en Vagrant)

# --- 1. Credenciales de la Base de Datos (Para la aplicación Flask) ---
DATABASE_USER=flask_user
DATABASE_PASSWORD=flask_password
DATABASE_HOST=localhost
DATABASE_DB=tasks_db

# --- 2. Variables de la Aplicación Flask ---
SECRET_KEY=una-clave-secreta-larga-y-unica

# --- 3. Credenciales de MariaDB Root ---
MYSQL_ROOT_PASSWORD=su_contraseña_root_segura
Código config.py:

Este módulo define clases de configuración que heredan de una base (Config) y permiten aplicar ajustes específicos para diferentes entornos (ej. DEBUG = True solo en DevelopmentConfig).

Finalidad: Construir la cadena de conexión de la base de datos (SQLALCHEMY_DATABASE_URI) a partir de las variables individuales leídas del entorno (os.environ.get(...)), asegurando que la aplicación sepa cómo conectarse a MariaDB.

import os

# Configuración base (compartida entre entornos)
class Config:
	# Lee SECRET_KEY del .env
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'fallback-clave-secreta' 
    
    # Lee las variables individuales de la DB
    DB_USER = os.environ.get('DATABASE_USER')
    DB_PASS = os.environ.get('DATABASE_PASSWORD')
    DB_HOST = os.environ.get('DATABASE_HOST')
    DB_NAME = os.environ.get('DATABASE_DB')

    # Construye la URL de conexión
    SQLALCHEMY_DATABASE_URI = (
        f'mysql+pymysql://{DB_USER}:{DB_PASS}@{DB_HOST}/{DB_NAME}' 
        if DB_USER and DB_PASS and DB_HOST and DB_NAME else None
    )
    
    SQLALCHEMY_TRACK_MODIFICATIONS = False

# Configuración específica para desarrollo
class DevelopmentConfig(Config):
    DEBUG = True
Código __init__.py:

Este archivo es el módulo de inicialización del paquete Python app. Contiene una única línea de código que expone la función principal del paquete.

Finalidad: Actúa como un proxy o puente para que cualquier otro archivo que necesite acceder a la función fábrica de la aplicación (como el comando flask o scripts externos) pueda importarla directamente usando from app import create_app, sin tener que especificar el archivo interno app.app. Simplemente hace que la función create_app definida en app.py sea accesible al nivel superior del paquete app.

from .app import create_app
Código app.py:

Este módulo contiene la función fábrica de la aplicación (create_app), el corazón de la arquitectura Flask. Es responsable de crear y configurar la instancia de la aplicación de manera flexible, permitiendo diferentes configuraciones (como Desarrollo o Pruebas).

Finalidad: La función create_app es el punto de control central que:

1) Carga el Entorno: Usa load_dotenv() para cargar variables de configuración (credenciales de DB, SECRET_KEY) desde el archivo .env.

2) Configura la Aplicación: Aplica los ajustes específicos de entorno (tomados de config.py) a la instancia de Flask.

3) Inicializa Extensiones: Vincula extensiones como SQLAlchemy (db.init_app) y Flask-Migrate a la aplicación.

4) Registra Rutas: Agrega los módulos de rutas (los Blueprints) a la aplicación, completando la configuración antes de ser devuelta.


from flask import Flask
from app.routes import bp as tasks_blueprint
from .models import db # SQLAlchemy instance
from flask_migrate import Migrate
from config import DevelopmentConfig # Importamos la configuración
from dotenv import load_dotenv
import os

load_dotenv()


def create_app(config_class=DevelopmentConfig): # Usa Desarrollo por defecto
    app = Flask(__name__)
    
    # 1. Aplicar la configuración
    app.config.from_object(config_class)
    
    # 2. Inicializar extensiones
    db.init_app(app)
    # Inicializar Flask-Migrate (necesita app y db)
    Migrate(app, db) 

    app.register_blueprint(tasks_blueprint)
        
    return app
Código models.py:

Este módulo define el Modelo Relacional de Objetos utilizando Flask-SQLAlchemy (ORM).

Finalidad: Declarar la estructura de la tabla tasks y proporcionar funciones de acceso a datos de alto nivel (get_all_tasks, create_task) que interactúan con la base de datos de manera segura dentro de un contexto de aplicación (current_app.app_context()).

from flask_sqlalchemy import SQLAlchemy
from flask import current_app

db = SQLAlchemy()

class Task(db.Model):
    __tablename__ = 'tasks'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    done = db.Column(db.Boolean, default=False)
    
    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'done': self.done
        }

# --- Funciones de Acceso a Datos ---

def get_all_tasks():
    with current_app.app_context():
        return [task.to_dict() for task in Task.query.order_by(Task.id).all()]

def create_task(title):
    if not title:
        raise ValueError("El título es necesario")
    
    with current_app.app_context():
        new_task = Task(title=title, done=False)
        db.session.add(new_task)
        db.session.commit()
        return new_task.to_dict()


Código routes.py:

Este módulo utiliza el concepto de Blueprint de Flask para organizar las rutas y vistas de la aplicación, separando la lógica de enrutamiento de la lógica de la fábrica.

Finalidad: Define los endpoints HTTP (/, /add_task, /tasks). Incluye rutas para la interfaz web (render_template) y endpoints API (jsonify) que llaman a las funciones de acceso a datos definidas en models.py.

from flask import Blueprint, jsonify, request, render_template, redirect, url_for
from app.models import get_all_tasks, create_task

bp = Blueprint('tasks', __name__)

@bp.route('/')
def task_list():
    return render_template('tasks.html', tasks=get_all_tasks())

@bp.route('/tasks', methods=['GET'])
def get_tasks():
    return jsonify({'tasks': get_all_tasks()})

@bp.route('/add_task', methods=['POST'])
def add_task_html():
    title = request.form.get('title')
    try:
        create_task(title)
        return redirect(url_for('tasks.task_list'))
    except ValueError as e:
        return str(e), 400

@bp.route('/tasks', methods=['POST'])
def create_task_api():
    data = request.get_json()
    title = data.get('title') if data else None
    try:
        task = create_task(title)
        return jsonify(task), 201
    except ValueError as e:
        return jsonify({'error': str(e)}), 400


Archivo templates/tasks.html:

La plantilla tasks.html es la encargada de mostrar las tareas y proporcionar un formulario para agregar nuevas tareas.

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gestor de Tareas</title>
</head>
<body>
    <h1>Gestor de Tareas</h1>

    <form action="{{ url_for('tasks.add_task_html') }}" method="POST">
        <input type="text" name="title" placeholder="Añadir nueva tarea">
        <button type="submit">Añadir tarea</button>
    </form>

    <h2>Lista de Tareas:</h2>
    <ul>
        {% for task in tasks %}
            <li>{{ task.title }} {% if task.done %}(completada){% endif %}</li>
        {% endfor %}
    </ul>
</body>
</html>
OPCIONAL: Script local_setup.sh para lanzar la app en local

Si quieres lanzar la aplicación de manera local, puedes utilizar este script:

#!/bin/bash

# =========================================================================
# SCRIPT DE SETUP Y LANZAMIENTO LOCAL PARA FLASK CON MARIADB
# =========================================================================

# --- 0. Asegurar la ubicación ---
# Cambia al directorio del script. Esto garantiza que encuentre .env y .venv.
cd "$(dirname "$0")"
echo "Directorio de trabajo actual: $(pwd)"

# --- 1. Variables y Rutas del Entorno Virtual ---
# Definimos las rutas a los binarios DENTRO del venv para usar rutas ABSOLUTAS.
VENV_DIR=".venv"
PYTHON_BIN="$VENV_DIR/bin/python"
PIP_BIN="$VENV_DIR/bin/pip"
FLASK_BIN="$VENV_DIR/bin/flask"

# --- 2. Cargar Variables de Configuración desde .env ---
echo "--- 1. Cargando configuración desde el archivo .env ---"

if [ ! -f .env ]; then
    echo "❌ ERROR: El archivo .env no se encontró en $(pwd). ¡Crealo primero!"
    exit 1
fi

. .env

# Comprobación de carga
if [ -z "$DATABASE_DB" ]; then
    echo "❌ ERROR: Las variables de la base de datos (ej. DATABASE_DB) no se cargaron correctamente."
    exit 1
fi
echo "✅ Variables cargadas: Usuario=$DATABASE_USER, DB=$DATABASE_DB"

# --- 3. Preparar Entorno Python e Instalar Dependencias ---
echo "--- 2. Creando Entorno Virtual Python (.venv) ---"

# Crear el entorno virtual si no existe
if [ ! -d "$VENV_DIR" ]; then
    python3 -m venv "$VENV_DIR"
fi

# Usando el PIP ABSOLUTO del entorno virtual para la instalación
echo "--- Instalando dependencias necesarias (Flask, SQLAlchemy, PyMySQL, etc.) ---"
"$PIP_BIN" install --upgrade pip
# Lista de dependencias clave para el proyecto:
"$PIP_BIN" install flask python-dotenv flask-sqlalchemy pymysql flask-migrate

# --- 4. Generar requirements.txt ---
echo "--- 3. Generando requirements.txt a partir del entorno actual ---"
"$PIP_BIN" freeze > requirements.txt
echo "✅ requirements.txt creado con las dependencias instaladas."

# --- 5. Configuración de MariaDB (Usuario y Base de Datos) ---
echo "--- 4. Configurando usuario y base de datos en MariaDB... ---"

# Se ejecuta el comando SQL con las variables cargadas
# Esto requiere permisos de sudo para acceder al usuario 'root' de MariaDB
sudo mysql -u root <<EOF
CREATE DATABASE IF NOT EXISTS $DATABASE_DB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '$DATABASE_USER'@'localhost' IDENTIFIED BY '$DATABASE_PASSWORD';
GRANT ALL PRIVILEGES ON $DATABASE_DB.* TO '$DATABASE_USER'@'localhost';
FLUSH PRIVILEGES;
EOF

echo "✅ Usuario '$DATABASE_USER' y base de datos '$DATABASE_DB' configurados."

# --- 6. Inicializar y Aplicar Migraciones ---
echo "--- 5. Aplicando migraciones de base de datos (Flask-Migrate)... ---"

# Exporta la variable FLASK_APP
export FLASK_APP=app.app:create_app

# Usando el binario de Flask del VENV para ejecutar la migración
"$FLASK_BIN" db upgrade

echo "✅ Migraciones completadas. Las tablas están listas."

# --- 7. Lanzar la Aplicación ---
echo "--- 6. Lanzando el servidor Flask en http://127.0.0.1:5000 ---"

# Usando el binario de Flask del VENV para ejecutar la aplicación
"$FLASK_BIN" run


Para lanzarlo, usa:

chmod +x local_setup.sh
./local_setup.sh


Parte 1: Aprovisionamiento mediante un Script de Shell

En la primera parte del tutorial, aprovisionaremos el entorno de desarrollo con un script de shell (`provision.sh`).

Crear el Vagrantfile

Recuerda que el Vagrantfile es el archivo de configuración que Vagrant utiliza para definir y gestionar una máquina virtual. Este archivo es el núcleo de cualquier proyecto que utilice Vagrant, ya que contiene instrucciones específicas para la configuración de la máquina virtual, incluyendo el sistema operativo, red, sincronización de carpetas, y scripts de aprovisionamiento.

Al colocar el Vagrantfile en el directorio raíz del proyecto (flask_testing_project/), mantenemos todos los recursos y configuraciones necesarias para el despliegue de la aplicación en un solo lugar. Esto hace que el proyecto sea fácil de portar y reproducir, ya que cualquier persona con este directorio y Vagrant puede ejecutar el proyecto en una máquina virtual idéntica.

A continuación, crea un archivo llamado Vagrantfile en el directorio flask_testing_project/ y añade el siguiente contenido:

# Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  
  # 1. Sistema Operativo
  config.vm.box = "ubuntu/jammy64" 
  
  # 2. Mapear puerto de Flask (5000)
  config.vm.network "forwarded_port", guest: 5000, host: 5000
  
  # 3. Provisionamiento: Ejecuta el script completo
  config.vm.provision "shell", path: "provision.sh"
end

Explicación línea a línea del Vagrantfile

El archivo Vagrantfile define las especificaciones para crear y configurar la máquina virtual (VM) en la que se ejecutará tu aplicación Flask.

Vagrant.configure("2") do |config|

Esta línea inicia el bloque de configuración del entorno de Vagrant. Indica que estamos utilizando la versión 2 del formato de configuración y asigna un objeto llamado config que se usará para definir todos los ajustes de la máquina virtual.

config.vm.box = "ubuntu/jammy64"

Esta es la línea que define el Sistema Operativo de la máquina virtual. "ubuntu/jammy64" es el nombre de la "caja base" (la imagen del sistema operativo) que Vagrant descargará e instalará. En este caso, se especifica una versión de Ubuntu 64 bits (Jammy Jellyfish, o 22.04 LTS).

config.vm.network "forwarded_port", guest: 5000, host: 5000

Esta línea se encarga del Mapeo de Puertos. Le indica a Vagrant que debe redirigir el tráfico. Específicamente, el puerto 5000 de la máquina anfitriona (tu máquina local, el host) se reenvía al puerto 5000 dentro de la máquina virtual (guest). Esto es crucial porque permite que accedas a la aplicación Flask (que se ejecuta dentro de la VM en el puerto 5000) desde tu navegador web local visitando http://localhost:5000.

config.vm.provision "shell", path: "provision.sh"

Esta línea define el aprovisionamiento. Le indica a Vagrant que, después de arrancar e instalar el sistema operativo, debe ejecutar un script de automatización. El tipo de provisioner es "shell", y el argumento path: "provision.sh" especifica que el archivo a ejecutar es provision.sh, el cual debe encontrarse en el mismo directorio que este Vagrantfile. Este script se encargará de instalar MariaDB, Python y configurar la base de datos y la aplicación.

end

Esta línea simplemente cierra el bloque de configuración iniciado por Vagrant.configure("2").