Undokumentierte natürliche Konstante

12. April 2010 von Jonas Pasche

Aus der Mail eines Kunden über einen seiner Kunden, nachdem wir ein von ihm gemeldetes Problem bearbeitet hatten, das sich bei genauerer Betrachtung als bei weitem nicht so massiv darstellte, wie der Kundeskunde behauptete:

Das liegt wohl am $KUNDESKUNDE-Faktor, der beträgt bei Übertreibungen ca. 3, und bei Untertreibungen ca. 0,3. Ist so ähnlich wie Pi oder e eine bisher undokumentierte natürliche Konstante ;-)

Das ist fein beobachtet!

April-Support

09. April 2010 von Jonas Pasche

Der Hostblogger jammert über den zusätzlichen Support, der aus dem diesjährigen Tagesschau-Aprilscherz resultierte. Wir hingegen erfreuen uns an solchen Supportanfragen:

Es geht um das Thema kostenpflichtige Emails. Dies soll ja schon zum 01.06.2010 und nur für *.de-Adressen eingeführt werden. Ist es da Sinnvoll, mit dem Emailverkehr auf eine Adresse wie z. B. *.biz auszuweichen?

Allerdings war das eine echte Ausnahme. Die meisten unserer Kunden lassen sich offensichtlich entweder nicht so leicht aufs Glatteis führen, oder sie hören einfach nicht soviel Radio. :-)

MySQL mit daemontools betreiben

29. März 2010 von Christopher Hirschmann

Dieser Artikel ist der vierte und letzte Teil einer kleinen Gruppe von Artikeln über MySQL-Backups.

Hier ist der erste Artikel: MySQL-Backups, aber wie?

Hier ist der zweite Artikel: MySQL Replikation

Hier ist der dritte Artikel: Hinter der MySQL-Replikation aufräumen

Wer häufiger liest, was wir so über unsere Arbeit schreiben, wird schon mitbekommen haben, daß wir sehr gerne die daemontools von Daniel J. Bernstein einsetzen, um init-Skripte, xinetd, rc-Skripte und sogar cron (mit der daemontools-Erweiterung runwhen) zu ersetzen.

Das funktioniert mit dem meisten Diensten ganz gut, mit einigen weniger, wobei die letztere Gruppe seit Jahren schrumpft und durch die zunehmende Verbreitung von Upstart (das ganz ähnlich wie daemontools arbeitet und den gleichen Zweck erfüllt) dürfte sich dieser Prozess noch beschleunigen.

Bei MySQL erscheint die Einrichtung unter daemontools oft etwas kompliziert. Der Grund hierfür ist eigentlich ironisch: Die Entwickler von MySQL waren auch auf Probleme mit init gestoßen und haben keine allgemein verbreitete Lösung dafür finden können (es gab halt keine, möglicherweise wird Upstart jetzt die allgemeine Lösung werden, aber für MySQL kam das viele Jahre zu spät, gerade die Schwäche von init, Dienste nicht zuverlässig neuzustarten ist halt für einen Dienst wie MySQL kein Pappenstiel). Also haben sie das Problem kurzerhand selbst gelöst und einen Wrapper um den MySQL Daemon geschrieben, der diese Aufgabe übernimmt, egal ob init gerade korrekt arbeitet oder nicht, mysqld_safe war geboren.

Wenn man nun MySQL unter daemontools laufen lassen möchte, unterläuft einem sehr schnell ein verbreiteter Fehler, den ich anfangs auch gemacht habe. Ich hab nämlich ganz naiv ins init-Skript geschaut wie MySQL denn von dort gestartet wird und das einfach auf das run-Skript für daemontools übertragen:


#!/bin/sh
exec /usr/bin/mysqld_safe --defaults-file=/etc/my.cnf

Und das funktioniert auch — auf den ersten Blick. Der MySQL-Dienst wird gestartet und läuft und läuft und läuft. Aber leider läuft er auch nach einem svc -d /service/mysql (dem Äquivalent zu service mysql stop) weiter. Um MySQL mit diesem run-Skript sauber zu beenden, muß man dem Dienst manuell ein SIGTERM schicken. Er beendet dann zwar sauber, aber irgendwie entspricht das nicht dem Sinn von daemontools.

Die Ursache liegt eben genau in mysqld_safe, der darauf vorbereitet ist von init oder mysqladmin den Befehl zum Beenden zu erhalten, aber eben nicht auf daemontools vorbereitet ist. Ich hab dem irgendwann als es mich mal wieder nervte nachrecherchiert und dabei herausgefunden, daß mysqld_safe letztlich genau das gleiche macht was daemontools tun und daß man MySQL auch wunderbar ohne ihn direkt unter daemontools laufen lassen kann.

