I was recently introduced to using Jenkins as a replacement for cron and I've enjoyed it so much, that I thought I'd give it a bit of a write up.

Motivation

This started out for me with a problem where I had a set of tasks that had to be kicked off at a certain time of day. I have multiple servers that act as workers, any of which could run the task, but I definitely only want one of them to do so. Until recently, I would use a boolean host variable in my ansible provsioning scripts called cron_master, to signify that this particular host is considered the cron_master and should have the crontab set up with the desired scheduled tasks. This has worked well and I can't really fault it. However, I've recently been getting in to the whole servers as cattle, not pets thing and that makes this approach less feasible. What happens when we shoot the cron_master? What happens if we spin up a second cron_master by mistake? I'm sure we could go about this in some complicated way with service discovery and leader election, but my friend @stephenmelrose suggested try using jenkins and ansible to fire the jobs.

Scheduling tasks in Jenkins

Jenkins offers a simple way to start jobs on a schedule, which is set up in a very similar way to how you would schedule using crontab.

Under the build triggers section, check Build periodically and enter a schedule. The schedule is much like the cron schedule, but there are a few differences - use jenkins' built in help to learn more.

Scheduling a job with Jenkins

Running one off commands with ansible

I already had jenkins provisioning servers with ansible scripts, so the inventory and key management was all available. Ansible's patterns are very flexible, allowing you to select a slice of hosts by group.

ansible "worker[0]" - inventory/staging.py -a "cowsay dave"

This pattern can be read as the first host that is in the worker group, taken from the staging inventory.

You could take this a step further, by creating playbooks for each task and using the run_once flag, but I think this way is clear enough for me.

We can then just set this up as an Execute Shell build step in the Jenkins job.

Setting up a shell build step

What are the benefits?

Jenkins keeps and makes visible a track history of the success of the tasks.

Jenkins build history

Jenkins keeps and makes available the console output for each task run.

Jenkins console output

Jenkins has a wide array of plugins and options available for triggering notifications when tasks start, fail, succeed, become unstable etc.

Jenkins posting to Slack

Jenkins makes running tasks manually available to less technical users. Each task has a build now button, allowing those who have access to run the tasks whenever they please.

Jenkins Build Now

Other solutions

While not as flexible as using jenkins to drive the tasks, there are systems that offer Dead Man's Switch style behaviour, where the task at hand or the cron command itself phones home to say the task was completed. There are tools specifically made for this, like Dead Man's Snitch or Taylor Otwell's recently released deployment tool Envoyer has a "heartbeat" feature.