<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Schedule · XB Field Notes</title><link>https://hk.xbcatm.com/tags/schedule/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://hk.xbcatm.com/tags/schedule/index.xml" rel="self" type="application/rss+xml"/><item><title>A CronJob turns a schedule into Jobs</title><link>https://hk.xbcatm.com/posts/k8s-cronjob-schedules-batch-work/</link><pubDate>Fri, 17 Dec 2027 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/posts/k8s-cronjob-schedules-batch-work/</guid><description>CronJobs create Jobs on an approximate schedule; concurrency, missed-start deadlines and application idempotency determine useful batch execution.</description><content:encoded>&lt;p&gt;A CronJob combines a Job template with a recurring schedule. The CronJob controller creates Jobs; each Job manages the Pods that perform the work. A run can involve retries, replacement Pods, or parallel workers, depending on the Job specification. There need not be a dedicated application process waiting between runs, although completed Jobs and Pods may remain for inspection.&lt;/p&gt;
&lt;p&gt;Scheduling is approximate. Duplicate Jobs and missed occurrences are both possible, so do not promise unconditional at-least-once delivery for every scheduled time. Make repeated execution safe and record which business periods actually completed. Idempotency prevents duplicate effects; a separate completeness check detects work that never ran.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-k8s-cronjob-schedules-batch-work.detail.en.svg" alt="A schedule leads to a Job and its Pods. Allow permits overlap, Forbid skips a start while a Job is active, and Replace replaces the active Job, all within one CronJob. Approximate scheduling needs safe repetition and a separate check for completed business periods." width="560" height="2540"&gt;&lt;figcaption&gt;Scheduling is approximate, not a durable queue or a promise that every tick completes. startingDeadlineSeconds limits start lateness, not run duration. Source commands were not rerun; example execution remains blocked.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="a-schedule-makes-a-job"&gt;A schedule makes a Job&lt;/h2&gt;
&lt;p&gt;The five schedule fields describe minute, hour, day of month, month, and day of week. Ranges, steps, and macros such as &lt;code&gt;@daily&lt;/code&gt; are supported. &lt;code&gt;*/10 * * * *&lt;/code&gt; requests ten-minute intervals; it does not guarantee that a Pod starts precisely on those boundaries. Resource shortages and image pulls can delay execution after Job creation.&lt;/p&gt;
&lt;p&gt;Set &lt;code&gt;spec.timeZone&lt;/code&gt; explicitly when wall-clock interpretation matters. Without it, the controller manager&amp;rsquo;s local time zone applies; moving machines only changes behavior if that configuration changes. Use the timeZone field rather than putting TZ or CRON_TZ inside the schedule. Daylight-saving transitions and application reporting periods still need a deliberate policy.&lt;/p&gt;
&lt;p&gt;Keep the CronJob name within 52 characters because generated Job names need an appended suffix. Since Kubernetes 1.32, generated Jobs carry &lt;code&gt;batch.kubernetes.io/cronjob-scheduled-timestamp&lt;/code&gt;. Compare that intended time with creation and start timestamps to investigate delays. The annotation helps diagnosis; it is not the sole possible audit trail or evidence that the business operation finished.&lt;/p&gt;
&lt;h2 id="concurrency-is-scoped-to-one-cronjob"&gt;Concurrency is scoped to one CronJob&lt;/h2&gt;
&lt;p&gt;The default &lt;code&gt;Allow&lt;/code&gt; policy permits overlapping Jobs. &lt;code&gt;Forbid&lt;/code&gt; declines a new start while an earlier Job remains active; the skipped time counts as missed, and a later eligible start still depends on its deadline. &lt;code&gt;Replace&lt;/code&gt; requests deletion before creating a replacement; asynchronous deletion does not prove the old Pods have stopped. These policies do not coordinate separate CronJobs or external workers touching the same data.&lt;/p&gt;
&lt;p&gt;A task consistently longer than its interval can fail to complete under Replace. Forbid trades overlap avoidance for missed starts, rather than providing a durable queue. Use application-level deduplication or coordination where overlapping effects would be harmful, including interruption during replacement. A lock alone does not prove that a partially committed operation can safely resume.&lt;/p&gt;
&lt;h2 id="deadlines-suspension-and-history"&gt;Deadlines, suspension and history&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;startingDeadlineSeconds&lt;/code&gt; limits the lateness of a start, not execution duration. Without it, occurrences have no individual starting deadline. The concept guide describes refusing catch-up beyond 100 missed schedules. However, the reviewed 1.34.0 scheduling helper emits a warning and returns the most recent eligible time; it does not reject solely for that count. Verify the server version instead of inferring a permanent stop from the warning. A configured deadline limits the counting window, and values below ten seconds can miss controller checks.&lt;/p&gt;
&lt;p&gt;Suspension stops new Jobs without stopping existing ones. Suspended schedule times count as missed, so resuming can trigger immediate catch-up subject to the other rules. Do not treat that as a promise to replay one Job for every missed tick. Keep a durable work ledger if every reporting period must be processed.&lt;/p&gt;
&lt;p&gt;History limits retain completed Job objects, with defaults of three successes and one failure. They do not cap concurrent running Jobs or replace execution deadlines. Updating the template affects future Jobs; already-started Jobs retain their own specifications.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;A twenty-five-minute task on a ten-minute schedule can overlap under Allow and duplicate writes. Inspect actual ownership, active Pods, and completion records before attributing duplicate business effects to a schedule. A missed-start deadline should reflect how late the result remains useful, rather than an arbitrary attempt to hide controller warnings.&lt;/p&gt;
&lt;p&gt;A long gap in lastScheduleTime can also be intentional: suspension, Forbid, a sparse calendar expression, or a changed time zone may explain it. Events expire, and successful Job creation does not establish successful backups. Alert on the last verified usable result as well as scheduling health.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;In the intended read-only context and namespace, inspect an existing &lt;code&gt;nightly-report&lt;/code&gt;. These commands were retained from the earlier article and were not rerun against a cluster in this review.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get cronjob nightly-report -o jsonpath=&amp;#39;{.spec.schedule} {.spec.concurrencyPolicy} {.spec.startingDeadlineSeconds}{&amp;#34;\n&amp;#34;}&amp;#39;
kubectl get cronjob nightly-report -o jsonpath=&amp;#39;{.status.lastScheduleTime}{&amp;#34;\n&amp;#34;}&amp;#39;
kubectl get jobs --sort-by=.metadata.creationTimestamp -o custom-columns=NAME:.metadata.name,START:.status.startTime,SUCCEEDED:.status.succeeded
kubectl get events --field-selector involvedObject.kind=CronJob --sort-by=.lastTimestamp | tail -5
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The Jobs listing includes unrelated Jobs in the namespace; correlate owner references before drawing conclusions. START timestamps alone cannot prove overlap, and an empty SUCCEEDED field does not prove a Job is still running. Check active/failed counts and conditions alongside each scheduled timestamp. Interpret gaps against the actual calendar and policy, and use controller logs when retained Events lack the explanation.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/"&gt;Kubernetes CronJob&lt;/a&gt;, schedule syntax, concurrency policy, the 100-missed-schedule rule and history limits, checked 2026-10-15.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/reference/kubernetes-api/batch/cron-job-v1/"&gt;CronJob v1 API reference&lt;/a&gt;, field-level definition of the CronJob spec, checked 2026-10-15.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/kubernetes/kubernetes/v1.34.0/pkg/controller/cronjob/utils.go"&gt;Kubernetes 1.34.0 CronJob scheduling helper&lt;/a&gt;, mostRecentScheduleTime and nextScheduleTime select the latest missed time and warn above 100, checked 2026-10-15.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/kubernetes/kubernetes/v1.34.0/pkg/controller/cronjob/injection.go"&gt;CronJob job control (v1.34.0)&lt;/a&gt;, DeleteJob uses background propagation; replacement is not a Pod-termination barrier, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>