Archiv der Kategorie: Server-Zeugs

WordPress-Update auf Server hinter Proxy

Kürzlich musste ich feststellen, dass das Aktualisieren von WordPress auf einem Server, der nur über einen Proxy nach draußen kommunizieren kann, ziemlich problematisch sein kann. Eigentlich liest man überall, dass es ausreicht, in der Datei wp-config.php die beiden Optionen WP_PROXY_HOST und WP_PROXY_PORT korrekt zu setzen. In meinem Fall war das nicht genug. WordPress konnte dann zwar Informationen über verfügbare Aktualisierungen und Plug-ins abrufen, diese jedoch nicht herunterladen („Download fehlgeschlagen“). Nach schier endlosem Herumprobieren und Suchen fand ich schließlich den entscheidenden Hinweis: Man muss das Paket php-curl installieren. Danach klappte alles prima.

DSGVO/GDPR: Speichern von IP-Adressen durch PHP-Anwendungen und in Server-Logdateien verhindern

Ein wichtiger Hinweis im Voraus: Allein durch die hier dargestellten Methoden hat man nicht automatisch alle Vorgaben der DSGVO erfüllt – dazu gehört deutlich mehr! Ich möchte hier nur ein paar Tipps geben, wie gewisse Teilprobleme ohne viel Aufwand gelöst werden können.

Da in knapp einem Monat die neue Datenschutz-Grundverordnung (DSGVO) bzw. General Data Protection Regulation (GDPR) zur Anwendung kommt, muss sich so manch ein Webmaster Gedanken darüber machen (idealerweise bereits gemacht haben), wie er seinen PHP-Anwendungen (WordPress, CMS, Forum, Wiki, …) beibringt, nicht mehr die IP-Adressen der Besucher zu speichern. Die DSGVO definiert IP-Adressen nämlich explizit als personenbezogen. Sofern man sich nicht glaubhaft auf ein berechtigtes Interesse berufen kann, sollte man diese Adressen somit lieber nicht unnötigerweise speichern. Je weniger man speichert, desto weniger Angst muss man vor eventuellen Bußgeldern haben und desto weniger Schaden kann bei einem Datendiebstahl entstehen.

Nun kann man entweder für jede seiner Anwendungen ein Plug-in suchen oder selber schreiben, oder man versucht es gleich mit einem radikaleren Ansatz. Zumindest für PHP-Anwendungen gibt es eine sehr einfache und „idiotensichere“ Lösung, und zwar die php.ini-Variable auto_prepend_file. Wenn man hier eine PHP-Datei einträgt, dann wird diese am Anfang jedes ausgeführten PHP-Skripts geladen, so als ob sie mit require() eingebunden worden wäre. Und genau das können wir nutzen, um die Variable $_SERVER["REMOTE_ADDR"], in der PHP die IP-Adresse des Besuchers ablegt, zu verändern, bevor die PHP-Anwendung sie zu Gesicht bekommt. Also schreiben wir in unsere php.ini oder eine spezielle Datei im conf.d-Verzeichnis:

auto_prepend_file = /pfad/zur/anonymize_ip.php

Die Datei anonymize_ip.php enthält den Code, der die IP-Adresse anonymisiert, indem das letzte Oktett auf 0 gesetzt wird (mein Server unterstützt noch kein IPv6, also behandle ich erst einmal nur IPv4-Adressen – wer IPv6 unterstützt, der sollte den Code anpassen und einen deutlich größeren Teil der Adresse auf 0 setzen):

<?php

if (isset($_SERVER["REMOTE_ADDR"]))
{
    $ip = $_SERVER["REMOTE_ADDR"];
    $_SERVER["ORIGINAL_REMOTE_ADDR"] = $ip;
    $octets = explode(".", $ip);
    if (count($octets) == 4)
    {
        $octets[3] = "0";
        $_SERVER["REMOTE_ADDR"] = implode(".", $octets);
    }
}

?>

