Ansible #3: Finishing our Ansible playbook to manage workstation and server updates

0

In part two of this series on writing Ansible playbooks, Ansible #2 How to create an Ansible Playbook, we examined the task of installing updates for servers and workstations. This playbook is intended to manage updates differently depending on the role the systems play on the network. Last time we created the play designed to install updates on the Ansible hub. This time we add two more plays; one to install updates on the servers and another to install updates on the regular workstations.

I have decided to streamline my update process for customer systems I support as well as for my own Linux devices. While a series of complex scripts have served me well for a long time, the advantages of using Ansible for this task are just too many to pass up.

In part one of this article series, I discussed the actual update process, established the structure of my playbook, and presented keywords and comments. The playbook contains three plays. Each play manages a category of systems. Play 1 handles my Ansible controller device (my primary workstation), while Plays 2 and 3 manage servers and any remaining workstations.

Let’s begin this article by examining the second play.

The second play

Here is the entire second play. The purpose of this play is to perform updates on the firewall and server.

The firewall needs to be up and running while the servers—and all other hosts—are being updated so they can have Internet access to download the update packages. The server needs to run while the firewall and other hosts are being updated to provide DHCP and name services. To accomplish that, this play updates those two hosts one at a time but the specific sequence is not important. The names for these two hosts are contained in the [all_servers] group in the /etc/ansible/hosts inventory file.

Figure 1 shows play 2 for the servers.

#######################################################################
#######################################################################
# Play 2 - Do servers 
#######################################################################
#######################################################################
- name: Play 2 - Install updates for servers yorktown and wally
  hosts: all_servers
  serial: 1
  remote_user: root
  vars:
    run: false
    reboot: false

  tasks:
#######################################################################
# Do some preliminary checking
#######################################################################
    - name: Install the latest version of the doUpdates script
      copy:
        src: /root/ansible/Updates/files/doUpdates
        dest: /usr/local/bin
        mode: 0774
        owner: root
        group: root

    - name: Check for currently available updates
      command: doUpdates -c
      register: check
    - debug: var=check.stdout_lines

#######################################################################
# Do the updates.
#######################################################################
# Install all available updates
    - name: Install all current updates
      dnf:
        name: "*"
        state: latest
      when: (check.stdout | regex_search('updates ARE available')) and run == "true"

    - name: Update the man database
      command: mandb
      when: run

    - name: Reboot if necessary and reboot extra variable is true
      reboot:
      when: (check.stdout | regex_search('reboot will be required')) and reboot == "true" and run == "true"

Figure 1: Play 2 installs updates on the servers.

This entire second play is almost the same as the first, except for two lines.

serial: This additional statement tells Ansible to run this play on one host at a time, that is, serially rather than in parallel. If the all_servers group in the inventory contained ten servers, I could use a higher limit, such as two, so that this play would be run against two servers at a time. In this case, I need wally, the firewall, to be up and running so that the yorktown server has access to the Internet to download the updated packages. It does not matter which sequence these two hosts are updated so long as they are not both done simultaneously.

reboot: Ansible has a built-in reboot capability, so that we can use that in this play instead of the Linux poweroff command. The critical feature of the Ansible reboot function is that it performs a verification that the reboot has been successful, the remote host is up and running, and SSH communication is once again working. The default timeout for this is 10 minutes, after which Ansible throws an error.

The third play

Figure 2 shows the third play. This play updates all of the remaining hosts on my network. The names for these hosts are contained in the [workstations] group of the /etc/ansible/hosts inventory file.

#######################################################################
#######################################################################
# Play 3 - Do all workstations except david
#######################################################################
#######################################################################
- name: Play 3 - Install updates for all workstations except david
  hosts: workstations
  strategy: free
  remote_user: root
  vars:
    run: false
    reboot: false

  tasks:
#######################################################################
# Do some preliminary checking
#######################################################################
    - name: Install the latest version of the doUpdates script
      copy:
        src: /root/ansible/Updates/files/doUpdates
        dest: /usr/local/bin
        mode: 0774
        owner: root
        group: root

    - name: Check for currently available updates
      command: doUpdates -c
      register: check
    - debug: var=check.stdout_lines

#######################################################################
# Do the updates.
#######################################################################
# Install all available updates
    - name: Install all current updates
      dnf:
        name: "*"
        state: latest
      when: (check.stdout | regex_search('updates ARE available')) and run == "true"

    - name: Reboot if necessary and reboot extra variable is true
      reboot:
      when: (check.stdout | regex_search('reboot will be required')) and reboot == "true" and run == "true"

Figure 2: Play number 3 installs updates on the rest of the workstations on my network.

There is only one change in this playbook other than the list of hosts.

strategy: The free strategy tells Ansible to perform the tasks in this play freely. The tasks in this play are run on each host as quickly as the host can do them. This means that some hosts may finish the last task well before other, slower hosts have completed even the first task in the playbook. It can appear to be sort of a free-for-all as you read the STDOUT data stream.

Each strategy is a separate plugin, and there are a couple of other plugins that can be used with this keyword. The default is linear, which performs each task on all hosts before moving on to the next task. The host_pinned plugin performs all tasks in the play on each host before moving on to the next host. The debug plugin runs tasks interactively so that the play can be debugged.

Running the playbook

I run this playbook using the command:

# ansible-playbook -e "run=true reboot=true" doUpdates.yml

The -e option stands for “extra variables.” Here it specifies values for the two variables defined in each play. In this case, setting them both to true allows the updates to be performed and the reboot to take place if it is required.

I won’t reproduce the STDOUT data stream here because it is quite long.

Final thoughts

With a few changes to reflect your network’s details, this playbook can be used to automate your own update tasks. Performing updates using a playbook similar to mine is a good way to get started using Ansible. Although it uses several keywords that can perform complex tasks, this is a relatively simple playbook. I started with just the first play to update my personal workstation, and the rest was mostly copy/paste, with a few minor changes to accommodate the needs of the different groups of hosts.

Yes, there are other ways of doing this. I probably could have used different tasks using conditionals within one or two plays instead of three plays. Or I could have used conditionals and blocks to deal with handling specific hosts differently. Personally, I think that individual plays help to separate the logic enough that changing how tasks are handled in one play does not affect the others. In my opinion, this separation is also more elegant because the overall logic is simpler to write, understand, and easier to manage.

Resources

The most complete and useful document I have found is the Ansible User Guide on the Ansible web site. This document is intended as a reference and not as a how-to or getting-started document.

Opensource.com has published many articles about Ansible over the years, and I have found most of them very helpful for my needs. The Red Hat Enable Sysadmin web site also has a lot of Ansible articles that I have found to be beneficial. Despite the fact that these two resources no longer publish new articles, they remain good resources for Linux SysAdmins.

There are also some good but terse man pages available.