Django في البرية: نصائح للبقاء على قيد الحياة

قبل نشر تطبيق الويب Django الخاص بك في العالم الحقيقي ، تحتاج إلى التأكد من أن مشروعك جاهز للإنتاج. من الأفضل البدء في تنفيذها في وقت مبكر. يوفر عليك أنت وفريقك الكثير من الوقت والصداع. فيما يلي بعض النقاط التي تعلمتها على طول الطريق:

  1. استخدم pipenv (أو requirements.txt + venv). قم بتنفيذ 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 Software Foundation موصى بها رسميًا ، فقد تواجه مشكلات في تشغيلها على بعض الأجهزة. أنا شخصياً ما زلت أستخدم النهج القديم ، لكنني سأستخدم pipenv لمشروعي التالي.

استخدام ملف venv و requirements.txt

إذا كنت تستخدم Python ≥ 3.6 (يجب أن تكون كذلك) ، فيمكنك ببساطة إنشاء واحد باستخدام python -m venv ENV. تأكد من تسمية البيئة الافتراضية الخاصة بك (بدلاً من استخدام .). تحتاج أحيانًا إلى حذف بيئتك الافتراضية وإنشائها من جديد. يجعل الأمر أسهل. أيضًا ، يجب عليك إضافة ENVدليل إلى ملف. gitignoreملف (انا افضل ENV اسم بدلا من venv ، .env ، ... لأنها تبرز عندما LS مجلد المشروع).

لإدارة التبعيات ، يعمل كل مطور pip freeze > requirements.txt كلما قاموا بتثبيت حزمة جديدة ، وإضافتها والتزامها في الريبو. سيستخدمون pip install -r requirements.txtعندما ينسحبون من المستودع البعيد.

باستخدام pipenv

إذا كنت تستخدم pipenv ، فأنت تحتاج فقط إلى إضافة Pipfile و Pipfile.lock إلى الريبو الخاص بك.

لديك نص سريع البدء

يساعد هذا في التأكد من أن المطورين لديك يقضون أقل وقت ممكن في العمل على أشياء لا تتعلق مباشرة بوظائفهم.

لا يوفر هذا الوقت والمال فحسب ، بل يتأكد أيضًا من أنهم جميعًا يعملون في بيئات مماثلة (نفس إصدارات Python و pip ، على سبيل المثال).

لذا ، حاول أتمتة أكبر عدد ممكن من مهام الإعداد.

علاوة على ذلك ، فإن وجود نص بناء بخطوة واحدة هو إلى حد كبير ما تدور حوله الخطوة الثانية لـ 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 لديه إطار اختبار لائق. يمكنك أيضًا استخدام أطر عمل اختبار قائمة على الخصائص ، مثل Hypothesis ، والتي تساعدك على كتابة اختبارات رياضية أقصر وصارمة لشفرتك البرمجية. في كثير من الحالات ، تكون كتابة الاختبارات القائمة على الخصائص أسرع. في الممارسة العملية ، قد ينتهي بك الأمر باستخدام هذين الإطارين لكتابة اختبارات شاملة وسهلة القراءة والكتابة.

استخدم متغيرات البيئة للإعدادات

بك 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الملف.

Don’t use direnv on your production server. Instead, create a file called .server.envrc, add it to the .gitignore, and put the production settings there. Now, create a script,runinenv.sh, to automatically source the environment variables from .server.envrc, activate the virtual environment, and run the provided command. You will see how it is used in the next section. Here is how runinenv.sh should look (link to GitHub).

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

Handle your migrations properly

Django migrations are great, but working with them, especially in a team, is far from hassle-free.

First, you should make sure that all developers commit the migration files. Yes, you might (will) end up having conflicts, especially if you work with a large team, but it’s better than having an inconsistent schema.

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.

آمل أن تكون قد وجدت هذه الوظيفة مفيدة. واسمحوا لي أن أعرف ما هو رأيك في ذلك ، وأظهر له بعض ❤ إذا كنت تريد.