Neuigkeiten von trion.
Immer gut informiert.

PHP in Docker

Docker

Auch wenn mit Docker die Containertechnologie fast überall Einzug hält, gibt es immer wieder in der praktischen Arbeit Herausforderungen zu meistern. Ganz besonders dann, wenn die Anwendung, die in einen Container verbracht werden soll, nicht dafür ausgelegt wurde, entsprechend betrieben zu werden. Vor allem im PHP Umfeld finden sich oft Anwendungsarchitekturen, die der Docker Philosophie "Ein Container, eine Aufgabe" entgegen stehen.

Ein möglicher Ansatz zur Lösung ist die Nutzung von Docker lediglich zur Ausführung der PHP- und Webserverprozesse und separate Verwaltung der PHP Programmdateien. Ein Host-Mount kann die Dateien in beliebige Container einbinden - doch verliert man dann gerade die Stärke von Docker, sowohl für Rollout als auch Rollback eine handhabbare Einheit bestehend aus Anwendungsserver und Programmdateien bereitzustellen.

Das Problem besteht bei PHP Anwendungen in Docker darin, dass diese typischerweise aus mehreren Komponenten bestehen:

  • PHP-FPM für die Ausführung von PHP als Resultat von Web-Anfragen

  • Cron Jobs, oft ebenfalls als Trigger von PHP Scripten für periodische und asynchrone Aufgaben

  • Memcached oder Redis als Cache

  • Eine Datenbank wie MySQL oder PostgreSQL

  • Ein Webserver (nginx, Apache httpd, …​) zur Auslieferung statischer Dateien

Zumindest die ersten beiden Dienste benötigen Zugriff auf die selben PHP Programmdateien. Einen Scheduler und PHP-FPM im selben Container auszuführen passt nicht so ganz zur reinen Lehre von Docker, ist aber solange leicht zu verschmerzen, wie lediglich eine Container-Instanz läuft.

Sowieso als externe Resourcen verwaltete Dienste wie die Datenbank oder einen Memcached in einem separaten Container laufen zu lassen ist naheliegen. Doch wie geht mit man Software um, die für eine gänzlich andere Betriebsumgebung konzipiert wurde?

Am Beispiel typischer PHP Projekte kann man die unterschiedlichen Vorgehensweisen und damit einhergehende Eigenschaften betrachten.

PHP Single Container Variante

Diese Variante ist sicherlich am leichtesten umzusetzen. Docker, oder die jeweils genutzte Container-Technologie, wird wie eine virtuelle Maschine verwendet und im Container alle benötigten Prozesse gestartet.

Im einfachsten Fall kann dies ein Apache httpd sein, der die Erweiterung mod_php aktiviert hat. Da der Apache httpd sowohl aktive Inhalte durch mod_php, als auch statische Dateien über die eigene Funktionalität ausliefert, kann man zwar darüber diskutieren, dass die Verantwortlichkeiten nicht dogmatisch sauber getrennt sind. Doch zum einen findet sich dieser Ansatz auch bei anderen Plattformen, bspw. Tomcat im Java Umfeld, zum anderen stört es in der Praxis nicht, wenn neben den PHP Prozessen auch ein Webserver als gemeinsame Deploymenteinheit verwaltet wird.

Soll statt dem Apache httpd ein PHP-FPM mit nginx zum Einsatz kommen, können diese durch ein Shell-Script oder einen supervisord gestartet werden.

Dieses Setup ist relativ robust und einfach aufzusetzen, lediglich bei dem Prinzip "Ein Container, eine Aufgabe" müssen Abstriche gemacht werden.
Als Konsequenz daraus resultiert, dass eine horizontale Skalierung des Containers mit der Skalierung eines Webservers einher geht, da mehrere Dienste in dem selben Container abgebildet werden, laufen diese allesamt mehrfach.

Schwierig wird es, wenn bspw. ein Cron-Scheduler gemeinsam mit im Container läuft. Dies kann zu unerwünschten Effekten führen, wenn die selben Aktionen parallel mehrfach durchgeführt werden, da der Container skaliert wurde. Es spricht jedoch prinzipiell nichts dagegen, wenn der Scheduler zwar im Container ist, jedoch - z.B. durch eine spezielle Umgebungsvariable - sichergestellt ist, dass er lediglich in einem Container aktiv wird.

Typische Beispiele für diesen Ansatz sind die phpBB Images von bitnami und blueimp

PHP Multi-Container mit redundanten Daten

In dieser Variante werden mehrere Images gebaut und jeder erhält die für seinen Einsatzzweck relevanten Daten.

