اذهب إلى المحتوى

إعداد تطبيق جانغو جاهز للنشر مع قاعدة بيانات Postgres وخادم Nginx و Gunicorn


رشا سعد

جانغو هو أشهر أطر العمل البرمجية المكتوبة بلغة بايثون، إذ يساعدك هذا الإطار المميز على تطوير تطبيقات الويب ووضعها في الخدمة بسرعة ويُسر، ويحتوي جانغو خادم تطوير مُضمّن يتيح لك اختبار الكود محليًا والتأكد من عمله إلّا أنه مبسط يلبي أغراض التجربة فقط، فأي تطبيق يُطلق في بيئة العمل الفعلية مهما بلغت بساطته يتطلب خادم ويب بمواصفات أعلى وحماية أكبر.

سنُحاكي في هذا المقال بيئة عمل كاملة لتطبيق جانغو مبنية على خادم أوبونتو إصدار 20.04، ونعرض كيفية تثبيت وإعداد المكونات اللازمة لعمله على الخادم، بدءًا من بناء قاعدة بيانات PostgreSQL بدلًا من SQLite (قاعدة بيانات جانغو الافتراضية)، ومن ثم إعداد خادم التطبيق من نوع Gunicorn، وصولًا إلى تهيئة خادم وكيل عكسي من نوع Nginx يعمل أمام Gunicorn ويقدم لتطبيقنا كل ما يملكه من مميزات تدعم أمانه وأدائه.

متطلبات بيئة العمل والأهداف

ستحتاج إلى خادم بنظام تشغيل أوبونتو وقد استعملنا في هذا المقال الإصدار 20.04، مع مستخدم –ليس مسؤول- ولكنه يتمتع بصلاحيات عالية sudo، بالإضافة إلى تفعيل جدار الحماية الافتراضي على الخادم، ويمكنك الاستعانة بمقال التهيئة الأولية لخادم أوبونتو.

سنثبت جانغو في بيئةٍ افتراضية لما تقدمه من مرونة تتيح لنا التعامل مع التطبيق ومتطلباته الخارجية بطريقة منفصلة أي كل على حدة، وسنبدأ بإعداد قاعدة البيانات وبمجرد الانتهاء منها وتشغيل التطبيق، سنثبت Gunicorn ليتعامل مع طلبات العملاء الواردة عبر HTTP ويحولها إلى أوامر بايثون يعالجها التطبيق، ومن ثم سنشغل خادم وسيط عكسي هو Nginx أمام Gunicorn للاستفادة من قدراته العالية في التعامل الفعال والآمن مع حركة البيانات الواردة إلى التطبيق.

لنبدأ التطبيق العملي!

تثبيت الحزم اللازمة من مستودعات أوبونتو

سنُنثبت معظم المكونات اللازمة للعمل من مستودعات أوبونتو، ومن ثم سنستخدم pip مدير حزم بايثون لتثبيت مكونات أخرى خاصة بالتطبيق.

البداية دومًا مع تحديث apt دليل الحزم المحلي لأوبونتو، ومن بعدها سنبدأ بتحميل الحزم اللازمة وتثبيتها بما يتوافق مع إصدار بايثون الذي تستخدمه فالأوامر تختلف قليلًا بين بايثون 2 و 3، علمًا أن جانغو 1.11 هو أحدث إصدار من الإصدارات التي تدعم بايثون 2، لذا ننصحك بترقية إطار العمل الذي تعتمده واستخدام بايثون 3 إن كنت تنشئ مشروعًا جديدًا.

إن كنت تستخدم بايثون 3 اكتب الأوامر:

sudo apt update
sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl

أما أوامر بايثون 2 فهي:

sudo apt update
sudo apt install python-pip python-dev libpq-dev postgresql postgresql-contrib nginx curl

ثبتنا بموجب الأوامر السابقة pip وملفات تطوير جانغو اللازمة لبناء Gunicorn لاحقًا و نظام قواعد البيانات Postgres مع المكاتب اللازمة للتفاعل معه وأخيرًا خادم الويب Nginx.

إنشاء قاعدة البيانات PostgreSQL ومستخدم التطبيق

يمتلك نظام قواعد بيانات Postgres ميزةً افتراضية تسمى توثيق النِّدّ Peer Authentication وهي خاصة بمصادقة الاتصالات المحلية، وتعني أن وجود مستخدم في نظام التشغيل يطابق اسمه اسم مستخدم Postgres فعال، يمنح هذا المستخدم -أي مستخدم نظام التشغيل- القدرة على الدخول إلى Postgres دون الحاجة إلى توثيق.

وقد أُنشئ مستخدم لنظام التشغيل اسمه postgres ليتوافق مع مستخدم postgres المدير لنظام PostgreSQL، وسنستفيد من هذا المستخدم للمهام الإدارية، مع إمكانية استخدام sudo وتمرير اسم المستخدم بعد الراية u-.

سجل الدخول إلى Postgres عبر الأمر التالي:

sudo -u postgres psql

