Job dan CronJob

Kerja batch sekali jalan dengan Job dan kerja terjadwal dengan CronJob beserta kebijakan ulangnya.

Daftar isi

Job adalah objek Kubernetes yang menjalankan Pod hingga pekerjaannya tuntas, sedangkan CronJob membuat Job baru mengikuti jadwal yang ditetapkan. Keduanya melayani kerja batch, yaitu pemrosesan data dalam jumlah besar yang berhenti setelah selesai, berbeda dari layanan yang harus terus berjalan. Topik ini membahas bidang completions, parallelism, backoffLimit, dan activeDeadlineSeconds, kebijakan restartPolicy, pola jadwal CronJob, serta perbandingannya dengan Deployment. Simulasi di tengah topik menghitung ulangan Job dan merangkum pembacaan jadwal cron.

Job untuk kerja sekali jalan#

Deployment menuntut Pod agar terus berjalan, sedangkan Job justru menunggu Pod berhenti dengan sukses. Job menganggap satu slot pekerjaan selesai apabila Pod yang menjalankannya keluar dengan kode sukses, dan pekerjaan keseluruhan selesai apabila jumlah Pod yang sukses mencapai completions. Keberhasilan tercatat pada bidang .status.succeeded, sedangkan kegagalan terakumulasi pada .status.failed. Angka keberhasilan itulah yang dibandingkan dengan completions, sedangkan sisa pekerjaan terus dihitung ulang oleh controller.

Berkas job.yaml berikut mendefinisikan Job bernama hitung-statistik yang menjalankan empat slot pekerjaan dengan dua Pod paralel untuk menghitung statistik nilai ujian. Untuk contoh sederhana, setiap Pod menjalankan perintah yang sama, sedangkan pembagian pekerjaan nyata biasanya dilakukan dengan membedakan input per Pod atau melalui antrean bersama:

yaml job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: hitung-statistik
  labels:
    app: aplikasi-catatan
spec:
  completions: 4
  parallelism: 2
  backoffLimit: 6
  activeDeadlineSeconds: 600
  template:
    metadata:
      labels:
        app: aplikasi-catatan
    spec:
      restartPolicy: Never
      containers:
        - name: hitung
          image: python:3.12-slim
          command:
            - python
            - -c
            - |
              import statistics
              nilai = [72, 85, 90, 68, 77, 88]
              print(f"n={len(nilai)} rata-rata={statistics.mean(nilai):.1f} median={statistics.median(nilai)}")

Perintah berikut menerapkan Job lalu menampilkan statusnya setelah selesai. Kolom COMPLETIONS menunjukkan empat slot yang seluruhnya sukses, dan keempat Pod mencapai keadaan Completed:

bash
# tidak-dijalankan
# terapkan manifest Job ke kluster
kubectl apply -f job.yaml
# tampilkan status Job beserta pod yang dihasilkannya
kubectl get jobs,pods -l app=aplikasi-catatan
text Contoh keluaran
job.batch/hitung-statistik created
NAME               STATUS     COMPLETIONS   DURATION   AGE
hitung-statistik   Complete   4/4           2m21s      2m21s
NAME                     READY   STATUS      RESTARTS   AGE
hitung-statistik-w9kfc   0/1     Completed   0          2m21s
hitung-statistik-b4n8m   0/1     Completed   0          2m21s
hitung-statistik-6tpdx   0/1     Completed   0          1m19s
hitung-statistik-z2vqh   0/1     Completed   0          1m19s

Kebijakan ulang dan batas waktu#

Kegagalan merupakan keadaan yang diantisipasi pada kerja batch, sehingga Job menyediakan lapisan ulangan yang diatur tiga bidang. backoffLimit menyatakan banyaknya kegagalan yang ditoleransi sebelum Job dinyatakan gagal, dan jumlahnya dihitung dari seluruh Pod yang gagal, bukan per slot pekerjaan. restartPolicy: Never membuat kegagalan berujung pada Pod baru, sedangkan restartPolicy: OnFailure memulai ulang container pada Pod yang sama. Perbedaannya memengaruhi kecepatan ulangan, karena Pod baru membawa biaya penjadwalan, sedangkan mulai ulang container tetap di dalam Pod yang sama. Pencacahan keberhasilan dan kegagalan terbaca pada bidang .status Job, sehingga keputusan ulangan selalu dapat diikuti tanpa membaca log satu per satu.

activeDeadlineSeconds menetapkan batas waktu untuk keseluruhan Job, dihitung sejak Job mulai berjalan. Apabila batas terlampaui, seluruh Pod yang masih berjalan dihentikan dan Job dinyatakan gagal dengan alasan DeadlineExceeded, bahkan apabila backoffLimit belum tercapai. Kombinasi keduanya membatasi kerugian, karena backoffLimit menghentikan ulangan yang percuma, sedangkan activeDeadlineSeconds menghentikan pekerjaan yang menggantung terlalu lama. Batas waktu berlaku tanpa memandang keberhasilan parsial, sehingga slot yang sudah sukses tetap tercatat meskipun Job keseluruhan dinyatakan gagal.

Simulasi completions, parallelism, dan backoffLimit#

Skrip berikut membaca job.yaml, lalu mensimulasikan pengisian slot pekerjaan dengan pola kegagalan yang tetap, sehingga banyaknya percobaan dapat dihitung ulang persis. Setiap slot mewakili satu unit pekerjaan yang sukses setelah salah satu Pod-nya keluar dengan sukses. Pod yang gagal mengembalikan slotnya ke antrean, dan kegagalannya dihitung terhadap backoffLimit. Bagian akhir skrip membaca cronjob.yaml dan meringkas lima field jadwal cron beserta interpretasi tiga pola yang umum.

python simulasi-job.py
"""Simulasi completions, parallelism, dan backoffLimit Job serta pola jadwal CronJob."""
import yaml

with open("job.yaml", encoding="utf-8") as berkas:
    job = yaml.safe_load(berkas)
spec = job["spec"]
completions = spec["completions"]
parallelism = spec["parallelism"]
backoff_limit = spec["backoffLimit"]
restart = spec["template"]["spec"]["restartPolicy"]

# pola kegagalan deterministik: banyaknya percobaan gagal sebelum sukses per slot
kegagalan_slot = [2, 0, 1, 1]
assert len(kegagalan_slot) == completions

print(f"completions={completions}  parallelism={parallelism}  backoffLimit={backoff_limit}  restartPolicy={restart}")
print()
antre = list(range(completions))  # slot pekerjaan yang belum sukses
percobaan = {s: 0 for s in range(completions)}
urutan_pod = 0
total_gagal = 0
berjalan = []
while antre or berjalan:
    # isi kapasitas paralel dengan slot yang belum sukses
    while antre and len(berjalan) < parallelism:
        s = antre.pop(0)
        percobaan[s] += 1
        urutan_pod += 1
        gagal = percobaan[s] <= kegagalan_slot[s]
        berjalan.append((s, gagal))
        status = "gagal" if gagal else "sukses"
        print(f"  pod {urutan_pod}  slot {s}  percobaan {percobaan[s]} -> {status}")
    # percobaan selesai: slot yang gagal dikembalikan ke antrean
    for s, gagal in berjalan:
        if gagal:
            total_gagal += 1
            antre.append(s)
    berjalan = []
if total_gagal > backoff_limit:
    print()
    print(f"Job gagal: {total_gagal} kegagalan melebihi backoffLimit {backoff_limit}")
else:
    print()
    print(f"Job selesai: {completions} slot sukses, {urutan_pod} pod dibuat, {total_gagal} kegagalan (batas {backoff_limit})")

FIELD_CRON = [
    ("menit", "0-59"),
    ("jam", "0-23"),
    ("tanggal dalam bulan", "1-31"),
    ("bulan", "1-12"),
    ("hari dalam minggu", "0-6, dengan 0 untuk Minggu"),
]
ARTI = {
    "30 2 * * *": "pukul 02:30 setiap hari",
    "0 * * * *": "pukul 00:00 setiap awal jam",
    "0 9 * * 1-5": "pukul 09:00 hari Senin sampai Jumat",
}