PHP Dateien werden beispielsweise in ein PHP-FPM und ein separates Cron Image gelegt, alle statischen Daten werden in das Image für den Webserver gepackt. Das Deployment kann dann mittels docker-compose verwaltet werden.

Als Herausforderung zeigt sich hier, dass die Image Versionen für die gemeinsam betriebenen Container synchron gehalten werden müssen, und mehrere Docker (oder OCI) Images gebaut werden müssen.

Diese Variante funktioniert nicht, falls durch PHP Dateien anlegt werden, die anschließend durch den Webserver ausgeliefert werden sollen. Als Abhilfe könnte für diese Dateien, wenn sie sich durch ein separates Verzeichnis sauber trennen lassen, ein gemeinsames Volume oder sogar ein Host-Mount in Frage kommen. Berücksichtigt eine Anwendung ein Cloud-native Deployment als Szenario würden variable Daten in einem Object-Store, z.B. Amazon S3, oder einer vergleichbaren Infrastruktur gespeichert. Das bringt den Vorteil mit sich, dass die Dateien in der Regel ohne Umweg über einen eigenen Webserver direkt vom Content-Delivery-Network des Providers ausgeliefert werden können.

Im Gegenzug für den etwas höheren Aufwand bei Build und Verwaltung können die Container unabhängig voneinander skaliert werden, und damit beispielsweise sichergestellt werden, dass der Cron Container lediglich einmal läuft.

Für dieses Design finden sich keine Vertreter von populären Projekten.

PHP Multi-Container mit gemeinsamen Volume

Diese Variante erfüllt die Anforderungen, lediglich einen Dienst pro Container zu betreiben und ein gemeinsam genutztes Dateisystem zu verwenden. Die Umsetzung erfolgt so, dass ein spezieller Docker ENTRYPOINT verwendet wird, in dem die PHP Daten aus dem Quell-Image in ein anonymes VOLUME kopiert werden. Das Volume kann dann durch andere Container gemounted werden, und dient somit als shared-storage. Damit das Volume in anderen Containern nutzbar wird, wird mit dem Parameter --volumes-from gearbe

Beispiel ENTRYPOINT mit dem die PHP Dateien in ein shared-volume propagiert werden
if [ ! -e index.php ]; then
  tar cf - --one-file-system -C /usr/src/app . | tar xf -
  chown -R www-data .
fi
exec $$0 [email protected]

Vorteilhaft an der Variante ist, dass keine zusätzlichen Images gebaut werden müssen. Auf der anderen Seite ist die Vorgehensweise eher als Hack zu bezeichnen, denn als saubere Architektur.

Zudem kann lediglich ein anonymes Volume verwendet werden, wenn Updates durch ein neues Docker Image bereitgestellt werden sollen: Nur bei anonymen Volumes ist die Vorraussetzung erfüllt, dass das Volume initial leer ist, und lediglich die aktuellsten und noch benötigten Dateien in das Volume kopiert werden.

Ein Vertreter dieser Vorgehensweise ist Wordpress - das kommt dann allerdings mit fast 300 Zeilen Startup-Script daher: https://github.com/docker-library/wordpress/tree/master/php7.1/fpm-alpine

Auch bei dem Matomo Projekt wird dieser Ansatz verfolgt, wie im ENTRYPOINT zu sehen ist https://github.com/matomo-org/docker/blob/master/docker-entrypoint.sh

Fazit

Für welche Variante man sich letztlich auch entscheidet, man merkt PHP deutlich an, dass eine Möglichkeit fehlt, statische Dateien auszuliefern: So ist man stets auf einen zusätzlichen Webserver angewiesen.

Ein Apache mit mod_php lässt sich mit Docker deutlich einfacher handhaben, als ein nginx mit php-fpm. Gibt es keine zusätzlichen Dienste, wie bspw. Cron, kann damit ein Single-Container die einfachere und robustere Lösung darstellen.

Möchte man als Entwickler, dass sich eine Anwendung sowohl gut in einer Container basierten Cloud, als auch in einem klassischen Umfeld anfühlt, so ist dies frühzeitig beim Design zu berücksichtigen. Typischerweise wird die zu verwendende Persistenzlösung für zur Laufzeit erzeugte oder durch Nutzer übermittelte Daten sowohl eine lokale Auslieferung durch PHP als auch die Verwendung von S3 artigen Objectstores unterstützen müssen.




Zu den Themen Kubernetes, Docker und Cloud Architektur bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.


Los geht's!

Bitte teilen Sie uns mit, wie wir Sie am besten erreichen können.