في عالم التطبيقات الحاوية، حجم الصورة ليس مجرد رقم، بل هو مقياس يؤثر مباشرة على سرعة النشر وتكاليف التخزين ومساحة الهجوم. بالنسبة للمطورين من المستوى المتوسط إلى المتقدم، يعد الانتقال من البناء أحادي المرحلة إلى متعدد المراحل أحد أهم التحسينات في مجموعة أدوات مهندس DevOps. يستكشف هذا المنشور الآليات والفوائد والتنفيذ العملي للبناء متعدد المراحل في Docker.
مشكلة البناء أحادي المرحلة
تقليدياً، كان بناء صورة Docker يتضمن دمج بيئة البناء وبيئة التشغيل في طبقة واحدة. خذ في الاعتبار تطبيقاً نموذجياً مكتوباً بلغة Go أو Java. لتجميع الكود المصدري، تحتاج إلى مترجمات (مثل gcc أو javac)، وأدوات بناء (make، gradle)، وربما مكتبات تطوير. تضيف هذه الأدوات وزناً كبيراً للصورة النهائية.
حتى إذا قمت بإزالة هذه التبعيات قبل تشغيل التطبيق، تبقى الطبقات في سجل الصورة. يؤدي ذلك إلى صور منتفخة تستغرق وقتاً أطول لدفعها وسحبها من المستودعات، وأوقات تشغيل أبطأ، ومساحة هجوم أكبر للثغرات الأمنية. يحل البناء متعدد المراحل هذه المشكلة من خلال السماح لك باستخدام عبارات FROM متعددة في ملف Dockerfile، ونسخ المخرجات من مرحلة إلى أخرى، والتخلص من الانتفاخ غير الضروري.
كيف يعمل البناء متعدد المراحل
يقسم البناء متعدد المراحل عملية البناء إلى مراحل متميزة. تبدأ كل تعليمات FROM مرحلة جديدة. يمكنك إعطاء أسماء للمراحل لتسهيل الإشارة إليها. المفهوم الرئيسي هو أن المرحلة النهائية فقط تصبح الصورة الفعلية التي يتم دفعها إلى المستودع الخاص بك.
دعنا ننظر إلى مثال عملي باستخدام تطبيق بسيط مكتوب بلغة Go. الهدف هو إنتاج ملف تنفيذي ثابت صغير يعمل في صورة أساسية من Alpine Linux خفيفة.
مثال: تحسين تطبيق Go
# المرحلة 1: بيئة البناء
FROM golang:1.21-alpine AS builder
# تثبيت أدوات البناء الضرورية
RUN apk add --no-cache git
# تعيين دليل العمل
WORKDIR /app
# نسخ ملفات go.mod و go.sum لتخزين التبعيات في ذاكرة التخزين المؤقت
COPY go.mod go.sum ./
RUN go mod download
# نسخ الكود المصدري
COPY . .
# بناء التطبيق
RUN go build -o myapp .
# المرحلة 2: بيئة التشغيل
FROM alpine:3.18 AS final
# تثبيت شهادات ca-certificates لطلبات HTTPS
RUN apk --no-cache add ca-certificates
# إنشاء مستخدم غير جذري للأمان
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# نسخ الملف التنفيذي من مرحلة البناء
COPY --from=builder /app/myapp /usr/local/bin/myapp
# تعيين الملكية للمستخدم غير الجذري
RUN chown appuser:appgroup /usr/local/bin/myapp
# التبديل إلى المستخدم غير الجذري
USER appuser
# فتح المنفذ (إذا كان ذلك مناسباً)
EXPOSE 8080
# تشغيل التطبيق
CMD ["myapp"]
في هذا المثال، تستخدم مرحلة builder صورة Go كاملة الميزات لتجميع الكود. تستخدم مرحلة final صورة Alpine خفيفة، وتنسخ الملف التنفيذي فقط، وتتخلص من جميع أدوات البناء والكود المصدري. تكون الصورة الناتجة أصغر بكثير — غالباً أقل من 10 ميجابايت مقارنة بمئات الميجابايت.
أفضل الممارسات والتقنيات المتقدمة
تسمية المراحل
قم دائماً بتسمية مراحلك باستخدام AS name. يجعل هذا ملف Dockerfile الخاص بك أكثر قابلية للقراءة ويسمح لك بالإشارة إلى مراحل محددة أثناء عملية النسخ باستخدام --from=name.
تحسين ذاكرة التخزين المؤقت
ضع الأوامر التي تتغير نادراً (مثل تثبيت التبعيات) في وقت مبكر من عملية البناء. يستفيد هذا من ذاكرة التخزين المؤقت للطبقات في Docker، مما يسرع إعادة البناء عند تعديل الكود المصدري ولكن ليس التبعيات.
اعتبارات الأمان
من خلال استخدام صورة نهائية خفيفة (مثل Alpine أو Distroless)، تقلل من عدد الحزم المثبتة وبالتالي عدد الثغرات الأمنية المحتملة. بالإضافة إلى ذلك، قم دائماً بتشغيل تطبيقك كمستخدم غير جذري للحد من تأثير الهروب من الحاوية.
الخاتمة
البناء متعدد المراحل ليس مجرد ميزة مريحة، بل هو ضرورة للحاويات الجاهزة للإنتاج. فهو يمكّن المطورين من فصل بيئة البناء عن بيئة التشغيل، مما يؤدي إلى صور أصغر حجماً وأسرع وأكثر أماناً. من خلال اعتماد هذا النمط، تقوم بتبسيط خطوط أنابيب CI/CD، وتقليل تكاليف التخزين السحابي، وتحسين الموثوقية العامة للبنية التحتية الخاصة بك. ابدأ بإعادة هيكلة ملفات Dockerfile الخاصة بك اليوم للاستفادة من هذه الميزة القوية.