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

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

لجهة التوسع الأفقي يمكنك استخدام أكثر من خادم تطبيق لأداء مهمة تشغيل جانغو مع خادم WSGI HTTP الخاص به (وهو في الغالب Gunicorn أو uWSGI)، وحول توجيه الحركة يمكنك إعداد خادم خاص يوزع الطلبات بين خوادم التطبيق مثل Nginx فهو قادر على القيام بدور الوكيل العكسي reverse proxying و موازن الحمل load balancing، بالإضافة إلى تأمين التخزين المؤقت Cache لمكونات وأصول التطبيق الساكنة static assets، علاوةً على أنه يدعم بروتوكول أمان طبقة النقل TLS لتكون كافة اتصالات التطبيق آمنة عبر HTTPS.

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

ستتعلم في هذا المقال كيفية توسيع تطبيق جانغو Polls -هو اسم التطبيق- المغلف في حاوية توسيعًا أفقيًا بإعداد خادمين للتطبيق يُشغل كل منهما حاوية تتضمن جانغو وخادم Gunicorn، وكيفية تأمينه بتفعيل HTTPS وإعداد خادم ثالث يؤدي وظيفة الوكيل العكسي ويُشغل حاويتين واحدة تتضمن Nginx والأخرى تتضمن عميل Certbot الذي سيوفر لخادم Nginx شهادات TLS مصدقة من Let’s Encrypt وقادرة على منح تطبيقك تقييم عالي بنتائج اختبار SSL Labs، سيتلقى هذا الوكيل العكسي كافة الطلبات الخارجية الواردة ويحولها إلى خادمي تطبيق جانغو للإجابة عليها، وزيادة في الأمان سنقيّد الاتصال الخارجي بحيث يتم عبر الوكيل العكسي حصرًا.

هذا المقال جزء من سلسلة قصيرة حول احتواء تطبيقات جانغو بحاويات وإدارتها وإليك فهرس السلسلة:

  • بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر
  • توسيع تطبيق جانغو وتأمينه عبر حاوية دوكر وخادم Nginx وخدمة Let's Encrypt
  • نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس

متطلبات بيئة العمل

ستحتاج المتطلبات التالية لتتمكن من التطبيق العملي للمقال:

  1. ثلاثة خوادم عليها نظام تشغيل أوبنتو (استعملنا في المقال إصدار 18.04 واستعن بمقال التهيئة الأولية لخادم أوبونتو 18.04):
  • اثنان منها خوادم تطبيق لزوم تشغيل تطبيقك (جانغو/Gunicorn).
  • خادم واحد يؤدي وظيفة الوكيل لزوم تشغيل Nginx و Certbot.
  • مستخدم عادي -غير مسؤول- يتمتع بصلاحية sudo وجدار ناري فعال على الخوادم الثلاثة.
  1. تثبيت دوكر على الخوادم الثلاثة، اطلع على كيفية تثبيت دوكر واستخدامه على دبيان لإنجاز المهمة إذ طريقة التثبيت نفسها في النظامين.
  2. حجز اسم نطاق لتطبيقك، سنعتمد في مقالنا على الاسم your_domain.com، مع العلم بإمكانية حصولك على اسم نطاق مجاني من Freenom.
  3. ربط اسم النطاق بعنوان الـ IP العام للخادم الوكيل عبر إضافة سجل DNS من النوع A، يمكنك الحصول على تفاصيل إضافية حول الموضوع بالاطلاع على القسم الخاص بخدمة اسم النطاق DNS على أكاديمية حسوب.
  4. خدمة تخزين كائني متوافقة مع S3 ومع إضافة django-storages مثل DigitalOcean Space وظيفتها تأمين المساحة التخزينية اللازمة لملفات تطبيق جانغو الساكنة، مع مجموعة مفاتيح الوصول الخاصة بإدارة هذه المساحة، استرشد بكيفية إعداد مساحة على DigitalOcean Space وعدّل بما يلائم خدمة التخزين التي اخترتها.
  5. نظام إدارة قواعد بيانات متوافق مع جانغو مع إنشاء قاعدة بيانات ومستخدم خاص بالتطبيق، نحن اخترنا خادم PostgreSQL، اهتم بالتفاصيل التالية أثناء إنجازك لهذه النقطة لتتمكن من متابعة خطوات المقال:
  • استخدم الاسم polls لقاعدة البيانات والاسم sammy لمستخدم قاعدة البيانات (هذه الأسماء ليست ملزمة ولكننا استخدمناها في المقال واستخدامك لها سيسهل عليك المتابعة)، لمزيد من التفاصيل حول الإنشاء استرشد بالخطوة /1/ من مقالنا السابق [بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر]()، مع العلم أن هذه المهمة يمكن إنجازها من أي خادم من الخوادم الثلاثة لبيئة العمل.
  • اعتمدنا في المقال على نظام إدارة قواعد البيانات من نوع Managed PostgreSQL cluster الذي توفره DigitalOcean.
  • لكن يمكنك تثبيت برنامج PostgreSQL وتشغيله وإعداده بنفسك مع الاستعانة بالفيديو التعليمي تثبيت وإعداد قاعدة بيانات PostgreSQL.

الخطوة 1: إعداد خادم التطبيق الأول

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

تنويه: في حال كنت مستمرًا معنا من المقال السابق [بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر]() فهذه الخطوة ستكون جاهزة لديك ويمكنك الانتقال للخطوة /2/ وهي تجهيز الخادم الثاني.

