مقدمة
النشر اليدوي («ادخل الخادم، اسحب الكود، أعد التشغيل وتمنَّ الخير») ينجح حتى اليوم الذي يفشل فيه أمام المستخدمين. التكامل المستمر (CI) يفحص كل تغيير تلقائياً، والنشر المستمر (CD) ينقل التغيير الناجح إلى الإنتاج بخطوات ثابتة يمكن التراجع عنها.
أهداف التعلم
- بناء خط CI يوقف الدمج عند فشل أي فحص.
- فهم خطوات النشر الآمن وترتيبها.
- كتابة ترحيلات قاعدة بيانات متوافقة مع الإصدار السابق.
- تجهيز فحص صحة وتراجع تلقائي.
1. خط CI أساسي بـ GitHub Actions
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env: { POSTGRES_PASSWORD: ci, POSTGRES_DB: app_test }
ports: ["5432:5432"]
options: --health-cmd "pg_isready" --health-interval 5s --health-retries 10
env:
DATABASE_URL: postgresql://postgres:ci@localhost:5432/app_test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npx prisma migrate deploy # يتحقق أن الترحيلات تعمل على قاعدة جديدة
- run: npm test
- run: npm audit --omit=dev --audit-level=high
- run: npm run build
كل خطوة تفشل ← يفشل الخط ← لا يُدمج التغيير (فعّل «قواعد حماية الفرع» في GitHub لتلزم بذلك). أضف أيضاً فحص الأسرار (مثل gitleaks) حتى لا يُرفع مفتاح بالخطأ.
2. ترتيب النشر الآمن
1. بناء الإصدار من commit مُراجَع ونجح في CI
2. نسخة احتياطية من قاعدة البيانات والتأكد أنها حديثة وغير فارغة
3. تطبيق الترحيلات — والتوقف فوراً إن فشلت
4. تشغيل الإصدار الجديد بجانب القديم أو استبداله
5. فحص الصحة (health check) واختبارات دخان سريعة
6. نجاح ← الإبقاء عليه | فشل ← تراجع تلقائي إلى الإصدار السابق
أهم قاعدة: لا تخفِ الأخطاء. سطر مثل npx prisma migrate deploy || true يجعل فشل الترحيل يمر بصمت، فيعمل الإصدار الجديد على مخطط قديم. اجعل كل خطوة حرجة توقف النشر:
set -euo pipefail
if ! npx prisma migrate deploy; then
echo "فشل الترحيل؛ لن يُنشر الإصدار" >&2
exit 1
fi
3. ترحيلات متوافقة (Expand / Contract)
أثناء النشر يعمل الإصدار القديم والجديد على القاعدة نفسها لفترة. لذلك:
- التوسيع أولاً: أضف عموداً جديداً قابلاً لأن يكون فارغاً أو بقيمة افتراضية، وجدولاً جديداً. الإصدار القديم يتجاهله.
- انشر الكود الذي يكتب في العمود الجديد ويقرؤه.
- انقل البيانات إن لزم.
- التقليص لاحقاً: احذف العمود القديم في إصدار تالٍ، بعد التأكد أن لا شيء يستخدمه.
لا تعِد تسمية عمود أو تحذفه في الترحيل نفسه الذي ينشر الكود الجديد.
4. فحص الصحة والتراجع
for i in $(seq 1 30); do
code=$(curl -s -o /dev/null -w '%{http_code}' https://example.com/api/health || true)
[ "$code" = 200 ] && break
sleep 1
done
if [ "$code" != 200 ]; then
ln -sfn "$PREVIOUS_RELEASE" /opt/app/current && systemctl restart app
echo "فشل فحص الصحة؛ أُعيد الإصدار السابق" >&2
exit 1
fi
نقطة /api/health يجب أن تفحص الأساسيات فعلاً (الاتصال بقاعدة البيانات مثلاً) لا أن تعيد 200 دائماً، ولا تكشف تفاصيل داخلية للعامة.
5. بعد النشر
راقب السجلات ومعدل الأخطاء ووقت الاستجابة لدقائق بعد كل نشر. والتراجع يجب أن يكون مجرَّباً لا نظرياً.
تمرين عملي
- أضف خط CI أعلاه إلى مستودع لديك، وأنشئ طلب دمج يكسر اختباراً عمداً، ولاحظ منع الدمج.
- اكتب سكربت نشر بالخطوات الست، وجرّب فشل الترحيل عمداً على قاعدة اختبار وتأكد أن الإصدار لم يُفعّل.
- نفّذ تغيير اسم عمود بطريقة التوسيع/التقليص على ثلاث مراحل.
أخطاء شائعة
|| trueأوcontinue-on-errorعلى خطوات حرجة.- النشر دون نسخة احتياطية حديثة مُتحقق منها.
- ترحيلات تحذف أو تعيد تسمية ما يستخدمه الإصدار الحالي.
- وضع الأسرار في ملفات الخط بدل أسرار المستودع/المنصة.
- خطة تراجع لم تُختبر قط.
الخلاصة
اجعل الآلة تفحص كل تغيير قبل الدمج، وانشر بخطوات ثابتة: نسخة احتياطية، ترحيلات توقف النشر عند الفشل، فحص صحة، وتراجع تلقائي. الترحيلات المتوافقة تجعل كل ذلك ممكناً دون توقف.
مصادر مقترحة
- توثيق GitHub Actions.
- كتاب Continuous Delivery لـ Jez Humble وDavid Farley.
- مقال «Evolutionary Database Design» لـ Martin Fowler.