Seite wählen

NETWAYS Blog

Cloud Management – Teil 1: blkdeviotune/migrate-setmaxdowntime


Viele von euch haben bestimmt schon einmal von OpenNebula oder OpenStack gehört, beiden Plattformen verwenden standardmäßig libvirtd als Hypervisor, da jedoch die darunter liegende Hardware mit unter stark beansprucht werden wird, vor allem im Cloud Bereich, wo mehrerer KVM Instanzen auf einem Virt Host um CPU/Speicher/IO Resourcen konkurrieren, ist es gelegentlich notwendig die einen oder andere Instanz zu bändigen bzw. in die Schranken zu weisen.
Unsere Cloud läuft derzeit noch mit OpenNebula, wir selbst sind sehr zufrieden mit dem Stack, da sich dieser leicht Verwalten lässt und sich auch gut mit unserem Ceph als Backend verträgt, natürlich verwenden wir in unserem Cloud Stack noch andere Tools, diese sind aber nicht Gegenstand dieses Artikels, mehr dazu in späteren Teilen der Serie.
Libvirtd kann mit CLI Tools wie virsh gesteuert werden, auch besteht die Möglichkeit libvirtd über die entsprechende libvirt C API oder durch seine ScriptLanguage Bindings für ruby/python/perl/javascript/etc… anzusprechen bzw. anzusteuern zu können.
So beispielsweise unterstützt man OpenNebula wenn die Migration einer KVM Instanz von Virt Host A zu Virt Host B aufgrund von Last auf der Instanz selbst partout nicht klappen will…

root@virt1: ~ $ virsh migrate-setmaxdowntime --downtime 1800 one-8366
error: Requested operation is not valid: domain is not being migrated (dieser Fehler kommt nur wenn sich die Instanz nich im Status Migrate befindet, ist hier also nur exemplarisch mit abgebildet)

…oder wenn die Instanz AMOK läuft und somit andere Instanzen zu stark beeinträchtigt…

root@virt1: ~ $ virsh blkdeviotune one-8366 vda --live
total_bytes_sec: 62914560
read_bytes_sec : 0
write_bytes_sec: 0
total_iops_sec : 400
read_iops_sec  : 0
write_iops_sec : 0

…dann limitieren wir diese einfach ein bisschen…

root@virt1: ~ $ virsh blkdeviotune one-8366 vda --live 62914560 0 0 200 0 0

…und vergewissern uns nochmal ob unsere neuen IOPs Limits übernommen wurden…

root@virt1: ~ $ virsh blkdeviotune one-8366 vda --live
total_bytes_sec: 62914560
read_bytes_sec : 0
write_bytes_sec: 0
total_iops_sec : 200
read_iops_sec  : 0
write_iops_sec : 0

…ich denke, damit sollte klar sein worauf wir hier abzielen möchten.
In folgenden Teilen dieser Serie werden wir euch noch mehr Tips & Tricks mit auf den Weg geben, die helfen sollen eure Cloud zu bändigen bzw. aufkommende Probleme anzugehen, also bleibt gespannt. 😉

NodeJS HA mit PM2…

ich hatte in Vergangenheit bereits einen Blog Post über NodeJS in Verbindung mit dem NodeJS ‚Cluster‘ Modul geschrieben ( siehe hier ), nun möchte ich euch zeigen wie Ihr eure NodeJS Anwendung auch ohne Programmierarbeit (also als reine Ops Tätigkeit) zum Cluster umfunktioniert und wie ihr diese anschließend verwalten könnt.
nodejs-logo-2015
Legen wir los, wir benötigen hierzu NodeJS, am besten 4.4 LTS und 4 zusätzliche Module/Helper, fangen wir also mit der Installation an…

$ npm install -g pm2
$ npm install -g express-generator
$ mkdir ~/project && cd project
$ npm install --save express
$ npm install --save sleep

…nun generieren wir das Express Anwendungsgerüst…

$ express -f .
   create : .
   create : ./package.json
   create : ./app.js
   create : ./public
   create : ./public/javascripts
   create : ./public/images
   create : ./public/stylesheets
   create : ./public/stylesheets/style.css
   create : ./routes
   create : ./routes/index.js
   create : ./routes/users.js
   create : ./views
   create : ./views/index.jade
   create : ./views/layout.jade
   create : ./views/error.jade
   create : ./bin
   create : ./bin/www
   install dependencies:
     $ cd . && npm install
   run the app:
     $ DEBUG=project:* npm start
