* tech-edition: Wie man eine Django-Web-App elegant andockt

Django ist ein fantastisches Webanwendungs-Framework mit vielen nützlichen Funktionen. Mein Lieblingsaspekt sind wohl Datenbankmigrationen, die einen Prozess, der bei manueller Ausführung fehleranfällig sein kann, erheblich entlasten können. Es gibt auch viele Gründe, warum man ein Django-Projekt andocken möchte. Wir können die Bewegung der Infrastruktur als Code mitmachen und Abhängigkeiten und den Zustand der zugrunde liegenden Infrastruktur in Konfigurationsdateien und als Code klar definieren und diese der Quellcodeverwaltung übergeben. Wir können unsere Entwicklungsumgebungen auch ein wenig an unsere Staging- und Produktionsumgebungen angleichen und # 10 der Zwölf-Faktoren-App-Faktoren einhalten. Wenn Sie immer noch nicht überzeugt sind, ist eine Steigerung der Produktivität durch einfache Rüst- und Abbruchroutinen möglicherweise das entscheidende Argument (Wäre für mich!).

Container, erleichtert den Versand! [Quelle]

In diesem Beitrag werde ich versuchen, eine Django-App auf elegante Weise anzudocken. Dabei werden einige nützliche Funktionen in Django und Docker verwendet, z. B. separat gespeicherte Umgebungsvariablen und Datenmigrationen, damit alles sicher und reibungslos läuft . In einem zweiten Artikel gehe ich etwas weiter in Richtung einer Produktionsumgebung. Wir werden dort weitermachen, wo wir es hier lassen, indem wir mehr Flexibilität in unsere Umgebungen einführen und einen Reverse-Proxy einrichten.

Der Code kann auch in seinem GitHub-Repo eingesehen werden. Die schrittweise Anleitung wird im Festschreibungsprotokoll dargestellt.

Die Voraussetzungen

Dieser Artikel baut auf einigen Grundlagen auf und diese sind:

  • Docker & Docker-Compose
  • Python 3.6.5 und Django 2.1
  • virtualenv & pip
  • git
  • Grundlegendes Verständnis von Django (z. B. durch Ausführen des Django-Tutorials)
  • Arbeiten in der Terminal / Linux-Shell

Es ist nicht notwendig, ein Experte für diese Technologien zu sein, nur ein Verständnis sollte ausreichen. Es sollte sowieso möglich sein, mitzumachen, aber ich werde keine Erklärungen dazu geben.

Die Einrichtung

Zunächst erstellen wir ein Projektverzeichnis (mkdir docker-django) und richten ein Git-Repo (git init) ein. Um ehrlich zu sein, erstelle ich das Repo normalerweise auf einem entfernten Git-Server (in diesem Fall GitHub) und klone es dann lokal - spart einige Mühen beim Einrichten der Fernbedienung. Lassen Sie uns sofort einen neuen Entwicklungszweig erstellen und auschecken (git checkout -b develop). Der zweite Teil des Setups bezieht sich auf das Abhängigkeitsmanagement. Daher erstellen wir eine virtuelle Umgebung (virtualenv env) und aktivieren sie (source env / bin / activate). Dann werden wir Django installieren (pip install Django> = 2.1) Nun wird Django lokal in der virtuellen Umgebung installiert, was genau das ist, was wir jetzt anstreben. Wir werden die Funktionalität von Django verwenden, um ein Projekt einzurichten. Wir wollen aber auch unsere Abhängigkeiten für andere Umgebungen synchron halten, also werden wir die Abhängigkeit zur Datei requirements.txt hinzufügen (echo 'Django> = 2.1' >> requirements.txt), die ein erstes Commit verdient (git add * && git commit -m "...")

Der dritte Teil des Setups wird von Django ausgeführt: Starten wir ein Django-Projekt (django-admin startproject mysite). Ich verwende Visual Studio Code als Editor, daher schließe ich das von ihm eingerichtete Verzeichnis von der Versionskontrolle aus (Echo ".vscode /" >> .gitignore). Wir führen einen Testlauf des Projekt-Setups durch, indem wir einen lokalen Server ausführen (python mysite / manage.py runserver). Dies sollte es uns ermöglichen, den lokal ausgeführten Server unter http: // localhost: 8000 zu überprüfen.

Beenden wir das Setup mit einem weiteren schnellen Commit (das übliche git add ... & git commit ...).