Auf diese Weise muss man die PHP-Anwendung nicht anpassen. Sie bekommt von vornherein nur eine gekürzte IP-Adresse zu Gesicht, die dann nicht mehr personenbezogen ist. Man muss also nicht mühsam alle Stellen suchen, an denen die Anwendung die IP-Adresse benutzt. Den Trick mit dem Kürzen der IP-Adresse benutzt übrigens auch Google Analytics, um die Datenschützer zufriedenzustellen. Probleme mit den PHP-Anwendungen sind dabei eigentlich nicht zu erwarten, denn die gekürzten IP-Adressen sind immer noch gültig, und es ist sowieso nicht unüblich, dass mehrere Besucher unter derselben IP-Adresse unterwegs sind (Universitäten, Firmen, …). Und falls man in gewissen Fällen doch mal die komplette IP-Adresse benötigt, dann kann man immer noch über $_SERVER["ORIGINAL_REMOTE_ADDR"] auf sie zugreifen.

Doch was ist mit den Server-Logdateien? In der Standardkonfiguration loggen Webserver wie Apache jede Anfrage in einer Logdatei. Das sieht dann beispielsweise so aus:

89.245.114.71 - - [27/Apr/2018:16:49:53 +0200] "GET /impressum-datenschutz/ HTTP/1.1" 200 10408 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"

Auch hier wird die IP-Adresse gespeichert (im Beispiel meine eigene, also darf ich das), und das ist möglicherweise problematisch. Hier gibt es mehrere Lösungen:

  • Die meisten Webserver (wenigstens Apache und nginx) erlauben die Änderung des Logformats. Möchte man keine IP-Adressen loggen, dann ist das kein Problem – man lässt das entsprechende Feld einfach weg.
  • Möchte man nicht komplett auf IP-Adressen verzichten, sondern sie in gekürzter Form (so wie oben beschrieben) loggen, dann geht das auch – zumindest mit Apache. Man kann nämlich Piped Logs benutzen. Anstatt in eine Datei zu loggen, wird ein separater Prozess gestartet, der die Logzeilen über seine Standardeingabe erhält. Nun muss man nur ein kleines Tool schreiben, das Zeile für Zeile liest, die IP-Adressen kürzt und das Ergebnis in eine Logdatei schreibt.
  • Bei den Server-Logdateien stehen die Chancen gut, dass man sich auf ein berechtigtes Interesse berufen kann, um die IP-Adressen zumindest für eine begrenzte Zeit in ungekürzter Form zu speichern, denn sie bieten mehr oder weniger die einzige Möglichkeit, Angriffe nachvollziehen und gegen sie vorgehen zu können. Man könnte sich also ein kleines Programm schreiben, das über einen Cronjob jeden Tag über die Logdateien läuft und bei allen Einträgen, die älter sind als z. B. eine Woche, die IP-Adressen kürzt. In Verbindung mit Logrotation ist das besonders einfach.

Ich hoffe, dieser Artikel konnte bei dem einen oder anderen die DSGVO-Kopfschmerzen zumindest ein bisschen lindern.

Echtzeitanalyse von Apache-Logs ohne Umweg über das Dateisystem

Ich wollte mir schon seit langer Zeit ein Tool schreiben, mit dem ich meine Apache-Logs mehr oder weniger in Echtzeit analysieren kann (z. B. um Angriffe zu erkennen). Mein erster Ansatz wäre zunächst gewesen, alle paar Minuten ein Skript laufen zu lassen, das alles, was seit dem letzten Lauf in die Log-Dateien geschrieben wurde, verarbeitet. Aber so richtig schön wäre das nicht gewesen, ginge das doch unnötigerweise über das Dateisystem. Das Rotieren der Log-Dateien hätte ebenfalls speziell behandelt werden müssen.

Dann sah ich, dass mit der CustomLog-Direktive von Apache nicht nur normale Dateien als Log-Ziel angegeben werden können, sondern es auch möglich ist, die Log-Ausgaben über eine Pipe in einen anderen Prozess zu leiten („Piped Logs“). Dazu gibt man den Pfad zur ausführbaren Datei eines Programms an, das von Apache gestartet wird und dann die Log-Ausgaben über seine Standardeingabe erhält, zum Beispiel so:

CustomLog "||/pfad/zum/programm" "<%h|%D>"

Der zweite Teil der Direktive definiert das Log-Format. Im Beispiel werden nur die IP-Adresse des Aufrufers und die zur Verarbeitung benötigte Zeit geloggt. Das ist noch ein Vorteil gegenüber der Analyse von Log-Dateien: Man kann genau auswählen, welche Daten man zur Echtzeitverarbeitung benötigt. Warum ich die Zeichen < und > am Anfang und am Ende benutze, erläutere ich weiter unten.

Nun könnte man die Log-Ausgaben direkt in diesem einen Programm verarbeiten. Jedoch wird für jeden Apache-Elternprozess eine separate Instanz dieses Programms gestartet, also laufen normalerweise viele davon parallel. Das würde es schwierig machen, die Daten zusammenzuführen.

Um eine zentrale Verarbeitung zu ermöglichen, erstelle ich ein FIFO (mkfifo) und starte ein separates Auswertungsprogramm, das stets im Hintergrund läuft. Das Programm, das von Apache gestartet wird, wird nur zum Sammeln der Daten benutzt und heißt daher von nun an „Sammelprogramm“. Alle Instanzen des Sammelprogramms schreiben ins FIFO, und das Auswertungsprogramm liest daraus und analysiert die Daten.

Ein paar Hinweise, falls das jemand nachbauen möchte:

  • Da viele Instanzen des Sammelprogramms parallel laufen werden, sollte man es möglichst speichersparend implementieren. Ich habe dafür C++ genommen statt wie üblich Python.
  • Wenn das Auswertungsprogramm nicht läuft (z. B. weil es abgestürzt ist), werden die Sammelprogramme irgendwann hängen bleiben. Das ist nicht gut, weil dann auch der jeweilige Apache-Prozess beim Loggen hängen bleibt. Zum Glück kann man die Situation erkennen, indem man das FIFO nicht-blockierend öffnet (in C/C++ mit dem Flag O_NONBLOCK). Das Öffnen schlägt dann einfach fehl statt zu blockieren.
  • Das Auswertungsprogramm sollte das FIFO zuerst zum Lesen öffnen und danach zusätzlich auch zum Schreiben. Somit verhindert man, dass es irgendwann keinen Prozess mehr gibt, der das FIFO zum Schreiben geöffnet hat (dann passieren seltsame Dinge).
  • Wenn mehrere Prozesse parallel in ein FIFO schreiben (so wie es hier der Fall ist), ist es nur unter gewissen Umständen garantiert, dass die Daten jeweils am Stück geschrieben werden. Theoretisch kann es passieren, dass das Auswertungsprogramm zuerst einen Teil der von Prozess A geschriebenen Daten liest und dann einen Teil der von Prozess B geschriebenen Daten („interleaved“). Um solche Fälle zu erkennen, verwende ich die Zeichen < und > am Anfang und am Ende jeder ins FIFO geschriebenen Zeile. Somit kann man erkennen, ob man eine unvollständige Zeile gelesen hat. Solange man nur Daten am Stück schreibt, die höchstens PIPE_BUF Bytes groß sind (Linux: 4096), sollte dies jedoch nie passieren.

Ich bin bisher sehr zufrieden mit dieser Vorgehensweise und kann sie nur weiterempfehlen.

Mit PhantomJS Platzhalter für datenschutzkonforme „Gefällt mir“- und „+1“-Buttons erstellen

Wer Gefällt mir– oder +1-Buttons direkt in seine Website einbindet, verstößt dabei wohl gegen geltendes Datenschutzrecht. Denn selbst wenn man in seinen Datenschutzrichtlinien über die Buttons informiert, so werden sie ja automatisch sofort geladen, bevor der Besucher den Text überhaupt lesen konnte. Und da die Buttons direkt von Facebook & co. geladen werden, können diese damit wunderbar ein digitales „Bewegungsprofil“ ihrer Nutzer und Nichtnutzer erstellen.

