Django in the wild: συμβουλές για την επιβίωση της ανάπτυξης

Πριν αναπτύξετε την εφαρμογή web Django στον πραγματικό κόσμο, πρέπει να βεβαιωθείτε ότι το έργο σας είναι έτοιμο για παραγωγή. Καλύτερα να αρχίσετε να τα εφαρμόζετε νωρίτερα. Σας εξοικονομεί πολύ χρόνο και πονοκέφαλο. Εδώ είναι μερικά σημεία που έμαθα στην πορεία:

  1. Χρησιμοποιήστε pipenv (ή requirements.txt + venv). Commit Pipefile και Pipefile.lock (ή requirements.txt). Ονομάστε το venv σας.
  2. Έχετε ένα σενάριο γρήγορης εκκίνησης.
  3. Γράψτε δοκιμές. Χρησιμοποιήστε το πλαίσιο δοκιμών Djangoκαι υπόθεση.
  4. Χρησιμοποιήστε το περιβάλλονκαι direnv για να διαχειριστείτε τις ρυθμίσεις σας και να φορτώσετε αυτόματα τις μεταβλητές περιβάλλοντος.
  5. Βεβαιωθείτε ότι όλοι οι προγραμματιστές πραγματοποιούν τις μετεγκαταστάσεις τους. Μετεγκαταστάσεις σκουός από καιρό σε καιρό. Επαναφέρετέ τα εάν είναι απαραίτητο. Αρχιτέκτονα το έργο σας για ομαλότερες μεταναστεύσεις Διαβάστε για μετεγκαταστάσεις.
  6. Χρησιμοποιήστε συνεχή ενσωμάτωση. Προστατέψτε τον κύριο κλάδο σας.
  7. Μεταβείτε στη λίστα ελέγχου της επίσημης ανάπτυξης του Django.
  8. Μην διαχειρίζεστε το δικό σας διακομιστή, αλλά αν πρέπει, χρησιμοποιήστε μια σωστή δομή καταλόγου και χρησιμοποιήστε το Supervisord, Gunicorn και NGINX.

Αυτή η λίστα αναπτύχθηκε οργανικά καθώς περνούσα την πρώτη μας εφαρμογή Django και δεν είναι εξαντλητική. Αλλά νομίζω ότι αυτά είναι μερικά από τα πιο σημαντικά σημεία που πρέπει να γνωρίζετε.

Διαβάστε παρακάτω για μια συζήτηση για κάθε ένα από τα σημεία.

Διαχειριστείτε τις εξαρτήσεις και τα εικονικά περιβάλλοντά σας με τον σωστό τρόπο

Εσείς και η ομάδα σας θα πρέπει να συμφωνήσετε για έναν τρόπο διαχείρισης των εξαρτήσεων και των εικονικών σας περιβαλλόντων. Σας προτείνω είτε να χρησιμοποιήσετε το pipenv, που είναι ο νέος τρόπος διαχείρισης τόσο των εικονικών σας περιβαλλόντων όσο και των εξαρτημάτων σας, ή να χρησιμοποιήσετε την καλή παλιά προσέγγιση της δημιουργίας ενός venv και να παρακολουθείτε τις εξαρτήσεις σας με ένα requirements.txtαρχείο.

Η χρήση της requirements.txtπροσέγγισης είναι επιρρεπής σε ανθρώπινο σφάλμα, καθώς οι προγραμματιστές τείνουν να ξεχνούν να ενημερώνουν τη λίστα πακέτων. Αυτό δεν είναι πρόβλημα με το pipenv, καθώς ενημερώνει αυτόματα το Pipefile. Το μειονέκτημα του pipenv είναι ότι δεν έχει περάσει αρκετά καιρό. Ακόμα κι αν συνιστάται επίσημα από το Ίδρυμα Λογισμικού Python, μπορεί να έχετε προβλήματα με το να το χρησιμοποιείτε σε ορισμένα μηχανήματα. Προσωπικά, εξακολουθώ να χρησιμοποιώ την παλιά προσέγγιση, αλλά θα χρησιμοποιήσω το pipenv για το επόμενο έργο μου.

Χρήση του venv και requirements.txt

Εάν χρησιμοποιείτε Python 6 3.6 (θα πρέπει να είστε), μπορείτε απλά να δημιουργήσετε ένα με python -m venv ENV. Βεβαιωθείτε ότι ονομάζετε το εικονικό σας περιβάλλον (αντί να το χρησιμοποιείτε .). Μερικές φορές πρέπει να διαγράψετε το εικονικό περιβάλλον σας και να το δημιουργήσετε εκ νέου. Το διευκολύνει. Επίσης, πρέπει να προσθέσετε ENVκατάλογο στο δικό σας. gitignoreαρχείο (προτιμώ το ENV όνομα αντί του venv , .env , ... αφού ξεχωρίζει όταν ls το φάκελο του έργου).

