Kubernetes

CronJob يجدول جرابًا. وهو لا ينسّق شيئًا.

Kubernetes يجدول أصلًا. ما لا يفعله هو التعبير عن الترتيب بين المهام، والاحتفاظ بالسجلات بعد جمع الجراب، والوصول إلى أي شيء خارج العنقود. ينفّذ Dagu كل خطوة كمهمة Kubernetes ويضيف حولها الرسم والسجل وعبور الحدود.

كل خطوة مهمة Kubernetes حقيقية، لا نصًا برمجيًا يلتف حول kubectl
سجلات الجراب تدخل سجل تشغيل Dagu وتبقى بعد زوال الجراب
يمكن لسير عمل واحد أن يمتد عبر عناقيد عدة بفضل سياقات kubeconfig
خطوات العنقود تجاور خطوات SSH وHTTP والموافقة في الرسم نفسه
01

ما لا يفعله CronJob

بوصفه مجدولًا لجراب واحد، فإن CronJob جيد. تظهر الثغرات لحظة أن تصبح الدفعة أكثر من جراب واحد، وكل فريق يصطدم بالثلاث نفسها.

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

الخطوات مهام، والسجلات تعود

ينشئ Dagu لكل خطوة مهمة بحاوية واحدة، وينتظرها، ويبثّ سجلات الجراب، ويستخدم رمز الخروج للحاوية المنتهية. تُكتب السجلات في سجل التشغيل، فتبقى موجودة بعد زوال الجراب والمهمة معًا.

  • تعبّر depends عن الترتيب مباشرة، فيوقف الاستخراج الفاشل عملية التحميل بدل أن يغذّيها بلا شيء
  • تعيد retry_policy تنفيذ الخطوة بإنشاء مهمة جديدة، وهو أمر مختلف عن إعادة backoffLimit تشغيل الجراب في مكانه
  • تُحذف المهام بعد الاكتمال افتراضيًا، فلا يترك الرسم الليلي وراءه أثرًا من مهام منتهية

يعرض Kubernetes تدفق سجلات حاوية مدمجًا، لذا يصل stdout وstderr في هذا النوع من الخطوات كتدفق واحد متداخل.

دفعة من ثلاث خطوات كمهام Kubernetes حقيقية
# k8s-nightly-etl.yaml
schedule: "0 2 * * *"
max_active_runs: 1

kubernetes:
  namespace: batch
  service_account: dagu-runner
  resources:
    requests:
      cpu: "250m"
      memory: "512Mi"

steps:
  - id: extract
    action: k8s.run
    with:
      image: ghcr.io/example/extract:1.4.0
      command: extract --source warehouse
    retry_policy:
      limit: 2
      interval_sec: 120

  - id: transform
    action: k8s.run
    with:
      image: ghcr.io/example/transform:1.4.0
    depends: extract

  - id: load
    action: k8s.run
    with:
      image: ghcr.io/example/load:1.4.0
      resources:
        limits:
          cpu: "2"
          memory: "4Gi"
    depends: transform

handler_on:
  failure:
    run: ./scripts/page-oncall.sh

mail_on:
  failure: true
03

العمل الذي لا يتسع له عنقود واحد

هذا هو الجزء الذي لا يستطيع CronJob أداءه مهما بُذل من جهد، لأن نطاقه محصور بالعنقود الذي يعيش فيه. وسير العمل الذي يمس عنقودين، أو عنقودًا ونظامًا محليًا، لا مكان له داخل Kubernetes.

  • يمكن ضبط kubeconfig وcontext لكل خطوة، فتصير بيئة التجريب والإنتاج خطوتين في سير عمل واحد بدل نشرتين للبيان نفسه
  • يمكن لخطوة عنقودية أن تقع بين خطوة SSH على مضيف قديم واستدعاء HTTP لواجهة SaaS، لأن المنسّق نفسه ليس موردًا من موارد العنقود
  • توقف خطوة human.task سير العمل من أجل موافقة ذات حقول محددة، دون احتجاز أي عملية أثناء الانتظار، ثم تمضي إلى خطوة الإنتاج
ترقية ترحيل عبر العناقيد مع بوابة موافقة
# k8s-promote-across-clusters.yaml
max_active_runs: 1

kubernetes:
  namespace: batch
  service_account: dagu-runner

steps:
  - id: migrate_staging
    action: k8s.run
    with:
      context: staging
      image: ghcr.io/example/migrator:3.2
      command: migrate up

  - id: verify_staging
    run: ./scripts/verify-schema.sh staging
    depends: migrate_staging

  - id: approve
    action: human.task
    with:
      prompt: Promote the migration to production?
      form:
        type: object
        properties:
          change_ticket:
            type: string
          confirmed:
            type: boolean
        required: [change_ticket, confirmed]
    depends: verify_staging

  - id: migrate_production
    action: k8s.run
    with:
      context: production
      image: ghcr.io/example/migrator:3.2
      command: migrate up
    depends: approve

  - id: record
    run: ./scripts/record-change.sh "${steps.approve.outputs.change_ticket}"
    depends: migrate_production

