Die Grenzen des Webservers und darüber hinaus… – Verwaltung von mehr als 256 virtuellen Hosts unter Apache

Einführung

Wer einen Server mit mehr als 200 virtuellen Hosts betreibt wird früher oder später auf unerwartete Probleme treffen. Der Webserver verweigert seinen Dienst, Dateien an denen nichts geändert wurden, können plötzlich nicht mehr gelesen werden und all dies passiert auch noch so, daß man es nicht immer nachstellen kann.

Die Symptome und Hintergründe

Wenn der Webserver beim Aufruf von sonst zugreifbaren Dateien auf einmal nicht mehr lesbar sind und es zu einem Fehler mit einem Code über 400 kommt, kann dies folgende Gründe haben:

  1. Die Datei ist wirklich nicht vorhanden oder nicht lesbar. In der Errorlog finden sich solche Eintraege:
    [Tue Aug 27 10:34:29 2002] [error] [client XXX.XXX.XXX.XXX] File does not exist:
    /irgendwo/irgendwas/5_dokt_on.gif
    [Tue Aug 27 10:34:29 2002] [error] [client XXX.XXX.XXX.XXX] File does not exist:
    /irgendwo/irgendwas/06_sekr_on.gif
    [Tue Aug 27 10:34:29 2002] [error] [client XXX.XXX.XXX.XXX] File does not exist:
    /irgendwo/irgendwas/01_univ_on.gif

    In anderen Worten: Jemand hat einfach eine nicht vorhandene Datei aufgerufen. Entweder ist ein Link falsch, es war ein dummer User, oder aber der Webseitenbastler hat einfach vergessen eine Datei anzulegen oder korrekt zu bennen.
    Als Betreuer der Serversoftware haben wir nichts zu tun. Non est mea culpa.

  2. Es gibt eine .htaccess-Datei, die den Zugriff verhindert. Ist aber eher selten. In der Errorlog sieht dies so aus:
    [Tue Aug 27 10:19:24 2002] [error] [client XXX.XXX.XXX.XXX] client denied by server
    configuration: /EN/index.shtml
    [Tue Aug 27 10:19:29 2002] [error] [client XXX.XXX.XXX.XXX] client denied by server
    configuration: /EN/pic/pfeil3.gif

    Hier kann der Client nicht auf die Dateien zugreifen, weil es aufgrund von Serverinstellungen explizit verboten ist. Entweder gibt es eine Datei .htaccess im Webverzeichnis, der die Zugriffsrechte einschränkt, oder aber in der Konfiguration des Webserver ist eine entsprechende Einstellung. Das obige Beispiel war von einer .htaccess-Datei generiert, wo der Zugriff auf gewisse IP-Adressen beschraenkt war. Folgendes Beispiel zeigt den verbotenen Zugriff auf eine Datei, die bereits aus der httpd.conf verboten wird:

    [Tue Aug 27 10:43:04 2002] [error] [client XXX.XXX.XXX.XXX] client denied by server
    configuration: /proj/webbin/cgi-bin/formmail.pl

    Man sieht in der Log keinen Unterschied zu dem vorherigen Beispiel. Hier jedoch wurde der Zugriff in der httpd.conf durch folgende Einstellung verhindert (die uebrigens bei allen bekannten unsicheren Skripten zu empfehlen ist):

    <Location /cgi-bin/formmail*>
       Deny from all
       ErrorDocument 403 http://www.meine_domain.tld/errorskript.shtml
    </Location>
    <Location /cgi-bin/phf*>
       Deny from all
       ErrorDocument 403 http://www.meine_domain.tld/errorskript.shtml
    </Location>

    Als Webmaster kann man nun zwei Dinge tun: Die Achseln zucken und sich darüber feuen, dass der Zugriffsschutz funktionierte, oder aber diesen anpassen, wenn er zu streng ist.

  3. Alle Filesystem-Handles, die dem Apache zur Verfügung stehen, werden bereits genutzt. In der Errorlog sieht dies bei einem Solaris-System und Apache 1.3.26 wie folgt aus:
    [Tue Aug 27 09:06:57 2002] [error] [client XXX.XXX.XXX.XXX] (2)No such file or
    directory: file permissions deny server access: /irgendwo/irgendwas/infopack.pdf
    [Tue Aug 27 09:07:13 2002] [error] [client XXX.XXX.XXX.XXX] (2)No such file or
    directory: file permissions deny server access: /irgendwo/irgendwas/index_e.html

    Diese Meldungen treten dabei recht gehäuft auf… Ausserdem stellen wir bei der Ansicht der Dateien und Verzeichnisse fest: Alles ist lesbar, ggf. sogar für alle User auf dem Filesystem. Es gibt eigentlich keinen Grund, warum die Datei nicht lesbar sein sollte.

    Wenn sowas passiert haben wir möglicherweise Problem: Wir erreichen die Grenzen unseres Systems. Und wir sind da nicht die ersten. In der Bug-Datenbank von Apache finden sich bereits Hinweise und Fragen hierzu. Beispiele:

    Die dort vorgeschlagenen Lösungen dort sind jedoch nicht gerade prickelnd.