سنبدأ بتسجيل الدخول إلى خادم التطبيق الأول واستنساخ polls-docker من قسم تطبيقات جانغو Polls في مستودع GitHub فهذا القسم يتضمن عينات تجريبية من التطبيق Polls مجهزة خصيصًا لتوثيقات جانغو التعليمية.

أما القسم polls-docker من المستودع فيتضمن نسخًا معدّلة مسبقًا من تطبيق Polls ومجهزة للعمل بفعالية في بيئة الحاويات، راجع مقالنا السابق بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر لمزيد من المعلومات.

إليك تعليمة الاستنساخ اللازم تنفيذها:

git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

توجه بعدها إلى المجلد django-polls عبر الأمر التالي:

cd django-polls

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

استعرض الملف Dockerfile عبر تعليمة لينكس cat:

cat Dockerfile

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

FROM python:3.7.4-alpine3.10

ADD django-polls/requirements.txt /app/requirements.txt

RUN set -ex \
    && apk add --no-cache --virtual .build-deps postgresql-dev build-base \
    && python -m venv /env \
    && /env/bin/pip install --upgrade pip \
    && /env/bin/pip install --no-cache-dir -r /app/requirements.txt \
    && runDeps="$(scanelf --needed --nobanner --recursive /env \
        | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
        | sort -u \
        | xargs -r apk info --installed \
        | sort -u)" \
    && apk add --virtual rundeps $runDeps \
    && apk del .build-deps

ADD django-polls /app
WORKDIR /app

ENV VIRTUAL_ENV /env
ENV PATH /env/bin:$PATH

EXPOSE 8000

CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "mysite.wsgi"]

انظر تعليمات الملف السابق بالترتيب وتابع الشرح الآتي.

سيستخدم الملف صورة دوكر بإصدار بايثون 3.7.4، ويثبت اعتماديات وحزم بايثون اللازمة لكل من جانغو وخادم gunicorn وفق ملف متطلبات التطبيق django-polls/requirements.txt ومن ثم يحذف الملفات غير اللازمة بعد انتهاء التثبيت، وينسخ بعد ذلك كود التطبيق إلى الصورة ويسند قيمة المتغير PATH، وأخيرًا سيحدد البوابة 8000 لاستقبال حركة البيانات الواردة إلى الحاوية، ويشغل خادم gunicorn مع عمال workers عدد /3/ عبر البوابة 8000.

لننتقل إلى بناء صورة الحاوية باستخدام docker build:

docker build -t polls .

سميّنا الصورة polls باستخدام الراية t- ومررنا المسار الحالي . كمسار context للتعليمة build فهو يتضمن كافة الملفات اللازمة لعملية البناء.

بعد انتهاء عملية البناء استخدم الأمر docker images لاستعراض الصور المتاحة:

docker images

وستحصل على الخرج التالي:

REPOSITORY          TAG                    IMAGE ID             CREATED              SIZE
polls                           latest                  80ec4f33aae1         2 weeks ago          197MB
python                   3.7.4-alpine3.10     f309434dea3a       8 months ago        98.7MB

تشغيل الصورة هي الخطوة التالية لعملية البناء، إلا أنها تتطلب ضبط متغيرات البيئة الموجودة ضمن الملف env وهو أحد لوازم تعليمة التشغيل docker run، لنعدّل عليه: افتح الملف env الموجود ضمن المجلد django-polls باستخدام محرر النصوص nano:

nano env

سيظهر أمامك الملف بالشكل التالي:

DJANGO_SECRET_KEY=
DEBUG=True
DJANGO_ALLOWED_HOSTS=
DATABASE_ENGINE=postgresql_psycopg2
DATABASE_NAME=polls
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
STATIC_ACCESS_KEY_ID=
STATIC_SECRET_KEY=
STATIC_BUCKET_NAME=
STATIC_ENDPOINT_URL=
DJANGO_LOGLEVEL=info

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

  1. المفتاح السري DJANGO_SECRET_KEY: اختر له قيمة فريدة صعبة التخمين كما توصي توثيقات Docker نقدم لك في الخطوة /5/ من مقال كيفية إعداد تطبيق جانغو عبر DigitalOcean Managed Databases and Spaces إحدى طرق جانغو لتوليد المفاتيح.
  2. DJANGO_ALLOWED_HOSTS: أسند له اسم النطاق الخاص بك your_domain.com، أو يمكنك وضع الرمز * لأغراض الاختبار فقط وليس ضمن بيئة العمل الفعلية فهذا الرمز يسمح لأي عنوان IP بالوصول، يحميك هذا المحدد من الهجمات المعتمدة على حقن ترويسة HTTP وفي حال أردت المزيد من المعلومات اطلع على توثيقات جانغو الخاصة بهذا المحدد ضمن Core Settings
  3. DATABASE_USERNAME: ضع اسم مستخدم قاعدة البيانات PostgreSQL الذي أنشأته أثناء إعدادك لمتطلبات العمل.
  4. DATABASE_NAME: ضع القيمة polls أو الاسم الذي اخترته لقاعدة البيانات أثناء إعدادك لمتطلبات العمل.
  5. كذلك الأمر لكلمة المرور DATABASE_PASSWORD.
  6. ضع اسم مضيف قاعدة البيانات DATABASE_HOST ورقم بوابة الاتصال معها DATABASE_PORT
  7. أما المحددات STATIC_ACCESS_KEY_ID و STATIC_SECRET_KEY و STATIC_BUCKET_NAME و STATIC_ENDPOINT_URL فهي تتعلق بخدمة التخزين الكائني الخارجية، اضبطها بما يتناسب مع الخدمة التي تستخدمها.