with open("cronjob.yaml", encoding="utf-8") as berkas:
    cronjob = yaml.safe_load(berkas)
schedule = cronjob["spec"]["schedule"]
policy = cronjob["spec"]["concurrencyPolicy"]

print()
print(f"CronJob {cronjob['metadata']['name']} menjadwalkan Job dengan schedule \"{schedule}\" dan concurrencyPolicy {policy}")
print()
print("Lima field jadwal cron, berurutan dari kiri ke kanan:")
for nama_field, rentang in FIELD_CRON:
    print(f"  {nama_field:<22} {rentang}")
print()
print("Interpretasi pola jadwal umum:")
for pola, arti in ARTI.items():
    print(f"  {pola:<12} -> {arti}")
text Keluaran
completions=4  parallelism=2  backoffLimit=6  restartPolicy=Never

  pod 1  slot 0  percobaan 1 -> gagal
  pod 2  slot 1  percobaan 1 -> sukses
  pod 3  slot 2  percobaan 1 -> gagal
  pod 4  slot 3  percobaan 1 -> gagal
  pod 5  slot 0  percobaan 2 -> gagal
  pod 6  slot 2  percobaan 2 -> sukses
  pod 7  slot 3  percobaan 2 -> sukses
  pod 8  slot 0  percobaan 3 -> sukses

Job selesai: 4 slot sukses, 8 pod dibuat, 4 kegagalan (batas 6)

CronJob statistik-harian menjadwalkan Job dengan schedule "30 2 * * *" dan concurrencyPolicy Forbid

Lima field jadwal cron, berurutan dari kiri ke kanan:
  menit                  0-59
  jam                    0-23
  tanggal dalam bulan    1-31
  bulan                  1-12
  hari dalam minggu      0-6, dengan 0 untuk Minggu

Interpretasi pola jadwal umum:
  30 2 * * *   -> pukul 02:30 setiap hari
  0 * * * *    -> pukul 00:00 setiap awal jam
  0 9 * * 1-5  -> pukul 09:00 hari Senin sampai Jumat

Delapan Pod dibuat untuk empat slot karena empat kegagalan menyebar pada slot 0, slot 2, dan slot 3. Total kegagalan berjumlah empat dan masih berada di bawah backoffLimit 6, sehingga Job dinyatakan selesai. Apabila pola kegagalan menambah satu kegagalan lagi, totalnya menjadi lima dan tetap lolos, sedangkan tujuh kegagalan melampaui batas dan menghentikan Job dengan keadaan Failed.

Bagian cron menampilkan lima field jadwal dalam urutan menit, jam, tanggal dalam bulan, bulan, dan hari dalam minggu. Tanda bintang berarti pemenuhan setiap nilai, sedangkan tanda hubung menyusun rentang, sehingga 30 2 * * * dibaca sebagai pukul 02:30 setiap hari tanpa memandang tanggal maupun hari. Interpretasi semacam ini bersifat statis dan tidak menyentuh waktu berjalan pada kluster mana pun.

Pola schedule CronJob#

CronJob membuat objek Job baru setiap kali jadwal tercapai, sehingga kebijakan ulang pada Job tetap berlaku untuk setiap pelaksanaannya. Bidang schedule menuliskan lima field jadwal cron, sedangkan jobTemplate berisi spec Job yang akan dibuat. Bidang concurrencyPolicy mengatur perilaku saat pelaksanaan sebelumnya belum selesai, yaitu Allow membiarkan tumpang tindih, Forbid menunda pelaksanaan baru, dan Replace menghentikan pelaksanaan lama untuk digantikan. Apabila bidang timeZone tidak diisi, jadwal ditafsirkan mengikuti zona waktu komponen control plane.