ستظهر لك نافذة أوامر PostgreSQL، اكتب فيها تعليمة إنشاء قاعدة بيانات المشروع:

CREATE DATABASE myproject;

تنويه: تذكر أن تنهي كافة أوامر Postgres بفاصلة منقوطة لتضمن التنفيذ السليم.

أنشئ بعدها مستخدم قاعدة البيانات واختر له كلمة سر قوية، وفق الأمر التالي:

CREATE USER myprojectuser WITH PASSWORD 'password';

اضبط الآن القيم الافتراضية لمحددات اتصال المستخدم المنشأ بالتوافق مع توصيات جانغو بحيث لا يتطلب الأمر الاستعلام عنها وإعادة تعيينها مع كل تأسيس جديد للاتصال ما سينعكس إيجابًا على تسريع عمليات قاعدة البيانات، والمحددات المطلوب ضبطها هي: الترميز encoding الذي يأخذ القيمة الافتراضية UTF-8، وأسلوب عزل العملية transaction isolation scheme الذي يوضع "read committed" ومهمته منع قراءة العمليات غير المكتملة uncommitted transactions، وأخيرًا المنطقة الزمنية نثبتها على UTC، وفق التعليمات التالية:

ALTER ROLE myprojectuser SET client_encoding TO 'utf8';
ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE myprojectuser SET timezone TO 'UTC';

وأخيرًا امنح مستخدم قاعدة البيانات المنشأ صلاحيات الإدارة الكاملة لقاعدة البيانات:

GRANT ALL PRIVILEGES ON DATABASE myproject TO myprojectuser;

يمكنك الآن الخروج من موجه أوامر PostgreSQL بكتابة الأمر:

\q

إنشاء بيئة بايثون الافتراضية

بعد أن جهزنا قاعدة بيانات التطبيق سننتقل الآن لتثبيت كافة متطلبات بايثون اللازمة له ضمن بيئة افتراضية سهلة الإدارة، ولكن أولًا علينا تثبيت virtualenv بعد تحديث pip، ستختلف أوامر التثبيت اختلافًا بسيطًا بين بايثون 2 وبايثون 3.

إن كنت تستخدم بايثون 3 اكتب الأوامر التالية:

sudo -H pip3 install --upgrade pip
sudo -H pip3 install virtualenv

أما لبايثون 2 فاكتب:

sudo -H pip install --upgrade pip
sudo -H pip install virtualenv

أنشئ مجلدًا باسم مشروعك، ثم انتقل إلى داخله:

mkdir ~/myprojectdir
cd ~/myprojectdir

وأنشئ البيئة الافتراضية ضمنه بكتابة الأمر التالي:

virtualenv myprojectenv

بموجب الأوامر السابقة، سيُنشَأ ضمن مجلد مشروعك myprojectdir مجلدًا فرعيًا للبيئة الافتراضية اسمه myprojectenv، وسيُثبت بداخله نسخة محلية من بايثون ومدير الحزم pip محلي.

تؤمن هذه البيئة الافتراضية بيئة بايثون معزولة لمشروعنا ولكنها تحتاج لتفعيل قبل البدء باستخدامها وتنزيل ما يلزم ضمنها، والتفعيل وفق الأمر التالي:

source myprojectenv/bin/activate

سيتغير بعدها محث سطر الأوامر ليشير إلى أنك تعمل في البيئة الافتراضية وسيبدو مثل:

(myprojectenv)user@host:~/myprojectdir$

يمكنك الآن باستخدام pip تثبيت جانغو و Gunicorn و psycopg2 الخاص بالتخاطب مع قاعدة البيانات التي بنيناها:

تنويه: يُستخدم pip دومًا داخل البيئة الافتراضية وليس pip3 بغض النظر عن إصدار بايثون المعتمد.

pip install django gunicorn psycopg2-binary

يفترض الآن تثبيت كل المتطلبات اللازمة لبدء مشروع جانغو Django جديد.

إنشاء وإعداد مشروع جانغو جديد

يمكننا الآن إنشاء ملفات المشروع بعد أن أعددنا متطلبات بايثون اللازمة له ضمن البيئة الافتراضية.

إنشاء مشروع جانغو Django

لدينا بالفعل مجلدًا لمشروعنا هو myprojectdir ولذا سنكتبه في أمر إنشاء المشروع حتى يثبت جانغو الملفات ضمنه بدلًا من اختياره مجلدًا آخر تلقائيًا، وعند تنفيذ الأمر سيُنشئ مجلدًا فرعيًا ضمن مجلد المشروع يحمل نفس الاسم ليحتوي على الشيفرة البرمجية أو كود التطبيق ويضاف فيه أيضًا السكربت الخاص بالإدارة:

django-admin startproject myproject ~/myprojectdir

وبناءً على ذلك سيتضمن مجلد المشروع (وهو في حالتنا myprojectdir/~) العناصر التالية:

  • myprojectdir/manage.py/~: سكربت الإدارة الخاص بجانغو.
  • /myprojectdir/myproject/~: حزمة مشروع جانغو، ويجب أن تتضمن الملفات التالية:
  • ‎init__.py__
  • settings.py
  • urls.py
  • asgi.py
  • wsgi.py
  • /myprojectdir/myprojectenv/~: مجلد البيئة الافتراضية المنشأة في الخطوة السابقة.

