Aufsetzen eines Apache/PHP Applikationsservers


Aufsetzen eines Apache/PHP Applikationsservers

Title: Applikationsserver Aufsetzen
Author: Christian Folini
Tutorial Nr: 3
Letztes Update: 04.02.2020
Erscheinungsdatum: 11.10.2010
Schwierigkeit: Mittel
Dauer: 1/2h

Was machen wir?

Wir setzen einen Apache / PHP Applikationsserver mit den minimal nötigen Modulen auf.

Warum tun wir das?

Ein nackter Apache eignet sich, um statische Files auszuliefern. Komplexere Applikationen bauen auf dynamischem Content auf. Dies bedingt eine Erweiterung des Servers. Ein sehr schneller und sicherer Applikationsserver lässt sich mit Suexec, einem externen FastCGI-Daemon und PHP realisieren. Dies ist bei weitem nicht die einzige Variante und im Unternehmensumfeld vermutlich auch nicht die verbreitetste. Aber es ist eine sehr einfache Architektur, die sich hervorragend für ein Testsystem eignet.

Voraussetzungen

Schritt 1: Apache konfigurieren

Wir konfigurieren den Webserver vorneweg; wohlwissend, dass er in dieser Konfiguration noch nicht lauffähig ist. Basierend auf dem in Lektion 2 beschriebenen minimalen Webserver konfigurieren wir einen sehr einfachen Applikationsserver in der Datei conf/httpd.conf_fastcgid.

ServerName              localhost
ServerAdmin             root@localhost
ServerRoot              /apache
User                    www-data
Group                   www-data

ServerTokens            Prod
UseCanonicalName        On
TraceEnable             Off

Timeout                 10
MaxRequestWorkers       100

Listen                  127.0.0.1:80

LoadModule              mpm_event_module        modules/mod_mpm_event.so
LoadModule              unixd_module            modules/mod_unixd.so

LoadModule              log_config_module       modules/mod_log_config.so
LoadModule              mime_module             modules/mod_mime.so

LoadModule              authn_core_module       modules/mod_authn_core.so
LoadModule              authz_core_module       modules/mod_authz_core.so

LoadModule              suexec_module           modules/mod_suexec.so
LoadModule              fcgid_module            modules/mod_fcgid.so

ErrorLogFormat          "[%{cu}t] [%-m:%-l] %-a %-L %M"
LogFormat               "%h %l %u [%{%Y-%m-%d %H:%M:%S}t.%{usec_frac}t] \"%r\" %>s %b \
\"%{Referer}i\" \"%{User-Agent}i\"" combined

LogLevel                debug
ErrorLog                logs/error.log
CustomLog               logs/access.log combined

AddHandler              fcgid-script .php

DocumentRoot            /apache/htdocs

<Directory />

        Require all denied

        Options         SymLinksIfOwnerMatch

</Directory>

<VirtualHost 127.0.0.1:80>

      <Directory /apache/htdocs>

        Require all granted

        Options          ExecCGI

        FCGIWrapper      /apache/bin/php-fcgi-starter/php-fcgi-starter

      </Directory>

</VirtualHost>

Ich gehe nicht mehr auf die gesamte Konfiguration ein, sondern nur noch auf die Abweichungen zur Anleitung 2. Hinzugekommen sind drei Module: Neben dem Suexec- und dem FCGI-Modul ist es das Mime-Modul, welches uns erlaubt, die Dateiendung .php dem FCGI-Daemon zuzuweisen. Diese Zuweisung erfolgt mittels der AddHandler-Direktive.

Das Verzeichnis /apache/htdocs benötigt neu die zusätzliche Option ExecCGI. Schliesslich der FCGIWrapper. Das ist das Verbindungsstück zwischen dem Webserver und dem noch zu konfigurierenden FCGI-Daemon. Sobald die erste Anfrage mit der Endung .php an den Webserver gestellt wird, ruft der Server ein Wrapper-Script auf und startet damit den FCGI-Daemon, der sich ab diesem Moment um die PHP-Anfragen kümmert.

