How to update/upgrade Debian/Ubuntu Linux using Ansible

It is essential to keep your system up to date and to apply all security patches. If you are tasked with managing multiple servers, the process of logging in to each and every server to perform the task can be cumbersome. You can use Ansible to achieve the functionality. The Ansible apt module can be used to manage apt updates and apt upgrades. The module uses either aptitude or the apt-get command on the remote server for package management.

Similarly, if there are kernel updates that require OS reboot, it would be best to use the reboot ansible module to reboot the machine, wait for it to go down, come back up and to respond to commands

In this guide, we will create a script to update Debian based systems cache and upgrade packages installed. We will also include a task to restart the server if there were kernel updates that requires reboot.

Also check:

Using Ansible apt module to update all packages

Before doing a package upgrade in Debian based systems, it is always recommended to perform an apt cache refresh. This can be achieved using this command:

1
sudo apt-get update

We can achieve the same using Ansible with this task:

1
2
3
4
5
- name: Update apt repo and cache on all Debian/Ubuntu boxes
  apt:
    update_cache: yes
    force_apt_get: yes
    cache_valid_time: 3600

Where,

  • update_cache: yes - Run the equivalent of apt-get update command on all servers
  • force_apt_get: yes - Do not use the aptitude command, instead use the apt-get command on Debian/Ubuntu boxes
  • cache_valid_time: 3600 - Update the apt cache if it’s older than the cache_valid_time. This option is set in seconds. In this examples, it is set to 3600 seconds.

Next we do the upgrade. We normally run this apt-get command to achieve the function:

1
sudo apt-get upgrade -y

This is the Ansible task to achieve the upgrade:

1
2
3
4
- name: Upgrade all packages on servers
  apt:
    upgrade: dist
    force_apt_get: yes

Where,

  1. upgrade: dist - Run the equivalent of apt-get upgrade command on all Ubuntu or Debian Linux servers. In other words, upgrade all packages to latest version.
  2. force_apt_get: yes - Use apt-get instead of aptitude.

Rebooting the system if there are Kernel Upgrades

If there are Kernel upgrades, we would need to reboot the system to apply those changes. If a reboot is required a file with this path /var/run/reboot-required will be created.

What we would want to do is check if that file exist, then reboot the system. We can register a new variable if file /var/run/reboot-required exists on the system using the ansible stat:

1
2
3
4
5
- name: Check if a reboot is needed on all servers
  register: reboot_required_file
  stat:
    path: /var/run/reboot-required
    get_md5: no

Where:

  1. register: reboot_required_file - The register keyword decides what variable to save a result in and we are going to use it as follows to reboot the box.
  2. stat: path: /var/run/reboot-required - Determine if a path (/var/run/reboot-required) exists
  3. get_md5: no - Algorithm to determine checksum of file. In this example, I am using md5, but you can use sha1, sha224, sha256, sha384, and sha512.

Now that we know if the server is to be rebooted or not, let us add a task to reboot the server if the file exist:

1
2
3
4
5
6
7
8
9
- name: Reboot the server if kernel updated
  reboot:
    msg: "Reboot initiated by Ansible for kernel updates"
    connect_timeout: 5
    reboot_timeout: 300
    pre_reboot_delay: 0
    post_reboot_delay: 30
    test_command: uptime
  when: reboot_required_file.stat.exists

Where,

  1. test_command: uptime - Execute uptime command on the rebooted server and expect success from to determine the machine is ready for further tasks.
  2. when: reboot_required_file.stat.exists - First, check that the file named /var/run/reboot-required exists using a variable named reboot_required_file. The reboot module will only work if that file exists and it is enforced using when: reboot_required_file.stat.exists ansible condition.

Creating the hosts file

Now that we have the logic in place, let us create a hosts.yaml file. Save this content to a file called hosts.yaml.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
all:
  hosts:
    ubuntusrv:
      ansible_ssh_host: 10.2.11.10
      ansible_ssh_user: ubuntu
    debiansrv:
      ansible_ssh_host: 10.2.11.11
      ansible_ssh_user: admin
  children:
    allservers:
      hosts:
        ubuntusrv:
        debiansrv:

The whole playbook