Die Grenzen des Systems

Das Problem tritt laut Apache.org (FAQ) je nach OS ab 128 bis 250 virtuellen Hosts auf, welche eigene Logdateien haben. Vergleiche:

Je nach OS ist die maximale Anzahl der virtuellen Hosts, die eigene Logdateien benutzen, ebenfalls begrenzt durch die Anzahl der verfuegbaren File-Descriptoren. Der Apache kann nicht mehr als X Files gleichzeitig geöffnet haben. Diese Anzahl haengt ab vom OS und den zur Verfügung stehenden Ressourcen, die den Webserver zugewiesen werden:

Anzahl der offenen Dateien <= Soft Limit <= Hard Limit <= Kernel Limit

Schaut man sich diese bei unterschiedlichen OS an, kommt man zu folgenden Zahlen für die maximal möglichen Filehandles, die offen sein koennen:

BSDI: 240
FreeBSD: 240
Linux: 256
Solaris: 240
AIX bis 3.2: 128
ab 4.1.5: 2000

Lösungen

Natuerlich kann man der Lösung folgen, dass man keine eigenen Logdateien mehr für die virtuellen Hosts zulaesst. Stattdessen läßt man alles in der Standardlog-Datei speichern. Diesen Tipp kann man dann auch bei der Apache-FAQ nachlesen. Schliesslich fallen damit gleich eine grosse Anzahl von Filehandles die nur für die Verwaltung der Logfiles benutzt werden, weg. Jedoch ist dieser Tipp nicht unbedingt immer gut:

Wer bereits über etwa 240 virtuelle Hosts auf den Webserver betreibt, hat in der Regel auch entsprechend viele Zugriffszahlen, worunter auch ein erkleklicher Anteil an fehlerhaften Anfragen und Brute-Force-Attacken auf CGI- und PHP-Skripten zu finden sind. Wird jeder Zugriff auf alle Hosts in einer einzigen Logdatei gespeichert, kann diese sehr schnell sehr gross werden. Wenn die Datei grösser wird, als wie das OS es verkraften kann (bei alten UNIX- und Linuxversionen und bei normalen Windowsversionen liegt die Grenze bei 2 GB), dann kann es zu einem Crash des Webservers kommen. Wenn man diese Moeglichkeit also nutzt, muss man also unbedingt darauf achten, dass man die „zentrale Logdatei“ wieder aufsplittet, die Logs also nicht in eine normale Datei geschrieben werden, sondern in ein Skript, das den <STDIN> verteilt.

Apache bietet seit der Version 1.3 hierzu ein eigenes Shell-Skript an, welches in Apache-Sourceverzeichnis src/support/split-logfile zu finden ist. Damit dieses funktioniert, muss man in der httpd.conf die Einstellung für das Logfile-Format so aendern, dass der Hostname am Anfang steht.

Beispielsweise das Common Log Format:

LogFormat	"%v %h %l %u %t \"%r\" %>s %b"

und damit dann den Aufruf von split-logfile über TransferLog:

TransferLog "| /pfad/zu/split-logfile.pl"