Eine beliebte Alternative sind Lösungen wie die von Heise ins Leben gerufene Shariff-Bibliothek. Hier wird die Verbindung zum sozialen Netzwerk erst hergestellt, nachdem der Benutzer einmal geklickt hat. Leider bietet Shariff von sich aus keine Unterstützung für Gefällt mir– und +1-Buttons, sondern es erlaubt lediglich das Teilen. Die ebenfalls von Heise entwickelte Zwei-Klick-Lösung SocialSharePrivacy ist mittlerweile auch schon arg angestaubt und hat den Nachteil, dass die aktuelle Anzahl von „Likes“ nicht angezeigt werden kann.

Ich wollte daher eine eigene Alternative entwickeln. Mein Ansatz ist derselbe wie bei SocialSharePrivacy: Der Benutzer sieht zunächst eine „ausgegraute“ Version des Buttons, bei der es sich nur um ein Bild handelt, das vom eigenen Server ausgeliefert wird. Ein kleiner Hinweis informiert darüber, dass der Button mit dem ersten Klick aktiviert und mit dem zweiten Klick bedient wird. Bei erfolgtem Klick wird das Bild durch den tatsächlichen Button ersetzt, beispielsweise mit Hilfe eines iframe-Elements.

Die Platzhaltergrafiken für die Buttons sollten bereits die aktuelle Anzahl von „Likes“ enthalten, daher scheidet ein manuelles Anfertigen dieser Grafiken aus. Und genau hier hat sich PhantomJS wieder einmal als ein hervorragendes Tool bewiesen. Mit nur ein paar Zeilen JavaScript kann der echte Button von Facebook/Google in einem „virtuellen Browser“ geladen und als Screenshot abgespeichert werden. Ein bisschen Nachbearbeitung mit ImageMagick sorgt dann für einen ausgegrauten/unscharfen Look. Die Aktualisierung der Platzhaltergrafiken erfolgt mittels Cronjob automatisch einmal pro Stunde, bei Bedarf auch öfter oder seltener.

Wenn mod_deflate scheinbar nur mit HTTPS funktioniert

Gerade bin ich einem sehr knackigen Rätsel auf die Spur gekommen. Wenn ich eine meiner Seiten im Browser lade und mir die HTTP-Requests anzeigen lasse, dann stelle ich fest, dass HTML-Inhalte scheinbar ohne Kompression ausgeliefert werden, obwohl mod_deflate im Apache-Webserver korrekt aktiviert ist. Noch seltsamer ist aber, dass das Problem nicht auftritt, wenn ich die Seite mit HTTPS (SSL) aufrufe. Aus der Sicht externer Tools wiederum, die Seiten auf Kompression testen, wird alles ordnungsgemäß komprimiert — sowohl mit HTTP als auch mit HTTPS. Nur meine Browser sind anderer Meinung.

Des Rätsels Lösung: Kaspersky Endpoint Security for Windows ist schuld! Die Komponente Web Anti-Virus agiert als eine Art Proxy zwischen dem Browser und dem Webserver und dekomprimiert die HTML-Inhalte, um sie nach schädlichem Zeugs zu durchsuchen. Das erklärt auch, warum Inhalte über HTTPS komprimiert beim Browser ankommen: Die Kaspersky-Software hat hier wegen der Verschlüsselung keine Möglichkeit, die Daten abzufangen!

Wenn also jemandem etwas Ähnliches passiert, dann erst einmal prüfen, ob nicht irgendeine Sicherheitssoftware dazwischenfunkt.

Lösung: Pipe-Symbol über VNC tippen

Heute wurde ich mit dem Problem konfrontiert, das Pipe-Symbol („|“) über eine VNC-Verbindung zu meinem Server zu tippen. Wenn ich Alt Gr + < drücke, passiert einfach gar nichts. Andere Zeichen funktionieren hingegen wunderbar. Eine ganz einfache Notlösung (die ich seltsamerweise nirgendwo gefunden habe) ist Folgende:

Bei gedrückter Alt-Taste einfach die Ziffernfolge 124 auf dem Ziffernblock (NumPad) tippen, denn 124 ist der ASCII-Wert des Pipe-Zeichens.

… und schon erscheint das ersehnte Symbol. Bei mir jedenfalls. 😉