We can put the whole logic in a playbook. Save this content to the file upgrade.yaml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
---
- name: Playbook to Update cache, upgrade packages and reboot os if necessary
  hosts: <meta charset="utf-8">allservers
  become: yes
  gather_facts: False
  tasks:
    - name: Update apt repo and cache on all Debian/Ubuntu boxes
      apt:
        update_cache: yes
        force_apt_get: yes
        cache_valid_time: 3600

    - name: Upgrade all packages on servers
      apt:
        upgrade: dist
        force_apt_get: yes

    - name: Check if a reboot is needed on all servers
      register: reboot_required_file
      stat:
        path: /var/run/reboot-required
        get_md5: no

    - name: Reboot the server if kernel updated
      reboot:
        msg: "Reboot initiated by Ansible for kernel updates"
        connect_timeout: 5
        reboot_timeout: 300
        pre_reboot_delay: 0
        post_reboot_delay: 30
        test_command: uptime
      when: reboot_required_file.stat.exists

Running the playbook

Make sure you set up ssh keys and run it as follows:

1
ansible-playbook -i hosts.yaml upgrade.yaml -vv

This is the output on my server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
$ ansible-playbook -i hosts.yaml upgrade.yaml -vv
ansible-playbook [core 2.12.1]
  config file = /Users/etowett/.ansible.cfg
  configured module search path = ['/Users/etowett/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Library/Python/3.8/site-packages/ansible
  ansible collection location = /Users/etowett/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible-playbook
  python version = 3.8.2 (default, Apr  8 2021, 23:19:18) [Clang 12.0.5 (clang-1205.0.22.9)]
  jinja version = 3.0.3
  libyaml = True
Using /Users/etowett/.ansible.cfg as config file
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
Skipping callback 'default', as we already have a stdout callback.
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.

PLAYBOOK: upgrade.yaml ************************************************************************************************************************************************************
1 plays in upgrade.yaml

PLAY [Playbook to Update cache, upgrade packages and reboot os if necessary] ******************************************************************************************************
META: ran handlers

TASK [Update apt repo and cache on all Debian/Ubuntu boxes] ***********************************************************************************************************************
task path: /home/citizix/labs/ansible/upgrade.yaml:7
changed: [ubuntusrv] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "cache_update_time": 1639737368, "cache_updated": true, "changed": true}

TASK [Upgrade all packages on servers] ********************************************************************************************************************************************
task path: /home/citizix/labs/ansible/upgrade.yaml:13
ok: [ubuntusrv] => {"changed": false, "msg": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nCalculating upgrade...\n0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.\n", "stderr": "", "stderr_lines": [], "stdout": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nCalculating upgrade...\n0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.\n", "stdout_lines": ["Reading package lists...", "Building dependency tree...", "Reading state information...", "Calculating upgrade...", "0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded."]}

TASK [Check if a reboot is needed on all servers] *********************************************************************************************************************************
task path: /home/citizix/labs/ansible/upgrade.yaml:18
ok: [ubuntusrv] => {"changed": false, "stat": {"atime": 1639737351.6237016, "attr_flags": "", "attributes": [], "block_size": 4096, "blocks": 8, "charset": "us-ascii", "checksum": "20f7959b87e8cd55b7c985e46d6fa38a4063037d", "ctime": 1639679787.0725238, "dev": 26, "device_type": 0, "executable": false, "exists": true, "gid": 0, "gr_name": "root", "inode": 1143, "isblk": false, "ischr": false, "isdir": false, "isfifo": false, "isgid": false, "islnk": false, "isreg": true, "issock": false, "isuid": false, "mimetype": "text/x-diff", "mode": "0644", "mtime": 1639679787.0725238, "nlink": 1, "path": "/var/run/reboot-required", "pw_name": "root", "readable": true, "rgrp": true, "roth": true, "rusr": true, "size": 32, "uid": 0, "version": null, "wgrp": false, "woth": false, "writeable": true, "wusr": true, "xgrp": false, "xoth": false, "xusr": false}}

TASK [Reboot the server if kernel updated] ****************************************************************************************************************************************
task path: /home/citizix/labs/ansible/upgrade.yaml:24
changed: [ubuntusrv] => {"changed": true, "elapsed": 73, "rebooted": true}
META: ran handlers
META: ran handlers

PLAY RECAP ************************************************************************************************************************************************************************
ubuntusrv                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Wrapping up

You learned how to update all packages on your Debian and Ubuntu Linux boxes and reboot the server if required using Ansible playbooks.

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy