Daemon Watchdog Daemon

Meteor / Node.js und DJB’s Daemontools

TL;DR: Hier das Repository mit der Konfiguration, die ich für Node.js / Meteor geschrieben habe. Simpel — wie ich das mag!


Annie43 / photocase.com

Wie betreibt man Node.js Applikationen am besten produktiv? Node.js ist ein Application Server, das heisst die Applikation wird zusammen mit dem Server (und zusätzlichen Modulen) entwickelt. Wenn mehrere unterschiedliche Node.js Web-Apps auf einem Server laufen sollen, muss auch Node.js mehrere male laufen — anders als zB bei PHP auf einem klassischem LAMP Stack.

Ein weiterer Unterschied zu PHP-Anwendungen besteht im Verhalten bei Fehlern: Ein LAMP-Server läuft in der Regel weiter, auch wenn er ein fehlerhaftes PHP-Skript ausgeführt hat — und dieses vielleicht abbrechen musste. Eine fehlerhafte Node.js-Anwendung stoppt alle Serverfunktionen.

Dass ein Dienst in so einem Fall crasht, anstatt weiter zu laufen, mag für Leute, die den Computer hauptsächlich zum Surfen und eMail Schreiben benutzen ein “Gschmäckle” haben — für mich ist es ein Feature (Siehe Candea und Fox: Crash-Only Software). Ich habe lieber einen Dienst, der bei einem unvorhergesehenem Zustand die Reißleine zieht, als einen, der einfach weiter läuft und im Hintergrund vielleicht die Datenbank korrumpiert.

Um trotzdem immer verfügbar zu sein, braucht ein solches Setup einen Wachhund, der sich darum kümmert, dass der Application-Server immer läuft. In komplexeren Systemen muss sich der Wachhund auch um die anderen benötigten Systembestandteile (Datenbank, Cache) kümmern. Sollte sich ein Dienst aufgrund eines unvorhergesehenen Fehlers selbst beenden, startet der Wachhund den Dienst neu.

Im Zusammenhang mit Node.js und Meteor wird für diese Aufgabe immer wieder auf Forever oder auch Upstart und Monit verwiesen.

  • Forever ist selbst in Node.js geschrieben. Bisher ist Forever wohl nur für Node.js-Anwendungen geeignet; Ein Minuspunkt für uns. Viel wichtiger ist für mich allerdings, dass Forever mich schon hat hängen lassen: Forever meint, der Dienst läuft, dabei läuft er nicht — oder anders herum, beides schon gehabt. Außerdem bringt Forever eine lange Liste an Abhängigkeiten mit. Dafür, was es machen soll, braucht Forever verdächtig viel Code: Ein
    du -s ~/apps/lib/node_modules/forever

    auf das Forever-Verzeichnis sagt mir, dass die aktuelle Version 8,8 MB Plattenplatz benötigt. Kein Wunder, denn Forever lädt bei der Installation 55 Pakete herunter (WTF?!). Die hübschen Farben des Terminal-Outputs sind da leider nur ein kleiner Trost.

  • Monit hat sich bei der ersten Konfiguration angefühlt als wär’s in Visual Basic geschrieben. Die Syntax des Konfigurationsfiles ist extrem kompliziert, und auch wenn ich Features wie “Monitoring eines HTTP Services” super finde und das der Grund ist, warum ich Monit zuerst benutzen wollte — Ich trau’ Monit nicht.
  • Upstart ist Ubuntu-Only. Vielleicht ist es auch auf Debian Gnu/Linux zu installieren, aber das ist zu wenig. Upstart ist nicht leicht einzurichten wenn man keine Root-Rechte auf dem Server hat. Und Upstart ist noch ziemlich neu, und in den letzten Versionen wurde immer noch viel verändert.
  • Die Daemontools von DJB schrecken zuerst vielleicht etwas ab — die Benutzung der Tools mag ungewohnt sein, wenn man bisher nur mit den gängigen Linux-Distributionen gearbeitet hat. Aber Daniel J. Bernstein ist für die Zuverlässigkeit seiner Software bekannt. Siehe beispielsweise QMail oder DJBDNS mit der dazu gehörigen Security Guarantee. Die Konfiguration passiert über ein einfaches Key/Value Schema: Eine Konfiguration besteht aus einem Verzeichnis, der Dateiname ist der Key, die erste Zeile der Datei der Value. Das ist sehr einfach zu parsen, d.h. es schleichen sich weniger leicht Fehler in den Parser-Code ein. Außerdem funktionieren die Daemontools auch ohne Root-Zugang — oft eine Erleichterung. Und nicht zuletzt: Die Daemontools laufen auf allen halbwegs unixoiden Systemen. Sollten wir uns entscheiden, zwecks der Extra-Coolness (d.h. wegen den coolen Solaris Containern (Zones), ZFS und DTrace) unsere Node.js Instanzen auf OpenSolaris oder SmartOS zu hosten, müssen wir den kompletten Stack (Node.js, MongoDB, evtl Memcached oder Nginx…) zwar neu kompilieren; Das sollte es dann aber auch gewesen sein. Dazu ein ander mal mehr…