$

… danach löschen wir in der Datei app.js alles von Zeile 13 bis 58, wir benötigen nur das Gerüst selbst und ein paar Zeile selbst geschriebenen Code, um diesen kommen wir leider nicht herum, da wir ja auch was sehen wollen.
Fügt nun ab Zeile 14 folgenden Code hinzu…

var crypto = require( 'crypto' );
var util = require( 'util' );
var sleep = require( 'sleep' ).sleep;
function getRandHash() {
    var current_date = (new Date()).valueOf().toString();
    var random = Math.random().toString();
    return crypto.createHash('sha1').update(current_date + random).digest('hex');
}
var myID = getRandHash();
app.get( '/', function( req, res ) {
    sleep( Math.floor( (Math.random() * 3) + 1 ) );
    res.send({
        "ID": myID,
        "PID": process.pid,
        "ENV": process.env
    });
});

…nun starten wir die App über PM2 im Cluster Mode, hier bitte dem Parameter ‚-i‘ nur einen Wert gleich der Anzahl der CPU Cores mitgeben. Dann testen wir das Ganze doch gleich mal…

$ cd project
$ pm2 start bin/www -i 4

…ihr solltet nun folgende Ausgabe zu sehen bekommen…
pm2-start-www-cluster
…ihr könnt eure Anwendung auch auf Fehler überprüfen, dafür gibt euch PM2 einen Parameter ‚logs‘ mit, der wiederum einige Flags kennt. Wenn ihr nur ‚pm2 logs‘ eingebt dann bekommt ihr die Standardausgabe unserer Anwendung zu sehen, wenn ihr zusätzlich das Flag ‚–err‘ mit angebt, bekommt ihr die Standard Fehlerausgabe, was für das Debugging recht hilfreich ist.
pm2-logs-examine
So, nun könnt ihr in eurem Browser mal die Anwendung aufrufen. Der Listener der Anwendung horcht auf dem Port 3000. Ein Aufruf auf http://127.0.0.1:3000 sollte ein JSON liefern, ähnlich zu dieser Ausgabe hier…
pm2-cluster-test1
Da wir in unserem Code eine künstliche Verzögerung eingebaut haben, könnt ihr nun mit 2 direkt Aufeinander folgenden Browser Reload sehen das sich die ID u. PID Werte gelegentlich ändern, dies bedeutet, dass unsere Cluster Anwendung funktioniert.
pm2-logo
Ich hoffe ich konnte euch nun zum Spielen und Experimentieren animieren. PM2 wird von der Firma Kissmetrics als OpenSource Project stetig weiter entwickelt und hat noch so viel mehr zu bieten, ganz findige unter euch werden die Monitoring API zu schätzen wissen und diese vmtl. in Icinga2, Graphite, Kibana und anderen Lösungen integrieren wollen.
Dann bis demnächst 🙂

GitLab – Webhooks

gitlab-webhooks
Da ich vor kurzem für einen unserer namhaften Kunden ein GitLab Setup aufsetzten durfte und dieser sich nun auch Automatische Checkouts seiner Live Branches auf seinen Produktiv Systemen wünschte, habe ich mich dazu entschlossen euch ein bisschen an der Einrichtung dieser Mechaniken teilhaben zu lassen, natürlich nicht im vollem Umfang des Projektes für unseren Kunden, das eine oder andere habe ich für diesen Artikel verständlicherweise leicht abwandeln müssen, das hier gezeigte funktioniert trotzdem, ich habe es selber ausprobiert.
Dann legen wir mal los…
In einem klassischen Git Setup werden die Hooks direkt im Repository im Unterordner hooks abgelegt, hier mal ein Beispiel wie das aussehen kann…

