Django in freier Wildbahn: Tipps für das Überleben des Einsatzes

Bevor Sie Ihre Django-Webanwendung in der realen Welt bereitstellen, müssen Sie sicherstellen, dass Ihr Projekt produktionsbereit ist. Beginnen Sie besser früher mit der Implementierung. Das spart Ihnen und Ihrem Team viel Zeit und Kopfschmerzen. Hier sind einige Punkte, die ich auf meinem Weg gelernt habe:

  1. Verwenden Sie pipenv (oder require.txt + venv). Übernehmen Sie Pipefile und Pipefile.lock (oder die Anforderungen.txt). Nennen Sie Ihren Venv.
  2. Haben Sie ein Schnellstart-Skript.
  3. Schreiben Sie Tests. Verwenden Sie das Django-Testframeworkund Hypothese.
  4. Verwenden Sie die Umgebungund direnv, um Ihre Einstellungen zu verwalten und Ihre Umgebungsvariablen automatisch zu laden.
  5. Stellen Sie sicher, dass alle Entwickler ihre Migrationen festschreiben. Squash-Migrationen von Zeit zu Zeit. Setzen Sie sie gegebenenfalls zurück. Entwickeln Sie Ihr Projekt für reibungslosere Migrationen. Lesen Sie mehr über Migrationen.
  6. Verwenden Sie die kontinuierliche Integration. Schützen Sie Ihren Hauptzweig.
  7. Sehen Sie sich die offizielle Einsatz-Checkliste von Django an.
  8. Verwalten Sie nicht Ihren eigenen Server, aber wenn Sie müssen, verwenden Sie eine geeignete Verzeichnisstruktur und verwenden Sie Supervisord, Gunicorn und NGINX.

Diese Liste ist organisch gewachsen, als ich die Veröffentlichung unserer ersten Django-App durchlief, und ist nicht vollständig. Aber ich denke, dies sind einige der wichtigsten Punkte, die Sie beachten müssen.

Lesen Sie mit, um eine Diskussion zu jedem der Punkte zu erhalten.

Verwalten Sie Ihre Abhängigkeiten und virtuellen Umgebungen richtig

Sie und Ihr Team sollten sich darauf einigen, wie Sie Ihre Abhängigkeiten und virtuellen Umgebungen verwalten können. Ich empfehle entweder die Verwendung von pipenv, der neuen Methode zum Verwalten Ihrer virtuellen Umgebungen und Abhängigkeiten, oder den guten alten Ansatz, ein venv zu erstellen und Ihre Abhängigkeiten mit einer requirements.txtDatei zu verfolgen .

Die Verwendung des requirements.txtAnsatzes ist anfällig für menschliches Versagen, da Entwickler die Aktualisierung der Paketliste häufig vergessen. Dies ist kein Problem mit pipenv, da Pipefile automatisch aktualisiert wird. Der Nachteil von pipenv ist, dass es nicht lange genug existiert. Obwohl es offiziell von der Python Software Foundation empfohlen wird, können Probleme auftreten, wenn es auf einigen Computern ausgeführt wird. Persönlich verwende ich immer noch den alten Ansatz, aber ich werde pipenv für mein nächstes Projekt verwenden.

Verwenden von venv und require.txt

Wenn Sie Python ≥ 3.6 verwenden (sollten Sie sein), können Sie einfach eine mit erstellen python -m venv ENV. Stellen Sie sicher, dass Sie Ihre virtuelle Umgebung benennen (anstatt zu verwenden .). Manchmal müssen Sie Ihre virtuelle Umgebung löschen und neu erstellen. Es macht es einfacher. Außerdem sollten Sie ENVIhrem Verzeichnis ein Verzeichnis hinzufügen . gitignoreDatei (Ich ziehe die ENV - Namen anstelle von Venv , .env , ... da steht es, wenn ich ls die Projektordner).

Um die Abhängigkeiten zu verwalten, wird jeder Entwickler ausgeführt, pip freeze > requirements.txt wenn er ein neues Paket installiert und es dem Repo hinzufügt und festschreibt. Sie werden pip install -r requirements.txtimmer dann verwendet, wenn sie aus dem Remote-Repository abgerufen werden.

Mit pipenv

Wenn Sie pipenv verwenden , müssen Sie Ihrem Repo nur Pipfile und Pipfile.lock hinzufügen.

Haben Sie ein Schnellstart-Skript

Auf diese Weise stellen Sie sicher, dass Ihre Entwickler so wenig Zeit wie möglich damit verbringen, an Dingen zu arbeiten, die nicht direkt mit ihrem Job zusammenhängen.

Dies spart nicht nur Zeit und Geld, sondern stellt auch sicher, dass alle in ähnlichen Umgebungen arbeiten (z. B. dieselben Versionen von Python und pip).

Versuchen Sie also, so viele Setup-Aufgaben wie möglich zu automatisieren.

Darüber hinaus geht es in Joel Tests zweitem Schritt sehr darum, ein einziges Schritt-Build-Skript zu haben.

Hier ist ein kleines Skript, das ich verwende und das meinen Entwicklern einige Tastenanschläge erspart:

#!/bin/bash python3.6 -m venv ENV source ENV/bin/activate pip install --upgrade pip pip install -r requirements.txt source .envrc python ./manage.py migrate python ./manage.py loaddata example-django/fixtures/quickstart.json python ./manage.py runserver

Schreiben Sie Tests

Jeder weiß, dass das Schreiben von Tests eine gute Praxis ist. Aber viele übersehen es, um eine schnellere Entwicklung zu erreichen. NICHT. Tests sind eine absolute Notwendigkeit, wenn es darum geht, in der Produktion verwendete Software zu schreiben, insbesondere wenn Sie in einem Team arbeiten. Der einzige Weg, um zu wissen, dass das neueste Update nichts kaputt gemacht hat, sind gut geschriebene Tests. Außerdem benötigen Sie unbedingt Tests für die kontinuierliche Integration und Lieferung Ihres Produkts.

Django hat ein anständiges Test-Framework. Sie können auch eigenschaftsbasierte Test-Frameworks wie Hypothesis verwenden, mit denen Sie kürzere, mathematisch strenge Tests für Ihren Code schreiben können. In vielen Fällen ist das Schreiben von eigenschaftsbasierten Tests schneller. In der Praxis verwenden Sie möglicherweise beide Frameworks, um umfassende, einfach zu lesende und zu schreibende Tests zu schreiben.

Verwenden Sie Umgebungsvariablen für Einstellungen

In Ihrer Datei settings.py speichern Sie alle wichtigen Projekteinstellungen: Ihre Datenbank-URL, Pfade zu Medien und statischen Ordnern usw. Diese haben unterschiedliche Werte auf Ihrer Entwicklungsmaschine und Ihrem Produktionsserver. Der beste Weg, dies zu beheben, ist die Verwendung von Umgebungsvariablen. Der erste Schritt besteht darin, Ihre settings.py zu aktualisieren, um mithilfe von environ aus den Umgebungsvariablen zu lesen :

import environ import os root = environ.Path(__file__) - 2 # two folders back (/a/b/ - 2 = /) env = environ.Env(DEBUG=(bool, False),) # set default values and casting GOOGLE_ANALYTICS_ID=env('GOOGLE_ANALYTICS_ID') SITE_DOMAIN = env('SITE_DOMAIN') SITE_ROOT = root() DEBUG = env('DEBUG') # False if not in os.environ DATABASES = { 'default': env.db(), # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ } public_root = root.path('./public/') MEDIA_ROOT = public_root('media') MEDIA_URL = '/media/' STATIC_ROOT = public_root('static') STATIC_URL = '/static/' AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY') ..

Um zu vermeiden, dass Ihre Envvars manuell geladen werden, richten Sie direnv auf Ihren Entwicklungscomputern ein und speichern Sie die Envvars in einer .envrcDatei in Ihrem Projektverzeichnis. Immer wenn Sie sich cdin Ihrem Projektordner befinden, werden die Umgebungsvariablen automatisch geladen. Fügen Sie .envrces Ihrem Repository hinzu (wenn alle Entwickler dieselben Einstellungen verwenden) und stellen Sie sicher, dass Sie es ausführen, direnv allowwenn sich die .envrcDatei ändert .

Nicht direnvauf Ihrem Produktionsserver verwenden. Erstellen Sie stattdessen eine Datei mit dem Namen .server.envrc , fügen Sie sie der hinzu .gitignoreund legen Sie die Produktionseinstellungen dort ab. Erstellen Sie nun ein Skript, aus runinenv.shdem die Umgebungsvariablen automatisch bezogen werden .server.envrc, aktivieren Sie die virtuelle Umgebung und führen Sie den angegebenen Befehl aus. Wie es verwendet wird, erfahren Sie im nächsten Abschnitt. So runinenv.shsollte es aussehen (Link zu GitHub).

#!/bin/bash WORKING_DIR=/home/myuser/example.com/example-django cd ${WORKING_DIR} source .server.envrc source ENV/bin/activate exec [email protected]

Behandeln Sie Ihre Migrationen ordnungsgemäß

Django-Migrationen sind großartig, aber die Zusammenarbeit mit ihnen, insbesondere in einem Team, ist alles andere als problemlos.

Stellen Sie zunächst sicher, dass alle Entwickler die Migrationsdateien festschreiben. Ja, es kann (wird) zu Konflikten kommen, insbesondere wenn Sie mit einem großen Team arbeiten, aber es ist besser als ein inkonsistentes Schema.

Im Allgemeinen ist der Umgang mit Migrationen nicht so einfach. Sie müssen wissen, was Sie tun, und Sie müssen einige Best Practices befolgen, um einen reibungslosen Workflow sicherzustellen.

Eine Sache, die ich mir gedacht habe, ist, dass es normalerweise hilft, wenn Sie die Migrationen von Zeit zu Zeit (z. B. wöchentlich) unterdrücken. Dies hilft bei der Reduzierung der Anzahl der Dateien und der Größe des Abhängigkeitsdiagramms, was wiederum zu einer schnelleren Erstellungszeit und normalerweise weniger (oder einfacher zu handhabenden) Konflikten führt.

Manchmal ist es einfacher, Ihre Migrationen zurückzusetzen und neu zu erstellen, und manchmal müssen Sie die in Konflikt stehenden Migrationen manuell beheben oder zusammenführen. Im Allgemeinen ist der Umgang mit Migrationen ein Thema, das einen eigenen Beitrag verdient, und es gibt einige gute Lektüren zu diesem Thema:

  • Django-Migrationen und Umgang mit Konflikten
  • So setzen Sie Migrationen zurück

Darüber hinaus hängt die Art und Weise, wie die Migrationen Ihres Projekts enden, von Ihrer Projektarchitektur, Ihren Modellen usw. ab. Dies ist besonders wichtig, wenn Ihre Codebasis wächst, wenn Sie mehrere Apps haben oder wenn Sie komplizierte Beziehungen zwischen Modellen haben.

Ich empfehle Ihnen dringend, diesen großartigen Beitrag über die Skalierung von Django-Apps zu lesen, der das Thema ziemlich gut abdeckt. Es hat auch ein kleines, nützliches Migrationsskript.

Verwenden Sie die kontinuierliche Integration

Die Idee hinter CI ist einfach: Führen Sie Tests aus, sobald neuer Code gepusht wird.

Verwenden Sie eine Lösung, die sich gut in Ihre Versionskontrollplattform integrieren lässt. Am Ende habe ich CircleCI verwendet. CI ist besonders hilfreich, wenn Sie mit mehreren Entwicklern arbeiten, da Sie sicher sein können, dass deren Code alle Tests besteht, bevor sie eine Pull-Anfrage senden. Wie Sie sehen, ist es wiederum sehr wichtig, gut abgedeckte Tests durchzuführen. Stellen Sie außerdem sicher, dass Ihre Hauptniederlassung geschützt ist.

Checkliste für die Bereitstellung

Die offizielle Website von Django bietet eine praktische Checkliste für die Bereitstellung. Es hilft Ihnen, die Sicherheit und Leistung Ihres Projekts zu gewährleisten. Stellen Sie sicher, dass Sie diese Richtlinien befolgen.

Wenn Sie Ihren eigenen Server verwalten müssen ...

Es gibt viele gute Gründe, Ihren eigenen Server nicht zu verwalten. Docker bietet Ihnen mehr Portabilität und Sicherheit, und serverlose Architekturen wie AWS Lambda bieten Ihnen noch mehr Vorteile für weniger Geld.

Es gibt jedoch Fälle, in denen Sie mehr Kontrolle über Ihren Server benötigen (wenn Sie mehr Flexibilität benötigen, wenn Sie Dienste haben, die nicht mit containerisierten Apps funktionieren können - wie z. B. Sicherheitsüberwachungsagenten usw.).

Verwenden Sie eine geeignete Verzeichnisstruktur