Mit ein paar weiteren Tweaks sieht das run-Skript dann so aus:


#!/bin/sh -u
# remap STDERR to STDOUT, so all errors will show up in the logs
exec 2>&1
exec \
setuidgid mysql \
mysqld \
--defaults-file=/etc/my.cnf

Hier wird noch die Ausgabe von stderr nach stdout umgeleitet, damit man vom MySQL-Dienst auch ein nützliches Log erhält. Außerdem lasse ich den Dienst als User mysql laufen, um seine Rechte einzuschränken, wie mysqld_safe es in den meisten Konfigurationen auch tun würde.

Ein weiterer Vorteil von daemontools ist auch, daß man mysqld nahezu alle Konfigurationsoptionen beim Aufruf über die Kommandozeile übergeben kann — also auch über das run-Skript. Wer möchte, kann sich die /etc/my.cnf dadurch sehr kurz halten. Bestimmte Einstellungen kann man aus der /etc/my.cnf jetzt ohnehin entfernen, z.B. braucht man keine PID-Datei mehr (hier sollte man aber aufpassen, da sich der Name und Speicherort der Binary Log Dateien anscheined nach der PID-Datei richtet, wenn man sie nicht mit --log-bin[=base_name] explizit konfiguriert hat, wie ich im vorigen Artikel beschrieben habe).

Noch ein Hinweis für einen besonderen Anwendungsfall: Im zweiten Artikel dieser Serie beschrieb ich, daß man zum Einrichten einer Replikation auf dem MySQL-Master den Befehl FLUSH TABLES WITH READ LOCK; absetzen soll und den Master dann mit mysqladmin anhalten soll, ohne daß die Sperre aufgehoben wird. Dieses Herunterfahren läßt sich mit daemontools durch den Befehl svc -t erreichen, allerdings nur, wenn daemontools nicht darauf konfiguriert sind, den Dienst sofort wieder neuzustarten.

PlayStation 3 jetzt ganz ohne Linux

29. März 2010 von Jonas Pasche

Die PS3 bietet von Haus aus ein sogenanntes „Other OS“-Feature, mit dem es möglich ist, zusätzlich zum Sony-eigenen GameOS auch Linux zu installieren. Das haben viele Leute, auch aus meinem persönlichen Umfeld, gerne genutzt – nicht zuletzt, weil die übers Zocken hinausgehenden Fähigkeiten der PS3 als Mediacenter nur recht begrenzt waren und Linux hier viel mehr Möglichkeiten bot. In erster Linie war es aber schon die schiere Rechenleistung, die den Einsatz der PS3 als Rechnerersatz auch im prominenten Umfeld salonfähig machte, so etwa als die US Air Fore 2.200 Konsolen beantragte und zumindest 1.700 bekam, nachdem sie bereits einen High-Performance-Cluster aus 336 Konsolen im Einsatz hatte. Durch die Presse ging aber auch, dass das US Immigration and Customs Enforcement Cyber Crimes Center auch einen interessanten Verwendungszweck für die PS3s gefunden hat, nämlich um die Passwörter zu knacken, mit denen mutmaßliche Pädophile ihre Kinderpornos verschlüsseln.

Damit ist jetzt Schluss. Bereits als Sony den Nachfolger herausbrachte, nämlich die PS3 Slim, war das „Other OS“-Feature weg. Ärgerlich, aber bei einem neuen Produkt natürlich Sonys gutes Recht – das betrifft ja nicht die bestehenden PS3s, bei denen „Other OS“ eben nun mal ein Ausstattungsmerkmal war. Dennoch ging es auf den betreffenden Mailinglisten hoch her, und nicht wenige sorgten sich darum, dass dieses Feature auch auf den älteren Konsolen wegfallen könnte. So hoch, dass sich Geoff Levand, der seit 2000 bei Sony für die Linux-Aktivitäten zuständig ist und auch der Maintainer der entsprechenden Kernel-Erweiterungen ist, sich dazu genötigt sah, in mehreren Postings auf cbe-oss-dev dieses Statement von seiner offiziellen Sony-Mailadresse aus zu verfassen („SCE“ steht für „Sony Computer Entertainment“, also die Tochtergesellschaft von Sony, die für die Playstation verantwortlich zeichnet):

Please be assured that SCE is committed to continue
the support for previously sold models that have the
„Install Other OS“ feature and that this feature will
not be disabled in future firmware releases.

