Tutorial configurando vagrant para una aplicación 2526
Contenido
- 1 Despliegue de una Aplicación Python en Vagrant
- 2 Parte 1: Aprovisionamiento mediante un Script de Shell
- 3 Parte 2: Aprovisionamiento mediante un playbook de Ansible
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_seguraCó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 = TrueCó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_appCó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 appCó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>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`).
¿Qué es un archivo con extensión .sh?
Un archivo con extensión .sh es un script de shell, un archivo de texto que contiene una serie de comandos escritos en un lenguaje de scripting específico para el shell, el intérprete de comandos que interactúa con el sistema operativo. Los scripts .sh se usan ampliamente en sistemas Unix y Linux, aunque también funcionan en otros entornos compatibles con shells como bash, sh o zsh.
¿Para qué sirven los archivos .sh?
Los archivos .sh permiten automatizar tareas, ya que el shell ejecuta cada comando en el archivo de forma secuencial, sin necesidad de intervención manual. Esto es especialmente útil para configurar sistemas, instalar paquetes, mover o copiar archivos, configurar redes, iniciar aplicaciones y realizar prácticamente cualquier acción que se pueda ejecutar desde la línea de comandos.
Por ejemplo, en el contexto de nuestro proyecto con Vagrant y Flask, usaremos un archivo .sh llamado provision.sh para:
Instalar dependencias como Python y Flask. Configurar la base de datos. Preparar el entorno de la aplicación. Esto permite que, al ejecutar vagrant up, el script se ejecute automáticamente, configurando la máquina virtual sin necesidad de realizar los pasos de configuración uno a uno.
Cómo se ejecuta un archivo .sh
Para ejecutar un archivo .sh en un sistema Linux o Mac, simplemente hay que abrir una terminal y escribir: sh nombre_del_archivo.sh bash nombre_del_archivo.shCuando Vagrant encuentra un archivo .sh especificado en el Vagrantfile (como en config.vm.provision "shell", path: "provision.sh"), ejecuta ese archivo automáticamente en la máquina virtual como parte del proceso de aprovisionamiento. Esto garantiza que la máquina esté configurada correctamente cada vez que se inicia.
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"
endExplicació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").
Crear el script de aprovisionamiento
Este script, llamado provision.sh, es ejecutado por Vagrant cuando se levanta la máquina virtual (vagrant up). Su objetivo es automatizar la instalación de software, la configuración de la base de datos y el despliegue inicial de la aplicación Flask.
En la raíz del proyecto, al mismo nivel que el Vagrantfile, crea un script provision.sh con este contenido:
#!/bin/bash
# --- 1. Cargar Variables del .env ---
# Carga las credenciales de la DB y la aplicación desde el archivo unificado .env
echo "--- 1. Cargando configuración desde el archivo .env ---"
set -a
# Usamos el path /vagrant para acceder a los archivos del directorio compartido
source /vagrant/.env
set +a
PROJECT_DIR="/vagrant"
# --- 2. Instalación de Dependencias del Sistema ---
echo "--- 2. Instalando MariaDB, Python (>=3.10) y utilidades ---"
sudo apt-get update
# python3-dev es necesario para compilar algunas dependencias
sudo apt-get install -y mariadb-server python3-pip python3-venv git python3-dev
# --- 3. Configuración de MariaDB (Idempotente) ---
echo "--- 3. Configurando MariaDB y creando usuario '$DATABASE_USER' ---"
sudo systemctl enable mariadb
sudo systemctl start mariadb
sleep 5
# Los comandos SQL deben usar la sintaxis más compatible para asegurar la creación del usuario.
sudo mysql -u root <<EOF
-- 3a. Crear la base de datos
CREATE DATABASE IF NOT EXISTS $DATABASE_DB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 3b. Crear/Modificar el usuario con la contraseña
-- Usamos la sintaxis estándar para compatibilidad.
CREATE USER IF NOT EXISTS '$DATABASE_USER'@'localhost' IDENTIFIED BY '$DATABASE_PASSWORD';
-- 3c. Asegurar que el plugin de autenticación sea compatible con PyMySQL (si la versión de MariaDB lo requiere)
-- Nota: Si usas una versión reciente de MariaDB, este paso es implícito o usa IDENTIFIED BY.
-- Lo mantenemos simple para evitar el ERROR 1064.
-- 3d. Otorgar permisos sobre la base de datos
GRANT ALL PRIVILEGES ON $DATABASE_DB.* TO '$DATABASE_USER'@'localhost';
-- 3e. Aplicar los cambios
FLUSH PRIVILEGES;
EOF
echo "✅ MariaDB configurado."
# --- 4. Preparación del Entorno Python ---
echo "--- 4. Configurando entorno Python e instalando requirements.txt ---"
cd $PROJECT_DIR
# 4a. Crear y activar el entorno virtual
if [ ! -d ".venv" ]; then
python3 -m venv .venv
fi
source .venv/bin/activate
# 4b. Instalar dependencias
pip install -r requirements.txt
# --- 5. Migraciones y Creación de Tablas ---
echo "--- 5. Ejecutando migraciones de Flask (Alembic) ---"
export FLASK_APP=app.app:create_app
# 5a. Inicializar el repositorio de migraciones (si no existe)
if [ ! -d "migrations" ]; then
flask db init
fi
# 5b. Crear la migración inicial si es necesario (ESTA ES LA LÍNEA FALTANTE)
# Flask-Migrate solo crea tablas si existe un archivo de versión.
flask db migrate -m "Initial database migration"
# 5c. Aplicar las migraciones. Esto crea las tablas en la base de datos.
flask db upgrade
echo "✅ Base de datos inicializada."
# --- 6. Lanzamiento de la Aplicación ---
echo "--- 6. Iniciando la aplicación Flask en segundo plano (Puerto 5000) ---"
# Detener cualquier instancia previa para idempotencia
pkill -f 'flask run --host=0.0.0.0'
# Iniciar la aplicación en segundo plano con nohup
# El .env se carga automáticamente por python-dotenv
nohup flask run --host=0.0.0.0 > /tmp/flask_app.log 2>&1 &
echo "✅ Aprovisionamiento y lanzamiento completados. Accede en http://localhost:5000"
deactivate
Explicación paso a paso del script de aprovisionamiento
Aquí tienes la explicación detallada del script en texto, agrupada por pasos lógicos:
Paso 1: Carga de Variables de Configuración
Este bloque asegura que las credenciales de la base de datos y otras variables de entorno definidas en el archivo .env de tu máquina local estén disponibles dentro de la máquina virtual (VM).
set -a: Este comando asegura que todas las variables definidas o modificadas a partir de este punto sean automáticamente exportadas al entorno (export all), haciéndolas accesibles para los comandos subsiguientes como mysql.
source /vagrant/.env: Comando clave. El directorio /vagrant es el punto de montaje predeterminado que conecta el directorio de tu proyecto local con la VM. Este comando lee el archivo .env compartido e inserta sus variables ($DATABASE_USER, $DATABASE_DB, etc.) en el shell de la VM.
set +a Desactiva la exportación automática de variables.
PROJECT_DIR="/vagrant": Define una variable para el directorio raíz del proyecto, facilitando la navegación posterior.
Paso 2: Instalación de Dependencias del Sistema
Esta sección se encarga de instalar todo el software que necesita la aplicación para funcionar, asumiendo que la VM comienza con un sistema operativo limpio.
sudo apt-get update: Actualiza la lista de paquetes disponibles en el sistema operativo.
sudo apt-get install -y mariadb-server python3-pip python3-venv git python3-dev: Instala los paquetes esenciales sin pedir confirmación (-y):
-
mariadb-server: El servidor de base de datos.
-
python3-pip y python3-venv: Herramientas necesarias para gestionar paquetes y crear entornos virtuales de Python.
-
git: Útil para gestión de código.
-
python3-dev: Necesario para compilar bibliotecas de Python que tienen componentes nativos, como el conector de MariaDB (pymysql).
Paso 3: Configuración de MariaDB
Este bloque configura la base de datos y el usuario de manera idempotente (es decir, puede ejecutarse varias veces sin causar errores).
sudo systemctl enable mariadb / sudo systemctl start mariadb: Asegura que el servicio MariaDB esté habilitado para arrancar con el sistema y lo inicia inmediatamente.
sleep 5: Espera 5 segundos para que el servicio de la base de datos se inicie completamente antes de intentar ejecutar comandos SQL.
sudo mysql -u root <<EOF ... EOF: Ejecuta los comandos SQL dentro del bloque.
-
CREATE DATABASE IF NOT EXISTS $DATABASE_DB ...: Crea la base de datos utilizando la variable cargada, si esta aún no existe.
-
CREATE USER IF NOT EXISTS '$DATABASE_USER'@'localhost' ...: Crea el usuario de la aplicación, también utilizando las variables cargadas.
-
GRANT ALL PRIVILEGES ON $DATABASE_DB.* TO '$DATABASE_USER'@'localhost': Otorga todos los permisos al usuario recién creado, limitando su acceso a la base de datos de la aplicación.
-
FLUSH PRIVILEGES: Ordena a MariaDB que recargue la tabla de permisos inmediatamente.
Paso 4: Preparación del Entorno Python
En esta sección se configura el entorno de ejecución del proyecto.
cd $PROJECT_DIR : Navega al directorio raíz del proyecto (/vagrant).
if [ ! -d ".venv" ]; then python3 -m venv .venv; fi : Crea el entorno virtual (.venv) si aún no existe (idempotencia).
source .venv/bin/activate : Activa el entorno virtual. Esto hace que los comandos como pip y flask apunten a los binarios dentro del .venv.
pip install -r requirements.txt : Instala todas las dependencias de Python listadas en el archivo requirements.txt.
Paso 5: Migraciones y Creación de Tablas
Se utiliza Flask-Migrate (Alembic) para configurar la base de datos con el esquema de modelos de SQLAlchemy.
export FLASK_APP=app.app:create_app: Establece la variable de entorno necesaria para que el comando flask sepa dónde encontrar la instancia de la aplicación (la función create_app en app/app.py).
if [ ! -d "migrations" ]; then flask db init; fi: Comprueba si el directorio migrations existe. Si no, inicializa el repositorio de migraciones de Alembic.
flask db upgrade: Aplica la migración más reciente a la base de datos, lo que resulta en la creación de todas las tablas definidas por los modelos de la aplicación.
Paso 6: Lanzamiento de la Aplicación
Finalmente, se lanza la aplicación Flask y se sale del entorno de aprovisionamiento.
pkill -f 'flask run --host=0.0.0.0': Intenta detener cualquier proceso Flask anterior que pudiera estar ejecutándose, asegurando que solo haya una instancia activa.
nohup flask run --host=0.0.0.0 > /tmp/flask_app.log 2>&1 &: Lanza el servidor Flask:
-
--host=0.0.0.0: Permite que la aplicación sea accesible desde fuera de la VM (a través del puerto reenviado en Vagrantfile).
-
nohup ... &: Ejecuta el comando en segundo plano (&) y lo hace inmune a colgarse (nohup), manteniendo el servidor activo incluso después de que termine la sesión de aprovisionamiento. La salida se redirige al archivo /tmp/flask_app.log.
deactivate: Sale del entorno virtual de Python.
Iniciar el Entorno Virtual con Vagrant
Desde la terminal, navega al directorio `flask_testing_project/` y ejecuta el siguiente comando:
vagrant upCuando el proceso finalice, podrás acceder a la aplicación en `http://localhost:5000`.
¿Has podido ver qué ocurría en cada paso? ¡Prueba a entrar en la máquina virtual y ver cómo está funcionando todo por dentro!
Parte 2: Aprovisionamiento mediante un playbook de Ansible
El aprovisionamiento con Ansible ofrece varias ventajas frente al uso de scripts de shell. Mientras que los scripts de shell son imperativos, es decir, detallan los pasos exactos para lograr un objetivo, Ansible adopta un enfoque declarativo, donde especificamos el estado deseado del sistema y Ansible se encarga de llevarlo a cabo. Esto permite una mayor idempotencia, es decir, que la configuración se pueda aplicar varias veces sin causar efectos no deseados. Además, Ansible facilita la gestión de infraestructuras grandes de forma escalable y reproducible, lo que hace que sea más fácil mantener entornos consistentes y automatizados en comparación con los scripts tradicionales. Un aspecto clave de Ansible es que permite aprovisionar diferentes máquinas sin importar el sistema operativo o la infraestructura subyacente, ya que simplemente le indicamos qué necesitamos y Ansible se encarga de implementar la configuración adecuada, independientemente de cómo o dónde se ejecute.
Paso 1: Modificar el Vagrantfile
Modifica el Vagrantfile para ejecutar un playbook de Ansible en lugar del script provision.sh:
# 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 (usando 5001 como puerto de host para evitar colisiones)
# Aplicación accesible en http://localhost:5001
config.vm.network "forwarded_port", guest: 5000, host: 5001
# 3. Provisionamiento: Ejecuta el Playbook de Ansible
config.vm.provision "ansible" do |ansible|
# Especifica que Vagrant debe ejecutar el archivo playbook.yml
ansible.playbook = "playbook.yml"
# Ejecutar tareas con privilegios de root (sudo)
ansible.become = true
# Asegura que se ejecuta en el host de la MV
ansible.limit = "all"
end
end