Typische Entwicklungssituation: lokale App und eine Datenbank im lokalen Dateisystem.

Die „App“ läuft jetzt lokal, was während der Entwicklung häufig der Fall ist. Natürlich ist es keine großartige App, aber vergessen wir das vorerst. Ich würde argumentieren, dass das Standardprojekt von Django bereits so viele Elemente enthält, dass es zu einer App wie der Admin-App wird. Ich werde diese Funktionalität also nur für unsere Reise verwenden.

Dockerisieren Sie die "App"

Nachdem die Grundlagen geschaffen wurden, können wir mit dem Andocken der App beginnen. Alles beginnt mit einer Docker-Datei (Touch-Docker-Datei), die ungefähr so ​​aussehen sollte:

Es erstellt ein Image basierend auf dem offiziellen Python-Image auf Docker Hub, kopiert unseren Code, installiert die Anforderungen und startet die App.

Jetzt wird die App in einem Docker-Container ausgeführt, und die Datenbank befindet sich im Container. Dies führt zu Datenverlust, wenn der Behälter abgerissen wird.

Wir starten den Build des Images (Docker build -t django-docker: 0.0.1.). Dies dauert normalerweise eine Weile, aber wenn es fertig ist, können Sie es testen, indem Sie unsere "App" in einem Container ausführen (Docker-Run -p 8001: 8001 Docker-Django: 0.0.1). Da der Server, der im Container ausgeführt wird, Port 8001 abhört, im Gegensatz zu 8000 im Vergleich zu unserer „App“, die lokal ausgeführt wird, ordnen wir Port 8001 des Containers Port 8001 unseres lokalen Computers zu. Überprüfen wir, ob es auf http: // localhost: 8001 antwortet.