$ MyAwesomeProject/hooks $ ll
-rwxrwxr-x 1 enzo enzo  452 Jun 28 11:46 applypatch-msg.sample*
-rwxrwxr-x 1 enzo enzo  896 Jun 28 11:46 commit-msg.sample*
-rwxrwxr-x 1 enzo enzo  189 Jun 28 11:46 post-update.sample*
-rwxrwxr-x 1 enzo enzo  398 Jun 28 11:46 pre-applypatch.sample*
-rwxrwxr-x 1 enzo enzo 1642 Jun 28 11:46 pre-commit.sample*
-rwxrwxr-x 1 enzo enzo 1239 Jun 28 11:46 prepare-commit-msg.sample*
-rwxrwxr-x 1 enzo enzo 1352 Jun 28 11:46 pre-push.sample*
-rwxrwxr-x 1 enzo enzo 4898 Jun 28 11:46 pre-rebase.sample*
-rwxrwxr-x 1 enzo enzo 3611 Jun 28 11:46 update.sample*

GitLab geht hier allerdings einen anderen Weg, da dieses das hooks Verzeichnis durch einen Symbolischen Link auf System eigene Hooks umlenkt (wie hier Beispielhaft zu sehen ist)…
gitlab-webhooks-art2
…nun sollte man hier auch besser die Finger heraus lassen, da GitLab dieses Verzeichnis für allerlei anderen Mechaniken benötigt, der Weg über die Webhooks ist meiner Meinung nach aber auch flexibler (allein schon wegen der einfachen Anbindung an Web Dienste wie GitHub, Bitbucket, Heroku, etc.).
Nun direkt zum spaßigen Teil, im GitLab benötigen wir einen neuen User ohne irgendwelche besonderen Rechte incl. SSH Public Key, fangen wir mit dem Key selbst an, da der Checkout User keinen besonderen Rechte benötigt außer einen Pull/Fetch zu tätigen, reicht es uns den Key also entsprechend Unprivilegiert zu preparieren (bei der Passwortabfrage bitte nichts angeben, einfach mit Enter bestätigen)…

$ ssh-keygen -t rsa -b 2048 -O clear -O no-agent-forwarding -O no-port-forwarding -O no-pty -O no-user-rc -O no-x11-forwarding -f gitlab.key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in gitlab.key.
Your public key has been saved in gitlab.key.pub.
The key fingerprint is:
4b:f3:ae:60:d4:5d:5e:02:75:64:4c:7f:ce:09:ec:8a enzo@ebony
The key's randomart image is:
+--[ RSA 2048]----+
|          ...+=  |
|           ..o.. |
|            oo. o|
|       . . o.o.oo|
|      . S . .. .o|
|     . . +. .    |
|      o .E..     |
|     . . .       |
|        ...      |
+-----------------+
$ ll
insgesamt 8
-rw------- 1 enzo enzo 1675 Jun 28 11:52 gitlab.key
-rw-r--r-- 1 enzo enzo  392 Jun 28 11:52 gitlab.key.pub

…nun benötigen wir lediglich noch einen User im GitLab, das sollte keine große Herausforderung darstellen, bitte stellt auch sicher das ihr den SSH PublicKey in unserem Beispiel gitlab.key.pub dem User, lasst ihn uns hier der Einfachheit halber checkout nennen zuweist, da die Webhooks mit den Berechtigungen des Standard Apache User www-data läuft (das ist zumindest unter Debian/Ubuntu der Fall, unter Redhat Systemen heißt dieser wwwrun) solltet ihr dem Key als Identifier so etwas wie www-data to gitlab oder ähnliches benennen, damit ihr später ganz einfach den Überblick behalten könnt…
gitlab-webhooks-art4
gitlab-webhooks-art5
…nun müssen wir unserem User checkout lediglich noch in unser Projekt aufnehmen, die Rechte eines Reporter sollten dafür ausreichend sein um Fetch/Pull oder auch Clone zu können, mehr benötigen wir an dieser Stelle nicht…
gitlab-webhooks-art9
…somit sind wir auf schon fast fertig, zumindest was das GitLab Setup selbst betrifft, nun müssen wir lediglich noch die URL unseres Webhook konfigurieren und schon können wir uns dem Hook selber widmen…
gitlab-webhooks-art7
gitlab-webhooks-art8…bitte beachtet auch, das es beim Entwickeln eines Webhooks sehr hilfreich sein kann wenn ihr über den Button [Test Hook] den Push Event einfach mal Manuell triggert, ansonsten würde euer Webhook erst aufgerufen werden wenn ihr wirklich in das Repository Pusht (das geht natürlich auch, legt euch hierzu einfach ein neues Repository zum spielen an).
So nun geht es an den Server der unseren Webhook ausführen soll, ihr benötigt lediglich einen simplen Apache/NginX vHost mit Standard Rechten, man kann selbstverständlich auch ein komplizierteres Deployment bauen aber darum geht es in diesem Artikel nicht (ich nutzte hier den Apache2 mit PHP5, da dieser bereits auf dem Kunden System vorhanden wahr und zusätzliche Scriptsprachen installieren ist meist schlechte Praxis sowie auch unnötiger Aufwand).
Ich habe mich hier für den Standard Pfad des Apache Setups entschieden da der Standard vHost bereits auf diesen zeigt und dieser auch nur aus dem internen Netz bedienbar ist, somit ist sichergestellt das Niemand von außerhalb Unfug treiben kann.
Dort habe ich im DocumentRoot des Apache ein Verzeichnis api erstellt, der volle Pfad lautet hier /var/www/html/api, dort habe ich mein Webhook mit Namen on-push.php abgelegt, dieser hat nun folgenden Inhalt…

