20.3.2019
Desarrollamos un VestaCP template para solucionar la vida de todos los desarrolladores que quieran desplegar NodeJS web apps en este panel de control de hosting.
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.
Corriendo varias web app en un mismo servidor.
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:
- Desplegar la web app
app1
en el servidor. - Iniciar
app1
escuchando un puerto TCP desocupado, por ejemplo3000
- Modificar el archivo /home/admin/conf/web/dominio1.com.nginx.conf y modificar el
proxy_pass http://localhost:3000
- Desplegar la web app
app2
en el servidor. - Iniciar
app2
escuchando un puerto TCP desocupado, por ejemplo3001
- Modificar el archivo
/home/admin/conf/web/dominio2.com.nginx.conf
y modificar elproxy_pass http://localhost:3001
- Si hay más apps, repetir pasos 1 al 3.
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.
Nuestro enfoque a VestaCP y Node.JS: Unix Sockets y PM2.
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.
Implementación.
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:
- Todas las apps se tienen que desplegar en el directorio
~/web/dominio.com/nodeapp/
de cada usuario y dominio. Por ejemplo: el usuarioadmin
quiere ejecutar una web con el nombresuperwebapp.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 denodeapp
. - El entry point de la app se tiene que llamar
app.js
y estar en la raíz denodeapp
(nodeapp/app.js). - Por último, la aplicación tiene que utilizar un UNIX socket llamado
app.sock
y que se encuentre en la raíz denodeapp
(nodeapp/app.sock). A continuación dejamos un ejemplo de un archivo de configuración básico para un stackExpressJS
yMongoDB
. En modo desarrollo utiliza el puerto TCP3000
y en producción el archivoapp.sock
. - Finalmente, subir los archivos NodeJS.tpl, NodeJS.stpl y NodeJS.sh a
/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];
Template VestaCP NodeJS.
Este template para NGINX
tiene las siguientes caracteristicas:
- Redirección de HTTP a HTTPS.
- Soporte para HTTP/2 (acá está la guía completa).
- Proxy al socket de la app de NodeJS.
- Activado compresión GZip.
- Optimizaciones para mejorar los scores en Google PageSpeed Insights, GTMetrix y Pingdom Tools.
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*;
}
NodeJS.tpl
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*;
}
NodeJS.sh
Este script se ejecuta cada vez que se selecciona (y guarda) el proxy template NodeJS
. Lo que hace:
- Eliminar el socket si existe.
- Reiniciar la aplicación, si esta apagada la enciende.
- Esperar 5 segundos y darle permiso de escritura para que
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"
Descargas y repo.
Si encuentran errores o mejoras pueden contribuir en el repositorio de GitHub.