Berkas cronjob.yaml berikut menjadwalkan statistik nilai ujian setiap pukul 02:30 dengan kebijakan Forbid, sehingga dua pelaksanaan tidak pernah berjalan bersamaan. Riwayat pelaksanaan dibatasi melalui successfulJobsHistoryLimit dan failedJobsHistoryLimit, yang menyimpan objek Job lama agar dapat diperiksa. Batas ini berlaku secara bawaan, sehingga daftar Job lama tidak menumpuk tanpa kendali:

yaml cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: statistik-harian
  labels:
    app: aplikasi-catatan
spec:
  schedule: "30 2 * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 1800
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: hitung
              image: python:3.12-slim
              command:
                - python
                - -c
                - |
                  import statistics
                  nilai = [72, 85, 90, 68, 77, 88]
                  print(f"n={len(nilai)} rata-rata={statistics.mean(nilai):.1f}")

Perintah berikut menerapkan CronJob lalu menampilkan jadwal dan keadaan pelaksanaannya:

bash
# tidak-dijalankan
# terapkan manifest CronJob ke kluster
kubectl apply -f cronjob.yaml
# tampilkan CronJob beserta jadwal dan pelaksanaan aktifnya
kubectl get cronjob statistik-harian
text Contoh keluaran
NAME               SCHEDULE      TIMEZONE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
statistik-harian   30 2 * * *    <none>      False     0        8h              3d2h

Perbandingan dengan Deployment#

Deployment, Job, dan CronJob melayani masa hidup Pod yang berbeda. Deployment menjaga sejumlah replika agar selalu berjalan dan mengganti Pod yang mati secara otomatis, sedangkan Job menghitung keberhasilan dan berhenti setelah target completions tercapai. CronJob berada satu lapis di atas Job karena tidak menjalankan Pod secara langsung, melainkan membuat Job mengikuti jadwal.

AspekDeploymentJobCronJob
Masa hidup PodTerus berjalanBerhenti setelah suksesBerhenti setelah sukses, diulang sesuai jadwal
Jumlah PodreplicascompletionsSatu Job per pelaksanaan jadwal
Reaksi terhadap kegagalanPod dibuat ulang tanpa batasDihitung terhadap backoffLimitDihitung terhadap backoffLimit per Job
Pembaruan templateRollingUpdate bertahapTanpa rollout, Pod baru mengikuti template terbaruTanpa rollout, diwarisi jobTemplate
Contoh kegunaanLayanan web aplikasi catatanPemrosesan statistik nilai ujianStatistik harian terjadwal

Pemilihan objek mengikuti pertanyaan sederhana tentang masa hidup Pod. Apabila layanan harus selalu tersedia, Deployment menjadi pilihan bawaan. Apabila pekerjaan berakhir setelah satu kali berjalan, Job menghindari Pod yang tidak pernah berhenti, dan hasilnya terbaca pada jumlah keberhasilan. Apabila pekerjaan yang sama perlu berulang pada waktu tertentu, CronJob menetapkan jadwalnya tanpa alat penjadwal tambahan di luar kluster.

Ringkasan#

  • Job menjalankan Pod hingga sukses dan dinyatakan selesai setelah jumlah Pod yang berhasil mencapai completions, dengan kecepatan yang diatur parallelism.
  • backoffLimit membatasi total kegagalan, sedangkan activeDeadlineSeconds membatasi durasi keseluruhan Job dan menghentikannya dengan alasan DeadlineExceeded.
  • restartPolicy pada template Job wajib Never atau OnFailure, karena Pod yang selalu dimulai ulang tidak pernah memberi tanda keberhasilan.
  • CronJob membuat Job mengikuti jadwal lima field cron dan mengatur tumpang tindih pelaksanaan melalui concurrencyPolicy.

Referensi#

  • Jobs — Kubernetes — Dokumentasi Job, termasuk completions, parallelism, backoffLimit, dan activeDeadlineSeconds.
  • CronJob — Kubernetes — Dokumentasi CronJob, termasuk schedule, concurrencyPolicy, dan batas riwayat pelaksanaan.