object_kind == 'push' ) {
                if( $obj->ref == 'refs/heads/'.BRANCH ) {
                    $allowed = false;
                    foreach( $USERS as $user ) {
                        msg( "permission test ( ".$obj->user_name ." == ". $user." ) ..." );
                        if( preg_match( "/$user/i", $obj->user_name ) ) {
                            msg( $user . " allowed to do updates on refs/heads/".BRANCH." branch" );
                            $allowed = true;
                            break;
                        }
                    }
                    if( $allowed ) {
                        if( $obj->before != "0000000000000000000000000000000000000000" ) {
                            msg( "changing directory to ".PROJECTROOT );
                            chdir( PROJECTROOT );
                            msg( "executing: ". COMMAND );
                            $fh = popen( COMMAND." 2>&1", "r" );
                            $result = fread( $fh );
                            while( !feof( $fh ) ) {
                                msg( rtrim(fgets( $fh, 4096 )) );
                            }
                            pclose( $fh );
                        } else {
                            msg( "it's a empty repository, we'll do nothing at this point" );
                        }
                    } else {
                        msg( "permission denied for ". $obj->user_name );
                    }
                } else {
                    msg( "everybody can push to ". $obj->ref.", but we'll do nothing, please check your project settings" );
                }
            } else {
                msg( "[ ". $obj->object_kind ." ] event handler not yet implemented" );
            }
        } else {
            msg( strtoupper($_SERVER['REQUEST_METHOD'])." from [ ".$_SERVER['REMOTE_ADDR'] ." ] is not allowed, please check your server settings" );
            return_status( ERR );
        }
    } catch( Exception $e ) {
        msg( "======= EXCEPTION BEGIN ========" );
        msg( $e->getMessage() );
        msg( "======= EXCEPTION END ==========" );
        return_status( ERR );
    }
    return_status( OK );
    // ================================ FUNCTIONS ===========================================
    function return_status( $status = ERR ) { header( $status ); }
    function getRequestBody() { return json_decode( file_get_contents('php://input') ); }
    function msg( $message ) { if( $message != null ) file_put_contents( LOGFILE, $message."\n", FILE_APPEND ); }
?>


..damit das nun funktionieren kann, müssen wir nun noch das Projekt Klonen und die SSH Settings anlegen, fangen wir daher direkt mit den SSH Settings an…

# (beginne mit Root Login)
su - -s /bin/bash www-data
mkdir -p .ssh && chmod 0600 .ssh
exit
cp gitlab.key /var/www/.ssh/ && chown www-data. /var/www/.ssh/gitlab.key && chmod 0400 /var/www/.ssh/gitlab.key
su - -s /bin/bash www-data
vi .ssh/config

…die .ssh/config sieht dabei wie folgt aus…

Host gitlab.example.org
    UserKnownHostsFile /dev/null
    StrictHostKeyChecking no
    IdentityFile ~/.ssh/gitlab.key
    User git
    LogLevel VERBOSE

…speichert nun das ganze und Klont eurer Projekt nach /var/www/project1/htdocs

# (beginne mit Root Login)
su - -s /bin/bash www-data
cd /var/www/project1
git clone git@gitlab.example.org:test/number1.git htdocs

