استخدام Docker لتغليف تطبيق الذكاء الاصطناعي وضمان عمله على أي سيرفر
لماذا يصبح 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