Nun, offensichtlich hat Sony kein sehr langes Gedächtnis. Gerade einmal sieben Monate später verkündet das offizielle Playstation-Blog Informationen zum kommenden Firmware-Update 3.21:

[…] It will disable the “Install Other OS” feature that was available on the PS3 systems prior to the current slimmer models, launched in September 2009. […] If you are one of the few who use the “Other OS” feature, or if you belong to an organisation that does, then you can choose not to upgrade your system. However, doing so will mean that the following features will not be available:

  • Ability to sign in to PlayStation Network and use network features that require signing in to PlayStation Network, such as online features of PS3 games and chat.

[…]

Sehe ich das richtig? Wenn man das Firmware-Update installiert, muss man also damit leben, dass ein offizielles Feature wegfällt – und damit auch die ganzen auf dem „Other OS“ abgelegten Daten. An die kommt man dann nämlich laut Blogpost nicht mehr dran. Wenn man aber das Firmware-Update nicht installiert, muss man damit leben, dass man ein anderes offizielles Feature künftig nicht mehr nutzen kann. Was also hätten’s denn gern: Pest oder Cholera?

Wie man so mit seinen eigenen Kunden, die das Gerät mitsamt der entsprechenden Ausstattungsmerkmale gekauft haben, so behandeln kann, will sich mir einfach nicht erschließen. Aber vielleicht haben die Kunden, wenn bei der nächsten Anschaffung eines technischen Geräts auch ein Sony-Produkt zur Auswahl steht, ja ein ausreichend langes Gedächtnis.

Du machst jetzt gefälligst soziale Erfahrungen!

28. März 2010 von Jonas Pasche

Um dir die Möglichkeit zu geben, auch außerhalb von Facebook nützliche Erfahrungen im sozialen Bereich machen zu können, sind wir gelegentlich gezwungen, anderen überprüften Webseiten und Anwendungen, die sich auf die Facebook-Plattform stützen, allgemeine Daten über dich zur Verfügung zu stellen, wenn du diese besuchst (wenn du noch bei Facebook angemeldet bist).

Quelle: (Offensichtlich ernst gemeinter) Vorschlag zur Neufassung der Facebook-Datenschutzrichtlinie, der ab April gelten soll.

Ich kann mir nicht helfen, aber mit „nützliche Erfahrungen im sozialen Bereich machen“ verbinde ich eher sowas wie „in der Kirchengemeinde bei der Obdachlosenspeisung helfen“. Und nicht mal da fühlt sich jemand gezwungen (gezwungen!), mir diese Erfahrungen dadurch zu ermöglichen, dass er meine Daten ungefragt an andere weitergibt.

Die Facebook-Nutzer können den Vorschlag zur Neufassung noch bis zum 3. April kommentieren. Nicht dass ich mit einem Quell der Entrüstung rechne; wer sich bei Facebook angemeldet hat, hat ja vermutlich – und das meine ich ausdrücklich nicht bewertend – ein lockeres Verhältnis zur Privatsphäre, was ja sein gutes Recht. Oder aber – das wäre die traurigere Variante – nicht viel Ahnung davon.

Bevor man das RAID anfaßt

26. März 2010 von Christopher Hirschmann

Nur eine Anmerkung zu dem Bericht meines Kollegen Jonas über die Rettung eines RAID 5:

Ich war mal mit einem RAID 10 in einer ganz ähnlichen Situation, ich hatte kein aktuelles Backup der Daten auf dem RAID und ich konnte leider auch keine Images der Platten im RAID mehr ziehen, um bei einem gescheiterten Reparaturversuch nötigenfalls den Versuch rückgäng zu machen. Es ging auch nicht nur um ein paar Photos, sondern leider um ziemlich wichtige Daten. Ich war also sehr auf Vorsicht bedacht.

Wie schon gesagt war die Situation ganz ähnlich. Ich war mir sehr sicher, daß die Daten auf den Platten noch konsistent waren, aber die Superblöcke wollten partout nicht zusammengehen. Auch ich bin damals auf den Hinweis gestoßen, daß man das RAID auch quasi neu anlegen kann und so wieder zu intakten Superblöcken kommen kann. Aber da ich mir Fehler nicht leisten konnte, zögerte ich etwas mit der Umsetzung.

