Kubernetes

CronJob एक पॉड शेड्यूल करता है। वह किसी चीज़ का ऑर्केस्ट्रेशन नहीं करता।

Kubernetes पहले से शेड्यूल करता है। जो वह नहीं करता वह है जॉब के बीच क्रम बताना, पॉड हटने के बाद लॉग रखना, या क्लस्टर के बाहर किसी चीज़ तक पहुँचना। Dagu हर चरण को Kubernetes Job के रूप में चलाता है और उसके चारों ओर ग्राफ़, इतिहास और सीमा-पार पहुँच जोड़ता है।

हर चरण एक वास्तविक Kubernetes Job है, kubectl के इर्द-गिर्द की स्क्रिप्ट नहीं
पॉड के लॉग Dagu के रन इतिहास में आते हैं और पॉड से अधिक जीते हैं
kubeconfig context के ज़रिए एक वर्कफ़्लो कई क्लस्टर तक फैल सकता है
क्लस्टर चरण उसी ग्राफ़ में SSH, HTTP और अनुमोदन चरणों के साथ रहते हैं
01

CronJob जो नहीं करता

एक पॉड के शेड्यूलर के रूप में CronJob अच्छा है। कमी तब दिखती है जब बैच एक पॉड से बड़ा हो जाता है, और हर टीम उन्हीं तीन से टकराती है।

  • CronJob के बीच कोई निर्भरता नहीं होती। क्रम घड़ी के अंतर का अनुमान लगाकर बताया जाता है, जो पहली बार किसी जॉब के लंबा चलते ही चुपचाप टूट जाता है
  • इतिहास डिफ़ॉल्ट रूप से उथला है: तीन सफल और एक विफल रन सहेजे जाते हैं, और लॉग पॉड के साथ चले जाते हैं। पिछले मंगलवार की विफलता समझाना अक्सर असंभव होता है
  • यदि लगातार सौ से अधिक शेड्यूल छूट जाएँ और startingDeadlineSeconds सेट न हो, तो CronJob शेड्यूल करना ही बंद कर देता है और किसी के ध्यान देने का इंतज़ार करता है
02

चरण ही Job हैं, और लॉग वापस आते हैं

Dagu हर चरण के लिए एक सिंगल-कंटेनर Job बनाता है, उसकी प्रतीक्षा करता है, पॉड के लॉग स्ट्रीम करता है, और समाप्त कंटेनर का एग्ज़िट कोड उपयोग करता है। लॉग रन इतिहास में लिखे जाते हैं, इसलिए पॉड और Job के जाने के बाद भी बने रहते हैं।

  • depends सीधे क्रम बताता है, इसलिए विफल एक्सट्रैक्ट लोड को रोक देता है, उसे खाली डेटा नहीं खिलाता
  • retry_policy नया Job बनाकर चरण दोहराता है, जो backoffLimit द्वारा पॉड को उसी जगह पुनः आरंभ करने से अलग बात है
  • डिफ़ॉल्ट रूप से समाप्ति के बाद Job हटा दिए जाते हैं, इसलिए रात्रिकालीन ग्राफ़ पूर्ण हुए Job की कतार नहीं छोड़ता

Kubernetes कंटेनर का एकीकृत लॉग स्ट्रीम देता है, इसलिए इस चरण प्रकार में stdout और stderr एक ही मिश्रित स्ट्रीम के रूप में आते हैं।

तीन चरणों का बैच वास्तविक Kubernetes Job के रूप में
# 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 किसी भी प्रयास से नहीं कर सकता, क्योंकि CronJob उसी क्लस्टर तक सीमित है जिसमें वह रहता है। दो क्लस्टर, या एक क्लस्टर और एक ऑन-प्रिमाइस सिस्टम को छूने वाले वर्कफ़्लो के लिए Kubernetes के भीतर कोई जगह नहीं है।

  • kubeconfig और context प्रति चरण सेट किए जा सकते हैं, इसलिए स्टेजिंग और प्रोडक्शन एक ही वर्कफ़्लो के दो चरण बनते हैं, न कि एक ही मैनिफ़ेस्ट की दो तैनातियाँ
  • क्लस्टर चरण किसी पुराने होस्ट पर SSH चरण और किसी SaaS API पर HTTP कॉल के बीच रह सकता है, क्योंकि ऑर्केस्ट्रेटर स्वयं क्लस्टर संसाधन नहीं है
  • 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 ऑब्जेक्ट हों, उसी RBAC और GitOps प्रवाह से प्रबंधित, तो वह Argo का डिज़ाइन है, Dagu का नहीं
