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

نشر تطبيق جانغو آمن وقابل للتوسيع باستخدام كوبيرنتس Kubernetes


رشا سعد

هذا المقال هو الثالث من سلسلة تعليمية تتضمن ثلاث مقالات عن الانتقال من الحاويات إلى كوبيرنتس Kubernetes باستخدام جانغو Django إطار العمل الخاص بتسريع تطوير تطبيقات الويب المبنية بلغة بايثون، في المقال الأول تعلمنا تعديل عينة تجريبية من تطبيق جانغو (تسمى التطبيق Polls) وفق منهجية Twelve-Factor الخاصة بتطوير تطبيقات ويب سحابية قابلة للتوسع والعمل ضمن الحاويات، وفي المقال الثاني تعرفنا على طرق توسيع هذا التطبيق (المعدّل المغلف في حاوية دوكر) توسيعًا أفقيًا وتأمينه بتشغيل وكيل عكسي أمامه هو خادم Nginx مع استخدام شهادات TLS مصدقة من Let's Encrypt، أما اليوم نشارككم كيفية نشر التطبيق المعدّل نفسه باستعمال عنقود كوبيرنتس Kubernetes Cluster.

كوبيرنتس kubernetes هو حل مفتوح المصدر لإدارة الحاويات، ميزته الأساسية أتمتة عمليات نشر وتوسيع وإدارة تطبيقات الويب المغلفة ضمن حاويات، وذلك من خلال مجموعة من الكائنات الخاصة مثل خرائط الإعدادات ConfigMaps والأسرار Secrets التي تسمح بتهيئة مركزية للبيئة من خارج الحاويات، ووحدات التحكم مثل النشر Deployments المسؤولة عن إعادة تشغيل الحاويات تلقائيًا في حال حدوث أي خلل وعن التوسعة التلقائية بتشغيل حاويات إضافية مماثلة لحاوية التطبيق عند ازدياد الطلب عليه، وأيضًا كائن الإدخال ingress أو المُدخل (تترجم أحيانًا إلى كائن الولوج واعتمدنا هنا كائن الإدخال والمُدخل حسب سياق الجملة) المسؤول عن توجيه حركة البيانات HTTP أو HTTPS المشفرة بشهادات TLS من المصادر الخارجية إلى خدمات كوبيرنتس الداخلية عبر قواعد وصول محددة مع وحدة التحكم مفتوحة المصدر ingress-nginx، أما التجديد الدوري للشهادات وتصديقها من Let's Encrypt تنجزه الوظيفة الإضافية مدير الشهادات cert-manager.

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

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

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

  1. عنقود كوبيرنتس بإصدار 1.15 أو أعلى، مع تفعيل خاصية التحكم بالوصول المستند على الدور Role-based access control (RBAC)، استخدمنا في المقال عنقود خدمة DigitalOcean، لكنك لست ملزمًا بذلك يمكنك إعداده بنفسك بالطريقة التي تناسبك.
  2. تثبيت أداة سطر الأوامر kubectl على جهازك المحلي وإعدادها للاتصال بالعنقود، يمكنك معرفة المزيد عن تثبيت kubectl بالاطلاع على توثيقات كوبيرنتس الرسمية. أما إن اعتمدت DigitalOcean فاستعن بالمقال إعداد الاتصال مع عنقود كوبيرنتس في DigitalOcean.
  3. حجز اسم نطاق لتطبيقك، سنعتمد في مقالنا على الاسم your_domain.com، مع العلم بإمكانية حصولك على اسم نطاق مجاني من Freenom.
  4. تثبيت وحدة التحكم ingress-nginx ومدير الشهادات cert-manager وضبط الإعدادات اللازمة للتحقق من الشهادات، يمكنك الاطلاع على كيفية إعداد Nginx Ingress ومدير الشهادات على كوبيرنتس.
  5. ربط اسم النطاق بعنوان IP العام لمُدخل موازن الحمل عبر إضافة سجل DNS من النوع A، يمكنك الحصول على تفاصيل إضافية حول DNS بالاطلاع على القسم الخاص بخدمة اسم النطاق DNS على أكاديمية حسوب.
  6. خدمة تخزين كائني متوافقة مع S3 ومع إضافة django-storages مثل DigitalOcean Space وظيفتها تأمين المساحة التخزينية اللازمة لملفات تطبيق جانغو الساكنة، مع مجموعة مفاتيح الوصول الخاصة بإدارة هذه المساحة، استرشد بكيفية إعداد مساحة على DigitalOcean Space وعدّل بما يلائم خدمة التخزين التي اخترتها.
  7. نظام إدارة قواعد بيانات متوافق مع جانغو مع إنشاء قاعدة بيانات ومستخدم خاص بالتطبيق، نحن اخترنا خادم PostgreSQL، اهتم بالتفاصيل التالية أثناء إنجازك لهذه النقطة لتتمكن من متابعة خطوات المقال:
  • استخدم الاسم polls لقاعدة البيانات والاسم sammy لمستخدم قاعدة البيانات (هذه الأسماء ليست ملزمة ولكننا استخدمناها في المقال واستخدامك لها سيسهل عليك المتابعة)، لمزيد من التفاصيل حول الإنشاء استرشد بالخطوة /1/ من مقالنا السابق [بناء تطبيق جانغو بخادم Gunicorn ووضعه ضمن حاوية دوكر]()، مع العلم أن هذه المهمة يمكن إنجازها من أي خادم من الخوادم الثلاثة لبيئة العمل.
  • اعتمدنا في المقال على نظام إدارة قواعد البيانات من نوع Managed PostgreSQL cluster الذي توفره DigitalOcean.
  • لكن يمكنك تثبيت PostgreSQL وتشغيله وإعداده بنفسك مع الاستعانة بالفيديو التعليمي تثبيت وإعداد قاعدة بيانات PostgreSQL.
  1. حساب على Docker Hub، استرشد بتوثيقات دوكر الخاصة بالمستودعات لإنشائه.
  2. تثبيت دوكر على جهازك المحلي، استعن لذلك بمقال كيفية تثبيت دوكر واستخدامه على دبيان.