Als erstes müssen Sie eine geeignete Ordnerstruktur verwenden. Wenn Sie auf Ihrem Server nur die Django-App bereitstellen möchten, können Sie Ihr Repository einfach klonen und als Hauptverzeichnis verwenden. Dies ist jedoch selten der Fall: Normalerweise benötigen Sie auch einige statische Seiten (Startseite, Kontakte usw.). Sie sollten von Ihrer Django-Codebasis getrennt sein.

Ein guter Weg, dies zu tun, besteht darin, ein übergeordnetes Repository zu erstellen, das verschiedene Teile Ihres Projekts als Submodule enthält. Ihre Django-Entwickler arbeiten an einem Django-Repository, Ihre Designer arbeiten am Homepage-Repository,… und Sie integrieren alle in ein Repository:

example.com/ example-django/ homepage/

Use Supervisord, NGINX, and Gunicorn

Sure, manage runserver works, but only for a quick test. For anything serious, you need to use a proper application server. Gunicorn is the way to go.

Keep in mind that any application server is a long-running process. And you need to make sure that it keeps running, is automatically restarted after a server failure, logs errors properly, and so on. For this purpose, we use supervisord.

Supervisord needs a configuration file, in which we tell how we want our processes to run. And it is not limited to our application server. If we have other long-running processes (e.g. celery), we should defined them in /etc/supervisor/supervisord.conf. Here is an example (also on GitHub):

[supervisord] nodaemon=true logfile=supervisord.log [supervisorctl] [inet_http_server] port = 127.0.0.1:9001 [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [program:web-1] command=/home/myuser/example.com/example-django/runinenv.sh gunicorn example.wsgi --workers 3 --reload --log-level debug --log-file gunicorn.log --bind=0.0.0.0:8000 autostart=true autorestart=true stopsignal=QUIT stdout_logfile=/var/log/example-django/web-1.log stderr_logfile=/var/log/example-django/web-1.error.log user=myuser directory=/home/myuser/example.com/example-django [program:celery-1] command=/home/myuser/example.com/example-django/runinenv.sh celery worker --app=example --loglevel=info autostart=true autorestart=true stopsignal=QUIT stdout_logfile=/var/log/example-django/celery-1.log stderr_logfile=/var/log/example-django/celery-1.error.log user=myuser directory=/home/myuser/example.com/example-django [program:beat-1] command=/home/myuser/example.com/example-django/runinenv.sh celery beat --app=example --loglevel=info autostart=true autorestart=true stopsignal=QUIT stdout_logfile=/var/log/example-django/beat-1.log stderr_logfile=/var/log/example-django/beat-1.error.log user=myuser directory=/home/myuser/example.com/example-django [group:example-django] programs=web-1,celery-1,beat-1

Note how we use runinenv.sh here (lines 14, 24 and 34). Also, pay attention to line 14, where we tell gunicorn to dispatch 3 workers. This number depends on the number of cores your server has. The recommended number of workers is: 2*number_of_cores + 1.

You also need a reverse proxy to connect your application server to the outside world. Just use NGINX, since it has a wide user base, and it’s very easy to configure (you can also find this code on GitHub):

server { server_name www.example.com; access_log /var/log/nginx/example.com.log; error_log /var/log/nginx/example.com.error.log debug; root /home/myuser/example.com/homepage; sendfile on; # if the uri is not found, look for index.html, else pass everthing to gunicorn location / { index index.html; try_files $uri $uri/ @gunicorn; } # Django media location /media { alias /home/myuser/example.com/example-django/public/media; # your Django project's media files } # Django static files location /static { alias /home/myuser/example.com/example-django/public/static; # your Django project's static files } location @gunicorn { proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; #proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_redirect off; proxy_pass //0.0.0.0:8000; } client_max_body_size 100M; listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { server_name example.com; listen 443 ssl; ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem; # managed by Certbot return 301 //www.example.com$request_uri; } server { if ($host = www.example.com) { return 301 //$host$request_uri; } # managed by Certbot if ($host = example.com) { return 301 //$host$request_uri; } # managed by Certbot listen 80 default_server; listen [::]:80 default_server; server_name example.com www.example.com; return 301 //$server_name$request_uri; }

Store the configuration file in /etc/nginx/sites-available, and create a symbolic link to it in /etc/nginx/sites-enabled.

I hope you’ve found this post helpful. Please let me know what you think about it, and show it some ❤ if you will.