resources/views/pages/frontend/docs/arquitectura-bare-metal.md
Última modificación: 27 Oct, 2026

Arquitectura Bare Metal Distribuida y Balanceada (Alta Disponibilidad)

La Arquitectura Bare Metal Distribuida de Mañuti desacopla el monolito en una granja de servidores físicos o virtuales (VPS) independientes sobre Ubuntu LTS, conectados a través de una Red Privada (VPC/LAN) bajo una política estricta de Zero Trust (Confianza Cero) e ISO/IEC 27001.

En esta topología, los servidores de aplicación son 100% Stateless (Sin Estado Local): cualquier nodo Web puede destruirse o clonarse horizontalmente en segundos sin perder sesiones, archivos subidos, tareas programadas ni logs de errores.


1. Topología de Red e Inventario de Nodos

flowchart TD
    U(["Usuarios / Clientes Externos"])

    LB["NODO LB: LOAD BALANCER NGINX<br/>(192.168.1.122 / .143)<br/>[SSL Termination + Bastión Deploy]"]

    WEB["NODOS WEB (1..N)<br/>(.64 / .130 / .147..)<br/>[Nginx + PHP 8.5]"]
    WORKER["NODO WORKER<br/>(.126 / .150)<br/>[Horizon + Cron]"]
    RT["NODO REALTIME<br/>(.127 / .151)<br/>[Reverb Server]"]

    CACHE[("NODO CACHÉ: REDIS<br/>(.124 / .146 :6379)")]

    SQL[("NODO SQL<br/>MariaDB (:3306)<br/>(.123 / .144)")]
    NOSQL[("NODO NoSQL<br/>MongoDB (:27017)<br/>(.120 / .145)")]
    STORAGE[("NODO STORAGE<br/>MinIO S3 (:9000)<br/>(.125 / .132)")]
    LOGS[("LOGS DE SISTEMA<br/>system_errors<br/>(En MongoDB)")]

    U -->|"HTTPS (443/80)"| LB
    U -->|"WSS (WebSockets /app)"| LB

    LB -->|"Balanceo HTTP (Upstream)"| WEB
    LB -->|"Proxy WSS (/app -> :8080)"| RT

    WEB -->|"(1) Sesiones/Cache/Pub"| CACHE
    WORKER -->|"(2) Consume Colas"| CACHE
    CACHE -.->|"(3) Subscribe Pub/Sub"| RT

    WEB --> SQL
    WEB --> NOSQL
    WEB --> STORAGE
    WEB --> LOGS

Matriz de Roles de Servidores

Capa Rol del Nodo Servicios Activos Estado Puertos Internos (UFW)
Acceso Load Balancer (LB) Nginx Reverse Proxy + Bastión SSH Stateless 80/tcp, 443/tcp, 22/tcp
App Nodos WEB (01..N) Nginx + PHP 8.5-FPM Stateless 80/tcp (Solo desde LB), 22/tcp
App Nodo WORKER Supervisor (horizon) + Cron Maestro Stateless 22/tcp (Sin tráfico entrante web)
App Nodo REALTIME Supervisor (reverb:start) Stateless 8080/tcp (Desde LB/LAN), 22/tcp
Datos Nodo SQL MariaDB 10.11+ Stateful 3306/tcp (Solo desde App Nodes)
Datos Nodo NoSQL MongoDB 7.0+ Stateful 27017/tcp (Solo desde App Nodes)
Datos Nodo CACHÉ Redis 7.0+ Stateful 6379/tcp (Solo desde App Nodes)
Storage Nodo S3 MinIO Object Storage Stateful 9000/tcp (API), 9001/tcp (Consola)

2. Pilares del Código Stateless en Mañuti

Antes de repartir el tráfico entre múltiples servidores, Mañuti implementa 5 adaptaciones críticas para operar sin estado local:

2.1. Trusted Proxies (bootstrap/app.php)

Como todo el tráfico pasa por el Load Balancer, Laravel vería la IP del balanceador en lugar de la IP real del usuario, arruinando la auditoría forense. Mañuti lee TRUST_PROXIES desde el .env:

// bootstrap/app.php
$trustedProxies = env('TRUST_PROXIES', null);
if ($trustedProxies) {
    $proxiesArray = $trustedProxies === '*' ? '*' : array_map('trim', explode(',', $trustedProxies));
    $middleware->trustProxies(at: $proxiesArray);
}
$middleware->trustProxies(headers: Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO);

2.2. Almacenamiento Distribuido con MinIO (S3)

Ningún archivo subido (avatares, mapas SVG de arquitectura o exportaciones JSON de privacidad) se guarda en el disco duro de los nodos Web. Todo viaja vía league/flysystem-aws-s3-v3 al servidor MinIO usando FILESYSTEM_DISK=s3 y storePublicly(). Includes reglas de ciclo de vida (mc ilm rule add) para autodestruir exportaciones temporales tras 24 horas.

2.3. Scheduler Atómico (->onOneServer())

Para evitar que 3 servidores ejecuten la misma tarea programada 3 veces, Mañuti centraliza el Cron en el Nodo Worker y además protege cada tarea en routes/console.php con bloqueos atómicos en Redis (SETNX):

Schedule::command('privacy:prune-logs')->dailyAt('03:00')->onOneServer();
Schedule::call(function () {
    Artisan::call('system:backup');
})->dailyAt('02:00')->name('system-backup-dynamic')->onOneServer();

2.4. Escalado Horizontal de Reverb vía Redis Pub/Sub

Al activar REVERB_SCALING_ENABLED=true, cuando el Nodo Web 2 dispara un evento (ej. Kill Switch), lo publica en el canal reverb de Redis. El Nodo Realtime está suscrito a Redis, captura el evento y lo empuja por el túnel WebSocket al navegador del usuario.

2.5. Logging Centralizado de Excepciones en MongoDB con Fallback

En lugar de fragmentar errores en archivos storage/logs/laravel.log repartidos en múltiples servidores, Mañuti utiliza App\Logging\CreateMongoLogger con un FallbackGroupHandler de Monolog:

  1. Intenta escribir toda excepción del sistema en la colección system_errors de MongoDB (incluyendo la IP del nodo que falló y el Stack Trace).
  2. Si MongoDB sufriera una caída de red, conmuta automáticamente al archivo rotativo local sin derribar la aplicación.
  3. Incluye un visor administrativo en tiempo real (Monitor de Errores del Sistema) dentro del panel.

3. Configuración del Balanceador de Carga (Nodo LB)

El Nodo LB es la única cara pública del clúster. No tiene PHP ni bases de datos; únicamente ejecuta Nginx como Reverse Proxy para tráfico HTTP y túnel WebSockets (WSS).

Archivo /etc/nginx/sites-available/framedev_lb:

upstream backend_servers {
    server 192.168.1.64:80;
    server 192.168.1.130:80;
    keepalive 32;
}

upstream reverb_server {
    server 192.168.1.127:8080;
}

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    # 1. TRÁFICO HTTP DE APLICACIÓN (Balanceado entre Nodos Web)
    location / {
        proxy_pass http://backend_servers;

        # Header de diagnóstico para identificar qué Nodo Web respondió
        add_header X-Served-By $upstream_addr always;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_buffering off;
        proxy_read_timeout 90;
    }

    # 2. TÚNEL WEBSOCKETS (Enrutado directo al Nodo Realtime Reverb)
    location ~ ^/app/ {
        proxy_pass http://reverb_server;

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;

        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
    }
}

4. Aprovisionamiento y Blindaje de la Capa de Datos (Stateful)

Todos los nodos deben sincronizar su reloj mediante chrony (sudo timedatectl set-timezone UTC && sudo apt install -y chrony) para garantizar la integridad de los tokens y logs forenses.

4.1. Nodo SQL (MariaDB)

  1. Tuning de Kernel (/etc/sysctl.conf y /etc/security/limits.conf):
    • vm.swappiness=1 y vm.vfs_cache_pressure=50.
    • File Descriptors (nofile) elevados a 65535 para el usuario mysql.
  2. Red (/etc/mysql/mariadb.conf.d/50-server.cnf):
    [mysqld]
    bind-address = 0.0.0.0
    max_connections = 500
    
  3. Privilegios Mínimos por Subred:
    CREATE USER 'framedev_user'@'192.168.1.%' IDENTIFIED BY 'PasswordSeguroSQL';
    GRANT ALL PRIVILEGES ON framedev_db.* TO 'framedev_user'@'192.168.1.%';
    FLUSH PRIVILEGES;
    
  4. Firewall UFW (Zero Trust):
    sudo ufw default deny incoming
    sudo ufw allow ssh
    sudo ufw allow from 192.168.1.64 to any port 3306 proto tcp comment 'WEB-01'
    sudo ufw allow from 192.168.1.130 to any port 3306 proto tcp comment 'WEB-02'
    sudo ufw allow from 192.168.1.126 to any port 3306 proto tcp comment 'WORKER'
    sudo ufw enable
    

4.2. Nodo CACHÉ (Redis)

  1. Tuning de Kernel (/etc/sysctl.d/99-redis-tuning.conf):
    • vm.overcommit_memory = 1 (Evita fallos en snapshots en segundo plano).
    • net.core.somaxconn = 65535 (Cola TCP de alta concurrencia).
    • Desactivar Transparent Huge Pages (THP) mediante servicio systemd (disable-thp.service).
  2. Configuración (/etc/redis/redis.conf):
    bind 0.0.0.0
    protected-mode yes
    requirepass "PasswordSeguroRedis"
    appendonly yes
    
  3. Firewall UFW: Abrir el puerto 6379/tcp exclusivamente para las IPs de WEB-01, WEB-02, WORKER y REALTIME.

4.3. Nodo NoSQL (MongoDB)

  1. Control de Versiones (ISO 27001): Bloquear actualizaciones automáticas no planeadas con echo "mongodb-org hold" | sudo dpkg --set-selections y desactivar THP.
  2. Autenticación y Red (/etc/mongod.conf):
    net:
      port: 27017
      bindIp: 0.0.0.0
    security:
      authorization: enabled
    
  3. Cadena de Conexión con authSource=admin:
    MONGODB_DSN="mongodb://framedev_mongo:[email protected]:27017/framedev_logs?authSource=admin"
    

5. Especialización y Saneamiento de Nodos de Aplicación

Si los nodos de aplicación se crean clonando una Máquina Virtual base, es obligatorio purgar los servicios que no corresponden a su rol para ahorrar RAM y reducir la superficie de ataque:

  • En TODOS los Nodos de Aplicación (Web, Worker, Realtime): Purgar los servidores de bases de datos locales (sudo apt purge mariadb-server redis-server mongodb-org-server -y), conservando únicamente los clientes CLI (mariadb-client, redis-tools) y las extensiones de PHP 8.5.
  • En Nodos WEB:
    • Deshabilitar supervisor y limpiar cualquier crontab.
    • Ajustar el pool de PHP-FPM (/etc/php/8.5/fpm/pool.d/www.conf) respetando la regla matemática min_spare_servers (5) < start_servers (10) < max_spare_servers (15) con pm.max_children = 50.
    • Firewall UFW: Permitir puerto 80/tcp únicamente desde la IP del Load Balancer.
  • En Nodo WORKER (Horizon):
    • Purgar nginx por completo.
    • En /etc/supervisor/conf.d/ conservar únicamente framedev-horizon.conf.
    • Activar el único Crontab maestro del clúster (sudo crontab -u www-data -e).
  • En Nodo REALTIME (Reverb):
    • Purgar nginx por completo.
    • En /etc/supervisor/conf.d/ conservar únicamente framedev-reverb.conf ejecutando php artisan reverb:start --host=0.0.0.0 --port=8080 con minfds=10000.
Edita este documento en >> arquitectura-bare-metal.md
← Volver a la Landing Page