ضبط إعدادات مشروع جانغو

سنعمل في هذه الفقرة على ثلاث إعدادات هي قائمة العناوين وأسماء النطاقات المسموح باستخدامها للاتصال مع جانغو، وقاعدة البيانات المعتمدة، ومسار ملفات التطبيق الساكنة.

افتح ملف الإعدادات باستعمال محرر النصوص لنبدأ بهذه التعديلات بالترتيب:

nano ~/myprojectdir/myproject/settings.py

ابحث عن المحدد ALLOWED_HOSTS إذ سنبدأ تعديلاتنا به، ويحميك هذا المحدد من الاتصالات غير المشروعة، ووظيفته تعريف الخوادم المسموح باستخدامها للاتصال بالتطبيق، وتُعرف باستخدام عنوان IP الخادم أو اسم النطاق أو اسم النطاق الفرعي، وأي طلب يرد للتطبيق يحمل في ترويسته اسم مضيف غير معرف ضمن هذا المحدد فسيُمنع.

تُكتب قائمة عناوين IP أو أسماء النطاقات المسموح لها بالاتصال ضمن الأقواس المربعة، وكل عنوان أو اسم منها ضمن اشارتي اقتباس مفردتين، ويفصل بين كل قيمة وأخرى فاصلة، ولو رغبت بالإشارة إلى النطاق الرئيس وكل ما يحوي من نطاقات فرعية فاستخدم النقطة قبل اسم النطاق، انظر الأمثلة التالية لمزيد من الإيضاح:

. . .
# The simplest case: just add the domain name(s) and IP addresses of your Django server
# ALLOWED_HOSTS = [ 'example.com', '203.0.113.5']
# To respond to 'example.com' and any subdomains, start the domain with a dot
# ALLOWED_HOSTS = ['.example.com', '203.0.113.5']
ALLOWED_HOSTS = ['your_server_domain_or_IP', 'second_domain_or_IP', . . ., 'localhost']

تنويه: تأكد من إضافة الخادم المحلي localhost على قائمة الأجهزة المسموح لها بالاتصال إذ إننا سننشئ عليه وكيل محلي Nginx.

المحدد التالي للضبط هو DATABASES الخاص بقواعد البيانات، ابحث عنه ضمن ملف الإعدادات وسنُعدّله ليناسب قاعدة بيانات PostgreSQL التي أعددناها بدلًا من SQLite قاعدة البيانات الافتراضية لجانغو.

تتمثل تعديلاتنا عليه في جعل جانغو يستخدم المحول psycopg2 (سبق أن ثبتناه باستخدام pip) ليناسب التخاطب مع قاعدة البيانات الجديدة PostgreSQL، ومن ثم كتابة اسم قاعدة البيانات الصحيح واسم مستخدم قاعدة البيانات وكلمة المرور الخاصة به، وأيضًا تحديد مضيف قاعدة البيانات وهو في حالتنا الخادم المحلي، أما بالنسبة لبوابة الاتصال مع قاعدة البيانات port فيمكنك تركها فارغة، انظر التعليمات أدناه:

. . .
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'myproject',
        'USER': 'myprojectuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}
. . .

أما التعديل الأخير على الملف settings.py فهو إضافة مسار ملفات التطبيق الساكنة، وهذا التعديل ضروري ليتمكن Nginx من معرفة مكان هذه الملفات والتعامل مع الطلبات الواردة بخصوصها، انتقل الآن إلى نهاية الملف واكتب الأسطر التالية التي ستحدد مكان وجود هذه الملفات ويُشار إليه بـ static:

. . .
STATIC_URL = '/static/'
import os
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

والآن احفظ الملف وأغلقه.

إكمال الإعداد الأولي للمشروع

بعد أن ضبطنا إعدادات قاعدة البيانات يمكننا الآن تهجير مخطط قاعدة البيانات الأولي إلى قاعدة بيانات PostgreSQL المعتمدة في مشروعنا وذلك باستخدام سكريبت الإدارة:

~/myprojectdir/manage.py makemigrations
~/myprojectdir/manage.py migrate

ومن بعدها سننشئ حساب المستخدم المسؤول عن إدارة التطبيق بكتابة الأمر التالي:

~/myprojectdir/manage.py createsuperuser

وسيُطلب منك النظام إدخال اسم مستخدم لهذا الحساب وكلمة مرور وعنوان بريد إلكتروني.

أما الآن سننفذ عملية جمع المكونات الساكنة للتطبيق static assets في المسار الذي حددناه في الفقرة السابقة وذلك وفق الأمر التالي:

~/myprojectdir/manage.py collectstatic

سيُطلب منك تأكيد العملية، وبعدها ستُجمع الملفات الساكنة ضمن مجلد مشروعك في المكان الذي أطلقنا عليه اسم static.