FastCGI ist eine Technik um dynamischen Programmcode aus einem Webserver heraus aufzurufen. Es ist eine sehr schnelle Technik, welche den Server weitgehend unberührt lässt und die Applikation auf einem separaten Daemon ausführt. Um die Geschwindigkeit zu steigern stellt FastCGI mehrere Instanzen dieses Daemons bereit, so dass eine Anfrage ohne Wartepause verarbeitet werden kann. In der Praxis ist dies ein ansprechender Performance-Gewinn und vor allem eine Architektur, welche Speicher spart, wie unten genauer erklärt wird.

Schritt 2: Apache mir Suexec-Unterstützung kompilieren

Wir müssen nun zwei fehlende Module kompilieren und weitere Komponenten für den FCGI-Daemon bereitstellen. Beginnen wir mit dem Suexec-Modul.

In Lektion 1 wurde ein Apache Webserver selbst kompiliert. Aber obschon dort die Option --enable-mods-shared=all verwendet wurde, ist suexec noch nicht kompiliert. Das Modul ist demnach so speziell, dass es nicht als Standard-Modul mitkompiliert werden kann, obschon es im Quelltext vorhanden ist.

Der in Lektion 2 konfigurierte Webserver läuft als Benutzer www-data oder je nach Konfiguration als beliebiger anderer dedizierter Benutzer. Unsere dynamische Applikation möchten wir noch weiter eingrenzen und den separaten Daemon unter einem weiteren separaten User drehen lassen. Diesen User kreieren wir in einem der nächsten Schritte. Dies wird uns durch das Modul suexec ermöglicht. Diese Massnahme ist nicht zwingend. Aber sie bringt ein Mehr an Sicherheit mit wenig zusätzlichem Aufwand.

Begeben wir uns ins Verzeichnis mit dem Apache Quelltext und kompilieren den Server neu.

$> cd /usr/src/apache/httpd-2.4.41
$> ./configure --prefix=/opt/apache-2.4.41 --enable-mods-shared=all \
--with-apr=/usr/local/apr/bin/apr-1-config --with-apr-util=/usr/local/apr/bin/apu-1-config \
--enable-mpms-shared="event" --enable-nonportable-atomics=yes \
--enable-suexec --with-suexec-caller=www-data --with-suexec-docroot=/opt/apache-2.4.41/bin && \
make && sudo make install

Zum bekannten configure sind nun drei Optionen hinzugekommen, die sich um das Suexec kümmern. Enable-suexec spricht für sich, with-suexec-caller teilen wir dem gewissenhaften Modul mit, dass ausschliesslich der User www-data die Erlaubnis erhalten soll, das Modul mit dem dahinter liegenden Programm aufzurufen. Schliesslich geben wir dem Modul noch bekannt, wo die aufzurufenden Skripte liegen sollen. Der Einfachheit halber nehmen wir das existierende bin-Verzeichnis. Allerdings ist suexec pingelig und wir können nicht mit dem Symlink arbeiten. Es muss also der voll qualifizierte Pfad sein.

Nach der erfolgreichen Konfiguration wird obenstehende Befehlszeile den Compiler aufrufen und nach dessen erfolgreichem Abschluss den neu-kompilierten Server installieren.

Übrigens: Wenn man bei der obenstehenden Konfiguration einen Fehler macht und Suexec andere Compilerkonstanten mitgeben möchte, dann muss man vor dem erneuten Kompilieren zuerst die Compiler-Umgebung reinigen. Ansonsten werden die neuen Optionen ignoriert. Dies können wir über den Befehl make clean vor dem configure erreichen, oder durch manuelles Löschen der Dateien support/suexec, support/suexec.lo und support/suexec.o, was schneller geht, weil danach nicht mehr der gesamte Webserver neu gebaut werden muss.

Schritt 3: FastCGI Modul herunterladen und kompilieren

Das FastCGI-Modul wird von Apache verwaltet. Es ist aber nicht Teil des normalen Quellcodes des Webservers. Laden wir den Quelltext für das zusätzliche Modul also herunter und prüfen wir die über eine verschlüsselte Verbindung geladene Checksumme.

$> cd /usr/src/apache
$> wget http://www.apache.org/dist/httpd/mod_fcgid/mod_fcgid-2.3.9.tar.gz
$> wget https://www.apache.org/dist/httpd/mod_fcgid/mod_fcgid-2.3.9.tar.gz.sha1
$> sha1sum --check mod_fcgid-2.3.9.tar.gz.sha1

