Adventskalender 5. Dezember 2023: Nieder mit PHP!

Smalltalk
Antworten
Benutzeravatar
TRex
Moderator
Beiträge: 8086
Registriert: 23.11.2006 12:23:54
Wohnort: KA

Adventskalender 5. Dezember 2023: Nieder mit PHP!

Beitrag von TRex » 05.12.2023 01:29:29

Praise the python! :mrgreen:

Heute zeige ich euch, wie man ohne den Seuchenhaufen ( :P ) PHP Webservices betreiben kann, und dabei kein CGI schreiben muss.

Der Stack, den ich euch demonstrieren möchte, besteht aus nginx (wobei das eher Geschmackssache ist, lighttpd würde es auch tun), gunicorn und einem Webservice in python.

nginx kümmert sich um den http-Teil und leitet die Anfragen an einen unix-Socket weiter, gunicorn stellt den Socket und verteilt die Anfragen auf worker. Um den Vergleich mit LAMP und DAMPF vom Tag 1 ein bisschen realistischer zu gestalten, hab ich noch ne NoSQL-DB integriert - das beabsichtigte Konfliktpotential hab ich dabei aber versehentlich eliminiert :mrgreen:
Üblicherweise setze ich da auf leichtgewichtige Lösungen auf Dateibasis wie tinydb oder sqlite, die aber nicht mit mehreren Workern funktionieren würden, und mongodb war zu meiner Enttäuschung nicht in den Repos...

Genug geschwafelt, hier die Schritte zum Setup:

1. nginx-site:

Code: Alles auswählen

$ cat /etc/nginx/sites-enabled/down-with-php 
server {
   listen 8008;
   server_name magni;

   location / {
           include proxy_params;
           proxy_pass http://unix:/home/me/Projekte/down-with-php/app.sock;
   }
}
2. gunicorn (systemd-Unit, die unter meinem Benutzer läuft)

Code: Alles auswählen

$ systemctl cat down-with-php.service 
# /etc/systemd/system/down-with-php.service
[Unit]
Description=Gunicorn instance to serve my project
After=network.target

[Service]
User=thorsten
Group=www-data
WorkingDirectory=/home/me/Projekte/down-with-php
ExecStart=/home/me/.virtualenvs/down-with-php/bin/gunicorn --workers 3 --bind unix:app.sock -m 007 main:app

[Install]
WantedBy=multi-user.target
Installiert habe ich dazu Debiannginx, Debianredis-server und die üblichen Pakete für virtualenvs, um dann mit letzterem ein virtualenv mit den Abhängigkeiten für die App zu installieren (hier: gunicorn, flask, flask-redis).

3. Der App-Code ist hier zu finden:

main.py: NoPaste-Eintrag42024
templates/index.html: NoPaste-Eintrag42025
templates/view_paste.html: NoPaste-Eintrag42026

Und damit man die Performance irgendwie greifbar machen kann, ein Performance-Test in/für Debianpython3-locust:

NoPaste-Eintrag42027

Die Grenzen hab ich damit nicht ausgelotet, das dürft ihr tun :) Sehr viel Spielraum ist da bei gunicorn, wobei der Flaschenhals aber eher bei der App zu suchen wäre. Für mich ist das aber selten wichtig. Das war auch mein erster Ausflug mit Redis als NoSQL-Ersatz (ohne konfigurierte Persistenz auch eher witzlos :D ), viel anders als mit der mongodb wars aber nicht (auf dem Niveau).

Für die Interessierten, wie funktioniert das unter der Haube?
  1. nginx (oder lighttpd) übernimmt die Annahme von http-Verbindungen, bei nginx spricht man beim Ziel von "upstream". Ist das nicht da oder kaputt, sieht man HTTP/502 oder 503 - Gateway kaputt. In der obigen Konfiguration ist das ein unix-Socket, man kann aber auch eine TCP-Verbindung verwenden (wenn das Ziel ebenfalls HTTP spricht). Braucht man denn dann noch einen Webserver? Naja, streng genommen nicht. Es gibt nur viele Dinge, die nginx und co besser machen als die wsgi-Server. Außerdem kann man damit auch gleich sowas wie vhosts aufsetzen ;)
  2. gunicorn: alternativ kann man auch uwsgi verwenden, die Tools tun ähnliches, die Kernfunktionalität ist gleich. Ist wie vim vs emacs (von außen betrachtet). gunicorn bekommt erklärt, was es zu starten hat und wie viele Resourcen es dafür verwenden soll - also wie viele "Worker" gestartet werden sollen, ob die multithreaded sein sollen... Das ist aber nicht zu verwechseln mit php-fpm, was meines Wissens nach zustandslose Worker in einem Pool sind. Hat die Anwendung irgendwie state, hat jeder der Worker diesen. Darum konnte ich da auch nicht sowas wie ne sqlite-Verbindung verwenden (wobei PHP mit parallelen Zugriffen da auch früher oder später Konflikte produziert hätte, das hat man eben mit Parallelität - es hätte mit python vermutlich nur bereits sehr viel früher geknallt).
  3. die eigentliche Anwendung: muss ein WSGI-Interface implementieren, flask tut das (bottle würds auch tun). Der python-Code der main.py wird beim import ausgeführt, das main:app (in gunicorns Kommandozeile) zeigt auf die Variable app (vom Typ flask.Flask) und da findet gunicorn alles zum Starten der Anwendung. Für Entwicklungszwecke startet man in der Regel direkt den eingebauten httpd in python via flask, siehe main.py am Ende.
