Zurück zum Blog

Wie man eine Django-Anwendung mit Docker, Nginx und Let’s Encrypt sichert und skaliert

Wie man eine Django-Anwendung mit Docker, Nginx und Let’s Encrypt sichert und skaliert

Millionen von Nutzern gehen ins Internet, um auf Informationen für verschiedene Zwecke zuzugreifen, darunter Lernen, Unterhaltung, Nachrichten und das Teilen des Fortschritts ihres Lebens’ mit Freunden. Daher liegt es bei der Bereitstellung einer App in Ihrem besten Interesse, eine hochsichere und skalierbare Infrastruktur für Ihre Anwendung zu implementieren. Die Cloud bietet verschiedene Möglichkeiten zur Absicherung und Skalierung einer Django-Anwendung. Horizontale Skalierung ist eine Methode, mit der Sie mehrere Kopien Ihrer App ausführen können. Dies stellt sicher, dass sie fehlertoleranter und hochverfügbar ist. Zudem erhöht es die Leistung, um mehrere Anfragen gleichzeitig zu verarbeiten.

Horizontales Skalieren einer Django-Anwendung

Sie können eine Django-Anwendung horizontal skalieren, indem Sie mehrere App-Server bereitstellen, auf denen die Django-Anwendung und ihr WSGI-HTTP-Server (wie Gunicorn oder uWSGI) ausgeführt werden. Sie müssen dann eine Infrastruktur einrichten, um eingehende Anfragen auf diese App-Server zu verteilen. Ein Load Balancer und ein Reverse-Proxy wie Nginx können Ihrer Infrastruktur bei der Verkehrsverteilung helfen. Nginx kann SSL-Zertifikate bereitstellen, um sichere Verbindungen zu Ihrer App über HTTPS zu gewährleisten. Schließlich kann Nginx auch das Caching von statischen Inhalten übernehmen, um die Last auf Ihrem Server zu minimieren.

Diese verschiedenen Komponenten separat zu konfigurieren und sicherzustellen, dass sie miteinander kommunizieren, kann eine entmutigende Aufgabe sein. Glücklicherweise vereinfacht die Verwendung von Docker den Konfigurationsprozess und stellt sicher, dass sich die verschiedenen Komponenten unabhängig von ihrem Bereitstellungsort gleich verhalten.

Was Sie in dieser Anleitung tun werden

In dieser Anleitung lernen Sie, wie Sie eine containerisierte Django-Anwendung, die mit einem Gunicorn-WSGI-HTTP-Server bereitgestellt wird, horizontal skalieren. Sie werden zwei Anwendungsserver bereitstellen, auf denen jeweils Docker installiert ist und die dieselbe Kopie eines Django- und Gunicorn-App-Containers ausführen.

Sie werden Ihre Anwendung auch mit einem Let’s Encrypt-SSL-Zertifikat sichern, indem Sie einen dritten Proxy-Server bereitstellen und konfigurieren, auf dem ein Nginx-Reverse-Proxy-Container und ein Certbot-Client-Container ausgeführt werden. Certbot ist ein Paket, das bei der Verwaltung von SSL-Zertifikaten der Zertifizierungsstelle Let’s Encrypt hilft. Es ruft das Zertifikat ab, konfiguriert Nginx-Server-Blöcke mit dem Speicherort des Zertifikats und verwaltet automatische Verlängerungen. Dies geschieht durch die Konfiguration eines Cron-Jobs, der regelmäßig überprüft, ob das Zertifikat bald abläuft und erneuert werden muss. Indem Sie Ihr SSL-Zertifikat auf dem neuesten Stand halten, hat Ihre Website immer eine hohe Sicherheitsbewertung auf SSL Labs.

Der dritte Proxy-Server befindet sich vor Ihrer verteilten Architektur und empfängt den gesamten eingehenden externen Datenverkehr. Anschließend verteilt er den Datenverkehr auf Ihre App-Server. Die App-Server befinden sich hinter einer Firewall, die nur dem Proxy-Server den Zugriff erlaubt.

Dieses Tutorial ist das zweite in einer Reihe von drei Tutorials zur Arbeit mit Django, Docker und Kubernetes. Sie sollten zuerst den Schritten folgen, die im Tutorial zu Erstellen einer Django- und Gunicorn-Anwendung mit Docker auf Ubuntu beschrieben sind. In diesem Tutorial haben wir den Basis-Projektcode und ein Dockerfile eingerichtet und die App mit MinIo Simple Storage Service (S3) verbunden, um unsere statischen Dateien bereitzustellen.

Voraussetzungen

Um diesem Tutorial folgen zu können, benötigen Sie Folgendes:

  1. Vier Ubuntu 20.04 Server:

Wenn Sie den Schritten im vorausgesetzten Tutorial Erstellen einer Django- und Gunicorn-Anwendung mit Docker auf Ubuntu gefolgt sind, haben Sie bereits zwei der vier Server:

  • Auf dem ersten Server wird die PostgreSQL-Datenbankinstanz ausgeführt. Folgen Sie den Schritten 1 und 2 des Tutorials: Erstellen einer Django- und Gunicorn-Anwendung mit Docker auf Ubuntu, um die Datenbank einzurichten. Die Postgres-Konfigurationen sollten so geändert werden, dass externe Verbindungen nur von den IPs Ihrer App-Server zugelassen werden.

  • Der zweite und dritte Server werden die Container für Ihren Anwendungscode hosten. Der zweite Server sollte bereits aus dem vorausgesetzten Tutorial laufen. Wir werden seine Firewall so anpassen, dass sie nur externe Verbindungen von der IP des Proxy-Servers zulässt. Sie können den Schritten 1 bis 4 dieses Schritt-für-Schritt-Tutorials folgen, um Ihren Ubuntu-Server einzurichten auf CloudSigma.

  • Der vierte Server wird der Proxy-Server sein, der den Lastausgleich und die Verteilung des Datenverkehrs auf die beiden Anwendungsserver-Container übernimmt.

  1. Docker sollte auf den beiden App-Servern und dem Proxy-Server installiert sein.

    Nachdem Sie die Schritte im Voraussetzungs-Tutorial befolgt haben, sollten Sie Docker bereits auf einem der Server installiert haben. Sie können den Schritten 1, 2 und 3 unseres Tutorials zur Installation und zum Betrieb von Docker folgen. Denken Sie daran, den oben erstellten Sudo-Benutzer zur Docker-Gruppe hinzuzufügen.

  2. Erwerben Sie einen registrierten Domainnamen und richten Sie dessen DNS-Einträge so ein, dass sie auf die öffentliche IP-Adresse des Proxy-Servers verweisen. Zu Demonstrationszwecken verwenden wir example_domain.com.
  1. Richten Sie einen S3-Objektspeicherdienst ein. Wir haben MinIO als Speicherdienst im Voraussetzungs-Tutorial verwendet. Befolgen Sie daher die Erklärungen in Schritt 5 des Voraussetzungs-Tutorials, um Ihren MinIO-Speicher-Bucket einzurichten.

Schritt 1: Überprüfen, ob der erste Django-Anwendungsserver funktioniert

Wie in den Voraussetzungen erklärt, folgt diese Anleitung auf das Tutorial über Erstellen einer Django- und Gunicorn-Anwendung mit Docker auf Ubuntu. Wenn Sie von diesem Tutorial kommen und die Schritte bereits umgesetzt haben, sollte der erste Server laufen. Der Code der Anwendung basiert auf dem Polls-Anwendungs-Tutorial der Django-Dokumentation. Es ist wichtig, dass Sie diese Schritte durchlesen, um ein Verständnis für die Ersteinrichtung zu bekommen. Wenn Sie die Schritte im Tutorial bereits umgesetzt haben, können Sie diesen ersten Schritt überspringen.

Andernfalls können Sie einfach den dockerisierten Branch auf Ihren Server klonen. Melden Sie sich zunächst auf Ihrem ersten App-Server an und führen Sie den folgenden git-Befehl aus, um den django-polls-docker Branch des django-polls-Repositorys:

Navigieren Sie als Nächstes in das django-polls-Verzeichnis:

cd django-polls

In diesem Verzeichnis finden Sie eine Dockerfile, die von Docker verwendet wird, um das Anwendungs-Image zu erstellen, das django-polls-Verzeichnis, welches den Python-Anwendungscode enthält, und eine env-Datei, die eine Liste von Umgebungsvariablen enthält, die beim Start an den Container übergeben werden, um dessen Verhalten zu ändern. In der Dockerfile, definieren wir Django-Paketabhängigkeiten über die requirements.txt-Datei. Darüber hinaus müssen wir einen Port deklarieren, 8000 der für den eingehenden Datenverkehr verwendet werden soll, und ihn so einrichten, dass ein gunicorn-Server mit 3 Workern ausgeführt wird. Um mehr über die Dockerfile-Anweisungen zu erfahren, werfen Sie bitte einen Blick auf Schritt 7 des Tutorials Erstellen einer Django- und Gunicorn-Anwendung mit Docker auf Ubuntu.

Sie können das Docker-Image mit folgendem Befehl erstellen:

docker build -t django-polls:v1 .

Nachdem Docker das Image erstellt hat, können Sie die verfügbaren Images auf dem Server mit dem folgenden Befehl auflisten:

docker images

Hier ist die Ausgabe, als wir den Befehl ausgeführt haben:

Django Application scrn 1

Als Nächstes müssen wir die env-Datei ändern, die zur Konfiguration der Laufzeitumgebung verwendet wird. Diese Datei wird beim Starten des Containers an den Docker-Run-Container übergeben. Öffnen Sie die env-Datei mit dem Nano-Editor:

Die env-Datei enthält einige Platzhaltertexte, die Sie ändern und mit Ihren korrekten Werten füllen müssen:

  • DJANGO_SECRET_KEY: Generieren Sie einen eindeutigen, unvorhersehbaren Wert, wie in der Django-Dokumentation erklärt. Sie können diesen Befehl verwenden, um eine zufällige Zeichenfolge zu generieren und sie der Variablen zuzuweisen:  python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'.

  • DJANGO_ALLOWED_HOSTS: Dieser Wert wird verwendet, um Ihre App vor HTTP-Host-Header-Angriffen zu schützen. Sie können ihn auf * festlegen, ein Platzhalter (Wildcard), der im Entwicklungsmodus mit allen Hosts übereinstimmt. Wenn Sie Ihre App in der Produktionsumgebung bereitstellen, setzen Sie dies auf Ihren registrierten Domainnamen. Für unsere Demonstration ist es example_domain.com.

  • DB_DATABASE: Setzen Sie dies auf den Namen der PostgreSQL-Datenbank, die Sie im Abschnitt Voraussetzungen erstellt haben, in unserem Fall ist es polls_db.

  • DB_USERNAME: Setzen Sie dies auf den Benutzernamen, den Sie für Ihre Datenbank gewählt haben.

  • DB_PASSWORD: Setzen Sie dies auf das Passwort, das Sie für Ihre Datenbank gewählt haben.

  • DB_HOST: Setzen Sie dies auf den Host, auf dem Ihre Datenbankinstanz läuft, wie Sie es im Abschnitt Voraussetzungen eingerichtet haben. Dies wird in den Schritten 1 und 2 des Tutorials Erstellen einer Django- und Gunicorn-Anwendung mit Docker auf Ubuntu zur Einrichtung der Datenbank erklärt.

  • DB_PORT: Setzen Sie dies auf den Port Ihrer Datenbank.