كما ورد في متطلبات بيئة العمل في بداية المقال فإن الخادم أوبونتو يجب أن يتضمن جدار ناري فعال هو الجدار الناري الأساسي للينكس UFW (يسمى الجدار الناري غير المعقد)، ولذا يتحتم علينا فتح البوابة التي سنستخدمها في هذا الجدار (وهي البوابة 8000) حتى نتمكن من الاتصال مع خادم تطوير جانغو واختباره.

يتم فتح البوابة بالأمر التالي:

sudo ufw allow 8000

والآن يمكنك اختبار تطبيقك بتشغيل خادم التطوير عبر الأمر التالي:

~/myprojectdir/manage.py runserver 0.0.0.0:8000

استعرض التطبيق بكتابة عنوان IP أو اسم النطاق مع البوابة 8000: ضمن متصفحك:

http://server_domain_or_IP:8000

وستحصل على صفحة الدليل index الافتراضية لجانغو:

صفحة الدليل index الافتراضية لجانغو

ولو أضفت admin/ في نهاية الرابط، سترى شاشة تسجيل الدخول إلى لوحة التحكم:

شاشة تسجيل الدخول إلى لوحة التحكم

أدخل اسم المستخدم وكلمة المرور الذي أنشأته عبر استخدام الأمر createsuperuser فتدخل إلى لوحة التحكم:

استخدام الأمر createsuperuser  للدخول إلى لوحة التحكم

أوقف خادم التطوير بالضغط على ctrl-c داخل شاشة الطرفية حالما تنتهي من اختباراتك.

التحقق من قدرة Gunicorn على تخديم المشروع

التحقق من قدرة Gunicorn على تخديم المشروع هو الأمر الأخير الذي سنقوم به ضمن البيئة الافتراضية، وذلك عبر الدخول إلى مجلد المشروع ومن ثم استخدام الأمر gunicorn لتشغيل وحدة WSGI الخاصة بتطبيقنا:

cd ~/myprojectdir
gunicorn --bind 0.0.0.0:8000 myproject.wsgi

سيشغل الأمر السابق Gunicorn على نفس الواجهة التي استخدمها خادم تطوير جانغو ويمكنك اختبار ذلك بإعادة طلب صفحات التطبيق عبر المتصفح.

تنويه: ستلاحظ أن لوحة التحكم غير مُنسقة لأن Gunicorn لا يملك حتى الآن مسار صفحات CSS المسؤولة عن ذلك.

لاحظ أننا استدعينا Gunicorn ومررنا له الوحدة التي تتضمن ملف wsgi.py -الذي يعدّ نقطة الدخول إلى تطبيقنا- مع تحديد موقع هذا الملف (حددناه عبر تنفيذ الأمر في مكان وجود الملف أي في مجلد المشروع)، ويحتوي الملف wsgi.py على دالة تسمى التطبيق application هي المسؤولة عن التواصل مع التطبيق. يمكنك معرفة المزيد حول مواصفات WSGI.

اضغط على الاختصار ctrl-c داخل شاشة الطرفية عندما تنتهي من الاختبار لإيقاف خادم Gunicorn.

يمكنك الآن إيقاف البيئة الافتراضية بالأمر التالي بعد أن انتهت إعدادات جانغو:

deactivate

وستلاحظ اختفاء محث البيئة الافتراضية من نافذة سطر الأوامر.

إنشاء ملفات تمهيد Gunicorn

اختبرنا في الفقرة السابقة تفاعل Gunicorn مع جانغو وتأكدنا من عمله، والآن سننشئ ملفات التمهيد systemd الخاصة به (المِقبس socket والخدمة)، لاستخدامها في إيقاف وتشغيل خادم التطبيق بطريقة أفضل.

سيُنشَأ عند الإقلاع مِقبس Gunicorn ويبقى جاهزًا يستمع بانتظار الاتصالات، وعند ورود أي اتصال، فإن systemd سيشغل تلقائيًا عملية Gunicorn لتتعامل مع هذا الاتصال.

لنبدأ بإنشاء ملف التمهيد لمِقبس Gunicorn بكتابة الأمر التالي مع امتيازات sudo:

sudo nano /etc/systemd/system/gunicorn.socket

بنية هذا الملف تتضمن ثلاثة مقاطع سنكتبها ضمنه، وهي: المقطع [Unit] مخصص لوصف المِقبس، والمقطع [Socket] يبين موقع المِقبس، أما المقطع [Install] فيحرص على إنشاء المقبس في الوقت الصحيح، وبذلك يصبح الملف بالشكل التالي:

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

يمكنك الآن إغلاق الملف بعد حفظ التغييرات.

ومن ثم أنشئ ملف التمهيد لخدمة Gunicorn وفق الأمر التالي وأيضًا مع امتيازات sudo، لاحظ أن الاسم والمسار متطابقان بين ملف المقبس وملف الخدمة باستثناء اللاحقة فهي التي تحدد نوع الملف:

sudo nano /etc/systemd/system/gunicorn.service