الخطوة 1: استنساخ التطبيق وضبط إعداداته

تتلخص هذه الخطوة باستنساخ كود التطبيق من مستودع GitHub بالإضافة إلى ضبط محددات الاتصال مع خادم قاعدة البيانات والمفاتيح الخاصة بخدمة التخزين الكائني المستخدمة.

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

اكتب الأمر التالي للاستنساخ:

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

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

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"]

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

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

لنبني صورة الحاوية باستخدام 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

لنضبط متغيرات البيئة تمهيدًا لعملية التشغيل docker run، وذلك بتعديل الملف env باستعمال محرر النصوص:

nano env

سيظهر أمامك الملف 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 بعد الانتهاء وأغلقه، ولننتقل للخطوة التالية.

الخطوة 2: إنشاء مخطط قاعدة البيانات ورفع ملفات التطبيق إلى وحدة التخزين الكائني

شغل صورة الحاوية polls:latest عبر الأمر التالي:

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

وبموجبه الأمر سيجري تجاوز الأمر الافتراضي عند بدء التشغيل المحدد بتعليمة CMD ضمن الملف 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 polls 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

الحصول على واجهة polls

اكتب الرابط التالي:

 http://APP_SERVER_1_ip/admin

وستتمكن من الوصول إلى واجهة الدخول للوحة التحكم الخاصة بالتطبيق:

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

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

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

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

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

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

الخطوة 3: رفع صورة جانغو إلى Docker Hub

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

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

سنقدم في مثالنا طريقة سحب الصور من سجل Docker Hub عام ويمكنك الاطلاع على توثيقات كوبيرنتس لمعرفة طريقة العمل مع السجلات الخاصة.