Speichern und schließen Sie die Datei, sobald Sie die Bearbeitung abgeschlossen haben. Da unsere Datenbank-Anmeldedaten nun vorliegen, können wir das Datenbankschema erstellen, indem wir den Container ausführen und den im Dockerfile festgelegten CMD-Befehl überschreiben. Weitere Informationen zum Dockerfile-Einstiegspunkt in der offiziellen Dokumentation finden. Führen Sie als Nächstes den folgenden Befehl aus:

In diesem Befehl führen wir das Image django-polls:v1 aus und übergeben die zuvor geänderte Datei env. Der Teil: sh -c "python manage.py makemigrations && python manage.py migrate erstellt das durch den App-Code definierte Datenbankschema. Wenn Sie den Befehl zum ersten Mal ausführen, sollten Sie eine ähnliche Ausgabe sehen, die die Erstellung des Datenbankschemas anzeigt:

Django Application scrn 2

Sobald das Schema erstellt ist, können wir den Django-Superuser erstellen. Führen Sie den folgenden Befehl aus, um den Container mit einer interaktiven Shell zu starten:

Der Befehl startet den Container mit einer Shell-Eingabeaufforderung, über die Sie mit der Python-Shell interagieren können. Erstellen wir einen Benutzer mit dem folgenden Befehl:

Folgen Sie den Aufforderungen, um einen Benutzernamen, eine E-Mail-Adresse und ein Passwort einzugeben. Geben Sie das Passwort erneut ein und drücken Sie die Eingabetaste, um den Benutzer zu erstellen. Verlassen Sie die Shell und beenden Sie den Container durch Drücken von STRG+D.

Als Nächstes müssen wir den Container erneut ausführen und den Standardbefehl mit dem Django-Befehl collectstatic überschreiben. Der Befehl generiert die statischen Dateien für die App und lädt sie in den MinIO-Cloud-Speicher hoch:

Der Befehl generiert die Datei und lädt sie in Ihren konfigurierten Object-Storage-Dienst hoch. Hier ist die Ausgabe:

object storage

Sie können die Anwendung nun ausführen, ohne einen zusätzlichen Befehl anzugeben, um den im Dockerfile definierten Standard-CMD-Befehl zu überschreiben:

Django Application scrn 3

Docker führt den im Dockerfile definierten Standardbefehl aus, startet den Container mit dem gunicorn-Server, gibt den Container-Port 8000 frei und leitet ihn an den Ubuntu-Port 80 weiter. Sie können nun die Benutzeroberfläche der Anwendung in Ihrem Browser anzeigen, indem Sie die IP-Adresse des ersten Servers in Ihre Adresszeile eingeben: http://FIRST_SERVER_IP.

Sie erhalten eine 404 Page Not Found, da wir nichts für den /-Pfad. Navigieren Sie zu http://FIRST_SERVER_IP/polls, um die Polls-Schnittstelle zu sehen:

Django Application image 1

Besuchen Sie die Admin-Schnittstelle, um einige Umfragen zu erstellen: http://FIRST_SERVER_IP/admin:

polls

Geben Sie die Anmeldedaten ein, die Sie mit dem Befehl createsuperuser oben festgelegt haben, um auf die Admin-Schnittstelle zuzugreifen:

polls administration

Wenn Sie sich den Quelltext der Seite ansehen, werden Sie feststellen, dass die statischen Dateien wie definiert aus dem Storage Bucket geladen werden. Nachdem Sie sich vergewissert haben, dass der Container die App wie erwartet bereitstellt, können Sie den Container beenden, indem Sie STRG+C im Terminal drücken.

Als Nächstes müssen wir den Container im detached-Modus laufen lassen, damit wir die SSH-Sitzung des ersten Servers beenden können. Dadurch läuft der Container im Hintergrund weiter. Führen Sie den folgenden Befehl aus:

Das Flag -d startet den Container im detached-Modus, sodass er im Hintergrund weiterlaufen kann. Das Flag --rm bereinigt das Dateisystem des Containers, nachdem dieser beendet wurde. Wir geben dem Container einen Namen, polls, damit wir ihn sehen können, wenn wir die Container auflisten.

Beenden Sie die SSH-Sitzung Ihres ersten Servers und navigieren Sie zu http://FIRST_SERVER_IP/polls in Ihrem Browser, um zu bestätigen, dass es wie erwartet läuft. Wenn Sie die Polls-Schnittstelle sehen können, wurde Ihr erster App-Server erfolgreich eingerichtet. Richten wir im nächsten Schritt den zweiten Anwendungsserver ein.

Schritt 2: Einrichten des zweiten Anwendungsservers