Ich hab mich für die Daemontools entschieden. Wie sich heraus stellt passen Node.js und die Daemontools hervorragend zusammen: Node.js fork()ed nicht in den Hintergrund, Logging passiert — wenn man kein Logging-Paket (wie Winston oder Log4Js) einsetzt — auf STDOUT und STDERR, die Konfiguration kann leicht über Umgebungsvariablen angepasst werden (Siehe NConf oder app.settings.env für Express-Nutzer).


Tutorial: Daemontools konfigurieren für Node.js

Die Daemontools werden über eine Verzeichnishierarchie konfiguriert. Jeder Dienst bekommt ein Unterverzeichnis in einem Dienste-Verzeichnis:

mkdir -p service/nodejs

In das Verzeichnis legt man ein Shell-Script mit dem Dateinamen “run”, das die richtige Umgebung für den Daemon herstellt (Umgebungsvariablen, UID/GID setzen etc.) und den Daemon im Vordergrund startet. Mittels “exec” übernimmt Node.js den Shell-Prozessraum und kann direkt Signale empfangen.

  • “exec 2>&1″ leitet STDERR nach STDOUT um, so dass später beides in der selben Logdatei landet.
  • “node ../../bundle/main.js” startet meine Node-Applikation (hier eine per “meteor bundle” erstellte Meteor-Anwendung).
  • Das “envdir” Tool, das oben im Script erwähnt wird, setzt die Umgebungsvariablen, die es nach DJB-Schema aus den Dateien im service/nodejs/env Verzeichnis liest.
chmod +x service/nodejs/run

macht das ‘run’-Script ausführbar.

Die folgenden Zeilen konfigurieren das ‘envdir’-Tool. Dieses wird uns später beim Start unseres Dienstes die Umgebungsvariablen setzen.

Um die Ausgaben des Dienstes zu speichern bietet sich das “Multilog” Tool an. Multilog ist ebenfalls Teil der Daemontools. Ähnlich zu dem eigentlichen Dienst legt man ein weiteres Dienst-Konfigurationsverzeichnis für Multilog an:

mkdir service/nodejs/log

und legt auch darin ein Shell-Script mit dem Namen “run” an:

  • “t” fügt einen Timestamp in jede Zeile ein,
  • “s999999″ legt die Größe bis zur nächsten Rotation der Logdatei fest und
  • “n20″ bestimmt, dass 20 alte Logfiles bereit gehalten werden. Ältere werden gelöscht.

Ältere Logfiles muss man sich, sollten wir sie denn benötigen, aus dem Backup holen. Vorteil: Es ist von Anfang an klar, wie viel Plattenplatz die Logs maximal belegen werden (hier: 20 MB).

Soweit. Wenn man auf einem System keine Root-Rechte besitzt, kann man mit einem einfachen