احفظ التغيرات على الملف env بعد الانتهاء وأغلقه استعدادًا لتنفيذ أمر تشغيل الحاوية عبر التعليمة docker run التي سنكتبها بطريقة تسمح بتجاوز CMD (التي تحدد افتراضيات بدء التشغيل) وتنشئ أثناء التشغيل مخطط قاعدة البيانات باستخدام كل من manage.py makemigrations و manage.py migrate وفق ما يلي:

docker run --env-file env polls sh -c "python manage.py makemigrations && python manage.py migrate"

لنشرح الأمر السابق، شغلنا صورة الحاوية polls:latest ومررنا لها متغيرات البيئة التي ضبطناها للتو ضمن الملف env، وتجاوزنا الأمر الافتراضي في ملف Dockerfile لننفذ الأمر التالي بدلًا منه الذي أنشأ مخطط قاعدة البيانات بالمواصفات المحددة في كود التطبيق:

sh -c "python manage.py makemigrations && python manage.py migrate"

إن كنت تنفذ docker run للمرة الأولى فستحصل على الخرج التالي:

No changes detected
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying polls.0001_initial... OK
  Applying sessions.0001_initial... OK

مع العلم أن جانغو لن يجري أي عملية عندما تنفذ migrate في المرات القادمة طالما لم يطرأ أي تغيير على مخطط قاعدة بيانات التطبيق.

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

docker run -i -t --env-file env polls sh

تظهر بنتيجة هذا الأمر نافذة الصدفة shell ضمن الحاوية اكتب فيها تعليمة إنشاء المستخدم الخاص بتطبيق جانغو:

python manage.py createsuperuser

استكمل بياناته وهي اسم المستخدم وكلمة المرور والبريد الإلكتروني، واضغط بعدها على الاختصار Ctrl+D للخروج من الحاوية وإنهاء عملها.

أما العملية الأخيرة فهي جمع الملفات الساكنة الخاصة بالتطبيق collectstatic ورفعها على خدمة التخزين الكائني وتتم وفق الأمر التالي علمًا أنها قد تستغرق بعض الوقت:

docker run --env-file env django-polls:v0 sh -c "python manage.py collectstatic --noinput"

يظهر بعدها الخرج التالي:

121 static files copied.

يمكنك الآن تشغيل التطبيق:

docker run --env-file env -p 80:8000 polls

ستحصل على الخرج:

[2019-10-17 21:23:36 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2019-10-17 21:23:36 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2019-10-17 21:23:36 +0000] [1] [INFO] Using worker: sync
[2019-10-17 21:23:36 +0000] [7] [INFO] Booting worker with pid: 7
[2019-10-17 21:23:36 +0000] [8] [INFO] Booting worker with pid: 8
[2019-10-17 21:23:36 +0000] [9] [INFO] Booting worker with pid: 9

مع العلم أن التشغيل هذه المرة وفق الإعدادات الافتراضية المنصوص عليها في ملف Dockerfile وهي:

gunicorn --bind :8000 --workers 3 mysite.wsgi:application

وأن البوابة أو المنفذ 8000 للحاوية فُتحت لتستقبل حركة البيانات القادمة من المنفذ 80 للخادم أوبونتو.

يمكنك الآن كتابة عنوان URL الخاص بالتطبيق polls بمتصفح الإنترنت واستعراضه، لاحظ أنك ستحصل على الخطأ 404 (لم يتم العثور على الصفحة) في حال اكتفيت بالعنوان:

http://APP_SERVER_1_IP/

بسبب عدم وجود بيانات للتطبيق تحت الجذر / لذا اكتب العنوان التالي لتحصل على واجهة polls:

http://APP_SERVER_1_IP/polls

** تنويه **: إذا كنت تستخدم جدارًا ناريًا من النوع UFW فإنك ستلاحظ وجود ثغرة أمنية في عمل حاوية دوكر وهي أن دوكر يتجاهل كافة السياسات الأمنية الموجودة على هذا النوع من الجدران النارية ويسمح بالاتصالات بغض النظر عنها، فقد مكّن الاتصال مع البوابة 80 على الخادم دون أن نضبط مسبقًا أي إعداد على الجدار الناري، هذه الثغرة في الواقع موثقة على GitHub Issue وتعالجها الخطوة /5/ من مقالنا عبر تصحيح إعدادات الجدار الناري UFW، أما في حال كنت تستخدم جدارًا ناريًا أقوى وأكثر تطورًا كالذي توفره DigitalOcean’s Cloud Firewalls على سبيل المثال يمكنك تخطي هذا التحذير.

01-img-polls_app.png

اكتب الرابط التالي http://APP_SERVER_1_ip/admin للوصول إلى واجهة الدخول للوحة التحكم الخاصة بالتطبيق:

واجهة الدخول للوحة التحكم الخاصة بالتطبيق

أدخل بيانات المستخدم مدير التطبيق المنشأ سابقًا باستخدام الأمر createsuperuser وستظهر أمامك واجهة الإدارة والتحكم الخاصة بالتطبيق.

واجهة الإدارة والتحكم الخاصة بالتطبيق