handler_on:
  failure:
    run: ./scripts/page-oncall.sh

mail_on:
  failure: true
04

أين يكون Argo Workflows الجواب الأفضل

Argo Workflows هو الجواب الأصيل في Kubernetes للمشكلة نفسها، وهو الصحيح لصنف واسع من الأعمال. والفارق في مكان إقامة المنسّق.

  • يعمل Argo عبر CRD ووحدة تحكم داخل العنقود، وهي ميزة إذا كان كل ما تنسّقه موجودًا هناك أصلًا، وقيد إن لم يكن
  • أما Dagu فملف تنفيذي واحد يمكن أن يقع خارج العنقود، وهذا بالضبط ما يجعل الرسوم العابرة للعناقيد والحدود ممكنة
  • وإذا أردت أن تكون تعريفات سير العمل كائنات Kubernetes تُدار بالصلاحيات ومسار GitOps نفسه كبقية العنقود، فذلك تصميم Argo لا تصميم Dagu
05

ما لا يوفّره المنفّذ عن قصد

نوع خطوة Kubernetes ضيّق عن عمد. يغطي الشكل الشائع لخطوة دفعية تغطية جيدة، ولا يحاول أن يصير أداة عامة لتطبيق البيانات.

  • حاوية واحدة لكل خطوة وأمر واحد. ولا يوجد تمرير مباشر لمواصفات Pod أو Job الخام
  • لا تُعرض خصائص parallelism وcompletions وcompletion mode وsuccess policy للمهام، فتبقى المهام المفهرسة والمتوازية خارج النطاق
  • restart_policy غير قابل للضبط، وخيارات Windows الخاصة وSELinux وAppArmor وproc_mount غير متاحة

إن لم يكن حقل ما موثقًا لهذا النوع من الخطوات فاعتبره غير مدعوم. والعمل الذي يحتاج مواصفة مهمة كاملة يُطبَّق عبر kubectl من خطوة أوامر، أو يُترك لـ Argo.

FAQ

Practical questions before adopting

هل يجب تشغيل Dagu داخل العنقود؟

لا. فهو يحدد العنقود عبر kubeconfig صريح، ثم قواعد التحميل المعتادة، ثم الإعداد داخل العنقود، ويعمل في الحالتين. تشغيله خارجًا هو ما يتيح سير العمل العابر للعناقيد والحدود، وتشغيله داخلًا مناسب حين يكون كل ما يلمسه داخل ذلك العنقود.

بماذا يختلف هذا عن CronJob يشغّل نصًا يستدعي kubectl؟

ذلك النمط يمنحك الترتيب فقط: رمز خروج الجراب الغلاف يخفي أي مرحلة أخفقت، وإعادة المحاولة تعيد تشغيل النص كله، والسجلات تدفق واحد غير مميّز يزول مع الجراب. أما Dagu فينشئ مهمة لكل خطوة، فتصبح لكل مرحلة حالتها وإعادة محاولتها وسجلها المحفوظ.

هل تتشارك الخطوات نظام ملفات؟

لا. كل خطوة مهمة منفصلة وبالتالي جراب منفصل. مرّر البيانات عبر تخزين الكائنات، أو وحدة تخزين دائمة تُركَّب في كل خطوة، أو مخرجات الخطوات للقيم الصغيرة، تمامًا كما تفعل بين مهام منفصلة.

ماذا يحدث للمهمة عند إلغاء التشغيل؟

مسارات الإلغاء والإنهاء وانتهاء المهلة تفرض التنظيف حتى مع ضبط cleanup_policy على keep، فلا يترك التشغيل الموقوف المهمة خلفه. وفي التشغيل الاعتيادي يكون الافتراض حذف المهمة بعد انتهائها.

هل نستخدم هذا بدل Argo Workflows؟

فقط إذا كان وجود المنسّق خارج العنقود مفيدًا لك. فإذا كانت كل المهام حاويات داخل عنقود واحد وأردت أن تكون مسارات العمل كائنات Kubernetes تحت الصلاحيات ومسار GitOps نفسه، فـ Argo أنسب. أما Dagu فيناسب الرسوم التي تمزج مهام العنقود بالمضيفات وواجهات البرمجة وموافقات البشر.

Next step

Start with one workflow.

Install Dagu, move one script that runs on cron today into YAML, and decide from a real run history.