لنبدأ بتسجيل الدخول إلى Docker Hub وفق الحساب الذي ذكرناه في متطلبات العمل:

docker login

سيُطلب منك إدخال اسم المستخدم وكلمة المرور لحسابك وفق التالي:

Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username:

تحمل صورة التطبيق في بيئتنا الوسم polls:latest أعطها وسمًا جديدًا يتضمن اسم حسابك على Docker Hub واسم مستودعك باستخدام tag وفق التالي:

docker tag polls:latest your_dockerhub_username/your_dockerhub_repo_name:latest

استعملنا في مثالنا الاسم sammy للحساب و sammy-django للمستودع وبذلك يصبح الأمر على الشكل التالي:

docker push sammy/sammy-django:latest

ستحصل على خرج يتضمن تفاصيل عملية الرفع، وبعد إتمام العملية ستصبح الصورة متاحة أمام كوبيرنتس على Docker Hub.

الخطوة 4: إعداد خريطة الإعدادات ConfigMap

خريطة الإعدادات ConfigMap هي كائن كوبيرنتس المسؤول عن تمرير متغيرات بيئة التشغيل العامة إلى العنقود مثل إعدادات التطبيق، ويشاركه المهمة كائن آخر يدعى السرّ Secret إلاّ أنه يُعنى بالمتغيرات الحساسة مثل البيانات الخاصة بواجهات التخاطب API أو بقاعدة البيانات ولذا فهو يخضع لقواعد خاصة لحمايته والتحكم بوصول المستخدمين إليه مثل تفعيل خاصية encryption at rest، وبناءً على هذا الاختلاف في طبيعة بيانات الكائنين فإن بيانات ConfigMap تخزن بشكل نصي صريح ومقروء للمستخدم، بينما تخزن بيانات Secret معماة بأسلوب base64.

تتشابه هذه الآلية في الواقع مع تمرير متغيرات البيئة لحاوية دوكر بالملف env الذي يلقم هذه المعلومات لأمر تشغيل الحاوية docker-run.

لنبدأ بإعداد ConfigMap، والخطوة الأولى هي إنشاء مجلد يدعى yaml سنخزن ضمنه وثائق كوبرنيتس الأساسية:

mkdir yaml
cd

ومن ثم أنشئ ضمن المجلد ملف نصي باسم polls-configmap.yaml يتضمن خريطة الإعدادات وفق التالي:

nano polls-configmap.yaml

وهذا يعني أننا بصدد إنشاء كائن كوبرنتس من نوع ConfigMap يحمل الاسم polls-config.

الآن لننتقي من الملف env (الذي عملنا عليه في الخطوة /1/) البيانات العامة للتطبيق التي لا تتمتع بحساسية خاصة ونكتبها في الملف polls-configmap.yaml وذلك وفق الصيغة التالية:

apiVersion: v1
kind: ConfigMap
metadata:
  name: polls-config
data:
  DJANGO_ALLOWED_HOSTS: "*"
  STATIC_ENDPOINT_URL: "https://your_space_name.space_region.digitaloceanspaces.com"
  STATIC_BUCKET_NAME: "your_space_name"
  DJANGO_LOGLEVEL: "info"
  DEBUG: "True"
  DATABASE_ENGINE: "postgresql_psycopg2"

بالطبع مع وضع نفس القيم الموجودة في env وبخصوص المحدد DJANGO_ALLOWED_HOSTS نعطيه القيمة * كوننا نعمل في بيئة تجريبية.

نحفظ بعدها التغييرات ونغلق الملف تمهيدًا لإنشاء الكائن ConfigMap بالأمر الأمر التالي:

kubectl apply -f polls-configmap.yaml

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

configmap/polls-config created

الخطوة 5: إعداد السر Secret