تذكر أن تسليم المكونات الساكنة للتطبيق admin و polls يتم من خدمة التخزين الكائني ويمكنك اتباع هذه الخطوات لاختبار جودته وأيضًا التأكد من صحة إحضار الملفات.

بعد أن تنتهي من التصفح اضغط Ctrl+c في نافذة كتابة الأوامر السطرية التي تشغل حاوية دوكر لتنهي عمل الحاوية ثم شغلها مجددًا بالنمط المنفصل detached الذي يسمح لها أن تعمل بالخلفية ويمكنك من تسجيل الخروج من جلسة ssh.

اكتب الأمر التالي لتشغيل النمط المنفصل:

docker run -d --rm --name polls --env-file env -p 80:8000 polls

تشير الراية d- إلى النمط المنفصل، بينما تشير الراية rm-- إلى تنظيف نظام ملفات الحاوية بعد إغلاقها، أما polls فهو اسم الحاوية.

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

الخطوة 2: إعداد خادم التطبيق الثاني

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

سجل الدخول إلى خادم التطبيقات الثاني وابدأ باستنساخ القسم polls-docker من مستودع django-polls على GitHub:

git clone --single-branch --branch polls-docker https://github.com/do-community/django-polls.git

توجه إلى المسار التالي:

cd django-polls

أنشئ صورة التطبيق باستخدام docker build:

docker build -t polls .

افتح الملف env بمحرر النصوص الذي تفضله:

nano env

لتحصل على الخرج التالي:

DJANGO_SECRET_KEY=
DEBUG=True
DJANGO_ALLOWED_HOSTS=
DATABASE_ENGINE=postgresql_psycopg2
DATABASE_NAME=polls
DATABASE_USERNAME=
DATABASE_PASSWORD=
DATABASE_HOST=
DATABASE_PORT=
STATIC_ACCESS_KEY_ID=
STATIC_SECRET_KEY=
STATIC_BUCKET_NAME=
STATIC_ENDPOINT_URL=
DJANGO_LOGLEVEL=info

اضبط قيم الملف بما يناسب بيئتك ثم احفظ التغييرات على الملف وأغلقه.

وأخيرًا شغل حاوية التطبيق بالنمط المنفصل كما يلي:

docker run -d --rm --name polls --env-file env -p 80:8000 polls

وافتح التطبيق باستخدام المتصفح للتأكد من سلامة عمله:

http://APP_SERVER_2_IP/polls 

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

أنهينا تجهيز خادمي تطبيق جانغو لننتقل للخطوة التالية ونجهز حاوية الوكيل العكسي.

الخطوة 3: إعداد حاوية دوكر للخادم Nginx

يتمتع خادم الويب Nginx بالعديد من المزايا أهمها قدرته على أداء وظائف متعددة مثل الوكيل العكسي وموازنة الحمل والتخزين لملفات الموقع، سيؤدي منها في هذه البيئة وظيفة الوكيل العكسي لحماية الواجهات الخلفية لخادمي جانغو وموازن الحمل لتوزيع الطلبات بينهما، كما سيؤمن تشفير الاتصالات مع خوادم التطبيق باستخدام شهادة TLS التي يوفرها Certbot ما يعني أنه سيجبر العميل على استخدام HTTPS عبر إعادة توجيه كافة الطلبات من نوع HTTP إلى المنفذ 443، ومن ثم إيصال طلبات HTTPS إلى خوادم جانغو بعد فك تشفيرها، أما وظيفة التخزين فلن نحتاجها إذ الآلية التي استخدمناها لإفراغ ملفات جانغو على وحدة التخزين الكائني تفي بالغرض.

ونوّد أن نبين لك أن التصميم الذي اخترناه في هذا المقال والمؤلف من خادمي تطبيق كل منهما في حاوية مع حاوية ثالثة تتضمن Nginx هو واحد من عدة خيارات متاحة لكل منها مزايا مختلفة من حيث الأداء والحماية، فعلى سبيل المثال كان الممكن بناء Nginx ضمن إحدى حاويتي التطبيق ليقوم بدور الوكيل محليًا لخادم التطبيق الموجود معه في الحاوية نفسها وللخادم في الحاوية الثانية، وفي احتمالٍ آخر يمكنك بناء حاويتي Nginx فتصبح البنية حاوية Nginx مقابل كل حاوية تطبيق ومن ثم استخدام موازن حمل سحابي من أي مزود خدمة مثل DigitalOcean، أما المفاضلة بين هذه التصاميم المختلفة أنجزها بناءً على تحليل متطلباتك ونتائج اختبار الحمل Load Test الذي يبين لك نقاط الاختناق في كل بنية لتوسعها، فبنية مقالنا مثلًا تتمتع بالمرونة لجهة قابليتها للتوسع بإضافة حاوية Nginx ثانية أو أكثر في أي مرحلة تظهر فيها اختناقات الشبكة بسبب خادم Nginx الوحيد، كذلك يمكننا الاعتماد على موزان حمل سحابي بسرعة أعلى أو موازن حمل من رتبة الطبقة الرابعة L4 مثل HAProxy. والآن بعد أن أعطينا فكرة عن الاحتمالات المتعددة لتصميم البنية لنرجع إلى تطبيقنا العملي.

سجل الدخول إلى الوكيل العكسي Nginx وأنشئ مسارًا يدعى config كما يلي:

mkdir conf