Und dann hatte ich eine Idee, die letztlich auch funktioniert hat: Man kann sich auf einem anderen Rechner mit Linux auf dem man LVM eingerichtet hat (in diesem Fall war das eine gerade nicht gebrauchte Workstation, auf der im LVM noch viel Platz war) einfach ein paar Logical Volumes einrichten und damit einen RAID einrichten. Natürlich ist ein RAID über Logical Volumes auf derselben Festplatte vollkommen sinnfrei und absolut inperformant, aber es eignet sich hervorragend um das Verhalten von mdadm zu testen. Ich hab mir also vier LVs angelegt, darüber einen RAID 10 erzeugt, darin ein Dateisystem angelegt und einfach ein paar Nutzdaten reinkopiert: eine größere Logdatei, eine MP3, einen Film. Dann habe ich das Dateisystem wieder ausgehängt und den RAID bewußt beschädigt — und zwar so daß derselbe Fehlerzustand hergestellt wurde wie auf dem „echten“ RAID 10. Ich konnte dann die Reparatur in aller Ruhe testen, man könnte sogar sagen üben, und nach erfolgreicher Reparatur des Test-RAIDs wunderbar verifizieren, ob die Daten in dem Dateisystem noch stimmten.

Das beste daran war, daß mdadm eben wirklich nicht auf das neu angelegte RAID schreibt, solange man es in degradiertem Zustand anlegt. D.h. ich konnte mir sogar mehrere Fehlversuche leisten. (Bei einem RAID 10 nicht unwahrscheinlich, man muß ja die korrekte Anordnung der Festplatten finden.) Ob das RAID korrekt zusammengefügt war hab ich jeweils damit getestet, ob ich das Dateisytem darin im Read Only Modus mounten konnte oder nicht.

Ein weiterer Vorteil dieser LVM Testumgebung: Man kann sich mit LVM wunderbar Snapshots der LVs anlegen. Wenn man dann bei den Reparaturversuchen tatsächlich etwas beschädigen sollte (kam mir damals nicht vor, aber es hätte ja), dann kann man seine Testumgebung schnell wieder in den Ausgangszustand zurückversetzen und muß sie nicht komplett neu anlegen.

Hinter der MySQL-Replikation aufräumen

26. März 2010 von Christopher Hirschmann

Dieser Artikel ist der dritte Teil einer kleinen Gruppe von Artikeln über MySQL-Backups. Die weiteren Artikel werden hier verlinkt, sobald sie erscheinen.

Hier ist der erste Artikel: MySQL-Backups, aber wie?

Hier ist der zweite Artikel: MySQL Replikation

Hier ist der vierte und letzte Artikel: MySQL mit daemontools

Nachdem ich hier schon vor einer Weile beschrieben habe wie man eine MySQL-Replikation einrichtet und dabei am Rande erwähnt habe, daß man die durch die Replikation anfallenden Binary Logs beizeiten wegrotieren sollte, will ich heute darauf eingehen, wie man das bewerkstelligen kann.

Wer unbedingt möchte, kann sich sein eigenes Skript dazu schreiben, das in allen Slaves nachschaut, welche Binary Log Datei sie gerade lesen und dann auf dem Master alle Binary Log Dateien die älter sind als die älteste von einem Slave genutze entfernt. Diese Vorgehensweise funktioniert, ist aber ein bißchen aufwendiger. Vor allem gibt es ein paar Stolperfallen. So ist es mir zum Beispiel schonmal passiert, daß MySQL sich nach einem Update veranlaßt sah, den üblichen Dateinamen der Binary Logs zu verändern und diese sogar gleich in einem ganz anderen Ordner zu speichern (der Speicherort richtet sich anscheined danach, ob und wenn ja wo PID-Dateien abgelegt werden) — nach solchen Aktionen läuft so ein Skript dann natürlich gegen die Wand. Wobei man dieser Art von Veränderungen vorbeugen kann, indem man in seiner my.cnf explizit einträgt, wo Binary Logs abzulegen sind und wie sie zu heißen haben (mit --log-bin[=base_name]). Wichtig ist bei dieser Vorgehensweise vor allem, daß man keinen Fehler macht und keinem Slave ein Binary Log wegnimmt das er noch benötigt.

Und es gibt noch eine weitere Stolperfalle: Normalerweise heißen Binary Log Dateien z.B. mysql-bin.000008 oder nach dem Hostnamen (es sei denn, man hat mit --log-bin[=base_name] etwas anderes angeordnet). Wer den üblichen Ärger mit Software gewohnt ist, ahnt jetzt schon, was kommt. Die Dateien tragen eine Nummer im Namen die hochgezählt wird — und selbstverständlich auch irgendwann überlaufen wird, später oder eben auch mal früher. Nun könnte man meinen, man hätte ja die alten Binary Logs wegrotiert, da könnte MySQL ja die alten Namen quasi recyceln. Das wird es allerdings nicht tun, denn es pflegt in der Datei mysql-bin.index einen Index der bereits verwendeten Dateinamen (den Namen kann man wieder um mit --log-bin-index[=file_name] festschreiben). Das allerbeste an diesem Index ist, daß man ihn laut der Dokumentation von MySQL nicht editieren sollte während MySQL läuft. Und man sollte ihn editieren, denn wenn sein Inhalt nicht mit dem Zustand auf der Festplatte übereinstimmt, kann das bestimmte MySQL-Versionen verwirren, besonders wenn der unten beschriebene Befehl PURGE BINARY LOGS verwendet wird — neuere Versionen geben dann wenigstens noch eine Fehlermeldung aus.

