Skip to content

hkonitzer/devcampka2016-hko

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

devcampka2016-hko

Session "Performance Monitoring von NodeJS Web-Apps mit dem ELK Stack" im DevCamp Karlsruhe 2016

Worum geht es?

Die Session will zeigen, wie man eine in NodeJS implementierte Web-App/ -Server mit einfachen Mitteln via dem Elastic Stack montioren kann. Dabei geht es unter anderem, um die Aufzeichnung der Antwortzeiten sowie der Anzahl der Requests.

Als Demo dient eine einfache REST-API mit drei Endpunkten die JSON zurückliefert. Anzahl und Antwortzeiten dieser API werden "near realtime" via Logstash an einen Elasticsearch Index geliefert und mit Kibana visualisiert. Um in der Session Last auf der API zu erzeugen, wird das Tool "Vegeta" verwendet.

Vorraussetzungen

Downloads

1. Schritt

Branch: init_expressjs

Installieren des ExpressJS Frameworks mit dem integrierten Generator

Damit ist ein einfacher HTTP Server aufgesetzt, der eine (zunächst) leere Webseite unter / ausgibt.

2. Schritt

Branch: init_demoapi

Implementierung einer API zur Demonstration. Als Basis dienen Fragen und Antworten aus dem Kartenspiel "Cards Against Humanity" Es wird eine neue Route "/api" zugefügt, die die Endpunkte /api/answer, /api/question und /api/pick bereitstellt. Die Endpunkte wählen jeweils einen zufälligen Datensatz (Karten aus dem Spiel) aus und liefern ihn als JSON zurück.

Die Default Startseite ("Homepage") wird angepasst, so dass alle drei API Requests im Browser ausprobiert werden können.

Die API wird als sog. Route in cards.js angelegt und enthält die drei möglichen Endpunkte, die sich die Daten aus einem Array holt.

// API endpoints - requests goes here
router.get('/:endpoint', function(req, res, next) {
    if (req.params.endpoint === 'question') {
        res.status(200).send({ question: question() });
    } else if (req.params.endpoint === 'answer') {
        res.status(200).send({ answer: answer() });
    } else if (req.params.endpoint === 'pick') {
        res.status(200).send(pick());
    } else {
        res.status(404).send({ error: 'Not found' })
    }
    next();
});

Die neuen Routen werden in der app.js zugefügt:

app.use('/api', apiRoutes); 

Der Rest der generierten App bleibt zunächst unverändert.

3. Schritt

Branch: 3_implement_monitoring

Elastic Stack einrichten

Zunächst wird der Elastic Stack gestartet. Nach dem Download der drei Pakete (Elasticsearch, Logstash und Kibana) einfach die Archive entpacken und im jeweiligen /bin Verzeichnis aufrufen. In der Reihenfolge muss zunächst Elasticsearch und dann Kibana gestartet werden - Kibana wird dann automatisch Verbindung zum nun laufenden lokalen Elasticsearch Server aufnehmen. Logstash hingegen muss zunächst konfiguriert werden.

Logstash konfigurieren

Logstash benötigt eine sog. Pipeline-Konfiguration siehe Dokumentation Konfiguriert wird ein "Input" und ein "Output":

input {
     tcp {
        codec => "json"
        port => "3515"
     }
}

Als "Input" wird das TCP-Plugin via JSON Code benutzt, beides wird mitgeliefert. Es wird lediglich noch der Port konfiguriert, auf dem Logstash lauschen soll

output {
    stdout {
        codec => json
    }
    elasticsearch {
        hosts => [ "localhost:9200" ]
    }
}

Als "Output" wird zunächst stdout für Debugging-Zwecke angelegt, damit wird jeder Request an Logstash auf der Konsole gespiegelt. Dann wird das Elasticsearch Plugin (ebenfalls im Lieferumfang) benutzt, bei dem man nur Adresse und Port der zu benutzenden Elasticsearch Installation angeben muss (hier stehen die Default Werte aus dem Elasticsearch Paket)

Die Konfiguration findet sich hier pipeline.conf

Nun kann auch Logstash gestartet werden.

Implementation in die ExpressJS Welt

Zunächst wird ein Handler erzeugt, der die Nachrichten zusammen baut und an Logstash versendet: logstash.js

Dieses Modul übernimmt das Aufbauen der Verbindung, das Zusammensetzen der Nachricht (also des Log-Eintrages) und das Versenden desselben.