ومن ثم أنشئ ملفًا نصيًا باسم nginx.conf باستعمال أحد محررات النصوص مثل nano:

nano conf/nginx.conf

والصق ضمن هذا الملف الإعدادات التالية:

upstream django {
    server APP_SERVER_1_IP;
    server APP_SERVER_2_IP;
}

server {
    listen 80 default_server;
    return 444;
}

server {
    listen 80;
    listen [::]:80;
    server_name your_domain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your_domain.com;

    # SSL
    ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;

    ssl_session_cache shared:le_nginx_SSL:10m;
    ssl_session_timeout 1440m;
    ssl_session_tickets off;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;

    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

    client_max_body_size 4G;
    keepalive_timeout 5;

        location / {
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header Host $http_host;
          proxy_redirect off;
          proxy_pass http://django;
        }

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
    }

}

جهزنا الإعدادات السابقة بالاعتماد على عدة مصادر تتحدث عن إعداد Nginx مثل GitHub\Certbot و Gunicorn و Docker\Nginx ودراستها وتعديلها لتناسب البيئة، هذه العملية في الواقع خارج نطاق مقالنا لذا ننصحك بالاطلاع على المقال فهم بنية ملف إعدادات nginx وسياقات الإعدادات من أكاديمية حسوب والمقال الذي يتحدث عن خوارزميات اختيار كتل Server و Location ضمن الملف من DigitalOcean لتفهم المزيد عن الإعدادات، أو الاستعانة ببعض الأدوات الخاصة بتجهيز ملف Nginx مثل NGINXConfig على سبيل المثال لا الحصر.

لنشرح الآن دلالات هذه الإعدادات وكيفية عملها حيث أنها تتضمن السياقات الرئيسية upstream و server و location وهي تُعنى بإعادة توجيه طلبات HTTP إلى HTTPS ومن ثم إيصالها إلى خادمي التطبيق مع موازنة الحمل بينهما.

سياقات الملف conf/nginx.conf بالترتيب هي: سياق الخوادم العليا upstream وهو وحيد، يليه ثلاثة سياقات متتالية تسمى سياق الخادم server، وأخيرًا سياق الموقع location وهو مكرر مرتين متتاليتين في نهاية الملف.

يحدد سياق الخوادم العليا upstream الخوادم التي ستتلقى طلبات الوكيل عبر عملية التوجيه proxy_pass:

upstream django {
    server APP_SERVER_1_IP;
    server APP_SERVER_2_IP;
}
. . .

أطلقنا الاسم django على هذا السياق وعرفنا ضمنه خادمي التطبيق عبر كتابة عنوان الـ IP الخاص بكل خادم، أما في حال كنت تستخدم شبكة سحابية افتراضية خاصة VPC للربط بين الخادمين فاستبدل هذه العناوين بعناوين الخوادم الخاصة Private IP وفق إعدادات الشبكة VPC، ويمكنك الاستعانة بالمثال لمزيد من التفاصيل حول تفعيل هذه الشبكة.

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

. . .
server {
    listen 80 default_server;
    return 444;
}
. . .

أما ثاني سياق من نوع خادم فهو يتلقى الطلبات الواردة إلى نطاقك عبر HTTP ويعيد توجيهها إلى HTTPS عبر توجيه HTTP 301، ليعالجها بعد ذلك سياق الخادم الأخير:

. . .
server {
    listen 80;
    listen [::]:80;
    server_name your_domain.com;
    return 301 https://$server_name$request_uri;
}
. . .

تحدد أسطر التوجيه التالية مسارات شهادة TLS والمفتاح الخاص Secret Key، علمًا أن Certbot سيؤمن هذه الملفات، ومن ثم ستُوصل إلى داخل الحاوية عبر التعليمة mount كما سنرى في المراحل التالية:

. . .
ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
. . .

أما محددات اتصال SSL فتُضبط وفقًا للقيم الافتراضية التي توصي بها Certbot كما يلي:

. . .
    ssl_session_cache shared:le_nginx_SSL:10m;
    ssl_session_timeout 1440m;
    ssl_session_tickets off;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;

    ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
. . .

إن رغبت بمعلوماتٍ إضافية عن هذه المحددات وطريقة ضبطها فإننا نرشّح لك مقالين حول الموضوع من Nginx و Mozilla.

استرشدنا بتوثيق Gunicorn الخاص بإعدادات Nginx لضبط عدد الاتصالات الأعظمي المسموح به client_max_body_size وزمن جلسة الاتصال keepalive_timeout وهي المدة الزمنية العظمى التي يتم بعدها قطع الاتصال مع العميل وتقدر بالثانية:

. . .
client_max_body_size 4G;
keepalive_timeout 5;
. . .

لننتقل الآن للسياقات الأخيرة ضمن الملف وهي سياقات الموقع.

يوجه أول سياق موقع Nginx أثناء تلقيمه وعكسه الطلبات إلى خوادم التطبيق المعرّفة ضمن سياق الخوادم العليا upstream وذلك عبر بروتوكول HTTP، مع المحافظة على معلومات ترويسة HTTP لاتصال العميل مثل عنوان الـ IP الأصلي وبروتوكول الاتصال المستخدم وعنوان الهدف:

. . .
location / {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://django;
}
. . .

اطلع على توثيق Gunicorn وتوثيق النموذج ngx_http_proxy_module لمزيدٍ من المعلومات.