Sobald das Image erstellt wurde und ordnungsgemäß ausgeführt wird, wird es zum ersten Dienst in einer neuen docker-compose.yml-Definition (berühren Sie docker-compose.yml, die folgendermaßen aussehen sollte:

Jetzt können wir unseren Dienst einfach mit einer schnellen Docker-Zusammenstellung ausführen. Das ist ziemlich beeindruckend und genau das möchten wir erreichen, indem wir unsere App mit einem einzigen Befehl starten können. Der Befehl zum Ausführen der "App" im Container muss gestartet werden und für den Moment 0.0.0.0 angeben. Da die Container über ein eigenes Netzwerk verfügen, bezieht sich localhost nur auf den Container selbst. Beachten Sie auch, dass wir die CMD aus der Docker-Datei löschen und angeben sollten, welcher Befehl für den Container in docker-compose.yml ausgeführt werden soll.

Unsere "App" informiert uns daher, dass es nicht angewendete Migrationen gibt, nämlich die zusätzlichen Apps, die im Standard-Django-Projekt enthalten sind, und die Administratorkonsole ist noch nicht verfügbar, da diese Migrationen nicht angewendet wurden. Als ersten Schritt könnten wir die Datenbank in unserem lokalen Repository migrieren (python mysite / manage.py migrate). Dies würde auf die lokale Datenbank db.sqlite3 in unserem Dateisystem zutreffen und die Administratorkonsole wäre jetzt auf der lokal ausgeführten App unter http: // localhost: 8000 / admin verfügbar (vorausgesetzt, die App wird ausgeführt).

Um dieselbe Migration für den Container durchzuführen, können Sie den Befehl im Container ausführen (docker-compose exec web python mysite / manage.py migrate) und Folgendes überprüfen: http: // localhost: 8001 / admin

Beachten Sie, dass sowohl der Container als auch der Server aktiv sein können und Sie den Befehl exec separat auslösen können. Es ist im Grunde dasselbe wie das Starten einer neuen Shell, das Anmelden am Container und das Ausführen des Befehls. Auf die Admin-App kann jetzt zugegriffen werden, es existiert jedoch noch kein Admin-Benutzer.

Wir helfen Ihnen also dabei und erstellen einen Administrator im Container. (Docker-Compose Exec Web Python MySite / manage.py erstellt Superuser). Lassen Sie uns das in der Admin-App testen - Sie sollten sich jetzt in das Admin-Tool einloggen und den Admin-Benutzer sehen können, wenn Sie auf "Benutzer" klicken.

Nach dem Einloggen in die Admin-App sollten wir in der Lage sein, die Daten in der Datenbank unserer App zu durchsuchen.

Sieht gut aus, aber jetzt haben wir den Zustand unseres Containers manuell geändert. Wenn wir es fallen lassen und eines von Grund auf neu bauen, müssen wir alle diese Schritte erneut ausführen, um den Container wieder in diesen Zustand zu versetzen. Unsere Bemühungen zielen darauf ab, genau das zu vermeiden. Um den Status nicht zu verlieren, erstellen wir ein Volume für unseren Container, sodass unser Container und unsere lokale Entwicklungsumgebung auf dieselben Dateien zugreifen können:

Bände:
  -.: / code
Durch das Zuordnen eines Verzeichnisses innerhalb des Containers zum lokalen Dateisystem können wir den lokalen Status und das Innere des Containers synchron halten.

Unser lokales aktuelles Verzeichnis wird dem Codeverzeichnis im Container zugeordnet. Die lokale Datei db.sqlite3 ist in meinem Fall von der Versionskontrolle ausgeschlossen, da ich das Repo vom Remoteserver geklont habe, auf dem automatisch ein .gitignore für Python erstellt wurde, der diese SQLite-Dateien bereits als ausgeschlossen markiert.

Wenn wir jetzt unseren alten Container abreißen (Docker-Compose-Down) und alle Dienste neu starten (Docker-Compose-Up), sind wir ohne den Superuser wieder da. Lassen Sie uns also die beiden obigen Schritte in unserem Container wiederholen (Migrieren anwenden und Superuser erstellen). Wir können jetzt sowohl die lokale als auch die containerisierte App überprüfen und beide sollten jetzt synchron sein. Das heißt, wir sollten uns mit dem gerade eingerichteten Admin-Benutzer bei http: // localhost: 8000 / admin anmelden können. Wenn wir hier Daten ändern, sollten wir die gleichen Änderungen an den Daten in unserer containerisierten App sehen, und die Datenbank im lokalen Dateisystem sollte jetzt mit der im Docker-Container identisch sein. Das ist ziemlich toll.

Schließlich möchten wir eine App entwickeln. Starten wir also eine App in unserem Projekt:

cd mysite
python mysite / manage.py startapp myapp

Datenbankdienste hinzufügen

Wir könnten mit unserer SQLite-Datenbank wahrscheinlich ziemlich weit kommen, da es heutzutage möglich ist, enorme Server in der Cloud zu betreiben, aber wir möchten eine App erstellen, die horizontal skaliert werden kann und eine vollwertige Datenbank wie Postgres verwendet. Wechseln wir einfach zu Postgres. Dazu fügen wir unserer docker-compose.yml einen Datenbankdienst, kurz db, hinzu:

db:
  Bild: Postgres
  Netzwerke:
   - Backend
Netzwerke:
  Backend:
    Fahrer: Brücke

Wir verwenden hier das offizielle Postgres-Image von Docker Hub. Außerdem müssen wir unseren vorhandenen Webdienst mit dem Datenbankdienst verknüpfen, indem wir beide zum selben Netzwerk hinzufügen, sodass sie miteinander kommunizieren können.

Umgebungsvariablen in Docker

Um eine Verbindung zur Datenbank herzustellen, müssen wir Django die Verbindungsdetails und Anmeldeinformationen geben, indem wir die Postgres-Datenbank zu settings.py hinzufügen:

POSTGRES_PASSWORD = os.environ.get ('POSTGRES_PASSWORD')
DATABASES = {
    'Standard': {
        'ENGINE': 'django.db.backends.postgresql',
        "NAME": "postgres",
        'USER': 'postgres',
        'PASSWORD': POSTGRES_PASSWORD,
        'HOST': 'db',
        "PORT": "5432",
    }
}

Das hier definierte Kennwort hängt davon ab, was wir für den Datenbankdienst im anderen Docker-Container eingerichtet haben. Um diese Umgebungsinformationen synchron zu halten, verwenden wir eine separate Umgebungsdatei .env (nicht quellengesteuert), auf die beide Container zugreifen können, und speichern diese Anmeldeinformationen in Umgebungsvariablen (touch .env). Dies ist eine gängige, wenn auch nicht bewährte Vorgehensweise. Bewährte Methoden für den Umgang mit Anmeldeinformationen sind jedoch ein völlig anderes Thema und gehen weit über den Umfang dieses Artikels hinaus.

Wir können auch andere Geheimnisse wie Djangos geheimen Schlüssel in einer .env-Datei und anderen Informationen speichern, die unsere aktuelle Entwicklungsumgebung definieren:

POSTGRES_PASSWORD = geheim
POSTGRES_USER = Postgres
DJANGO_SECRET_KEY = geheim

Um eine Verbindung zu Postgres herzustellen, ist Django auf das Python-Paket psycopg2 angewiesen. Daher fügen wir diese Abhängigkeit zu unserer Anforderungsdatei requirements.txt hinzu:

Django> = 2.1
psycopg2-binary

Fügen wir unserer Docker-Datei weitere Abhängigkeiten hinzu:

...
RUN apk update && \
    apk add --virtuelle build-deps gcc python-dev musl-dev && \
    apk postgresql-dev hinzufügen
...

Dies bedeutet, dass unser Image neu aufgebaut werden muss.

Als Referenz kann es einfacher sein, sich das Commit hier anzusehen.

Ein separater Web- und Datenbankdienst, der miteinander kommunizieren kann.

Das ist es aber wirklich, wir sollten in der Lage sein, alle Dienste mit Docker-Compose wieder zu starten. Das war eigentlich ziemlich einfach, oder? Jetzt können wir mit einer Postgres-Datenbank herumspielen.

Datenmigrationen, um automatisch einen Superuser einzurichten

Wir können erneut manuell Migrationen für den Datenbankdienst ausführen und dann unsere App testen und prüfen, ob alles einwandfrei funktioniert.

docker-compose exec web python mysite / manage.py migrieren
docker-compose exec web python mysite / manage.py erstellt einen Superuser

An diesem Punkt werden wir es leid, Migrationen durchzuführen und vor allem einen Superuser zu erstellen, der so viele Eingaben erfordert. Wir möchten dies jedoch nicht jedes Mal tun, wenn wir unsere App starten. Außerdem sollten die Anmeldeinformationen für den Superuser genauso sicher sein wie die anderen Anmeldeinformationen und nicht an einer Stelle im Repository fest codiert oder als Befehlszeilenargumente übergeben werden, die normalerweise in unserem Bash-Verlauf enden.

Verwenden Sie eine Datenmigration, um einen Superuser zu erstellen. Auf diese Weise ist die Erstellung des Superusers nur eine weitere anzuwendende Migration.

python mysite / manage.py makemigrations - leere myapp

Dadurch wird eine leere Migration erstellt, die wir in eine Migration wie die folgende ändern können:

Alle Umgebungsvariablen, die bei dieser Migration verwendet werden, müssen auch in unserer docker-compose.yml sowie in der .env-Datei angegeben werden. Sobald sie jedoch vorhanden sind, wird alles synchronisiert.

Dienstleistungen:
  Netz:
    [...]
    Umgebung:
      POSTGRES_PASSWORD: $ {POSTGRES_PASSWORD}
      POSTGRES_USER: $ {POSTGRES_USER}
      DJANGO_DB_NAME: $ {DJANGO_DB_NAME}
      DJANGO_SU_NAME: $ {DJANGO_SU_NAME}
      DJANGO_SU_EMAIL: $ {DJANGO_SU_EMAIL}
      DJANGO_SU_PASSWORD: $ {DJANGO_SU_PASSWORD}
      DJANGO_SECRET_KEY: $ {DJANGO_SECRET_KEY}
[...]

Dies bringt uns zu einem umstrittenen Thema: Sollen Migrationen beim Hochfahren eines Containers automatisch angewendet werden? Einige Leute bevorzugen es auf diese Weise, was es ihnen ermöglicht, die App mit einem einzigen Befehl zu starten. Ich denke jedoch, dass sie nicht automatisch durchgeführt werden sollten, da dies zu Problemen in der Produktion führen kann. Beispielsweise wird die Web-App in einem neuen Container gestartet und führt unbeabsichtigt Migrationen gegen eine Produktionsdatenbank durch. Was ist auch, wenn wir mehrere Container der App starten? Sie würden gleichzeitig Migrationen anstoßen. Klingt für mich nach Ärger. Ich würde diesen Schritt vorziehen, um manuell zu bleiben und es einfacher zu machen, sie schnell anzuwenden, indem ein Shell-Alias ​​definiert wird.

Verschaffen Sie sich mit pgadmin einen besseren Überblick über unseren `db`-Service

Zu Entwicklungszwecken möchten wir in der Lage sein, unser Postgres-Schema zu sehen und die Daten zu untersuchen. Anstatt einen Postgres-Client zu installieren oder dazu unsere IDE zu verwenden, werden wir jetzt die Leistung von Containern nutzen, um ein Admin-Tool bereitzustellen. Tatsächlich verfügt unser db-Dienst derzeit nicht über zugeordnete Ports zu unserem lokalen Computer, sodass wir tatsächlich keine Verbindung mit einem lokalen JDBC-Client herstellen können. Fügen wir stattdessen einen pgadmin-Dienst zu unserer docker-compose.yml hinzu. Ich habe kein offizielles Bild gefunden, aber es gibt hier ein öffentliches. Da es sich nur um ein Hilfsbild handelt, werden wir es als vertrauenswürdig einstufen. Für Sachen in der Produktion wollen wir das vielleicht nicht. Die docker-compose.yml würde also im Wesentlichen so aussehen:

Auch hier müssen wir sicherstellen, dass die erforderlichen Umgebungsvariablen auch in .env angegeben sind. Jetzt ist es an der Zeit, alle Dienste erneut zu starten und unser Verwaltungstool für Postgres unter http: // localhost: 8080 zu überprüfen.

Drei separate Dienste werden als Container ausgeführt, die jeweils für ihre individuellen Zwecke erstellt wurden und nur auf bestimmte Weise miteinander interagieren können.

Mit den Anmeldeinformationen aus den Umgebungsvariablen sollten wir uns anmelden können. Klicken Sie auf "Server hinzufügen", um eine Verbindung zu unserer Datenbank herzustellen. Dies öffnet ein Modell, in dem "Name" beliebig sein kann, sagen Sie "Test". Klicken Sie dann auf die Registerkarte "Verbindung" und geben Sie "db" als Hostnamen und unsere Datenbankanmeldeinformationen ein, die in der ENV-Datei für Benutzername und Kennwort definiert sind. so zum beispiel postgres und secret. Sobald eine Verbindung zur Datenbank hergestellt wurde, können wir das Schema durchsuchen und einige Beispieldaten in der Datenbank anzeigen. Dies wird angezeigt, wenn dies nicht erfolgreich war. Schöne Tage für DB-Admins! Lassen Sie uns nun der Einfachheit halber auch den Port 5432 unseres Containers verfügbar machen, damit unsere IDE uns das Schema anzeigen kann, indem Sie auf localhost tippen.

Mit Hilfe des Tools pgadmin können wir das Schema der Datenbanken sowie die Daten in der Datenbank untersuchen.

Zu diesem Zeitpunkt haben wir bereits eine ziemlich ordentliche Entwicklungsumgebung. Der große Vorteil dieses Aufbaus ist die Modularität. Wir können Dinge zwischen lokaler Ausführung und in einem Container hin- und herbewegen, je nachdem, welche Art von Entwicklung gerade stattfindet. Wir können einen einzelnen db-Dienst starten und die lokale App mit ihm kommunizieren lassen, sofern die Ports des Containers entsprechend zugeordnet und die Datenbankeinstellungen entsprechend festgelegt sind.

Ein etwas anderes Setup, eine lokale App, die mit einer Datenbank in einem Container kommuniziert. Dies kann auch eine nützliche Entwicklungsumgebung sein, beispielsweise zum Debuggen einer lokal ausgeführten App.

Dies sollte vorerst ausreichen und es uns ermöglichen, produktiv an einer App zu arbeiten. Es gäbe noch einige weitere Dinge zu tun, aber lassen Sie uns dies für ein anderes Mal behalten. In einem anderen Abschnitt werfen wir einen Blick darauf, wie ein Reverse-Proxy wie nginx vor der App eingefügt wird und wie wir mit der Protokollierung umgehen.

Wenn Sie über meine Vorgehensweise beim Andocken einer Django-App diskutieren und debattieren möchten, können Sie gerne einen Kommentar hinterlassen oder Kontakt mit LinkedIn oder Twitter aufnehmen

Tritt unserer Community Slack bei und lies unsere wöchentlichen Faunthemen ⬇

Wenn dieser Beitrag hilfreich war, klicken Sie bitte einige Male auf die Schaltfläche zum Klicken ap, um Ihre Unterstützung für den Autor anzuzeigen! ⬇