انتقل إلى المحتوى
HA_

← جميع المقالات

· 3 دقائق قراءة

ترحيلات توقف الإصدار عند فشلها

كيف يشغّل ClickFortify ترحيلات Prisma كخطّاف PreSync في ArgoCD، بحيث يوقف الترحيل المعطوب الإصدار قبل أن يبدأ أي pod جديد.

devopskubernetesarchitecture

ترجمة آلية. اقرأ النسخة الأصلية

تغيير المخطط (schema) هو الجزء الأخطر في معظم عمليات النشر. يمكن التراجع عن الشيفرة الجديدة في ثوانٍ. أما قاعدة البيانات العالقة في منتصف الطريق بين شكلين فلا يمكن التراجع عنها. في ClickFortify، حيث أعمل مهندساً معمارياً رئيسياً، تعمل الخدمات على Kubernetes وينشرها ArgoCD عبر GitOps. أردت خاصية واحدة من خطوة الترحيل: إذا فشلت، فلا يعمل أي شيء جديد.

هذا ما أعنيه بالفشل المغلق (fail closed). يجب أن يوقف الترحيل الفاشل الإصدار، لا أن يسمح للـ pods الجديدة بالبدء على مخطط لا تتوقعه.

أين يُنفَّذ الترحيل

تستخدم خدمات NestJS في ClickFortify قاعدة PostgreSQL عبر Prisma. وتُطبَّق الترحيلات باستخدام prisma migrate deploy، الذي ينفّذ كل ترحيل معلّق بالترتيب ولا يغيّر شيئاً آخر.

كان السؤال هو أين يُنفَّذ. تشغيله داخل حاوية التطبيق عند بدء التشغيل يعني أن كل نسخة متماثلة تتسابق لإجراء الترحيل، ويظهر الفشل على هيئة pods تتعطل وتعيد التشغيل في حلقة متكررة. وتشغيله يدوياً من حاسوب محمول يعني أنه يعتمد على تذكّر أحدهم. لا يحقق أي من الخيارين الفشل المغلق.

بدلاً من ذلك، الترحيل هو Job في Kubernetes يشغّله ArgoCD بوصفه خطّاف PreSync. يشغّل ArgoCD خطّافات PreSync قبل تطبيق بقية ملفات manifests. فإذا فشل Job الخطّاف، تُلغى عملية المزامنة (sync)، ولا يُطبَّق الـ Deployment الجديد أبداً.

apiVersion: batch/v1
kind: Job
metadata:
  name: migrate
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  backoffLimit: 0
  activeDeadlineSeconds: 600
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: registry.example.com/api:sha-abc1234
          command: ['npx', 'prisma', 'migrate', 'deploy']

هذا الملف توضيحي، وليس منسوخاً من ملف حقيقي.

الصورة نفسها التي يحملها الإصدار

يشغّل الـ Job الصورة نفسها التي يحملها الإصدار الذي يحرسه. النشر يعني إيداع وسم صورة جديد في المستودع الذي يراقبه ArgoCD، ويُطبَّق ذلك الوسم على Job الترحيل وعلى Deployment الخاص بـ api معاً. وبذلك تكون الترحيلات التي تُنفَّذ هي بالضبط تلك المشحونة مع الشيفرة التي توشك أن تصبح فعّالة. لا توجد صورة ترحيلات منفصلة يمكن أن تخرج عن التزامن مع الشيفرة.

تعيش حالة النشر في مستودع خاص بها، منفصل عن شيفرة التطبيق. يمنع ذلك ArgoCD من إعادة استنساخ مستودع أحادي (monorepo) ضخم عند كل تحديث، ويعني أن ما ينشره ArgoCD هو تاريخ صغير قابل للمراجعة من الوسوم وملفات manifests.

لا إعادة محاولة عمياء

القيمة backoffLimit: 0 مقصودة. سيعيد Kubernetes بكل سرور محاولة تشغيل Job فاشل، لكن الترحيل الذي فشل في منتصفه ربما ترك المخطط مطبّقاً جزئياً. وإعادة تشغيله دون فحص قد تزيد الأمر سوءاً. محاولة واحدة، ثم التوقف وترك شخص يقرأ السجلات.

ويضع activeDeadlineSeconds سقفاً للمدة التي يمكن أن يعلق فيها الـ Job، على قفل مثلاً، قبل أن يُعدّ فاشلاً.

سجلات تبقى محفوظة

سياسة حذف الخطّاف هي BeforeHookCreation. لا يحذف ArgoCD الـ Job القديم إلا عندما يوشك على إنشاء الـ Job التالي. وحتى ذلك الحين، يبقى الـ Job الفاشل وسجلات الـ pod الخاص به في العنقود، وهذا بالضبط الوقت الذي تحتاج إليها فيه. أما السياسة التي تحذف الخطّاف عند الفشل فستتخلص من الدليل.

من السهل التعرف على التشغيل السليم في تلك السجلات: يذكر Prisma الترحيلات التي عثر عليها، ثم إما قائمة بما طبّقه أو أنه لم يكن هناك شيء معلّق، ثم يكتمل الـ Job.

الترتيب قد يكون مهماً

تشغيل الترحيلات قبل الـ pods الجديدة هو الخيار الافتراضي الصحيح، وأحياناً يكون ضرورياً. تخيّل إصداراً تتوقف فيه الشيفرة الجديدة عن الكتابة في عمود ما، وأصبحت القيمة الافتراضية لهذا العمود خاطئة في الوضع الجديد. يجب أن يُطبَّق الترحيل الذي يصحح الصفوف الموجودة والقيمة الافتراضية قبل أن تبدأ الشيفرة الجديدة، وإلا حصلت الصفوف الجديدة على القيمة الافتراضية القديمة دون أي شيء يصححها.

يوفر خطّاف PreSync هذا الترتيب دون أي جهد إضافي.

ما سأقوله لمن يُعِدّ هذا النظام

  1. شغّل الترحيلات كخطّاف سابق للنشر قادر على إيقاف الإصدار، لا عند بدء تشغيل التطبيق.
  2. ابنِ Job الترحيل من وسم الصورة نفسه الذي يحمله الإصدار، حتى لا تخرج الشيفرة والمخطط عن التزامن.
  3. اضبط backoffLimit: 0 واحتفظ بسجلات الـ Job الفاشل؛ فالمخطط المطبّق جزئياً يحتاج إلى شخص، لا إلى إعادة محاولة.
  4. انشر عبر المزامنة فقط، لأن إعادة التشغيل تتخطى الخطّافات.