Wir werden den dockerisierten Branch der Anwendung klonen, die wir im Tutorial Building a Django and Gunicorn Application with Docker on Ubuntu erstellt haben. Weitere Details zu den Befehlen, die wir hier verwenden werden, finden Sie in diesem Tutorial oder in der zusammengefassten Version in Schritt 1.

Sie sollten den zweiten Server in Betrieb haben, einen Nicht-Root-Sudo-Benutzer hinzugefügt und Docker installiert haben, wie im Abschnitt Prerequisites erklärt.

Der nächste Schritt besteht darin, diesen Server so zu konfigurieren, dass er eine Verbindung zur PostgreSQL-Serverinstanz herstellt. Wie in Schritt 1 des Tutorials Building a Django and Gunicorn Application with Docker on Ubuntu erklärt, müssen Sie die IP-Adresse des zweiten Servers in der ufw und den PostgreSQL-Konfigurationen zulassen.

Melden Sie sich zuerst mit Ihrem Nicht-Root-Sudo-Benutzer auf der PostgreSQL-Datenbankserverinstanz an. Um die ufw-Regel hinzuzufügen, führen Sie den folgenden Befehl aus:

Führen Sie als Nächstes diesen Befehl aus und fügen Sie die IP-Adresse des zweiten Servers zur PostgreSQL-Client-Authentifizierungsdatei hinzu:

Lesen Sie die Kommentare durch, um mehr über die Konfigurationen zu erfahren. Fügen Sie als Nächstes diese Zeile unter dem Abschnitt „hosts“ hinzu und geben Sie Ihre IP-Adresse an:

Speichern und schließen Sie die Datei, wenn Sie mit der Bearbeitung fertig sind.

Starten Sie anschließend den PostgreSQL-Dienst neu, damit die Änderungen wirksam werden:

Melden Sie sich von der PostgreSQL-Datenbankserverinstanz ab und fahren Sie mit der Konfiguration der zweiten App-Serverinstanz fort.

Melden Sie sich auf dem second app server mit ssh an. Klonen Sie dann den django-polls-Branch des django-polls-Repositorys mit dem folgenden Befehl:

Wechseln Sie in das Verzeichnis django-polls:

Erstellen Sie danach das Image mit dem folgenden Befehl:

Sobald der Image-Erstellungsprozess abgeschlossen ist, ändern Sie die Datei env mit den Konfigurationswerten, wie in Step 1 erklärt. Öffnen Sie die Datei mit nano:

Ersetzen Sie die Platzhaltertexte durch die tatsächlichen Werte, die Sie in Schritt 1. Denken Sie daran, die DJANGO_ALLOWED_HOSTS-Variable entsprechend anzupassen. Speichern und schließen Sie die Datei, wenn Sie fertig sind. Aktualisieren Sie Ihre MinIO-Anmeldedaten in der env -Datei, wie Sie es im vorherigen Schritt getan haben.

Jetzt können Sie den App-Container im Detached-Modus mit dem folgenden Befehl ausführen:

Der Befehl startet den Container und lässt ihn im Hintergrund laufen. Beenden Sie die SSH-Sitzung des zweiten App-Servers und navigieren Sie zu http://SECOND_SERVER_IP/polls in Ihrem Browser, um zu bestätigen, dass er wie erwartet läuft. Sie sollten die Polls-Benutzeroberfläche sehen können, wenn alles wie erwartet verlaufen ist.

Sie haben nun zwei App-Server, auf denen dieselbe Kopie Ihrer Anwendung läuft. Im nächsten Schritt konfigurieren Sie den Nginx-Container so, dass er als Reverse-Proxy fungiert.

Schritt 3: Einrichten des Nginx-Docker-Containers

Nginx ist eine der beliebtesten Open-Source-Webserver-Softwares der Welt. Sie ist dafür verantwortlich, die Verfügbarkeit und Skalierbarkeit der am stärksten frequentierten Websites im Internet sicherzustellen. Sie garantiert Sicherheit und ist sehr vielseitig. Sie können sie für Reverse-Proxying, Caching und Lastverteilung verwenden. Wir haben unsere Anwendung so eingerichtet, dass sie einen separaten Object-Storage-Dienst zur Verwaltung ihrer statischen und Mediendateien verwendet. Daher werden wir die Caching-Funktionen von Nginx nicht nutzen. Stattdessen werden wir die Reverse-Proxy- und Lastverteilungsfunktionen von Nginx verwenden. Der vorgeschaltete Nginx-Server empfängt den eingehenden Datenverkehr und verteilt ihn auf die Backend-Anwendungsserver. Anschließend sorgt er für eine sichere Kommunikation zwischen Client und Server, indem er den Datenverkehr mit SSL-Zertifikaten sichert, die von Let’s Encrypt.

Es gibt verschiedene Möglichkeiten, Nginx-Reverse-Proxying und Lastverteilung zu implementieren. Eine Möglichkeit besteht darin, den Nginx-Reverse-Proxy getrennt vom Backend-Anwendungsserver einzurichten, wie wir es in diesem Tutorial getan haben. Dieses Setup ist flexibel und ermöglicht es Ihnen, sowohl die Nginx-Proxy-Ebene als auch die Anwendungsebene zu skalieren. Sie können mehrere Nginx-Proxys hinzufügen oder einen Cloud-Load-Balancer implementieren. Eine andere Möglichkeit zur Implementierung von Reverse-Proxying besteht darin, einen der Backend-App-Server als Nginx-Proxy zu verwenden. Dann können Sie eingehende Anfragen lokal und an andere App-Server weiterleiten. Optional können Sie einen Nginx-Container auf allen Backend-App-Servern konfigurieren und einen vorgeschalteten Cloud-Load-Balancer einrichten, der den eingehenden Datenverkehr empfängt und an die Backend-App-Server verteilt.