تتضمن بنية ملف الخدمة ثلاثة مقاطع أيضًا هي: [Unit] و [Service] و [Install].

يُخصص المقطع [Unit] لبيانات التعريف والاعتماديات، ونكتب ضمنه وصف الخدمة ونخبر نظام التمهيد أن تشغيل هذه الخدمة مرتبط بالوصول إلى هدف الشبكة الذي يعتمد على المقبس (عند ورود اتصال على المقبس كما ذكرنا سابقًا) وهذه العلاقة بين المقبس والخدمة سنذكرها ضمن المحدد Requires كما يلي:

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

نحدد ضمن المقطع [Service] من يتمتع بصلاحية تشغيل الخدمة من مستخدمين ومجموعات عمل، وهم في حالتنا مستخدم التطبيق sammy وهو مالك كافة الملفات المرتبطة، ومجموعة العمل www-data التي ستتيح لخادم Nginx الاتصال بسهولة مع Gunicorn.

ونوضح بعده مجلد العمل والأمر الذي سيُستخدم لتشغيل الخدمة: بكتابة المسار الكامل لملف Gunicorn التنفيذي (الذي تم تثبيته في بيئتنا الافتراضية)، وربط العملية بمقبس يونيكس الذي أنشأناه ضمن المسار المحدد في run/ (انظر المقطع [Socket] في ملف المقبس) حتى تتمكن العملية من التواصل مع Nginx.

وأخيرًا سنكتب ضمن المقطع التعليمات اللازمة لتُسجل كافة الأحداث في ملفات خرج قياسية حتى تتمكن journald من التقاط تسجيلات الأحداث الصادرة عن Gunicorn، وننوه إلى إمكانية ضبط تعديلات أخرى خاصة بـ Gunicorn هنا مثل تحديد عدد العمال workers بثلاثة كما في حالتنا.

انظر الآن إلى ملف الخدمة بعد إضافة المقطع [Service]:

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myprojectdir
ExecStart=/home/sammy/myprojectdir/myprojectenv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          myproject.wsgi:application

أما المقطع [Install] وهو المقطع الأخير، نستخدمه لإخبار نظام التمهيد بمرحلة بدء التشغيل التي سيرتبط بها تشغيل الخدمة في حال فعلّناها لتعمل تلقائيًا عند الإقلاع، وفي حالتنا ضبطنا الموضوع لتعمل الخدمة عندما يصل نظام التشغيل إلى مرحلة تعدد المستخدمين multi-user.

انظر الآن الشكل النهائي لملف الخدمة، ولاتنسَ حفظ التغييرات عليه قبل الإغلاق:

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
User=sammy
Group=www-data
WorkingDirectory=/home/sammy/myprojectdir
ExecStart=/home/sammy/myprojectdir/myprojectenv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          myproject.wsgi:application

[Install]
WantedBy=multi-user.target

يمكننا الآن تشغيل وتفعيل مقبس Gunicorn وفق الأمرين أدناه، وسيؤدي هذا إلى إنشاء ملف المقبس في المسار run/gunicorn.sock/ الآن عند تشغيلنا اليدوي للمقبس وتلقائيًا عند إقلاع نظام التشغيل، وفور ورود اتصال إلى المقبس سيشغل systemd تلقائيًا الخدمة gunicorn.service لتتعامل مع الاتصال:

sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket

يمكنك التأكد من نجاح العملية عبر التحقق من وجود ملف المقبس.

اطلع على معلومات إضافية عن systemd من خلال المقالين:

التحقق من ملف المقبس

تأكد من حالة العملية لترى إن كانت فعالة وقابلة للتشغيل عن طريق الأمر التالي:

sudo systemctl status gunicorn.socket

وستحصل على ما يشبه الخرج التالي في حال كانت فعالة دون أخطاء:

 gunicorn.socket - gunicorn socket
     Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor prese>
     Active: active (listening) since Fri 2020-06-26 17:53:10 UTC; 14s ago
   Triggers:  gunicorn.service
     Listen: /run/gunicorn.sock (Stream)
      Tasks: 0 (limit: 1137)
     Memory: 0B
     CGroup: /system.slice/gunicorn.socket

تحقق الآن من وجود ملف المقبس gunicorn.sock ضمن المسار run/ بكتابة الأمر التالي:

file /run/gunicorn.sock

ستحصل على الخرج التالي الذي يبين وجود الملف:

/run/gunicorn.sock: socket

أما في حال عدم وجود الملف gunicorn.sock، أو ظهور أي خطأ في خرج التعليمة systemctl status السابقة، فهذا يعني حدوث خطأ ما منع اكتمال العملية وإنشاء ملف المقبس، يمكنك تبيانه عبر استعراض سجلات الأحداث الخاصة بمقبس Gunicorn من خلال الأمر:

sudo journalctl -u gunicorn.socket

ومن ثم فتح الملف gunicorn.socket ضمن المسار etc/systemd/system/ ثانيةً لتصحيح واستدراك أي مشاكل موجودة بينتها السجلات قبل استكمال العمل.