Damit es jedoch nicht zu Nebeneffekten kommt, duerfen bei diesen Fall in den virtuellen Hosts keine eigenen TransferLog’s mehr angegeben sein, da dann die Summe der Filediscriptoren doch wieder über die kritische Grenze steigt.

Diese Lösung ist ansonsten relativ praktikabel. Sie ist jedoch nur das Verschieben des Menetekels auf spaetere Zeiten und auf einen anderen Bereich. Zudem gelten auch für diesen Prozess Filesystem-Beschraenkungen, die zur Folge haben, dass nicht alle Logdateien vollstaendig geschrieben werden.

Das Skript split-logile ist ein Perlskript, bei welchem die Summe der Filehandle abhaengig ist von den Kernel-Parametern im OS. Mit Hilfe des Shellkommandos ‚limit‘ (bzw. ‚limits‘ oder ‚ulimit‘) kann man sich die Zahl der Gesamt zur Verfuegung stehenden Filediscriptoren anzeigen lassen. Beispiel unter Solaris:

xwolf@eisbaer: 16:28 [~] > limit
cputime         unlimited
filesize        unlimited
datasize        2097148 kbytes
stacksize       8192 kbytes
coredumpsize    0 kbytes
vmemoryuse      unlimited
descriptors     1024
xwolf@eisbaer: 16:28 [~] >

Reicht auch diese Gesamtzahl nicht aus, kann man versuchen, diese Zahl durch Neucompilierung des Kernels zu aendern. Empfehlen wuerde ich dies jedoch nicht. Grund: Ein Rechner, der als Webserver im netz erreichbar ist, ist dauernden Angriffen ausgesetzt. Dies erzwingt quasi, dass man bei erscheinen von Patches für das OS diese schnell einbindet – am Besten noch über Autopatch-Mechanismen. Viele dieser Mechanismen gehen jedoch von Standardkonfigurationen des Kernels aus. Hat man einen modifizierten Kernel, kann es unter Umstaenden notwendig sein, von Hand nachzuarbeiten. Und wann wollen Sie in Urlaub gehen? Können Sie sicher sein, daß auch alle ihre Kollegen wissen, was zu tun ist, wenn nach dem Einspielen eines Patches auf einmal ein paar Hundert Domains nicht mehr zugreifbar sind?

Eine weitere Lösungsmöglichkeit liegt darin, zwei oder mehr Apache-Webserver auf einer Hardware zu verwenden. In diesem Fall laege die Grenze der offenen Dateien im OS. Der gravierende Nachteil an dieser Methode liegt jedoch darin, dass nur eine Apache-Instanz auf den Port 80, welcher der Standardport für Web ist, lauschen darf. Alle virtuellen Hosts, die von anderen Apache-Instanzen verwaltet werden wuerden, muessten jeweils auf einen anderen Port liegen.

Meiner Meinung nach besteht die sinnvollste Lösung in einer Hardware-Erweiterung, d.h. dem Aufbau eines Webserver-Parks. In anderen Worten: Jede Server enthaelt eine einzige Apache-Installation mit jeweils maximal 220 – 240 virtuellen Hosts. Hinzu kommt ein Server für die Fallbacksicherung und ein Server als Proxy. Ein weiterer Server sollte für Datenbanken zur Verfuegung stehen.

Beispielsweise würde die Architektur bei 600 virtuellen Hosts wie folgt aussehen:

Der Fallback-Server erhält Zugriff auf alle Webbereiche und kann die Konfigurationsdateien aller Webserver lesen. Eine Überwachung sollte dafür sorgen, daß der Fallback ggf. automatisch die Funktionen eines der Hauptwebserver ersetzt.
Damit der Fallback-Server auf alle Webbereiche und die lokalen Konfigurationen zugreifen kann, muss ein Konzept vorhanden sein, wie die Verzeichnisse und Dateien angeordnet sind.

So sollten auf allen Webservern die Webdateien und Verzeichnisse beispielsweise unter dem Ordner

/proj.stand/websource

abgelegt werden. für einen virtuellen Host www.blafasel.de auf dem Server 1 beispielsweise:

/proj.stand/websource/server1/www.blafasel.de

