{"id":11620,"date":"2025-08-17T15:30:10","date_gmt":"2025-08-17T15:30:10","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=11620"},"modified":"2025-08-17T15:30:10","modified_gmt":"2025-08-17T15:30:10","slug":"kubernetes-cronjobs","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/kubernetes-cronjobs\/","title":{"rendered":"Kubernetes cronjobs"},"content":{"rendered":"<p>You know the scenario, you&#8217;re wanting to run jobs either at certain points in a day or throughout the data every N timespans (i.e. every 5 mins). <\/p>\n<p>Kubernetes has you covered, there&#8217;s a specific &#8220;kind&#8221; of job for this, as you guessed from the title, the <em>CronJob<\/em>.<\/p>\n<p><strong>An example app.<\/strong><\/p>\n<p>Let&#8217;s assume you created yourself a job &#8211; I&#8217;m going to create a simple job that just outputs the date\/time at the scheduled time. I&#8217;ve written this in Rust but to be honest it&#8217;s simple enough that this could be any language. Here&#8217;s the Cargo.toml<\/p>\n<p>The application is just a standard console application named crj (for cronjob or cron rust job, I really didn&#8217;t think about it :)).<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;package]\r\nname = &quot;crj&quot;\r\nversion = &quot;0.1.0&quot;\r\nedition = &quot;2024&quot;\r\n\r\n&#x5B;dependencies]\r\nchrono = &quot;0.4&quot;\r\n<\/pre>\n<p>Here&#8217;s the code<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nuse chrono::Local;\r\n\r\nfn main() {\r\n    let now = Local::now();\r\n    println!(&quot;Current date and time: {}&quot;, now);\r\n}\r\n<\/pre>\n<p>See I told you it was simple. <\/p>\n<p><strong>Docker<\/strong><\/p>\n<p>For completeness, here&#8217;s the Dockerfile and the steps to get things built, tagged and pushed<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nFROM rust:1.89.0-slim AS builder\r\n\r\nWORKDIR \/app\r\nCOPY . .\r\n\r\nRUN cargo build --release\r\n\r\nFROM debian:bookworm-slim\r\n\r\nRUN apt-get update &amp;&amp; apt-get install -y ca-certificates &amp;&amp; \\\r\n    rm -rf \/var\/lib\/apt\/lists\/*\r\n\r\nCOPY --from=builder \/app\/target\/release \/usr\/local\/bin\/crj\r\n\r\nRUN chmod +x \/usr\/local\/bin\/crj\r\n\r\nENTRYPOINT &#x5B;&quot;\/usr\/local\/bin\/crj\/crj&quot;]\r\n<\/pre>\n<p>Next up we need to build the image using (remember to use the image you created as well as the correct name for your container registry)<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndocker build -t putridparrot\/crj:1.0.0 .\r\n<\/pre>\n<p>then tag it using<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndocker tag putridparrot\/crj:1.0.0 putridparrotreg\/putridparrot\/crj:1.0.0\r\n<\/pre>\n<p>Finally we&#8217;ll push it to our container registry using<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndocker push putridparrotreg\/putridparrot\/crj:1.0.0\r\n<\/pre>\n<p><strong>Kubernetes CronJob<\/strong><\/p>\n<p>All pretty standard stuff and to be honest the next bit is simple enough. We need to create a kubernetes yaml file (or helm charts). Here&#8217;s my cronjob.yaml<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\napiVersion: batch\/v1\r\nkind: CronJob\r\nmetadata:\r\n  name: scheduled-job\r\n  namespace: dev\r\nspec:\r\n  schedule: &quot;*\/5 * * * *&quot; # every 5 minutes\r\n  jobTemplate:\r\n    spec:\r\n      template:\r\n        spec:\r\n          containers:\r\n            - name: scheduled-job\r\n              image:  putridparrotreg\/putridparrot\/crj:1.0.0\r\n          restartPolicy: Never\r\n<\/pre>\n<p>My cronjob has the name <em>scheduled-job<\/em> (I know, not very imaginative). We apply this file to Kubernetes as usual i.e.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nkubectl apply -f .\\cronjob.yaml\r\n<\/pre>\n<p><strong>Did it work?<\/strong><\/p>\n<p>We&#8217;ll ofcourse want to take a look at what happened after this CronJob was set up in Kubernetes. We can simply use the following. You can set the namespace used, such as dev in my case.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nkubectl get cronjobs --all-namespaces -w\r\n<\/pre>\n<p>you&#8217;ll see something like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nNAMESPACE   NAME            SCHEDULE      TIMEZONE   SUSPEND   ACTIVE   LAST SCHEDULE   AGE\r\ndev         scheduled-job   *\/5 * * * *   &lt;none&gt;     False     0        &lt;none&gt;          9s\r\ndev         scheduled-job   *\/5 * * * *   &lt;none&gt;     False     1        0s              16s\r\ndev         scheduled-job   *\/5 * * * *   &lt;none&gt;     False     0        13s             29s\r\ndev         scheduled-job   *\/5 * * * *   &lt;none&gt;     False     1        0s              5m16s\r\n<\/pre>\n<p>In my case the job starts (ACTIVE) and then completes and shuts down. Then 5 minutes later it starts again as expected with this cron schedule.<\/p>\n<p>On the pods side you can run <\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nkubectl get pods -n dev -w\r\n<\/pre>\n<p>Now what you&#8217;ll see is something like this<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nNAME                           READY   STATUS              RESTARTS   AGE\r\nscheduled-job-29257380-5w4rg   0\/1     Completed           0          51s\r\nscheduled-job-29257385-qgml2   0\/1     Pending             0          0s\r\nscheduled-job-29257385-qgml2   0\/1     Pending             0          0s\r\nscheduled-job-29257385-qgml2   0\/1     ContainerCreating   0          0s\r\nscheduled-job-29257385-qgml2   1\/1     Running             0          2s\r\nscheduled-job-29257385-qgml2   0\/1     Completed           0          3s\r\nscheduled-job-29257385-qgml2   0\/1     Completed           0          5s\r\nscheduled-job-29257385-qgml2   0\/1     Completed           0          5s\r\nscheduled-job-29257390-2x98r   0\/1     Pending             0          0s\r\nscheduled-job-29257390-2x98r   0\/1     Pending             0          0s\r\nscheduled-job-29257390-2x98r   0\/1     ContainerCreating   0          0s\r\nscheduled-job-29257390-2x98r   1\/1     Running             0          2s\r\n<\/pre>\n<p>Notice that the pod is created and goes into a &#8220;Pending&#8221; state. Then &#8220;ContainerCreating&#8221; before &#8220;Running&#8221; and finally &#8220;Completed&#8221;, but the next run of the cronjob creates a new pod name. Therefore, if you&#8217;re trying to log the pods i.e. kubectl logs scheduled-job-29257380-5w4rg -n dev &#8211; then you&#8217;ll get something like the below, but you cannot -f (follow) the logs as the next time the job runs it creates a new pod.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nCurrent date and time: 2025-08-17 15:00:09.294317303 +00:00\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>You know the scenario, you&#8217;re wanting to run jobs either at certain points in a day or throughout the data every N timespans (i.e. every 5 mins). Kubernetes has you covered, there&#8217;s a specific &#8220;kind&#8221; of job for this, as you guessed from the title, the CronJob. An example app. Let&#8217;s assume you created yourself [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[314,191],"tags":[],"class_list":["post-11620","post","type-post","status-publish","format-standard","hentry","category-kubernetes","category-rust"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11620","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=11620"}],"version-history":[{"count":3,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11620\/revisions"}],"predecessor-version":[{"id":11624,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/11620\/revisions\/11624"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=11620"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=11620"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=11620"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}