nohup svscan ~/service

einen Dienst starten, der das service-Verzeichnis regelmäßig scannt und für die darin konfigurierten Dienste supervise-Prozesse startet. Auf einem Server mit Root-Rechten installiert man die Daemontools auch systemweit und lässt einen svscan-Dienst laufen als der Benutzer, der Node.js ausführen soll. Mit dem Befehl

ps auxf

kann man sich sehr schön die Prozess-Hierarchie anzeigen lassen. Ein Beispiel:

root      1378  0.0  0.1   1752   524 ?        Ss   20:32   0:00 /bin/sh /usr/bin/svscanboot
root      1380  0.0  0.0   1708   396 ?        S    20:32   0:00  \_ svscan /etc/service
root      1387  0.0  0.0   1548   340 ?        S    20:32   0:00  |   \_ supervise svscan-fs
fs        1389  0.0  0.0   1708   396 ?        S    20:32   0:00  |   |   \_ svscan /home/fs/service
fs        2792  0.0  0.0   1548   340 ?        S    21:12   0:00  |   |       \_ supervise nodejs
fs        5197  0.1  3.4  63252 17860 ?        Sl   21:42   0:01  |   |       |   \_ node /home/fs/bundle/main.js
fs        4916  0.0  0.0   1548   340 ?        S    21:38   0:00  |   |       \_ supervise log
fs        5139  0.0  0.0   1556   340 ?        S    21:41   0:00  |   |           \_ multilog t s999999 n20 ./main
root      1388  0.0  0.0   1548   340 ?        S    20:32   0:00  |   \_ supervise log
root      1390  0.0  0.0   1688   412 ?        S    20:32   0:00  |       \_ multilog t ./main
root      1381  0.0  0.0   1536   304 ?        S    20:32   0:00  \_ readproctitle service errors: ................................
fs        1393  0.0  0.0   1556   336 ?        S    20:32   0:00 multilog t s999999 n20 ./main

So long! Ich werde das Repository mit der Daemontools-Konfiguration für Node.js aktualisieren, wenn ich nützliche Konfigurationsdateien für weitere von unseren Projekten benötigte Dienste erstellt und getestet habe. Bei dem Projekt, das mir den Anlass gegeben hat, die Daemontools einzusetzen, starte ich die systemweite MongoDB über /etc/init.d; Aber spätestens, wenn wir wieder ein Depoyment auf einer Maschine ohne Root-Rechte machen müssen, werden ein paar weitere Skripte dazukommen.

Zum Abschluss noch ein paar Links:

Wenn ich schon einen Wachhund für meine Dienste brauche, dann bitte auch einen zuverlässigen! Keinen zu fetten, keinen zu jungen, und keinen, der nur in seiner gewohnten Umgebung überhaupt Dienst tut.

This post was written by

Florian Sesser – who has written posts on IT:Agenten GmbH.
Systems Architect. Security Engineer, Software Developer

Email

Tags: , , ,

