1.2.1
Immutable
release. Only release title and notes can be modified.
This is the largest yacron2 release since the fork. Its headline feature is
clustering: several replicas can now verify they hold the same job set, elect
a leader so each scheduled job runs on one node instead of every node, spread
jobs across the fleet, and fail over when a node dies, coordinating either
peer-to-peer over mutual TLS or through a Kubernetes or etcd lease. The
release also adds native Prometheus metrics, accepts classic crontab files as
configuration, and grows the web dashboard into a small operations console.
Clustering is entirely opt-in; see the upgrade notes below for the few
behavior changes that apply to existing deployments.
Clustering and leader election
- New optional top-level
cluster:section. Give every replica the same
static peer list and a dedicated mutual-TLS listener (cluster.listen,
cluster.tls.{ca,cert,key},cluster.peers), and each node polls every
peer once per round (cluster.interval, default 30 seconds), comparing
job-set ids (see 1.1.8) to attest that all replicas hold an identical job
set. Peers are reported asagreed,syncing,drifted(a mismatch that
persists forcluster.driftAfterconsecutive rounds, default 3),
unreachable,untrusted(TLS verification failed), orconflict. On its
own this is observe-only (every node still runs every job), which makes it
a safe first rollout step. - Leader election (
cluster.electLeader: true). The leader is the lowest
cluster.nodeName(default: the hostname) among the agreeing nodes this
node can see, and only when they form a strict majority of the declared
cluster size; below quorum a node stands down and runs nothing. Two
disjoint majorities cannot exist, so a clean partition produces at most one
leader within about one polling interval. Conflicts fail closed: a
duplicatenodeName, a cluster-size disagreement (say, a rolling resize
from 3 to 5 nodes), or a coordination-policy divergence parksLeaderjobs
until the fleet reconverges, and each is logged loudly and shown on the
dashboard. A freshly started or reconfigured node holds its jobs until it
has polled every configured peer at least once, so a blank-view node cannot
elect itself. Two-node election is refused at config load (it is strictly
worse than a single replica); even cluster sizes log a warning. - A per-job
clusterPolicy(also settable underdefaults:) decides
what election means for each job:Leader(the default: only the elected
leader runs it, and it is skipped when in doubt),PreferLeader(never
skip: the lowest reachable agreeing node runs it even without quorum,
accepting a rare double-run, so reserve it for idempotent jobs), and
EveryNode(all nodes run it, for node-local housekeeping). Manual runs
viaPOST /jobs/{name}/startare never gated. Automatic retries re-check
the gate before every relaunch and abandon a pending retry when ownership
has demonstrably moved to another node;@rebootjobs under election are
deferred until the cluster converges and then run once, on the owner. - Load-balanced jobs:
cluster.distribution: spread. Instead of one
leader running everyLeader/PreferLeaderjob, each such job is
assigned an owning node by rendezvous (highest-random-weight) hashing over
the quorate agreeing members, spreading work across the fleet. A
membership change moves only the departing or joining node's share of
jobs. The same quorum gating applies, andGET /jobsreports each job's
currentclusterOwner. - Mutual TLS is the entire trust boundary. A peer must present a
certificate chaining to the configured CA and matching the host it was
reached at, so the CA should be dedicated to yacron2 nodes and nothing
else. Certificates rotated in place are detected and reloaded without a
restart. The peer endpoint is served only on the cluster listener, never
on the web API listener. - The failure semantics are documented, not hand-waved. The built-in
backend is best-effort by design: there are narrow, self-healing windows
where aLeaderjob can be skipped or aPreferLeaderjob can
double-run, and the new wiki page enumerates them. When you need fenced
exactly-once execution, use one of the lease backends below. yacron2 --validate-configvalidates the entireclustersection (peer
list, TLS material, lease timing invariants) without starting anything.
Kubernetes and etcd lease backends
cluster.backend: gossip | kubernetes | etcdpicks the coordination
mechanism (defaultgossip, the peer-to-peer mode above). The lease
backends replace the peer quorum with a fenced, expiring lease in an
external store: exactly one node holds the lease at a time, soLeader
jobs are exactly-once while the store is reachable. With a lease backend,
election is always on, there is no peer list or cluster mTLS to manage,
anddistribution: spreadis rejected at config load (a single lease
cannot express per-job ownership).- Kubernetes (
cluster.kubernetes.*): replicas campaign for a
coordination.k8s.io/v1Lease object using the client-go leader-election
algorithm (leaseNamedefaults toyacron2-leader;
leaseDurationSeconds/renewDeadlineSeconds/retryPeriodSecondsdefault
to 15/10/2, with the client-go timing invariants enforced at config load).
In-cluster ServiceAccount token, CA, namespace, and API server are
detected automatically; akubeconfigand an explicitapiServer(https
only) are supported. The backend talks to the API server over the bundled
aiohttp by default; installing the new optional extra
(pip install yacron2[kubernetes]) switches to the official Kubernetes
client (clientLibrary: auto|http|library). The stored holder identity
embeds a per-process token, so two pods that accidentally share a name
cannot both hold the lease.example/kubernetes/ships a ready-to-apply
Deployment with the minimal ServiceAccount, Role, and RoleBinding. - etcd (
cluster.etcd.*): replicas campaign for a lease-bound key
(electionNamedefaults toyacron2/leader) through etcd's v3 JSON/HTTP
gRPC gateway, using a create-if-absent transaction fenced by the lease id
(ttldefaults to 15 seconds, minimum 3). Multipleendpointsfail over
in order; optionalusername/password(literal,fromFile, or
fromEnvVar) and client TLS are supported, and credentials are refused
unless every endpoint ishttps://.example/etcd/ships a compose demo
with an etcd and two yacron2 replicas. - Failover is fast and clock-safe on both. All fencing runs on the
monotonic clock with a one-second skew margin (no wall-clock or cross-node
clock-sync assumptions): a holder whose renewals stall demotes itself
locally before the store-side lease can expire, without a network call. A
graceful shutdown releases the lease explicitly (Kubernetes clears
holderIdentity, etcd revokes its lease) so a survivor takes over
immediately rather than waiting out the TTL. If the store becomes
unreachable,Leaderjobs fail closed andPreferLeaderjobs keep
running.@rebootbookkeeping is persisted in the store, scoped to the
job-set id, so aLeader@rebootjob runs once per job configuration
rather than once per fleet restart. - No new runtime dependencies. Both lease backends are plain HTTP over
the existing aiohttp core (no etcd or gRPC client; the Kubernetes client
is optional), so all packaged architectures keep working.
Prometheus metrics
- Native Prometheus metrics at
GET /metrics, served by the existing
web listener whenever the web API is enabled; there is no exporter
sidecar and no new dependency (the exposition is generated in-process).
Both the classic text format and OpenMetrics 1.0 are served, negotiated
via theAcceptheader. - Per-job series cover run outcomes (
yacron2_job_runs_totallabeled by
job_nameandstatus), retries, permanent failures, failures to start,
a duration histogram with configurable buckets
(web.metrics.durationBuckets), last success/failure/run timestamps and
exit code, and live state (enabled, running, next run). Daemon series
cover the version, start time, job-set id, job counts, and config-reload
health. Cluster series cover size, quorum, leadership, per-peer status
counts, conflicts, and leader/quorum transition counters; the wiki
suggests alerting onsum(yacron2_cluster_is_leader) > 1and on losing
yacron2_cluster_quorate. Metrics are recorded at the same point as the
run history, so/metricsand/jobs/{name}/runsnever disagree. web.authToken, when configured, protects/metricslike every other
endpoint;web.metrics.public: trueexempts just this endpoint for a
scraper, andweb.metrics: falseremoves it entirely.
Classic crontab files
- Classic (Vixie) crontabs are now accepted as configuration. A file
with a.crontabor.cronextension, or named exactlycrontab, is
parsed as a crontab wherever configuration is loaded: passed to-c,
dropped into a config directory alongside*.yamlfiles, or pulled in
viainclude:; a neutral-named file given to-corinclude:is
content-sniffed. Supported syntax: five-field entries (the same field
dialect as YAMLschedulestrings),@keywords(including@rebootand
@midnight), position-sensitiveVAR=valueenvironment lines, comments,
and the\%escape.SHELLandCRON_TZassignments are honored as the
job'sshellandtimezone. - Each entry becomes a normal yacron2 job named
<file>:<line>,
indistinguishable downstream: it shows up on the dashboard and HTTP API,
participates in the job-set id and clustering, and can be run or
cancelled on demand. yacron2's defaults apply rather than cron's (UTC
unlessCRON_TZsays otherwise; any stderr output marks the run as
failed). Deviations are deliberate and loud: an unescaped%is a
load-time error instead of silently not feeding stdin,MAILTOis
exported to the job but sends no mail, and the six-column system-crontab
user field is not supported. Per-entry knobs (retries, reporting,
timeouts) still require migrating that line to YAML; the new wiki page
shows how.
Web dashboard
- A cluster operations view. A cluster panel shows this node's role
(leader, follower, or standing down and why), quorum state, and a
per-peer table with status dots and a per-peer history timeline; lease
backends show the lease holder and expiry instead. A page-level alert bar
calls out a conflict or lost quorum without scrolling, and a fleet view
(backed by the newGET /fleet) renders a jobs-by-nodes matrix of every
node's last outcome per job, with a failing-only filter. - Incident tooling. A verdict bar summarizes active failures and, when
several jobs fail together, says whether they look like one cause or
independent ones; an incident timeline (pressi) orders every job's
most recent run; a mitigate console bulk-starts or bulk-cancels jobs and
copies a Markdown incident summary; and a merged multi-tail console
streams up to four jobs' logs into one color-coded view. - Wallboard and zen mode. A full-screen TV mode (press
w, or open
#tv) shows worst-first job tiles with a staleness watchdog that shows
NO SIGNALrather than a false all-green; when everything is healthy and
idle for a while it drifts into a zen screensaver in which every job
pulses at its real next fire time. - More ways to read the schedule. An activity heatmap (a 6h/24h/7d
punchcard per job), a cron sandbox that validates a cron expression and
previews its next 12 fire times alongside live jobs sharing it, a column
picker adding Owner/Policy/TZ/Next-at/Rate columns, and an opt-in,
browser-local run ledger (IndexedDB) that keeps run history beyond the
daemon's in-memory cap and flags unusually slow runs against each job's
own duration baseline. - Finishing touches. A fourth theme,
carolina(a Carolina-Blue CRT
phosphor), joins amber/green/modern; optional audible cues with a volume
setting and an escalating failure alarm (pressato acknowledge); and
an optional boot self-test splash on load.
HTTP API
- New endpoints:
GET /cluster(this node's cluster view: backend, quorum,
leader, conflicts, per-peer detail) andGET /fleet(fleet-wide per-job
run summaries carried on the gossip round; observability only), plus
GET /metricsabove.GET /jobsgainsclusterPolicyand, under
distribution: spread, each job'sclusterOwner. The HTTP API wiki page
now documents every endpoint with full response shapes. - Configured
web.headersare now applied to every successful response,
including the new endpoints, and to the409 Conflictbodies of
start/cancel. - A web-section configuration error found during a reload now leaves the
web API down with a clear log line while the rest of the reload (jobs,
cluster, logging) still applies.
Reliability
- A failed job launch can no longer crash the scheduler. Spawning a job
now guards against anyOSError(file-descriptor exhaustion, fork or
memory limits, permission errors), not just a missing executable: the run
is recorded as an ordinary start failure with exit code 127 (and counted
inyacron2_job_start_failures_total) instead of the error propagating
out of the scheduling loop and taking the daemon down.
Upgrade notes
/metricsis served by default wherever the web API is enabled. It sits
behindweb.authTokenlike every other endpoint when a token is
configured; setweb.metrics: falseto remove it.- The job-set id changes once on upgrade:
clusterPolicyis now part of
every job's fingerprint, so an unchanged configuration hashes to a
differentv1:id than 1.1.8 through 1.1.11 reported. Compare ids only
between nodes running the same yacron2 version; a mixed-version fleet
reads as drift until the rollout completes. - Config directories now load crontab-named files (
*.crontab,*.cron,
crontab) that earlier releases silently ignored.
Packaging
- New
yacron2.backendssubpackage, and a new optional extra:
pip install yacron2[kubernetes]installs the official Kubernetes client
forclientLibrary: library. A core install gains no new runtime
dependency and the supported Python range is unchanged. The PyPI metadata
gains prometheus/metrics/monitoring keywords and an updated description.
Documentation and examples
- Three new wiki pages: Clustering and Leader Election (a full operations
guide: both backend families, every failure window, sizing, rollout, and
troubleshooting), Metrics with Prometheus, and Classic Crontabs, plus
major updates to the HTTP API, Web Dashboard, Production Deployment,
Architecture and Internals, and Troubleshooting pages. The README gains
matching sections. - New runnable demos:
docker-compose-cluster.yml(a three-node mutual-TLS
cluster with scripted try-it failover scenarios),
docker-compose-cluster-large.yml(ten nodes withdistribution: spread
and CPU-heavy jobs, for watching work fan out),docker-compose-acme.yml
(a five-node simulated data platform with a mail sink, a statsd exporter,
and deterministic scripted incidents that exercise the dashboard's
incident tooling),docker-compose-zen.yml(one calm node for the zen
screensaver),example/etcd/andexample/kubernetes/for the lease
backends, andexample/crontab/for classic crontabs.
Internal
- The test suite roughly doubles, with new suites for the cluster manager,
both lease backends, the leadership abstraction, the Prometheus
registry/exposition, crontab parsing, and backend config validation, plus
large additions to the scheduler tests. Thedevextra adds
cryptographyfor the cluster mTLS tests (skipped on Windows ARM64,
which has no wheel). A new.gitattributespins*.shto LF line
endings so the bind-mounted cluster demos work from a Windows clone.
Full Changelog: 1.1.11...1.2.1