Den Backslash („\“) erhält man übrigens mit Alt + 92 und die Tilde („~“) mit Alt + 126.

Wenn wget versagt: PhantomJS

Aus einem gewissen Grund brauche ich regelmäßig die aktuellen IP-Ranges von Amazon EC2 (ein Produkt für Cloud Computing). Zum Glück gibt es im entsprechenden Amazon-Forum einen Thread, in dem immer die aktuellen IP-Ranges aufgelistet sind. Bis vor einiger Zeit konnte ich diesen Thread problemlos wie folgt herunterladen, um ihn dann mit einem regulären Ausdruck nach IP-Ranges zu durchsuchen:

wget -O amazon_ec2.txt https://forums.aws.amazon.com/ann.jspa?annID=1701

Nun geht das nicht mehr — man erhält nur eine Fehlermeldung, dass der Browser JavaScript unterstützen muss. Das Forum arbeitet jetzt scheinbar Client-seitig mit JavaScript, und wget kann kein JavaScript ausführen.

Doch zum Glück gibt es PhantomJS! Dabei handelt es sich um ein Tool, das sozusagen einen Browser simuliert und mit JavaScript gesteuert werden kann. Mit folgendem Skript kann ich die Seite laden und ihren HTML-Quellcode ausgeben, womit das Problem gelöst ist:

var page = require("webpage").create();
page.open("https://forums.aws.amazon.com/ann.jspa?annID=1701",
 function(status) {
  var f = function() {
   var html = page.evaluate(function() { return document.documentElement.innerHTML; });
   console.log(html);
   phantom.exit();
  };
  setTimeout(f, 10000);
 }
);

Die Verzögerung von 10 Sekunden sorgt dafür, dass alle Umleitungen (mit hoher Wahrscheinlichkeit) abgeschlossen sind, bevor der HTML-Quellcode ausgegeben wird. Es geht wahrscheinlich auch eleganter, aber es erfüllt seinen Zweck.

Bislang habe ich für solche Zwecke übrigens Awesomium benutzt, was auch ganz nett ist, aber hier gibt es schon seit Ewigkeiten keine 64-Bit-Version mehr für Linux (eigentlich ein Unding). Vielleicht werde ich meine schon existierenden Anwendungen, die auf Awesomium basieren, auf PhantomJS portieren.

Nachtrag (1. Oktober 2016): Dieser umständliche Weg ist für den konkreten Anwendungsfall nicht mehr nötig, da Amazon die IP-Ranges nun als JSON-Datei bereitstellt.

Server-Backups im laufenden Betrieb mit LVM und ionice

LVM-Snapshots für konsistente Backups im laufenden Betrieb

Wie ich schon einmal erzählt habe, läuft mein Webserver als (bislang einzige) virtuelle Maschine auf meinem dedizierten Server. Die virtuelle Festplatte ist dabei in einem 200 GiB großen Logical Volume untergebracht. Logical Volumes werden mit dem Logical Volume Manager (LVM) erzeugt und sind sozusagen die moderne Version von Partitionen.

Ein Feature von LVM finde ich dabei besonders beeindruckend und extrem hilfreich, nämlich das Anfertigen von Snapshots von Logical Volumes. Ein Snapshot ist eine Momentaufnahme des kompletten Inhalts eines Logical Volumes. Einen Snapshot anzufertigen ist eine Angelegenheit von maximal ein paar Sekunden, es muss dabei nämlich nichts kopiert werden. Erst wenn im Original-Volume ein Datenblock geschrieben wird, muss dessen vorheriger Inhalt gesichert werden, damit er für den Snapshot noch verfügbar ist. Solch ein Verfahren nennt sich Copy-On-Write. Beim Erzeugen des Snapshots muss man bereits angeben, wie viele Daten sich höchstens ändern dürfen, während der Snapshot existiert.