Για τη διαχείριση των εξαρτήσεων, κάθε προγραμματιστής εκτελείται pip freeze > requirements.txt κάθε φορά που εγκαθιστά ένα νέο πακέτο και προσθέτει και δεσμεύεται στο repo. Θα χρησιμοποιούν pip install -r requirements.txtόποτε τραβούν από το απομακρυσμένο αποθετήριο.

Χρήση του pipenv

Εάν χρησιμοποιείτε pipenv , απλά πρέπει να προσθέσετε Pipfile και Pipfile.lock στο repo σας.

Έχετε ένα σενάριο γρήγορης εκκίνησης

Αυτό βοηθά να διασφαλίσετε ότι οι προγραμματιστές σας ξοδεύουν όσο το δυνατόν λιγότερο χρόνο δουλεύοντας σε πράγματα που δεν σχετίζονται άμεσα με την εργασία τους.

Αυτό όχι μόνο εξοικονομεί χρόνο και χρήμα, αλλά διασφαλίζει επίσης ότι εργάζονται σε παρόμοια περιβάλλοντα (για παράδειγμα, οι ίδιες εκδόσεις Python και pip).

Επομένως, προσπαθήστε να αυτοματοποιήσετε όσο το δυνατόν περισσότερες εργασίες εγκατάστασης.

Επιπλέον, το να έχεις ένα σενάριο build ενός βήματος είναι πολύ το 2ο βήμα του Joel Test.

Εδώ είναι ένα μικρό σενάριο που χρησιμοποιώ, το οποίο σώζει τους προγραμματιστές μου αρκετές βασικές πινελιές:

#!/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

Γράψτε δοκιμές

Όλοι γνωρίζουν ότι οι δοκιμές γραφής είναι μια καλή πρακτική. Όμως πολλοί το παραβλέπουν, για χάρη, ταχύτερα, την ανάπτυξη. ΜΗΝ. Οι δοκιμές είναι απόλυτη αναγκαιότητα όσον αφορά τη σύνταξη λογισμικού που χρησιμοποιείται στην παραγωγή, ειδικά όταν εργάζεστε σε μια ομάδα. Ο μόνος τρόπος με τον οποίο μπορείτε να γνωρίζετε ότι η τελευταία ενημέρωση δεν έσπασε κάτι είναι να έχετε καλά γραπτές δοκιμές. Επίσης, χρειάζεστε απολύτως δοκιμές για συνεχή ενσωμάτωση και παράδοση του προϊόντος σας.

Το Django έχει ένα αξιοπρεπές πλαίσιο δοκιμών. Μπορείτε επίσης να χρησιμοποιήσετε πλαίσια δοκιμών βάσει ιδιοτήτων, όπως η υπόθεση, τα οποία σας βοηθούν να γράψετε πιο σύντομα, μαθηματικά αυστηρά τεστ για τον κώδικά σας. Για πολλές περιπτώσεις, η σύνταξη δοκιμών βάσει ιδιοκτησίας είναι ταχύτερη. Στην πράξη, ενδέχεται να καταλήξετε να χρησιμοποιείτε και τα δύο αυτά πλαίσια για τη σύνταξη ολοκληρωμένων, ευανάγνωστων και γραπτών τεστ.

Χρησιμοποιήστε μεταβλητές περιβάλλοντος για ρυθμίσεις

Το αρχείο settings.py είναι το σημείο όπου αποθηκεύετε όλες τις σημαντικές ρυθμίσεις έργου: τη διεύθυνση URL της βάσης δεδομένων σας, διαδρομές προς πολυμέσα και στατικούς φακέλους και ούτω καθεξής. Αυτές θα έχουν διαφορετικές τιμές στον υπολογιστή ανάπτυξης και στον διακομιστή παραγωγής σας. Ο καλύτερος τρόπος για να το αντιμετωπίσετε είναι να χρησιμοποιήσετε μεταβλητές περιβάλλοντος. Το πρώτο βήμα είναι να ενημερώσετε το settings.py για να διαβάσετε από τις μεταβλητές περιβάλλοντος, χρησιμοποιώντας το περιβάλλον :

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') ..

Για να αποφύγετε τη μη αυτόματη φόρτωση των envvars σας, ρυθμίστε το direnv στα μηχανήματα ανάπτυξης και αποθηκεύστε τους envvars σε ένα .envrcαρχείο στον κατάλογο του έργου σας. Τώρα, όποτε μπαίνετε cdστο φάκελο έργων σας, οι μεταβλητές περιβάλλοντος φορτώνονται αυτόματα. Προσθέστε .envrcστο αποθετήριο σας (εάν όλοι οι προγραμματιστές χρησιμοποιούν τις ίδιες ρυθμίσεις) και βεβαιωθείτε ότι εκτελείτε direnv allowόποτε υπάρχει αλλαγή στο .envrcαρχείο.

Μην το χρησιμοποιείτε direnvστον διακομιστή παραγωγής σας. Αντ 'αυτού, δημιουργήστε ένα αρχείο που ονομάζεται .server.envrc , προσθέστε το στο .gitignoreκαι τοποθετήστε τις ρυθμίσεις παραγωγής εκεί. Τώρα, δημιουργήστε ένα σενάριο, runinenv.shγια να .server.envrcπρομηθευτείτε αυτόματα τις μεταβλητές περιβάλλοντος , ενεργοποιήστε το εικονικό περιβάλλον και εκτελέστε την παρεχόμενη εντολή. Θα δείτε πώς χρησιμοποιείται στην επόμενη ενότητα. Δείτε πώς runinenv.shπρέπει να εμφανίζεται (σύνδεσμος προς το GitHub).

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

Αντιμετωπίστε σωστά τις μετακινήσεις σας

Οι μεταναστεύσεις του Django είναι υπέροχες, αλλά η συνεργασία μαζί τους, ειδικά σε μια ομάδα, απέχει πολύ από την ταλαιπωρία.

Πρώτον, πρέπει να βεβαιωθείτε ότι όλοι οι προγραμματιστές δεσμεύουν τα αρχεία μετεγκατάστασης. Ναι, μπορεί να (θα) καταλήξετε σε συγκρούσεις, ειδικά εάν εργάζεστε με μια μεγάλη ομάδα, αλλά είναι καλύτερο από το να έχετε ένα ασυνεπές σχήμα.

In general, dealing with migrations is not that easy. You need to know what you are doing, and you need to follow some best practices to ensure a smooth workflow.

One thing that I figured is that it usually helps if you squash the migrations from time to time (e.g. on a weekly basis). This helps with reducing the number of files and the size of the dependency graph, which in turn leads to faster build time, and usually fewer (or easier to handle) conflicts.

Sometimes it’s easier to reset your migrations and make them anew, and sometimes you need to manually fix the conflicting migrations or merge them together. In general, dealing with migrations is a topic which deserves its own post, and there are some good reads on this topic:

  • Django migrations and how to manage conflicts
  • How to Reset Migrations

Moreover, how your project’s migrations end up depends on your project architecture, your models, and so on. This is especially important when your code-base grows, when you have multiple apps, or when you have complicated relations between models.

I highly recommend you to read this great post about scaling Django apps, which covers the topic pretty well. It also has a tiny, useful migration script.

Use Continuous Integration

The idea behind CI is simple: run tests as soon as new code is pushed.

Use a solution which integrates well with your version controlling platform. I ended up using CircleCI. CI is especially helpful when you work with multiple developers, since you can be sure their code passes all the tests before they send a pull request. Again, as you can see, it’s very important to have well covered tests in place. Moreover, make sure your master branch is protected.

Deployment checklist

Django’s official website provides a handy deployment checklist. It helps you ensure your project’s security and performance. Make sure you follow through those guidelines.

If you must manage your own server…

There are many good reasons to not manage your own server. Docker gives you more portability and security, and serverless architectures, such as AWS Lambda, can provide you with even more benefits for less money.

But there are cases where you need more control over your server (if you need more flexibility, if you have services cannot work with containerized apps — such as security monitoring agents, and so on).

Use a proper directory structure

The first thing to do is to use a proper folder structure. If all you want to serve on your server is the Django app, you can simple clone your repository and use that as your main directory. But that’s rarely the case: usually you also need to have some static pages (home page, contacts, …). They should be separate from your Django code base.

A good way to go about it is to create a parent repository, which has different parts of your project as submodules. Your Django developers work on a django repository, your designers work on the homepage repository, … and you integrate all of them in a 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

Σημειώστε πώς χρησιμοποιούμε runinenv.shεδώ (γραμμές 14, 24 και 34). Επίσης, δώστε προσοχή στη γραμμή 14, όπου λέμε στον gunicorn να αποστείλει 3 εργαζόμενους. Αυτός ο αριθμός εξαρτάται από τον αριθμό των πυρήνων που διαθέτει ο διακομιστής σας. Ο συνιστώμενος αριθμός εργαζομένων είναι: 2 * number_of_cores + 1.

Χρειάζεστε επίσης έναν αντίστροφο διακομιστή μεσολάβησης για να συνδέσετε τον διακομιστή εφαρμογών σας με τον εξωτερικό κόσμο. Απλώς χρησιμοποιήστε το NGINX, καθώς διαθέτει μια ευρεία βάση χρηστών και είναι πολύ εύκολο να διαμορφώσετε (μπορείτε επίσης να βρείτε αυτόν τον κωδικό στο 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; }

Αποθηκεύστε το αρχείο διαμόρφωσης /etc/nginx/sites-availableκαι δημιουργήστε έναν συμβολικό σύνδεσμο σε αυτό /etc/nginx/sites-enabled.

Ελπίζω να βρείτε χρήσιμη αυτήν την ανάρτηση. Παρακαλώ επιτρέψτε μου να ξέρω τι σκέφτεστε γι 'αυτό και να το δείξετε ❤ αν θέλετε.