05

एक्ज़ीक्यूटर जानबूझकर क्या नहीं देता

Kubernetes चरण प्रकार जानबूझकर सीमित रखा गया है। यह बैच चरण के सामान्य स्वरूप को अच्छी तरह कवर करता है और सामान्य मैनिफ़ेस्ट लागू करने वाला बनने की कोशिश नहीं करता।

  • प्रति चरण एक कंटेनर, एक ही कमांड। कच्ची Pod या Job स्पेक सीधे पास करने का रास्ता नहीं है
  • Job के parallelism, completions, completion mode और success policy उपलब्ध नहीं हैं, इसलिए इंडेक्स्ड और समानांतर Job दायरे से बाहर रहते हैं
  • restart_policy विन्यास योग्य नहीं है, और Windows-विशिष्ट विकल्प, SELinux, AppArmor तथा proc_mount उपलब्ध नहीं हैं

यदि कोई फ़ील्ड इस चरण प्रकार के लिए प्रलेखित नहीं है तो उसे असमर्थित मानें। जिस काम को पूर्ण Job स्पेक चाहिए, उसे कमांड चरण से kubectl द्वारा लागू करना बेहतर है, या Argo पर छोड़ देना।

FAQ

Practical questions before adopting

क्या Dagu को क्लस्टर के भीतर चलाना ज़रूरी है?

नहीं। यह पहले स्पष्ट kubeconfig, फिर सामान्य kubeconfig लोडिंग नियम, फिर इन-क्लस्टर कॉन्फ़िगरेशन से क्लस्टर तय करता है, इसलिए दोनों तरह काम करता है। बाहर चलाना ही क्लस्टर-पार और सीमा-पार वर्कफ़्लो संभव बनाता है; जब वह जिन चीज़ों को छूता है वे सब उसी क्लस्टर में हों तो भीतर चलाना भी ठीक है।

यह kubectl बुलाने वाली स्क्रिप्ट चलाने वाले CronJob से कैसे अलग है?

उस तरीके से केवल क्रम मिलता है: रैपर पॉड का एग्ज़िट कोड छिपा देता है कि कौन-सा चरण विफल हुआ, रीट्राई पूरी स्क्रिप्ट दोबारा चलाती है, और लॉग एक अविभेदित धारा होते हैं जो पॉड के साथ गायब हो जाती है। Dagu प्रति चरण एक Job बनाता है, इसलिए हर चरण की अपनी स्थिति, अपनी रीट्राई और अपना सहेजा हुआ लॉग होता है।

क्या चरण फ़ाइल सिस्टम साझा करते हैं?

नहीं। हर चरण अलग Job है और इसलिए अलग पॉड। चरणों के बीच डेटा ऑब्जेक्ट स्टोरेज, हर चरण में माउंट किए गए परसिस्टेंट वॉल्यूम, या छोटे मानों के लिए चरण आउटपुट से भेजें — ठीक वैसे ही जैसे अलग-अलग Job के बीच करते।

रन रद्द होने पर Job का क्या होता है?

रद्द करने, समाप्त करने और टाइमआउट के रास्ते cleanup_policy के keep होने पर भी सफ़ाई लागू करते हैं, इसलिए रोका गया रन Job पीछे नहीं छोड़ता। सामान्य संचालन में डिफ़ॉल्ट यह है कि समाप्ति पर Job हटा दिया जाए।

क्या हमें Argo Workflows के बजाय यह उपयोग करना चाहिए?

केवल तब जब ऑर्केस्ट्रेटर का क्लस्टर के बाहर होना आपके लिए उपयोगी हो। यदि सभी कार्य एक ही क्लस्टर के कंटेनर हैं और आप चाहते हैं कि वर्कफ़्लो उसी RBAC और GitOps के तहत Kubernetes ऑब्जेक्ट हों, तो Argo बेहतर बैठता है। Dagu उन ग्राफ़ के लिए उपयुक्त है जो क्लस्टर जॉब को होस्ट, API और मानवीय अनुमोदन के साथ मिलाते हैं।

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.