…wenn ihr nun Lokal mit eurer Arbeitskopie arbeitet und an dem Punkt seit das ihr alles fertig zuhaben scheint und dann einen Push in euren staging Branch macht, wird GitLab jedesmal den Webhook on-push.php Aufrufen und auf dem Projekt/Web -Server einen Fetch/Pull ausführen lassen, um das Projekt auf den selben Stand wie eure Arbeitskopie zu bringen.
Zugegeben der PHP Handler oben ist keinesfalls perfekt, ich bin auch kein guter PHP Programmierer, genau genommen eigentlich gar keiner ;). Der Hook für das Kunden Projekt umfasst zudem noch mehr Fähigkeiten und würde mit seine zu diesem Zeitpunt ca. 800 Zeilen diesen Artikel in jedem Fall sprengen, aber ich denke das der gezeigte Webhook oben bereits eine gute Ausgangsbasis darstellt, sodass ich euch hoffentlich dazu Animieren konnte, das ganze doch einmal selber nachzubauen.
Zum zweck des Debugging, loggt dieser zur Kontrolle nach /var/www/html/api/on-push.log , hier lohnt also ein Blick.
Noch ein kleiner Hinweis meinerseits, der eine oder andere von euch wird schnell feststellen, das GitLab selber im Admin Panel auch einen Menü Eintrag mit der Bezeichnung [Deploy Keys] bereitstellt, womit sich das oben gezeigte auch umsetzten lässt, aufgrund einiger bedenken bzgl. Sicherheit dieses allerdings nicht immer gewünscht ist, da alle Keys die dort hinterlegt werden automatisch, für allen Projekte (auch für zukünftige) direkt verwendet werden können.
Nützliche Links:

Es trifft mich wie ein Blitz …

Java-Logo
… heute früh meldete Heise Security einen seit ca. neun Monaten bekannten Exploit, der sich als besonders kritisch einstuft.
Zu diesem Zeitpunkt war ich gerade in einem Meeting mit Bernd und bin fast aus allen Wolken gefallen, als Sebastian mit dieser Nachricht in der Tür stand.
Ich bot an dieser Meldung genauer auf den Grund zu gehen, da ich selber begeisterter Java Entwickler bin und mich wie vermutlich auch viele von euch direkt oder eben auch indirekt mit Java und dem erweiterten Stack tagtäglich beschäftige.
Im Netz kursiert seit dem 6. November der folgende Artikel, dessen Autor Herr Stephen Breen, sehr genau, fast schon spielerisch beschreibt, wie dieser Exploit denn genau funktioniert.
Er schreibt dort im wesentlichen über die in Java schon seit Version 1.1 verfügbare ‚Serializable‘ Schnittstelle, die es dem Programmierer ermöglicht fertig instanziierte Java Objekte über das Netzwerk zu senden oder auch von Festspeichern zu lesen. Diese Klasse ist allerdings nur der kleinere Teil des Übels, genauer gesagt ist es die Methode „InvokerTransformer“ der „Apache CommonCollections API“ die hier über die „Java Reflections API“ es einem möglichen Angreifer hier Kinderleicht macht seinen Schadcode auf dem Zielsystem zur Ausführung zu bringen.
Nun könnte man fast meinen das einen das ja nicht wirklich betrifft, da muss ich euch leider enttäuschen und das sogar berechtigterweise.
Folgende Szenarien um das Problem im wesentlichen zu konkretisieren:

  • Servlet Container sowie Application Server verwenden diese API, wie im Artikel zu lesen ist, auch um Cookies und HTTP Header zu lesen. Das ist im ersten Moment noch nicht so kritisch, nur werden heutzutage auch viele Cross-Site-Request-Forgery und andere Angriffe dazu verwendet die Systeme der Wahl erfolgreich zu attackieren.
    Ein Angreifer könnte versuchen über entsprechende HTTP Header mit Cookie Payload ein bösartiges Objekt seiner Wahl einzubetten, das wiederum wenn auf dem Ziel System einmal angekommen, im ersten Moment Zugriff auf die der dort laufenden Anwendung zu Verfügung stehenden Klassen nutzen kann. Hier ist es dann bereits ein leichtes dem Application Server bzw. ServletContainer einen separaten URL Context ( zum Beispiel unter ) mit anderen Funktionen unterzujubeln. Et Voila der Schaden wäre bereits immens.
    Auch ein simpler Download als Injected Java Objekt könnte hier den Rest des Ziel Systems mit entsprechenden Schadcode über eine Art Bootstrap Job infizieren.
    Um dem vorher gehenden Beispiel noch einmal etwas Würze zu verleihen, einen bereits bestehenden URL Context einer großen Bank könnte so manipuliert werden, um Kundendaten abzugreifen. Auch hier wäre der Schaden nicht zu ermessen.
  • Die Software von Drittanbietern ist davon mit unter auch betroffen. Vor allem wenn sie die oben genannte Schnittstelle verwendet oder sogar muss. Hier sollte man schnell Handeln aber auf keinen Fall das Problem ignorieren. Allein anzunehmen das hier ja bereits verschlüsselte Kommunikation stattfindet, schütz vor diesem Exploit nicht.
  • Ich schlage ihnen daher vor, den Rat des Autors zu beherzigen und diese Schnittstelle aus der Apache CommonCollections API zu verbannen, allein die Nutzung der Application einzustellen um anschließend ein komplettes Refacturing der Anwendungen vorzunehmen verursacht vermutlich sehr hohe Kosten und ist hier mit der Gefahr verbunden sich einem weiteren Exploit zu schaffen, jedoch recht schnell geschehen. Schnelle Änderungen schaffen immer wieder neue Bugs und vermutlich auch neue noch unbekannte ZeroDay Vectoren.

