Handling kernel upgrades with Ansible prior to an Oracle installation

As part of the process of setting up VMs in the cloud for use with the Oracle database it is frequently necessary to update the systems to the latest and greatest, and hopefully more secure packages before the Oracle installation can begin. In a similar way I regularly upgrade the (cloud-vendor provided) base image when building a custom image using Packer. This demands for an automated process in my opinion, and Ansible is the right tool for me.

I may have mentioned once or twice that a Spacewalk powered (or equivalent) local repository is best for consistency. You may want to consider using it to ensure all systems are upgraded to the same packages. Applying the same package updates in production as you did in test (after successful regression testing of course) makes testing in lower-tier environments so much more meaningful ;)

Upgrading

My target VM is running in Oracle’s Cloud, and I’m spinning it and its required supporting infrastructure up with a small Terraform script. The playbook is executed immediately after the VM becomes available.

The playbook you are about to see later in the article is only intended for use prior to the initial installation of the Oracle binaries, after the VM has been freshly provisioned.

My playbook will determine whether a new kernel-uek has been installed as part of the upgrade process, and optionally reboot the VM should it have to. A reboot is acceptable in my scenario where I’m building a new VM with Oracle software to be installed as part of the process. In other cases, this approach is not tenable, consider yourself warned. A flag controls the reboot behaviour.

Be advised that not using a local repository can lead to an upgrade of kernel UEK5 to UEK6. The most current oraclelinux-release-el7 package ships with the ol7_UEKR5 repository disabled and ol7_UEKR6 repository enabled. The playbook therefore enables the UEK5 repository explicitly, and disables ol7_UEKR6 to remain on the UEK5 kernel branch. It also checks for UEK5 and mandates Oracle Linux 7.8 or newer because everything is really old by modern standards …

A few days ago Oracle Linux 7.9 has become available, and again – depending on your yum configuration – you might end up upgrading 7.8 to 7.9. Which is exactly what I want, but not necessarily what you want. Please review your configuration carefully to ensure the correct outcome. It goes without saying that testing is a must.

Introducing Ansible for system upgrades

I have been using Ansible a lot over the past years, it’s a handy tool to know. The question I wanted to answer is: how can I perform a full system upgrade in Ansible prior to installing the Oracle database?

The Ansible Playbook

Before talking more about the playbook, let’s see it first:

---
#
# Ansible playbook to update all RPM packages on a lab VM (powered by Oracle Linux 7.8 and later)
# prior to the installation of Oracle binaries (only).
#
# NEVER USE THIS PLAYBOOK ON A VM THAT HASN'T JUST FRESHLY BEEN PROVISIONED! 
#
# It is _only_ intended to be used as part of the initial Oracle installation on a fresh VM.
#
# The system upgrade includes the kernel as well, as it's an integral part of the system 
# and newer kernels provide security fixes and performance enhancements.
#
# The installation of a new kernel requires a reboot to become effective. You can control
# whether you want to reboot straight away or later (see variable "reboot"). The default is
# not to reboot the VM as part of the playbook's execution.
#
# The playbook requires the server to be booted into Oracle's Unbreakable Enterprise Kernel
# (UEK) for now, this can easily be changed if needed. 
#
# The reboot module requires ansible >= 2.7. 
#
# Both conditions are enforced. 
#
# As an added safety net, the playbook checks  for the presence of /etc/oraInst.loc and 
# /etc/oratab, which admittedly isn't a perfect way of identifying the presence of Oracle 
# software, but it's better than nothing. 
# It is _your_ responsibility to ensure you don't run this playbook outside the initial Oracle 
# software installation.
#
# PARAMETERS
# - reboot: if set to true the playbook is going to reboot the VM straight away. If set to
#      false it is your responsibility to reboot into the new kernel
# 
# Please refer to https://martincarstenbach.wordpress.com/ for more details
#

