كان كل درس في هذه السلسلة لبنةً بناء منفردة. الآن حان وقت تجميعها في خط أنابيب CI/CD جاهز للإنتاج — من النوع الذي تستخدمه شركات مثل Shopify وStripe وGitHub نفسها. يأخذك هذا الدرس خطوةً بخطوة في تصميم وكتابة وتشغيل سير عمل متكامل يبني ويختبر ويعبّئ وينشر تطبيق Node.js في حاوية إلى بيئة سحابية، مع حماية كل مرحلة بفحوصات جودة آلية وضوابط موافقة.
البنية المستهدفة
التطبيق عبارة عن REST API معبأ كصورة Docker، تُرفع إلى سجل حاويات، وتُنشر في مجموعة Kubernetes. يفرض خط الأنابيب هذا التسلسل: يجب أن يجتاز الكود التحليل الساكن والاختبارات الوحدوية قبل بناء الصورة؛ ويجب فحص الصورة بحثاً عن الثغرات قبل رفعها؛ ويجب أن ينجح النشر في البيئة التجريبية وأن تجتاز اختبار الدخان قبل فتح الباب للإنتاج؛ وتستلزم البيئة الإنتاجية موافقةً من شخص محدد بالاسم.
خط أنابيب CI/CD الشامل: كل مرحلة عبارة عن وظيفة ذات حماية؛ والنشر الإنتاجي يستلزم موافقة يدوية.
ملف سير العمل الكامل
تعيش المراحل الخمس جميعها في ملف سير عمل واحد. يفرض المفتاح needs سلسلة التبعيات؛ وتضيف كتلة environment: production بوابة الموافقة. لاحظ أن IMAGE_TAG مشتق من SHA الخاص بـ Git — ثابت غير قابل للتغيير، قابل للتتبع، ولا يمكن الكتابة فوقه بالخطأ.
خط الأنابيب لا يكون أفضل من الـ Dockerfile الذي يبنيه. استخدم البناء متعدد المراحل للحفاظ على صغر حجم الصورة النهائية، ولا تشغّل التطبيق بصلاحيات root في الإنتاج. إن تعليمات --chown وUSER node غير قابلة للتفاوض في شركات التقنية الكبرى.
# Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 nodeuser
COPY --from=builder --chown=nodeuser:nodejs /app/dist ./dist
COPY --from=deps --chown=nodeuser:nodejs /app/node_modules ./node_modules
USER nodeuser
EXPOSE 3000
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:3000/healthz || exit 1
CMD ["node", "dist/server.js"]
القرارات التصميمية الرئيسية موضّحةً
هوية الصورة عبر الـ digest لا الوسم
تكشف وظيفة build-image عن digest الصورة (وهو hash SHA-256 لمحتواها) كمخرج. كل وظيفة تالية تشير إلى الصورة عبر هذا الـ digest لا عبر وسم قابل للتغيير كـ :latest. يضمن ذلك أن البايناري الذي نشرته في التجربة هو بالضبط ما يصل إلى الإنتاج — لا سباقات كتابة فوق الوسم، ولا انحراف بين البيئات.
فحص الثغرات بوابةً صارمة
يعمل Trivy مع exit-code: 1، أي أن أي ثغرة CRITICAL أو HIGH ستُفشل وظيفة scan وتمنع انطلاق نشر التجربة والإنتاج كليهما. تُرفع نتائج SARIF إلى تبويب Security في GitHub ليتمكن المهندسون من المراجعة دون مغادرة GitHub.
بيئة الموافقة
تعليمة environment: production تربط الوظيفة ببيئة GitHub مهيأة بـ Required Reviewers. عند بلوغ سير العمل تلك الوظيفة يتوقف وتُرسل GitHub إشعاراً للمراجعين المحددين. لا تعديل في الكود لإضافة المراجعين أو إزالتهم — يُدار الأمر كله من واجهة إعدادات المستودع ومحفوظ بالكامل في سجل التدقيق.
الشرط if على وظيفة deploy-production بالغ الأهمية. بدون if: github.event_name == 'release'، كل عملية دمج في main ستضع نشراً إنتاجياً في طابور الانتظار. يجب أن يفتح باب الإنتاج إصدارُ GitHub Release فحسب. النشر في التجربة يتم عند كل دمج في main؛ أما النشر الإنتاجي فيتم عند حدث الإصدار فقط.
استراتيجية التراجع
كل صورة مرفوعة ثابتة وموسومة بـ SHA. التراجع يكون بأمر واحد: ابحث في واجهة Actions عن آخر تشغيل ناجح، انسخ الـ digest الخاص به وأعد التشغيل، أو ببساطة:
# التراجع: إعادة صورة الـ deployment إلى الـ digest السابع المعروف بأنه سليم
kubectl set image deployment/api \
api=ghcr.io/your-org/your-repo@sha256:<previous-digest> \
-n production
kubectl rollout status deployment/api -n production --timeout=300s
# التحقق
kubectl get pods -n production -l app=api -o wide
ثبّت إجراءات GitHub على commit SHA في خطوط الأنابيب الإنتاجية. استخدام actions/checkout@v4 مريح لكن actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 ثابت غير قابل للتغيير. يمكن لمهاجم اختراق وسم إجراء وتحديث محتواه دون تغيير الرقم؛ أما الـ SHA المثبت فلا يمكن تغييره. أدوات مثل Dependabot وRenovate تحافظ على تحديث هذه التثبيتات آلياً.
أنماط الفشل الشائعة في الإنتاج
انتهاء مهلة النشر قبل أن تصبح Pods في وضع صحي — مسبار الجاهزية يفشل؛ تحقق من سجلات التطبيق بـ kubectl logs -n staging -l app=api --since=2m قبل إلقاء اللوم على خط الأنابيب.
تذبذب اختبار الدخان — حلقة إعادة المحاولة في المثال تعالج بدء تشغيل موازن الحمل المتأخر؛ اضبط مدة الانتظار وفق وقت الإقلاع في بنيتك التحتية.
يوقف Trivy بسبب نتيجة إيجابية خاطئة — استخدم .trivyignore لتجاهل معرفات CVE محددة مع تعليق يوضح القرار وتاريخ المراجعة.
GITHUB_TOKEN يفتقر إلى packages: write — يجب تعريف الصلاحية على مستوى الوظيفة، لا مجرد افتراضها. لهذا أُضيفت صراحةً في build-image.
انتهاء صلاحية kubeconfig — جدّد أسرار STAGING_KUBECONFIG وPROD_KUBECONFIG عند انتهاء رموز حسابات الخدمة. ضع تذكيراً في التقويم أو استخدم اتحاد OIDC بدلاً من ذلك (تغطى في الدرس الثامن).
لا تضع بيانات اعتماد المجموعة في متغيرات بيئة سير العمل المرئية في السجلات. ثبّت دائماً بفك تشفير من سر base64 مباشرةً إلى ملف (echo "$SECRET" | base64 -d > ~/.kube/config) وعيّن صلاحيات صارمة (chmod 600). تخفي GitHub قيم الأسرار في السجلات، لكن echo $SECRET الصريح قد يسرّب جزءاً من القيمة في بعض البيئات. النمط في هذا الدرس هو الأسلوب الآمن.
ما يمكن إضافته لاحقاً
هذا خط الأنابيب أساس متين. في قاعدة كود شركة حقيقية ستضيف: اختبارات التكامل والنهاية إلى النهاية كوظائف متوازية بين test وbuild-image؛ وظائف ترحيل قاعدة البيانات مع بوابة تشغيل جاف؛ إشعارات Slack / PagerDuty عند الفشل باستخدام خطوات if: failure()؛ وإرسال مقاييس DORA (تكرار النشر، وقت الاستيفاء) إلى منصة الرصد والمراقبة. تتوسع هذه البنية لأن كل اهتمام وظيفته الخاصة — إضافة بوابة جديدة مسألة إدراج وظيفة بسلسلة needs الصحيحة.
نستخدم ملفات تعريف الارتباط لتشغيل هذا الموقع وتحليل الزيارات وعرض إعلانات مخصّصة. يمكنك قبول كل ملفات تعريف الارتباط أو رفض غير الأساسية منها.
سياسة الخصوصية