Es geht aber auch einfacher: MySQL bietet für solche Fälle die Funktion PURGE BINARY LOGS, die hier dokumentiert ist. Sie entfernt nicht nur alte Binary Logs, sie aktualisiert auch den Index.

Hier hat man zwei Möglichkeiten, man kann entweder den Namen der ältesten Datei, die erhalten bleiben soll verwenden und z.B. befehlen PURGE BINARY LOG TO 'mysql-bin.001024'; oder man orientiert sich an einem Datum und befiehlt PURGE BINARY LOG BEFORE '2010-03-26 00:00:00';. Beides hat letztlich den gleichen Effekt, es ist eher eine Geschmacksfrage, welche Variante man nutzen möchte. Die erste hat den Vorteil, daß man das älteste noch benötigte Binary Log relativ bequem von einem Slave abfragen kann. Bei der zweiten Variante müßte man vom Slave abfragen wie viele Sekunden er hinter dem Master zurückliegt und dann den passenden Zeitpunkt berechnen.

Ein Vorteil von PURGE BINARY LOG ist in jedem Fall, daß es keinem aktiven Slave ein Binary Log wegnehmen wird. Sollte man das versuchen oder aus Versehen machen, dann wird MySQL den Fehler abfangen. Moneyquote: „This statement is safe to run while slaves are replicating. You need not stop them. If you have an active slave that currently is reading one of the log files you are trying to delete, this statement does nothing and fails with an error.“ Aber das gilt nur für Slaves, die zu diesem Zeitpunkt eine aktive Verbindung zum Master haben: „However, if a slave is not connected and you happen to purge one of the log files it has yet to read, the slave will be unable to replicate after it reconnects.“

Wer darauf vertraut, daß seine Slaves nicht allzuweit hinterherhinken, kann sogar die Variable expire_log_days in MySQL setzen, dann kümmert sich MySQL automatisch darum, daß die Binary Logs nach der angegebenen Anzahl von Tagen gelöscht werden. Sowas sollte man ggf. mit genügend Abstand einrichten und sich mit Nagios oder etwas ähnlichem einen Check bauen, der einen warnt wenn ein Slave dieser Grenze näher kommt.

Wir verwenden für diese Aufgabe hier ein einfaches Skript, das ich als Referenz hier verlinke.

Wenn sonst nichts mehr beim kaputten RAID5 hilft

25. März 2010 von Jonas Pasche

Ein Kundeskunde ist stolzer Besitzer einer Synology CubeStation. Das sind kleine NAS-Server für daheim, auf Linux-Basis mit einem proprietären Webinterface, aber immer noch mit einer Shell. Je nach Modell sind unterschiedlich viele Platten verbaut; hier konkret vier. Jede Platte hat drei Partitionen, wobei die ersten beiden kleineren jeweils ein kleines RAID1 für die Root- bzw. Swap-Partition darstellen (und hier ergo dreifache Redundanz haben); die jeweils dritten – großen – Partitionen sind zu einem RAID5 ohne Spare zusammengefasst, das als /volume1 gemountet wird, was bei den Synology-Boxen offensichtlich für die eigentliche Ablage von Daten benutzt wird.

Eine der Platten ging kaputt. Kein Problem, dafür hat man ja das RAID. Beim Ausbau der defekten Platte wurde allerdings offensichtlich das Stromkabel einer der weiteren Platten gelockert. Wir wissen nicht genau, was passiert ist, aber der Zustand war der, dass die ersten beiden RAIDs problemlos zusammengesetzt werden konnten; das dritte aber nicht. /dev/sda3 und /dev/sdc3 waren da, /dev/sdb3 fehlte (weil ausgebaut), und /dev/sdd3 – die Platte mit dem Wackelkontakt am Strom – hatte zwar einen md-superblock, der aber nicht mit den anderen harmonierte. Zwar stimmte die UUID; allerdings war der Update-Timestamp nicht ganz synchron. Ein

mdadm --assemble /dev/md2 /dev/sd[acd]3