Dem einen oder anderen von euch stellen sich hier vermutlich schon die Nackenhaare auf, daher wollen wir nun lieber über die Deeskalation bzw. möglichen Lösungen kümmern, was gilt es nun zu tun!?

  • Evaluieren ob die Apache CommonCollections API auf dem eigenen Systemen existiert.
  • Prüfen ob die eingesetzte Java Software von dieser Gebrauch macht.
    • Wenn dem so ist, sollte die Anwendung aus der DMZ entfernt werden, das blockieren der von außerhalb erreichbaren Ports genügt im ersten Moment bereits.
    • Wenn dem nicht so ist, dann Schwein gehabt.
  • Sofern die betroffenen Anwendungen ermittelt werden konnten, sollten folgende Punkte abgearbeitet werden.
    • Test der Anwendung in isolierter Umgebung ( Docker kann hierbei recht hilfreich sein ) mit der Apache CommonCollections API. Wobei vor Beginn die org/apache/commons/collections/functors/InvokerTransformer.class bereits aus der API entfernt sein sollte.
      Wenn es hier nichts zu beanstanden gibt und die Application auch ohne diese Schnittstelle zu laufen scheint, kann diese wieder in den Betrieb überführt werden.
    • Sollte die Anwendung nicht korrekt funktionieren, wird empfohlen diese unter Vorbehalt nur den eig. Mitarbeitern zur Verfügung zu stellen, sollte es sich bei der Anwendung allerdings, um eine des Kunden handeln, sieht es da schon etwas schwieriger aus, wobei sich vmtl. kein Kunde den damit einhergehenden Gefahren gerne ausgesetzt sieht.
      Hier sollte dem Kunden angeraten werden, die Application bis zu einem erfolgreichen Security Audit vom Netz zu nehmen.

Weitere Lösungsansätze sind derzeit noch nicht bekannt, wir versuchen euch aber entsprechend auf dem Laufenden zu halten.
Nützliche Links:

Link Inhalt
http://www.heise.de/security/meldung/Zero-Day-Alarm-fuer-viele-Server-mit-Java-2913605.html Heise Security Artikel
http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/ der Exploit im Detail
Javadoc zum betroffenen Interface
https://github.com/frohoff/ysoserial das daraus entwickelte Framework für Penetration Tests
https://www.cvedetails.com/vulnerability-list/vendor_id-93/product_id-19117/Oracle-JRE.html Java CVE – Übersicht

Improved NodeJS Events durch Cluster …