بيانات الكائن من نوع سرّ secret هي بيانات حساسة ومهمة كما ذكرنا سابقًا لذا فهي تُعمَّى بالأساس64، ولتحقيق ذلك لديك طريقتين إما ترميز البيانات خارجيًا من ثم كتابتها معماة ضمن الملف الذي سنعدّه للكائن، أو استخدام الأمر kubectl create مع الراية from-env-file-- وهي الطريقة التي استعملناها هنا.

نعود للملف env استخدمنا متغيراته العامة في إنشاء الكائن Configmap والآن سنستخدم بقية المتغيرات التي تعد بيانات حساسة لإعداد الكائن secret.

لننتقل للتطبيق العملي، انسخ الملف env إلى المجلد yaml وسمي النسخة polls-secrets وفق التالي:

cp ../env ./polls-secrets

افتح الملف polls-secrets باستعمال محرر النصوص:

nano polls-secrets

ستحصل على بيانات الملف 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

الآن احذف منه المتغيرات التي كتبناها ضمن ConfigMap في الخطوة السابقة، وستبقى المتغيرات التالية ضمنه:

DJANGO_SECRET_KEY=your_secret_key
DATABASE_NAME=polls
DATABASE_USERNAME=your_django_db_user
DATABASE_PASSWORD=your_django_db_user_password
DATABASE_HOST=your_db_host
DATABASE_PORT=your_db_port
STATIC_ACCESS_KEY_ID=your_space_access_key
STATIC_SECRET_KEY=your_space_access_key_secret

ضع القيم الخاصة بتطبيقك نفسها التي ضبطناها في الخطوة /1/ من المقال (مثل محددات قاعدة البيانات والمفتاح الخاص وغيرها)، واحفظ التغييرات على الملف polls-secrets وأغلقه.

أنشئ الآن الكائن من نوع secret والذي يحمل الاسم polls-secret من خلال الأمر التالي مع تمرير المتغيرات اللازمة للعنقود عبر الملف polls-secrets الذي أغلقته توًا:

kubectl create secret generic polls-secret --from-env-file=poll-secrets

وستحصل على الخرج الذي يبين نجاح الإنشاء:

secret/polls-secret created

استعرض بعدها وصف الكائن باستخدام kubectl describe:

kubectl describe secret polls-secret

لاحظ القيم المرمزة في الخرج:

Name:         polls-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
DATABASE_PASSWORD:     8 bytes
DATABASE_PORT:         5 bytes
DATABASE_USERNAME:     5 bytes
DJANGO_SECRET_KEY:     14 bytes
STATIC_ACCESS_KEY_ID:  20 bytes
STATIC_SECRET_KEY:     43 bytes
DATABASE_HOST:         47 bytes
DATABASE_NAME:         5 bytes

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

الخطوة 6: نشر التطبيق باستخدام وحدة تحكم النشر

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

تتحكم وحدة النشر Deployment بواحد أو أكثر من كائنات Pod، أما Pod فهي أصغر عناصر عنقود كوبيرنتس وكل واحدة منها يمكن أن تتضمن حاوية أو أكثر، لمعرفة المزيد يمكنك الاطلاع على تعلم أساسيات Kubernetes على أكاديمية حسوب.

افتح ملفًا جديدًا يحمل الاسم polls-deployment.yaml عبر أي محرر تفضله وليكن عبر nano مثلًا:

nano polls-deployment.yaml

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

apiVersion: apps/v1
kind: Deployment
metadata:
  name: polls-app
  labels:
    app: polls
spec:
    replicas: 2
  selector:
    matchLabels:
      app: polls
  template:
    metadata:
      labels:
        app: polls
    spec:
      containers:
        - image: your_dockerhub_username/app_repo_name:latest
          name: polls
          envFrom:
          - secretRef:
              name: polls-secret
          - configMapRef:
              name: polls-config
          ports:
            - containerPort: 8000
              name: gunicorn

أكتب اسم الصورة التي بنيناها في الخطوة /2/ ولا تنسَ الإشارة للاسم الذي رفعنا به الصورة على Docker Hub.

