استخدام Docker لتغليف تطبيق الذكاء الاصطناعي وضمان عمله على أي سيرفر

دقائق القراءة: 5

لماذا يصبح Docker خطوة حاسمة في مشاريع الذكاء الاصطناعي؟

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

في تطبيقات الذكاء الاصطناعي، تزداد أهمية هذا التغليف لأننا نتعامل مع مكتبات حساسة مثل transformers وtorch وlangchain وواجهات مزودات النماذج. لذلك فإن التغليف بالحاويات لا يحل فقط مشكلة “يعمل عندي ولا يعمل عندك”، بل يرفع قابلية النقل، ويُسهّل الاختبار، ويجعل النشر أكثر ثباتاً.

إذا كنت قد بنيت التطبيق مسبقاً كواجهة برمجية، فراجع درس تحويل تطبيق الذكاء الاصطناعي إلى واجهة برمجة تطبيقات (API) باستخدام FastAPI، وإذا كنت تريد فهم النشر اللاحق على المنصات السحابية فراجع النشر السحابي (Deployment): رفع تطبيق الـ AI الخاص بك على منصات مثل Render أو AWS.

الهيكلية الهندسية: كيف يحيط Docker بتطبيق الذكاء الاصطناعي؟

عملياً، الحاوية ليست بديلاً عن التطبيق، بل غلاف تشغيل قياسي حوله. يمكن تصور البنية كالتالي:

  • المستخدم يرسل طلباً إلى تطبيق FastAPI أو واجهة Streamlit.
  • التطبيق داخل الحاوية يقرأ متغيرات البيئة مثل OPENAI_API_KEY.
  • ثم يعالج المدخلات، وقد يستدعي LangChain أو نظام RAG أو قاعدة متجهات.
  • بعدها يتصل بالنموذج الخارجي أو المحلي ويرجع الناتج.
  • السيرفر لا يحتاج إلا إلى تثبيت Docker Engine لتشغيل الصورة نفسها في أي مكان.

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

بنية المشروع قبل التغليف

قبل كتابة ملف Dockerfile، نظّم المشروع بشكل واضح. مثال مناسب لتطبيق API بسيط:

app/
├── main.py
├── services/
│   └── llm_service.py
├── requirements.txt
├── .env
└── Dockerfile

الفكرة هي فصل طبقة الاستقبال API Layer عن طبقة استدعاء النموذج Inference Layer. هذا الفصل يسهل لاحقاً اختبار الخدمات، واستبدال مزود النماذج، أو دمج RAG دون فوضى.

مثال على تطبيق FastAPI بسيط

from fastapi import FastAPI
from pydantic import BaseModel
import os

app = FastAPI()

class PromptRequest(BaseModel):
    prompt: str

@app.get("/")
def health_check():
    return {"status": "ok"}

@app.post("/generate")
def generate_text(data: PromptRequest):
    api_key = os.getenv("OPENAI_API_KEY", "missing")
    return {
        "prompt": data.prompt,
        "message": "AI application is running inside Docker",
        "key_loaded": api_key != "missing"
    }

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

كتابة ملف requirements.txt وملف Dockerfile

حدد التبعيات بدقة حتى لا تعتمد الصورة على تثبيتات عشوائية:

fastapi
uvicorn
python-dotenv

بعدها نكتب ملف التغليف الأساسي:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

هذا الملف يحدد صورة أساسية خفيفة، ثم ينشئ مجلد العمل، وينسخ التبعيات، ويثبتها، ثم ينسخ المشروع، ويفتح المنفذ، وأخيراً يشغل الخادم. ترتيب الأوامر هنا مهم لأن Docker Layer Caching يجعل بناء الصور أسرع عند عدم تغيّر التبعيات.

بناء الصورة وتشغيل الحاوية

بعد تجهيز الملفات، تأتي مرحلة البناء والتشغيل. من منظور هندسي، لدينا خطوتان منفصلتان:

  • تحويل المشروع إلى صورة ثابتة Image.
  • إنشاء نسخة تشغيلية منها تسمى Container.
docker build -t ai-app .
docker run -p 8000:8000 --env-file .env ai-app

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

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

كيف تتعامل الحاويات مع تطبيقات LLM وRAG؟

عند الانتقال من تطبيق نصي بسيط إلى أنظمة أعقد مثل مشروع RAG مصغر: سكربت Python يتيح لك “الدردشة” مع كتاب PDF، ستظهر اعتبارات إضافية:

  • الملفات المرجعية مثل PDF أو قواعد ChromaDB قد تحتاج إلى مجلدات دائمة خارج الحاوية.
  • النماذج المحلية كبيرة الحجم لا يُفضّل نسخها داخل الصورة كل مرة.
  • خدمات مثل Ollama قد تعمل في حاوية مستقلة، بينما يعمل التطبيق في حاوية أخرى.
  • إذا كنت تستخدم Streaming Responses فيلزم التأكد من أن الخادم والوكيل العكسي لا يقطعان التدفق.

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

متى تستخدم docker-compose؟

إذا كان لديك أكثر من خدمة، يصبح تنظيمها بملف واحد أكثر احترافية. مثال مفاهيمي:

version: "3.9"

services:
  api:
    build: .
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - chroma

  chroma:
    image: chromadb/chroma
    ports:
      - "8001:8000"

الفائدة هنا أن تشغيل البنية كاملة يتم بأمر واحد، وتصبح العلاقة بين الخدمات واضحة وقابلة لإعادة الإنتاج.

أفضل الممارسات الأمنية والأدائية

  • لا تضع مفاتيح API داخل Dockerfile.
  • استخدم ملف .dockerignore لتجنب نسخ الملفات غير الضرورية.
  • قلّل حجم الصورة باختيار صور أساسية خفيفة.
  • راقب الاستهلاك والكمون، خاصة عند استدعاء النماذج المدفوعة أو عند إدارة Tokens.
  • اختبر التطبيق ضد الهجمات المنطقية مثل Prompt Injection، فالحاوية لا تحل مشاكل الأمان على مستوى البرومبت.

الخلاصة الهندسية

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

1 comment

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *