من الكود إلى الحزمة القابلة للتشغيل: ما يحدث قبل النشر
لقد انتهيت للتو من كتابة ميزة جديدة. الكود يترجم محليًا، والاختبارات تمر على جهازك، وتشعر بالثقة. ولكن عندما تدفع الكود وتحاول النشر، يحدث خطأ ما. يشتكي الخادم من مكتبة مفقودة. يتعطل الملف الثنائي لأنه تم تجميعه على إصدار نظام تشغيل مختلف. لا يمكن لسكريبت النشر العثور على الأرتيفكت الصحيح.
هذا الموقف أكثر شيوعًا مما تعترف به معظم الفرق. الفجوة بين "الكود يعمل على جهازي" و"الكود يعمل في الإنتاج" مليئة بخطوات يسهل التغاضي عنها. فهم ما يحدث بين كتابة الكود والحصول على حزمة قابلة للنشر أمر ضروري لأي مهندس مشارك في تسليم البرمجيات.
ما هو البناء حقًا؟
عندما يكتب المطور كودًا، فإن هذا الكود ليس جاهزًا بعد للتشغيل على خادم. يحتاج إلى تحويله إلى شيء قابل للتنفيذ. عملية التحويل هذه تسمى بناء. ناتج البناء يسمى أرتيفكت.
تأتي الأرتيفكتات في شكلين شائعين:
- الملفات الثنائية: ملف تنفيذي واحد يمكن لنظام التشغيل تشغيله مباشرة. فكر في برنامج Go مُجمّع أو ملف Java JAR.
- صور الحاويات: حزمة تحتوي على الثنائي بالإضافة إلى كل ما يلزم لتشغيله: المكتبات، ملفات الإعدادات، متغيرات البيئة، وبيئة التشغيل نفسها. تُستخدم صور الحاويات عند النشر إلى بيئات مثل Docker أو Kubernetes.
الاختيار بين الثنائي وصورة الحاوية يعتمد على بنية النشر الخاصة بك. توفر الحاويات تناسقًا أكبر عبر البيئات، بينما تكون الثنائيات أخف وزنًا وأسرع في البدء. على أي حال، الهدف هو نفسه: إنتاج حزمة مكتفية ذاتيًا يمكن نشرها بشكل موثوق.
المراحل الأربع للبناء
كل خدمة خلفية، بغض النظر عن اللغة أو الإطار، تمر بهذه المراحل. دعنا نستعرضها واحدة تلو الأخرى.
يظهر الرسم البياني أدناه خط أنابيب البناء الكامل من الكود المصدري إلى الأرتيفكت المخزن.
المرحلة 1: التجميع
إذا كان الكود الخاص بك مكتوبًا بلغة مُجمّعة مثل Go أو Java أو Rust أو C++، فيجب تجميعه إلى كود آلة أو bytecode قبل أن يتم تشغيله. اللغات مثل Python أو Node.js أو Ruby تتخطى هذه الخطوة لأنها تُفسّر في وقت التشغيل.
التفصيل الحاسم هنا هو تناسق البيئة. إذا قمت بالتجميع على حاسوب مطور ثم نسخت الثنائي إلى خادم إنتاج، فإنك تخاطر بمشاكل التوافق. قد يكون لدى خادم الإنتاج إصدار نظام تشغيل مختلف، أو مكتبات نظام مختلفة، أو بنية CPU مختلفة. قم دائمًا بالتجميع في بيئة تطابق بيئة الإنتاج المستهدفة قدر الإمكان.
بالنسبة للغات المفسّرة، ليست هناك حاجة للتجميع، ولكنك لا تزال بحاجة إلى تحضير بيئة التشغيل. هذا يقودنا إلى المرحلة التالية.
المرحلة 2: حزم التبعيات
تطبيقات الخلفية لا تعمل أبدًا تقريبًا بكودها الخاص فقط. فهي تعتمد على مكتبات خارجية للوصول إلى قواعد البيانات، معالجة HTTP، المصادقة، التسجيل، وعشرات الاهتمامات الأخرى. يجب جمع هذه التبعيات وحزمها مع الكود الخاص بك.
الآلية الدقيقة تعتمد على بيئة اللغة الخاصة بك:
- Python: قم بتشغيل
pip installواجمع كل الحزم في مجلد معين أو بيئة افتراضية. - Node.js: قم بتشغيل
npm installوتأكد من اكتمال دليلnode_modules. - Java: تُحزم التبعيات داخل ملف JAR أو WAR أثناء عملية البناء.
- Go: تُجمّع التبعيات مباشرة داخل الثنائي، لذلك لا حاجة لحزم منفصل.
المبدأ بسيط: عندما يعمل الأرتيفكت على خادم، يجب أن يكون كل ما يحتاجه موجودًا بداخله بالفعل. لا ينبغي لأحد أن يضطر إلى تثبيت التبعيات يدويًا أثناء النشر. هذه وصفة لعدم التناسق والفشل.
المرحلة 3: إنشاء الأرتيفكت
الآن تحتاج إلى إنتاج الحزمة النهائية. إذا كان فريقك يستخدم الحاويات، فهذا يعني بناء صورة Docker باستخدام Dockerfile. تحتوي الصورة على الثنائي المُجمّع أو كود التطبيق، جميع التبعيات، الإعدادات الأساسية، والأمر لتشغيل التطبيق.
إليك مثال عملي على Dockerfile متعدد المراحل يقوم بتجميع تطبيق Go وإنتاج صورة حاوية صغيرة الحجم:
# Stage 1: Compilation and dependency bundling
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .
# Stage 2: Create the final artifact
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
COPY --from=builder /app/server /server
EXPOSE 8080
CMD ["/server"]
إذا كان فريقك لا يستخدم الحاويات، فإن ناتج البناء هو ملف ثنائي أو مجلد مضغوط يمكن نسخه مباشرة إلى الخادم. الثنائيات أبسط وأسرع في النشر، لكنها توفر عزلًا وقابلية نقل أقل من صور الحاويات.
على أي حال، يجب أن يكون الأرتيفكت غير قابل للتغيير. بمجرد بنائه، لا ينبغي تعديله. أي تغيير يعني بناء جديد وأرتيفكت جديد. هذا يضمن أن ما اختبرته هو بالضبط ما تنشره.
المرحلة 4: تخزين الأرتيفكت
يحتاج الأرتيفكت النهائي إلى مكان دائم يمكن لعمليات النشر الوصول إليه. موقع التخزين هذا يسمى سجل أو مستودع.
- صور الحاويات تذهب إلى سجل الحاويات: Docker Hub، Amazon ECR، Google Artifact Registry، أو خيارات مستضافة ذاتيًا مثل Harbor.
- الملفات الثنائية تذهب إلى مستودع الأرتيفكتات: Nexus، Artifactory، أو حتى تخزين كائنات مثل S3.
يجب أن يكون لكل أرتيفكت معرف فريد. هذا عادة ما يكون رقم إصدار، أو تجزئة commit من Git، أو مزيج من الاثنين. باستخدام المعرفات الفريدة، يمكن لفريقك دائمًا تتبع إصدار الكود الذي يعمل في الإنتاج. عندما يحدث خطأ ما، يمكنك النظر إلى معرف الأرتيفكت ومعرفة بالضبط ما تم نشره.
لماذا الأتمتة مهمة
يجب أن تحدث كل مرحلة من هذه المراحل بنفس الطريقة في كل مرة. إذا قمت بالبناء يدويًا، فستتخطى في النهاية خطوة، أو تستخدم إصدار تبعية خاطئ، أو تجمّع على الجهاز الخطأ. النتيجة هي أرتيفكت يتصرف بشكل مختلف عما توقعته.
يقوم خط أنابيب CI بأتمتة العملية بأكملها. يتم تشغيله عند كل تغيير في الكود، وينفذ مراحل البناء بالتسلسل، ويخزن الأرتيفكت بمعرف مناسب. يضمن خط الأنابيب أن كل أرتيفكت يتم بناؤه بنفس الطريقة، بنفس الأدوات، في نفس البيئة.
هذا التناسق هو ما يجعل عمليات النشر قابلة للتنبؤ. عندما تنشر أرتيفكت من خط أنابيب CI، فإنك تعرف بالضبط ما تحصل عليه. لا توجد مفاجآت من الخطوات اليدوية أو اختلافات البيئة.
قائمة تحقق عملية لعملية البناء الخاصة بك
قبل إعداد خط أنابيب البناء الخاص بك، تحقق من هذه النقاط:
- بيئة البناء تطابق بيئة الإنتاج (إصدار نظام التشغيل، مكتبات النظام، بنية CPU).
- جميع التبعيات مُعلنة بشكل صريح (ملفات القفل، requirements.txt، go.mod، إلخ).
- يتم بناء الأرتيفكت مرة واحدة بالضبط لكل تغيير في الكود ويتم تخزينه بشكل غير قابل للتغيير.
- كل أرتيفكت له معرف فريد قابل للتتبع (وسم إصدار أو تجزئة commit).
- عملية البناء تعمل تلقائيًا عند كل push أو دمج إلى الفرع الرئيسي.
- سجل الأرتيفكتات قابل للوصول من قبل عملية النشر دون تدخل يدوي.
ما التالي
بمجرد أن يصبح الأرتيفكت جاهزًا ومخزنًا، السؤال التالي هو ما إذا كان آمنًا للنشر. هذا هو المكان الذي تأتي فيه الاختبارات الآلية وفحص الأمان. اختبارات الوحدة، اختبارات التكامل، فحوصات الثغرات، وغيرها من الفحوصات تُجرى على الأرتيفكت قبل أن يصل إلى الإنتاج.
لكن لا شيء من هذه الفحوصات يهم إذا كان البناء نفسه معطلاً. عملية بناء موثوقة هي أساس كل خط أنابيب CI/CD. بدونها، أنت تنشر تخمينًا، وليس برمجيات.
الخلاصة: البناء ليس مجرد خطوة تقنية. إنها اللحظة التي يصبح فيها الكود أصلًا قابلاً للنشر. تعامل معه بنفس الدقة التي تطبقها على أنظمة الإنتاج. قم بأتمتته، وتوحيده، واجعل كل أرتيفكت قابلًا للتتبع. ذاتك المستقبلية، التي تتصحح مشكلة إنتاجية في الساعة 2 صباحًا، ستشكرك.