Si hablamos de paneles de control de hosting gratis y open source, podemos decir sin temor a equivocarnos, que VestaCP es uno de los mejores. Liviano, muy fácil de instalar y el UX (user experience) para clientes es simplemente genial. La desventaja (o ventaja) es que está enfocado en web apps desarrolladas en PHP. Y para nosotros, los que desarrollamos aplicaciones JavaScript
, hacerlas correr de manera estable en Vesta puede ser todo un desafío. Máxime cuando son varias en el mismo servidor.
Pero fear not my friends, desarrollamos un script y templates necesarios para que esto no vuelva a ser un problema.
Cómo sabrás correr una sola aplicación en un servidor es tarea trivial, abrir un puerto en el firewall y hacer que la app escuche ahí. El problema se presenta cuando necesitamos correr varias web apps de cara al público. Parte del problema se soluciona con NGINX como proxy reverso, escuchando en los puertos TCP 80
(HTTP) o 443
(HTTPS) y haciendo las peticiones locales a nuestra app.
Hasta acá es el funcionamiento, más o menos, normal de un webserver. El inconveniente aparece al querer automatizar este proceso en VestaCP.
La alternativa manual para 2 web apps puede ser:
app1
en el servidor.app1
escuchando un puerto TCP desocupado, por ejemplo 3000
proxy_pass http://localhost:3000
app2
en el servidor.app2
escuchando un puerto TCP desocupado, por ejemplo 3001
/home/admin/conf/web/dominio2.com.nginx.conf
y modificar el proxy_pass http://localhost:3001
Cómo se puede ver, hay que tener bien presente los puertos utilizados. Otro problema que se encuentra es que si se modifican manualmente los archivos de configuración y se utiliza SSL con Let's Encrypt, cada vez que hace la renovación los archivos de configuración de NGINX
se resetean.
Una fix alternativo puede ser el de hacer un template de VestaCP por cada app. Pero si tienen clientes que hagan deploys de sus propias Node.JS apps esto no es posible.
La solución que encontramos fue que las apps no escuchen sobre un puerto TCP sino que lo hagan sobre un UNIX socket. Según wikipedia:
Un socket de dominio UNIX (UDS) o socket IPC (socket de comunicación interprocesos) es un socket virtual, similar a un socket de Internet que se utiliza en los sistemas operativos POSIX para comunicación entre procesos. Estas conexiones aparecen como flujos de bytes, al igual que las conexiones de red, pero todos los datos se mantienen dentro de la computadora local.
Lo que hace el template es que crea una configuración para NGINX
y haga un proxy_pass
a ese archivo, que es único para cada web y se encuentra dentro una ubicación conocida. Para iniciar y controlar el proceso de la app utilizaremos PM2.
Terminada la introducción vamos a poner manos a la obra. Primero que nada, logicamente, tenemos que tener instalado y funcionando VestaCP y Node.JS
. Con eso listo, instalamos PM2
como usuario root
npm install -g pm2
Para que el template funcione se tienen que cumplir las siguientes condiciones:
~/web/dominio.com/nodeapp/
de cada usuario y dominio. Por ejemplo: el usuario admin
quiere ejecutar una web con el nombre superwebapp.io
. Lo que va a tener que hacer es crear en /home/admin/web/superwebapp.io/nodeapp
y subir o clonar su aplicación dentro de nodeapp
.app.js
y estar en la raíz de nodeapp
(nodeapp/app.js).app.sock
y que se encuentre en la raíz de nodeapp
(nodeapp/app.sock). A continuación dejamos un ejemplo de un archivo de configuración básico para un stack ExpressJS
y MongoDB
. En modo desarrollo utiliza el puerto TCP 3000
y en producción el archivo app.sock
./usr/local/vesta/data/templates/web/nginx/
y seleccionar el proxy template NodeJS en la interfaz web.Ejemplo de configuración app.
// config.js
var path = require('path'),
rootPath = path.normalize(__dirname + '/..'),
env = process.env.NODE_ENV || 'development';
var config = {
development: {
root: rootPath,
app: {
name: 'server'
},
port: process.env.PORT || 3000,
db: 'mongodb://localhost/server-development-logico'
},
test: {
root: rootPath,
app: {
name: 'server'
},
port: process.env.PORT || 3000,
db: 'mongodb://localhost/server-test-logico'
},
production: {
root: rootPath,
app: {
name: 'server'
},
port: rootPath + '/app.sock',
db: 'mongodb://localhost/server-production-logico'
}
};
module.exports = config[env];
Este template para NGINX
tiene las siguientes caracteristicas:
server {
listen %ip%:%proxy_port%;
server_name %domain_idn%
return 301 https://%domain_idn%$request_uri;
}
server {
listen %ip%:%proxy_ssl_port% http2 ssl;
server_name %domain_idn%;
ssl_certificate %ssl_pem%;
ssl_certificate_key %ssl_key%;
error_log /var/log/%web_system%/domains/%domain%.error.log error;
gzip on;
gzip_min_length 1100;
gzip_buffers 4 32k;
gzip_types image/svg+xml svg svgz text/plain application/x-javascript text/xml text/css;
gzip_vary on;
location / {
proxy_pass http://unix:%home%/%user%/web/%domain%/nodeapp/app.sock:/$1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-NginX-Proxy true;
location ~* ^.+\.(%proxy_extentions%)$ {
root %sdocroot%;
access_log /var/log/%web_system%/domains/%domain%.log combined;
access_log /var/log/%web_system%/domains/%domain%.bytes bytes;
expires max;
try_files $uri @fallback;
add_header Pragma public;
add_header Cache-Control "public";
}
}
location /error/ {
alias %home%/%user%/web/%domain%/document_errors/;
}
location @fallback {
proxy_pass http://unix:%home%/%user%/web/%domain%/nodeapp/app.sock:/$1;
}
location ~ /\.ht {return 404;}
location ~ /\.svn/ {return 404;}
location ~ /\.git/ {return 404;}
location ~ /\.hg/ {return 404;}
location ~ /\.bzr/ {return 404;}
include %home%/%user%/conf/web/s%proxy_system%.%domain%.conf*;
}
Este template se aplica cuando la conexión es HTTP. Sólo lo dejamos como compatibilidad ya que no es recomendable utilizar HTTP.
server {
listen %ip%:%proxy_port%;
server_name %domain_idn% %alias_idn%;
error_log /var/log/httpd/domains/%domain%.error.log error;
location / {
proxy_pass http://unix:%home%/%user%/web/%domain%/nodeapp/app.sock:/$1;
location ~* ^.+\.(%proxy_extentions%)$ {
root %docroot%;
access_log /var/log/httpd/domains/%domain%.log combined;
access_log /var/log/httpd/domains/%domain%.bytes bytes;
expires max;
try_files $uri @fallback;
}
}
location /error/ {
alias %home%/%user%/web/%domain%/document_errors/;
}
location @fallback {
proxy_pass http://unix:%home%/%user%/web/%domain%/nodeapp/app.sock:/$1;
}
location ~ /\.ht {return 404;}
location ~ /\.svn/ {return 404;}
location ~ /\.git/ {return 404;}
location ~ /\.hg/ {return 404;}
location ~ /\.bzr/ {return 404;}
include %home%/%user%/conf/web/nginx.%domain%.conf*;
}
Este script se ejecuta cada vez que se selecciona (y guarda) el proxy template NodeJS
. Lo que hace:
NGINX
pueda escribir en el socket.#!/bin/bash
user=$1
domain=$2
ip=$3
home=$4
docroot=$5
rm "$home/$user/web/$domain/nodeapp/app.sock"
runuser -l $user -c "NODE_ENV=production pm2 restart $home/$user/web/$domain/nodeapp/app"
sleep 5
chmod a+w "$home/$user/web/$domain/nodeapp/app.sock"
Si encuentran errores o mejoras pueden contribuir en el repositorio de GitHub.