لنشرح التعليمات السابقة بالترتيب.

أطلقنا على وحدة التحكم الاسم polls-app وميّزناها بعنوان label ذا القيمة المزدوجة app: polls، وضبطنا عدد نسخ Pods المطلوب تشغيلها بنسختين وفق replicas: 2 أما إعدادات هذه Pods فتُحددها المواصفات المكتوبة تحت template.

أما configMapRef و secretRef المستخدمين بعد envFrom فيحددان على الترتيب قيم المتغيرات التي عرفناها من خلال كائن خريطة الإعدادات واسمه polls-config والكائن سرّ واسمه polls-secret، وفي نهاية الملف عرفنا بوابة الحاوية containerPort وهي تحمل الرقم 8000 والاسم gunicorn.

يمكنك تعلم المزيد عن وحدة تحكم النشر من خلال توثيقات كوبيرنتس.

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

kubectl apply -f polls-deployment.yaml

تحصل على الخرج الذي يؤكد نجاح العملية:

deployment.apps/polls-app created

يمكنك أيضًا التأكد من الإنشاء عبر الأمر:

kubectl get deploy polls-app

الذي يعطي النتيجة:

NAME        READY   UP-TO-DATE   AVAILABLE   AGE
polls-app   2/2     2            2           6m38s

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

kubectl describe deploy

أما لفحص الـ Pods وحالتها فاستخدم:

kubectl get pod

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

NAME                         READY   STATUS    RESTARTS   AGE
polls-app-847f8ccbf4-2stf7   1/1     Running   0          6m42s
polls-app-847f8ccbf4-tqpwm   1/1     Running   0          6m57s

الخطوة 7: السماح للمصادر الخارجية بالوصول إلى الخدمة

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

تتعدد أنواع خدمات كوبيرنتس، مثل خدمة ClusterIP التي تعتمد على عنوان IP الداخلي للعنقود، وخدمة NodePort التي تعرض الخدمة على بوابة محددة من كل عقدة، وخدمة LoadBalancer التي توفر موازنة حمل سحابية لحركة مرور البيانات القادمة من المصادر الخارجية إلى Pods داخل العنقود وذلك عبر خدمات NodePorts الخاصة بكل عقدة، والتي يتم إنشاؤها تلقائيًا في هذه الحالة.

يمكنك تعلم المزيد عن هذه الخدمات من خلال توثيقات كوبيرنتس.

لنبدأ بإنشاء الخدمة الخاصة بتطبيق جانغو وسنعتمد النوع NodePort.

أنشئ ملفًا باسم polls-svc.yaml باستعمال محرر النصوص الذي تفضله وليكن باستعمال نانو nano:

nano polls-svc.yaml

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

apiVersion: v1
kind: Service
metadata:
  name: polls
  labels:
    app: polls