schlug von daher fehl. Nun ist das an sich kein großes Problem: Man kann mdadm einfach zwingen und die md-superblocks dann wieder neu schreiben. Also:

mdadm --assemble --force /dev/md2 /dev/sd[acd]3

Denkste. Zwar bereitet sich mdadm schon korrekt vor und informiert darüber, dass es den abweichenden update count der dritten Partition anzupassen gedenkt. Dann jedoch stirbt es mit einem Segmentation Fault.

Offensichtlich ein bekanntes Problem. Und zwar nicht spezifisch für die Syno-Box; auch der Ubuntu-Bugtracker findet:
mdadm seg faults when forced to assemble array. Das Problem scheint also spezifisch für mdadm 2.6.7 zu sein – schon bei der 2.6.7.1 soll es angeblich wieder gehen.

Nun ist auf der Syno-Box aber eben die 2.6.7 – und ein Update ist nicht in Sicht. Was also tun? Mein erster Gedanke dazu (und ich bleibe dabei, so schlecht war er nicht): IPKG installieren, und darüber dann eine hoffentlich wahlweise ältere oder neuere Version von mdadm installieren, die diesen Bug nicht hat. Und wenn’s keine gibt, bliebe immer noch die Möglichkeit, über IPKG einen Compiler zu installieren und mdadm 2.6.7.1 aus den Sourcen zu kompilieren.

Es gibt ein mdadm in IPKG. Ein 2.6 – also älter. Nur behauptet /opt/sbin/mdadm --examine (also das via IPKG installierte, im Gegensatz zum vorinstallierten /sbin/mdadm) bei allen Partitionen, dort keinen md-superblock vorzufinden. Hm, vielleicht zu alt? Also doch die Variante mit dem Compiler, und dann ein mdadm aus den Sourcen kompiliert. Noch eine neuere Version genommen. Und dann noch eine. Und noch eine ältere. Aber es half nichts: Alle Versionen fanden keinen md-superblock. Nicht mal eine aus den Sourcen kompilierte 2.6.7, obwohl das doch nun die identische Version zur offiziell installierten ist.

(Gängige Reparaturanleitungen empfehlen, die Platten aus der Syno in einen normalen PC zu bauen – wofür man erstmal entsprechend viele freie SATA-Ports bräuchte – und dann dort den Superblock von der little-endian-Byteorder der ARM-CPU der Syno auf big endian einer Intel-CPU zu ändern, damit das RAID auf einem Intel-PC wieder zusammengesetzt werden kann. Mein Bauchgefühl sagt mir, dass das Problem mit den überhaupt nicht mehr erkannten md-superblocks etwas damit zu tun haben könnte. Allerdings habe ich das mdadm ja auf der Syno kompiliert, also auch mit deren Headerfiles. Insofern müsste die Byteorder eigentlich stimmen. Hm.)

Lange Rede, kurzer Sinn: Nichts half. Bis auf das RAID-Wiki bei kernel.org, das den entscheidenden Tipp gab:

When an array is created, the data areas are not written to, *provided* the array is created in degraded mode; that is with a ‚missing‘ device.

Ich denke, man kann sich das in etwa so vorstellen wie eine defekte Partitionstabelle auf einer Festplatte: Es scheint alles weg; keine Partition ist mehr lesbar. Dabei ist klar, dass die Daten an sich alle noch auf der Platte vorhanden sind – man kommt nur ohne Partitionstabelle nicht mehr dran. Man muss es also „nur“ schaffen, die paar Bytes Partitionstabelle wiederherzustellen, und das Problem ist gelöst.

So auch hier: Wird bei einem mdadm --create ein Device als „missing“ angegeben, wird der Datenbereich der Platten in Ruhe gelassen und nur der Superblock neu geschrieben. Das ist natürlich nur was für echte Männer. Naja, oder für Männer wie mich, die sich vorher versichert haben, dass der Kunde Images der Platten gesichert hat, falls bei den Arbeiten am RAID irgendetwas schief geht. Auf jeden Fall muss man für so ein „Reparatur-create“ allerdings wissen, wie dieser Superblock auszusehen hat: Welcher RAID-Level? Wieviele Platten? Welche Platte in welchem RAID-Slot? Welche Chunk-Size? Welche Symmetrie? Das war in diesem Fall einfach: Alle drei Partitionen haben ja schließlich noch einen md-superblock, der all diese Parameter verrät. Letztlich war es also nur das hier:

mdadm --create /dev/md2 --chunks=64 --level=5 --raid-devices=4 \
  /dev/sda3 missing /dev/sdc3 /dev/sdd3