Jeder Eintrag wird als JSON versendet und kann beliebige Felder enthalten, hier werden folgende verwendet:

  • request_start: Startzeitpunkt des Requests
  • request_method: Die Methode, hier gibt es nur GET
  • request_uri: Der Pfad
  • response_statuscode: Der Statuscode, mit dem der Request beantwortet wurde
  • user_agent: Der User-Agent des Clients
  • duration: Die Dauer des Requests in der Verarbeitung
  • host: Der Quell-Host
  • type: Bezeichnet die Herkunft und kann beliebig sein, nützlich zum Filtern

Logstash selbst fügt noch weitere Felder hinzu, wie bspw. @timestamp (Zeitpunkt des Empfangs der Nachricht in Logstash).

Alle Felder bis auf "duration", enthalten statische Informationen die die Umgebung (host) oder ExpressJS selbst bereitstellt (request_method etc.).

Die "duration" wird selbst berechnet - Dazu wird zunächst beim Start des Requests die Zeit aufgenommen und am Request-Objekt zwischen gespeichert. Zu diesem Zweck wird eine einfache, sog. Middleware für ExpressJS definiert: (eingesetzt in app.js)

app.use(function(req, res, next) {
    req.hrstart = process.hrtime();
    next();
});

Dieser Teil wird nun bei jedem Request ausgeführt und holt die aktuelle Zeit ("high-resolution real time", siehe process.hrtime) und speichert sie am übergebenen Request-Object.

Sofern die Response gesendet (beendet) wird, kann nun die Dauer ermittelt werden:

    var hrend, duration;
    hrend = process.hrtime(req.hrstart);
    duration = (hrend[0]*1000) + (hrend[1]/1000000);

Das Modul logstash.js startet die Verbindung und stellt eine "send"-Methode bereit, die ebenfalls als ExpressJS Middleware benutzt werden kann (da ihre Signatur die benötigten Variablen "Request", "Response" und "Next" enthält).

In dieser Methode wird die Nachricht zusammengebaut und versendet. Es wird dazu auf das Event "finish" der "Response" gewartet (relevant für die Ermittlung der Dauer "duration"):

send: function(req, res, next) {
    res.on('finish', function() {
        var message = new Message(req, res);
        _send(message);
    });
    next();
}

Dann wird diese Middleware für die API-Routen in der app.js bekannt gemacht:

app.use('/api', [logstash.send, apiRoutes]); 

Die Middleware kann hier an erster Position (im Array) stehen, da sie sowieso auf das "finish" Event der "Response" wartet.

Jetzt wird bei jedem Request gegen die /api eine Nachricht an Logstash gesendet.

4. Schritt

Branch: 4_visualize

Wir erzeugen Last auf den API-Endpunkten mit Vegeta, einem "load testing tool".

Damit die Antwortzeiten varieren, wird eine künstliche Aktivität in der API simuliert: Ein simples .setTimeout warten eine zufällige Zahl an Millisekunden bis die Antwort gesendet wird. Ausserdem wird in etwa jeder 10. Antwort ein Fehler simuliert, beim mit HTTP Statuscode 500 geantwortet wird.

// API endpoints - requests goes here
router.get('/:endpoint', function(req, res, next) {
    setTimeout(function() { // simulate i/o (e.g. database) activity
        var statusCode;
        if (Math.random() <= 0.1) { // simulate an error every 10th request
            res.status(500).send({ error: 'Internal error' });
        } else {
            if (req.params.endpoint === 'question') {
                res.status(200).send({ question: question() });
            } else if (req.params.endpoint === 'answer') {
                res.status(200).send({ answer: answer() });
            } else if (req.params.endpoint === 'pick') {
                res.status(200).send(pick());
            } else {
                res.status(404).send({ error: 'Not found' });
            }
        }
        next();
    }, Math.floor(Math.random()*250));
});

Vegeta bekommt die Ziele, also die API-Endpunkte via Konfiguration als Text-Datei und wird mit einer vorgegebenen Dauer (Parameter "duration") und maximalen Anzahl Anfragen pro Sekunde (Parameter "rate") gestartet. Vegeta erzeugt beim Ausführen einen Report, der durchgereicht und am Ende als Text angezeigt wird.

vegeta.exe attack -targets="docs\vegeta\targets.txt" -duration=300s -rate=1000 | tee results.bin | vegeta report

Sofern alle Dienste gestartet wurden, können die einlaufenden Requests in Kibana betrachtet und aufbereitet werden.

About

Session Dev Camp Karlsruhe 2016

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published