Dieser Bereich wird auf alle angeschlossenen Server gemountet wie

/proj/websource/server1/www.blafasel.de

Dies hat auch Sicherheitstechnische Vorteile: Kunden kann der Filesystem-Zugang zu den eigentlichen Webservern verschlossen bleiben. Man erlaubt nur das Einloggen auf einen speziell dafür vorgesehenen Server. Die Webserver kann man somit dicht machen, d.h. alle unnötigen Dienste abschalten. Ein weiterer Vorteil liegt auch darin, dass eine optionale globale Suchmaschine leichten Zugriff auf alle Dateien über das Filesysten erhalten koennte.

Im DNS wird ein gesonderte Konfiguration verwendet, damit der Fallbackserver schnell und sogar automatisch anspringen kann: Alle Domainnamen sind CNAME-Records (auch Aliase genannt), die auf einen Server-Namen weisen. Dieser Server-Name ist nun mit der IP des Rechners verbunden.

Beispiel:

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
$ORIGIN         meine_webserver.tld
;   Definition des Servers 1
www1             IN      A       IP.ADR.ESS.E1
www2             IN      A       IP.ADR.ESS.E2

$ORIGIN         domainname1.tld
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
localhost       IN      A       127.0.0.1
www             IN      CNAME   www1.meine_webserver.tld.
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
$ORIGIN         domainname2.tld
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
localhost       IN      A       127.0.0.1
www             IN      CNAME   www1.meine_webserver.tld.
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
$ORIGIN         domainname3.tld
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
localhost       IN      A       127.0.0.1
www             IN      CNAME   www2.meine_webserver.tld.
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

(Die Variable $ORIGIN im obigen Beispiel ist eine Abkürzung für einen SOA-Eintrag, in welchem die Nameserver, Version, Refreshrate und andere Daten angegeben sind.)

Die Domains www.domainname1.tld und www.domainname2.tld sind nach dieser Anordnung dem Server 1 zugeordnet und www.domainname3.tld dem Server 2. Wenn nun der Server 1 ausfallen wuerde, muss allein in der Zeile

www2             IN      A       IP.ADR.ESS.E2

die IP-Adresse geaendert werden auf die Adresse des Fallbackservers. In den Eintraegen der eigentlichen Webdomains braucht man nichts aendern.

Entsprechend wie mit den Webdateien wird auch mit den Logdateien und den Serverkonfigurationen und der Serversoftware verfahren. Auf den Bereichen Serversoftware, OS und optionaler Module für die Webserver (wie FastCGI, PHP, SSL usw.) sind alle Server, zzgl. dem Fallback-Server gleichwerige Kopien.

Ein Problem, dass sich hier auftut (was aber auch schon vorher vorhanden war): Zugriffsrechte der User auf dem Filesystem. Hier muss dafür Sorge getragen werden, dass die jeweiligen Webverzeichnisse nur von den Usern selbst und dem Webserver gelesen werden koennen. Leider reicht es nicht aus, mit SUXEC zu arbeiten, da der Webserver dieses nur auf Skripten, nicht jedoch auf Verzeichnisse anwendet und PHP-Skripten überhaupt nicht durch SUEXEC erfasst werden. Lösungen sind hierfür unter Unix/Linux das Setzen von ACLs und das Nutzen von NIS-Maps. Doch dies jetzt zu beschreiben würde den Umfang dieses Artikels sprengen.

Von der Hardware her muessen Proxy und Datenbankserver hoch zuverlässig sein. Alle Webserver muessen sehr sicher gegen Hackerattacken sein. Ich empfehle hier Rechner von Sun mit Solaris, wobei Proxy und Datenbankserver speziell zugeschnitten sein sollten auf die dort laufenden Anwendungen (z.B. ist beim Proxy sehr viel RAM notwendig). Diese Server sollten Rechner der höheren Leistungsklasse (Enterprise, SunBlade1000, Suncat,..) sein. Die Webserver und der Fallbackserver sollten die gleiche Hardware aufweisen. Als Hardware reichen Server der mittleren Leistungsklasse (Enterprise, Sunblade100) mit viel RAM und jeweils mindestens 50 GB Festplattenplatz.