spec:
  type: NodePort
  selector:
    app: polls
  ports:
    - port: 8000
      targetPort: 8000
    ```

لاحظ التعليمات السابقة، نوع الخدمة هو NodePort، اسمها polls وهي مميزة بالعنوان app: polls، وحددنا من خلال مُحدِّد selector الربط مع Pods الواجهات الخلفية بالعنوان app: polls و البوابة 8000.

أغلق الملف بعد حفظ التغييرات، ومن ثم أنشئ الخدمة بالأمر kubectl apply:

kubectl apply -f polls-svc.yaml

ونحصل على ما يؤكد الإنشاء وفق الخرج التالي:

service/polls created

استعرض الخدمة وتأكد من إنشائها عبر kubectl get svc:

kubectl get svc polls

وسيظهر لك الخرج التالي، الذي يتضمن عنوان IP الداخلي للعنقود وبوابة العقدة NodePort وهي في حالتنا 32654:

NAME    TYPE       CLUSTER-IP       EXTERNAL-IP     PORT(S)           AGE
polls   NodePort   10.245.197.189        <none>           8000:32654/TCP   59s

حصلنا على رقم البوابة، ونحتاج إلى عنوان IP الخارجي للخدمة للاتصال مع التطبيق، وسنحصل عليه بالأمر التالي:

kubectl get node -o wide

الذي يعطينا الخرج:

NAME                   STATUS   ROLES    AGE   VERSION   INTERNAL-IP   EXTERNAL-IP      OS-IMAGE                       KERNEL-VERSION          CONTAINER-RUNTIME
pool-7no0qd9e0-364fd   Ready    <none>   27h   v1.18.8   10.118.0.5    203.0.113.1   Debian GNU/Linux 10 (buster)   4.19.0-10-cloud-amd64   docker://18.9.9
pool-7no0qd9e0-364fi   Ready    <none>   27h   v1.18.8   10.118.0.4    203.0.113.2    Debian GNU/Linux 10 (buster)   4.19.0-10-cloud-amd64   docker://18.9.9
pool-7no0qd9e0-364fv   Ready    <none>   27h   v1.18.8   10.118.0.3    203.0.113.3   Debian GNU/Linux 10 (buster)   4.19.0-10-cloud-amd64   docker://18.9.9

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

http://203.0.113.1:32654/polls

من المفترض حصولك على واجهة التطبيق نفسها التي استعرضتها محليًا في الخطوة /1/:

نتيجة واجهة التطبيق

تصفح أيضًا واجهة إدارة التطبيق admin/ للتأكد أكثر:

http://203.0.113.1:32654/admin

واجهة التطبيق

إذًا تطبيق جانغو الآن يستخدم حاويتين متماثلتين ويمكن الوصول له من خارج العنقود عبر خدمة كوبيرنتس مستقرة من نوع NodePort، والخطوة الأخيرة المتبقية لنا هي تأمين هذا الوصول ليتم عبر HTTPS وذلك بالاستفادة من وحدة التحكم ingress-nginx (وهي من متطلبات بيئة العمل المفترض تحضيرها) وبإنشاء كائن إدخال يوجه الحركة الخارجية إلى الخدمة polls.

الخطوة 8: إعداد HTTPS باستخدام Nginx Ingress ومدير الشهادات

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

لابد أنك جهزت المتطلبات الواردة في بداية المقال وأنت تحضر بيئة العمل، ومنها تثبيت وحدة التحكم Ingress-nginx والوظيفة الإضافية cert-manager الخاصة بأتمتة شهادات TLS، مع ما يلزم لتصديق النطاق من Let’s Encrypt، وفي أثناء العملية أنشأت كائن إدخال لاختبار صلاحية الشهادة والتشفير TLS مع اثنين من خدمات الواجهات الخلفية الوهمية المنشأة لهذا الغرض، لذا سنبدأ بحذف echo-ingress الذي أنشأته خلال التحضير وفق التالي:

kubectl delete ingress echo-ingress

يمكنك أيضًا حذف الخدمات الوهمية المنشأة أثناء التحضير عبر التعليمتين

kubectl delete svc 
kubectl delete deploy

لكنه ليس أمرًا ملزمًا في حالتنا.

لديك أيضًا (من متطلبات بيئة العمل) سجل DNS من النوع A يربط اسم لنطاقك بعنوان الـ IP العام لمُدخل موازن الحمل، فيمكنك إذًا إنشاء مُدخل لنطاقك your_domain.com والخدمة polls.

أنشئ ملف polls-ingress.yaml باستخدام محرر النصوص:

nano polls-ingress.yaml

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

[polls-ingress.yaml]
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: polls-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-staging"
spec:
  tls:
  - hosts:
    - your_domain.com
    secretName: polls-tls
  rules:
  - host: your_domain.com
    http:
      paths:
      - backend:
          serviceName: polls
          servicePort: 8000

بموجب التعليمات السابقة، وصّفنا كائن إدخال يدعى polls-ingress ليستخدم وحدة التحكم ingress-nginx ووظيفة مدير الشهادات cert-manager مع ضبط ClusterIssuer الخاص بها على خيار staging، كما فعّلنا الشهادات TLS لترتبط باسم النطاق الخاص بنا your_domain.com أما ملف الشهادة والمفتاح الخاص تم تخزينهم في كائن من نوع سرّ يدعى polls-tls، وأخيرًا وجهّنا حركة البيانات التي تطلب النطاق your_domain.com لتمر عبر البوابة 8000 من الخدمة polls.

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

kubectl apply -f polls-ingress.yaml

سنحصل بعدها على الخرج:

ingress.networking.k8s.io/polls-ingress created

يمكنك استعراض وصف الكائن وتتبع حالته باستخدام kubectl describe:

kubectl describe ingress polls-ingress

التي تعطي الخرج:

Name:             polls-ingress
Namespace:        default
Address:          workaround.your_domain.com
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
  polls-tls terminates your_domain.com
Rules:
  Host        Path  Backends
  ----        ----  --------
  your_domain.com
                 polls:8000 (10.244.0.207:8000,10.244.0.53:8000)
Annotations:  cert-manager.io/cluster-issuer: letsencrypt-staging
              kubernetes.io/ingress.class: nginx
Events:
  Type    Reason             Age   From                      Message
  ----    ------             ----  ----                      -------
  Normal  CREATE             51s   nginx-ingress-controller  Ingress default/polls-ingress
  Normal  CreateCertificate  51s   cert-manager              Successfully created Certificate "polls-tls"
  Normal  UPDATE             25s   nginx-ingress-controller  Ingress default/polls-ingress

وبنفس الطريقة تستطيع استعراض وصف polls-tls للتأكد من صحته:

kubectl describe certificate polls-tls

وستظهر لك حالة الشهادة في الخرج:

. . .
Events:
  Type    Reason     Age    From          Message
  ----    ------     ----   ----          -------
  Normal  Issuing    3m33s  cert-manager  Issuing certificate as Secret does not exist
  Normal  Generated  3m32s  cert-manager  Stored new private key in temporary Secret resource "polls-tls-v9lv9"
  Normal  Requested  3m32s  cert-manager  Created new CertificateRequest resource "polls-tls-drx9c"
  Normal  Issuing    2m58s  cert-manager  The certificate has been successfully issued

بذلك نكون قد وجّهنا الطلبات الواردة إلى التطبيق لتصبح HTTPS مشفرة، ولكن Clusterissuer في وضع staging ما يعني أن شهادتنا التجريبية المزيفة من Let's Encrypt لن تحظى بثقة معظم متصفحات الويب، أكتب الطلب التالي لنطاقك في نافذة سطر الأوامر:

wget -O - http://your_domain.com/polls

ولاحظ التحذير الظاهر في الخرج بسبب هذه الشهادة:

. . .
ERROR: cannot verify your_domain.com's certificate, issued by ‘CN=Fake LE Intermediate X1’:
  Unable to locally verify the issuer's authority.
To connect to your_domain.com insecurely, use `--no-check-certificate'.