5 Responses to “Daemon Watchdog Daemon”

  1. Nico 19. November 2012 at 17:41 #

    Daemontools finde ich im Zusammenhang mit Node.js auf Debian auch ziemlich gut.

    In unserer VM-Umgebung sind wir allerdings auf SysVinit beschränkt, deswegen verwenden wir eine Kombination aus einem Init-Script, das straight-forward ein Shellscript startet oder beendet, und dem Shellscript, das dafür sorgt dass der Node-Prozess immer wieder gestartet wird, wenn er mal abraucht:

    https://github.com/nicokaiser/node-init

    Das ist nicht besonders elegant, aber funktioniert, kann stdout/stderr vernünftig loggen und braucht keine Extrawurst auf dem Server.

    - Upstart auf Debian geht zwar prinzipiell, ist aber lange nicht so gut integriert wie in Ubuntu, und bei dist-upgrades geht gerne auch mal was total kaputt. Allerdings für diesen Fall eigentlich optimal.

    - daemontools ist cool, fühlt sich aber auch fremd an. Uberspace hat Userspace-Daemons mit daemontools ziemlich cool gelöst: http://uberspace.de/dokuwiki/system:daemontools

    - Monit taugt m.E. mehr zum Monitoring als für “halte Service XY am laufen”. Wenn der Prozess abstürzt dauert es bis zu X Minuten (je nach Config) bis das von Monit erkannt wird

    Auf SmartOS würde ich allerdings kein daemontools verwenden, da gibt es ja schon SMF/svcadm: http://joyent.com/blog/installing-a-node-service-on-a-joyent-smartmachine

    • Florian Sesser 19. November 2012 at 18:27 #

      Eine Lösung ähnlich zu Deinem Restart-Loop hatte ich auch eine Weile — in einer Screen-Session ^^ Aber halt auch nur in der Prototyp-Phase. Irgendwie hat’s mich vor dem “Screen-Deployment” gegraust :)

      Solaris’ svcadm kommt auch nach meinem Gefühl den Daemontools noch am nächsten. Ich hab mit Solaris noch nicht viel gemacht, aber wenn ich mich richtig erinnere braucht svcadm Admin-Rechte und ist halt wieder Plattform-spezifisch. Daemontools geht auch als normaler User und auf allen Systemen gleich. Der Kern der Überspace-Lösung erlaubt IMHO auch sehr schön, eine Kiste für unterschiedliche User/Projekte zu benutzen: Eine globale Daemontools-Instanz startet eine weitere Instanz für jeden User als dieser User — und die einzelnen User können damit dann recht elegant unterschiedliche Dienste laufen lassen.

      Hat alles Vor- und Nachteile, klar — als eingefleischter Solaris-Admin wäre ich wahrscheinlich nicht sonderlich begeistert, wenn ich eine Kiste übernehmen müsste, wo jemand gemeint hat nicht die vom System angebotenen Standard-Tools zu benutzen — sondern eine Extra-Wurst wie die vom DJB.

      Wenn die vom System angebotenen Tools so antiquierter Kram sind wie init.d-Skripte hab ich andererseits auch keine großen Skrupel, anstatt eine Extrawurst einzubauen ^^ Und das init.d-Skripte einiges vermissen lassen haben ja anscheinend auch die Jungs von Ubuntu gemerkt. Nur wirkt deren Lösung “upstart” halt irgendwie noch nicht fertig.

    • Florian Sesser 19. November 2012 at 18:30 #

      [upstart]
      > Allerdings für diesen Fall eigentlich optimal.

      Warum? Weil’s der “Debian”-Weg ist?

      Ich hatte keine Lust eine “upstart” Dependency einzuführen; Außerdem hat sich Upstart über die letzten drei Ubuntu-Versionen so stark geändert, dass ich Angst habe, die Skripte (oder eigentlich sind’s ja mehr Config-Files) jedes mal neu schreiben zu müssen.

      • Nico 20. November 2012 at 11:11 #

        Weil’s (auf Ubuntu) eh schon dabei ist und das tut, was es soll (Server-Prozess starten/restarten, Logging).

        Unter Debian ist’s ja mehr als nur eine zusätzliche Dependency (eher ein komplett anderer Startprozess, der nur so halb gut integriert ist), das war bei meinen Versuchen ein bisschen hakelig.

  2. Nico 20. November 2012 at 11:14 #

    > Eine Lösung ähnlich zu Deinem Restart-Loop hatte ich auch eine Weile — in einer Screen-Session ^^ Aber halt auch nur in der Prototyp-Phase. Irgendwie hat’s mich vor dem “Screen-Deployment” gegraust

    Screen-Deployment hatten wir eine zeitlang. Macht keinen Spaß.

    > Hat alles Vor- und Nachteile [...] Wenn die vom System angebotenen Tools so antiquierter Kram sind wie init.d-Skripte hab ich andererseits auch keine großen Skrupel, anstatt eine Extrawurst einzubauen

    Volle Zustimmung.