Di episode ini kita akan coba bahas Kubernetes CronJob, controller untuk menjalankan Job berdasarkan time-based schedule. Kita akan mempelajari bagaimana CronJob enable scheduled task, recurring operation, dan automated maintenance di Kubernetes.

Catatan
Untuk kalian yang ingin membaca episode sebelumnya, bisa click thumbnail episode 14 di bawah ini
Di episode sebelumnya kita sudah belajar tentang Job, yang menjalankan task sampai completion. Selanjutnya di episode 15 kali ini, kita akan coba bahas CronJob, yang build on Job untuk menyediakan scheduled, recurring task execution.
Catatan: Disini saya akan menggunakan Kubernetes Cluster yang di install melalui K3s.
Jika kalian familiar dengan Unix/Linux cron, CronJob di Kubernetes bekerja mirip - dia membuat Job on a repeating schedule. Bayangkan seperti task scheduler cluster kalian untuk automated backup, report, cleanup, dan recurring operation apapun.
CronJob membuat Job berdasarkan time-based schedule. Dia menggunakan cron format yang sama seperti Unix/Linux system untuk define kapan Job harus run. CronJob mengelola lifecycle dari Job, membuatnya di scheduled time dan cleanup old Job.
Bayangkan CronJob seperti cron daemon di Linux - dia menjalankan task di specified time secara otomatis. Di Kubernetes, CronJob membuat Job object on schedule, yang kemudian membuat Pod untuk execute actual work.
Karakteristik kunci CronJob:
CronJob dirancang untuk recurring task yang perlu run on a schedule:
Tanpa CronJob, kalian perlu:
Mari kita pahami perbedaan kunci nya:
| Aspek | CronJob | Job |
|---|---|---|
| Execution | Scheduled, recurring | One-time |
| Schedule | Cron syntax | Immediate |
| Job creation | Automatic on schedule | Manual |
| Use case | Recurring task | One-time task |
| Management | Create dan manage Job | Run Pod directly |
| Cleanup | Automatic dengan history limit | Manual atau TTL |
Contoh scenario:
CronJob menggunakan standard cron format dengan 5 field:
┌───────────── minute (0 - 59)
│ ┌───────────── hour (0 - 23)
│ │ ┌───────────── day of month (1 - 31)
│ │ │ ┌───────────── month (1 - 12)
│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday)
│ │ │ │ │
│ │ │ │ │
* * * * *Setiap menit:
* * * * *Setiap jam di menit 0:
0 * * * *Setiap hari jam 2:30 pagi:
30 2 * * *Setiap Senin jam 9:00 pagi:
0 9 * * 1Setiap 15 menit:
*/15 * * * *Setiap 6 jam:
0 */6 * * *Hari pertama setiap bulan di midnight:
0 0 1 * *Setiap weekday jam 6:00 sore:
0 18 * * 1-5Setiap Minggu jam 3:00 pagi:
0 3 * * 0Dua kali sehari (6 pagi dan 6 sore):
0 6,18 * * *Mari kita buat basic CronJob:
Buat file bernama cronjob-basic.yml:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cronjob
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:1.36
command:
- /bin/sh
- -c
- date; echo "Hello from CronJob!"
restartPolicy: OnFailureCronJob ini run setiap menit.
Apply konfigurasi:
sudo kubectl apply -f cronjob-basic.ymlVerifikasi CronJob dibuat:
sudo kubectl get cronjobsAtau gunakan shorthand:
sudo kubectl get cjOutput:
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
hello-cronjob */1 * * * * False 0 <none> 10sTunggu satu menit dan cek Job yang dibuat:
sudo kubectl get jobsOutput:
NAME COMPLETIONS DURATION AGE
hello-cronjob-28458640 1/1 5s 45s
hello-cronjob-28458641 1/1 4s 5sCek Pod:
sudo kubectl get podsLihat log dari completed Pod:
sudo kubectl logs hello-cronjob-28458640-abc12Output:
Sun Mar 1 10:00:00 UTC 2026
Hello from CronJob!Kontrol bagaimana CronJob handle overlapping execution:
Allow concurrent Job untuk run:
apiVersion: batch/v1
kind: CronJob
metadata:
name: concurrent-cronjob
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Allow
jobTemplate:
spec:
template:
spec:
containers:
- name: task
image: busybox:1.36
command:
- /bin/sh
- -c
- echo "Starting"; sleep 120; echo "Done"
restartPolicy: OnFailureBehavior:
Prevent concurrent Job:
apiVersion: batch/v1
kind: CronJob
metadata:
name: sequential-cronjob
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: task
image: busybox:1.36
command:
- /bin/sh
- -c
- echo "Starting"; sleep 120; echo "Done"
restartPolicy: OnFailureBehavior:
Replace currently running Job dengan yang baru:
apiVersion: batch/v1
kind: CronJob
metadata:
name: replace-cronjob
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Replace
jobTemplate:
spec:
template:
spec:
containers:
- name: task
image: busybox:1.36
command:
- /bin/sh
- -c
- echo "Starting"; sleep 120; echo "Done"
restartPolicy: OnFailureBehavior:
Kontrol berapa banyak completed dan failed Job yang di-keep:
apiVersion: batch/v1
kind: CronJob
metadata:
name: history-cronjob
spec:
schedule: "*/5 * * * *"
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: task
image: busybox:1.36
command: ["echo", "Task completed"]
restartPolicy: OnFailureCronJob ini:
Default value:
successfulJobsHistoryLimit: 3failedJobsHistoryLimit: 1Set deadline untuk starting Job:
apiVersion: batch/v1
kind: CronJob
metadata:
name: deadline-cronjob
spec:
schedule: "0 2 * * *"
startingDeadlineSeconds: 3600
jobTemplate:
spec:
template:
spec:
containers:
- name: task
image: busybox:1.36
command: ["echo", "Task completed"]
restartPolicy: OnFailureCronJob ini:
Use case: Jika cluster down saat scheduled time, Job masih bisa start saat cluster kembali up (dalam deadline).
Temporarily pause CronJob execution:
apiVersion: batch/v1
kind: CronJob
metadata:
name: suspended-cronjob
spec:
schedule: "*/1 * * * *"
suspend: true
jobTemplate:
spec:
template:
spec:
containers:
- name: task
image: busybox:1.36
command: ["echo", "Task completed"]
restartPolicy: OnFailureDengan suspend: true, CronJob tidak akan membuat Job baru.
Suspend existing CronJob:
sudo kubectl patch cronjob hello-cronjob -p '{"spec":{"suspend":true}}'Resume suspended CronJob:
sudo kubectl patch cronjob hello-cronjob -p '{"spec":{"suspend":false}}'Specify timezone untuk schedule (Kubernetes 1.25+):
apiVersion: batch/v1
kind: CronJob
metadata:
name: timezone-cronjob
spec:
schedule: "0 9 * * *"
timeZone: "America/New_York"
jobTemplate:
spec:
template:
spec:
containers:
- name: task
image: busybox:1.36
command: ["echo", "Good morning!"]
restartPolicy: OnFailureIni run jam 9:00 pagi Eastern Time.
Important
Penting: Timezone support require Kubernetes 1.25 atau later. Tanpa timeZone field, schedule menggunakan controller manager's timezone (biasanya UTC).
Untuk melihat informasi detail tentang CronJob:
sudo kubectl describe cronjob hello-cronjobOutput:
Name: hello-cronjob
Namespace: default
Labels: <none>
Annotations: <none>
Schedule: */1 * * * *
Concurrency Policy: Allow
Suspend: False
Successful Job History Limit: 3
Failed Job History Limit: 1
Starting Deadline Seconds: <unset>
Selector: <unset>
Parallelism: <unset>
Completions: <unset>
Pod Template:
Labels: <none>
Containers:
hello:
Image: busybox:1.36
Command:
/bin/sh
-c
date; echo "Hello from CronJob!"
Environment: <none>
Mounts: <none>
Volumes: <none>
Last Schedule Time: Sun, 01 Mar 2026 10:05:00 +0000
Active Jobs: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 5m cronjob-controller Created job hello-cronjob-28458640
Normal SuccessfulCreate 4m cronjob-controller Created job hello-cronjob-28458641
Normal SuccessfulCreate 3m cronjob-controller Created job hello-cronjob-28458642apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
labels:
app: backup
type: database
spec:
schedule: "0 2 * * *"
timeZone: "UTC"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
activeDeadlineSeconds: 3600
template:
metadata:
labels:
app: backup
type: database
spec:
containers:
- name: backup
image: postgres:15-alpine
command:
- /bin/sh
- -c
- |
BACKUP_FILE="/backup/db-backup-$(date +%Y%m%d-%H%M%S).sql"
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME > $BACKUP_FILE
gzip $BACKUP_FILE
echo "Backup completed: ${BACKUP_FILE}.gz"
env:
- name: DB_HOST
value: "postgres-service"
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_NAME
value: "production"
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: backup-storage
mountPath: /backup
volumes:
- name: backup-storage
persistentVolumeClaim:
claimName: backup-pvc
restartPolicy: OnFailureCronJob ini:
apiVersion: batch/v1
kind: CronJob
metadata:
name: log-cleanup
spec:
schedule: "0 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
containers:
- name: cleanup
image: busybox:1.36
command:
- /bin/sh
- -c
- |
echo "Starting log cleanup..."
find /logs -name "*.log" -mtime +7 -delete
echo "Cleanup completed"
volumeMounts:
- name: logs
mountPath: /logs
volumes:
- name: logs
hostPath:
path: /var/log/app
restartPolicy: OnFailureCronJob ini:
apiVersion: batch/v1
kind: CronJob
metadata:
name: weekly-report
spec:
schedule: "0 8 * * 1"
timeZone: "America/New_York"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 4
failedJobsHistoryLimit: 2
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
containers:
- name: report-generator
image: python:3.11-slim
command:
- python
- -c
- |
import datetime
print("Generating weekly report...")
print(f"Report date: {datetime.datetime.now()}")
# Generate report logic here
print("Report generated successfully")
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
- name: SMTP_HOST
value: "smtp.example.com"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
restartPolicy: OnFailureCronJob ini:
apiVersion: batch/v1
kind: CronJob
metadata:
name: cache-warmer
spec:
schedule: "0 6 * * *"
concurrencyPolicy: Replace
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 1
jobTemplate:
spec:
activeDeadlineSeconds: 1800
template:
spec:
containers:
- name: warmer
image: curlimages/curl:latest
command:
- /bin/sh
- -c
- |
echo "Warming cache..."
curl -s http://api-service/api/warm-cache
echo "Cache warmed successfully"
restartPolicy: OnFailureCronJob ini:
apiVersion: batch/v1
kind: CronJob
metadata:
name: certificate-checker
spec:
schedule: "0 0 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
containers:
- name: checker
image: alpine:latest
command:
- /bin/sh
- -c
- |
apk add --no-cache openssl
echo "Checking certificates..."
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
echo "Certificate check completed"
restartPolicy: OnFailureCronJob ini:
sudo kubectl get cronjobssudo kubectl describe cronjob hello-cronjobsudo kubectl get jobs --selector=cronjob=hello-cronjobsudo kubectl get cronjobs -wsudo kubectl get cronjob hello-cronjob -o jsonpath='{.status.lastScheduleTime}'sudo kubectl get events --sort-by='.lastTimestamp' | grep CronJobProblem: Menggunakan wrong cron format atau invalid value.
Solusi: Validate cron syntax sebelum apply:
# Gunakan online cron validator atau test locally
# Benar: */5 * * * * (setiap 5 menit)
# Salah: 5 * * * * (di menit 5 setiap jam)Problem: Multiple Job running concurrently saat seharusnya tidak.
Solusi: Set appropriate concurrencyPolicy:
spec:
concurrencyPolicy: Forbid # Untuk task yang tidak boleh overlapProblem: Accumulation dari old Job consuming resource.
Solusi: Set history limit:
spec:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1Problem: Job run di wrong time karena timezone confusion.
Solusi: Explicitly set timezone (Kubernetes 1.25+):
spec:
timeZone: "America/New_York"Problem: Missed schedule pile up saat cluster recover.
Solusi: Set startingDeadlineSeconds:
spec:
startingDeadlineSeconds: 3600Problem: CronJob Pod consume excessive resource.
Solusi: Selalu set resource limit di Job template:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"Pilih berdasarkan task requirement:
spec:
concurrencyPolicy: Forbid # Untuk sequential task
# concurrencyPolicy: Allow # Untuk independent task
# concurrencyPolicy: Replace # Untuk latest-only taskPrevent Job accumulation:
spec:
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1Handle missed schedule gracefully:
spec:
startingDeadlineSeconds: 3600Make schedule explicit (Kubernetes 1.25+):
spec:
timeZone: "UTC"Prevent Job dari running terlalu lama:
jobTemplate:
spec:
activeDeadlineSeconds: 3600Organize dan filter CronJob:
metadata:
labels:
app: backup
type: database
schedule: dailyJangan hardcode sensitive data:
env:
- name: PASSWORD
valueFrom:
secretKeyRef:
name: credentials
key: passwordPrevent resource exhaustion:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"Verify cron syntax dan timing:
# Test dengan frequent schedule dulu
schedule: "*/1 * * * *" # Setiap menit untuk testing
# Kemudian ubah ke production schedule
schedule: "0 2 * * *" # Daily jam 2 pagiSet up alert untuk failed Job:
# Check untuk failed Job
sudo kubectl get jobs --field-selector status.successful=0sudo kubectl delete cronjob hello-cronjobIni menghapus CronJob dan semua Job yang dibuatnya.
sudo kubectl delete cronjob hello-cronjob --cascade=orphanIni hanya menghapus CronJob, membiarkan Job running.
Cek apakah CronJob suspended:
sudo kubectl get cronjob hello-cronjob -o jsonpath='{.spec.suspend}'Cek schedule syntax:
sudo kubectl describe cronjob hello-cronjobCek Job log:
# Get latest Job
JOB=$(kubectl get jobs --sort-by=.metadata.creationTimestamp | tail -1 | awk '{print $1}')
# Get Pod dari Job
POD=$(kubectl get pods --selector=job-name=$JOB -o jsonpath='{.items[0].metadata.name}')
# View log
sudo kubectl logs $PODAdjust history limit:
sudo kubectl patch cronjob hello-cronjob -p '{"spec":{"successfulJobsHistoryLimit":3}}'Pada episode 15 ini, kita telah membahas CronJob di Kubernetes secara mendalam. Kita sudah belajar apa itu CronJob, bagaimana dia build on Job untuk menyediakan scheduled execution, dan cara menggunakannya untuk recurring task.
Key takeaway:
CronJob essential untuk automating recurring task di Kubernetes. Dengan memahami CronJob, kalian bisa effectively schedule backup, generate report, cleanup resource, dan automate maintenance task tanpa external scheduler.
Bagaimana, makin jelas kan tentang CronJob di Kubernetes? Di episode 16 berikutnya, kita akan membahas Node Selector, mekanisme simple untuk kontrol Pod placement di specific node berdasarkan label. Jadi, pastikan tetap semangat belajar dan nantikan episode selanjutnya!
Catatan
Untuk kalian yang ingin lanjut ke episode berikutnya, bisa click thumbnail episode 16 di bawah ini