One place for hosting & domains

      sichern

      Sichern von MongoDB unter Ubuntu 20.04


      Eine frühere Version dieser Anleitung wurde von Melissa Anderson verfasst.

      Einführung

      MongoDB , auch als Mongo bekannt, ist eine Open-Source-Dokumentendatenbank, die in vielen modernen Webanwendungen verwendet wird. Sie ist als NoSQL-Datenbank klassifiziert, weil sie sich nicht auf eine traditionelle tabellenbasierte relationale Datenbankstruktur stützt. Stattdessen verwendet sie JSON-ähnliche Dokumente mit dynamischen Schemas.

      Bei MongoDB ist Authentifizierung nicht standardmäßig aktiviert. Das bedeutet, dass jeder Benutzer mit Zugriff auf den Server, auf dem die Datenbank installiert ist, ohne Einschränkungen Daten hinzufügen und löschen kann. Zur Beseitigung dieser Schwachstelle leitet Sie dieses Tutorial durch das Erstellen eines administrativen Benutzers und das Aktivieren der Authentifizierung. Dann werden Sie testen, ob nur dieser administrative Benutzer Zugriff auf die Datenbank hat.

      Voraussetzungen

      Um dieses Tutorial zu absolvieren, benötigen Sie Folgendes:

      • Einen Server, auf dem Ubuntu 20.04 ausgeführt wird. Dieser Server sollte über einen administrativen non-root user und eine mit UFW konfigurierte Firewall verfügen. Sie können dies einrichten, indem Sie unserem Leitfaden zur Ersteinrichtung des Servers mit Ubuntu 20.04 folgen.
      • MongoDB, das auf Ihrem Server installiert ist. Dieses Tutorial wurde mit MongoDB-Version 4.4 validiert, wobei es im Allgemeinen auch mit älteren Versionen von MongoDB funktionieren sollte. Um MongoDB auf Ihrem Server zu installieren, folgen Sie unserem Tutorial Installieren von MongoDB unter Ubuntu 20.04.

      Schritt 1 — Hinzufügen eines administrativen Benutzers

      Seit der Veröffentlichung von Version 3.0 ist das MongoDB-Daemon so konfiguriert, dass es Verbindungen nur aus dem lokalen Unix-Socket akzeptiert und nicht automatisch für das breitere Internet geöffnet ist. Authentifizierung ist dennoch standardmäßig deaktiviert. Das bedeutet, dass alle Benutzer, die Zugriff auf den Server haben, auf dem MongoDB installiert ist, auch über vollständigen Zugriff auf die Datenbanken verfügen.

      Als ersten Schritt zum Beseitigen dieser Schwachstelle erstellen Sie einen administrativen Benutzer. Später aktivieren Sie Authentifizierung und stellen als der administrative Benutzer eine Verbindung her, um auf die Datenbank zuzugreifen.

      Zum Hinzufügen eines administrativen Benutzers müssen Sie zunächst eine Verbindung mit der Mongo-Shell herstellen. Da Authentifizierung deaktiviert ist, können Sie dies mit dem Befehl mongo ohne jegliche anderen Optionen tun:

      Es gibt eine Ausgabe über der Mongo-Shell-Eingabeaufforderung. Da Sie Authentifizierung noch nicht aktiviert haben, umfasst dies eine Warnung darüber, dass Zugriffskontrolle für die Datenbank nicht aktiviert ist und der Zugriff zum Lesen und Schreiben auf Daten und die Konfiguration der Datenbank uneingeschränkt ist:

      Output

      MongoDB shell version v4.4.0 . . . 2020-06-09T13:26:51.391+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database. 2020-06-09T13:26:51.391+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted. . . . >

      Diese Warnungen verschwinden, wenn Sie Authentifizierung aktivieren. Vorerst bedeuten sie aber, dass jeder, der auf Ihren Ubuntu-Server zugreifen kann, auch die Kontrolle über Ihre Datenbank übernehmen kann.

      Führen Sie zur Veranschaulichung den Befehl show dbs aus:

      Dieser Befehl gibt eine Liste aller Datenbanken auf dem Server zurück. Wenn Authentifizierung aktiviert ist, ändert sich die Liste jedoch je nach Rolle des Mongo-Benutzers oder Zugriffsstufe auf bestimmte Datenbanken. Da Authentifizierung aber deaktiviert ist, wird jede Datenbank zurückgegeben, die sich derzeit im System befindet (ohne Einschränkungen):

      Output

      admin 0.000GB config 0.000GB local 0.000GB

      In dieser Beispielausgabe erscheinen nur die Standarddatenbanken. Wenn Sie in Ihrem System jedoch über Datenbanken verfügen, die vertrauliche Daten enthalten, können beliebige Benutzer sie mit diesem Befehl finden.

      Zur Beseitigung dieser Schwachstelle geht es in diesem Schritt um das Hinzufügen eines administrativen Benutzers. Dazu müssen Sie zunächst eine Verbindung mit der admin-Datenbank herstellen. Hier werden Informationen über Benutzer wie deren Benutzernamen, Passwörter und Rollen gespeichert:

      Output

      switched to db admin

      MongoDB wird zusammen mit einer Reihe von JavaScript-basierten Shell-Methoden installiert, die Sie zur Verwaltung Ihrer Datenbank verwenden können. Eine von ihnen, die Methode db.createUser, dient zur Erstellung neuer Benutzer in der Datenbank, in der die Methode ausgeführt wird.

      Initiieren Sie die Methode db.createUser:

      Diese Methode erfordert die Angabe eines Benutzernamens und eines Passworts für den Benutzer sowie der Rollen, die der Benutzer haben soll. Erinnern Sie sich daran, dass MongoDB Daten in JSON-ähnlichen Dokumenten speichert. Wenn Sie einen neuen Benutzer erstellen, erstellen Sie also lediglich ein Dokument, in dem die entsprechenden Benutzerdaten als einzelne Felder gespeichert werden.

      Wie bei Objekten in JSON beginnen und enden Dokumente in MongoDB mit geschweiften Klammern ({ und }). Um einen Benutzer hinzuzufügen, geben Sie eine öffnende geschweifte Klammer ein:

      Anmerkung: Mongo registriert die Methode db.createUser erst als abgeschlossen, wenn Sie eine schließende Klammer eingeben. Bis dahin ändert sich die Eingabeaufforderung von einem Größer-als-Zeichen (>) in ein Auslassungszeichen (...).

      Geben Sie als Nächstes ein user:-Feld mit dem gewünschten Benutzernamen als Wert in doppelten Anführungszeichen gefolgt von einem Komma ein. Das folgende Beispiel gibt den Benutzernamen AdminSammy an, Sie können aber einen beliebigen Benutzernamen eingeben:

      Geben Sie als Nächstes ein pwd-Feld mit der Methode passwordPrompt() als Wert ein. Wenn Sie die Methode db.createUser ausführen, stellt die Methode passwordPrompt() eine Eingabeaufforderung bereit, in die Sie Ihr Passwort eingeben müssen. Dies ist sicherer als die Alternative, bei der Sie das Passwort in Klartext ausschreiben, wie Sie es bei Ihrem Benutzernamen getan haben.

      Anmerkung: Die Methode passwordPrompt() ist nur mit MongoDB-Versionen 4.2 und höher kompatibel. Wenn Sie eine ältere Version von Mongo verwenden, müssen Sie Ihr Passwort in Klartext ausschreiben, ähnlich wie bei Ihrem Benutzernamen:

      Lassen Sie auf dieses Feld ein Komma folgen:

      Geben Sie dann die Rollen ein, die der administrative Benutzer haben soll. Da Sie einen administrativen Benutzer erstellen, sollten Sie ihm mindestens die Rolle userAdminAnyDatabase für die admin-Datenbank gewähren. Damit kann der administrative Benutzer neue Benutzer und Rollen erstellen sowie modifizieren. Da der administrative Benutzer diese Rolle in der admin-Datenbank hat, erhält er auch Superuser-Zugriff auf das gesamte Cluster.

      Außerdem wird dem administrativen Benutzer im folgenden Beispiel die Rolle readWriteAnyDatabase gewährt. Dadurch erhält der administrative Benutzer die Möglichkeit, Daten in jeder beliebigen Datenbank im Cluster zu lesen und zu ändern. Ausgenommen davon sind die Datenbanken config und local, die meist der internen Verwendung dienen:

      • roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]

      Geben Sie danach eine schließende geschweifte Klammer ein, um das Ende des Dokuments zu markieren:

      Geben Sie dann eine schließende Klammer ein, um die Methode db.createUser zu schließen und auszuführen:

      Insgesamt sollte Ihre Methode db.createUser so aussehen:

      > db.createUser(
      ... {
      ... user: "AdminSammy",
      ... pwd: passwordPrompt(),
      ... roles: [ { role: "userAdminAnyDatabase", db: "admin" }, "readWriteAnyDatabase" ]
      ... }
      ... )
      

      Wenn die gesamte Zeilensyntax korrekt ist, wird die Methode ordnungsgemäß ausgeführt und Sie werden dazu aufgefordert, ein Passwort einzugeben:

      Output

      Enter password:

      Geben Sie ein starkes Passwort Ihrer Wahl ein. Dann erhalten Sie eine Bestätigung, dass der Benutzer hinzugefügt wurde:

      Output

      Successfully added user: { "user" : "AdminSammy", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" }, "readWriteAnyDatabase" ] }

      Nun können Sie den MongoDB-Client beenden:

      An dieser Stelle kann Ihr Benutzer Anmeldedaten eingeben. Das wird jedoch erst obligatorisch, wenn Sie Authentifizierung aktivieren und das MongoDB-Daemon neu starten.

      Schritt 2 — Aktivieren von Authentifizierung

      Um Authentifizierung zu aktivieren, müssen Sie mongod.conf, die Konfigurationsdatei von MongoDB, bearbeiten. Wenn Sie den Mongo-Dienst aktivieren und neu starten, können Benutzer weiterhin eine Verbindung zur Datenbank herstellen, ohne sich zu authentifizieren. Sie können Daten jedoch erst dann lesen oder ändern, nachdem sie einen richtigen Benutzernamen und ein richtiges Passwort eingegeben haben.

      Öffnen Sie die Konfigurationsdatei mit Ihrem bevorzugten Texteditor. Wir verwenden hier nano:

      • sudo nano /etc/mongod.conf

      Scrollen Sie nach unten, um den auskommentierten Abschnitt security zu finden:

      /etc/mongod.conf

      . . .
      #security:
      
      #operationProfiling:
      
      . . .
      

      Heben Sie die Kommentierung dieser Zeile auf, indem Sie das Doppelkreuzzeichen (#) entfernen:

      /etc/mongod.conf

      . . .
      security:
      
      #operationProfiling:
      
      . . .
      

      Fügen Sie dann den Parameter authorization hinzu und setzen Sie ihn auf "enabled". Wenn Sie damit fertig sind, sollten die Zeilen wie folgt aussehen:

      /etc/mongod.conf

      . . .
      security:
        authorization: "enabled"
      . . .
      

      Beachten Sie, dass die Zeile security: am Anfang keine Leerzeichen aufweist, während die Zeile authorization: um zwei Leerzeichen eingerückt ist.

      Nach dem Hinzufügen dieser Zeilen speichern und schließen Sie die Datei. Wenn Sie zum Bearbeiten der Datei nano verwendet haben, drücken Sie dazu Strg + X, Y und dann ENTER.

      Starten Sie dann das Daemon neu, um die neuen Änderungen anzuwenden:

      • sudo systemctl restart mongod

      Als Nächstes überprüfen Sie den Status des Diensts, um sicherzustellen, dass er richtig neu gestartet wurde:

      • sudo systemctl status mongod

      Wenn der Befehl restart erfolgreich war, erhalten Sie eine Ausgabe, die angibt, dass der Dienst mongod aktiv ist und vor kurzem gestartet wurde:

      Output

      ● mongod.service - MongoDB Database Server Loaded: loaded (/lib/systemd/system/mongod.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2020-06-09 22:06:20 UTC; 7s ago Docs: https://docs.mongodb.org/manual Main PID: 15370 (mongod) Memory: 170.1M CGroup: /system.slice/mongod.service └─15370 /usr/bin/mongod --config /etc/mongod.conf Jun 09 22:06:20 your_host systemd[1]: Started MongoDB Database Server.

      Nachdem Sie sich vergewissert haben, dass das Daemon ordnungsgemäß ausgeführt wird, können Sie nun testen, ob die von Ihnen hinzugefügte Authentifzierungseinstellung wie erwartet funktioniert.

      Schritt 3 — Testen von Authentifizierungseinstellungen

      Um zu testen, ob die im vorherigen Schritt hinzugefügten Authentifizierungsanforderungen richtig funktionieren, stellen Sie eine Verbindung her, ohne Anmeldedaten anzugeben. So können Sie prüfen, ob Ihre Möglichkeiten tatsächlich eingeschränkt sind:

      Nachdem Sie Authentifizierung aktiviert haben, erscheint nun keine der zuvor angezeigten Warnungen mehr:

      Output

      MongoDB shell version v4.4.0 connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb Implicit session: session { "id" : UUID("5d50ed96-f7e1-493a-b4da-076067b2d898") } MongoDB server version: 4.4.0 >

      Vergewissern Sie sich, dass Ihr Zugriff eingeschränkt ist, indem Sie erneut den Befehl show dbs ausführen:

      Rufen Sie sich aus Schritt 1 in Erinnerung, dass es mindestens einige Standarddatenbanken auf Ihrem Server gibt. In diesem Fall erzeugt der Befehl jedoch keine Ausgabe, da Sie sich nicht als privilegierter Benutzer authentifiziert haben.

      Da der Befehl keine Informationen zurückgibt, kann man ruhig sagen, dass die Authentifizierungseinstellung wie erwartet funktioniert. Sie werden auch keine Benutzer erstellen oder andere privilegierte Aufgaben ausführen können, ohne sich zunächst zu authentifizieren.

      Fahren Sie fort und schließen Sie die MongoDB-Shell:

      Anmerkung: Anstatt den folgenden exit-Befehl auszuführen, wie Sie es zuvor in Schritt 1 getan haben, ist eine alternative Methode zum Schließen der Shell das simple Drücken auf Strg+C.

      Als Nächstes stellen Sie sicher, dass sich Ihr administrativer Benutzer richtig authentifizieren kann, indem Sie als dieser Benutzer folgenden mongo-Befehl zum Herstellen einer Verbindung ausführen. Der Befehl enthält das Flag -u, das dem Namen des Benutzers, als der Sie eine Verbindung herstellen möchten, vorausgeht. Sorgen Sie dafür, dass Sie AdminSammy durch den Benutzernamen Ihres eigenen administrativen Benutzers ersetzen. Außerdem enthält der Befehl das Flag -p, das Sie zur Eingabe des Passworts des Benutzers auffordert, und gibt admin als Authentifizierungsdatenbank an, in der der angegebene Benutzername erstellt wurde:

      • mongo -u AdminSammy -p --authenticationDatabase admin

      Geben Sie das Passwort des Benutzers ein, wenn Sie dazu aufgefordert werden, um zur Shell zu gelangen. Versuchen Sie es hier erneut mit dem Befehl show dbs:

      Da Sie richtig authentifiziert haben, gibt der Befehl diesmal eine Liste aller Datenbanken zurück, die sich derzeit auf dem Server befinden:

      Output

      admin 0.000GB config 0.000GB local 0.000GB

      Das bestätigt, dass Authentifizierung erfolgreich aktiviert wurde.

      Zusammenfassung

      Durch Ausführung der Schritte in diesem Leitfaden haben Sie einen administrativen MongoDB-Benutzer eingerichtet, den Sie zum Erstellen und Ändern von neuen Benutzern und Rollen verwenden sowie zum sonstigen Verwalten Ihrer MongoDB-Instanz verwenden können. Außerdem haben Sie Ihre MongoDB-Instanz so konfiguriert, dass sich Benutzer mit einem gültigen Benutzernamen und einem Passwort authentifizieren müssen, bevor sie mit Daten interagieren können.

      Weitere Informationen zum Verwalten von MongoDB-Benutzern finden Sie in der offiziellen Dokumentation zu diesem Thema. Vielleicht wollen Sie auch mehr über die Funktionsweise von Authentifizierung in MongoDB erfahren.

      Wenn Sie mit Ihrer MongoDB-Instanz auch Remoteinteraktionen planen, können Sie unserem Leitfaden zum Konfigurieren von Remotezugriff für MongoDB unter Ubuntu 20.04 folgen.



      Source link

      Einrichten und Sichern eines etcd-Clusters mit Ansible unter Ubuntu 18.04


      Der Autor hat die Wikimedia Foundation dazu ausgewählt, im Rahmen des Programms Write for DOnations eine Spende zu erhalten.

      Einführung

      etcd ist ein verteilter Schlüsselwertspeicher, auf den sich viele Plattformen und Tools verlassen, darunter Kubernetes, Vulcand und Doorman. Innerhalb von Kubernetes dient etcd als globaler Konfigurationsspeicher, der den Status des Clusters speichert. Kenntnisse zur Verwaltung von etcd sind unerlässlich für die Verwaltung eines Kubernetes-Clusters. Zwar gibt es viele verwaltete Kubernetes-Produkte (auch als Kubernetes-as-a-Service bekannt), die diese Administrationsaufgaben für Sie übernehmen, doch entscheiden sich viele Unternehmen wegen der damit verbundenen Flexibilität immer noch für selbstverwaltete Kubernetes-Cluster.

      Die erste Hälfte dieses Artikels führt Sie durch die Einrichtung eines etcd-Clusters mit drei Knoten auf Ubuntu 18.04-Servern. In der zweiten Hälfte geht es um das Sichern des Clusters mit Transport Layer Security oder TLS. Um jede Einrichtung automatisiert auszuführen, verwenden wir durchgehend Ansible. Ansible ist ein Konfigurationsmanagement-Tool ähnlich wie Puppet, Chef, und SaltStack; damit können wir die einzelnen Einrichtungsschritte auf deklarative Weise definieren, und zwar in Dateien namens Playbooks.

      Am Ende dieses Tutorials verfügen Sie über einen sicheren etcd-Cluster mit drei Knoten, der auf Ihren Servern ausgeführt wird. Außerdem werden Sie über ein Ansible-Playbook verfügen, mit dem Sie die gleiche Einrichtung auf einem neuen Satz von Servern wiederholt und konsequent nachbilden können.

      Voraussetzungen

      Bevor Sie diese Anleitung beginnen, benötigen Sie Folgendes:

      • Python, pip und das auf Ihrem lokalen Computer installierte pyOpenSSL-Paket. Um zu erfahren, wie Sie Python3, pip und Python-Pakete installieren können, lesen Sie Installieren von Python 3 und Einrichten einer lokalen Programmierumgebung unter Ubuntu 18.04.

      • Drei Ubuntu 18.04-Server im gleichen lokalen Netzwerk mit mindestens 2 GB RAM und root SSH-Zugriff. Außerdem sollten Sie die Server so konfigurieren, dass sie die Hostnamen etcd1, etcd2 und etcd3 tragen. Die in diesem Artikel beschriebenen Schritte würden auf jedem generischen Server funktionieren, nicht nur bei DigitalOcean Droplets. Wenn Sie Ihre Server aber in DigitalOcean hosten möchten, können Sie dem Leitfaden Erstellen eines Droplets über das DigitalOcean Control Panel folgen, um diese Anforderung zu erfüllen. Beachten Sie, dass Sie bei der Erstellung Ihres Droplets die Option Private Networking aktivieren müssen. Um für vorhandene Droplets private Netzwerke zu aktivieren, lesen Sie Aktivieren von Private Networking in Droplets.

      Warnung: Da der Zweck dieses Artikels darin besteht, eine Einführung in das Einrichten eines etcd-Clusters in einem privaten Netzwerk zu liefern, wurden die drei Ubuntu 18.04-Server in dieser Einrichtung nicht mit einer Firewall getestet und als root user aufgerufen. In einer Produktionsumgebung würde jeder dem öffentlichen Internet ausgesetzte Knoten eine Firewall und einen Sudo-Benutzer erfordern, damit sich bewährte Sicherheitspraktiken einhalten lassen. Weitere Informationen finden Sie im Tutorial Ersteinrichtung des Servers mit Ubuntu 18.04.

      • Ein SSH-Schlüsselpaar, das Ihrem lokalen Rechner Zugriff auf die Server etcd1, etcd2 und etcd3 erlaubt. Wenn Sie nicht wissen, was SSH ist oder über kein SSH-Schlüsselpaar verfügen, können Sie hier mehr darüber erfahren: SSH Essentials: Working with SSH Servers, Clients, and Keys (SSH-Grundlagen: Arbeiten mit SSH-Servern, -Clients und -Schlüsseln).

      • Auf Ihrem lokalen Rechner installiertes Ansible. Wenn Sie beispielsweise Ubuntu 18.04 ausführen, können Sie Ansible installieren, indem Sie Schritt 1 des Artikels Installieren und Konfigurieren von Ansible unter Ubuntu 18.04 befolgen. Dadurch werden die Befehle ansible und ansible-playbook auf Ihrem Computer verfügbar. Vielleicht möchten Sie auch How to Use Ansible: A Reference Guide (Verwenden von Ansible: Ein Referenzhandbuch) parat halten. Die Befehle in diesem Tutorial sollten mit Ansible v2.x funktionieren; wir haben sie in Ansible v2.9.7 unter Ausführung von Python v3.8.2 getestet.

      Schritt 1 — Konfigurieren von Ansible für den Steuerknoten

      Ansible ist ein Tool, das zum Verwalten von Servern dient. Die Server, die Ansible verwaltet, werden verwaltete Knoten genannt. Das Gerät, auf dem Ansible ausgeführt wird, wird als Steuerknoten bezeichnet. Ansible arbeitet mit SSH-Schlüsseln im Steuerknoten, um Zugriff auf die verwalteten Knoten zu erhalten. Sobald eine SSH-Sitzung eingerichtet ist, führt Ansible eine Reihe von Skripten aus, um die verwalteten Knoten bereitzustellen und zu konfigurieren. In diesem Schritt testen wir, ob wir Ansible zur Verbindungsherstellung mit den verwalteten Knoten verwenden und den Befehl hostname ausführen können.

      Ein typischer Tag für einen Systemadministrator kann das Verwalten verschiedener Sätze von Knoten beinhalten. Beispielsweise können Sie Ansible verwenden, um neue Server bereitzustellen; später aber verwenden Sie es, um einen anderen Satz von Servern neu zu konfigurieren. Um Administratoren eine bessere Organisation des Satzes von verwalteten Knoten zu ermöglichen, verfügt Ansible über das Konzept des Hostinventars (oder kurz Inventar). Sie können jeden Knoten, den Sie mit Ansible verwalten möchten, in einer Inventardatei definieren und in Gruppen anordnen. Wenn Sie dann die Befehle ansible und ansible-playbook ausführen, können Sie angeben, für welche Hosts oder Gruppen der Befehl gelten soll.

      Standardmäßig liest Ansible die Inventardatei von /etc/ansible/hosts; wir können jedoch eine andere Inventardatei angeben, indem wir das Flag --inventory (oder kurz -i) verwenden.

      Erstellen Sie zunächst ein neues Verzeichnis auf Ihrem lokalen Rechner (dem Steuerknoten), in dem alle Dateien für dieses Tutorial installiert werden:

      • mkdir -p $HOME/playground/etcd-ansible

      Rufen Sie dann das gerade erstellte Verzeichnis auf:

      • cd $HOME/playground/etcd-ansible

      Erstellen und öffnen Sie im Verzeichnis mit Ihrem Editor eine leere Inventardatei namens hosts:

      • nano $HOME/playground/etcd-ansible/hosts

      Listen Sie in der Datei hosts alle Ihre verwalteten Knoten im folgenden Format auf und ersetzen Sie die markierten öffentlichen IP-Adressen durch die wahren öffentlichen IP-Adressen Ihrer Server:

      ~/playground/etcd-ansible/hosts

      [etcd]
      etcd1 ansible_host=etcd1_public_ip  ansible_user=root
      etcd2 ansible_host=etcd2_public_ip  ansible_user=root
      etcd3 ansible_host=etcd3_public_ip  ansible_user=root
      

      Die Zeile [etcd] definiert eine Gruppe namens etcd. Unter der Gruppendefinition listen wir alle unsere verwalteten Knoten auf. Jede Zeile beginnt mit einem Alias (z. B. etcd1), mit dem wir unter Verwendung eines leicht zu merkenden Namens anstelle einer langen IP-Adresse auf jeden einzelnen Host verweisen können. Die Variablen ansible_host und ansible_user sind Ansible-Variablen. In diesem Fall dienen sie zur Bereitstellung von Ansible mit den öffentlichen IP-Adressen und SSH-Benutzernamen, die beim Herstellen einer Verbindung über SSH verwendet werden.

      Um zu prüfen, ob Ansible eine Verbindung mit unseren verwalteten Knoten herstellen kann, testen wir mithilfe von Ansible durch Ausführung des Befehls hostname auf den einzelnen Hosts in der Gruppe etcd die Konnektivität:

      • ansible etcd -i hosts -m command -a hostname

      Lassen Sie uns diesen Befehl genauer ansehen, um zu erfahren, was die einzelnen Teile bedeuten:

      • etcd: gibt das Hostmuster an, mit dem ermittelt wird, welche Hosts aus dem Inventar mit diesem Befehl verwaltet werden. Hier verwenden wir den Gruppennamen als Hostmuster.
      • -i hosts: gibt die zu verwendende Inventardatei an.
      • -m command: Die Funktionalität hinter Ansible wird von Modulen bereitgestellt. Das command-Modul nimmt das übergebene Argument und führt es als Befehl auf den einzelnen verwalteten Knoten aus. Im Verlauf dieses Tutorials werden noch einige weitere Ansible-Module eingeführt.
      • -a hostname: das Argument, das an das Modul übergeben wird. Die Zahl und Arten von Argumenten hängen vom Modul ab.

      Nach Ausführung des Befehls sehen Sie die folgende Ausgabe, was bedeutet, dass Ansible richtig konfiguriert wurde:

      Output

      etcd2 | CHANGED | rc=0 >> etcd2 etcd3 | CHANGED | rc=0 >> etcd3 etcd1 | CHANGED | rc=0 >> etcd1

      Jeder Befehl, den Ansible ausführt, wird als Aufgabe bezeichnet. Die Verwendung von ansible in der Befehlszeile zum Ausführen von Aufgaben wird Ausführung von ad-hoc-Befehlen genannt. Der Vorteil von Ad-hoc-Befehlen besteht darin, dass sie schnell sind und wenig Einrichtung benötigen; der Nachteil ist, dass sie manuell ausgeführt werden und sich somit nicht für ein Versionskontrollsystem wie Git verwenden lassen.

      Eine kleine Verbesserung wäre es, ein Shell-Skript zu schreiben und unsere Befehle mit dem script-Modul von Ansible auszuführen. So könnten wir die Konfigurationsschritte, die wir ergriffen haben, in die Versionskontrolle aufnehmen. Allerdings sind Shell-Skripte imperativ. Das bedeutet, dass wir die auszuführenden Befehle (die „wie“s) ermitteln müssen, um das System mit Blick auf den gewünschten Zustand zu konfigurieren. Ansible hingegen setzt auf einen deklarativen Ansatz, bei dem wir definieren, „was“ der gewünschte Zustand unseres Servers in Konfigurationsdateien sein sollte. Ansible ist dafür verantwortlich, den Server in den gewünschten Zustand zu bringen.

      Der deklarative Ansatz wird bevorzugt, da die Absicht der Konfigurationsdatei sofort übermittelt wird, was bedeutet, dass er leichter zu verstehen und zu verwalten ist. Außerdem wird dabei die Verantwortung für die Bearbeitung von Edge-Fällen vom Administrator auf Ansible übertragen, was uns eine Menge Arbeit spart.

      Nachdem Sie nun den Ansible-Steuerknoten zur Kommunikation mit den verwalteten Knoten konfiguriert haben, stellen wir Ihnen im nächsten Schritt Ansible-Playbooks vor, mit denen Sie Aufgaben deklarativ angeben können.

      Schritt 2 — Abrufen der Hostnamen von verwalteten Knoten mit Ansible Playbooks

      In diesem Schritt werden wir replizieren, was wir in Schritt 1 getan haben: das Ausdrucken der Hostnamen der verwalteten Knoten. Anstatt Ad-hoc-Aufgaben auszuführen, werden wir die einzelnen Aufgaben jedoch deklarativ als Ansible-Playbook definieren und ausführen. Ziel dieses Schritt ist es, zu zeigen, wie Ansible Playbooks funktionieren. Wir werden mit Playbooks in späteren Schritten noch deutlich umfangreichere Aufgaben ausführen.

      Erstellen Sie in Ihrem Projektverzeichnis mit Ihrem Editor eine neue Datei namens playbook.yaml:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie in playbook.yaml die folgenden Zeilen hinzu:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        tasks:
          - name: "Retrieve hostname"
            command: hostname
            register: output
          - name: "Print hostname"
            debug: var=output.stdout_lines
      

      Schließen und speichern Sie die Datei playbook.yaml, indem Sie Strg+X drücken, gefolgt von J.

      Das Playbook enthält eine Liste von Plays; jedes Play enthält eine Liste von Aufgaben, die auf allen Hosts ausgeführt werden sollen, die mit dem vom Schlüssel hosts angegebenen Hostmuster übereinstimmen. In diesem Playbook verfügen wir über ein Play, das zwei Aufgaben enthält. Die erste Aufgabe führt den Befehl hostname mit dem command-Modul aus und registriert die Ausgabe in einer Variable namens output. In der zweiten Aufgabe verwenden wir das debug-Modul, um die Eigenschaft stdout_lines der output-Variablen auszugeben.

      Wir können dieses Playbook nun mit dem Befehl ansible-playbook ausführen:

      • ansible-playbook -i hosts playbook.yaml

      Sie erhalten die folgende Ausgabe, was bedeutet, dass Ihr Playbook ordnungsgemäß funktioniert:

      Output

      PLAY [etcd] *********************************************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************ ok: [etcd2] ok: [etcd3] ok: [etcd1] TASK [Retrieve hostname] ********************************************************************************************************** changed: [etcd2] changed: [etcd3] changed: [etcd1] TASK [Print hostname] ************************************************************************************************************* ok: [etcd1] => { "output.stdout_lines": [ "etcd1" ] } ok: [etcd2] => { "output.stdout_lines": [ "etcd2" ] } ok: [etcd3] => { "output.stdout_lines": [ "etcd3" ] } PLAY RECAP ************************************************************************************************************************ etcd1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      Anmerkung: ansible-playbook verwendet zum Teil cowsay als verspielte Methode zur Ausgabe der Überschriften. Wenn Sie in Ihrem Terminal viele ASCII-artige Kühe sehen, wissen Sie jetzt warum. Um diese Funktion zu deaktivieren, setzen Sie die Umgebungsvariable ANSIBLE_NOCOWS vor dem Ausführen von ansible-playbook auf 1, indem Sie in Ihrer Shell export ANSIBLE_NOCOWS=1 ausführen.

      In diesem Schritt sind wir von der Ausführung von imperativen Ad-hoc-Aufgaben zum Ausführen von deklarativen Playbooks übergegangen. Im nächsten Schritt ersetzen wir diese beiden Vorführaufgaben durch Aufgaben, die für die Einrichtung unseres etcd-Clusters sorgen werden.

      Schritt 3 — Installieren von etcd in den verwalteten Knoten

      In diesem Schritt zeigen wir Ihnen die Befehle zur manuellen Installation von etcd und demonstrieren, wie Sie die gleichen Befehle in unserem Ansible-Playbook in Aufgaben übersetzen können.

      etcd und dessen Client etcdctl sind als Binärdateien verfügbar, die wir herunterladen, extrahieren und in einem Verzeichnis platzieren werden, das Teil der PATH-Umgebungsvariablen ist. Bei manueller Konfiguration sind dies die Schritte, die wir auf jedem der verwalteten Knoten ausführen würden:

      • mkdir -p /opt/etcd/bin
      • cd /opt/etcd/bin
      • wget -qO- https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz | tar --extract --gzip --strip-components=1
      • echo 'export PATH="$PATH:/opt/etcd/bin"' >> ~/.profile
      • echo 'export ETCDCTL_API=3" >> ~/.profile

      Die ersten vier Befehle sorgen dafür, dass die Binärdateien heruntergeladen und im Verzeichnis /opt/etcd/bin/ extrahiert werden. Standardmäßig nutzt der etcdctl-Client API v2 zur Kommunikation mit dem etcd-Server. Da wir etcd v3.x ausführen, setzt der letzte Befehl die Umgebungsvariable ETCDCTL_API auf 3.

      Anmerkung: Hier verwenden wir etcd v3.3.13, was für Rechner mit Prozessoren entwickelt wurde, die das AMD64-Anweisungsset verwenden. Auf der offiziellen GitHub Release-Seite finden Sie Binärdateien für andere Systeme und Versionen.

      Um die gleichen Schritte in einer standardisierten Form zu replizieren, können wir unserem Playbook Aufgaben hinzufügen. Öffnen Sie die Playbook-Datei playbook.yaml in Ihrem Editor:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Ersetzen Sie die gesamte Datei playbook.yaml durch folgende Inhalte:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
            file:
              path: /opt/etcd/bin
              state: directory
              owner: root
              group: root
              mode: 0700
          - name: "Download the tarball into the /tmp directory"
            get_url:
              url: https://storage.googleapis.com/etcd/v3.3.13/etcd-v3.3.13-linux-amd64.tar.gz
              dest: /tmp/etcd.tar.gz
              owner: root
              group: root
              mode: 0600
              force: True
          - name: "Extract the contents of the tarball"
            unarchive:
              src: /tmp/etcd.tar.gz
              dest: /opt/etcd/bin/
              owner: root
              group: root
              mode: 0600
              extra_opts:
                - --strip-components=1
              decrypt: True
              remote_src: True
          - name: "Set permissions for etcd"
            file:
              path: /opt/etcd/bin/etcd
              state: file
              owner: root
              group: root
              mode: 0700
          - name: "Set permissions for etcdctl"
            file:
              path: /opt/etcd/bin/etcdctl
              state: file
              owner: root
              group: root
              mode: 0700
          - name: "Add /opt/etcd/bin/ to the $PATH environment variable"
            lineinfile:
              path: /etc/profile
              line: export PATH="$PATH:/opt/etcd/bin"
              state: present
              create: True
              insertafter: EOF
          - name: "Set the ETCDCTL_API environment variable to 3"
            lineinfile:
              path: /etc/profile
              line: export ETCDCTL_API=3
              state: present
              create: True
              insertafter: EOF
      

      Jede Aufgabe nutzt ein Modul; für diesen Satz von Aufgaben verwenden wir folgende Module:

      • file: zum Erstellen des Verzeichnisses /opt/etcd/bin und zum späteren Festlegen der Dateiberechtigungen für die Binärdateien etcd und etcdctl.
      • get_url: zum Herunterladen des gzip-ten Tarball auf die verwalteten Knoten.
      • unarchive: zum Extrahieren und Entpacken der Binärdateien etcd und etcdctl aus dem gzip-ten Tarball.
      • lineinfile: zum Hinzufügen eines Eintrags in die Datei .profile.

      Um diese Änderungen anzuwenden, schließen und speichern Sie die Datei playbook.yaml, indem Sie Strg+X drücken, gefolgt von J. Führen Sie dann im Terminal den gleichen Befehl ansible-playbook erneut aus:

      • ansible-playbook -i hosts playbook.yaml

      Der Abschnitt PLAY RECAP der Ausgabe wird nur ok und changed anzeigen:

      Output

      ... PLAY RECAP ************************************************************************************************************************ etcd1 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd2 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 etcd3 : ok=8 changed=7 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

      Um die ordnungsgemäße Installation von etcd zu prüfen, stellen Sie manuell eine SSH-Verbindung zu einem der verwalteten Knoten her und führen Sie etcd und etcdctl aus:

      etcd1_public_ip ist die öffentliche IP-Adresse des Servers namens etcd1. Sobald Sie sich SSH-Zugriff verschafft haben, führen Sie etcd --version aus, um die installierte Version von etcd auszudrucken:

      Sie werden eine Ausgabe erhalten, die in etwa der folgenden ähnelt. Das bedeutet, dass die Binärdatei etcd erfolgreich installiert wurde:

      Output

      etcd Version: 3.3.13 Git SHA: 98d3084 Go Version: go1.10.8 Go OS/Arch: linux/amd64

      Um sich zu vergewissern, dass etcdctl erfolgreich installiert wurde, führen Sie etcdctl version aus:

      Sie werden eine Ausgabe sehen, die etwa folgendermaßen aussieht:

      Output

      etcdctl version: 3.3.13 API version: 3.3

      Beachten Sie, dass die Ausgabe API version: 3.3 lautet, wodurch bestätigt wird, dass unsere Umgebungsvariable ETCDCTL_API richtig festgelegt wurde.

      Beenden Sie den etcd1-Server, um zu Ihrer lokalen Umgebung zurückzukehren.

      Wir haben etcd und etcdctl nun erfolgreich auf allen unseren verwalteten Knoten installiert. Im nächsten Schritt fügen wir unserem Play zusätzliche Aufgaben hinzu, sodass etcd als Hintergrunddienst ausgeführt wird.

      Schritt 4 — Erstellen einer Unit-Datei für etcd

      Die schnellste Methode zur Ausführung von etcd mit Ansible scheint die Verwendung des command-Moduls zur Ausführung von /opt/etcd/bin/etcd zu sein. Das funktioniert jedoch nicht, da etcd dadurch als Vordergrundprozess ausgeführt wird. Durch die Verwendung des command-Moduls wird Ansible hängenbleiben, da es auf die Rückgabe des Befehl etcd wartet, was nie geschehen wird. In diesem Schritt werden wir unser Playbook also so aktualisieren, dass stattdessen unsere Binärdatei etcd als Hintergrunddienst ausgeführt wird.

      Ubuntu 18.04 verwendet systemd als sein init-System. Das bedeutet, dass wir neue Dienste erstellen können, indem wir Unit-Dateien schreiben und im Verzeichnis /etc/systemd/system/ platzieren.

      Erstellen Sie zunächst in Ihrem Projektverzeichnis ein neues Verzeichnis namens files/:

      Erstellen Sie dann in diesem Verzeichnis mit Ihrem Editor eine neue Datei namens etcd.service:

      Kopieren Sie als Nächstes den folgenden Codeblock in die Datei files/etcd.service:

      ~/playground/etcd-ansible/files/etcd.service

      [Unit]
      Description=etcd distributed reliable key-value store
      
      [Service]
      Type=notify
      ExecStart=/opt/etcd/bin/etcd
      Restart=always
      

      Diese Unit-Datei definiert einen Dienst, der die ausführbare Datei unter /opt/etcd/bin/etcd ausführt, systemd benachrichtigt, sobald die Initialisierung beendet ist, und immer neu startet, sollte sie je beendet werden.

      Anmerkung: Wenn Sie mehr über systemd und Unit-Dateien erfahren möchten oder die Unit-Datei an Ihre Bedürfnisse anpassen möchten, lesen Sie den Leitfaden Understanding Systemd Units and Unit Files (systemd-Units und Unit-Dateien verstehen).

      Schließen und speichern Sie die Datei files/etcd.service, indem Sie Strg+X drücken, gefolgt von Y.

      Als Nächstes müssen wir eine Aufgabe in unserem Playbook hinzufügen, die die lokale Datei files/etcd.service für die einzelnen verwalteten Knoten in das Verzeichnis /etc/systemd/system/etcd.service kopiert. Wir können dies mit dem copy-Modul tun.

      Öffnen Sie Ihr Playbook:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie am Ende der bestehenden Aufgaben die folgende hervorgehobene Aufgabe an:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Set the ETCDCTL_API environment variable to 3"
            lineinfile:
              path: /etc/profile
              line: export ETCDCTL_API=3
              state: present
              create: True
              insertafter: EOF
          - name: "Create a etcd service"
            copy:
              src: files/etcd.service
              remote_src: False
              dest: /etc/systemd/system/etcd.service
              owner: root
              group: root
              mode: 0644
      

      Durch Kopieren der Unit-Datei in /etc/systemd/system/etcd.service wird nun ein Dienst definiert.

      Speichern und schließen Sie das Playbook.

      Führen Sie den gleichen Befehl ansible-playbook erneut aus, um die neuen Änderungen anzuwenden:

      • ansible-playbook -i hosts playbook.yaml

      Um zu prüfen, ob die Änderungen angewendet wurden, stellen Sie zunächst eine SSH-Verbindung mit einem der verwalteten Knoten her:

      Führen Sie dann systemctl status etcd aus, um systemd über den Status des Diensts etcd abzufragen:

      Sie erhalten die folgende Ausgabe, in der angegeben wird, dass der Dienst geladen wurde:

      Output

      ● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: inactive (dead) ...

      Anmerkung: Die letzte Zeile (Active: inactive (dead)) der Ausgabestatus gibt an, dass der Dienst inaktiv ist. Das bedeutet, dass er beim Starten des Systems nicht automatisch ausgeführt würde. Dies ist zu erwarten und kein Fehler.

      Drücken Sie q, um zur Shell zurückzukehren, und führen Sie dann exit aus, um den verwalteten Knoten zu verlassen und zu Ihrer lokalen Shell zurückzukehren:

      In diesem Schritt haben wir unser Playbook so aktualisiert, das es die Binärdatei etcd als systemd-Dienst ausführt. Im nächsten Schritt werden wir etcd weiter einrichten, indem wir Platz zur Speicherung seiner Daten zur Verfügung stellen.

      Schritt 5 — Konfigurieren des Datenverzeichnisses

      etcd ist ein Datenspeicher für Schlüsselwerte. Das bedeutet, dass wir ihm Platz zur Speicherung seiner Daten zur Verfügung stellen müssen. In diesem Schritt werden wir unser Playbook so aktualisieren, dass ein dediziertes Datenverzeichnis zur Verwendung durch etcd definiert wird.

      Öffnen Sie Ihr Playbook:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie am Ende der Liste der Aufgaben die folgende Aufgabe an:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create a etcd service"
            copy:
              src: files/etcd.service
              remote_src: False
              dest: /etc/systemd/system/etcd.service
              owner: root
              group: root
              mode: 0644
          - name: "Create a data directory"
            file:
              path: /var/lib/etcd/{{ inventory_hostname }}.etcd
              state: directory
              owner: root
              group: root
              mode: 0755
      

      Hier verwenden wir /var/lib/etcd/hostname.etcd als Datenverzeichnis, wobei hostname der Hostname des aktuellen verwalteten Knotens ist. inventory_hostname ist eine Variable, die den Hostnamen des aktuellen verwalteten Knoten darstellt; ihr Wert wird automatisch von Ansible ausgefüllt. Die Syntax mit geschweiften Klammern (d. h. {{ inventory_hostname }}) wird zur Variablenersetzung genutzt, unterstützt durch die Jinja2-Vorlagen-Engine, die die standardmäßige Vorlagen-Engine für Ansible ist.

      Schließen Sie den Texteditor und speichern Sie die Datei.

      Als Nächstes müssen wir etcd anweisen, dieses Datenverzeichnis zu verwenden. Dazu übergeben wir den Parameter data-dir an etcd. Zum Festlegen von etcd-Parametern können wir eine Kombination aus Umgebungsvariablen, Befehlszeilen-Flags und Konfigurationsdateien verwenden. In diesem Tutorial verwenden wir eine Konfigurationsdatei, da es deutlich eleganter ist, alle Konfigurationen in einer Datei zu isolieren, anstatt die Konfiguration über unser ganzes Playbook zu verteilen.

      Erstellen Sie in Ihrem Projektverzeichnis ein neues Verzeichnis namens templates/:

      Erstellen Sie dann in dem Verzeichnis mit Ihrem Editor eine neue Datei namens etcd.conf.yaml.j2:

      • nano templates/etcd.conf.yaml.j2

      Kopieren Sie als Nächstes die folgende Zeile und fügen Sie sie in die Datei ein:

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      

      Diese Datei verwendet die gleiche Jinja2-Variablenersetzungssyntax wie unser Playbook. Um die Variablen zu ersetzen und das Ergebnis in die einzelnen verwalteten Hosts hochzuladen, können wir das template-Modul verwenden. Es funktioniert auf ähnliche Weise wie copy, nimmt vor dem Upload jedoch eine Variablenersetzung vor.

      Beenden Sie etcd.conf.yaml.j2 und öffnen Sie dann Ihr Playbook:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie der Liste der Aufgaben die folgenden Aufgaben an, um ein Verzeichnis zu erstellen und die vorlagenbasierte Konfigurationsdatei darin hochzuladen:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create a data directory"
            file:
              ...
              mode: 0755
          - name: "Create directory for etcd configuration"
            file:
              path: /etc/etcd
              state: directory
              owner: root
              group: root
              mode: 0755
          - name: "Create configuration file for etcd"
            template:
              src: templates/etcd.conf.yaml.j2
              dest: /etc/etcd/etcd.conf.yaml
              owner: root
              group: root
              mode: 0600
      

      Speichern und schließen Sie diese Datei.

      Da wir diese Änderung vorgenommen haben, müssen wir nun die Unit-Datei unseres Diensts aktualisieren, damit ihr der Speicherort unserer Konfigurationsdatei übergeben wird (d. h. /etc/etcd/etcd.conf.yaml).

      Öffnen Sie die Datei etcd.service auf Ihrem lokalen Rechner:

      Aktualisieren Sie die Datei files/etcd.service, indem Sie das im Folgenden hervorgehobene Flag --config-file hinzufügen:

      ~/playground/etcd-ansible/files/etcd.service

      [Unit]
      Description=etcd distributed reliable key-value store
      
      [Service]
      Type=notify
      ExecStart=/opt/etcd/bin/etcd --config-file /etc/etcd/etcd.conf.yaml
      Restart=always
      

      Speichern und schließen Sie die Datei.

      In diesem Schritt haben wir unser Playbook zur Bereitstellung eines Datenverzeichnisses für etcd zum Speichern seiner Daten verwendet. Im nächsten Schritt werden wir noch einige Aufgaben hinzufügen, um den etcd-Dienst neu zu starten und für ein Ausführen beim Systemstart zu sorgen.

      Schritt 6 — Aktivieren und Starten des etcd-Diensts

      Jedes Mal wenn wir Änderungen an der Unit-Datei eines Diensts vornehmen, müssen wir diesen Dienst neu starten, damit die Änderungen wirksam werden. Wir können dazu den Befehl systemctl restart etcd ausführen. Damit der etcd-Dienst beim Systemstart automatisch gestartet wird, müssen wir systemctl enable etcd ausführen. In diesem Schritt werden wir mit dem Playbook diese beiden Befehle ausführen.

      Um Befehle auszuführen, können wir das command-Modul verwenden:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie am Ende der Aufgabenliste die folgenden Aufgaben an:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Create configuration file for etcd"
            template:
              ...
              mode: 0600
          - name: "Enable the etcd service"
            command: systemctl enable etcd
          - name: "Start the etcd service"
            command: systemctl restart etcd
      

      Speichern und schließen Sie die Datei.

      Führen Sie ansible-playbook -i hosts playbook.yaml erneut aus:

      • ansible-playbook -i hosts playbook.yaml

      Um zu überprüfen, ob der Dienst etcd neu gestartet und aktiviert wurde, stellen Sie eine SSH-Verbindung zu einem der verwalteten Knoten her:

      Führen Sie dann systemctl status etcd aus, um den Status des etcd-Diensts zu überprüfen:

      Sie werden im Folgenden enabled und active (running) als hervorgehoben sehen; das bedeutet, dass die Änderungen, die wir in unserem Playbook vorgenommen haben, wirksam geworden sind:

      Output

      ● etcd.service - etcd distributed reliable key-value store Loaded: loaded (/etc/systemd/system/etcd.service; static; vendor preset: enabled) Active: active (running) Main PID: 19085 (etcd) Tasks: 11 (limit: 2362)

      In diesem Schritt haben wir das command-Modul zur Ausführung von systemctl-Befehlen verwendet, die den Dienst etcd neu starten und auf unseren verwalteten Knoten aktivieren. Nachdem wir eine etcd-Installation eingerichtet haben, testen wir nun im nächsten Schritt ihre Funktionalität durch Ausführung von CRUD-Operationen zum Erstellen, Lesen, Aktualisieren und Löschen.

      Schritt 7 — Testen von etcd

      Zwar verfügen wir über eine funktionierende etcd-Installation, doch ist diese unsicher und noch nicht bereit für den Produktionseinsatz. Bevor wir aber unsere etcd-Installation in späteren Schritten sichern, sollten wir zunächst wissen, was etcd für Funktionen bietet. In diesem Schritt werden wir manuell Anfragen an etcd senden, um Daten hinzuzufügen, abzurufen, zu aktualisieren und zu löschen.

      Standardmäßig macht etcd eine API verfügbar, die an Port 2379 auf Client-Kommunikation lauscht. Das bedeutet, dass wir mit einem HTTP-Client rohe API-Anfragen an etcd senden können. Es ist jedoch schneller, den offiziellen etcd-Client etcdctl zu verwenden. Damit können Sie Schlüsselwertpaare mit den Unterbefehlen put, get bzw. del erstellen/aktualisieren, abrufen und löschen.

      Stellen Sie sicher, dass Sie sich noch im verwalteten Knoten etcd1 befinden, und führen Sie die folgenden etcdctl-Befehle aus, um sich zu vergewissern, dass Ihre etcd-Installation richtig funktioniert.

      Erstellen Sie zunächst mit dem Unterbefehl put einen neuen Eintrag.

      Der Unterbefehl put weist die folgende Syntax auf:

      etcdctl put key value
      

      Führen Sie für etcd1 folgenden Befehl aus:

      Der Befehl, den wir gerade ausgeführt haben, weist etcd an, den Wert "bar" im Speicher in den Schlüssel foo zu schreiben.

      Dann werden Sie OK in der Ausgabe sehen, was angibt, dass die Daten persistiert wurden:

      Output

      OK

      Wir können diesen Eintrag dann mit dem Unterbefehl get abrufen, der die Syntax etcdctl get key hat:

      Sie werden diese Ausgabe finden, die den Schlüssel in der ersten Zeile und den Wert anzeigt, den Sie zuvor in der zweiten Zeile eingefügt haben:

      Output

      foo bar

      Wir können den Eintrag mit dem Unterbefehl del löschen, der die Syntax etcdctl del key hat:

      Sie werden die folgende Ausgabe sehen, die die Anzahl der gelöschten Einträge angibt:

      Output

      1

      Lassen Sie uns nun den Unterbefehl get erneut ausführen, um zu versuchen, ein gelöschtes Schlüssel-Wert-Paar abzurufen:

      Sie erhalten keine Ausgabe, was bedeutet, dass etcdctl das Schlüssel-Wert-Paar nicht abrufen kann. Dadurch wird bestätigt, dass der Eintrag nach dem Löschen nicht mehr abgerufen werden kann.

      Nachdem Sie die grundlegenden Operationen von etcd und etcdctl getestet haben, verlassen wir nun unseren verwalteten Knoten und kehren zurück zu der lokalen Umgebung:

      In diesem Schritt haben wir den etcdctl-Client zum Senden von Anfragen an etcd verwendet. An diesem Punkt führen wir drei separate Instanzen von etcd aus, die jeweils unabhängig voneinander agieren. Jedoch ist etcd als verteilter Schlüssel-Wert-Speicher konzipiert; das bedeutet, dass sich mehrere etcd-Instanzen gruppieren können, um einen einzelnen Cluster zu bilden; jede Instanz wird dann ein Member (Mitglied) des Clusters. Nach der Einrichtung eines Clusters könnten Sie ein Schlüssel-Wert-Paar abrufen, das von einem anderen Memberknoten des Clusters eingefügt wurde. Im nächsten Schritt werden wir unser Playbook verwenden, um unsere drei 1-Node-Cluster in einen einzigen 3-Node-Cluster zu verwandeln.

      Schritt 8 — Erstellen eines Clusters mit statischer Erkennung

      Um anstelle von drei 1-Node-Clustern einen 3-Node-Cluster zu erstellen, müssen wir die etcd-Installationen so konfigurieren, dass sie miteinander kommunizieren. Das bedeutet, dass alle die IP-Adressen der anderen kennen müssen. Dieser Prozess wird Erkennung genannt. Erkennung kann entweder mit statischer Konfiguration oder mit einer dynamischen Diensterkennung erfolgen. In diesem Schritt werden wir den Unterschied zwischen den beiden erörtern sowie unser Playbook aktualisieren, um einen etcd-Cluster mit statischer Erkennung einzurichten.

      Erkennung mit statischer Konfiguration ist die Methode, die die geringste Einrichtung erfordert; hier werden die Endpunkte der einzelnen Memberknoten in den Befehl etcd übergeben, bevor er ausgeführt wird. Um statische Konfiguration zu verwenden, müssen vor der Initialisierung des Clusters folgende Bedingungen erfüllt sein:

      • die Anzahl der Memberknoten ist bekannt
      • die Endpunkte der einzelnen Memberknoten sind bekannt
      • die IP-Adressen für alle Endpunkte sind statisch

      Wenn sich diese Bedingungen nicht erfüllen lassen, können Sie einen dynamischen Erkennungsdienst verwenden. Bei der dynamischen Diensterkennung würden sich alle Instanzen beim Erkennungsdienst registrieren, damit jeder Memberknoten Informationen über den Ort anderer Memberknoten abrufen kann.

      Da wir wissen, dass wir einen 3-Node-etcd-Cluster nutzen möchten und alle unsere Server über statische IP-Adressen verfügen, werden wir statische Erkennung verwenden. Um unseren Cluster mit statischer Erkennung zu initiieren, müssen wir unserer Konfigurationsdatei mehrere Parameter hinzufügen. Verwenden Sie einen Editor, um die Vorlagendatei templates/etcd.conf.yaml.j2 zu öffnen:

      • nano templates/etcd.conf.yaml.j2

      Fügen Sie dann die folgenden hervorgehobenen Zeilen hinzu:

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      name: {{ inventory_hostname }}
      initial-advertise-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
      listen-peer-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,http://127.0.0.1:2380
      advertise-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
      listen-client-urls: http://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,http://127.0.0.1:2379
      initial-cluster-state: new
      initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=http://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
      

      Schließen und speichern Sie die Datei templates/etcd.conf.yaml.j2, indem Sie Strg+X drücken, gefolgt von J.

      Hier ist eine kurze Erläuterung der einzelnen Parameter:

      • name – ein menschenlesbarer Name für den Memberknoten. Standardmäßig verwendet etcd eine eindeutige, zufällig generierte ID zur Identifizierung der einzelnen Memberknoten; ein menschlich lesbarer Name erleichtert jedoch in Konfigurationsdateien und in der Befehlszeile das Verweisen darauf. Hier werden wir die Hostnamen als Membernamen (d. h. etcd1, etcd2 und etcd3) verwenden.
      • initial-advertise-peer-urls – eine Liste mit IP-Adress-/Port-Kombinationen, die andere Memberknoten zur Kommunikation mit diesem Memberknoten verwenden können. Neben dem API-Port (2379) macht etcd auch Port 2380 für Peer-Kommunikation zwischen etcd-Memberknoten verfügbar, sodass sie Nachrichten aneinander senden und Daten austauschen können. Beachten Sie, dass diese URLs von ihren Peers erreichbar sein müssen (und keine lokalen IP-Adressen sein dürfen).
      • listen-peer-urls – eine Liste mit IP-Adress-/Port-Kombinationen, bei denen der aktuelle Memberknoten auf Kommunikation von anderen Memberknoten lauscht. Sie muss alle URLs aus dem Flag --initial-advertise-peer-urls enthalten, aber auch lokale URLs wie 127.0.0.1:2380. Die Ziel-IP-Adresse/der Port eingehender Peer-Nachrichten müssen mit einer der hier aufgeführten URLs übereinstimmen.
      • advertise-client-urls – eine Liste mit IP-Adress-/Port-Kombinationen, die Clients zur Kommunikation mit diesem Memberknoten verwenden sollen. Diese URLs müssen vom Client erreichbar sein (und dürfen keine lokalen Adressen sein). Wenn der Client über das öffentliche Internet auf den Cluster zugreift, muss dies eine öffentliche IP-Adresse sein.
      • listen-client-urls – eine Liste mit IP-Adress-/Port-Kombinationen, bei denen der aktuelle Memberknoten auf Kommunikation von Clients lauscht. Sie muss alle URLs aus dem Flag --advertise-client-urls enthalten, aber auch lokale URLs wie 127.0.0.1:2379. Die Ziel-IP-Adresse/der Port eingehender Client-Nachrichten müssen mit einer der hier aufgeführten URLs übereinstimmen.
      • initial-cluster – eine Liste mit Endpunkten für jeden Memberknoten des Clusters. Jeder Endpunkt muss mit einer der initial-advertise-peer-urls-URLs des entsprechenden Memberknotens übereinstimmen.
      • initial-cluster-state – entweder new oder existing.

      Zur Gewährleistung der Konsistenz kann etcd nur Entscheidungen treffen, wenn eine Mehrheit der Knoten integer ist. Dies wird als Einrichten eines Quorum bezeichnet. Mit anderen Worten,: In einem Cluster mit drei Memberknoten wird das Quorum erreicht, wenn zwei oder mehr der Mitglieder integer sind.

      Wenn der Parameter initial-cluster-state auf new gesetzt ist, weiß etcd, dass dies ein neuer Cluster ist, für den Bootstrapping ausgeführt wird; so können Memberknoten parallel gestartet werden, ohne dass auf das Erreichen des Quorums gewartet werden muss. Konkret: Nachdem das erste Mitglied gestartet wurde, wird kein Quorum erreicht, da ein Drittel (33,33 %) nicht mehr als 50 % ist. Normalerweise wird etcd anhalten und sich weigern, weitere Aktionen zu übergeben; der Cluster wird nie erstellt. Wenn der initial-cluster-state jedoch auf new gesetzt ist, wird das anfängliche Fehlen des Quorums ignoriert.

      Wenn der Wert auf existing gesetzt ist, wird der Memberknoten versuchen, einem vorhandenen Cluster beizutreten, und erwarten, dass das Quorum bereits eingerichtet wurde.

      Anmerkung: Weitere Details zu allen unterstützten Konfigurations-Flags finden Sie im Abschnitt Konfiguration der etcd-Dokumentation.

      In der aktualisierten Vorlagendatei templates/etcd.conf.yaml.j2 gibt es einige Instanzen von hostvars. Wenn Ansible ausgeführt wird, erfassen sie Variablen aus verschiedenen Quellen. Wir haben bereits die Variable inventory_hostname verwendet, aber es gibt noch viel mehr. Diese Variablen sind verfügbar unter hostvars[inventory_hostname]['ansible_facts']. Hier extrahieren wir die privaten IP-Adressen der einzelnen Knoten und nutzen Sie zur Erstellung unseres Parameterwerts.

      Anmerkung: Da wir bei der Erstellung unserer Server die Option Private Networking aktiviert haben, würde jeder Server über drei IP-Adressen verfügen, die mit ihm verknüpft sind:

      • Eine Loopback-IP-Adresse – eine Adresse, die nur im gleichen Rechner gültig ist. Sie wird vom Rechner verwendet, um sich auf sich zu verweisen, z. B. 127.0.0.1.
      • Eine öffentliche IP-Adresse – eine Adresse, die über das öffentliche Internet routingfähig ist, z. B. 178.128.169.51.
      • Eine private IP-Adresse – eine Adresse, die nur im privaten Netzwerk routingfähig ist; im Fall von DigitalOcean Droplets gibt es in jedem Rechenzentrum ein privates Netzwerk, z. B. 10.131.82.225.

      Jede dieser IP-Adressen ist mit einer anderen Netzwerkschnittstelle verbunden: Die Loopback-Adresse ist mit der lo-Schnittstelle verbunden, die öffentliche IP-Adresse mit der eth0-Schnittstelle und die private IP-Adresse mit der eth1-Schnittstelle. Wir verwenden die eth1-Schnittstelle, damit der gesamte Datenverkehr im privaten Netzwerk bleibt, ohne je das Internet zu erreichen.

      Für diesen Artikel ist kein Verständnis von Netzwerkschnittstellen erforderlich; wenn Sie jedoch mehr erfahren möchten, ist An Introduction to Networking Terminology, Interfaces, and Protocols( Eine Einführung in Netzwerkbegriffe, Schnittstellen und Protokolle) ein guter Ausgangspunkt.

      Die Jinja2-Syntax {% %} definiert die for-Schleifenstruktur, die über jeden Knoten in der Gruppe etcd iteriert, um die Zeichenfolge initial-cluster in einem Format zu erstellen, das etcd benötigt.

      Um den neuen Cluster mit drei Memberknoten zu erstellen, müssen Sie zunächst den etcd-Dienst stoppen und das Datenverzeichnis löschen, bevor Sie den Cluster starten. Verwenden Sie einen Editor, um die Datei playbook.yaml auf Ihrem lokalen Rechner zu öffnen:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie dann vor der Aufgabe „Create a Data directory“ eine Aufgabe hinzu, um den etcd-Dienst anzuhalten:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
              group: root
              mode: 0644
          - name: "Stop the etcd service"
            command: systemctl stop etcd
          - name: "Create a data directory"
            file:
          ...
      

      Aktualisieren Sie als Nächstes die Aufgabe "Create a Data directory", um das Datenverzeichnis zunächst zu löschen und dann neu zu erstellen:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        become: True
        tasks:
          ...
          - name: "Stop the etcd service"
            command: systemctl stop etcd
          - name: "Create a data directory"
            file:
              path: /var/lib/etcd/{{ inventory_hostname }}.etcd
              state: "{{ item }}"
              owner: root
              group: root
              mode: 0755
            with_items:
              - absent
              - directory
          - name: "Create directory for etcd configuration"
            file:
          ...
      

      Die Eigenschaft with_items definiert eine Liste von Zeichenfolgen, über die diese Aufgabe iterieren wird. Es ist genauso, als würden Sie die gleiche Aufgabe zweimal wiederholen, aber mit verschiedenen Werten für die Eigenschaft state. Hier iterieren wir über die Liste mit Elementen absent und directory, was gewährleistet, dass das Datenverzeichnis zunächst gelöscht und danach neu erstellt wird.

      Schließen und speichern Sie die Datei playbook.yaml, indem Sie Strg+X drücken, gefolgt von J. Führen Sie dann ansible-playbook erneut aus. Ansible erstellt nun einen einzelnen etcd-Cluster mit drei Memberknoten:

      • ansible-playbook -i hosts playbook.yaml

      Sie können dies überprüfen, indem Sie eine SSH-Verbindung zu einem beliebigen etcd-Memberknoten herstellen:

      Führen Sie dann etcdctl endpoint health --cluster aus:

      • etcdctl endpoint health --cluster

      Dadurch wird der Zustand der einzelnen Memberknoten im Cluster aufgelistet:

      Output

      http://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 2.517267ms http://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 2.153612ms http://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 2.639277ms

      Wir haben nun erfolgreich einen etcd-Cluster mit drei Knoten erstellt. Wir können dies prüfen, indem wir auf einem Memberknoten einen Eintrag zu etcd hinzufügen und diesen dann auf einem anderen Memberknoten abrufen. Führen Sie auf einem der Memberknoten etcdctl put aus:

      Verwenden Sie dann ein neues Terminal, um eine SSH-Verbindung zu einem anderen Memberknoten herzustellen:

      Versuchen Sie als Nächstes, mit folgendem Schlüssel den gleichen Eintrag abzurufen:

      Sie können den Eintrag abrufen, was beweist, dass der Cluster funktioniert:

      Output

      foo bar

      Verlassen Sie abschließend die einzelnen verwalteten Knoten und kehren Sie zurück zu Ihrem lokalen Rechner:

      In diesem Schritt haben wir einen neuen Cluster mit drei Knoten bereitgestellt. Derzeit erfolgt die Kommunikation zwischen etcd-Memberknoten sowie ihren Peers und Clients über HTTP. Das bedeutet, dass die Kommunikation unverschlüsselt ist und jede Person, die den Verkehr abfangen kann, auch die entsprechenden Nachrichten lesen kann. Dies ist kein großes Problem, wenn der etcd-Cluster und die Clients alle in einem privaten Netzwerk oder einem virtuellen privaten Netzwerk (VPN) bereitgestellt werden, das Sie vollständig kontrollieren. Wenn jedoch Teile des Datenverkehrs über ein freigegebenes Netzwerk (privat oder öffentlich) übertragen werden, sollten Sie sicherstellen, dass dieser Datenverkehr verschlüsselt ist. Außerdem muss für Clients oder Peers ein Mechanismus eingerichtet werden, der die Authentizität des Servers überprüft.

      Im nächsten Schritt werden wir uns ansehen, wie wir Client-to-Server- sowie Peer-Kommunikation mit TLS sichern können.

      Schritt 9 — Abrufen der privaten IP-Adressen von verwalteten Knoten

      Um Nachrichten zwischen Memberknoten zu verschlüsseln, verwendet etcd Hypertext Transfer Protocol Secure oder HTTPS, was eine Ebene über der Transport Layer Security oder dem TLS-Protokoll ist. TLS nutzt ein System aus privaten Schlüsseln, Zertifikaten und vertrauenswürdigen Entitäten namens Zertifizierungsstellen (CAs) zum Authentifizieren und gegenseitigen Senden verschlüsselter Nachrichten.

      In diesem Tutorial muss jeder Memberknoten ein Zertifikat generieren, um sich selbst zu identifizieren, und von einer Zertifizierungsstelle signieren lassen. Wir werden konfigurieren alle Memberknoten so, dass sie dieser Zertifizierungsstelle vertrauen und somit auch Zertifikaten vertrauen, die von ihr signiert wurden. Dadurch können sich Mitgliedsknoten gegenseitig authentifizieren.

      Das Zertifikat, das ein Memberknoten generiert, muss es anderen Memberknoten erlauben, sich selbst zu identifizieren. Alle Zertifikate umfassen den Common Name (CN) der Entität, mit der sie verknüpft sind. Dies wird oft als Identität der Entität verwendet. Bei der Prüfung eines Zertifikats können Clientimplementierungen jedoch vergleichen, ob die von ihnen erfassten Informationen über die Entität mit dem übereinstimmen, was im Zertifikat angegeben wurde. Wenn beispielsweise ein Client das TLS-Zertifikat mit dem Betreff CN=foo.bar.com herunterlädt, der Client in Wahrheit aber mit einer IP-Adresse (z. B. 167.71.129.110) verbunden ist, gibt es einen Konflikt und der Client vertraut dem Zertifikat ggf. nicht. Indem Sie im Zertifikat einen Subject Alternative Name (SAN) angeben, erfährt der Überprüfer, dass beide Namen zur gleichen Entität gehören.

      Da unsere etcd-Memberknoten über ihre privaten IP-Adressen Peering betreiben, müssen wir diese privaten IP-Adressen, wenn wir unsere Zertifikate definieren, als alternative Antragstellernamen (SANs) angeben.

      Um die private IP-Adresse eines verwalteten Knoten zu erfahren, stellen Sie eine SSH-Verbindung damit her:

      Führen Sie dann den folgenden Befehl aus:

      • ip -f inet addr show eth1

      Sie sehen eine Ausgabe, die den folgenden Zeilen ähnelt:

      Output

      3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 inet 10.131.255.176/16 brd 10.131.255.255 scope global eth1 valid_lft forever preferred_lft forever

      In unserer Beispielausgabe ist 10.131.255.176 die private IP-Adresse des verwalteten Knotens und die einzige Information, an der wir interessiert sind. Um alles mit Ausnahme der privaten IP-Adresse herauszufiltern, können wir die Ausgabe des Befehls ip an das Dienstprogramm sed übergeben, das zum Filtern und Umformen von Text dient.

      • ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'

      Die einzige Ausgabe ist nun die private IP-Adresse selbst:

      Output

      10.131.255.176

      Sobald Sie damit zufrieden sind, dass der vorhergehende Befehl funktioniert, verlassen Sie den verwalteten Knoten:

      Um die vorherigen Befehle in Ihr Playbook aufzunehmen, öffnen Sie zunächst die Datei playbook.yaml:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie dann vor dem vorhandenen Play ein neues Play mit einer einzelnen Aufgabe hinzu:

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: etcd
        tasks:
          - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'
            register: privateIP
      - hosts: etcd
        tasks:
      ...
      

      Die Aufgabe verwendet das shell-Modul, um die Befehle ip und sed auszuführen, wodurch die private IP-Adresse des verwalteten Knoten abgerufen wird. Dann registriert sie den Rückgabewert des Shell-Befehls in einer Variable namens privateIP, die wir später verwenden werden.

      In diesem Schritt haben wir dem Playbook eine Aufgabe hinzugefügt, um die private IP-Adresse der verwalteten Knoten zu erhalten. Im nächsten Schritt werden wir diese Informationen verwenden, um für jeden der Knoten ein Zertifikat zu generieren und die Zertifikate von einer Zertifizierungsstelle (CA) signieren zu lassen.

      Schritt 10 — Erstellen der privaten Schlüssel und CSRs von etcd-Memberknoten

      Damit ein Memberknoten verschlüsselten Datenverkehr erhält, muss der Absender den öffentlichen Schlüssel des Memberknotens verwenden, um die Daten zu verschlüsseln. Der Memberknoten muss den privaten Schlüssel nutzen, um den verschlüsselten Text zu entschlüsseln und die Originaldaten abzurufen. Der öffentliche Schlüssel ist in einem Zertifikat verpackt und wurde von einer CA signiert, um sicherzustellen, dass er echt ist.

      Daher müssen wir für jeden etcd-Memberknoten einen privaten Schlüssel und eine Zertifikatsignaturanforderung (CSR) generieren. Um es einfacher zu machen, erstellen wir alle Schlüsselpaare und signieren alle Zertifikate lokal auf dem Steuerknoten und kopieren die entsprechenden Dateien dann auf die verwalteten Hosts.

      Erstellen Sie zunächst ein Verzeichnis namens artifacts/, in dem Sie die in dem Prozess generierten Dateien (Schlüssel und Zertifikate) platzieren werden. Öffnen Sie die Datei playbook.yaml mit einem Editor:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Verwenden Sie darin das file-Modul, um das Verzeichnis artifacts/ zu erstellen:

      ~/playground/etcd-ansible/playbook.yaml

      ...
          - shell: ip -f inet addr show eth1 | sed -En -e 's/.*inet ([0-9.]+).*/1/p'
            register: privateIP
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
          - name: "Create ./artifacts directory to house keys and certificates"
            file:
              path: ./artifacts
              state: directory
      - hosts: etcd
        tasks:
      ...
      

      Fügen Sie als Nächstes am Ende des Plays eine weitere Aufgabe hinzu, um den privaten Schlüssel zu generieren:

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
              ...
          - name: "Generate private key for each member"
            openssl_privatekey:
              path: ./artifacts/{{item}}.key
              type: RSA
              size: 4096
              state: present
              force: True
            with_items: "{{ groups['etcd'] }}"
      - hosts: etcd
        tasks:
      ...
      

      Das Erstellen von privaten Schlüsseln und CSRs kann mit den Modulen openssl_privatekey bzw. openssl_csr erfolgen.

      Das Attribut force: True stellt sicher, dass der private Schlüssel jedes Mal neu generiert wird, auch wenn er bereits existiert.

      Fügen Sie nun die folgende neue Aufgabe demselben Play an, um die CSRs für die einzelnen Memberknoten zu generieren; nutzen Sie dazu das Modul openssl_csr:

      ~/playground/etcd-ansible/playbook.yaml

      ...
      - hosts: localhost
        gather_facts: False
        become: False
        tasks:
          ...
          - name: "Generate private key for each member"
            openssl_privatekey:
              ...
            with_items: "{{ groups['etcd'] }}"
          - name: "Generate CSR for each member"
            openssl_csr:
              path: ./artifacts/{{item}}.csr
              privatekey_path: ./artifacts/{{item}}.key
              common_name: "{{item}}"
              key_usage:
                - digitalSignature
              extended_key_usage:
                - serverAuth
              subject_alt_name:
                - IP:{{ hostvars[item]['privateIP']['stdout']}}
                - IP:127.0.0.1
              force: True
            with_items: "{{ groups['etcd'] }}"
      

      Wir geben an, dass sich dieses Zertifikat für den Zweck der Serverauthentifizierung an einem digitalen Signaturmechanismus beteiligen kann. Das Zertifikat ist mit dem Hostnamen (z. B. etcd1) verknüpft; der Überprüfer soll jedoch auch die privaten und lokalen Loopback-IP-Adressen der einzelnen Knoten als alternative Namen behandeln. Beachten Sie, dass wir die Variable privateIP verwenden, die wir im vorherigen Play registriert haben.

      Schließen und speichern Sie die Datei playbook.yaml, indem Sie Strg+X drücken, gefolgt von J. Führen Sie das Playbook dann erneut aus:

      • ansible-playbook -i hosts playbook.yaml

      Wir werden in unserem Projektverzeichnis nun ein neues Verzeichnis namens Artefakte sehen; verwenden Sie ls, um den Inhalt aufzulisten:

      Sie sehen die privaten Schlüssel und CSRs für die einzelnen etcd-Memberknoten:

      Output

      etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key

      In diesem Schritt haben wir verschiedene Ansible-Module verwendet, um für die einzelnen Memberknoten private Schlüssel und öffentliche Schlüsselzertifikate zu generieren. Im nächsten Schritt werden wir uns ansehen, wie eine Zertifikatsignaturanforderung (CSR) signiert wird.

      Schritt 11 — Erstellen von CA-Zertifikaten

      Innerhalb eines etcd-Clusters verschlüsseln Memberknoten Nachrichten mit dem öffentlichen Schlüssel des Empfängers. Um sicherzustellen, dass der öffentliche Schlüssel echt ist, verpackt der Empfänger den öffentlichen Schlüssel in eine Zertifikatsignaturanforderung (CSR) und lässt diese von einer vertrauenswürdigen Entität (d. h. der CA) signieren. Da wir alle Memberknoten und die Zertifizierungstellen, denen sie vertrauen, kontrollieren, müssen wir keine externe CA nutzen und können als eigene CA fungieren. In diesem Schritt werden wir als eigene CA agieren; das bedeutet, dass wir einen privaten Schlüssel und ein selbstsigniertes Zertifikat erstellen müssen, um als CA zu fungieren.

      Öffnen Sie die Datei playbook.yaml mit Ihrem Editor:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie dann ähnlich wie im vorherigen Schritt eine Aufgabe im Play localhost an, um einen privaten Schlüssel für die CA zu generieren:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
        - name: "Generate CSR for each member"
          ...
          with_items: "{{ groups['etcd'] }}"
          - name: "Generate private key for CA"
            openssl_privatekey:
              path: ./artifacts/ca.key
              type: RSA
              size: 4096
              state: present
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Als Nächstes verwenden Sie das Modul openssl_csr, um eine neue CSR zu generieren. Dies ähnelt dem vorherigen Schritt; in dieser CSR fügen wir jedoch die Basiseinschränkung und Schlüsselverwendungserweiterung hinzu, um anzugeben, dass dieses Zertifikat als CA-Zertifikat verwendet werden kann:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate private key for CA"
            openssl_privatekey:
              path: ./artifacts/ca.key
              type: RSA
              size: 4096
              state: present
              force: True
          - name: "Generate CSR for CA"
            openssl_csr:
              path: ./artifacts/ca.csr
              privatekey_path: ./artifacts/ca.key
              common_name: ca
              organization_name: "Etcd CA"
              basic_constraints:
                - CA:TRUE
                - pathlen:1
              basic_constraints_critical: True
              key_usage:
                - keyCertSign
                - digitalSignature
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Verwenden Sie schließlich das Modul openssl_certificate, um das CSR selbst zu signieren:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate CSR for CA"
            openssl_csr:
              path: ./artifacts/ca.csr
              privatekey_path: ./artifacts/ca.key
              common_name: ca
              organization_name: "Etcd CA"
              basic_constraints:
                - CA:TRUE
                - pathlen:1
              basic_constraints_critical: True
              key_usage:
                - keyCertSign
                - digitalSignature
              force: True
          - name: "Generate self-signed CA certificate"
            openssl_certificate:
              path: ./artifacts/ca.crt
              privatekey_path: ./artifacts/ca.key
              csr_path: ./artifacts/ca.csr
              provider: selfsigned
              force: True
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Schließen und speichern Sie die Datei playbook.yaml, indem Sie Strg+X drücken, gefolgt von J. Führen Sie dann das Playbook erneut aus, um die Änderungen anzuwenden:

      • ansible-playbook -i hosts playbook.yaml

      Außerdem können Sie ls ausführen, um den Inhalt des Verzeichnisses artifacts/ zu überprüfen:

      Sie sehen nun das neu generierte CA-Zertifikat (ca.crt):

      Output

      ca.crt ca.csr ca.key etcd1.csr etcd1.key etcd2.csr etcd2.key etcd3.csr etcd3.key

      In diesem Schritt haben wir einen privaten Schlüssel und ein selbstsigniertes Zertifikat für die CA generiert. Im nächsten Schritt verwenden wir das CA-Zertifikat nutzen, um die CSRs der einzelnen Memberknoten zu signieren.

      Schritt 12 — Signieren der CSRs von etcd-Memberknoten

      In diesem Schritt signieren wir die CSRs der einzelnen Memberknoten. Dies ähnelt der Methode, mit der wir das Modul openssl_certificate verwendet haben, um das CA-Zertifikat selbst zu signieren. Aber anstelle des Anbieters selfsigned nutzen wir den Anbieter ownca, damit wir unsere eigenen CA-Zertifikate signieren können.

      Öffnen Sie Ihr Playbook:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Fügen Sie der Aufgabe "Generate self-signed CA certificate" die folgende hervorgehobene Aufgabe an:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: localhost
        ...
        tasks:
          ...
          - name: "Generate self-signed CA certificate"
            openssl_certificate:
              path: ./artifacts/ca.crt
              privatekey_path: ./artifacts/ca.key
              csr_path: ./artifacts/ca.csr
              provider: selfsigned
              force: True
          - name: "Generate an `etcd` member certificate signed with our own CA certificate"
            openssl_certificate:
              path: ./artifacts/{{item}}.crt
              csr_path: ./artifacts/{{item}}.csr
              ownca_path: ./artifacts/ca.crt
              ownca_privatekey_path: ./artifacts/ca.key
              provider: ownca
              force: True
            with_items: "{{ groups['etcd'] }}"
      - hosts: etcd
        become: True
        tasks:
          - name: "Create directory for etcd binaries"
      ...
      

      Schließen und speichern Sie die Datei playbook.yaml, indem Sie Strg+X drücken, gefolgt von “Y. Führen Sie dann das Playbook erneut aus, um die Änderungen anzuwenden:

      • ansible-playbook -i hosts playbook.yaml

      Listen Sie nun den Inhalt des Verzeichnisses artifacts/ auf:

      Sie finden den privaten Schlüssel, die CSR und das Zertifikat für jeden einzelnen etcd-Memberknoten und die CA:

      Output

      ca.crt ca.csr ca.key etcd1.crt etcd1.csr etcd1.key etcd2.crt etcd2.csr etcd2.key etcd3.crt etcd3.csr etcd3.key

      In diesem Schritt haben wir die CSRs der einzelnen Memberknoten mithilfe des Schlüssels der CA signiert. Im nächsten Schritt kopieren wir die relevanten Dateien in die einzelnen verwalteten Knoten, damit etcd zum Einrichten von TLS-Verbindungen Zugriff auf die entsprechenden Schlüssel und Zertifikate hat.

      Schritt 13 — Kopieren von privaten Schlüsseln und Zertifikaten

      Jeder Knoten muss eine Kopie des selbstsignierten Zertifikats der CA (ca.crt) haben. Jeder etcd-Memberknoten muss auch über einen eigenen privaten Schlüssel und ein Zertifikat verfügen. In diesem Schritt laden wir die Dateien hoch und platzieren sie in einem neuen Verzeichnis namens /etc/etcd/ssl/.

      Öffnen Sie zunächst die Datei playbook.yaml mit Ihrem Editor:

      • nano $HOME/playground/etcd-ansible/playbook.yaml

      Um diese Änderungen an unserem Ansible-Playbook vorzunehmen, aktualisieren Sie zunächst die Eigenschaft path der Aufgabe Create directory for etcd configuration, um das Verzeichnis /etc/etcd/ssl/ zu erstellen:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        ...
        tasks:
          ...
            with_items:
              - absent
              - directory
          - name: "Create directory for etcd configuration"
            file:
              path: "{{ item }}"
              state: directory
              owner: root
              group: root
              mode: 0755
            with_items:
              - /etc/etcd
              - /etc/etcd/ssl
          - name: "Create configuration file for etcd"
            template:
      ...
      

      Fügen Sie dann nach der modifizierten Aufgabe drei weitere Aufgaben hinzu, um die Dateien zu kopieren:

      ~/playground/etcd-ansible/playbook.yaml

      - hosts: etcd
        ...
        tasks:
          ...
          - name: "Copy over the CA certificate"
            copy:
              src: ./artifacts/ca.crt
              remote_src: False
              dest: /etc/etcd/ssl/ca.crt
              owner: root
              group: root
              mode: 0644
          - name: "Copy over the `etcd` member certificate"
            copy:
              src: ./artifacts/{{inventory_hostname}}.crt
              remote_src: False
              dest: /etc/etcd/ssl/server.crt
              owner: root
              group: root
              mode: 0644
          - name: "Copy over the `etcd` member key"
            copy:
              src: ./artifacts/{{inventory_hostname}}.key
              remote_src: False
              dest: /etc/etcd/ssl/server.key
              owner: root
              group: root
              mode: 0600
          - name: "Create configuration file for etcd"
            template:
      ...
      

      Schließen und speichern Sie die Datei playbook.yaml, indem Sie Strg+X drücken, gefolgt von J.

      Führen Sie ansible-playbook erneut aus, um diese Änderungen vorzunehmen:

      • ansible-playbook -i hosts playbook.yaml

      In diesem Schritt haben wir die privaten Schlüssel und Zertifikate erfolgreich in die verwalteten Knoten hochgeladen. Nachdem wir die Dateien kopiert haben, müssen wir nun unsere etcd-Konfigurationsdatei so aktualisieren, dass sie sie nutzt.

      Schritt 14 — Aktivieren von TLS in etcd

      Im letzten Schritt dieses Tutorials werden wir einige Ansible-Konfigurationen aktualisieren, um TLS in einem etcd-Cluster zu aktivieren.

      Öffnen Sie zunächst die Vorlagendatei templates/etcd.conf.yaml.j2 mit Ihrem Editor:

      • nano $HOME/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      Ändern Sie darin alle URLs so, dass sie https als Protokoll anstelle von http verwenden. Fügen Sie außerdem am Ende der Vorlage einen Abschnitt hinzu, um den Speicherort des CA-Zertifikats, des Serverzertifikats und des Serverschlüssels anzugeben:

      ~/playground/etcd-ansible/templates/etcd.conf.yaml.j2

      data-dir: /var/lib/etcd/{{ inventory_hostname }}.etcd
      name: {{ inventory_hostname }}
      initial-advertise-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380
      listen-peer-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2380,https://127.0.0.1:2380
      advertise-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379
      listen-client-urls: https://{{ hostvars[inventory_hostname]['ansible_facts']['eth1']['ipv4']['address'] }}:2379,https://127.0.0.1:2379
      initial-cluster-state: new
      initial-cluster: {% for host in groups['etcd'] %}{{ hostvars[host]['ansible_facts']['hostname'] }}=https://{{ hostvars[host]['ansible_facts']['eth1']['ipv4']['address'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}
      
      client-transport-security:
        cert-file: /etc/etcd/ssl/server.crt
        key-file: /etc/etcd/ssl/server.key
        trusted-ca-file: /etc/etcd/ssl/ca.crt
      peer-transport-security:
        cert-file: /etc/etcd/ssl/server.crt
        key-file: /etc/etcd/ssl/server.key
        trusted-ca-file: /etc/etcd/ssl/ca.crt
      

      Schließen und speichern Sie die Datei templates/etcd.conf.yaml.j2.

      Führen Sie als Nächstes Ihr Ansible-Playbook aus:

      • ansible-playbook -i hosts playbook.yaml

      Stellen Sie dann eine SSH-Verbindung zu einem der verwalteten Knoten her:

      Führen Sie darauf den Befehl etcdctl endpoint health aus, um zu überprüfen, ob die Endpunkte HTTPS verwenden und ob alle Memberknoten integer sind:

      • etcdctl --cacert /etc/etcd/ssl/ca.crt endpoint health --cluster

      Da unser CA-Zertifikat standardmäßig kein vertrauenswürdiges CA-Stammzertifikat ist, das im Verzeichnis /etc/ssl/certs/ installiert ist, müssen wir es mit dem Flag --cacert an etcdctl übergeben.

      Dadurch erhalten Sie folgende Ausgabe:

      Output

      https://etcd3_private_ip:2379 is healthy: successfully committed proposal: took = 19.237262ms https://etcd1_private_ip:2379 is healthy: successfully committed proposal: took = 4.769088ms https://etcd2_private_ip:2379 is healthy: successfully committed proposal: took = 5.953599ms

      Um zu bestätigen, dass der etcd-Cluster tatsächlich funktioniert, können wir erneut einen Eintrag auf einem etcd-Memberknoten erstellen und ihn dann von einem anderen etcd-Memberknoten abrufen:

      • etcdctl --cacert /etc/etcd/ssl/ca.crt put foo "bar"

      Verwenden Sie ein neues Terminal, um eine SSH-Verbindung zu einem anderen Knoten herzustellen:

      Rufen Sie nun mit dem Schlüssel foo den gleichen Eintrag ab:

      • etcdctl --cacert /etc/etcd/ssl/ca.crt get foo

      Dadurch wird der Eintrag zurückgegeben, der die folgende Ausgabe anzeigt:

      Output

      foo bar

      Sie können das Gleiche mit dem dritten Knoten tun, um zu prüfen, ob alle drei Memberknoten ausgeführt werden.

      Zusammenfassung

      Sie haben nun erfolgreich einen etcd-Cluster mit drei Knoten bereitgestellt, mit TLS gesichert und sich vergewissert, dass er funktioniert.

      etcd ist eine ursprünglich von CoreOS entwickelte Software. Um die Verwendung von etcd in Bezug auf CoreOS zu verstehen, können Sie Folgendes lesen: How To Use Etcdctl and Etcd, CoreOS’s Distributed Key-Value Store. Der Artikel führt Sie außerdem durch die Einrichtung eines dynamischen Erfassungsmodells, das in diesem Tutorial diskutiert, aber nicht vorgeführt wurde.

      Wie am Anfang dieses Tutorials erwähnt, ist etcd ein wichtiger Teil des Kubernetes-Ökosystems. Um mehr über Kubernetes und die Rolle von etcd darin zu erfahren, können Sie An Introduction to Kubernetes (Eine Einführung in Kubernetes) lesen. Wenn Sie etcd als Teil eines Kubernetes-Clusters bereitstellen, sollten Sie wissen, dass es andere Tools gibt, wie z. B. kubespray und kubeadm. Weitere Details dazu finden Sie unter Erstellen eines Kubernetes-Clusters unter Ubuntu 18.04.

      Schließlich wurden in diesem Tutorial auch viele Tools verwendet, die einzeln nicht genau besprochen werden konnten. Im Folgenden finden Sie Links, die Ihnen genauere Informationen zu den einzelnen Tools liefern:



      Source link

      Skalieren und Sichern einer Django-Anwendung mit Docker, Nginx und Let’s Encrypt


      Einführung

      In cloudbasierten Umgebungen gibt es mehrere Möglichkeiten, eine Django-Anwendung zu skalieren und zu sichern. Durch horizontale Skalierung und die Ausführung mehrerer Kopien Ihrer Anwendung können Sie ein fehlertolerantes und hochverfügbares System aufbauen und gleichzeitig den Durchsatz erhöhen, sodass Anfragen gleichzeitig verarbeitet werden können. Eine Möglichkeit zur horizontalen Skalierung einer Django-Anwendung besteht darin, zusätzliche App-Server bereitzustellen, die Ihre Django-Anwendung und Ihren WSGI-HTTP-Server (wie Gunicorn oder uWSGI) ausführen. Um eingehende Anfragen über diesen Satz von App-Servern zu leiten und zu verteilen, können Sie einen Load Balancer und Reverse Proxy wie Nginx verwenden. Nginx kann auch statische Inhalte zwischenspeichern und Transport Layer Security-Verbindungen (TLS-Verbindungen) beenden, die zur Bereitstellung von HTTPS- und sicheren Verbindungen zu Ihrer Anwendung verwendet werden.

      Die Ausführung Ihrer Django-Anwendung und des Nginx-Proxys innerhalb von Docker-Containern stellt sicher, dass sich diese Komponenten unabhängig von der Umgebung, in der sie bereitgestellt werden, gleich verhalten. Darüber hinaus bieten Container viele Funktionen, die das Paketieren und Konfigurieren Ihrer Anwendung erleichtern.

      In diesem Tutorial skalieren Sie eine containerisierte Django- und Gunicorn-Umfrageanwendung horizontal, indem Sie zwei App-Server bereitstellen, die jeweils eine Kopie eines Django- und Gunicorn-Anwendungscontainers ausführen.

      Des Weiteren aktivieren Sie HTTPS, 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 stellt TLS-Zertifikate für Nginx von der Let’s Encrypt Zertifizierungsstelle bereit. Dadurch wird sichergestellt, dass Ihre Website von SSL Labs eine hohe Sicherheitsbewertung erhält. Dieser Proxy-Server empfängt alle externen Anfragen Ihrer Anwendung und sitzt vor den beiden upstream Django-App-Servern. Schließlich härten Sie dieses verteilte System ab, indem Sie den externen Zugriff auf nur den Proxy-Server beschränken.

      Voraussetzungen

      Um dieser Anleitung zu folgen, benötigen Sie:

      • Drei Ubuntu 18.04-Server:

        • Zwei Server werden App-Server sein, die zum Ausführen Ihrer Django- und Gunicorn-Anwendung verwendet werden.
        • Ein Server wird ein Proxy-Server sein, auf dem Nginx und Certbot ausgeführt werden.
        • Alle Server sollten einen Nicht-Root-Benutzer mit sudo-Berechtigungen und eine aktive Firewall haben. Eine Anleitung für das Setup finden Sie im Leitfaden für die Ersteinrichtung des Servers.
      • Auf allen drei Servern installiertes Docker. Eine Anleitung zur Installation von Docker finden Sie in den Schritten 1 und 2 von Installieren und Verwenden von Docker unter Ubuntu 18.04.

      • Einen registrierten Domänennamen. In diesem Tutorial wird durchgängig your_domain.com verwendet. Einen Domänennamen können Sie kostenlos bei Freenom erhalten oder Sie nutzen eine Domänenregistrierungsstelle Ihrer Wahl.

      • Einen DNS-A-Eintrag mit your-domain.com, der auf die öffentliche IP-Adresse Ihres Proxy-Servers verweist. Falls Sie ein DigitalOcean-Konto nutzen, können Sie in dieser Einführung in DigitalOcean-DNS im Einzelnen nachlesen, wie Sie ihn hinzufügen.

      • Einen S3-Objektspeicher-Bucket wie beispielsweise einen DigitalOcean Space zur Speicherung der statischen Dateien Ihres Django-Projekts und einen Satz von Zugriffsschlüsseln für diesen Space. Um zu erfahren, wie Sie einen Space erstellen können, lesen Sie die Produktdokumentation Erstellen von Spaces. Um zu erfahren, wie Sie Zugriffsschlüssel für Spaces erstellen können, lesen Sie Zugriff auf Spaces mit Zugriffsschlüsseln gemeinsam nutzen. Mit geringfügigen Änderungen können Sie jeden Objektspeicherdienst verwenden, der das Plugin django-storages verwendet.

      • Eine PostgreSQL-Server-Instanz, Datenbank und Benutzer für Ihre Django-Anwendung. Mit geringfügigen Änderungen können Sie jede Datenbank verwenden, die Django unterstützt.

      Schritt 1 – Konfigurieren des ersten Django-App-Servers

      Wir klonen zunächst das Django-Anwendungs-Repository auf den ersten App-Server. Dann konfigurieren und erstellen wir das Docker-Image und testen die Anwendung durch Ausführung des Django-Containers.

      Anmerkung: Wenn Sie von Erstellen einer Django- und Gunicorn-Anwendung mit Docker fortfahren, haben Sie Schritt 1 bereits abgeschlossen und können mit Schritt 2 fortfahren, um den zweiten App-Server zu konfigurieren.

      Beginnen Sie mit der Anmeldung beim ersten der beiden Django-App-Server und verwenden Sie git, um den Zweig polls-docker der Django Tutorial Umfrageanwendung GitHub-Repository zu klonen. Dieses Repository enthält Code für die Umfrage-Beispielanwendung der Django-Dokumentation. Der Zweig polls-docker enthält eine dockerisierte Version der Umfrageanwendung. Um zu erfahren, wie die Umfrageanwendung modifiziert wurde, um effektiv in einer containerisierten Umgebung zu arbeiten, lesen Sie bitte Erstellen einer Django- und Gunicorn-Anwendung mit Docker.

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Navigieren Sie in das Verzeichnis django-polls:

      cd django-polls
      

      Dieses Verzeichnis enthält den Python-Code der Django-Anwendung, ein Dockerfile, das Docker zum Erstellen des Container-Images verwendet, sowie eine Datei env, die eine Liste von Umgebungsvariablen enthält, die an die laufende Umgebung des Containers übergeben werden müssen. Prüfen Sie das Dockerfile mit cat:

      cat Dockerfile
      

      Output

      FROM python:3.7.4-alpine3.10 ADD django-polls/requirements.txt /app/requirements.txt RUN set -ex && apk add --no-cache --virtual .build-deps postgresql-dev build-base && python -m venv /env && /env/bin/pip install --upgrade pip && /env/bin/pip install --no-cache-dir -r /app/requirements.txt && runDeps="$(scanelf --needed --nobanner --recursive /env | awk '{ gsub(/,/, "nso:", $2); print "so:" $2 }' | sort -u | xargs -r apk info --installed | sort -u)" && apk add --virtual rundeps $runDeps && apk del .build-deps ADD django-polls /app WORKDIR /app ENV VIRTUAL_ENV /env ENV PATH /env/bin:$PATH EXPOSE 8000 CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

      Dieses Dockerfile verwendet das offizielle Python 3.7.4 Docker-Image als Basis und installiert die Python-Paketanforderungen von Django und Gunicorn, wie sie in der Datei django-polls/requirements.txt definiert sind. Anschließend entfernt es einige unnötige Builddateien, kopiert den Anwendungscode in das Image und legt den Ausführungspfad PATH fest. Schließlich gibt es an, dass Port 8000 verwendet wird, um eingehende Container-Verbindungen zu akzeptieren und gunicorn mit 3 Workern ausgeführt wird, die Port 8000 abhören.

      Um mehr über die einzelnen Schritte in diesem Dockerfile zu erfahren, lesen Sie bitte Schritt 6 von Erstellen einer Django- und Gunicorn-Anwendung mit Docker.

      Erstellen Sie nun das Image mit docker build:

      Wir benennen das Image polls mit dem Flag -t und übergeben im aktuellen Verzeichnis als Build-Kontext den Satz von Daten, auf den beim Erstellen des Images verwiesen werden soll.

      Nachdem Docker das Image erstellt und mit Tags versehen hat, listen wir die verfügbaren Images mit docker images auf:

      docker images
      

      Sie sollten die polls-Images aufgelistet sehen:

      Output

      REPOSITORY TAG IMAGE ID CREATED SIZE polls latest 80ec4f33aae1 2 weeks ago 197MB python 3.7.4-alpine3.10 f309434dea3a 8 months ago 98.7MB

      Bevor wir den Django-Container ausführen, müssen wir seine Betriebsumgebung mithilfe der im aktuellen Verzeichnis vorhandenen Datei env konfigurieren. Diese Datei wird an den Befehl docker run übergeben, der zum Ausführen des Containers verwendet wird, und Docker injiziert die konfigurierten Umgebungsvariablen in die Betriebsumgebung des Containers.

      Öffnen Sie die Datei env mit nano oder Ihrem bevorzugten Editor:

      nano env
      

      Wir werden die Datei wie nachfolgend beschrieben konfigurieren und Sie müssen einige zusätzliche Werte hinzufügen.

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Geben Sie die fehlenden Werte für die folgenden Schlüssel ein:

      • DJANGO_SECRET_KEY: Setzen Sie diesen auf einen eindeutigen, nicht vorhersagbaren Wert, wie in den Django-Dokumentationen beschrieben. Eine Methode zur Generierung dieses Wertes wird in Anpassen der Anwendungseinstellungen in dem Tutorial Skalierbare Django-Anwendung angeboten.
      • DJANGO_ALLOWED_HOSTS: Diese Variable sichert die Anwendung und verhindert HTTP-Host-Header-Angriffe. Setzen Sie diese Variable für Testzwecke auf *, einen Platzhalter, der auf alle Hosts zutrifft. In der Produktion sollten Sie diese Variable auf your_domain.com setzen. Um mehr über diese Django-Einstellungen zu erfahren, konsultieren Sie die Core-Einstellungen der Django-Dokumentation.
      • DATABASE_USERNAME: Setzen Sie diesen auf den in den vorbereitenden Schritten erstellten PostgreSQL Datenbankbenutzer.
      • DATABASE_NAME: Setzen Sie diesen auf polls oder den in den vorbereitenden Schritten erstellten Namen der PostgreSQL-Datenbank.
      • DATABASE_PASSWORD: Setzen Sie dieses auf das in den vorbereitenden Schritten erstellte Passwort für den PostgreSQL Benutzer.
      • DATABASE_HOST: Setzen Sie diesen Wert auf den Hostnamen Ihrer Datenbank.
      • DATABASE_PORT: Setzen Sie diesen Wert auf den Port Ihrer Datenbank.
      • STATIC_ACCESS_KEY_ID: Setzen Sie diesen Wert auf den Zugriffsschlüssel Ihres S3-Buckets oder Space.
      • STATIC_SECRET_KEY: Setzen Sie diesen Wert auf das Zugriffsschlüsselgeheimnis Ihres S3-Bucket oder Space
      • STATIC_BUCKET_NAME: Setzen Sie diesen auf Ihren S3-Bucket- oder Space-Namen.
      • STATIC_ENDPOINT_URL: Setzen Sie diese auf die entsprechenden S3-Bucket- oder Space-Endpunkt-URL, z.B. https://space-name.nyc3.digitaloceanspaces.com, wenn sich Ihr Space in der Region nyc3 befindet.

      Wenn Sie die Bearbeitung abgeschlossen haben, speichern und schließen Sie die Datei.

      Wir verwenden nun docker run, um den CMD-Satz in dem Dockerfile zu überschreiben und das Datenbankschema mit den Befehlen manage.py makemigrations und manage.py migrate zu erstellen:

      docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"
      

      Wir führen das Container-Image polls:latest aus, übergeben die von uns gerade modifizierte Umgebungsvariablendatei und überschreiben den Dockerfile-Befehl mit sh -c "python manage.py makemigrations && python manage.py migrate", wodurch das durch den Anwendungscode definierte Datenbankschema erstellt wird. Wenn Sie dies zum ersten Mal ausführen, sollten Sie Folgendes sehen:

      Output

      No changes detected Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying polls.0001_initial... OK Applying sessions.0001_initial... OK

      Dies zeigt an, dass das Datenbankschema erfolgreich erstellt wurde.

      Wenn Sie die Migration zu einem späteren Zeitpunkt ausführen, führt Django eine Nulloperation durch, es sei denn, das Datenbankschema wurde geändert.

      Als Nächstes führen wir eine weitere Instanz des Anwendungscontainers aus und verwenden darin eine interaktive Shell, um einen Administratorbenutzer für das Django-Projekt zu erstellen.

      docker run -i -t --env-file env polls sh
      

      Dadurch erhalten Sie eine Shell-Eingabeaufforderung innerhalb des laufenden Containers, die Sie zum Erstellen des Django-Benutzers verwenden können:

      python manage.py createsuperuser
      

      Geben Sie einen Benutzernamen, eine E-Mail-Adresse und ein Passwort für Ihren Benutzer ein. Drücken Sie nach dem Erstellen des Benutzers STRG+D, um den Container zu verlassen und zu beenden.

      Schließlich generieren wir die statischen Dateien für die Anwendung und laden sie mit collectstatic in den DigitalOcean Space hoch. Beachten Sie, dass dies möglicherweise einige Zeit dauern kann.

      docker run --env-file env polls sh -c "python manage.py collectstatic --noinput"
      

      Nachdem diese Dateien generiert und hochgeladen sind, erhalten Sie folgende Ausgabe.

      Output

      121 static files copied.

      Wir können die Anwendung nun ausführen:

      docker run --env-file env -p 80:8000 polls
      

      Output

      [2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0 [2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1) [2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync [2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7 [2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8 [2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

      Hier führen wir den in dem Dockerfile definierten Standardbefehl gunicorn ---bind :8000 --workers 3 mysite.wsgi:application aus und stellen den Container-Port 8000 frei, sodass Port 80 auf dem Ubuntu-Server dem Port 8000 des Containers poll zugeordnet wird.

      Sie sollten nun über Ihren Webbrowser zu der Anwendung polls navigieren können, indem Sie http://APP_SERVER_1_IP in der URL-Leiste eingeben. Da für den Pfad / keine Route definiert ist, erhalten Sie wahrscheinlich einen 404 Page Not Found-Fehler, der zu erwarten ist.

      Warnung: Wenn Sie die UFW-Firewall mit Docker verwenden, umgeht Docker alle konfigurierten UFW-Firewallregeln, wie in diesem GitHub-Problem dokumentiert. Dies erklärt, warum Sie Zugriff auf Port 80 Ihres Servers haben, obwohl Sie in keinem vorbereitenden Schritt explizit eine UFW-Zugriffsregel erstellt haben. In Schritt 5 werden wir diese Sicherheitslücke schließen, indem wir die UFW-Konfiguration patchen. Wenn Sie UFW nicht verwenden und die Cloud Firewalls von DigitalOcean einsetzen, können Sie diese Warnung getrost ignorieren.

      Navigieren Sie zu http://APP_SERVER_1_IP/polls, um die Benutzeroberfläche der Umfrageanwendung zu sehen:

      Oberfläche der Umfrageanwendung

      Um die administrative Oberfläche anzuzeigen, besuchen Sie http://APP_SERVER_1_IP/admin. Sie sollten das Authentifizierungsfenster für den Administrator der Umfrageanwendung sehen:

      Authentifizierungsseite für Polls-Administrator

      Geben Sie den administrativen Benutzernamen und das Passwort ein, das Sie mit dem Befehl createsuperuser erstellt haben.

      Nach der Authentifizierung können Sie auf die administrative Oberfläche der Umfrageanwendung zugreifen:

      Administrative Hauptoberfläche von Polls

      Beachten Sie, dass statische Assets für die Anwendungen admin und polls direkt aus dem Objektspeicher bereitgestellt werden. Um dies zu bestätigen, konsultieren Sie Prüfen der statischen Dateizustellung von Spaces.

      Wenn Sie die Erkundung abgeschlossen haben, drücken Sie Strg+C im Terminalfenster, in dem der Docker-Container ausgeführt wird, um den Container zu beenden.

      Nachdem Sie nun bestätigt haben, dass der App-Container wie erwartet ausgeführt wird, können Sie ihn im getrennten (detached) Modus ausführen, wodurch er im Hintergrund ausgeführt wird und Ihnen ermöglicht, sich von Ihrer SSH-Sitzung abzumelden:

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      Das Flag -d weist Docker an, den Container im getrennten Modus auszuführen, das Flag -rm säubert das Dateisystem des Containers nach dem Verlassen des Containers und wir benennen den Container polls.

      Melden Sie sich von dem ersten Django App-Server ab und navigieren Sie zu http://APP_SERVER_1_IP/polls, um zu bestätigen, dass der Container wie erwartet ausgeführt wird.

      Nachdem Ihr erster Django-App-Server ausgeführt wird, können Sie nun Ihren zweiten Django-App-Server einrichten.

      Schritt 2 – Konfigurieren des zweiten Django-App-Servers

      Da viele der Befehle zur Einrichtung dieses Servers die gleichen sind wie im vorherigen Schritt, werden sie hier in abgekürzter Form dargestellt. Bitte lesen Sie Schritt 1 für weitere Informationen zu einem bestimmten Befehl in diesem Schritt.

      Beginnen Sie damit, sich bei dem zweiten Django-App-Server anzumelden.

      Klonen Sie den Zweig polls-docker des GitHub-Repositorys von django-polls:

      • git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

      Navigieren Sie in das Verzeichnis django-polls:

      cd django-polls
      

      Erstellen Sie das Image mit docker build:

      Öffnen Sie die Datei env mit nano oder Ihrem bevorzugten Editor:

      nano env
      

      django-polls/env

      DJANGO_SECRET_KEY=
      DEBUG=True
      DJANGO_ALLOWED_HOSTS=
      DATABASE_ENGINE=postgresql_psycopg2
      DATABASE_NAME=polls
      DATABASE_USERNAME=
      DATABASE_PASSWORD=
      DATABASE_HOST=
      DATABASE_PORT=
      STATIC_ACCESS_KEY_ID=
      STATIC_SECRET_KEY=
      STATIC_BUCKET_NAME=
      STATIC_ENDPOINT_URL=
      DJANGO_LOGLEVEL=info
      

      Geben Sie die fehlenden Werte wie in Schritt 1 ein. Wenn Sie die Bearbeitung abgeschlossen haben, speichern und schließen Sie die Datei.

      Führen Sie den App-Container anschließend im getrennten Modus aus:

      docker run -d --rm --name polls --env-file env -p 80:8000 polls
      

      Navigieren Sie zu http://APP_SERVER_2_IP/polls, um zu bestätigen, dass der Container wie erwartet ausgeführt wird. Sie können sich sicher von dem zweiten App-Server anmelden, ohne Ihren laufenden Container zu beenden.

      Da beide Django App-Container ausgeführt werden, können Sie mit der Konfiguration des Reverse-Proxy-Containers von Nginx fortfahren.

      Schritt 3 – Konfigurieren des Nginx Docker-Containers

      Nginx ist ein vielseitiger Webserver, der eine Reihe von Funktionen wie Reverse-Proxying, Load Balancing und Caching bietet. In diesem Tutorial haben wir die statischen Assets von Django in den Objektspeicher ausgelagert, sodass wir die Caching-Funktionen von Nginx nicht verwenden werden. Wir werden Nginx jedoch als Reverse-Proxy für unsere beiden Backend-Django-App-Server verwenden und eingehende Anfragen zwischen ihnen verteilen. Darüber hinaus wird Nginx TLS-Terminierung und -Umleitung unter Verwendung eines von Certbot bereitgestellten TLS-Zertifikats durchführen. Das bedeutet, dass es die Clients zwingen wird, HTTPS zu verwenden, und eingehende HTTPS-Anfragen an Port 443 umzuleiten. Anschließend entschlüsselt Nginx HTTPS-Anfragen und leitet sie an die vorgelagerten Django-Server weiter.

      In diesem Tutorial haben wir die Designentscheidung getroffen, die Nginx-Container von den Backend-Servern zu entkoppeln. Abhängig von Ihrem Anwendungsfall können Sie sich dafür entscheiden, den Nginx-Container auf einem der Django-App-Server auszuführen, das Proxying von Anfragen sowohl lokal als auch auf dem anderen Django-Server auszuführen. Eine weitere mögliche Architektur wäre die Ausführung von zwei Nginx-Containern, einer auf jedem Backend-Server, mit einem Cloud Load Balancer davor. Jede Architektur bietet andere Sicherheits- und Leistungsvorteile und Sie sollten Ihr System einem Lasttest unterziehen, um Engpässe aufzudecken. Die in diesem Tutorial beschriebene flexible Architektur ermöglicht es Ihnen, sowohl die Backend-Django-Anwendungsschicht als auch die Nginx-Proxying-Schicht zu skalieren. Sobald der einzelne Nginx-Container zum Engpass wird, können Sie auf mehrere Nginx-Proxys skalieren und einen Cloud Load Balancer oder schnellen L4 Load Balancer wie HAProxy hinzufügen.

      Da beide Django-App-Server ausgeführt werden, können wir mit dem Einrichten des Proxy-Servers beginnen. Melden Sie sich an Ihrem Proxy-Server an und erstellen Sie ein Verzeichnis namens conf:

      mkdir conf
      

      Erstellen Sie mit nano oder Ihrem bevorzugten Editor eine Konfigurationsdatei namens nginx.conf:

      nano conf/nginx.conf
      

      Fügen Sie die folgende Nginx-Konfiguration ein:

      conf/nginx.conf

      
      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      
      server {
          listen 80 default_server;
          return 444;
      }
      
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      
      server {
          listen 443 ssl http2;
          listen [::]:443 ssl http2;
          server_name your_domain.com;
      
          # SSL
          ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
      
          ssl_session_cache shared:le_nginx_SSL:10m;
          ssl_session_timeout 1440m;
          ssl_session_tickets off;
      
          ssl_protocols TLSv1.2 TLSv1.3;
          ssl_prefer_server_ciphers off;
      
          ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
      
          client_max_body_size 4G;
          keepalive_timeout 5;
      
              location / {
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header Host $http_host;
                proxy_redirect off;
                proxy_pass http://django;
              }
      
          location ^~ /.well-known/acme-challenge/ {
              root /var/www/html;
          }
      
      }
      

      Diese Blöcke upstream, server und location konfigurieren Nginx so, dass HTTP-Anfragen an HTTPS umgeleitet werden und sorgen für einen Lastausgleich zwischen den beiden in Schritt 1 und 2 konfigurierten Django-App-Servern. Um mehr über die Nginx-Konfigurationsdatei zu erfahren, lesen Sie bitte in diesem Artikel über das Verstehen der Nginx-Konfigurationsdateistruktur und der Konfigurationskontexte. Außerdem kann dieser Artikel zum Verstehen von Nginx-Server und Location-Block-Auswahlalgorithmen hilfreich sein.

      Diese Konfiguration wurde aus Beispielkonfigurationsdateien zusammengestellt, die von Gunicorn, Certbot und Nginx bereitgestellt wurden, und ist als eine minimale Nginx-Konfiguration gedacht, um diese Architektur betriebsbereit zu machen. Die Feineinstellung dieser Nginx-Konfiguration geht über den Umfang dieses Artikels hinaus, Sie können jedoch ein Tool wie NGINXConfig verwenden, um performante und sichere Nginx-Konfigurationsdateien für Ihre Architektur zu generieren.

      Der Block upstream definiert die Gruppe von Servern, die zum Proxying von Anfragen zur Verwendung der Anweisung proxy_pass verwendet werden:

      conf/nginx.conf

      upstream django {
          server APP_SERVER_1_IP;
          server APP_SERVER_2_IP;
      }
      . . .
      

      In diesem Block nennen wir den upstream django und schließen die IP-Adressen der beiden Django-App-Server ein. Wenn die App-Server auf DigitalOcean ausgeführt werden und VPC Networking aktiviert haben, sollten Sie hier ihre privaten IP-Adressen verwenden. Um zu erfahren, wie Sie VPC-Networking auf DigitalOcean aktivieren können, lesen Sie bitte Aktivieren von VPC-Networking auf vorhandenen Droplets.

      Der erste Block server erfasst Anfragen, die nicht Ihrer Domäne entsprechen und beendet die Verbindung. Beispielsweise würde eine direkte HTTP-Anfrage an die IP-Adresse Ihres Servers von diesem Block bearbeitet werden:

      conf/nginx.conf

      . . .
      server {
          listen 80 default_server;
          return 444;
      }
      . . .
      

      Der nächste Block server leitet HTTP-Anfragen an Ihre Domäne über eine HTTP 301-Umleitung an HTTPS um. Diese Anfragen werden dann vom letzten Block server bearbeitet:

      conf/nginx.conf

      . . .
      server {
          listen 80;
          listen [::]:80;
          server_name your_domain.com;
          return 301 https://$server_name$request_uri;
      }
      . . .
      

      Diese zwei Anweisungen definieren die Pfade zum TLS-Zertifikat und geheimen Schlüssel. Diese werden mit Certbot bereitgestellt und im nächsten Schritt in den Nginx-Container eingebunden.

      conf/nginx.conf

      . . .
      ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
      ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
      . . .
      

      Bei diesen Parametern handelt es sich um die von Certbot empfohlenen SSL-Sicherheitsstandards. Um mehr über sie zu erfahren, lesen Sie bitte das Modul ngx_http_ssl_module der Nginx-Dokumentation. Mozillas Sicherheits-/Serverseitiges TLS ist ein weiterer hilfreicher Leitfaden, den Sie für die Feinabstimmung Ihrer SSL-Konfiguration verwenden können.

      conf/nginx.conf

      . . .
          ssl_session_cache shared:le_nginx_SSL:10m;
          ssl_session_timeout 1440m;
          ssl_session_tickets off;
      
          ssl_protocols TLSv1.2 TLSv1.3;
          ssl_prefer_server_ciphers off;
      
          ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
      . . .
      

      Diese beiden Anweisungen aus Gunicorns Nginx-Beispielkonfiguration legen die maximal zulässige Größe des Client-Anfragekörpers fest und weisen das Timeout für Keep-Alive-Verbindungen mit dem Client zu. Nginx schließt Verbindungen mit dem Client nach keepalive_timeout-Sekunden.

      conf/nginx.conf

      . . .
      client_max_body_size 4G;
      keepalive_timeout 5;
      . . .
      

      Der erste Block location weist Nginx zum Proxying von Anfragen über HTTP an die upstream django-Server an. Er bewahrt zusätzlich Client HTTP-Header auf, die die Ursprungs-IP-Adresse, das zur Verbindung verwendete Protokoll und den Ziel-Host erfassen:

      conf/nginx.conf

      . . .
      location / {
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header Host $http_host;
          proxy_redirect off;
          proxy_pass http://django;
      }
      . . .
      

      Um mehr über diese Anweisungen zu erfahren, lesen Sie bitte Bereitstellen von Gunicorn und das Modul ngx_http_proxy_module der Nginx-Dokumentation.

      Der letzte Block location erfasst Anfragen an den Pfad /well-known/acme-Challenge/, der von Certbot für HTTP-01-Challenges verwendet wird, um Ihre Domäne mit Let’s Encrypt zu verifizieren und TLS-Zertifikate bereitzustellen oder zu erneuern. Weitere Informationen über die von Certbot verwendete HTTP-01-Challenge finden Sie unter Challenge-Arten in der Let’s Encrypt-Dokumentation.

      conf/nginx.conf

      . . .
      location ^~ /.well-known/acme-challenge/ {
              root /var/www/html;
      }
      

      Wenn Sie die Bearbeitung abgeschlossen haben, speichern und schließen Sie die Datei.

      Sie können diese Konfigurationsdatei nun verwenden, um einen Nginx Docker-Container auszuführen. In diesem Tutorial verwenden wir das Image nginx:1.19.0, Version 1.19.0 des offiziellen Docker-Images, das von Nginx verwaltet wird.

      Wenn wir den Container zum ersten Mal ausführen, wird Nginx einen Fehler ausgeben und fehlschlagen, da wir die in der Konfigurationsdatei definierten Zertifikate noch nicht bereitgestellt haben. Wir werden jedoch trotzdem den Befehl zum Herunterladen des Nginx-Images lokal ausführen und testen, ob alles andere korrekt funktioniert:

      docker run --rm --name nginx -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      Hier nennen wir den Container nginx und ordnen die Host-Ports 80 und 443 den jeweiligen Container-Ports zu. Das Flag -v bindet die Konfigurationsdatei unter /etc/nginx/conf.d/nginx.conf in den Nginx-Container ein, für dessen Laden das Nginx-Image vorkonfiguriert ist. Es wird im Modus ro oder „read-only“ eingebunden, sodass der Container die Datei nicht verändern kann. Das Web-Stammverzeichnis /var/www/html ist ebenfalls in den Container eingebunden. Schließlich weist nginx:1.19.0 Docker an, das Image nginx:1.19.0 aus Dockerhub zu ziehen und auszuführen.

      Docker wird das Image ziehen und ausführen, dann wird Nginx einen Fehler ausgeben, wenn es das konfigurierte TLS-Zertifikat und den geheimen Schlüssel nicht findet. Im nächsten Schritt stellen wir diese mithilfe eines dockerisierten Certbot-Clients und der Zertifizierungsstelle Let’s Encrypt bereit.

      Schritt 4 – Konfigurieren von Certbot und Let’s Encrypt-Zertifikaterneuerung

      Certbot ist ein Let’s Encrypt-Client, der von der Electronic Frontier Foundation entwickelt wurde. Er stellt kostenlose TLS-Zertifikate von der Let’s Encrypt-Zertifizierungsstelle zur Verfügung, mit denen Browser die Identität Ihrer Webserver überprüfen können. Da wir auf unserem Nginx-Proxy-Server Docker installiert haben, verwenden wir das Certbot Docker-Image zur Bereitstellung und Erneuerung der TLS-Zertifikate.

      Stellen Sie zunächst sicher, dass Sie über einen DNS-A-Eintrag verfügen, der der öffentlichen IP-Adresse des Proxy-Servers zugeordnet ist. Stellen Sie dann auf Ihrem Proxy-Server eine Staging-Version der Zertifikate unter Verwendung des Docker-Images certbot bereit:

      docker run -it --rm -p 80:80 --name certbot 
               -v "/etc/letsencrypt:/etc/letsencrypt" 
               -v "/var/lib/letsencrypt:/var/lib/letsencrypt" 
               certbot/certbot certonly --standalone --staging -d your_domain.com
      

      Dieser Befehl führt das certbot Docker-Image im interaktiven Modus aus und leitet Port 80 auf dem Host an den Container-Port 80 weiter. Er erstellt zwei Host-Verzeichnisse und bindet sie in die Container ein: /etc/letsencrypt/ und /var/lib/letsencrypt/. certbot wird im Modus standalone ohne Nginx ausgeführt und verwendet die Staging-Server von Let’s Encrypt, um die Domänenvalidierung durchzuführen.

      Geben Sie, wenn Sie dazu aufgefordert werden, Ihre E-Mail-Adresse ein und stimmen Sie den Nutzungsbedingungen zu. Wenn die Domänenvalidierung erfolgreich war, sollten Sie die folgende Ausgabe sehen:

      Output

      Obtaining a new certificate Performing the following challenges: http-01 challenge for stubb.dev Waiting for verification... Cleaning up challenges IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/your_domain.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/your_domain.com/privkey.pem Your cert will expire on 2020-09-15. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal.

      Sie können das Zertifikat mit cat inspizieren:

      sudo cat /etc/letsencrypt/live/your_domain.com/fullchain.pem
      

      Mit dem bereitgestellten TLS-Zertifikat können wir die im vorherigen Schritt eingebundene Nginx-Konfiguration testen:

      docker run --rm --name nginx -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /etc/letsencrypt:/etc/letsencrypt 
          -v /var/lib/letsencrypt:/var/lib/letsencrypt 
          -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      Dies ist derselbe Befehl, der in Schritt 3 ausgeführt wurde, mit dem Hinzufügen der beiden kürzlich erstellten Let’s Encrypt-Verzeichnisse:

      Sobald Nginx ausgeführt wird, navigieren Sie zu http://your_domain.com. Möglicherweise erhalten Sie in Ihrem Browser eine Warnung, dass die Zertifizierungsstelle ungültig ist. Dies ist zu erwarten, da wir Staging-Zertifikate und keine Produktions-Let’s Encrypt-Zertifikate bereitgestellt haben. Überprüfen Sie die URL-Leiste Ihres Browsers, um zu bestätigen, dass Ihre HTTP-Anfrage an HTTPS umgeleitet wurde.

      Drücken Sie zum Beenden von Nginx in Ihrem Terminal Strg+C und führen Sie den Client certbot erneut aus, diesmal ohne das Flag --staging:

      docker run -it --rm -p 80:80 --name certbot 
               -v "/etc/letsencrypt:/etc/letsencrypt" 
               -v "/var/lib/letsencrypt:/var/lib/letsencrypt" 
               certbot/certbot certonly --standalone -d your_domain.com
      

      Wenn Sie dazu aufgefordert werden, entweder das vorhandene Zertifikat beizubehalten oder es zu erneuern und zu ersetzen, drücken Sie 2 zum Erneuern und dann ENTER, um Ihre Wahl zu bestätigen.

      Wenn das Produktions-TLS-Zertifikat bereitgestellt ist, führen Sie den Nginx-Server erneut aus:

      docker run --rm --name nginx -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /etc/letsencrypt:/etc/letsencrypt 
          -v /var/lib/letsencrypt:/var/lib/letsencrypt 
          -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      Navigieren Sie in Ihrem Browser zu http://your_domain.com. Bestätigen Sie in der URL-Leiste, dass die HTTP-Anfrage an HTTPS umgeleitet wurde. Da für die Umfrageanwendung keine Standardroute konfiguriert ist, sollten Sie den Django-Fehler Page not found (Seite nicht gefunden) sehen. Navigieren Sie zu https://your_domain.com/polls und Sie sehen die Standardoberfläche der Umfrageanwendung:

      Oberfläche der Umfrageanwendung

      Zu diesem Zeitpunkt haben Sie mit dem Certbot Docker-Client ein Produktions-TLS-Zertifikat bereitgestellt und externe Anfragen an die beiden Django-App-Server durch Reverse Proxying und Lastausgleich umgeleitet.

      Die Let’s Encrypt-Zertifikate laufen alle 90 Tage ab. Um sicherzustellen, dass Ihr Zertifikat gültig bleibt, sollten Sie es regelmäßig vor dessen geplantem Ablauf erneuern. Wenn Nginx ausgeführt wird, sollten Sie den Certbot-Client im Modus webroot anstelle des Modus standalone verwenden. Das bedeutet, dass Certbot die Validierung durch die Erstellung einer Datei im Verzeichnis /var/www/html/.well-known/acme-challenge/ durchführt, und die Validierungsanforderungen von Let’s Encrypt an diesen Pfad werden von der in der Nginx-Konfiguration in Schritt 3 definierten Regel location erfasst. Certbot wird dann die Zertifikate rotieren, und Sie können Nginx neu laden, sodass es dieses neu bereitgestellte Zertifikat verwendet.

      Es gibt mehrere Möglichkeiten, um dieses Verfahren zu automatisieren und die automatische Erneuerung von TLS-Zertifikaten geht über den Umfang dieses Tutorials hinaus. Ein ähnliches Verfahren unter Verwendung des Scheduling-Dienstprogramms cron finden Sie in Schritt 6 von Sichern einer containerisierten Node.js-Anwendung mit Nginx, Let’s Encrypt, und Docker Compose.

      Drücken Sie zum Beenden des Nginx-Containers in Ihrem Terminal Strg+C. Führen Sie ihn erneut im getrennten Modus aus, indem Sie das Flag -d anhängen:

      docker run --rm --name nginx -d -p 80:80 -p 443:443 
          -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro 
          -v /etc/letsencrypt:/etc/letsencrypt 
          -v /var/lib/letsencrypt:/var/lib/letsencrypt 
        -v /var/www/html:/var/www/html 
          nginx:1.19.0
      

      Verwenden Sie mit im Hintergrund ausgeführtem Nginx den folgenden Befehl, um einen Probelauf des Zertifikatserneuerungsverfahrens auszuführen:

      docker run -it --rm --name certbot 
          -v "/etc/letsencrypt:/etc/letsencrypt" 
        -v "/var/lib/letsencrypt:/var/lib/letsencrypt" 
        -v "/var/www/html:/var/www/html" 
        certbot/certbot renew --webroot -w /var/www/html --dry-run
      

      Wir verwenden das Plugin --webroot, geben den Web-Stammpfad ein und verwenden das Flag --dry-run zum Überprüfen der ordnungsgemäßen Funktion, ohne die Zertifikatserneuerung tatsächlich durchzuführen.

      Wenn die Erneuerungssimulation erfolgreich ist, sollten Sie die folgende Ausgabe sehen:

      Output

      Cert not due for renewal, but simulating renewal for dry run Plugins selected: Authenticator webroot, Installer None Renewing an existing certificate Performing the following challenges: http-01 challenge for your_domain.com Using the webroot path /var/www/html for all unmatched domains. Waiting for verification... Cleaning up challenges - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - new certificate deployed without reload, fullchain is /etc/letsencrypt/live/your_domain.com/fullchain.pem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/your_domain.com/fullchain.pem (success) ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      In einer Produktionseinstellung sollten Sie nach der Erneuerung von Zertifikaten Nginx neu laden, damit die Änderungen wirksam werden. Führen Sie zum Neuladen von Nginx den folgenden Befehl aus:

      docker kill -s HUP nginx
      

      Dieser Befehl sendet ein HUP Unix-Signal an den Nginx-Prozess, der innerhalb des Docker-Containers nginx ausgeführt wird. Nach Empfang dieses Signals lädt Nginx seine Konfiguration und erneuerten Zertifikate neu.

      Wenn HTTPS aktiviert ist und alle Komponenten dieser Architektur ausgeführt werden, besteht der letzte Schritt darin, die Einrichtung zu sperren, indem der externe Zugriff auf die beiden Backend-App-Server verhindert wird; alle HTTP-Anfragen sollten über den Nginx-Proxy laufen.

      Schritt 5 – Verhindern des externen Zugriffs auf Django-App-Server

      In der in diesem Tutorial beschriebenen Architektur erfolgt die SSL-Terminierung am Nginx-Proxy. Das bedeutet, dass Nginx die SSL-Verbindung entschlüsselt und die Pakete unverschlüsselt an die Django-App-Server weitergeleitet werden. Für viele Anwendungsfälle ist diese Sicherheitsstufe ausreichend. Für Anwendungen mit Finanz- oder Gesundheitsdaten sollten Sie eventuell eine End-to-End-Verschlüsselung implementieren. Sie können dies tun, indem Sie verschlüsselte Pakete über den Load Balancer weiterleiten und auf den App-Servern entschlüsseln oder am Proxy neu verschlüsseln und auf den Django-App-Servern wieder entschlüsseln. Diese Techniken gehen über den Rahmen dieses Artikels hinaus. Um mehr zu erfahren, lesen Sie bitte End-to-End-Verschlüsselung.

      Der Nginx-Proxy fungiert als Gateway zwischen externem Datenverkehr und dem internen Netzwerk. Theoretisch sollten keine externen Clients direkten Zugriff auf die internen App-Server haben, und alle Anfragen sollten über den Nginx-Server laufen. Der Hinweis in Schritt 1 beschreibt kurz ein offenes Problem mit Docker, bei dem Docker standardmäßig die ufw-Firewalleinstellungen umgeht und Ports extern öffnet, die möglicherweise unsicher sind. Um dieses Sicherheitsproblem zu beheben wird empfohlen, bei der Arbeit mit Docker-fähigen Servern Cloud Firewalls zu verwenden. Weitere Informationen zum Erstellen von Cloud Firewalls mit DigitalOcean finden Sie unter Erstellen von Firewalls. Sie können iptables auch direkt manipulieren, anstatt ufw zu verwenden. Um mehr über die Verwendung von iptables mit Docker zu erfahren, lesen Sie bitte Docker und iptables.

      In diesem Schritt ändern wir die UFW-Konfiguration so, dass der externe Zugriff auf die von Docker geöffneten Host-Ports blockiert wird. Bei der Ausführung von Django auf den App-Servern haben wir das Flag -p 80:8000 an docker übergeben, das Port 80 auf dem Host an den Container-Port 8000 weiterleitet. Dadurch wurde Port 80 auch für externe Clients geöffnet, was Sie unter http://your_app_server_1_IP überprüfen können. Um den direkten Zugriff zu verhindern, ändern wir die UFW-Konfiguration mithilfe der im ufw-docker GitHub-Repository beschriebenen Methode.

      Beginnen Sie damit, sich bei dem ersten Django-App-Server anzumelden. Öffnen Sie dann die Datei /etc/ufw/after.rules mit superuser-Berechtigungen, indem Sie nano oder Ihren bevorzugten Editor verwenden:

      sudo nano /etc/ufw/after.rules
      

      Geben Sie bei Aufforderung Ihr Passwort ein und drücken Sie zur Bestätigung ENTER.

      Sie sollten die folgenden ufw-Regeln sehen:

      /etc/ufw/after.rules

      #
      # rules.input-after
      #
      # Rules that should be run after the ufw command line added rules. Custom
      # rules should be added to one of these chains:
      #   ufw-after-input
      #   ufw-after-output
      #   ufw-after-forward
      #
      
      # Don't delete these required lines, otherwise there will be errors
      *filter
      :ufw-after-input - [0:0]
      :ufw-after-output - [0:0]
      :ufw-after-forward - [0:0]
      # End required lines
      
      # don't log noisy services by default
      -A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input
      -A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input
      -A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input
      -A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input
      -A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input
      -A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input
      
      # don't log noisy broadcast
      -A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input
      
      # don't delete the 'COMMIT' line or these rules won't be processed
      COMMIT
      

      Scrollen Sie nach unten und fügen Sie den folgenden Block mit UFW-Konfigurationsregeln ein:

      /etc/ufw/after.rules

      . . .
      
      # BEGIN UFW AND DOCKER
      *filter
      :ufw-user-forward - [0:0]
      :DOCKER-USER - [0:0]
      -A DOCKER-USER -j RETURN -s 10.0.0.0/8
      -A DOCKER-USER -j RETURN -s 172.16.0.0/12
      -A DOCKER-USER -j RETURN -s 192.168.0.0/16
      
      -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
      
      -A DOCKER-USER -j ufw-user-forward
      
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
      
      -A DOCKER-USER -j RETURN
      COMMIT
      # END UFW AND DOCKER
      

      Diese Regeln schränken den öffentlichen Zugriff auf die von Docker geöffneten Ports ein und ermöglichen den Zugriff aus den privaten IP-Bereichen 10.0.0/8, 172.16.0.0/12 und 192.168.0.0/16. Wenn Sie VPC mit DigitalOcean verwenden, dann haben Droplets in Ihrem VPC-Netzwerk über die private Netzwerkschnittstelle Zugriff auf den offenen Port, externe Clients jedoch nicht. Weitere Informationen über VPC finden Sie in der offiziellen VPC-Dokumentation. Um mehr über die in diesem Snippet implementierten Regeln zu erfahren, lesen Sie bitte Funktionsweise in der ufw-docker README.

      Wenn Sie VPC nicht mit DigitalOcean verwenden und die öffentlichen IP-Adressen der App-Server in den Block upstream Ihrer Nginx-Konfiguration eingegeben haben, müssen Sie die UFW-Firewall explizit ändern, um den Datenverkehr vom Nginx-Server über Port 80 auf den Django-App-Servern zuzulassen. Eine Anleitung zur Erstellung von allow-Regeln mit der UFW-Firewall finden Sie unter UFW Grundlagen: Allgemeine Firewallregeln und -befehle.

      Wenn Sie die Bearbeitung abgeschlossen haben, speichern und schließen Sie die Datei.

      Starten Sie ufw neu, damit die neue Konfiguration übernommen wird:

      sudo systemctl restart ufw
      

      Navigieren Sie in Ihrem Webbrowser zu http://APP_SERVER_1_IP, um zu bestätigen, dass Sie über Port 80 nicht mehr auf die App-Server zugreifen können.

      Wiederholen Sie diesen Vorgang auf dem zweiten Django-App-Server.

      Melden Sie sich bei dem ersten App-Server ab oder öffnen Sie ein anderes Terminalfenster und melden Sie sich bei dem zweiten Django-App-Server an. Öffnen Sie dann die Datei /etc/ufw/after.rules mit superuser-Berechtigungen, indem Sie nano oder Ihren bevorzugten Editor verwenden:

      sudo nano /etc/ufw/after.rules
      

      Geben Sie bei Aufforderung Ihr Passwort ein und drücken Sie zur Bestätigung ENTER.

      Scrollen Sie nach unten und fügen Sie den folgenden Block mit UFW-Konfigurationsregeln ein:

      /etc/ufw/after.rules

      . . .
      
      # BEGIN UFW AND DOCKER
      *filter
      :ufw-user-forward - [0:0]
      :DOCKER-USER - [0:0]
      -A DOCKER-USER -j RETURN -s 10.0.0.0/8
      -A DOCKER-USER -j RETURN -s 172.16.0.0/12
      -A DOCKER-USER -j RETURN -s 192.168.0.0/16
      
      -A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
      
      -A DOCKER-USER -j ufw-user-forward
      
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
      -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
      
      -A DOCKER-USER -j RETURN
      COMMIT
      # END UFW AND DOCKER
      

      Wenn Sie die Bearbeitung abgeschlossen haben, speichern und schließen Sie die Datei.

      Starten Sie ufw neu, damit die neue Konfiguration übernommen wird:

      sudo systemctl restart ufw
      

      Navigieren Sie in Ihrem Webbrowser zu http://APP_SERVER_2_IP, um zu bestätigen, dass Sie über Port 80 nicht mehr auf die App-Server zugreifen können.

      Navigieren Sie abschließend zu https://your_domain_here/polls, um zu bestätigen, dass der Nginx-Proxy weiterhin Zugriff auf die upstream Django-Server hat. Sie sollten die Standardoberfläche der Umfrageanwendung sehen.

      Zusammenfassung

      In diesem Tutorial haben Sie mit Docker-Containern eine skalierbare Django Umfrageanwendung eingerichtet. Wenn Ihr Datenverkehr steigt und die Last auf dem System zunimmt, können Sie jede Schicht separat skalieren: die Nginx-Proxying-Schicht, die Django-Backend-Anwendungsschicht und die PostgreSQL-Datenbankschicht.

      Beim Aufbau eines verteilten Systems müssen Sie oft mehrere Designentscheidungen treffen, und mehrere Architekturen können Ihrem Anwendungsfall gerecht werden. Die in diesem Tutorial beschriebene Architektur ist als flexible Blaupause für den Entwurf skalierbarer Anwendungen mit Django und Docker gedacht.

      Möglicherweise möchten Sie das Verhalten Ihrer Container steuern, wenn sie auf Fehler stoßen, oder Container automatisch ausführen, wenn Ihr System gestartet wird. Zu diesem Zweck können Sie einen Prozessmanager wie Systemd verwenden oder Neustartrichtlinien implementieren. Weitere Informationen hierzu finden Sie unter Automatisches Starten von Containern in der Docker-Dokumentation.

      Wenn Sie im großen Maßstab mit mehreren Hosts arbeiten, die dasselbe Docker-Image ausführen, kann es effizient sein, Schritte mit einem Konfigurations-Managementtool wie Ansible oder Chef zu automatisieren. Um mehr über das Konfigurationsmanagement zu erfahren, lesen Sie bitte Eine Einführung in das Konfigurationsmanagement und Automatisieren der Servereinrichtung mit Ansible: Ein DigitalOcean-Workshop-Kit.

      Anstatt auf jedem Host dasselbe Image zu erstellen, können Sie die Bereitstellung auch mithilfe einer Image-Registrierung wie Docker Hub rationalisieren, bei der Docker-Images zentral erstellt, gespeichert und an mehrere Server verteilt werden. Zusammen mit einer Image-Registrierung kann Ihnen eine kontinuierliche Integrations- und Bereitstellungspipeline dabei helfen, Images zu erstellen, zu testen und auf Ihre App-Server zu verteilen. Weitere Informationen zu CI/CD finden Sie unter Eine Einführung in die CI/CD Best Practices.



      Source link