mdadm bemerkte korrekt, dass auf den Partitionen bereits etwas drauf sei, was nach einem RAID riechen würde. Okay, wissen wir ja. Nach einer entsprechenden Bestätigung wurden die Superblocks neu geschrieben. Keine Fehlermeldung. Ein

mount /dev/md2 /volume1

funktionierte anstandslos. Alle Daten vorhanden; auch nach einem Reboot erkennt die Syno-Box das RAID5 jetzt wieder von selbst und bindet es ein. Nach einer jetzt natürlich umgehend anstehenden Datensicherung wäre es von daher im Bereich des Möglichen, nun wieder die vierte, noch fehlende Platte einzubauen und einen Resync zu wagen.

Mein Kunde berichtet vom Kundeskunden, dieser sei „grad total hingerissen davon (echt jetzt)“. Schöne Sache, wobei es hier weniger um ein geschäftliches Desaster gegangen wäre, sondern „nur“ etliche Gigabyte an Fotos über den Jordan hätten gehen können. Aber die sind manchmal ja noch viel wertvoller. Ich gehe davon aus, ab heute macht der Kundeskunde Backups.

greylite kann nur 8 Minuten

25. März 2010 von Jonas Pasche

An sich ist auch das Übertragen größerer Dateien per SMTP kein Problem mit qmail, solange die Mail vom Umfang her unter dem in control/databytes angegeben Limit bleibt. Abbrüche aufgrund von Timeouts nimmt qmail-smtpd nur dann vor, wenn es 20 Minuten lang überhaupt keine Daten mehr vom Client bekommt (einstellbar in control/timeoutsmtpd). Solange Daten übertragen werden, werden die auch verarbeitet – auch wenn’s Stunden dauert.

Nun beklagte sich ein „Kundeskunde“, der nur eine 64-kbit/s-Leitung sein eigen nennt, darüber, dass beim Versenden größerer Dateien die Verbindung immer nach einigen Minuten abbrechen würde, ohne eine Fehlermeldung des Servers. Thunderbird sagt dann:

The message could not be sent because the connection to SMTP server $HOSTNAME was lost in the middle of the transaction.

Wir konnten uns zunächst keinen Reim darauf machen. Selbst wenn qmail-smtpd die Verbindung wegen eines Timeouts dichtmachen würde, dann doch zumindest mit einer entsprechenden Meldung (wobei fraglich wäre, ob diese den Client dann noch erreicht). Also doch ein Netzproblem?

Im Logfile von qmail-smtpd fand sich nur das hier:

2010-03-24 14:42:49.222521500 6412 > [EOF]
2010-03-24 14:42:49.222721500 tcpserver: end 6412 status 14

Die erste Zeile resultiert daraus, dass wir ein recordio haben mitlaufen lassen, um dem Problem auf die Spur zu kommen. Schnell war analysiert, dass derartige Abbrüche immer nach exakt 8 Minuten verzeichnet werden. Passt also auch nicht zur qmail-smtpd-Konfiguration. Mein Kollege Andreas schlug vor, die Sourcen der beteiligten Programme mal ganz stumpf nach „480“ (Sekunden = 8 Minuten) zu durchsuchen. Im gesamten qmail-Source fand sich wie erwartet nichts. Auch nicht im Source von ucspi-tcp. Aber dann, ein Treffer in greylite.c:

/* timeout in seconds for one read/write operation (seconds) */
#define TIMEOUT                     40
/* big timeout after which exiting (seconds) */
#define BIG_TIMEOUT                 480

Kurz darauf wird dann ein Alarm-Handler auf diesen Timeout gesetzt, der an sighand verzweigt:

    signal(SIGALRM, sighand);
    signal(SIGPIPE, sighand);
    alarm(BIG_TIMEOUT);
    [...]
void sighand(int sig) {
    switch (sig) {
        case SIGALRM:
            logmsg(LOG_WARNING, "Big timeout reached from '%s'!", client_metainfo.address);
            break;
        case SIGPIPE:
            logmsg(LOG_INFO, "Client '%s' closed connection prematurely.", client_metainfo.address);
            break;
    }
    exit(100);
}

Also: Nach 8 Minuten kappt greylite die Verbindung, egal was gerade ist oder ob gerade noch eine Mail überträgt. Hm, das ist ja mal ’ne tolle Wurst. Wir haben aus der 480 mal eine 1800 gemacht, neu kompiliert, und der Kunde bestätigt: Jetzt gibt’s kein Problem mehr.