Das Snapshot-Feature ist wie geschaffen für regelmäßige Backups eines Servers im laufenden Betrieb. Hierbei ist es nämlich wichtig, dass man den Inhalt sämtlicher Dateien quasi gleichzeitig sichert. Kopiert man einfach nur alle Dateien nacheinander, dann kommt es zwangsläufig zu Inkonsistenzen, wenn sich während des Backups etwas verändert (und das lässt sich kaum vermeiden, wenn der Server während des Backups benutzbar bleiben soll).

Also geht man wie folgt vor:

  1. Warten, bis alle Cronjobs abgeschlossen sind, denn wir möchten diese nicht unterbrechen. Danach am besten auch den Cron-Daemon anhalten.
  2. Apache und MySQL anhalten, um einen konsistenten Zustand der Datenbanken zu gewährleisten, ggf. noch weitere Dienste.
  3. Mittels lvcreate --snapshot einen Snapshot des Logical Volumes anfertigen, das man sichern möchte. Dabei muss man auch die Größe des Snapshots angeben, also wie viele Daten sich während seiner Existenz ändern dürfen. Ein oft empfohlener Wert ist 10% bis 20% der Größe des Logical Volumes, aber das hängt natürlich von der konkreten Situation ab.
  4. Jetzt, da der Snapshot erzeugt ist, können sämtliche Dienste wieder gestartet werden (Cron, MySQL, Apache, …). Die Downtime des Servers ist somit auf wenige Sekunden begrenzt.
  5. Nun kann der Snapshot „in Ruhe“ irgendwohin gesichert werden, z. B. via FTP. Ich bin dabei, meine eigene Backup-Lösung zu entwickeln, mit der die komplette virtuelle Festplatte blockweise gesichert wird (nicht auf der Ebene des Dateisystems, denn da gibt es immer Schwierigkeiten mit Besitzern, Rechten, Hardlinks, Softlinks usw.). Mehr dazu in einem späteren Beitrag.
  6. Sobald die Sicherung abgeschlossen ist, entfernt man den Snapshot wieder mit lvremove.

Mit nice und ionice die Reaktionszeit des Servers während des Backups verbessern

Das Sichern eines Snapshots beansprucht die Festplatte des Servers stark. Wenn man nicht aufpasst, reagieren Webseiten und andere Anwendungen, die auf dem Server laufen, während des Backups extrem langsam und träge.

Was viele nicht wissen: Unter Linux kann man nicht nur die CPU-Priorität von Prozessen ändern (mit nice), sondern auch ihre I/O-Priorität. Das funktioniert mit dem Befehl ionice, zu dem man diesen netten Artikel finden kann. nice und ionice lassen sich auch kombinieren. Somit kann man dafür sorgen, dass ein laufendes Backup die übrigen Prozesse weder in Sachen CPU noch in Sachen Festplatte zu sehr ausbremst.

Folgender Befehl startet das Skript backup.sh mit minimaler CPU- und I/O-Priorität:

nice -n 19 ionice -c 3 ./backup.sh

Nachtrag: Nach meinen ersten Erfahrungen kommt es auch bei der Verwendung der niedrigsten I/O-Priorität teilweise zu großen Beeinträchtigungen des Serverbetriebs. Abhilfe kann das Tool pv schaffen, mit dem man u. a. den Durchsatz einer UNIX-Pipe begrenzen kann. Somit kann man beispielsweise ein laufendes Backup auf eine Leserate von höchstens 60 MiB pro Sekunde drosseln. Eure Besucher werden es euch danken!

Noch ein Nachtrag: ZFS mit seinen ZVOLs ist nun meiner Meinung nach gegenüber LVM zu bevorzugen. ZFS bietet ebenfalls die Möglichkeit der Anfertigung von Snapshots, darüber hinaus wird die Integrität der Daten jedoch mit Prüfsummen sichergestellt. ZFS ersetzt auch MD (Software-RAID).

Node.js-Modul „ffi“ zum Laufen bringen

Mit frisch installiertem node.js und Paketmanager npm hatte ich gerade einen seltsamen Fehler, als ich das Paket ffi installieren und benutzen wollte (mit diesem Paket kann man native Funktionen aus node.js heraus aufrufen):