التحقق من تفعيل المقبس

صحيح أننا شغلنا المقبس gunicorn.socket وتحققنا منه في الخطوات السابقة، ولكن الخدمة gunicorn.service لم تُفعل بعد وهذا طبيعي فهي مرتبطة بالاتصالات الواردة ولن تعمل قبل ورود أول اتصال. ويمكنك التحقق من حالتها بالأمر:

sudo systemctl status gunicorn

وستحصل على الخرج التالي الذي يبين أنها غير فعالة:

 gunicorn.service - gunicorn daemon
   Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

أرسل الآن اتصال تجريبي عبر curl لتختبر آلية العمل، وفق التالي:

curl --unix-socket /run/gunicorn.sock localhost

وستحصل بموجبه على صفحة HTML من تطبيقك داخل شاشة الطرفية، ما يعني أن Gunicorn بدأ بالعمل وأنه قادرٌ على تخديم التطبيق، نفذ مجددًا أمر التحقق من حالة الخدمة:

sudo systemctl status gunicorn

ولاحظ اختلاف الخرج التنفيذ السابق قبل وجود اتصالات:

 gunicorn.service - gunicorn daemon
     Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
     Active: active (running) since Fri 2020-06-26 18:52:21 UTC; 2s ago
TriggeredBy:  gunicorn.socket
   Main PID: 22914 (gunicorn)
      Tasks: 4 (limit: 1137)
     Memory: 89.1M
     CGroup: /system.slice/gunicorn.service
             ├─22914 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunico>
             ├─22927 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunico>
             ├─22928 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunico>
             └─22929 /home/sammy/myprojectdir/myprojectenv/bin/python /home/sammy/myprojectdir/myprojectenv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunico>

Jun 26 18:52:21 django-tutorial systemd[1]: Started gunicorn daemon.
Jun 26 18:52:21 django-tutorial gunicorn[22914]: [2020-06-26 18:52:21 +0000] [22914] [INFO] Starting gunicorn 20.0.4
Jun 26 18:52:21 django-tutorial gunicorn[22914]: [2020-06-26 18:52:21 +0000] [22914] [INFO] Listening at: unix:/run/gunicorn.sock (22914)
Jun 26 18:52:21 django-tutorial gunicorn[22914]: [2020-06-26 18:52:21 +0000] [22914] [INFO] Using worker: sync
Jun 26 18:52:21 django-tutorial gunicorn[22927]: [2020-06-26 18:52:21 +0000] [22927] [INFO] Booting worker with pid: 22927
Jun 26 18:52:21 django-tutorial gunicorn[22928]: [2020-06-26 18:52:21 +0000] [22928] [INFO] Booting worker with pid: 22928
Jun 26 18:52:21 django-tutorial gunicorn[22929]: [2020-06-26 18:52:21 +0000] [22929] [INFO] Booting worker with pid: 22929

وفي حال حصلت على أي أخطاء في خرج systemctl status أو خرج curl، تفقد عندها سجلات الأحداث عبر الأمر:

sudo journalctl -u gunicorn

وافتح الملف gunicorn.service ضمن المسار etc/systemd/system/ وصحح المشاكل التي بينتها السجلات قبل إكمال العمل، وانتبه إلى أن أي تعديلات تطبقها عليه لن تصبح سارية المفعول إلّا بعد إعادة تحميل الخدمة ومن ثم إعادة تشغيل Gunicorn وفق الأوامر التالية:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn

إعداد Nginx ليؤدي دور وكيل Gunicorn

لنبدأ بإنشاء كتلة خادم جديدة لخادم Nginx ضمن المجلد sites-available.

اكتب أولًا الأمر التالي الذي سيفتح ملفًا نصيًا:

sudo nano /etc/nginx/sites-available/myproject

أنشئ فيه كتلة خادم جديدة وحدد مواصفاتها بحيث تتلقى الطلبات الطبيعية التي ترد إلى البوابة 80 وتطلب اسم نطاقك أو عنوان IP الخادم، وذلك بكتابة التعليمات التالية:

server {
    listen 80;
    server_name server_domain_or_IP;
}

وبعدها أضف ضمن الكتلة نفسها التعليمات الخاصة بـ location المبينة أدناه، ووظيفتها جعل Nginx يتجاهل الأخطاء التي تنجم عن عدم العثور على رمز الموقع favicon، وإرشاده إلى موقع ملفات التطبيق الساكنة أي المسارmyprojectdir/static/~ الذي اختصرناه إلى "static/"، إذًا أصبحت كتلة الخادم على الشكل التالي:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/sammy/myprojectdir;
    }
}

والخطوة الأخيرة ستكون إنشاء كتلة {} / location التي ستتعامل مع بقية الطلبات، والإشارة ضمنها للملف proxy_params القياسي الذي أنُشئ أثناء تثبيت Nginx، ومن ثم تمرير كامل حركة البيانات مباشرةً إلى مقبس Gunicorn:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/sammy/myprojectdir;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}