يُسجل سياق الموقع الثاني الطلبات ضمن المسار /well-known/acme-challenge/ لاستخدامها من قبل Certbot للتحقق من اسم النطاق وفق تحدي HTTP-01 وهو أحد أنواع تحديات Let’s Encrypt التي تستخدمها للاختبار والتحقق.

. . .
location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
}

أغلق الآن ملف الإعدادات بعد حفظ التغييرات، حيث أنه سيُستخدم لتشغيل الحاوية.

لننفذ الآن أمر تشغيل الحاوية وفق الصيغة التالية، بالاعتماد على الصورة nginx:1.19.0 وهي إحدى صور دوكر الرسمية المخصصة لخادم Nginx:

docker run --rm --name nginx -p 80:80 -p 443:443 \
    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
    -v /var/www/html:/var/www/html \
    nginx:1.19.0

ستلاحظ أثناء التشغيل ظهور خطأ مفاده عدم وجود الشهادات TLS المذكورة ضمن ملف الإعدادات nginx.conf، ومع ذلك سيكمل دوكر بناء الحاوية وتشغيلها، أما الخطأ سيُعالج في الخطوة التالية بتوفير الشهادات المطلوبة باستخدام Certbot و Let’s Encrypt.

بالرجوع لأمر التشغيل سترى أننا أطلقنا الاسم nginx على الحاوية، وربطنا المنفذ 80 والمنفذ 443 للمضيف بالمنافذ التي تحمل الأرقام نفسها في الحاوية، واستخدمنا الراية v- لوصل ملف الإعدادات nginx.conf الذي ضبطناه مسبقًا إلى المسار etc/nginx/conf.d/nginx.conf/ داخل الحاوية بحيث تعتمده الصورة مع الانتباه إلى التعليمة تضمنت الراية ro التي تشير إلى "read only" أي أن الملف سيكون للقراءة فقط بالنسبة الحاوية فلا يمكن تعديله من داخلها، بالإضافة لذلك وصلنا مسار الجذر لتطبيق الويب إلى المسار var/www/html/ المقابل ضمن الحاوية، أما السطر الأخير في أمر التشغيل وهو nginx:1.19.0 فسيرشد دوكر إلى الصورة المطلوبة على Dockerhub لاستخدامها.

الخطوة 4: إعداد Certbot وتجديد الشهادة من Let’s Encrypt

Certbot هو عميل Let’s Encrypt الأشهر طورته شركة Electronic Frontier Foundation، وظيفته تزويد خوادم الويب بشهادات TLS مجانية مصدقة من Let’s Encrypt تعطي للمتصفحين الثقة بهوية الموقع.

لنبدأ بالتطبيق العملي انطلاقًا من صورة Certbot المتوفرة على Dockerhub، ولكن تأكد أولًا من وجود سجل DNS من النوع A يرتبط بعنوان Public IP للخادم الوكيل، ثم نفذ أمر التشغيل التالي ليوفر عميل certbot الشهادات اللازمة للاختبار staging مع خوادم Let’s Encrypt:

docker run -it --rm -p 80:80 --name certbot \
         -v "/etc/letsencrypt:/etc/letsencrypt" \
         -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
         certbot/certbot certonly --standalone --staging -d your_domain.com

يشغل الأمر السابق الصورة certbot في الوضع التفاعلي، ويربط البوابة 80 على الخادم المضيف مع البوابة 80 ضمن الحاوية، كما أنه ينشئ المسارين /etc/letsencrypt/ و /var/lib/letsencrypt/ ويوصلهما بنفس المسارات على المضيف.

سيعمل Certbot بموجب الأمر السابق بالنمط المستقل أي بدون خادم Nginx ويتصل بخوادم Let’s Encrypt لإنجاز اختبار التشفير والتحقق من النطاق، وأثناء ذلك سيُطلب منك عنوان بريد إلكتروني وتأكيد الموافقة على شروط الخدمة، أدخل المطلوب وانتظر اكتمال العملية وفي حال نجح التحقق من نطاقك ستحصل على الخرج التالي الذي يخبرك بمكان تخزين الشهادة:

Obtaining a new certificate
Performing the following challenges:
http-01 challenge for stubb.dev
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/your_domain.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/your_domain.com/privkey.pem
   Your cert will expire on 2020-09-15. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.

يمكنك استعراض ملف الشهادة باستخدام cat:

sudo cat /etc/letsencrypt/live/your_domain.com/fullchain.pem

سنعيد الآن تشغيل الحاوية nginx بنفس أمر التشغيل المستخدم في الخطوة /3/ ولكن مع إضافة مسارات Let’s Encrypt التي بنيناها توًا أثناء تشغيل حاوية certbot:

docker run --rm --name nginx -p 80:80 -p 443:443 \
    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
    -v /etc/letsencrypt:/etc/letsencrypt \
    -v /var/lib/letsencrypt:/var/lib/letsencrypt \
    -v /var/www/html:/var/www/html \
    nginx:1.19.0

فور إقلاع الحاوية nginx يمكنك أن تجرب فتح التطبيق من خلال المتصفح بوضع الرابط

http://your_domain.com

وستلاحظ وجود تحذير أمني مفاده انتهاء صلاحية الشهادة وهذا متوقع لأننا ما زلنا نعمل على الشهادات المرحلية الاختبارية staging، ولكن مع ذلك ستجد أن طلبك قد تم إعادة توجيهه من HTTP إلى HTTPS.

