🔀Alternativen zu cron
cron ist überall – aber nicht immer die beste Wahl. systemd-Timer protokollieren sauber und holen verpasste Läufe nach, Kubernetes plant Jobs im Cluster, und in Anwendungen erledigen Bibliotheken das Scheduling.
⚙️systemd-Timer: cron → OnCalendar
Prüfen: systemd-analyze calendar --iterations=5 'Mon *-*-* 04:30:00'
[Unit] Description=backup (umgerechnet aus cron: 30 4 * * 1) [Timer] OnCalendar=Mon *-*-* 04:30:00 Persistent=true [Install] WantedBy=timers.target
[Unit] Description=backup [Service] Type=oneshot ExecStart=/usr/local/bin/backup.sh
systemctl daemon-reload systemctl enable --now backup.timer systemctl list-timers backup.timer journalctl -u backup.service # Ausgabe landet im Journal
$ systemd-analyze calendar weekly Original form: weekly Normalized form: Mon *-*-* 00:00:00 $ systemd-analyze calendar '*:0/15' Normalized form: *-*-* *:00/15:00
AccuracySec=1s setzen... statt -; Wiederholung 00/15 statt */15; Wochentage als Mon..Fri. Wochentag und Datum sind immer UND-verknüpft – für die cron-ODER-Regel braucht es zwei OnCalendar=-Zeilen. Sekunden sind möglich.# ad hoc, ohne Unit-Datei: systemd-run --on-calendar='Mon *-*-* 04:30:00' /usr/local/bin/backup.sh # alle Timer mit nächstem Lauf: systemctl list-timers --all
☸️Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: backup # max. 52 Zeichen
spec:
schedule: "30 4 * * 1"
timeZone: "Europe/Berlin" # stabil seit v1.27
concurrencyPolicy: Forbid # Allow | Forbid | Replace
startingDeadlineSeconds: 600
successfulJobsHistoryLimit: 3 # Standard 3
failedJobsHistoryLimit: 1 # Standard 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: registry.example.org/backup:1.4
args: ["/app/backup.sh"]Violett: läuft allein · Rot: parallel · Orange: durch Replace abgebrochen · ✗: übersprungen. Zeitraum 90 Minuten.
timeZone gilt die Zeitzone des kube-controller-managers. CRON_TZ= oder TZ= im schedule lehnt die API ab. Hat der Controller mehr als 100 Termine verpasst, startet er den Job nicht mehr („too many missed start times“) – startingDeadlineSeconds begrenzt das Zeitfenster. Laut Doku können in Ausnahmefällen zwei oder kein Job entstehen: Jobs sollten idempotent sein.🐳Docker: Cron im Container oder auf dem Host?
15 3 * * * /usr/bin/docker compose -f /srv/app/compose.yml run --rm worker python cleanup.py 0 * * * * /usr/bin/docker exec app-db pg_dump -U app app > /srv/backup/app.sql
Einfach und transparent. Nachteil: Zeitplan liegt außerhalb des Projekts; docker braucht vollen Pfad (PATH!) und Rechte.
FROM debian:trixie-slim RUN apt-get update && apt-get install -y cron COPY crontab /etc/cron.d/app RUN chmod 0644 /etc/cron.d/app CMD ["cron", "-f"]
Fallen: Jobs sehen die Container-Umgebung (-e DB_URL=…) NICHT – cron baut seine minimale Umgebung. Ausgaben erscheinen nur in docker logs, wenn man nach /proc/1/fd/1 umleitet. Dateiname in cron.d ohne Punkt!
Werkzeuge wie supercronic lesen eine crontab, laufen im Vordergrund, reichen die Umgebung an Jobs weiter und schreiben die Ausgabe auf stdout – gemacht für Container. Alternativ ein eigener Scheduler-Dienst im Compose-Stack oder – im Cluster – ein Kubernetes CronJob.
🐙GitHub Actions: on.schedule
on:
schedule:
- cron: "30 3 * * 1-5" # Standard: UTC
- cron: "30 5 * * 1-5"
timezone: "Europe/Berlin" # optional, IANA-Name
workflow_dispatch: {} # manuell auslösbar zum TestenBy default, scheduled workflows run in UTC. You can optionally specify a timezone using an IANA timezone string […]. The shortest interval you can run scheduled workflows is once every 5 minutes. The schedule event can be delayed during periods of high loads […]. High load times include the start of every hour. In a public repository, scheduled workflows are automatically disabled when no repository activity has occurred in 60 days. GitHub Actions does not support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly, and @reboot.
📚Scheduler in der Anwendung
import cron from "node-cron";
cron.schedule("30 4 * * 1", backup, {
timezone: "Europe/Berlin",
noOverlap: true,
});
// 6 Felder: "0 30 4 * * 1" (Sekunden vorne)from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.triggers.cron import CronTrigger
s = BlockingScheduler(timezone="Europe/Berlin")
s.add_job(backup, CronTrigger.from_crontab("30 4 * * 1"))
s.add_job(report, "cron", day_of_week="mon-fri", hour=5, minute=30)
s.start()await queue.upsertJobScheduler(
"backup-weekly",
{ pattern: "0 30 4 * * 1", tz: "Europe/Berlin" },
{ name: "backup", data: {} },
);
// Job Scheduler gibt es ab BullMQ 5.16📊Vergleich
| Merkmal | cron | systemd-Timer | Kubernetes CronJob | GitHub Actions | node-cron / APScheduler / BullMQ |
|---|---|---|---|---|---|
| Syntax | 5 Felder, @-Strings | OnCalendar=, Sekunden möglich | 5 Felder, @-Strings | 5 Felder, keine @-Strings | 5 oder 6 Felder (Sekunden) |
| Zeitzone | Systemzeit (cronie: CRON_TZ) | Systemzeit oder Zone im Ausdruck | .spec.timeZone | UTC, optional timezone | Option timezone / tz |
| Rechner war aus | verpasst (→ anacron) | Persistent=true holt nach | startingDeadlineSeconds | läuft in GitHubs Cloud | nur solange der Prozess läuft |
| Überlappung | ja (flock nötig) | nein – läuft der Dienst noch, startet er nicht erneut | concurrencyPolicy | Workflow-concurrency | je nach Bibliothek (noOverlap, max_instances …) |
| Logging | Mail/Umleitung | Journal (journalctl -u) | Pod-Logs, Job-Historie | Actions-Log | Anwendungs-Log |
| Kleinstes Intervall | 1 Minute | Sekunden (AccuracySec) | 1 Minute | 5 Minuten | 1 Sekunde |
| Abhängigkeiten | keine | systemd | Cluster | GitHub-Repo | laufender Prozess, BullMQ: Redis |