احفظ التغييرات على الملف وأغلقه.

ومن ثم فعل الملف عبر ربطه بالمجلد sites-enabled باستخدام الأمر:

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

اختبر صحة تعليمات Nginx من ناحية قواعد الكتابة من خلال الأمر:

sudo nginx -t

أعد تشغيل Nginx إن لم تظهر لك أي أخطاء:

sudo systemctl restart nginx

لنضبط الآن إعدادات الجدار الناري UFW عبر السماح بحركة مرور البيانات عبر البوابة 80، وإغلاق البوابة 8000 التي سمحنا بها سابقًا للتخاطب مع خادم التطوير، فهي لم تعد لازمة.

اكتب الأوامر التالية لضبط الجدار الناري:

sudo ufw delete allow 8000
sudo ufw allow 'Nginx Full'

يمكنك الآن الوصول لتطبيقك باستخدام اسم النطاق أو عنوان IP.

تنويه: الخطوة الأهم بعد إعداد الخادم الوكيل هي تأمين شهادات TLS لتشفير حركة البيانات المتبادلة مع التطبيق وحماية البيانات الحساسة مثل كلمات المرور، لذا احرص على تنفيذ هذه الخطوة، ويمكنك الحصول على شهادات مجانية من خدمة Let’s Encrypt في كان لديك اسم نطاق محجوز، واتبع الخطوات الواردة في مقال كيف تؤمّن خادم ويب NGINX على أوبنتو 16.04 لتثبيت الشهادة فالطريقة نفسها.

استكشاف الأخطاء وإصلاحها في هذه البيئة

إذا لم تُظهر هذه الخطوة الأخيرة تطبيقك، فستحتاج إلى استكشاف أخطاء التثبيت وإصلاحها.

يظهر Nginx الصفحة الافتراضية بدلاً من تطبيق جانغو

في حال أظهر Nginx صفحته الافتراضية بدلًا من تطبيقك، فهذا يعني عادةً حاجتك لضبط server_name في الملف التالي:

/etc/nginx/sites-available/myproject

ليشير إلى اسم نطاقك أو عنوان IP للخادم، إذ يستخدم Nginx القيمة الموجودة في server_name ليُحدد أي كتلة خادم ستلبي الطلبات الواردة إليه، وعندما يرجع لك صفحته الافتراضية فهذا يعني أنه لم يتمكن من مطابقة طلبك بدقة مع أي من الكتل الموجودة ضمن ملفاته فيُرجع تلقائيًا الصفحة الموجودة في كتلته الافتراضية في المسار التالي:

/etc/nginx/sites-available/default

لذا احرص على تحديد server_name في الكتلة الخاصة بمشروعك بدقة وعلى نطاق أضيق من المذكور في الكتلة الافتراضية بحيث لا تسمح لخادم Nginx بتجاهلها والعودة إلى كتلته الافتراضية.

ظهور Nginx الخطأ 502 بدلا من تطبيق جانغو

يوجد مجموعة واسعة من الاحتمالات التي قد تسبب الخطأ 502 Bad Gateway Error، لذا فهو يحتاج إلى معلومات وتقصي أكثر لبيان سببه الفعلي.

سجلات أحداث Nginx هي أهم مصدر معلومات عن الأحداث والأخطاء التي تحصل خلال عمل الوكيل أو الوسيط، ويمكنك تتبعها باستخدام الأمر:

sudo tail -F /var/log/nginx/error.log

والآن حدث الصفحة في متصفح الإنترنت لتولد خطأ جديدًا في السجل واقرأه بدقة من شأنه تضييق نطاق البحث عن سبب الخطأ، وسنعطيك بعض الاحتمالات عن ما يمكن أن يظهر لك من رسائل خطأ.

احتمال الخطأ الأول:

connect() to unix:/run/gunicorn.sock failed (2: No such file or directory)

وهو يعني أن Nginx لم يتمكن من العثور على ملف المقبس gunicorn.sock في الموقع المحدد، وعندها عليك مقارنة موقع proxy_pass المذكور في الملف التالي:

/etc/nginx/sites-available/myproject

مع الموقع الفعلي المذكور في ملف المقبس gunicorn.sock المنشئ بواسطة وحدة تمهيد النظام gunicorn.socket.

أما في حال لم تجد ملف المقبس gunicorn.sock في المسار run/ فارجع إلى فقرة التحقق من ملف المقبس واتبع إرشاداتها لمعالجة هذه الحالة.

احتمال الخطأ الثاني:

connect() to unix:/run/gunicorn.sock failed (13: Permission denied)

يدل هذا الخطأ على مشاكل في صلاحيات وصول Nginx إلى مقبس Gunicorn، وغالبًا ما ينجم هذا الخطأ عن تنفيذ إجرائية العمل بمستخدم الجذر بدلًا من مستخدم يملك امتيازات sudo وعندها فإن systemd سيُنشئ مقبس Gunicorn ولكنّ Nginx لن يتمكن من الوصول إليه.