Beginnen wir mit der Konfiguration des Proxy-Servers. Melden Sie sich auf dem vierten Ubuntu-Server an, den Sie als Nginx-Proxy eingerichtet haben, und erstellen Sie ein Konfigurationsverzeichnis:

Öffnen Sie eine Konfiguration mit nano innerhalb des Verzeichnisses:

Fügen Sie als Nächstes die folgende Konfiguration zur Datei hinzu:

In dieser Konfigurationsdatei geben wir die server, upstream, und location-Blöcke an, um Nginx anzuweisen, HTTP-Anfragen an HTTPS weiterzuleiten und die Anfragen auf die beiden App-Server zu verteilen, die wir in Schritt 1 und Schritt 2 eingerichtet haben. Allgemeine Informationen über die Struktur der Nginx-Konfigurationsdatei finden Sie in der offiziellen Dokumentation.

Wir haben Konfigurationsdateien untersucht, bereitgestellt von der Docker Hub-Nginx-Image-Dokumentation, Certbot und Gunicorn, um diese minimale Nginx-Konfigurationsdatei zu erstellen. Obwohl dies nur zu Demonstrationszwecken dient und dazu, unser Setup zum Laufen zu bringen, steht es Ihnen frei, zu erkunden und zu experimentieren mit anderen Konfigurationen gemäß den Nginx-Anleitungen.

Der Upstream-Block wird verwendet, um die Gruppe von Servern zu definieren, die eingehende Anfragen verarbeiten. Ein Name wird der Gruppe gegeben und von der proxy_pass-Direktive aufgerufen. Wir haben den Block django genannt und die IP-Adressen der beiden Backend-App-Server angegeben:

Wir haben außerdem 3 Server-Blöcke definiert. Der erste Server-Block erfasst alle Anfragen, die nicht mit Ihrer Domain übereinstimmen, und gibt einen 444-Code zurück (schließt die Verbindung, ohne eine Antwort an den Client zu senden, und verweigert so böswillige oder fehlerhafte Anfragen). Eine direkte HTTP-Anfrage an die IP-Adresse Ihres Servers wird von diesem Block verarbeitet, da er als default_server:

Der zweite Server-Block verarbeitet eingehende HTTP-Anfragen (Port 80) und leitet sie auf HTTPS (Port 443) um, unter Verwendung von HTTP-301-Weiterleitung:

Der dritte Server-Block verarbeitet nun die Anfragen. Er verfügt über mehrere Direktiven, deren Bedeutung wir im Folgenden definieren werden.

Wir haben zwei Direktiven, die die Pfade zum TLS-Zertifikat und -Schlüssel definieren, wie sie von Certbot bereitgestellt werden. Die Zertifikate werden beim Starten in den Nginx-Container gemountet:

Als Nächstes haben wir die von Certbot empfohlenen SSL-Sicherheitsstandards. Mehr erfahren Sie in den offiziellen Nginx-Dokumenten zum ngx_http_ssl_module. Mozilla bietet auch weitere Informationen zur serverseitigen Sicherheit an. Der ssl_ciphers-Wert in der conf-Datei ist von Mozillas Seite übernommen:

In den nächsten beiden Direktiven definieren wir die maximal zulässige Größe des Client-Anfrage-Bodys und legen das Timeout für keep-alive-Verbindungen mit dem Client fest. Nginx schließt Verbindungen mit dem Client nach den Sekunden, die Sie in der keepalive_timeout-Direktive festgelegt haben. Weitere Informationen zu Nginx-Konfigurationen für die Bereitstellung von Gunicorn finden Sie in der offiziellen Dokumentation:

In der Konfigurationsdatei haben wir außerdem zwei Location-Blöcke definiert. Der erste Block verarbeitet das Proxying der Anfragen, wie es mit den Proxy-Direktiven definiert ist. Eingehende Anfragen werden an die zuvor definierten upstream django-Server weitergeleitet:

Weitere Informationen zu den Proxy-Direktiven finden Sie im Nginx-Modul ngx_http_proxy_module und in der Dokumentation zur Bereitstellung eines Gunicorn-Servers.

In dem zweiten Location-Block definieren wir einen Pfad: /well-known/acme-challenge/. Er wird normalerweise von Certbot verwendet, um Ihren Domainnamen bei Let’s Encrypt zu verifizieren, bevor ein SSL-Zertifikat bereitgestellt oder erneuert wird:

Das ist alles für die Nginx-Konfigurationsdatei. Sie können die Datei nun speichern und schließen, sobald Sie die Bearbeitung abgeschlossen haben.

Die soeben definierte Konfigurationsdatei kann zum Ausführen eines Nginx-Containers verwendet werden. Dies wird jedoch fehlschlagen, da wir die SSL-Zertifikate von Let’s Encrypt noch nicht bereitgestellt haben. In diesem Tutorial verwenden wir die nginx:1.20.2-Docker-Image-Version 1.20.2 aus dem offiziellen Nginx-Image-Repository auf Docker Hub.

Sie können den folgenden Befehl ausführen, um das Image herunterzuladen und zu überprüfen, ob alles korrekt funktioniert:

Dieser Befehl erstellt einen Container namens nginx und ordnet die Ports 80 und 443 zwischen dem Host-System und dem Container zu. Das Flag --rm entfernt alle temporären Container nach einem erfolgreichen Build. Wir verwenden das Flag -v, um die Konfigurationsdatei im Container unter /etc/nginx/conf.d/nginx.conf bereitzustellen, was das Standardverzeichnis für Nginx-Konfigurationen ist. Sie wird mit dem Flag ro im schreibgeschützten Modus gemountet, um zu verhindern, dass der Nginx-Container sie ändert. Wir legen das Standard-Verzeichnis webroot fest und mounten es als /var/www/html. Zum Abschluss weisen wir Docker an, das Image nginx:1.20.2 für diesen Build zu verwenden. Lassen Sie uns im nächsten Schritt das TLS/SSL-Zertifikat und den Schlüssel von Let’s Encrypt anfordern.

Schritt 4: Bereitstellung des SSL/TLS-Zertifikats von Let’s Encrypt und Konfiguration der automatischen Certbot-Verlängerung

Certbot hilft bei der Bereitstellung kostenloser TLS-Zertifikate von Let’s Encrypt sowie bei der Verwaltung ihrer automatischen Verlängerung vor dem Ablaufdatum. Dies verbessert die Sicherheit Ihrer Websites und stellt sicher, dass sie über HTTPS bereitgestellt werden. Um unsere Architektur containerisiert zu halten, verwenden wir das Certbot-Docker-Image, um die SSL/TLS-Zertifikate abzurufen und die automatische Verlängerung zu konfigurieren. Stellen Sie sicher, dass Sie Docker auf Ihrem Proxy-Server installiert haben, wie in den Voraussetzungen beschrieben.

Sie sollten außerdem einen DNS-A-Eintrag für Ihren registrierten Domainnamen haben, der auf die IP-Adresse Ihres Proxy-Servers verweist. Sie können dies überprüfen, indem Sie das Certbot-Docker-Image ausführen und das Flag --staging übergeben:

Der Befehl lädt das Certbot-Image herunter und führt es im interaktiven Modus aus. Das bedeutet, dass es mit einer Shell gestartet wird, in der Sie einige Details eingeben können. Er ordnet Port 80 des Hosts dem Port 80 im Container zu. Wir verwenden das Flag -v, um zwei Host-Verzeichnisse im Container zu mounten: /etc/letsencrypt/ und /var/lib/letsencrypt/. Das Flag --standalone-Flag gibt an, dass das Certbot-Image ohne Nginx ausgeführt werden soll. Schließlich haben wir das --staging-Flag, das Certbot auf den Staging-Servern ausführen und Ihren Domainnamen validieren lässt.

Geben Sie Ihre E-Mail-Adresse ein und akzeptieren Sie die Nutzungsbedingungen wenn Sie dazu aufgefordert werden. Das Folgende ist die Ausgabe einer erfolgreichen Validierung:

NÄCHSTE SCHRITTE:

Das Zertifikat muss erneuert werden, bevor es abläuft. Certbot kann das Zertifikat automatisch im Hintergrund erneuern, aber Sie müssen möglicherweise Schritte unternehmen, um diese Funktionalität zu aktivieren. Weitere Informationen finden Sie unter diesem Link für Anweisungen.

Sie können das Zertifikat mit dem Befehl cat anzeigen:

Der obige Befehl sollte Ihr Zertifikat im Terminal anzeigen. Sobald Sie bestätigt haben, dass Certbot Ihr Zertifikat bereitgestellt hat, können Sie nun die Nginx-Konfiguration testen, die Sie in Schritt 3 erstellt haben. Führen Sie den folgenden Docker-Befehl aus, um den Nginx-Container zu starten:

In diesem Befehl haben wir das -v -Flag verwendet, um den Speicherort der Let’s Encrypt SSL/TLS-Zertifikatsverzeichnisse bereitzustellen.

Wenn der Container läuft, öffnen Sie die Webseite in Ihrem Browser: http://example_domain.com. Sie werden wahrscheinlich eine Warnung sehen, dass die Website unsicher ist:

Dies liegt daran, dass wir nur Staging-/Testzertifikate und keine production Zertifikate von Let’s Encrypt. Lassen Sie uns die Produktionszertifikate abrufen, indem wir den folgenden Certbot-Befehl ohne das --staging-Flag ausführen:

Bestätigen Sie in der Eingabeaufforderung, dass Sie das vorhandene Zertifikat renew and replace möchten, indem Sie 2 eingeben und ENTER drücken. Dies sollte ein produktionsbereites Zertifikat bereitstellen. Sie können nun den Nginx-Container ausführen und alles sollte einwandfrei funktionieren:

Sobald der Container läuft, öffnen Sie die Webseite in Ihrem Browser: http://example_domain.com erneut. Beachten Sie, dass Ihr Browser auf HTTPS weitergeleitet wird, selbst wenn Sie HTTP eingeben. Dies bedeutet, dass sowohl unser Server in der Nginx-Konfiguration als auch die bereitgestellten SSL/TLS-Zertifikate einwandfrei funktionieren. Navigieren Sie zur polls-Route http://example_domain.com/polls, da wir keine Route für den Home-Pfad definiert haben /. Sie sollten die Umfrage-Schnittstelle sehen:

Bislang haben Sie erfolgreich eine produktionsbereite Architektur konfiguriert. Sie haben zwei Backend-Server implementiert, die eingehende Anfragen verarbeiten, die vom Proxy-Server weitergeleitet werden. Der Proxy-Server übernimmt den Lastausgleich und die Absicherung des Datenverkehrs mithilfe der bereitgestellten TLS-Zertifikate.

Sie sollten jedoch bedenken, dass Let’s Encrypt-Zertifikate in 90 Tagen ablaufen. Daher sollten Sie sie vor Ablauf der 90-Tage-Frist erneuern. Da der Nginx-Container ausgeführt wird, sollten Sie den webroot-Modus anstelle des standalone-Modus verwenden, wenn Sie den certbot-Befehl zur Zertifikatserneuerung ausführen. Denken Sie daran, dass Sie das Verzeichnis /var/www/html/.well-known/acme-challenge/ in der Nginx-Konfigurationsdatei in Schritt 3 angegeben hatten. Certbot verwendet diesen Pfad, um Validierungsdateien zu speichern. Außerdem ruft der Let’s Encrypt-Client diesen Pfad mit Validierungsanfragen auf, wenn Sie versuchen, die Zertifikate zu erneuern. Sobald der Erneuerungsbefehl ausgeführt wurde, können Sie Nginx neu laden, um die Änderungen wirksam zu machen.

Beenden Sie den Container, indem Sie STRG+C in Ihrem Terminal drücken, und lassen Sie uns ihn im Detached-Modus mit dem Flag -d neu starten:

Dadurch läuft der Nginx-Container im Hintergrund weiter. Lassen Sie uns das Verfahren zur Zertifikatserneuerung mit dem Flag --dry-run testen, indem Sie den folgenden Befehl ausführen:

In diesem Befehl haben wir das Plugin --webroot sowie den Pfad für Validierungsanfragen mit dem Flag -w angegeben. Wir geben auch das Flag --dry-run an, um das automatische Erneuerungsverfahren zu überprüfen, ohne tatsächlich ein Zertifikat bereitzustellen.

Bei einer erfolgreichen Simulation sollten Sie eine ähnliche Ausgabe sehen:

Wann immer Sie ein Zertifikat für Ihre laufende Anwendung erneuern, müssen Sie Nginx neu laden, damit der Container das neue Zertifikat verwendet. Der folgende Docker-Befehl lädt den nginx-Container neu (denken Sie daran, dass wir den Container nginx genannt haben):

Der Befehl sendet ein HUP-Unix-Signal an den Nginx-Prozess, der im nginx-Docker-Container läuft. Dies veranlasst Nginx, seine Konfigurationen neu zu laden und die erneuerten Zertifikate zu verwenden.

Da wir TLS/SSL auf unserem Proxy-Server installiert haben und unsere Website über HTTPS bereitgestellt wird, müssen wir nun unsere Backend-App-Server absichern, um nur Anfragen vom Proxy-Server zuzulassen.

Schritt 5: Absichern der Backend-Django-Server vor externem Zugriff

Der in diesem Tutorial implementierte Proxy-Server übernimmt die SSL-Terminierung, bei der er die SSL-Verbindung entschlüsselt und unverschlüsselte Pakete an die Backend-App-Server weiterleitet. Da wir die Backend-Server gegen jeglichen externen Zugriff absichern werden, sollte dieses Sicherheitsniveau für die meisten Fälle ausreichen. Wenn Sie jedoch Anwendungen bereitstellen, die sensible Daten wie Bankinformationen oder Gesundheitsdaten übertragen, sollten Sie eine Ende-zu-Ende-Verschlüsselung.

In diesem Tutorial werden die Gunicorn-Server im Backend durch Nginx geschützt, da sie nicht für den direkten externen Zugriff gedacht sind. Der Nginx-Proxy-Server fungiert als Gateway zu den Backend-Servern und verhindert, dass externe Clients direkt auf die Backend-App-Server zugreifen. Sie sollten sicherstellen, dass alle Anfragen über den Proxy-Server laufen. Allerdings hat Docker ein bekanntes Problem, bei dem es die ufw-Firewall-Regeln umgeht und Ports nach außen öffnet, was Ihre Infrastruktur unsicher machen kann. Dies wird dadurch deutlich, dass wir unsere App-Server in Schritt 1 und Schritt 2 eingerichtet haben, ohne den Port 80 in den ufw-Regeln freizugeben. Dennoch können Sie weiterhin auf die Webseiten zugreifen, wenn Sie eine der öffentlichen IP-Adressen der Server im Browser aufrufen. Eine Möglichkeit, dieses Problem zu beheben, besteht darin, iptables direkt zu verwenden, ohne den Umweg über ufw zu gehen. Sie können die offiziellen Dokumente zu Docker und iptables lesen, um mehr zu erfahren. Eine weitere empfohlene Methode ist die Verwendung von Cloud-Firewalls.

Lassen Sie uns die Konfigurationen von UFW ändern, um den externen Zugriff auf alle Ports zu blockieren, die möglicherweise von Docker geöffnet wurden. Als wir den Host-Port 80 auf den Port des Docker-Containers 8000 mit dem Flag -p 80:8000 im Docker-Befehl abgebildet haben, haben wir versehentlich auch den Port 80 auf dem Host-Rechner geöffnet. Sie können diesen Zugriff deaktivieren, indem Sie die UFW-Konfiguration wie im ufw-docker-Repository-README beschrieben ändern.

Lassen Sie uns dies für den ersten Django-App-Server ändern. Melden Sie sich am Server an und öffnen Sie die Datei unter /etc/ufw/after.rules mit nano als Sudo-Benutzer:

Die Datei enthält die folgenden ufw-Regeln:

Fügen Sie den folgenden Block von UFW-Konfigurationszeilen am Ende der Datei hinzu:

Die von Ihnen hinzugefügten Regeln verhindern den öffentlichen Zugriff auf Ports, die Docker öffnet. Darüber hinaus erlauben sie den Zugriff aus den 10.0.0.0/8, 172.16.0.0/12, und 192.168.0.0/16 privaten IP-Bereichen. Weitere Details zu den Regeln finden Sie im ufw-docker README. Speichern und schließen Sie die Dateien, wenn Sie mit der Bearbeitung fertig sind. Dieses Setup sollte funktionieren, wenn Sie ein Virtual Private Cloud-Netzwerk (VPC) eingerichtet haben, in dem sich alle Ihre drei Server befinden, und Sie dann die privaten IPs der Django-Server in der Upstream-Direktive von Nginx- Konfigurations datei.

Wir haben jedoch öffentliche IPs verwendet und verfügen möglicherweise über keine VPC. Daher müssen Sie eine Regel zu ufw hinzufügen, um Datenverkehr vom Nginx-Proxy-Server über Port 80 beider Django-App-Server zuzulassen. Sie können eine Erlaubnisregel zu ufw hinzufügen, die die Ursprungs-Server-IP für Port 80 mit dem folgenden Befehl angibt:

Sobald Sie mit den Änderungen fertig sind, starten Sie Ihren Django-App-Server neu, damit die Änderungen wirksam werden, da das Ausführen von sudo ufw reload die Änderungen anscheinend nicht wirksam werden lässt:

Wenn der Server neu gestartet wurde, starten Sie den Container wie in Schritt 1 oder Schritt 2:

Versuchen Sie als Nächstes, die IP des ersten Django-Servers im Browser aufzurufen, um zu sehen, ob die Polls-Benutzeroberfläche angezeigt wird: http://FIRST_SERVER_IP/polls. Dies wird fehlschlagen. Melden Sie sich nun vom ersten Server ab und wiederholen Sie die Schritte, die Sie hier für den zweiten Server durchgeführt haben. Öffnen Sie die /etc/ufw/after.rules mit nano als Sudo-Benutzer:

Scrollen Sie wie zuvor nach unten und fügen Sie den UFW-Konfigurationsblock hinzu:

Speichern und schließen Sie die Datei, sobald Sie den obigen Block hinzugefügt haben.

Fügen Sie als Nächstes eine Freigaberegel zu ufw hinzu, die die ursprüngliche Server-IP für Port 80 mit dem folgenden Befehl angibt:

Starten Sie Ihren Server neu, damit die Änderungen wirksam werden:

Wenn der Server wieder hochgefahren ist, starten Sie den Container erneut mit dem Befehl:

Testen Sie, ob Sie die Polls-Schnittstelle anzeigen können, indem Sie direkt die IP-Adresse des zweiten Servers aufrufen: http://SECOND_SERVER_IP/polls. Dies sollte ebenfalls fehlschlagen.

Diese Architektur ist nun bereit, getestet zu werden. Sie können https://example_domain_here/polls aufrufen, um die Standard-Polls-Schnittstelle in Ihrem Browser anzuzeigen. Dies bedeutet, dass der Nginx-Proxy-Server weiterhin Zugriff auf die Django-Backend-Server hat.

Fazit

In dieser Anleitung haben wir Ihnen gezeigt, wie Sie eine skalierbare Infrastruktur mit Docker-Containern implementieren. Die Infrastruktur umfasst einen separaten PostgreSQL-Datenbankserver, zwei Backend-Anwendungsserver und einen Nginx-Proxy-Server zur Lastverteilung und Verteilung des Datenverkehrs auf die beiden Server. Obwohl wir unsere Anwendung auf der Django-Polls-Anwendung aufgebaut haben, können Sie diese Architektur für verschiedene Anwendungen mit unterschiedlichen Frameworks wie Node.js, Laravel usw. anpassen.

Dies ist eine grundlegende Richtlinie für den Einstieg. Einige Verbesserungen, die Sie hinzufügen können, bestehen darin, Ihr Image in einem Image-Repository wie Docker Hub zu hosten, was eine einfache Verteilung des Images auf mehrere Server ermöglicht. Sie können auch Pipelines für kontinuierliche Integration und Bereitstellung hinzufügen, um Images automatisch zu erstellen, zu testen und auf App-Servern bereitzustellen, sobald ein Ereignis eintritt. Ein Ereignis könnte beispielsweise das Pushen von neuem Code in einen bestimmten Branch in einem Git-Repository sein. Möglicherweise möchten Sie auch automatisieren, was passiert, wenn der Container auf einen Fehler stößt. Die offizielle Docker-Dokumentation bietet eine gute Richtlinie für das automatische Starten von Containern im Falle von Fehlern oder einem Systemneustart.

Viel Spaß beim Computing!

author

Hark Labs

Autor · CloudSigma

Preslav Dobrev ist ein kreativer Designer bei CloudSigma und konzentriert sich auf eine konsistente Unternehmensidentität durch traditionelle und innovative Marketingkanäle. Er versteht es meisterhaft, künstlerische Vision mit strategischem Marketing zu verbinden, um wirkungsvolle Markengeschichten zu schaffen.

Kommentare

Noch keine Kommentare. Schreiben Sie den ersten.