توفر البيئة السحابية حلولًا مرنة لتوسيع التطبيقات العاملة ضمنها مثل جانغو 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
- نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس
متطلبات بيئة العمل
ستحتاج المتطلبات التالية لتتمكن من التطبيق العملي للمقال:
- ثلاثة خوادم عليها نظام تشغيل أوبنتو (استعملنا في المقال إصدار 18.04 واستعن بمقال التهيئة الأولية لخادم أوبونتو 18.04):
- اثنان منها خوادم تطبيق لزوم تشغيل تطبيقك (جانغو/Gunicorn).
- خادم واحد يؤدي وظيفة الوكيل لزوم تشغيل Nginx و Certbot.
- مستخدم عادي -غير مسؤول- يتمتع بصلاحية sudo وجدار ناري فعال على الخوادم الثلاثة.
- تثبيت دوكر على الخوادم الثلاثة، اطلع على كيفية تثبيت دوكر واستخدامه على دبيان لإنجاز المهمة إذ طريقة التثبيت نفسها في النظامين.
- حجز اسم نطاق لتطبيقك، سنعتمد في مقالنا على الاسم your_domain.com، مع العلم بإمكانية حصولك على اسم نطاق مجاني من Freenom.
- ربط اسم النطاق بعنوان الـ IP العام للخادم الوكيل عبر إضافة سجل DNS من النوع A، يمكنك الحصول على تفاصيل إضافية حول الموضوع بالاطلاع على القسم الخاص بخدمة اسم النطاق DNS على أكاديمية حسوب.
- خدمة تخزين كائني متوافقة مع S3 ومع إضافة django-storages مثل DigitalOcean Space وظيفتها تأمين المساحة التخزينية اللازمة لملفات تطبيق جانغو الساكنة، مع مجموعة مفاتيح الوصول الخاصة بإدارة هذه المساحة، استرشد بكيفية إعداد مساحة على DigitalOcean Space وعدّل بما يلائم خدمة التخزين التي اخترتها.
- نظام إدارة قواعد بيانات متوافق مع جانغو مع إنشاء قاعدة بيانات ومستخدم خاص بالتطبيق، نحن اخترنا خادم 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
كما ترى فبعض محددات الملف قيمها موجودة والبعض الآخر يحتاج إلى ضبط بما يناسب بيئتك، سنذكر المحددات التي تحتاج إلى ضبط بالترتيب:
-
المفتاح السري
DJANGO_SECRET_KEY
: اختر له قيمة فريدة صعبة التخمين كما توصي توثيقات Docker نقدم لك في الخطوة /5/ من مقال كيفية إعداد تطبيق جانغو عبر DigitalOcean Managed Databases and Spaces إحدى طرق جانغو لتوليد المفاتيح. -
DJANGO_ALLOWED_HOSTS
: أسند له اسم النطاق الخاص بكyour_domain.com
، أو يمكنك وضع الرمز*
لأغراض الاختبار فقط وليس ضمن بيئة العمل الفعلية فهذا الرمز يسمح لأي عنوان IP بالوصول، يحميك هذا المحدد من الهجمات المعتمدة على حقن ترويسة HTTP وفي حال أردت المزيد من المعلومات اطلع على توثيقات جانغو الخاصة بهذا المحدد ضمن Core Settings -
DATABASE_USERNAME
: ضع اسم مستخدم قاعدة البيانات PostgreSQL الذي أنشأته أثناء إعدادك لمتطلبات العمل. -
DATABASE_NAME
: ضع القيمةpolls
أو الاسم الذي اخترته لقاعدة البيانات أثناء إعدادك لمتطلبات العمل. -
كذلك الأمر لكلمة المرور
DATABASE_PASSWORD
. -
ضع اسم مضيف قاعدة البيانات
DATABASE_HOST
ورقم بوابة الاتصال معهاDATABASE_PORT
-
أما المحددات
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 على سبيل المثال يمكنك تخطي هذا التحذير.
اكتب الرابط التالي 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
وذلك لاستعراض واجهة التطبيق التي تشبه الصورة التالية:
زودنا تطبيقنا لغاية الآن بشهادة 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.