ومن الممكن أن تظهر نفس المشكلة في حال وجود أي تقييد للصلاحيات على طول المسار الواصل إلى ملف المقبس بدءًا من الجذر / وصولًا إلى الملف gunicorn.sock، ويمكنك استعراض صلاحيات الوصول واسم المالك على كافة المجلدات الموجودة على مسار ملف المقبس باستخدام التعليمة namei وفق التالي:

namei -l /run/gunicorn.sock

وستحصل على المعلومات التفصيلية عن صلاحيات كافة عناصر المسار ضمن الخرج بالشكل التالي:

f: /run/gunicorn.sock
drwxr-xr-x root root /
drwxr-xr-x root root run
srw-rw-rw- root root gunicorn.sock

يبين لك العمود الأول الصلاحيات الممنوحة على العنصر، والعمود الثاني يشير إلى اسم المستخدم المالك، بينما العمود الثالث فهو خاص باسم مجموعة العمل التي ينتمي إليها المالك، وبذلك يمكنك معرفة نوع الوصول المسموح إلى ملف المقبس.

فمثلًا في الخرج السابق، كافة عناصر المسار تحمل صلاحية القراءة والتنفيذ فهي تنتهي بالرمز r-x بدلًا من --- وهذا يعني إمكانية الوصول إليها من قبل Nginx فلو كان أي منها لا يحمل هذه الصلاحيات لحصلت مشكلة في الوصول إلى المقبس، وتُحل المشكلة آنذاك بإعطاء صلاحية العمل الناقصة أو بضم مستخدم Nginx إلى مجموعة العمل التي تتضمن مالك العنصر.

ظهور جانغو (لا يمكن الاتصال بالخادم: تم رفض الاتصال)

هي إحدى الرسائل التي قد تتلقاها من جانغو عند محاولتك استعراض بعض أجزاء التطبيق في متصفح الإنترنت:

OperationalError at /admin/login/
could not connect to server: Connection refused
    Is the server running on host "localhost" (127.0.0.1) and accepting
    TCP/IP connections on port 5432?

تعني هذه الرسالة أن جانغو لم يتمكن من الوصول إلى قاعدة البيانات، لذا فالخطوة الأولى هي التأكد من إقلاع نسخة قاعدة البيانات Postgres instance باستخدام الأمر التالي:

sudo systemctl status postgresql

وفي حال تبين لك أنها لا تعمل فيمكنك تشغيلها ومن ثم تفعيلها لتعمل تلقائيًا عند الإقلاع (ما لم تكن مفعلة للتشغيل التلقائي) وفق الأمرين:

sudo systemctl start postgresql
sudo systemctl enable postgresql

وإن استمرت المشكلة تأكد من إعدادات قاعدة البيانات الموجودة في الملف التالي وصححها:

~/myprojectdir/myproject/settings.py

المزيد من استكشاف الأخطاء وإصلاحها

استعن دومًا بسجلات الأحداث فهي مصدر مهم للمعلومات وحدد الأخطاء بدقة وابحث عنها، إليك أهم السجلات.

  • للتحقق من سجلات عملية Nginx اكتب:
sudo journalctl -u nginx
  • للتحقق من سجلات وصول Nginx اكتب:
sudo less /var/log/nginx/access.log
  • للتحقق من سجلات خطأ Nginx اكتب:
sudo less /var/log/nginx/error.log
  • للتحقق من سجلات أحداث Gunicorn اكتب:
sudo journalctl -u gunicorn
  • للتحقق من سجلات أحداث مقبس Gunicorn اكتب:
sudo journalctl -u gunicorn.socket

عند تعديل أو تحديث الإعدادات أو التطبيق يجب إعادة تشغيل العمليات لتصبح التعديلات سارية المفعول.

ففي حال عدلت تطبيق جانغو يمكنك إعادة تشغيل عملية Gunicorn عبر الأمر:

sudo systemctl restart gunicorn

وإن عدلت ملفات مقبس أو خدمة Gunicorn فعليك إعادة تحميل العفريت وإعادة تشغيل الخدمة:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn.socket gunicorn.service

أما لو عدلت إعدادات كتلة الخادم لـ Nginx فعليك التحقق من صحة القواعد أولًا ومن ثم إعادة تشغيل Nginx وفق التالي:

sudo nginx -t && sudo systemctl restart nginx

ستفيدك هذه الأوامر في عكس تعديلاتك على عمل البيئة بصورة سليمة.

خاتمة

أعددنا في هذا المقال مشروع جانغو ضمن بيئة افتراضية معزولة ومجهز بخادم Gunicorn يوصل طلبات العملاء إلى جانغو حتى يعالجها، وبخادم Nginx الذي يلعب دور الوكيل العكسي لتحسين أداء التطبيق وأمانه، وقدمنا مجموعة من الأدوات المساعدة التي من شأنها خدمة تطبيقاتك العاملة على خادم واحد.

ترجمة -وبتصرف- للمقال How To Set Up Django with Postgres, Nginx, and Gunicorn on Ubuntu 20.04 لصاحبه Erin Glass.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...