Beim Deployment von FastAPI-Anwendungen besteht ein gängiger Ansatz darin, ein Linux-Containerimage zu erstellen. Normalerweise erfolgt dies mit Docker. Sie können dieses Containerimage dann auf eine von mehreren möglichen Arten bereitstellen.
Die Verwendung von Linux-Containern bietet mehrere Vorteile, darunter Sicherheit, Replizierbarkeit, Einfachheit und andere.
Tipp
Sie haben es eilig und kennen sich bereits aus? Springen Sie zum Dockerfile unten 👇.
Dockerfile-Vorschau 👀
FROMpython:3.9WORKDIR/codeCOPY./requirements.txt/code/requirements.txt
RUNpipinstall--no-cache-dir--upgrade-r/code/requirements.txt
COPY./app/code/app
CMD["uvicorn","app.main:app","--host","0.0.0.0","--port","80"]# Wenn Sie hinter einem Proxy wie Nginx oder Traefik sind, fügen Sie --proxy-headers hinzu# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"]
Container (hauptsächlich Linux-Container) sind eine sehr leichtgewichtige Möglichkeit, Anwendungen einschließlich aller ihrer Abhängigkeiten und erforderlichen Dateien zu verpacken und sie gleichzeitig von anderen Containern (anderen Anwendungen oder Komponenten) im selben System isoliert zu halten.
Linux-Container werden mit demselben Linux-Kernel des Hosts (Maschine, virtuellen Maschine, Cloud-Servers, usw.) ausgeführt. Das bedeutet einfach, dass sie sehr leichtgewichtig sind (im Vergleich zu vollständigen virtuellen Maschinen, die ein gesamtes Betriebssystem emulieren).
Auf diese Weise verbrauchen Container wenig Ressourcen, eine Menge vergleichbar mit der direkten Ausführung der Prozesse (eine virtuelle Maschine würde viel mehr verbrauchen).
Container verfügen außerdem über ihre eigenen isoliert laufenden Prozesse (üblicherweise nur einen Prozess), über ihr eigenes Dateisystem und ihr eigenes Netzwerk, was die Bereitstellung, Sicherheit, Entwicklung usw. vereinfacht.
Ein Container wird von einem Containerimage ausgeführt.
Ein Containerimage ist eine statische Version aller Dateien, Umgebungsvariablen und des Standardbefehls/-programms, welche in einem Container vorhanden sein sollten. Statisch bedeutet hier, dass das Container-Image nicht läuft, nicht ausgeführt wird, sondern nur die gepackten Dateien und Metadaten enthält.
Im Gegensatz zu einem „Containerimage“, bei dem es sich um den gespeicherten statischen Inhalt handelt, bezieht sich ein „Container“ normalerweise auf die laufende Instanz, das Ding, das ausgeführt wird.
Wenn der Container gestartet und ausgeführt wird (gestartet von einem Containerimage), kann er Dateien, Umgebungsvariablen usw. erstellen oder ändern. Diese Änderungen sind nur in diesem Container vorhanden, nicht im zugrunde liegenden bestehen Containerimage (werden nicht auf der Festplatte gespeichert).
Ein Containerimage ist vergleichbar mit der Programmdatei und ihrem Inhalt, z. B. python und eine Datei main.py.
Und der Container selbst (im Gegensatz zum Containerimage) ist die tatsächlich laufende Instanz des Images, vergleichbar mit einem Prozess. Tatsächlich läuft ein Container nur, wenn er einen laufenden Prozess hat (und normalerweise ist es nur ein einzelner Prozess). Der Container stoppt, wenn kein Prozess darin ausgeführt wird.
Durch die Verwendung eines vorgefertigten Containerimages ist es sehr einfach, verschiedene Tools zu kombinieren und zu verwenden. Zum Beispiel, um eine neue Datenbank auszuprobieren. In den meisten Fällen können Sie die offiziellen Images verwenden und diese einfach mit Umgebungsvariablen konfigurieren.
Auf diese Weise können Sie in vielen Fällen etwas über Container und Docker lernen und dieses Wissen mit vielen verschiedenen Tools und Komponenten wiederverwenden.
Sie würden also mehrere Container mit unterschiedlichen Dingen ausführen, wie einer Datenbank, einer Python-Anwendung, einem Webserver mit einer React-Frontend-Anwendung, und diese über ihr internes Netzwerk miteinander verbinden.
In alle Containerverwaltungssysteme (wie Docker oder Kubernetes) sind diese Netzwerkfunktionen integriert.
Ein Containerimage enthält normalerweise in seinen Metadaten das Standardprogramm oder den Standardbefehl, der ausgeführt werden soll, wenn der Container gestartet wird, sowie die Parameter, die an dieses Programm übergeben werden sollen. Sehr ähnlich zu dem, was wäre, wenn es über die Befehlszeile gestartet werden würde.
Wenn ein Container gestartet wird, führt er diesen Befehl/dieses Programm aus (Sie können ihn jedoch überschreiben und einen anderen Befehl/ein anderes Programm ausführen lassen).
Ein Container läuft, solange der Hauptprozess (Befehl oder Programm) läuft.
Ein Container hat normalerweise einen einzelnen Prozess, aber es ist auch möglich, Unterprozesse vom Hauptprozess aus zu starten, und auf diese Weise haben Sie mehrere Prozesse im selben Container.
Es ist jedoch nicht möglich, einen laufenden Container, ohne mindestens einen laufenden Prozess zu haben. Wenn der Hauptprozess stoppt, stoppt der Container.
Wenn Sie Ihren Container hinter einem TLS-Terminierungsproxy (Load Balancer) wie Nginx oder Traefik ausführen, fügen Sie die Option --proxy-headers hinzu. Das sagt Uvicorn, den von diesem Proxy gesendeten Headern zu vertrauen und dass die Anwendung hinter HTTPS ausgeführt wird, usw.
In diesem Dockerfile gibt es einen wichtigen Trick: Wir kopieren zuerst die Datei nur mit den Abhängigkeiten, nicht den Rest des Codes. Lassen Sie mich Ihnen erklären, warum.
COPY./requirements.txt/code/requirements.txt
Docker und andere Tools erstellen diese Containerimages inkrementell, fügen eine Ebene über der anderen hinzu, beginnend am Anfang des Dockerfiles und fügen alle durch die einzelnen Anweisungen des Dockerfiles erstellten Dateien hinzu.
Docker und ähnliche Tools verwenden beim Erstellen des Images auch einen internen Cache. Wenn sich eine Datei seit der letzten Erstellung des Containerimages nicht geändert hat, wird dieselbe Ebene wiederverwendet, die beim letzten Mal erstellt wurde, anstatt die Datei erneut zu kopieren und eine neue Ebene von Grund auf zu erstellen.
Das bloße Vermeiden des Kopierens von Dateien führt nicht unbedingt zu einer großen Verbesserung, aber da der Cache für diesen Schritt verwendet wurde, kann der Cache für den nächsten Schritt verwendet werden. Beispielsweise könnte der Cache verwendet werden für die Anweisung, welche die Abhängigkeiten installiert mit:
Die Datei mit den Paketanforderungen wird sich nicht häufig ändern. Wenn Docker also nur diese Datei kopiert, kann es für diesen Schritt den Cache verwenden.
Und dann kann Docker den Cache für den nächsten Schritt verwenden, der diese Abhängigkeiten herunterlädt und installiert. Und hier sparen wir viel Zeit. ✨ ... und vermeiden die Langeweile beim Warten. 😪😆
Das Herunterladen und Installieren der Paketabhängigkeiten könnte Minuten dauern, aber die Verwendung des Cache würde höchstens Sekunden dauern.
Und da Sie das Containerimage während der Entwicklung immer wieder erstellen würden, um zu überprüfen, ob Ihre Codeänderungen funktionieren, würde dies viel Zeit sparen.
Dann, gegen Ende des Dockerfiles, kopieren wir den gesamten Code. Da sich der am häufigsten ändert, platzieren wir das am Ende, da fast immer alles nach diesem Schritt nicht mehr in der Lage sein wird, den Cache zu verwenden.
Lassen Sie uns noch einmal über einige der gleichen Deployment-Konzepte in Bezug auf Container sprechen.
Container sind hauptsächlich ein Werkzeug, um den Prozess des Erstellens und Deployments einer Anwendung zu vereinfachen, sie erzwingen jedoch keinen bestimmten Ansatz für die Handhabung dieser Deployment-Konzepte, und es gibt mehrere mögliche Strategien.
Die gute Nachricht ist, dass es mit jeder unterschiedlichen Strategie eine Möglichkeit gibt, alle Deployment-Konzepte abzudecken. 🎉
Sehen wir uns diese Deployment-Konzepte im Hinblick auf Container noch einmal an:
Wenn wir uns nur auf das Containerimage für eine FastAPI-Anwendung (und später auf den laufenden Container) konzentrieren, würde HTTPS normalerweise extern von einem anderen Tool verarbeitet.
Es könnte sich um einen anderen Container handeln, zum Beispiel mit Traefik, welcher HTTPS und automatischen Erwerb von Zertifikaten handhabt.
Tipp
Traefik verfügt über Integrationen mit Docker, Kubernetes und anderen, sodass Sie damit ganz einfach HTTPS für Ihre Container einrichten und konfigurieren können.
Alternativ könnte HTTPS von einem Cloud-Anbieter als einer seiner Dienste gehandhabt werden (während die Anwendung weiterhin in einem Container ausgeführt wird).
Normalerweise gibt es ein anderes Tool, das für das Starten und Ausführen Ihres Containers zuständig ist.
Es könnte sich um Docker direkt, Docker Compose, Kubernetes, einen Cloud-Dienst, usw. handeln.
In den meisten (oder allen) Fällen gibt es eine einfache Option, um die Ausführung des Containers beim Hochfahren und Neustarts bei Fehlern zu ermöglichen. In Docker ist es beispielsweise die Befehlszeilenoption --restart.
Ohne die Verwendung von Containern kann es umständlich und schwierig sein, Anwendungen beim Hochfahren auszuführen und neu zu starten. Bei der Arbeit mit Containern ist diese Funktionalität jedoch in den meisten Fällen standardmäßig enthalten. ✨
Wenn Sie einen Cluster von Maschinen mit Kubernetes, Docker Swarm Mode, Nomad verwenden, oder einem anderen, ähnlich komplexen System zur Verwaltung verteilter Container auf mehreren Maschinen, möchten Sie wahrscheinlich die Replikation auf Cluster-Ebene abwickeln, anstatt in jedem Container einen Prozessmanager (wie Gunicorn mit Workern) zu verwenden.
Diese verteilten Containerverwaltungssysteme wie Kubernetes verfügen normalerweise über eine integrierte Möglichkeit, die Replikation von Containern zu handhaben und gleichzeitig Load Balancing für die eingehenden Requests zu unterstützen. Alles auf Cluster-Ebene.
In diesen Fällen möchten Sie wahrscheinlich ein Docker-Image von Grund auf erstellen, wie oben erklärt, Ihre Abhängigkeiten installieren und einen einzelnen Uvicorn-Prozess ausführen, anstatt etwas wie Gunicorn mit Uvicorn-Workern auszuführen.
Bei der Verwendung von Containern ist normalerweise eine Komponente vorhanden, die am Hauptport lauscht. Es könnte sich um einen anderen Container handeln, der auch ein TLS-Terminierungsproxy ist, um HTTPS zu verarbeiten, oder ein ähnliches Tool.
Da diese Komponente die Last an Requests aufnehmen und diese (hoffentlich) ausgewogen auf die Worker verteilen würde, wird sie üblicherweise auch Load Balancer – Lastverteiler – genannt.
Tipp
Die gleiche TLS-Terminierungsproxy-Komponente, die für HTTPS verwendet wird, wäre wahrscheinlich auch ein Load Balancer.
Und wenn Sie mit Containern arbeiten, verfügt das gleiche System, mit dem Sie diese starten und verwalten, bereits über interne Tools, um die Netzwerkkommunikation (z. B. HTTP-Requests) von diesem Load Balancer (das könnte auch ein TLS-Terminierungsproxy sein) zu den Containern mit Ihrer Anwendung weiterzuleiten.
Bei der Arbeit mit Kubernetes oder ähnlichen verteilten Containerverwaltungssystemen würde die Verwendung ihrer internen Netzwerkmechanismen es dem einzelnen Load Balancer, der den Haupt-Port überwacht, ermöglichen, Kommunikation (Requests) an möglicherweise mehrere Container weiterzuleiten, in denen Ihre Anwendung ausgeführt wird.
Jeder dieser Container, in denen Ihre Anwendung ausgeführt wird, verfügt normalerweise über nur einen Prozess (z. B. einen Uvicorn-Prozess, der Ihre FastAPI-Anwendung ausführt). Es wären alles identische Container, die das Gleiche ausführen, welche aber jeweils über einen eigenen Prozess, Speicher, usw. verfügen. Auf diese Weise würden Sie die Parallelisierung in verschiedenen Kernen der CPU nutzen. Oder sogar in verschiedenen Maschinen.
Und das verteilte Containersystem mit dem Load Balancer würde die Requests abwechselnd an jeden einzelnen Container mit Ihrer Anwendung verteilen. Jeder Request könnte also von einem der mehreren replizierten Container verarbeitet werden, in denen Ihre Anwendung ausgeführt wird.
Und normalerweise wäre dieser Load Balancer in der Lage, Requests zu verarbeiten, die an andere Anwendungen in Ihrem Cluster gerichtet sind (z. B. eine andere Domain oder unter einem anderen URL-Pfad-Präfix), und würde diese Kommunikation an die richtigen Container weiterleiten für diese andere Anwendung, die in Ihrem Cluster ausgeführt wird.
In einem solchen Szenario möchten Sie wahrscheinlich einen einzelnen (Uvicorn-)Prozess pro Container haben, da Sie die Replikation bereits auf Cluster ebene durchführen würden.
In diesem Fall möchten Sie also nicht einen Prozessmanager wie Gunicorn mit Uvicorn-Workern oder Uvicorn mit seinen eigenen Uvicorn-Workern haben. Sie möchten nur einen einzelnen Uvicorn-Prozess pro Container haben (wahrscheinlich aber mehrere Container).
Ein weiterer Prozessmanager im Container (wie es bei Gunicorn oder Uvicorn der Fall wäre, welche Uvicorn-Worker verwalten) würde nur unnötige Komplexität hinzufügen, um welche Sie sich höchstwahrscheinlich bereits mit Ihrem Clustersystem kümmern.
Natürlich gibt es Sonderfälle, in denen Sie einen Container mit einem Gunicorn-Prozessmanager haben möchten, welcher mehrere Uvicorn-Workerprozesse darin startet.
In diesen Fällen können Sie das offizielle Docker-Image verwenden, welches Gunicorn als Prozessmanager enthält, welcher mehrere Uvicorn-Workerprozesse ausführt, sowie einige Standardeinstellungen, um die Anzahl der Worker basierend auf den verfügbaren CPU-Kernen automatisch anzupassen. Ich erzähle Ihnen weiter unten in Offizielles Docker-Image mit Gunicorn – Uvicorn mehr darüber.
Hier sind einige Beispiele, wann das sinnvoll sein könnte:
Sie könnten einen Prozessmanager im Container haben wollen, wenn Ihre Anwendung einfach genug ist, sodass Sie die Anzahl der Prozesse nicht (zumindest noch nicht) zu stark tunen müssen und Sie einfach einen automatisierten Standard verwenden können (mit dem offiziellen Docker-Image), und Sie führen es auf einem einzelnen Server aus, nicht auf einem Cluster.
Sie könnten das Deployment auf einem einzelnen Server (kein Cluster) mit Docker Compose durchführen, sodass Sie keine einfache Möglichkeit hätten, die Replikation von Containern (mit Docker Compose) zu verwalten und gleichzeitig das gemeinsame Netzwerk mit Load Balancing zu haben.
Dann möchten Sie vielleicht einen einzelnen Container mit einem Prozessmanager haben, der darin mehrere Workerprozesse startet.
Sie könnten auch andere Gründe haben, die es einfacher machen würden, einen einzelnen Container mit mehreren Prozessen zu haben, anstatt mehrere Container mit einem einzelnen Prozess in jedem von ihnen.
Beispielsweise könnten Sie (abhängig von Ihrem Setup) ein Tool wie einen Prometheus-Exporter im selben Container haben, welcher Zugriff auf jeden der eingehenden Requests haben sollte.
Wenn Sie in hier mehrere Container hätten, würde Prometheus beim Lesen der Metriken standardmäßig jedes Mal diejenigen für einen einzelnen Container abrufen (für den Container, der den spezifischen Request verarbeitet hat), anstatt die akkumulierten Metriken für alle replizierten Container abzurufen.
In diesem Fall könnte einfacher sein, einen Container mit mehreren Prozessen und ein lokales Tool (z. B. einen Prometheus-Exporter) in demselben Container zu haben, welches Prometheus-Metriken für alle internen Prozesse sammelt und diese Metriken für diesen einzelnen Container offenlegt.
Der Hauptpunkt ist, dass keine dieser Regeln in Stein gemeißelt ist, der man blind folgen muss. Sie können diese Ideen verwenden, um Ihren eigenen Anwendungsfall zu evaluieren, zu entscheiden, welcher Ansatz für Ihr System am besten geeignet ist und herauszufinden, wie Sie folgende Konzepte verwalten:
Wenn Sie einen einzelnen Prozess pro Container ausführen, wird von jedem dieser Container (mehr als einer, wenn sie repliziert werden) eine mehr oder weniger klar definierte, stabile und begrenzte Menge an Arbeitsspeicher verbraucht.
Und dann können Sie dieselben Speichergrenzen und -anforderungen in Ihren Konfigurationen für Ihr Container-Management-System festlegen (z. B. in Kubernetes). Auf diese Weise ist es in der Lage, die Container auf den verfügbaren Maschinen zu replizieren, wobei die von denen benötigte Speichermenge und die auf den Maschinen im Cluster verfügbare Menge berücksichtigt werden.
Wenn Ihre Anwendung einfach ist, wird dies wahrscheinlich kein Problem darstellen und Sie müssen möglicherweise keine festen Speichergrenzen angeben. Wenn Sie jedoch viel Speicher verbrauchen (z. B. bei Modellen für maschinelles Lernen), sollten Sie überprüfen, wie viel Speicher Sie verbrauchen, und die Anzahl der Container anpassen, die in jeder Maschine ausgeführt werden. (und möglicherweise weitere Maschinen zu Ihrem Cluster hinzufügen).
Wenn Sie mehrere Prozesse pro Container ausführen (zum Beispiel mit dem offiziellen Docker-Image), müssen Sie sicherstellen, dass die Anzahl der gestarteten Prozesse nicht mehr Speicher verbraucht als verfügbar ist.
Wenn Sie mehrere Container haben, von denen wahrscheinlich jeder einen einzelnen Prozess ausführt (z. B. in einem Kubernetes-Cluster), dann möchten Sie wahrscheinlich einen separaten Container haben, welcher die Arbeit der Vorab-Schritte in einem einzelnen Container, mit einem einzelnenen Prozess ausführt, bevor die replizierten Workercontainer ausgeführt werden.
Info
Wenn Sie Kubernetes verwenden, wäre dies wahrscheinlich ein Init-Container.
Wenn es in Ihrem Anwendungsfall kein Problem darstellt, diese vorherigen Schritte mehrmals parallel auszuführen (z. B. wenn Sie keine Datenbankmigrationen ausführen, sondern nur prüfen, ob die Datenbank bereits bereit ist), können Sie sie auch einfach in jedem Container direkt vor dem Start des Hauptprozesses einfügen.
Wenn Sie ein einfaches Setup mit einem einzelnen Container haben, welcher dann mehrere Workerprozesse (oder auch nur einen Prozess) startet, können Sie die Vorab-Schritte im selben Container direkt vor dem Starten des Prozesses mit der Anwendung ausführen. Das offizielle Docker-Image unterstützt das intern.
Es gibt ein offizielles Docker-Image, in dem Gunicorn mit Uvicorn-Workern ausgeführt wird, wie in einem vorherigen Kapitel beschrieben: Serverworker – Gunicorn mit Uvicorn.
Es besteht eine hohe Wahrscheinlichkeit, dass Sie dieses oder ein ähnliches Basisimage nicht benötigen und es besser wäre, wenn Sie das Image von Grund auf neu erstellen würden, wie oben beschrieben in: Ein Docker-Image für FastAPI erstellen.
Dieses Image verfügt über einen Auto-Tuning-Mechanismus, um die Anzahl der Arbeitsprozesse basierend auf den verfügbaren CPU-Kernen festzulegen.
Es verfügt über vernünftige Standardeinstellungen, aber Sie können trotzdem alle Konfigurationen mit Umgebungsvariablen oder Konfigurationsdateien ändern und aktualisieren.
Anzahl der Prozesse auf dem offiziellen Docker-Image¶
Die Anzahl der Prozesse auf diesem Image wird automatisch anhand der verfügbaren CPU-Kerne berechnet.
Das bedeutet, dass versucht wird, so viel Leistung wie möglich aus der CPU herauszuquetschen.
Sie können das auch in der Konfiguration anpassen, indem Sie Umgebungsvariablen, usw. verwenden.
Das bedeutet aber auch, da die Anzahl der Prozesse von der CPU abhängt, welche der Container ausführt, dass die Menge des verbrauchten Speichers ebenfalls davon abhängt.
Wenn Ihre Anwendung also viel Speicher verbraucht (z. B. bei Modellen für maschinelles Lernen) und Ihr Server über viele CPU-Kerne, aber wenig Speicher verfügt, könnte Ihr Container am Ende versuchen, mehr Speicher als vorhanden zu verwenden, was zu erheblichen Leistungseinbußen (oder sogar zum Absturz) führen kann. 🚨
Sie sollten dieses offizielle Basisimage (oder ein ähnliches) wahrscheinlich nicht benutzen, wenn Sie Kubernetes (oder andere) verwenden und Sie bereits Replikation auf Cluster ebene mit mehreren Containern eingerichtet haben. In diesen Fällen ist es besser, ein Image von Grund auf zu erstellen, wie oben beschrieben: Ein Docker-Image für FastAPI erstellen.
Dieses Image wäre vor allem in den oben in Container mit mehreren Prozessen und Sonderfälle beschriebenen Sonderfällen nützlich. Wenn Ihre Anwendung beispielsweise einfach genug ist, dass das Festlegen einer Standardanzahl von Prozessen basierend auf der CPU gut funktioniert, möchten Sie sich nicht mit der manuellen Konfiguration der Replikation auf Cluster ebene herumschlagen und führen nicht mehr als einen Container mit Ihrer Anwendung aus. Oder wenn Sie das Deployment mit Docker Compose durchführen und auf einem einzelnen Server laufen, usw.
Klicken Sie auf die Zahlenblasen, um zu sehen, was jede Zeile bewirkt.
Eine Docker-Phase ist ein Teil eines Dockerfiles, welcher als temporäres Containerimage fungiert und nur zum Generieren einiger Dateien für die spätere Verwendung verwendet wird.
Die erste Phase wird nur zur Installation von Poetry und zur Generierung der requirements.txt mit deren Projektabhängigkeiten aus der Datei pyproject.toml von Poetry verwendet.
Diese requirements.txt-Datei wird später in der nächsten Phase mit pip verwendet.
Im endgültigen Containerimage bleibt nur die letzte Stufe erhalten. Die vorherigen Stufen werden verworfen.
Bei der Verwendung von Poetry wäre es sinnvoll, mehrstufige Docker-Builds zu verwenden, da Poetry und seine Abhängigkeiten nicht wirklich im endgültigen Containerimage installiert sein müssen, sondern Sie brauchen nur die Datei requirements.txt, um Ihre Projektabhängigkeiten zu installieren.
Dann würden Sie im nächsten (und letzten) Schritt das Image mehr oder weniger auf die gleiche Weise wie zuvor beschrieben erstellen.
Auch hier gilt: Wenn Sie Ihren Container hinter einem TLS-Terminierungsproxy (Load Balancer) wie Nginx oder Traefik ausführen, fügen Sie dem Befehl die Option --proxy-headers hinzu:
Mithilfe von Containersystemen (z. B. mit Docker und Kubernetes) ist es ziemlich einfach, alle Deployment-Konzepte zu handhaben:
HTTPS
Beim Hochfahren ausführen
Neustarts
Replikation (die Anzahl der laufenden Prozesse)
Arbeitsspeicher
Schritte vor dem Start
In den meisten Fällen möchten Sie wahrscheinlich kein Basisimage verwenden und stattdessen ein Containerimage von Grund auf erstellen, eines basierend auf dem offiziellen Python-Docker-Image.
Indem Sie auf die Reihenfolge der Anweisungen im Dockerfile und den Docker-Cache achten, können Sie die Build-Zeiten minimieren, um Ihre Produktivität zu erhöhen (und Langeweile zu vermeiden). 😎
In bestimmten Sonderfällen möchten Sie möglicherweise das offizielle Docker-Image für FastAPI verwenden. 🤓