سنتبع المقترح الوارد في الخرج أعلاه ونستخدم الراية no-check-certificate-- مع الطلب wget لتجاوز التحذير الخاص بالشهادة وفق التالي:

wget --no-check-certificate -q -O - http://your_domain.com/polls

ستحصل عندها على الخرج المبين أدناه والذي يظهر صفحة HTML المقابلة للواجهة polls/ وأيضًا مسار التخزين الكائني الذي أُحضرت منه الصفحة:

<link rel="stylesheet" type="text/css" href="https://your_space.nyc3.digitaloceanspaces.com/django-polls/static/polls/style.css">


    <p>No polls are available.</p>

وهذا دليل على نجاح طلب النطاق، ما يعني أن بإمكاننا الآن تبديل Clusterissuer إلى وضع المنتج production عوضًا عن staging عبر فتح الملف polls-ingress.yaml:

nano polls-ingress.yaml

وتعديل قيمة cluster-issuer كما ذكرنا:

[polls-ingress.yaml]
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: polls-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - your_domain.com
    secretName: polls-tls
  rules:
  - host: your_domain.com
    http:
      paths:
      - backend:
          serviceName: polls
          servicePort: 8000

احفظ التغييرات على الملف وأغلقه، ولكن انتبه فهي لن تصبح نافذة حتى تُحدّث الكائن polls-ingress باستخدام التعليمة kubectl apply:

kubectl apply -f polls-ingress.yaml

سيشير الخرج إلى نجاح العملية:

ingress.networking.k8s.io/polls-ingress configured

شاهد التغير الذي طرأ على حالة الشهادات باستخدام:

kubectl describe certificate polls-tls

أو

kubectl describe ingress polls-ingress

وستحصل على الخرج التالي الذي يبين نجاح التحقق من الشهادات الجديدة وكونها مخزنة في السرّ polls-tls:

. . .
Events:
  Type    Reason             Age                From                      Message
  ----    ------             ----               ----                      -------
  Normal  CREATE             23m                nginx-ingress-controller  Ingress default/polls-ingress
  Normal  CreateCertificate  23m                cert-manager              Successfully created Certificate "polls-tls"
  Normal  UPDATE             76s (x2 over 22m)  nginx-ingress-controller  Ingress default/polls-ingress
  Normal  UpdateCertificate  76s                cert-manager              Successfully updated Certificate "polls-tls"

استعرض الآن تطبيقك

 your_domain.com/polls 

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

نتيجة تفعيل HTTPS

ننصحك بإجراء اختياري أخير هو تعديل نوع الخدمة polls من NodePort إلى ClusterIP إذ إن هذا النوع يقبل فقط الحركة القادمة من عنوان IP الداخلي للعنقود ما يمنح تطبيقك مزيدًا من الأمان:

افتح الملف polls-svc.yaml باستخدام المحرر:

nano polls-svc.yaml

بدل قيمة المحدد type من NodePort إلى ClusterIP:

apiVersion: v1
kind: Service
metadata:
  name: polls
  labels:
    app: polls
spec:
  type: ClusterIP
  selector:
    app: polls
  ports:
    - port: 8000
      targetPort: 8000

احفظ التغييرات على الملف وأغلقه، وحدّث بعدها الخدمة عبر الأمر:

kubectl apply -f polls-svc.yaml --force

سيؤكد لك الخرج التالي نجاح تعديل الخدمة:

service/polls configured

تحقق من حالة الخدمة المحدثة باستخدام:

kubectl get svc polls

ولاحظ معلومات الخرج:

NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
polls   ClusterIP   10.245.203.186   <none>        8000/TCP   22s

وبذلك قيدّت الوصول لتطبيقك ليكون حصرًا من خلال اسم النطاق والمُدخل المنشأ في هذه الخطوة.

خاتمة

تعلمنا في هذا المقال طريقة نشر تطبيق جانغو قابل للتوسيع ومؤمن ببروتوكول HTTPS باستخدام عنقود كوبيرنتس، مع تسليم ملفات التطبيق الساكنة من وحدة تخزين كائني خارجية، وتوفير المرونة التامة لزيادة أو إنقاص عدد Pods التي تُخدم التطبيق بسرعة وبسهولة عبر المحدد replicas من ملف الإعدادات الخاص بوحدة تحكم النشر Deployment والتي أسميناها polls-app، ونذكرك بإمكانية تسريع التسليم باستخدام شبكة توصيل المحتوى CDN كإجراء اختياري.

ترجمة -وبتصرف- للمقال How To Deploy a Scalable and Secure Django Application with Kubernetes لصاحبه 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.


×
×
  • أضف...