Wir erwarten wieder ein OK. Wenn dies korrekt retourniert wurde, ist es Zeit für das Entpacken, Kompilieren und Installieren.

$> tar -xvzf mod_fcgid-2.3.9.tar.gz
$> cd mod_fcgid-2.3.9
$> APXS=/apache/bin/apxs ./configure.apxs
$> make
$> sudo make install

Der Configure-Befehl hat hier ein etwas anderes Format, da es sich beim FCGI-Modul um ein von Apache abhängiges Modul handelt. Wir verwenden deshalb APXS, das Apache Expansion Tool, das Teil des in Lektion 1 kompilierten Servers ist. Leider zerstört das make install teilweise die von uns gesetzten Besitzverhältnisse. Dies muss also nachjustiert werden.

$> sudo chown `whoami` /apache/conf/httpd.conf

Weiter benötigt der Apache-Benutzer Zugriff auf ein Verzeichnis, in das er Sockets anlegt, um mit dem FCGI-Daemon kommunizieren zu können. Dieses erstellen wir und übergeben es ihm direkt.

$> sudo mkdir /apache/logs/fcgidsock
$> sudo chown www-data:www-data /apache/logs/fcgidsock

Schritt 4: PHP installieren und vorkonfigurieren

Bislang haben wir die ganze Software Stück für Stück selbst kompiliert. Beim ganzen PHP-Stack ist aber eine Grenze erreicht. Es soll niemandem verwehrt werden, PHP selbst zu kompilieren, hier konzentrieren wir uns aber auf den Webserver und übernehmen dieses Stück Software deshalb aus der Linux-Distribution. In Debian/Ubuntu heisst das entsprechende Paket php7.0-cgi und es zieht php7.0-common nach sich.

PHP richtig zu konfigurieren ist ein weites Feld und ich empfehle die einschlägigen Seiten zu konsultieren, denn ein falsch konfiguriertes PHP kann ein grosses Sicherheitsproblem darstellen. Hier möchte ich nicht mehr Informationen dazu geben, da es von unserem eigentlichen Thema, dem einfachen Applikationsserver, wegführen würde. Für den Betrieb im Internet, also nicht mehr im heimischen Laborbereich, ist es aber deutlich angezeigt, sich mit den relevanten PHP-Sicherheitseinstellungen vertraut zu machen.

Schritt 5: CGI User erstellen

Oben wurde bereits beschrieben, dass wir planen, einen separaten Daemon zur Bearbeitung der PHP-Anfragen zu starten. Dieser Daemon soll mittels suexec gestartet werden und als eigenständiger Benutzer laufen. Wir erstellen diesen Benutzer folgendermassen:

$> sudo groupadd fcgi-php
$> sudo useradd -s /bin/false -d /apache/htdocs -m -g fcgi-php fcgi-php

Es ist zu erwarten, dass eine Warnung betreffend des vorhandenen Verzeichnisses /apache/htdocs erscheint. Diese können wir aber ignorieren.

Schritt 6: PHP-Wrapper Skript erstellen

Es ist üblich, PHP und FCGI mittels eines Wrapper-Skripts zusammenarbeiten zu lassen. Wir haben das oben auch schon in der Apache Konfiguration so vorgesehen. Der Webserver wird ausschliesslich dieses Skript aufrufen, während sich das Skript um alles weitere kümmert. Wir legen das Skript wie vorgesehen in /apache/bin/php-fcgi-starter/php-fcgi-starter ab. Das Unterverzeichnis ist nötig, denn suexec setzt voraus, dass das Verzeichnis dem vorkonfigurierten Benutzer gehört und wir möchten ./bin nicht komplett dem neuen Benutzer übergeben. Legen wir das Unterverzeichnis also an:

$> cd /apache/bin
$> sudo mkdir php-fcgi-starter
$> sudo chown fcgi-php:fcgi-php php-fcgi-starter

Wir müssen nun in diesem Verzeichnis ein Starter-Skript platzieren. Da wir das Verzeichnis bereits dem Benutzer fcgi-php übergeben haben, muss das Skript durch den Root-Benutzer erstellt werden. Oder durch ihn an diesen Ort kopiert werden. Das Erstellen eines Skripts in einem Verzeichnis, das uns nicht mehr gehört, ist bisweilen schwierig. Wir lösen das mit einem Trick mittels cat und einer Sub-Shell. Hier der Trick und daran anschliessend das Skript. (Die Eingabe in cat wird mittels STRG-D abgeschlossen).