- hosts: oracledb
  name: full system upgrade of a lab VM prior to the installation of Oracle 19c
  become: yes
  vars:
    reboot: false

  tasks:
  - name: fail if the O/S is not detected as Oracle Linux 7.8 or later
    fail:
      msg: This playbook is written for Oracle Linux Linux 7.8 and later
    when: ansible_distribution != 'OracleLinux' and ansible_distribution_version is version('7.8', '<')

  - name: fail if Oracle's UEK5 is not in use
    fail:
      msg: this playbook only covers Oracle's Unbreakable Enterprise Kernel UEK Release 5
    when: not ansible_kernel is search ("4.14")

  - name: fail if the Ansible release is older than 2.7
    fail:
      msg: This playbook requires Ansible 2.7 or later
    when: ansible_version.full is version('2.7', '<=')

  # no guarantee this detects _your_ Oracle installation, see notes
  - name: try to detect Oracle software
    block:
      - name: try to detect Oracle Universal Installer's inventory pointer 
        stat: path=/etc/oraInst.loc
        register: orainst

      - name: fail if inventory pointer was detected
        fail:
          msg: It appears as if Oracle database software has already been installed, aborting 
        when: orainst.stat.exists | bool

      - name: try to detect the database's oratab file
        stat: path=/etc/oratab
        register: oratab

      - name: fail if an oratab file was detected
        fail:
          msg: It appears as if Oracle database software has already been installed, aborting 
        when: oratab.stat.exists | bool

  #
  # this is where the actual work is done
  # 
  - name: update all packages (remain on the UEK5 branch)
    yum:
      name: '*'
      state: latest
      enablerepo: ol7_UEKR5
      disablerepo: ol7_UEKR6
      update_cache: yes

  - name: get latest kernel UEK installed on disk
    shell: /usr/bin/rpm -q kernel-uek | /usr/bin/sort -V | /usr/bin/tail -1
    register: latest_uek
    args:
      warn: false

  - name: trim the RPM name to make it easier to compare with Ansible's facts
    set_fact:
      latest_uek_rel: "{{ latest_uek.stdout | regex_replace('kernel-uek-(.*)', '\\1') }}"

  - name: print detected kernel releases
    debug:
      msg: |
        This server booted into UEK {{ ansible_kernel }}, 
        the latest kernel on disk is {{ latest_uek_rel }}.

  - name: reboot the VM
    reboot:
      msg: Ansible is rebooting the VM now
    when: ansible_kernel != latest_uek_rel and reboot | bool 

  - name: print reboot reminder
    debug:
      msg: A new kernel has been installed, please remember to reboot the VM at an opportune moment
    when: ansible_kernel != latest_uek_rel and reboot | bool == false 

Although it looks lengthy the code is straight forward. It will update all packages after a few safety checks. I experimented with another flag, upgrade_kernel, but had to learn the hard way that creating an exclusion list for yum is quite difficult given the many different packages starting ^kernel … At the end of the day I decided against its use.

Kernel Update

The hardest part was to come up with a way to compare the boot kernel with the latest installed kernel (on disk). The playbook only concerns itself with the Unbreakable Enterprise Kernel (UEK), ignoring the Red Hat Compatible Kernel (RHCK).

The latest kernel on disk can be found using a combination of the rpm, sort and tail commands. I tried to achieve the same result with Ansible’s yum module and the list option, but would have had to spend quite a bit of time working out how to get the latest installed kernel this way. Back to shell it was! Sort’s -V option is magical as is allows me to sort the kernels by release in ascending order. The last row returned has to be the most current kernel. If this kernel release (after being stripped of kernel-uek-) doesn’t match the boot kernel, a reboot is necessary.

Depending on whether you set the reboot flag to true or false, the system is rebooted. If you are building a Packer image, or prepare a VM for an Oracle installation you may want to consider setting the flag to true. If you don’t, a friendly message reminds you of the need to reboot at your convenience.

Summary

I don’t run this playbook in isolation, it has become an integral part of my Ansible-driven Oracle installation toolkit. I prefer to get the software updates out of the way early in the Oracle software installation process, it’s much easier this way.