… jeder von euch kennt das Problem, der Kollege kommt um die Ecke und möchte wenn möglich sofort, dass ein neues geiles Tools mit in den Infra Stack aufgenommen wird, gesagt getan.
In diesem Bespiel eine besonders cooles in NodeJS geschriebenes Dashboard für… lass es hier ein DockerManagement Tool sein. Dabei stößt man bei der großen Akzeptanz durch die Kollegen, auch auf einmal auf noch ganz andere Probleme (Stichwort: Performance Bottlenecks wie DDoS durch eigene Leute).
Hierbei könnte euch das NodeJS Modul Cluster behilflich sein, hier ist die Doku zu finden.
Cluster macht dabei nicht viel, es erweitert den NodeJS Stack insbesondere das Event Driven Modell von NodeJS ( so Arbeitet Google(s) v8 Engine ) um eine Art Interprocess Schnittstelle zu forked Child NodeJS Prozessen, die sich dabei Filesystem Elemente/Typen wie Sockets, Filedeskriptoren etc. teilen können und somit gemeinsam auf diesen operieren können.
Hier wollen wir eine schon bestehende Express Framework Anwendung mit einigen wenigen Zeilen Code um die Fähigkeit erweitern, dem System zur Verfügung stehenden CPU Cores effizienter ausnutzen zu können, damit sich die Events schneller abarbeiten lassen.
Ich werde hier allerdings nicht Express selber beschreiben, sondern setzte hier voraus das der Leser dieses Framework kennt. Wenn nicht könnt ihr die Infos unter diesem Link abrufen, so let’s beginn …

$ sudo -i                                  # <- da meine Workstation ein Ubuntu ist sollten wir zumindest was die Essentials sind kurz zu 'root' werden um diese Problemlos installiert zu bekommen
$ npm install express -g                   # <- installiert uns das Express Framework samt CLI Tools
express@4.13.3 /usr/lib/node_modules/express
├── escape-html@1.0.2
├── merge-descriptors@1.0.0
├── cookie@0.1.3
├── array-flatten@1.1.1
├── cookie-signature@1.0.6
├── utils-merge@1.0.0
├── content-type@1.0.1
├── fresh@0.3.0
├── path-to-regexp@0.1.7
├── content-disposition@0.5.0
├── vary@1.0.1
├── etag@1.7.0
├── serve-static@1.10.0
├── range-parser@1.0.2
├── methods@1.1.1
├── parseurl@1.3.0
├── depd@1.0.1
├── qs@4.0.0
├── on-finished@2.3.0 (ee-first@1.1.1)
├── finalhandler@0.4.0 (unpipe@1.0.0)
├── debug@2.2.0 (ms@0.7.1)
├── proxy-addr@1.0.8 (forwarded@0.1.0, ipaddr.js@1.0.1)
├── send@0.13.0 (destroy@1.0.3, statuses@1.2.1, ms@0.7.1, mime@1.3.4, http-errors@1.3.1)
├── type-is@1.6.9 (media-typer@0.3.0, mime-types@2.1.7)
└── accepts@1.2.13 (negotiator@0.5.3, mime-types@2.1.7)
$ npm install express-generator -g
/usr/bin/express -> /usr/lib/node_modules/express-generator/bin/express
express-generator@4.13.1 /usr/lib/node_modules/express-generator
├── sorted-object@1.0.0
├── commander@2.7.1 (graceful-readlink@1.0.1)
└── mkdirp@0.5.1 (minimist@0.0.8)
$ exit                                     # <- ab hier geht es ohne 'root' Rechte weiter
$ mkdir cool-app && cd cool-app            # <- Projekt Verzeichnis anlegen und in dieses wechseln
$ pwd                                      # <- vergewissern das wir uns auch in diesem wirklich befinden
/home/enzo/nodejsProjects/cool-app
$ express --git .                          # <- wir provisionieren uns unser Projekt from Scratch, dieses ist somit direkt Lauffähig ohne das wir einen Zeile Code schreiben müssen, haben hier leider keine Zeit zu 😉
   create : .
   create : ./package.json
   create : ./app.js
   create : ./.gitignore
   create : ./public
   create : ./routes
   create : ./routes/index.js
   create : ./routes/users.js
   create : ./views
   create : ./views/index.jade
   create : ./views/layout.jade
   create : ./views/error.jade
   create : ./bin
   create : ./bin/www
   create : ./public/javascripts
   create : ./public/images
   create : ./public/stylesheets
   create : ./public/stylesheets/style.css
   install dependencies:
     $ cd . && npm install
   run the app:
     $ DEBUG=cool-app:* npm start