اضغط الآن على الاختصار Ctrl+c للخروج من حاوية Nginx وشغل عميل Cerbot مجددًا ولكن مع حذف الراية الخاص بالاختبار staging--:

docker run -it --rm -p 80:80 --name certbot \
         -v "/etc/letsencrypt:/etc/letsencrypt" \
         -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
         certbot/certbot certonly --standalone -d your_domain.com

ستظهر لك رسالة لاختيار الإجراء الذي ترغب بتنفيذه تجديد الشهادة أو استبدالها، اضغط على الرقم 2 لخيار التجديد ومن ثم زر الإدخال Enter وستحصل بذلك على شهادة TLS فعالة production، ثم شغل الآن حاوية nginx بالاعتماد على هذه الشهادة وفق التالي:

docker run --rm --name nginx -p 80:80 -p 443:443 \
    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
    -v /etc/letsencrypt:/etc/letsencrypt \
    -v /var/lib/letsencrypt:/var/lib/letsencrypt \
    -v /var/www/html:/var/www/html \
    nginx:1.19.0

استعرض التطبيق مجددًا بكتابة الرابط في المتصفح:

http://your_domain.com

لاحظ إعادة توجيه طلبك إلى HTTPS وظهور رسالة "لم يتم العثور على الصفحة" بسبب عدم إعداد توجيه افتراضي للتطبيق Polls، استخدم إذًا الرابط التالي:

https://your_domain.com/polls

وذلك لاستعراض واجهة التطبيق التي تشبه الصورة التالية:

01-img-polls_app.png

زودنا تطبيقنا لغاية الآن بشهادة TLS عبر حاوية Certbot ووكيل عكسي وموازن حمل لتوجيه وموازنة الطلبات الخارجية الواردة إلى خوادم التطبيق، و قبل الانتقال للخطوة /5/ الختامية يتبقى لدينا موضوع أخير وهو تجهيز آلية مناسبة لتجديد صلاحية TLS حيث أن الشهادات التي تعطيها Let’s Encrypt تحتاج إلى تجديد دوري كل 90 يومًا.

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

/var/www/html/.well-known/acme-challenge/

كذلك ستصل طلبات التحقق من Let’s Encrypt لهذا المسار وفق قواعد التوجيه الموجودة في سياقات الموقع ضمن ملف إعدادات Nginx، وعند إتمام عملية التجديد سنحتاج إلى إعادة تحميل Nginx ليستخدم الشهادات الجديدة الصالحة.

يلجأ المستخدمون عادةً إلى أتمتة هذه العملية حتى ينجزها Certbot تلقائيًا وذلك بعدة أساليب أشهرها إضافة العملية على قائمة المهام المجدولة لنظام التشغيل لينكس عبر تعليمة cron، يمكنك الاطلاع على هذه العملية ضمن مقالٍ آخر حول تأمين Node.js من DigitalOcean.

لننهي عمل حاوية Nginx بالضغط على الاختصار Ctrl+c ونعيد تشغيلها في الوضع المنفصل مع الراية d- وفق التالي:

docker run --rm --name nginx -d -p 80:80 -p 443:443 \
    -v ~/conf/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro \
    -v /etc/letsencrypt:/etc/letsencrypt \
    -v /var/lib/letsencrypt:/var/lib/letsencrypt \
  -v /var/www/html:/var/www/html \
    nginx:1.19.0

بينما يعمل خادم Nginx في الخلفية، سنشغل الحاوية certbot مع كل من إضافة webroot-- التي ستحدد مسار الجذر لخادم الويب، والراية dry-run-- لمحاكاة عملية التجديد والتأكد أن كافة الخيارات صحيحة دون القيام بالتجديد فعلًا:

docker run -it --rm --name certbot \
    -v "/etc/letsencrypt:/etc/letsencrypt" \
  -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
  -v "/var/www/html:/var/www/html" \
  certbot/certbot renew --webroot -w /var/www/html --dry-run

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

Cert not due for renewal, but simulating renewal for dry run
Plugins selected: Authenticator webroot, Installer None
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for your_domain.com
Using the webroot path /var/www/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/your_domain.com/fullchain.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/your_domain.com/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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

docker kill -s HUP nginx

حيث ترسل التعليمة HUP إشارة إلى خادم Nginx داخل الحاوية ليعيد تحميل الإعدادات ويستخدم الإعدادات الجديدة بما فيها الشهادات المجددة.

لننتقل الآن للخطوة الأخيرة وهي حماية خوادم الواجهات الخلفية للتطبيق عبر تقيّد الاتصالات الخارجية معها بالخادم الوكيل فقط.

الخطوة 5: منع الوصول الخارجي إلى خادمي تطبيق جانغو

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

إذًا الخادم Nginx في بنيتنا هو بوابة العبور بين الاتصالات الخارجية وخادمي جانغو ومن المفترض نظريًا أن لا يتصل أي عميل مع خوادم التطبيق إلا عبر هذه البوابة، ولكن تذكر الثغرة الأمنية المفتوحة التي واجهتنا في الخطوة /1/ حيث تمكن دوكر من تجاوز الجدار الناري UFW وفتح بوابات خارجية دون إعداد أي سياسات صريحة لفتح هذه البوابات على الجدار الناري.

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