Fraglich bleibt, wieso wir keine „Big timeout reached“-Meldung im Logfile vorgefunden haben. Manchmal gibt’s die nämlich durchaus. Aber manchmal stirbt das greylite auch kommentarlos weg – aber eben unzweifelhaft nach jenen 8 Minuten.

DSPAM automatisch trainieren

23. März 2010 von Jonas Pasche

Wir setzen auf einigen Accounts DSPAM zur Filterung ein. Den Brückenschlag zwischen qmail und DSPAM schafft hierbei maildrop:

$ cat info/.qmail
|preline maildrop $HOME/dspamfilter

Der entsprechende Abschnitt in dspamfilter sieht dann so aus:

# DSPAM die Mail bewerten lassen
xfilter "/command/dspam --deliver=innocent,spam --stdout"

# Mail wegsortieren, wenn DSPAM das meint
if( /^X-DSPAM-Result: Spam/ )
{
  to "./Maildir/.0 Spamfilter.als Spam erkannt/"
}

# Ansonsten: In normales Maildir zustellen
to ./Maildir/

Nun basiert DSPAM letztlich auf Algorithmen aus dem Bereich der Statistik. Grob vereinfacht gesagt führt es eine Datenbank, in der er Wörter und Wortgruppen mit einem Wert zwischen 0 und 1 versieht, der aussagt, ob die Verwendung eher auf Spam (0) oder legitime Mails, also Ham (1) hindeutet. Anfangs haben alle Wörter einen mittleren Wert, der sich dann mit der Zeit durch Training nach oben oder unten verschiebt – oder in der Mitte bleibt, wenn ein Wort sowohl in Spam als auch in Ham häufig vorkommt.

Nun gibt es ab und zu aber Fälle, wo selbst das mehrfache Umlernen einer nicht als Spam erkannten Mail via „als Spam lernen“-Ordner einfach nicht den gewünschten Effekt erzielt; insbesondere dann, wenn sich DSPAM sowieso schon nicht so sicher ist, weil selbst die signifikantesten gefundenen Wörter und Wortgruppen eher im Mittelfeld liegen als gegen 0 oder 1 zu tendieren. Die Hauptforderung von Endkunden lautet dann schnell: „Setzt doch den Absender auf die Blacklist“. Diese Möglichkeit bietet qmail natürlich durch aus (wie control/badmailfrom), aber so global wollen wir solche Blacklists nicht pflegen. Aber in der Filterkonfiguration des Users, der sich das Blacklisting wünscht, können wir so etwas natürlich realisieren. Die einfachste Möglichkeit wäre sicherlich so etwas hier:

if( /^From: boeserspammer/ )
{
  to "./Maildir/.0 Spamfilter.als Spam erkannt/"
}

Allerdings würden wir ja schon gerne davon profitieren, dass die DSPAM-Treffergenauigkeit auch weiterhin berücksichtigt, dass der User diese Mails nicht haben will. Also haben wir an diesem Punkt ein automatisches Umlernen mit eingebaut, und damit das Filterscript flexibel bleibt, benutzen wir für die Liste der zu blacklistenden Adressen keinen fest eingebauten regulären Ausdruck, sondern die lookup-Funktion von maildrop, die reguläre Ausdrücke aus einer externen Datei einlesen kann.

Und so sieht’s aus:

# DSPAM die Mail bewerten lassen
xfilter "/command/dspam --deliver=innocent,spam --stdout"

# Wenn Mails von bekannten Spammern nicht als Spam erkannt wurden, als Spam umlernen
`test -f "dspamfilter-autolearn-spam"`
if( $RETURNCODE == 0 && ! /^X-DSPAM-Result: Spam/ && /^From:.*/ && lookup($MATCH, "dspamfilter-autolearn-spam") )
{
  xfilter "/command/dspam --class=spam --source=error --deliver=innocent,spam --stdout"
}

# Mail wegsortieren, wenn DSPAM das meint
if( /^X-DSPAM-Result: Spam/ || /^X-DSPAM-Reclassified: Spam/ )
{
  to "./Maildir/.0 Spamfilter.als Spam erkannt/"
}

# Ansonsten: In normales Maildir zustellen
to ./Maildir/

Es ist zu beachten, dass das X-DSPAM-Result trotz Umlernen identisch bleibt – DSPAM fügt lediglich noch einen X-DSPAM-Reclassified: Spam-Header hinzu. Also haben wir das Kriterium fürs Wegsortieren noch entsprechend erweitert.

Funktioniert prima; Kunde zufrieden. Die Pflege der Liste unerwünschter Adressen per einfacher Textdatei braucht praktisch keine Einarbeitung und kann schnell nebenbei erfolgen.


Impressum