$

Ab hier können wir das ganze mal kurz testen, um uns zu vergewissern das die Anwendung auch läuft ...

$ npm start
> cool-app@0.0.0 start /home/enzo/nodejsProjects/cool-app
> node ./bin/www

Nicht wundern die Applikation bleibt im Vordergrund hängen, was ist Ok ist, somit können wir diese schneller terminieren und relaunchen. Weiter geht es mit einem Test im Browser, die Anwendung lauscht standardmäßig am Port 3000, somit rufen wir hier einmal http://localhost:3000 auf und lassen uns überraschen was da so schönes kommt ...

Geil es läuft also, nun gut machen wir das ganze mit Cluster noch skalierbarer, let's go ...
Öffnet hierzu im Projekt Verzeichnis einmal die bin/www Datei, diese stellt laut package.json unseren Eintrittspunkt in die Anwendung dar, wir kopieren uns diese als Backup weg bevor wir anfangen hier Änderungen vorzunehmen.
Kommentiert bitte folgende Zeilen einmal aus ...

/**
 * Get port from environment and store in Express.
 */
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
 * Create HTTP server.
 */
var server = http.createServer(app);
/**
 * Listen on provided port, on all network interfaces.
 */
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

... nun fügt ihr ab Zeile 10 bitte folgendes hinzu ...

// cluster requirements
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
if( cluster.isMaster ) {
  for( var i = 0; i < numCPUs; i++ ) {
    cluster.fork();
  }
  cluster.on( 'listening', function( worker ){
    console.log( 'worker ' + worker.process.pid + ' is now listen for incoming connections ... ' );
  });
  cluster.on( 'exit', function( worker, code, signal ) {
    console.log( 'worker ' + worker.process.pid + ' died' );
  });
} else {
    // Workers can share any TCP connection
    // In this case it is an HTTP server
    /**
    * Get port from environment and store in Express.
    */
    var port = normalizePort(process.env.PORT || '3000');
    app.set('port', port);
    /**
    * Create HTTP server.
    */
    var server = http.createServer(app);
    /**
    * Listen on provided port, on all network interfaces.
    */
    server.listen(port);
    server.on('error', onError);
    server.on('listening', onListening);
}

.. jetzt sollte das ganze auch schon funktionieren, probieren wir es doch einmal aus.

$ npm start
> cool-app@0.0.0 start /home/enzo/nodejsProjects/cool-app
> node ./bin/www
worker 31979 is now listen for incoming connections ...
worker 31974 is now listen for incoming connections ...
worker 31985 is now listen for incoming connections ...
worker 31996 is now listen for incoming connections ...
GET / 304 840.784 ms - -
GET /stylesheets/style.css 304 31.416 ms - -

Jetzt könnte man denken das sich hier nichts geändert hat, dem ist aber nicht so, der Beweis ist in der Prozess Tabelle zu finden, gucken wir doch kurz einmal hinein ...

$ ps auxf | grep node
enzo     31969  0.1  0.4 901264 33004 pts/7    Sl+  14:47   0:00          |   |       \_ node ./bin/www
enzo     31974  0.1  0.4 901256 32324 pts/7    Sl+  14:47   0:00          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www
enzo     31979  0.4  0.6 946192 47560 pts/7    Sl+  14:47   0:01          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www
enzo     31985  0.1  0.4 902668 32736 pts/7    Sl+  14:47   0:00          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www
enzo     31996  0.1  0.4 901256 32168 pts/7    Sl+  14:47   0:00          |   |           \_ /usr/bin/nodejs /home/enzo/nodejsProjects/cool-app/bin/www

Whoop whoop, 4 neue NodeJS Prozesse die hier ihre Arbeit verrichten, somit lassen sich auch die beschäftigsten Web Anwendungen wieder beschleunigen.
Das ist aber nur der Anfang wie sich NodeJS Applications aller Art verbessern lassen, für weiter Themen habe ich heute allerdings keine Zeit daher heben wir uns den Rest einfach für ein anderes mal auf.
Ich wünsche euch hiermit noch viel Spaß, ich hoffe ihr habt nun mehr Lust auf NodeJS bekommen.