> require("ffi")
Error: Could not locate the bindings file. Tried:
 → /home/david/node_modules/ffi/node_modules/ref/build/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/build/Debug/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/build/Release/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/out/Debug/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/Debug/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/out/Release/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/Release/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/build/default/binding.node
 → /home/david/node_modules/ffi/node_modules/ref/compiled/0.6.12/linux/x64/binding.node
    at bindings (/home/david/node_modules/ffi/node_modules/bindings/bindings.js:88:9)
    at Object. (/home/david/node_modules/ffi/node_modules/ref/lib/ref.js:5:47)
    at Module._compile (module.js:441:26)
    at Object..js (module.js:459:10)
    at Module.load (module.js:348:32)
    at Function._load (module.js:308:12)
    at Module.require (module.js:354:17)
    at require (module.js:370:17)
    at Object. (/home/david/node_modules/ffi/lib/ffi.js:6:11)
    at Module._compile (module.js:441:26)

Aha, es fehlt also irgendeine komische „Bindings-Datei“. Um den Fehler zu beheben, braucht man das Paket node-gyp, das einfach mit npm install -g node-gyp global installiert werden kann.

Nun muss man das Tool node-gyp an zwei verschiedenen Stellen aufrufen, damit diese Bindings-Datei erzeugt wird, nämlich einmal im Verzeichnis ffi selbst und einmal in ffi/node_modules/ref:

david@webserver:~# cd node_modules/ffi/
david@webserver:~/node_modules/ffi# node-gyp rebuild
[...]
david@webserver:~/node_modules/ffi# cd node_modules/ref/
david@webserver:~/node_modules/ffi/node_modules/ref# node-gyp rebuild
[...]

Anschließend sollte das ffi-Modul korrekt geladen werden können.

Libvirt vergisst Memory Unit

Gestern Nacht habe ich viele Stunden mit einem im Nachhinein ziemlich dämlichen Problem verbracht. Meine virtuelle Maschine wollte nach einem Neustart nicht mehr booten, GRUB erzählte zunächst etwas von „Error 15: File not found“ und später nach meinen vergeblichen Reparaturversuchen dann „Error 28: Selected item cannot fit into memory„.

Zum Glück hat mich diese zweite Fehlermeldung auf die richtige Fährte gebracht. Die virtuelle Maschine hatte plötzlich nur noch 16 KiB(!) RAM, und da ist es ja kein Wunder, dass sie nicht booten mochte. Sofort habe ich mir die XML-Definition der virtuellen Maschine anzeigen lassen und las dann Folgendes:

<domain type='kvm'>
 <name>webserver</name>
 <memory>16</memory>
 <currentMemory>16</currentMemory>

Es gab also tatsächlich nur 16 KiB RAM für die virtuelle Maschine. Wie konnte das passieren? Ganz einfach: Laut libvirt-Handbuch kann man im memory-Element ein Attribut namens unit benutzen, um die Einheit der im Element angegebenen Zahl festzulegen. Standardmäßig sind es KiB. Ich hatte Folgendes eingetragen, um der VM bis zu 16 GiB Speicher zu geben:

<domain type='kvm'>
 <name>webserver</name>
 <memory unit='GiB'>16</memory>
 <currentMemory unit='GiB'>16</currentMemory>

Und nun der böse Fehler, aber nicht meinerseits: Beim Speichern der XML-Definition vergisst libvirt das unit-Attribut, so dass am Ende einfach nur noch die Zahl 16 ohne Einheit übrig bleibt, die dann beim nächsten Starten der VM als 16 KiB interpretiert wird. Nun benutze ich 16777216 (KiB), was meinen 16 GiB entspricht, und es gibt keine Probleme mehr.

Ziemlich ärgerlich! Aber immerhin habe ich bei meinen gar nicht notwendig gewesenen Reparaturversuchen etwas über GRUB gelernt und auch zum ersten Mal mit einem VNC-Client auf meine VM zugegriffen, was sehr hilfreich ist, da man ihr damit beim Booten zuschauen kann (ich habe TightVNC benutzt und fand es gut). Ohne VNC-Client hätte ich die Fehlermeldung beim Booten nicht sehen können und wäre noch ratloser gewesen …