Chapter 10: Automating Linux Administration Tasks

Managing the boot process and scheduled processes

Some work runs on a timetable: nightly backups, log rotation, a report every Monday. Some needs a reboot, or a different boot target. Ansible manages cron and at jobs, services, the default target and reboots, and waits for a rebooted host to come back before it carries on.

Recurring jobs: cron

ansible.builtin.cron manages entries in a user's crontab. Build a schedule and see both the task and the line it writes:

- name: Uptime is logged every five minutes
  ansible.builtin.cron:
    name: Log uptime
    user: devops
    minute: "*/5"
    job: uptime >> /home/devops/uptime.log
    state: present

The name is written into the crontab as a comment, #Ansible: Log uptime, and it is how the module recognises its entry on later runs. Change the name and you get a second job; keep it and change the schedule, and the existing line is updated. To remove a job, use the same name with state: absent.

ArgumentMeaning
minute, hour, day, month, weekdayThe schedule; each defaults to *
special_timereboot, daily, weekly and so on, instead of the five fields
userWhose crontab
cron_fileWrite to a file in /etc/cron.d/ instead of a user's crontab

A system cron job lives in a file of its own in /etc/cron.d/, and its lines name the user the job runs as. With cron_file, user sets that field instead of choosing a crontab:

- name: Flush the application cache every day
  ansible.builtin.cron:
    name: Flush Bolt cache
    cron_file: flush_bolt
    user: root
    minute: "45"
    hour: "11"
    job: php ./app/nut cache:clear

Never point cron_file at /etc/crontab: the module manages whole files, and that one belongs to the system.

One-off jobs: at

ansible.posix.at schedules a command to run once, after a delay. The atd service must be installed and running:

- name: at is installed
  ansible.builtin.dnf:
    name: at
    state: present

- name: atd is running
  ansible.builtin.service:
    name: atd
    state: started
    enabled: true

- name: A one-off job runs in 20 minutes
  ansible.posix.at:
    command: date > /tmp/at-was-here
    count: 20
    units: minutes
    unique: true

unique: true stops a second run of the playbook from queueing the same job again.

The lab's Rocky Linux image does not include at, so install it first, as above. On a standard RHEL server it is usually already there.

Services

ansible.builtin.service (or ansible.builtin.systemd, for systemd-specific options) controls services:

- name: httpd is running and starts at boot
  ansible.builtin.service:
    name: httpd
    state: started
    enabled: true
stateResult
started / stoppedRunning or not; nothing happens if it already is
restartedAlways restarted: use in a handler, not a normal task
reloadedTold to reread its configuration

ansible.builtin.systemd adds daemon_reload: true, needed after you install or change a unit file, and masked.

service works with whatever init system the host uses; systemd only with systemd, but it can also manage timers, the systemd alternative to cron:

- name: The dnf cache is no longer refreshed on a timer
  ansible.builtin.systemd:
    name: dnf-makecache.timer
    state: stopped
    enabled: false

The default boot target

The default target decides what a host boots into: multi-user.target for a server console, graphical.target for a desktop. It is a symbolic link, so the file module sets it idempotently:

- name: The system boots to the text console
  ansible.builtin.file:
    src: /usr/lib/systemd/system/multi-user.target
    dest: /etc/systemd/system/default.target
    state: link

The systemd module cannot set the default target. Another way, closer to what you would type by hand, runs systemctl and uses a condition to stay idempotent:

- name: Get current boot target
  ansible.builtin.command:
    cmd: systemctl get-default
  changed_when: false
  register: target

- name: Set default boot target
  ansible.builtin.command:
    cmd: systemctl set-default {{ default_target }}
  when: default_target not in target['stdout']

A change of target takes effect at the next boot, which leads to reboots.

Rebooting from a playbook

ansible.builtin.reboot reboots the host, waits for it to come back and answer again, and then continues with the next task:

- name: The system boots to the text console
  ansible.builtin.file:
    src: /usr/lib/systemd/system/multi-user.target
    dest: /etc/systemd/system/default.target
    state: link
  notify: reboot

handlers:
  - name: reboot
    ansible.builtin.reboot:
      reboot_timeout: 300

As a handler it reboots only when something actually changed, and only once however many tasks notified it.

Takeaway

Schedule recurring jobs with ansible.builtin.cron and one-off jobs with ansible.posix.at. Use the chapter lab to check this on a real host.