لنبدأ الآن بتعديل إعدادات الجدار الناري UFW المشروحة في مستودع ufw-docker في GitHub لمنع الاتصالات الخارجية مع بوابات المضيف التي فتحها دوكر، فقد مررنا الراية p 80:8000- لحاويات خوادم التطبيق أثناء تشغيلها، ما يعني أن البوابة 80 على المضيف فُتحت ووُجّهت إلى البوابة 8000 على الحاوية وهي بذلك أصبحت متاحة أمام الاتصالات الخارجية أيضًا حيث أنها تمكننا من فتح الرابط:

http://your_app_server_1_IP

سجل الدخول إلى خادم التطبيق الأول وافتح الملف etc/ufw/after.rules/ بامتيازات sudo:

sudo nano /etc/ufw/after.rules

أدخل كلمة المرور واضغط زر الإدخال Enter وستظهر لك محتويات الملف after.rules وهي الإعدادات الخاصة بالجدار الناري UFW:

#
# rules.input-after
#
# Rules that should be run after the ufw command line added rules. Custom
# rules should be added to one of these chains:
#   ufw-after-input
#   ufw-after-output
#   ufw-after-forward
#

# Don't delete these required lines, otherwise there will be errors
*filter
:ufw-after-input - [0:0]
:ufw-after-output - [0:0]
:ufw-after-forward - [0:0]
# End required lines

# don't log noisy services by default
-A ufw-after-input -p udp --dport 137 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 138 -j ufw-skip-to-policy-input
-A ufw-after-input -p tcp --dport 139 -j ufw-skip-to-policy-input
-A ufw-after-input -p tcp --dport 445 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 67 -j ufw-skip-to-policy-input
-A ufw-after-input -p udp --dport 68 -j ufw-skip-to-policy-input

# don't log noisy broadcast
-A ufw-after-input -m addrtype --dst-type BROADCAST -j ufw-skip-to-policy-input

# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT

الصق التعليمات التالية في نهاية الملف السابق (ومصدرها مستودع ufw-docker كما ذكرنا):

. . .

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER

تُقيّد التعليمات السابقة الوصول العام إلى البوابات التي فتحها دوكر وتسمح بالوصول لعناوين الشبكة الخاصة فقط وهي هنا 10.0.0.0/8 و 172.16.0.0/12 و 192.168.0.0/16 في حال كنت تستخدم الشبكات الخاصة مثل VPC وبذلك فإن Droplets البوابة الخاصة بهذه الشبكة ستتمكن من الوصول إلى البوابات المفتوحة بينما تُمنع المصادر الخارجية من ذلك، لتعرف المزيد عن إعدادات UFW في هذه الحالة انظر ufw-docker how-it-works.

وفي حال أنك وضعت العناوين العامة Public IP لكل من خادمي التطبيق ضمن سياق الخوادم العليا في ملف إعدادات Nginx ولم تعتمد VPC، فعليك إذًا تعديل إعدادات UFW والسماح صراحةً بحركة البيانات القادمة من الخادم الوكيل Nginx إلى خوادم التطبيق عبر البوابة 80 وذلك عبر إنشاء سياسة خاصة بذلك باستخدام التعليمة allow، استعن لكتابتها بالمقال أساسيات IPTables - قواعد وأوامر شائعة للجدار الناري، واحفظ بعدها التغيرات وأغلق الملف.

أعد تشغيل الجدار الناري ufw ليأخذ الإعدادات الجديدة عبر الأمر:

sudo systemctl restart ufw

تصفح بعدها موقع تطبيقك:

http://APP_SERVER_1_IP

ولاحظ أنه لم يعد متاحًا فقد منعت الاتصالات المباشرة مع خادم التطبيق عبر البوابة 80.

سجل الخروج من الخادم الأول، وكرر الخطوات نفسها على خادم التطبيق الثاني وذلك عبر فتح الملف etc/ufw/after.rules/ بصلاحيات sudo:

sudo nano /etc/ufw/after.rules

ولصق التعليمات التالية في نهايته:

. . .
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN

-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12

-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER

أغلق الملف بعد حفظ التغييرات، ومن ثم أعد تشغيل الجدار الناري:

sudo systemctl restart ufw

وتصفح موقع الخادم ولاحظ أنه لم يعد متاحًا فقد منعت الاتصالات المباشرة مع هذا الخادم أيضًا.:

http://APP_SERVER_2_IP

افتح الآن الرابط التالي للتأكد من استمرار قدرة الخادم الوكيل على الوصول إلى التطبيق:

https://your_domain_here/polls

ولاحظ أن الوصول ممكن وواجهة التطبيق ستظهر أمامك في المتصفح.

الخاتمة

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

ونقترح عليك بعض الأفكار لتطوير إدارة هذه البيئة منها استخدام أداة التمهيد systemd لإدارة نظامك بشكلٍ أفضل والاطلاع على توثيق دوكر الخاص بالتشغيل التلقائي للحاويات لخيارات متخصصة في التشغيل التلقائي للحاويات، والاستعانة بإحدى أدوات تكوين الحاويات مثل Ansible أو Chef لتسهيل العمل في بعض البيئات التي يستخدم فيها أكثر من مضيف الحاوية نفسها.

دون أن ننسى إمكانية استخدام سجل الصور من Docker Hub وكذلك خط أنابيب pipeline لتسهيل نشر واختبار الصور على خوادم متعددة عوضًا عن إعادة بنائها وضبط إعداداتها مجددًا على كل خادم.

ترجمة -وبتصرف- للمقال How To Scale and Secure a Django Application with Docker, Nginx, and Let's Encrypt لصاحبه Hanif Jetha.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...