Node.js in produzione, grazie Passenger!

9 dicembre 2013

Fabrizio Soppelsa

- RHCE, Autore di Linux&C, Autore di Linux Journal

Node.js è la tecnologia web più rivoluzioniaria degli ultimi tempi: essa capovolge drasticamente il concetto di programmazione web e l’approccio al protocollo HTTP.

nodejs

Node.js non è un framework, ma una piattaforma velocissima basata su Javascript e il motore V8 di Google, orientata agli eventi. È nata a partire dall’intuizione che con la programmazione asincrona Javascript lato server è possibile superare le limitazioni prestazionali dell’I/O, la cui lentezza rappresenta il collo di bottiglia principale che influisce sui tempi di risposta degli applicativi web.

Sebbene sia possibile sviluppare teoricamente pressoché ogni cosa con Node, l’uso più tipico di Node è legato alla realizzazione di applicazioni web: è particolarmente interessante quale base per quei progetti che devono supportare enormi carichi di request, altissimi livelli di concorrenza ed eventualmente le websocket (il probabile HTTP del futuro).

A differenza di Apache, che per ogni HTTP request alloca un processo (o un thread) con la relativa memoria, e che ha uno spazio limitato di massimo numero di processi servibili e scala, su grandissime dimensioni, solo con l’aggiunta di nuovi nodi e bilanciatori, Node usa un modello a event-loop, basato sulle callback Javascript, che serve sì una request alla volta, ma in un ciclo infinito e in modo asincrono molto efficiente.

Per chiarire ecco una banale e paradossale metafora. In una città ci sono due uffici anagrafe: uno sincrono e uno asincrono. Un bel mattino, nell’ufficio anagrafe sincrono (Apache) arrivano 100 utenti. Ci sono 10 impiegati, quindi gli utenti si accodano in modo più o meno bilanciato dietro ai 10 sportelli. Quando una persona deve compilare un modulo allo sportello, lo compila lasciando gli altri della sua coda ad aspettare dietro, finché non ha finito. Arrivano altri 100 utenti che si accodano, quindi l’ufficio decide di aggiungere altri 10 sportelli e impiegati. Arrivano altri 500 utenti e il capo dell’ufficio sincrono si gratta la testa e, per soddisfare in tempi ragionevoli le richieste, decide di aggiungere altri 50 impiegati. La cosa inizia a costare parecchio, senonché, a un certo punto arrivano altri 1000 utenti e nell’ufficio anagrafe non ci stanno più fisicamente altri sportelli. La gente che rimane di fuori ad aspettare al freddo si lagna. Quindi a fianco viene aperto un secondo ufficio anagrafe (il secondo nodo Apache), e davanti vengono impiegate delle persone che smistano la gente in un ufficio o nell’altro (il Bilanciatore). Nell’ufficio asincrono (Node), invece, c’è un solo impiegato e un solo sportello. Arrivano 100 utenti, che si mettono in fila. Quando una persona deve compilare un modulo, si mette un attimo da parte, e mentre lo compila, ne viene servita subito un’altra. Chi compila il modulo poi ritorna nella fila. Arrivano altri 100 utenti, e contemporaneamente si arriva ad avere decine di persone sui tavolini che compilano moduli mentre la fila va avanti. Arrivano altri 500 cittadini, e la coda si allunga, ma molte richieste sono già state evase oppure la gente sta compilando i moduli. Man mano, la coda si smaltisce. Ovviamente la fila in Node è più ordinata di un pubblico di italiani allo sportello!

Ora, gli sportellisti sono i processi in uso sul sistema, lo spazio fisico occupato dagli sportelli è la memoria del sistema, mentre i moduli da compilare sono le operazioni di I/O. Apache consuma un sacco di processi, di spazio in memoria (in modo esponenziale alla crescita degli utenti), ed è lento a gestire l’I/O. Node usa un processo unico, uno spazio di memoria fisso, ed è più efficiente a gestire l’I/O.

In questo articolo vediamo come si installa un ambiente server per Node.js su Red Hat Enterprise Linux.

Prima di tutto, per installare un server Node, bisogna installare Node (e raccomando anche npm, il gestore pacchetti Node) dai repository EPEL:

# curl -O http://download-i2.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
# rpm -ihv epel-release-6-8.noarch.rpm
# yum install nodejs npm

Un metodo valido e tra gli standard industriali maggiormente riconosciuti per mettere in produzione un’applicazione web Node è lanciare l’applicazione come istanza standalone ed installare un reverse proxy che redirige verso e da questa istanza. Quale reverse proxy io consiglio Nginx per due motivi: primo, perché funziona e combacia con i benefici di Node, basandosi su un event loop a processo singolo e a memoria fissa; secondo, perché in una configurazione più ottimizzata può agevolmente servire da sé i contenuti statici e interrogare l’applicativo solo per i contenuti dinamici.

È possibile lanciare l’istanza standalone direttamente con il comando node myserver.js, ma se questo va bene soprattutto in ambiente di sviluppo, non è la scelta più appropriata per un ambiente di produzione, dove è richiesta maggiore affidabilità, robustezza e tolleranza agli errori. Per questo, è possibile usare Passenger per eseguire l’istanza dell’applicativo. Installiamo Passenger Standalone e Passenger per Nginx:

# yum install http://passenger.stealthymonkeys.com/rhel/6/passenger-release.noarch.rpm
# yum install passenger-standalone passenger-nginx

Adesso posizioniamo la nostra applicazione web Node (kolobok) in una document root come /srv/www/kolobok. Nel caso, mi riferisco a un’applicazione basata su Express.js, che è un framework per lo sviluppo rapido di applicativi Node e la cui istanza ha i file pubblici nella directory public/ e che si lancia a partire dal suo file di server: app.js.

Entrando nella documentroot ed eseguendo Passenger standalone in modalità production, lanciamo l’istanza sulla porta 3000:

# passenger start -e production
=============== Phusion Passenger Standalone web server started ===============
PID file: /srv/www/kolobok/passenger.3000.pid
Log file: /srv/www/kolobok/passenger.3000.log
Environment: production
Accessible via: http://0.0.0.0:3000/

È adesso sufficiente creare un virtual host di Nginx in /etc/nginx/conf.d/kolobok.conf come il seguente con reverse proxy verso l’istanza in ascolto sulla porta 3000, e poi riavviare Nginx:

server {
        listen       80;
        server_name  kolobok.com;
        root         /srv/www/kolobok/public;

        location / {
              proxy_set_header X-Real-IP $remote_addr;
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
              proxy_set_header Host $http_host;
              proxy_set_header X-NginX-Proxy true;

              proxy_pass http://127.0.0.1:3000;
              proxy_redirect off;
        }
}

Ricordiamoci infine di impostare correttamente iptables in /etc/sysconfig/iptables, per bloccare la porta 3000 tranne che da localhost, e di impostare tramite qualche script in rc.local l’avvio dell’istanza di standalone, in modo che la configurazione sopravviva al riavvio.

Benvenuti in Node!

Post Scriptum. Chi è Kolobok? http://www.youtube.com/watch?v=MlqDTExvs3g