انشر محرّك AI على أيّ جهاز باستخدام Docker و Docker Compose

Ismail Mebsout
٢٣ أكتوبر ٢٠٢٤
٧ دقائق
جدول المحتويات

عند العمل على مشاريع AI، نميل إلى إجراء كل التطوير الأوّلي على حواسيبنا المحمولة ثم نوصل ذلك إلى العميل. وقد يحدث ألّا يتشارك العميل نظام التشغيل ذاته، ما يعني أنّ الكود قد لا يعمل على جانبه. وهنا يأتي دور Docker!

في هذا المقال، سنستعرض الدوافع الرئيسية وراء Docker وكيف يتيح بناء منتجات لا تعتمد على نظام التشغيل بما يُمكّن من تطوير ونشر أسرع.

الملخّص كالتالي:

  1. ما هو Docker؟
  2. الإعداد
  3. من مشروع AI إلى Docker image
  4. Docker compose
  5. أفضل الممارسات

ما هو Docker

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

Docker vs VM

اختُرع Docker أساساً لجعل البرامج لا تعتمد على نظام التشغيل من أجل تسهيل التطوير والنشر. ويستطيع Data Scientist مثلاً كتابة برنامجه على macOS وتشغيله باستخدام Docker على البنية التحتية لتقنية المعلومات لدى العميل بغضّ النظر عن نظام تشغيله (macOS أو linux أو Windows …).

Architecture

يقدّم Docker مفاهيم مختلفة:
• Dockerfile: مجموعة من الإجراءات تُنفَّذ مع caching بالترتيب المكتوب
• Docker image: نظام الملفّات والتثبيتات (من Dockerfile) الذي ستحتاج إليه لتشغيل برنامجك دون أيّ عملية مرفقة
• Docker container: هو instance من الـ image يستضيف نسخة من جميع الملفّات والبرامج اللازمة مع عملية مرفقة وتفاعلية عبر terminal
• .dockerignore: ملفّ يستضيف جميع مسارات العناصر التي لا تريد استضافتها في docker image
entrypoint.sh: ملفّ cmd يحدّد الأوامر التي تُطلق عند تشغيل الـ container

يقدّم Docker أيضاً Dockerhub، وهي خدمة سحابية يمكنها استضافة Docker images مشتركة يمكن للفرق دفعها وسحبها. كما تستضيف صور لغات رسمية مثل صورة Python.

الإعداد

يمكنك تثبيت Docker باستخدام موقعه الرسمي عن طريق اختيار نظام التشغيل المثبَّت على آلتك.

بمجرّد اكتمال التثبيت، شغّل Docker وقم بتنفيذ سطر الأوامر التالي للتحقّق من أنّ كل شيء على ما يرام:

docker run hello-world
Docker hello-world

من مشروع AI إلى Docker image

الإعداد

تأتي مرحلة "dockerization" لمشروع AI الخاص بك بمجرّد استقرار التطوير، الذي افترضنا أنّه مكتوب بلغة python.

تُوضع الملفّات Dockerfile و.dockerignore وentrypoint.sh في جذر المستودع كما يلي (راجع مقالي السابق حول تنظيم المشاريع):

Docker repository's structure

+نُعرّف entrypoint.sh كما يلي:

#!/bin/bash
set -xe
case $1 in

    test)
    python -m pytest -v --tb=line tests/unit_testing.py
    action_1)
    python app.py fonction_1;;

    action_2)
    python app.py fonction_2;;
esac

+لإنشاء Dockerfile 🐳:

FROM python:3.7-slim-buster
  • قم بتثبيت الـ modules الضرورية باستخدام apt-get (liblept5 مثلاً):
RUN apt-get update -y\
    && apt-get install -y liblept5
  • قم بإنشاء المجلّد data/ الذي سيكون working directory:
RUN mkdir /data
WORKDIR /data
  • نقوم بنسخ جميع ملفّات ومجلّدات المستودع المحلي إلى المجلّد /data من Docker image باستثناء تلك المذكورة في .dockerignore :
ADD . /data
  • نقوم بتثبيت Virtualenv وإنشاء project_vir_env وتفعيله ثم تثبيت جميع متطلّبات python فيه:
RUN pip install virtualenv
RUN virtualenv project_vir_env
RUN . project_vir_env/bin/activate
RUN pip install -r packages.txt
  • نضيف صلاحيات التنفيذ لملفّ entrypoint ونعيّنه كما يلي:
RUN chmod +x /data/entrypoint.sh
ENTRYPOINT ["data/entrypoint.sh"]

الـ Dockerfile كما يلي:

FROM python:3.7-slim-buster
RUN apt-get update -y \
    && apt-get install -y liblept5 \

RUN mkdir /data
WORKDIR /data
ADD . /data
RUN pip install virtualenv
RUN virtualenv project_vir_env
RUN . project_vir_env/bin/activate
RUN pip install -r packages.txt
RUN chmod +x /data/entrypoint.sh
ENTRYPOINT [ "/data/entrypoint.sh"]

+نُعرّف أيضاً .dockerignore:

project_vir_env/
notebooks/
.vscode/
**/__pycache__/
.DS_Store

الإطلاق

من أجل بناء الـ docker image، قم أوّلاً بتشغيل الـ docker engine ثم بتنفيذ سطر الأوامر التالي:

docker build -t nameofdockerimage .
  • -t : لتسمية الـ image
  • . : مكان الـ Dockerfile (المجلّد الحالي)

يمكنك تشغيل container باستخدام سطر الأوامر التالي:

docker run -v $PWD:/data/ nameofdockerimage entrypointfunction
  • -v: تُستخدم لتعريف volume وتربط directory الحالي (مستودع المشروع) بالمجلّد data/ الخاصّ بالـ container
  • nameofdockerimage: هو الاسم نفسه المستخدَم في مرحلة البناء
  • entrypointfunction: هي إحدى الدوال المعرَّفة في entrypoint.sh (test أو action_1 أو action_2)

يمكنك مراجعة هذه cheat sheet للحصول على أوامر docker المتاحة.

Docker compose

عند العمل على مشروع معقّد، قد يتألّف المنتج النهائي من خدمات مختلفة مثل frontend وDS API وقاعدة بيانات. ويُعدّ Docker-compose orchestrator لـ docker يساعدك على "dockerize" وتنظيم كل خدماتك بصيغة multi-containers.

من باب التوضيح، سنعتبر تطبيق python بـ Streamlit frontend يستدعي Flask API لحساب مجموع رقمين. والبنية كما يلي:

Docker compose - Communication

بنية المشروع كالتالي:

Docker compose's structure

لكلّ خدمة Dockerfile خاصّ بها:

  • Dockerfile لـ Data Science API:
FROM python:3.7-slim-buster
RUN mkdir /ds_api
WORKDIR /ds_api
ADD . /ds_api
RUN pip install virtualenv
RUN virtualenv api_vir_env
RUN . api_vir_env/bin/activate
RUN pip install -r requirements.txt
EXPOSE 8080
ENTRYPOINT ["python"]
CMD ["app.py"]
  • Dockerfile للـ frontend:
FROM python:3.7-slim-buster
RUN mkdir /frontend_dir
WORKDIR /frontend_dir
ADD . /frontend_dir
RUN pip install virtualenv
RUN virtualenv front_vir_env
RUN . front_vir_env/bin/activate
RUN pip install -r requirements.txt
EXPOSE 8501
ENTRYPOINT ["streamlit", "run"]
CMD ["app.py"]

يتمّ كشف DS API على المنفذ 8080 بينما يتمّ كشف الـ frontend على المنفذ 8501

لإدارة الخدمتين في الوقت ذاته، سنحتاج إلى إنشاء docker-compose.yml 🐳:

  • نقوم أوّلاً بضبط إصدار docker-compose
  • ثم نُعرّف خدمات تطبيقنا التي ستُطلق في containers مختلفة. وداخل كل خدمة نُعرّف:
    + container name
    + hostname
    + build (مجلّد واسم Dockerfile الخاص بالخدمة)
    + ports (للكشف): machine_port:container_port
    + restart (المقاربة)
version: '3'
services:
  datascience_api:
    container_name: datascience_api
    hostname: datascience_api
    build:
      context: ./datascience_api
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    restart: unless-stopped
  front:
    container_name: frontend
    hostname: frontend
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - 8501:8501
    restart: unless-stopped
    depends_on:
      - datascience_api

NB1: خدمة الـ frontend depends_on الـ datascience API الذي يجب تشغيله أوّلاً.

NB2: يستدعي الـ frontend الـ DS API باستخدام اسم الـ container الخاص به عبر بروتوكول http:

requests.post(“http://datascience_api:8080/add", params=query).json()

بمجرّد الانتهاء من ملفّ docker-compose نُشغّل أسطر الأوامر التالية في جذر المشروع:

docker-compose build

docker-compose build
docker-compose up
docker-compose up

يمكن عرض الـ containers التي تعمل باستخدام سطر الأوامر التالي:

docker-compose ps
docker-compose ps

بما أنّه تمّ استخدام المنافذ ذاتها لكلّ من الـ container والآلة المحلية، يمكننا فتح الـ frontend في متصفّح آلتنا:

Webapp

يمكنك العثور على جميع النصوص البرمجية في github repository.

أفضل الممارسات

  • قم بتجميد إصدارات جميع الـ images المسحوبة (python وjava)، فاستخدام latest قد يجعل بيئة الإنتاج غير مستقرّة
  • أطلق container أولاً بـ test entrypoint للتحقّق من أنّ جميع التثبيتات تمّت بشكل صحيح
  • قم بربط الـ docker container بـ volume من أجل إجراء التطوير في الوقت الفعلي دون الحاجة إلى إعادة البناء عند كل تغيير
  • قم بـ dockerize كل خدمة على حدة لتسهيل التطوير والـ debugging

الخاتمة

أتمنى أن تكون قد استمتعت بقراءة هذا المقال واكتسبتَ تجربة مباشرة مع docker. سيمكّنك ذلك من نشر منتجك بشكل أسرع وأكثر فعّالية بغضّ النظر عن مكان إجراء التطوير.

لا تتردّد في الاطّلاع على مقالاتي السابقة في DS Starter pack:

  • Must-have tools in Data Science projects
  • All you need to know about Git, GitHub & GitLab

ابقَ على تواصل

هل لديك سؤال؟ يسعدنا أن نسمع منك.