Für "richtige" deployments könnte man noch darüber nachdenken, das virtualenv in ein pex zu verpacken und somit alle Abhängigkeiten in einer Datei zu haben.
Und natürlich hätte ich auch mysql anbinden können - entweder roh und ungeschminkt mit python-mysql oder mit dem ORM-Layer von sqlalchemy - aber ich mag die notwendige Komplexität von RBDMS nicht, wenn ich sie nicht brauche.


Mein persönliches Fazit zu python-Webservices: man kann damit unheimlich schnell sehr akzeptable Ergebnisse erzielen, und die Sprache ist vielleicht nicht so sicher wie rust, aber php in dem Aspekt weit überlegen 8)


Ein paar Links für weiterführende Informationen:

Doku zu flask: https://flask.palletsprojects.com/en/2.3.x/
Alternative bottle: https://bottlepy.org/
gunicorn: https://gunicorn.org/
oder uwsgi: https://uwsgi-docs.readthedocs.io/en/latest/
Was pex wäre: https://pex.readthedocs.io
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: Adventskalender Tag 5: Nieder mit PHP!

Beitrag von paedubucher » 05.12.2023 08:05:45

Vielen Dank für dieses schöne Türchen! Ich habe ja jahrelang mit Python programmiert, ums Deployment musste ich mich aber nie in dieser Tiefe kümmern. Da mir Redis äusserst sympathisch ist (siehe letzjährigen Adventskalender), kommt dieser schlanke Stack für mich durchaus auch in Frage!

Noch eine Frage zum Starten von Gunicorn via systemd-Unit:

Code: Alles auswählen

ExecStart=/home/me/.virtualenvs/down-with-php/bin/gunicorn --workers 3 --bind unix:app.sock -m 007 main:app
Das -m ist ja der umask-Parameter, richtig? Was sind die praktischen Auswirkungen dieser Einstellung? Ist das eine Best Practice, oder hat es hier eine ganz konkrete Funktion?

PS: Morgen früh wird von PHP zurückgeschossen :wink:
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Benutzeravatar
TRex
Moderator
Beiträge: 8086
Registriert: 23.11.2006 12:23:54
Wohnort: KA

Re: Adventskalender Tag 5: Nieder mit PHP!

Beitrag von TRex » 05.12.2023 08:47:02

paedubucher hat geschrieben: ↑ zum Beitrag ↑
05.12.2023 08:05:45
Das -m ist ja der umask-Parameter, richtig? Was sind die praktischen Auswirkungen dieser Einstellung? Ist das eine Best Practice, oder hat es hier eine ganz konkrete Funktion?
Der default ist 0, d.h. jeder darf mit dem Socket interagieren - das drehts runter auf user und group.
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
Meillo
Moderator
Beiträge: 8818
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: Adventskalender Tag 5: Nieder mit PHP!

Beitrag von Meillo » 05.12.2023 08:58:06

Kleine Abwandlung des Titels: s/PHP/IT/ 8)
Use ed once in a while!

Benutzeravatar
paedubucher
Beiträge: 856
Registriert: 22.02.2009 16:19:02
Lizenz eigener Beiträge: GNU Free Documentation License
Wohnort: Schweiz
Kontaktdaten:

Re: Adventskalender Tag 5: Nieder mit PHP!

Beitrag von paedubucher » 05.12.2023 16:25:15

TRex hat geschrieben: ↑ zum Beitrag ↑
05.12.2023 08:47:02
paedubucher hat geschrieben: ↑ zum Beitrag ↑
05.12.2023 08:05:45
Das -m ist ja der umask-Parameter, richtig? Was sind die praktischen Auswirkungen dieser Einstellung? Ist das eine Best Practice, oder hat es hier eine ganz konkrete Funktion?
Der default ist 0, d.h. jeder darf mit dem Socket interagieren - das drehts runter auf user und group.
Danke für die Erläuterung! Ich dachte schon, es geht um Dateien die im Kontext des CGI-Programms erstellt werden, konnte mir aber nicht vorstellen, welche Dateien das betreffen sollte.
Habe nun, ach! Java
Python und C-Sharp,
Und leider auch Visual Basic!
Durchaus programmiert mit heissem Bemühn.
Da steh' ich nun, ich armer Tor!
Und bin so klug als wie zuvor.

Antworten