$> sudo sh -c "cat > php-fcgi-starter/php-fcgi-starter"
#!/bin/sh
export PHPRC=/etc/php/7.0/cgi/
export PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_CHILDREN=5
exec /usr/lib/cgi-bin/php

Was legen wir hier fest? Wir geben PHP bekannt, wo sich seine Konfiguration befindet, legen die maximale Zahl der Requests eines FCGI-Daemons auf 5'000 fest (danach wird er durch einen frischen Prozess ersetzt), wir bestimmen die Zahl der Prozess-Kinder auf 5 und rufen zu guter Letzt PHP selbst auf.

Nun nicht vergessen, das Skript auch dem FCGI-Benutzer zu übergeben und ausführbar zu machen:

$> sudo chown fcgi-php:fcgi-php php-fcgi-starter/php-fcgi-starter
$> sudo chmod +x php-fcgi-starter/php-fcgi-starter

Schritt 7: PHP-Testseite erstellen

Zum Schluss sollten wir noch eine einfache php-basierte Testseite erstellen: /apache/htdocs/info.php.

<?php
phpinfo();
?>

Schritt 8: Ausprobieren

Das war alles. Nun können wir den Webserver starten und ausprobieren.

$> cd /apache
$> sudo ./bin/httpd -X -f conf/httpd.conf_fastcgid

Erreichbar ist unser Testskript unter der URL http://localhost/info.php.

Screenshot: phpinfo()! Im Browser zeigt phpinfo einen umfassenden Statusbericht.

Ergibt der Start des Servers oder der Aufruf der URL eine Fehlermeldung, dann weiss das Fehler-Log des Servers oder das separate Suexec-Log unter _logs/suexeclog Abhilfe. Typische Fehler betreffen Besitz und Zugriffsrechte auf Verzeichnisse und Files.

Hier zur Zusammenfassung nochmals die relevanten Files und ihre Besitzer:

2107985    4 drwxr-xr-x   2 fcgi-php fcgi-php     4096 Jul  2 11:15 bin/php-fcgi-starter/
2107987    4 -rwxr-xr-x   1 fcgi-php fcgi-php      125 Jul  2 11:15 bin/php-fcgi-starter/php-fcgi-starter
2107977   32 -rwsr-xr-x   1 root     root        32146 Jul  2 10:44 bin/suexec
2633547    4 drwxr-xr-x   2 myuser   root         4096 Jul  2 11:16 htdocs/
2633758    4 -rw-r--r--   1 myuser   myuser         20 Jul  2 11:15 htdocs/info.php
2762281    4 drwxr-xr-x   2 www-data www-data     4096 Jul  2 10:46 /apache/logs/fcgidsock/

Auffällig ist das Suid-Bit auf dem Suexec-Binary.

Schritt 9 (Bonus): Ein kleiner Lasttest

Der hier gebaute Applikations-Server ist im Vergleich zu einem Apache mit eingebautem PHP sehr leistungsfähig. Ein kleiner Lasttest kann dies illustrieren. Wir starten unseren Webserver im Daemon-Mode und benutzen Apache-Bench um ihm mit 5 Usern auf den Zahn zu fühlen. Neu ist die Option -l, welche das Werkzeug instruiert, auf Abweichungen in der Länge der Antworten nicht weiter einzugehen. Denn die Seite ist ja dynamisch generiert und der Inhalt, sowie dessen Länge, weichen natürlicherweise immer wieder ein wenig ab. Nach dem Lasttest beenden wir den Server wieder.

$> sudo ./bin/httpd -k start -f conf/httpd.conf_fastcgid
$> ./bin/ab -c 5 -n 1000 -l http://localhost/info.php
...
$> sudo ./bin/httpd -k stop -f conf/httpd.conf_fastcgid

In meinem Fall lieferte ab folgenden Output:

This is ApacheBench, Version 2.3 <$Revision: 1663405 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        Apache
Server Hostname:        localhost
Server Port:            80

Document Path:          /info.php
Document Length:        Variable

