Systemd timer-units can be set up to depend on other systemd units. This is great if need to run periodic maintenance tasks inside docker containers that come and go on demand.
Manually editing and maintaining crontab files on hosts with many coming and going services can become cumbersome very fast.
In this scenario, replacing cron-jobs with systemd timer-units has several advantages:
- Jobs can be automatically started and will only run if (all) pre-conditions are met.
- Jobs are automatically shutdown/removed, if services are down or not available.
- Jobs are not running “interleaved”, e.g. a previous job hasn’t finished before a new one is started.
- Jobs log into the journal for easier debugging.
For it to work, two new unit-files are needed:
- A service-unit with the definition of what to do.
- A timer-unit that activates the service-unit based on predefined intervals.
Lets have a look at how you would setup ownCloud “cron-jobs”.
Example of use with ownCloud
OwnCloud is (self-hosted) server software that enables you to access and share files, calendars, contacts or more, similar to e.g. Google Drive.
A system like ownCloud sometimes requires tasks to be done on a regular basis without the need for user interaction or hindering ownCloud performance. For that purpose, as a system administrator, you can define background jobs (for example, database clean-ups) which are executed without any need for user interaction.
In this example, we’re going to setup cron.php
to be run periodically at fixed 15 minute intervals by executing a command inside the container.
As a prerequisite, you should have systemd manage Docker on your machine, and a working ownCloud-instance that you can log into.
Choose the right cron-type
Owncloud has three options for scheduling background maintenance tasks:
First, select option “Cron” in the admin configuration section. 1
While this is not strictly necessary, it will prevent automatic “AJAX” calls from degrading performance, and disallow someone from outside to trigger background tasks just by making a HTTP requests.
Create systemd unit-files
Next, create three files in systemd’s local configuration folder:
touch /etc/systemd/system/owncloud.service /etc/systemd/system/owncloud-cron.{timer,service}
The contents of respective unit-files (copy-n-paste/modify as needed) should look like this:
# /etc/systemd/system/owncloud.service
[Unit]
Description=Owncloud PHP-FPM Docker container
After=docker.service
Requires=docker.service
# This will auto-start the cron timer
Wants=owncloud-cron.timer
[Service]
EnvironmentFile=-/etc/sysconfig/owncloud
# Giving the container a name (owncloud.service in this case) is really helpful later on
ExecStart=/bin/docker run --rm -name %n $OPTIONS macedigital/phpfpm
ExecStop=/bin/docker stop %n
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
The owncloud.service
runs a Docker container with the same name, so it’s easy to identify it later on. Also, whenever the service is started, it will try to activate the owncloud-cron.timer
unit:
# /etc/systemd/system/owncloud-cron.timer
[Unit]
Description=Run owncloud cron.php every 15 minutes
# Auto-shutdown if 'owncloud.service' is not available
Requires=owncloud.service
[Timer]
# Explicitly declare service that this timer is responsible for
Unit=owncloud-cron.service
# Runs 'owncloud-cron' relative to when the *timer-unit* has been activated
OnActiveSec=15min
# Runs 'owncloud-cron' relative to when *service-unit* was last deactivated
OnUnitInactiveSec=15min
[Install]
WantedBy=timers.target
The important parts in the timer-unit are OnActiveSec
, and OnUnitInactiveSec
2. The former setting will activate ‘owncloud-cron.service’ (15min, in this case) after the timer has been activated. The latter setting will schedule ‘owncloud-cron.service’ every 15min after the service has been inactive.
This way, the timer-unit will run ‘owncloud-cron.service’ even after the host machine has been rebooted.
If, and only if, owncloud.service
is running, the timer-unit schedules a owncloud-cron.service
to run every 15 minutes:
# /etc/systemd/system/owncloud-cron.service
[Unit]
Description=Cronjob for owncloud.service
Requires=owncloud.service
After=owncloud.service
[Service]
Type=oneshot
# Execute cron inside the 'owncloud.service' container as user 'apache'
ExecStart=/bin/docker exec -u apache owncloud.service /bin/php /var/www/owncloud/cron.php
The owncloud-cron.service
simply runs cron.php
inside the container. The container can be identified by the name (owncloud.service) we gave it earlier on. For convenience, the service requires owncloud.service
, so it would attempt to start it in case it is not running, yet.
Service dependencies in action
Now that we have the configuration in place, reload the systemctl daemon, so it picks up the changes we have made, and start owncloud.service
.
systemctl daemon-reload && systemctl start owncloud
Use the systemctl list-timers
command line tool to verify the timer is running now as well:
As you can see, the timer-unit was activated automatically, without any need to manually do so. You can see exactly when a job was executed last, and when it will be called next time.
Whenever you shutdown owncloud.service
, it’s dependent timer-unit(s) will be deactivated, too. Active cron-tasks will always (attempt to) complete, before owncloud.service
is shut down.
Systemd’s built-in dependency management ensures cron-jobs only get scheduled, if the services they rely on are actually active and running. Only one “cron-job” will be running at any given point in time.
Maybe someone has a better idea of implementation, but right now I’m quite happy with the working solution timer-units offer.