Chapter 10: Automating Linux Administration Tasks

Exercise: Managing the boot process and scheduled processes

You will schedule a recurring job with cron and remove it again, schedule a one-off job with at, change the default boot target, and reboot a server from a playbook, checking each result on the host.

The project ~/system-process has an ansible.cfg and an inventory with servera in webservers. Every playbook in this exercise has one play for webservers, with privilege escalation.

Managing the boot process and scheduled processes

  • Schedule cron and at jobs.
  • Set the default boot target, and reboot hosts from a playbook.

Prerequisites: Complete control-node setup and the preceding lessons in this chapter. Run commands as student on workstation unless a step names another machine.

From the project directory, run the relevant home-lab check. For an exercise with several checkpoints, grade each state before changing or removing it:

lab grade system-process
lab grade system-process --checkpoint scheduled

The grader reads project files and host state. A passing report covers the listed checks; also perform the task’s independent connection, repeat-run, and reboot checks where requested.

Change the cron interval to five minutes. Inspect the resulting schedule without waiting for it.

Record the published result first. The grader checks the original requirements, so a changed name or value may intentionally fail those checks.

Demonstrate recurring work, one-off work, and a persistent boot-target change on the lab hosts.

  • A devops cron job must append the time to /home/devops/my_date_time_cron_job every two minutes during hours 09–16 on weekdays, from /etc/cron.d/add-date-time; later remove it.
  • Queue a single devops job that writes the time to ~/my_at_date_time one minute later, without duplicate queued jobs.
  • Change the default boot target only when necessary, reboot, and prove the target persisted. Restore the original target at the end.
  • Retain the named scheduling and target playbooks and capture each intermediate state before cleanup.

A recurring cron job

Create create_crontab_file.yml. Every two minutes from 09:00 to 16:59, Monday to Friday, the job appends the date and time to /home/devops/my_date_time_cron_job. It runs as devops, from the system cron file /etc/cron.d/add-date-time.

---
- name: Recurring cron job
  hosts: webservers
  become: true

  tasks:
    - name: Crontab file exists
      ansible.builtin.cron:
        name: Add date and time to a file
        job: date >> /home/devops/my_date_time_cron_job
        minute: "*/2"
        hour: 9-16
        weekday: 1-5
        user: devops
        cron_file: add-date-time
        state: present
[student@workstation system-process]$ ansible-navigator run -m stdout create_crontab_file.yml
...output omitted...
TASK [Crontab file exists] *****************************************************
changed: [servera.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0  ...
[student@workstation system-process]$ ssh devops@servera "cat /etc/cron.d/add-date-time"
#Ansible: Add date and time to a file
*/2 9-16 * * 1-5 devops date >> /home/devops/my_date_time_cron_job

The name became the #Ansible: comment, and, because this is a system cron file, the user is written on the line itself.

Remove it

Create remove_cron_job.yml that removes the same job. It must name the same job, user and file, with state: absent.

---
- name: Remove scheduled cron job
  hosts: webservers
  become: true

  tasks:
    - name: Cron job removed
      ansible.builtin.cron:
        name: Add date and time to a file
        user: devops
        cron_file: add-date-time
        state: absent
[student@workstation system-process]$ ansible-navigator run -m stdout remove_cron_job.yml
...output omitted...
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0  ...
[student@workstation system-process]$ ssh devops@servera "ls -l /etc/cron.d"
total 8
-rw-r--r--. 1 root root 128 ... 0hourly
...output omitted...

The module deleted add-date-time when its last job was gone.

A one-off job with at

Create schedule_at_task.yml. As devops, one minute from now, it writes the date into ~/my_at_date_time. unique stops a second run from queuing the same job again.

---
- name: Schedule at task
  hosts: webservers
  become: true
  become_user: devops

  tasks:
    - name: Create date and time file
      ansible.posix.at:
        command: date > ~/my_at_date_time
        count: 1
        units: minutes
        unique: true
        state: present

The home-lab servers do not have the at package, and the task fails with Failed to find required executable "at". Install it and start its service first, with two ad hoc commands:

[student@workstation system-process]$ ansible webservers -m ansible.builtin.dnf -a 'name=at state=present'
[student@workstation system-process]$ ansible webservers -m ansible.builtin.service \
> -a 'name=atd state=started enabled=true'
[student@workstation system-process]$ ansible-navigator run -m stdout schedule_at_task.yml
...output omitted...
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0  ...

A minute later:

[student@workstation system-process]$ ssh devops@servera "cat my_at_date_time"
Thu Oct  1 03:41:00 PM NZDT 2026

become_user: devops makes the job belong to devops, so it runs as that user, in that user's home directory.

Change the default boot target

Create set_default_boot_target_graphical.yml. It reads the current default target and sets it to graphical.target only if it is not already.

---
- name: Change default boot target
  hosts: webservers
  become: true
  gather_facts: false
  vars:
    default_target: "graphical.target"

  tasks:
    - 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']
[student@workstation system-process]$ ssh devops@servera "systemctl get-default"
multi-user.target
[student@workstation system-process]$ ansible-navigator run \
> -m stdout set_default_boot_target_graphical.yml
...output omitted...
TASK [Get current boot target] *************************************************
ok: [servera.lab.example.com]

TASK [Set default boot target] *************************************************
changed: [servera.lab.example.com]
...output omitted...
[student@workstation system-process]$ ssh devops@servera "systemctl get-default"
graphical.target

The home-lab servers already boot to graphical.target, so the first run skips the second task. Copy the playbook to set_default_boot_target_multi-user.yml with default_target: "multi-user.target" and run that one first; then come back to this one.

changed_when: false keeps the reading task from reporting a change, and the when keeps the second task idempotent.

Reboot from a playbook

Create reboot_hosts.yml that reboots the web servers, and check the boot time before and after:

---
- name: Reboot hosts
  hosts: webservers
  become: true

  tasks:
    - name: Hosts are rebooted
      ansible.builtin.reboot:
[student@workstation system-process]$ ssh devops@servera "who -b"
         system boot  2026-10-01 15:38
[student@workstation system-process]$ ansible-navigator run -m stdout reboot_hosts.yml
...output omitted...
TASK [Hosts are rebooted] ******************************************************
changed: [servera.lab.example.com]
...output omitted...
[student@workstation system-process]$ ssh devops@servera "who -b; systemctl get-default"
         system boot  2026-10-01 15:41
graphical.target

The task waited until servera was back and answering before it finished. The new default target survived the reboot.

Put the target back

Copy the boot target playbook to set_default_boot_target_multi-user.yml, set default_target to multi-user.target, and run it. (In the home lab, set it back to graphical.target instead.)

[student@workstation system-process]$ ansible-navigator run \
> -m stdout set_default_boot_target_multi-user.yml
...output omitted...
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0  ...

Finish

Check your work with lab grade system-process, then put the exercise away with lab finish system-process.