Concurrency Level:      5
Time taken for tests:   2.567 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      66892443 bytes
HTML transferred:       66739443 bytes
Requests per second:    389.63 [#/sec] (mean)
Time per request:       12.833 [ms] (mean)
Time per request:       2.567 [ms] (mean, across all concurrent requests)
Transfer rate:          25452.57 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:     4   13  70.9      7    1147
Waiting:        2   12  70.8      6    1143
Total:          4   13  70.9      7    1147

Percentage of the requests served within a certain time (ms)
  50%      7
  66%      9
  75%     10
  80%     11
  90%     14
  95%     17
  98%     24
  99%     28
 100%   1147 (longest request)

Das sind 389 dynamische Requests pro Sekunde. Das sind viele. Besonders weil das Resultat von einem kleinen Testrechner stammt. Auf einem modernen Server in Produktionsgrösse lässt sich ein Vielfaches davon realisieren.

Bemerkenswert ist aber weniger die Geschwingigkeit des Systems als der Speicherverbrauch. Im Gegensatz zu einem Applikationsserver mit integriertem PHP-Modul haben wir den PHP-Stack hier ausgelagert. Das erlaubt es uns einen Apache Webserver mit Event-MPM zu benutzen. In einem integrierten Setup müssten wir den Prefork-MPM verwenden, der nicht mit Serverthreads, sondern mit speicherhungrigen Serverprozessen arbeitet. Und jeder dieser Prozesse würde dann auch noch das PHP-Modul laden, unbesehen davon, dass die meisten Requests in der Regel auf statische Applikationsteile wie Bilder, CSS, Javascripts etc. entfallen. Auf meinem Testsystem schlägt jeder Prefork-Apache-Prozess inklusive PHP mit 6 MB Resident Size zu Buche. Ein Event-Prozess mit lediglich 4 MB und die Zahl der externen FCGI-Prozesse bleibt deutlich kleiner.

Newsletter

Hat dieses Tutorial Spass gemacht? Dann wäre doch unser Newsletter mit Infos zu neuen Artikeln hier bei netnea das Richtige. Hier geht es zum Einschreiben.
Der Newsletter erscheint in englischer Sprache.

Verweise

Lizenz / Kopieren / Weiterverwenden

Creative Commons License
Diese Arbeit ist wie folgt lizenziert / This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Changelog

  • 4. Februar 2020: Apache Update (2.4.41)
  • 28. Oktober 2019: Änderungen im deutschen Text gemäss englischem Text nachgezogen
  • 17. Dezember 2017: Updated Apache auf Version 2.4.29, einige Links gefixt
  • 25. September 2017: Update of apr (1.6.2), apr-util (1.6.0) and apache (2.4.27)
  • 25. Februar 2017: AllowOverride komplett entfernt
  • 28. Dezember 2016: Apache 2.4.23->2.4.25
  • 20. Dezember 2016: Timeout auf 10 gesetzt, MaxRequest auf 100, Kosmetik
  • 25. August 2016: Zeilenumbrüche justiert
  • 04. März 2016: Rechtschreibefehler korrigiert
  • 16. Januar 2016: Lizenzhinweis eingefügt
  • 29. Oktober 2015: Rechtschreibung (Benjamin Affolter)
  • 29. September 2015: html -> markdown, Update auf Apache 2.4, Option "-l" für ab
  • 9. Juli 2013: Bemerkung zum Erstellen des PHP-Starter-Skripts durch den Root-Benutzer; "chown" des fgcidsock-Verzeichnisses
  • 2. Juli 2013: Rechtschreibfehler in Konfiguration korrigiert (DefaultType vs. ContentType)
    Apache Version von 2.2.21 auf 2.2.25 erh&ouml;ht.
    Fcgid Version von 2.3.6 auf 2.3.7 erh&ouml;ht und download URL angepasst.
    PHP Screenshot erneuert.
  • 12. Oktober 2011: Version von 2.2.17 auf 2.2.21 erh&ouml;ht
  • 27. Februar 2011: Kommandos korrigiert
  • 25. Februar 2011: Kommandos und Rechtschreibfehler korrigiert
  • 9. Januar 2011: &Uuml;berarbeitet
  • 11. November 2010: &Uuml;berarbeitet
  • 8. November 2010: Erstellt