A Patch Train Solution for OCI OS Management with OCI Ansible Modules

 

With the OS Management Service (OSMS) and i-ansible-collection, Oracle Cloud Infrastructure provides a service to fully automate the patching of your Oracle Linux or Windows instances. It allows you to organize your systems into groups and then schedule jobs to apply the latest updates to all these systems. There is a wide range of predefined software sources to choose from, providing the full wealth of the Oracle yum repositories to your Linux systems. In the simplest case, this will keep all your systems up to date with the latest and consistent  patches all the time using Ansible modules.

In many cases, this eliminates the burden of constantly chasing security updates and keeping your fleet patched. However, sometimes your patching goals are more complex than just having the "latest and greatest" all the time. In this article, I'll describe one such scenario and a solution.

Patch trains.

The patch train idea can be easily described: Start with defining consistent patch bundles or in oci term custom software source, i.e. a set of exact packages including their version. Then apply the patch bundle to a testing environment and test it. If test shows good results, promote patch to next environment. And you do this in your testing environments until you are ready for applying the bundle to the production environment.


 


Enabling OS Management

OS Management will not out of the box in your tenancy. You first need to create a dynamic group and set some policies and configure accessing-the-oracle-cloud-infrastructure-api-using-instance-principals. This requires some administration rights on tenancy level.

Example: These policies are created in Root level to allow all Linux instance across the compartment that need a patching . If you think that the policies are more exposed then you can play with some more restricted rules.

 Allow dynamic-group linux-patching to manage osms-managed-instances in tenancy

Allow dynamic-group linux-patching to manage osms-managed-instance-groups in tenancy

Allow dynamic-group linux-patching to manage osms-software-sources in tenancy

Allow dynamic-group linux-patching to manage osms-errata in tenancy

Allow dynamic-group linux-patching to manage osms-scheduled-jobs in tenancy

Allow dynamic-group linux-patching to manage osms-work-requests in tenancy

Allow dynamic-group linux-patching to use instance-family in tenancy

Allow dynamic-group linux-patching to read instance-family in tenancy

Allow service osms to read instances in tenancy

For Automation hosts running Ansible task defined below needed below privilege.

Allow dynamic-group automation_servers to manage instance-family in compartment OS_Images

Allow dynamic-group automation_servers to read app-catalog-listing in tenancy

Allow dynamic-group automation_servers to use volume-family in compartment OS_Images

Allow dynamic-group automation_servers  to manage volume-family in tenancy

Allow dynamic-group automation_servers to use virtual-network-family in compartment OS_Images

Allow dynamic-group automation_servers to use virtual-network-family in compartment Networks

Allow dynamic-group automation_servers to read instance-images in tenancy

Allow dynamic-group automation_servers to read instance in tenancy

Allow dynamic-group automation_servers to manage volume-backups in tenancy

Allow dynamic-group automation_servers to manage osms-managed-instances in tenancy

Allow dynamic-group automation_servers to manage osms-managed-instance-groups in tenancy

Allow dynamic-group automation_servers to manage osms-software-sources in tenancy

Allow dynamic-group automation_servers to read osms-software-sources in tenancy

Allow dynamic-group automation_servers to manage osms-errata in tenancy

Allow dynamic-group automation_servers to manage osms-scheduled-jobs in tenancy

Allow dynamic-group automation_servers to manage osms-work-requests in tenancy

Allow dynamic-group automation_servers to manage instance-family in tenancy

Allow dynamic-group automation_servers to manage buckets in tenancy

Allow dynamic-group automation_servers to manage objects in tenancy

Allow dynamic-group automation_servers to read virtual-network-family in tenancy

Allow dynamic-group automation_servers to inspect compartments in tenancy

 

Note:  Indentation might  misplaces during pasting.

Ansible Collection block must need to call Ansible modules, see how to configure getting start and github

---

- name :Oracle Ansible collection

  collections:

    - oracle.oci

    - oracle.oci.oci_compute_dedicated_vm_host_facts

  connection: local

  hosts: localhost

 

Custom Parent Software Source

First thing we need to prepare is an  empty custom parent software source. This will later have the custom software sources attached that can be applied to specific managed instance groups.

 

- name: Create custom parent software_source

  oci_os_management_software_source:

    compartment_id: "{{ compartment_id }}"

    display_name: "{{ display_name }}"

    region: "{{  region | default('us-phoenix-1') }}"

    description: This shoftware source will use for all patching in OL7 release

    arch_type: X86_64

    state: present

  register: custom_parent_ss

  tags:

    - ss

 

Can to be done via CLI as well

oci os-management software-source create  -c ocid1.tenancy.oc1..bbbbbaatdylvcgv2r6xxplnkemowv123455nl4d2t6kpbp4s6ygq --display-name latest_july_2021 --arch-type X86_64

 

 

 

Creating Managed Instance Groups

I recommend to create a special managed instance group for reference/template instances. These instances must keep on using the standard software sources.

Create a managed instance group for each stage you plan to include in your patching process.

 

 

- name: Create managed_instance_group

  oci_os_management_managed_instance_group:

    display_name: linux_patching

    region: "{{  region | default('us-phoenix-1') }}"

    compartment_id: "{{ compartment_id }}"

  register: mns

 

- name: Dump managed_instance_group name

  debug:

    msg: '{{ mns.managed_instance_group.display_name }}'

 

 

CLI

oci os-management managed-instance-group create -c ocid1.tenancy.oc1..bbbbbaatdylvcgv2r6xxplnkemowv123455nl4d2t6kpbp4s6ygq --display-name test_patch

 

Adding Managed Instances to Managed Instance Groups

All we got so far are empty managed instance groups. Now it is time to add some Oracle Linux managed instances to the managed instance groups using  dynamic inventory file Working with Ansible Inventory

 

Example inventory.oci.yaml, I am searching for all Linux host with their OCID numbers those have batch1 free-form tags.

-------------------------------------------------------------------------------------------------------------------------------------

plugin: oracle.oci.oci

auth_type: instance_principal

regions:

  - us-ashburn-1

  - us-phoenix-1

debug: false

enable_parallel_processing: yes

compartments:

  - compartment_ocid: ocid1.tenancy.oc1..bbbbbaatdylvcgv2r6xxplnkemowv123455nl4d2t6kpbp4s6ygq

    fetch_hosts_from_subcompartments: true

hostname_format_preferences:

  #- "display_name+'.oci.com'"

  - "id"

include_host_filters:

  #- "'batch1' in freeform_tags.os_patching and 'RUNNING' in lifecycle_state"

  #- "'batch1' in freeform_tags.os_patching and 'RUNNING' in lifecycle_state"

  - "'batch1' in freeform_tags.os_patching and 'RUNNING' in lifecycle_state"

--------------------------------------------------------------------------------------------------

 

 

- name: Perform action attach_managed_instance on managed_instance_group

  oci_os_management_managed_instance_group_actions:

    managed_instance_group_id: '{{ mns.managed_instance_group.id }}'

    region: "{{  region | default('us-phoenix-1') }}"

    action: attach_managed_instance

    managed_instance_id: '{{item}}'

  with_items: '{{inventory_hostname}}'

 

 

List and then detach the standard parent software source

- name: List available_software_sources in instances

  oci_os_management_available_software_source_facts:

    managed_instance_id: '{{item}}'

    region: "{{  region | default('us-phoenix-1') }}"

  with_items:

    - '{{inventory_hostname}}'

  register: parent_software_id

 

 

 

- name: Perform action detach_parent_software_source on managed_instance

  oci_os_management_managed_instance_actions:

    software_source_id: "{{parent_software_id.results|community.general.json_query('[*].available_software_sources[*].parent_id') |flatten| min}}"

    action: detach_parent_software_source

    region: "{{  region | default('us-phoenix-1') }}"

    managed_instance_id: '{{item}}'

  with_items:

    - '{{inventory_hostname}}'

  when: "parent_software_id.results|community.general.json_query('[*].available_software_sources[*].parent_id') |flatten| length  != 0"

 

 

Also need to detach any custom software source if attached

- name: Get a specific managed_instance information for custom software source

  oci_os_management_managed_instance_facts:

    managed_instance_id: '{{item}}'

  with_items:

    - '{{inventory_hostname}}'

  register: attached_ss_id

- set_fact:

    ss: "{{attached_ss_id.results|community.general.json_query('[].managed_instances[].parent_software_source.id')}}"

 

- name: Perform action detach attached custom parent software_source on managed_instance

  oci_os_management_managed_instance_actions:

    software_source_id: "{{attached_ss_id.results|community.general.json_query('[].managed_instances[].parent_software_source.id')| first }}"

    action: detach_parent_software_source

    region: "{{  region | default('us-phoenix-1') }}"

    managed_instance_id: '{{item}}'

  with_items:

    - '{{inventory_hostname}}'

  when: ' "ss|length" != 0'

 

 
Attach the empty custom parent software source that was created before. Pass software source ID through vars are can be passed directly.

 

  - name: Perform action attach custom parent software_source on managed_instance

    oci_os_management_managed_instance_actions:

      software_source_id: '{{ software_source_phx_id  }}'

      action: attach_parent_software_source

      region: "{{  region | default('us-phoenix-1') }}"

      managed_instance_id: '{{item}}'

    with_items:

      - '{{inventory_hostname}}'

 

Extracting a packages and their version from default software source.

As all the instances in oci has their own list of packages hence generating a list of packages from sample instance will not practical for other instance and can skip many packages to update or patch. To avoid that situations and make the patching consistence we need a stable and robust solutions that can be applied in both stage and production environment. Hence I am extracting a list of packages and put in a file, later that file will be use to adding a packages in the empty custom software source.

 

- name: List software_sources

  oci_os_management_software_source_facts:

    compartment_id: "{{ compartment_id }}"

    lifecycle_state: ACTIVE

  register: software_list

- set_fact:

    date: "{{ lookup('pipe','date +%Y-%m-%d') }}"

 

Here I am looking for OL7 default latest software source list/

 

- name: Dump latest OL7 software source id list

  set_fact:

    test_software_source_id: '{{ item.parent_id }}'

  when: 'item.parent_name == "Oracle Linux 7Server Latest (x86_64)"'

  loop: "{{ software_list.software_sources }}"

 

- name: version of all available packages from OL7 latest source source

  oci_os_management_software_package_facts:

    software_source_id: '{{ test_software_source_id }}'

  register: software_list

 

Then copy an all packages to a local file that can be used added in custom software source

 

- name: copy packages name to a local file

  copy:

    content: "{{ software_list.software_packages | json_query('[*].name') }}"

    dest: "/u01/ansible/patching/group_vars/package_list"

  delegate_to: localhost

 

 

Adding the Packages to the Custom parent Software Source created before.

Now I am calling packages list of files created above. As the list items are in huge size and could be generate long API calls hence here I am adding that package in chunk of 500 in custom software source. Tag ss is used to just run required functions not everything to add the package in other region of software source.

 

- set_fact:

    list: '{{item}}'

  with_file: "/u01/idmauto/ansible/patching/group_vars/package_list"

  tags:

    - ss

 

- name: add packages to custom client software_source

  oci_os_management_software_source_actions:

    software_source_id: '{{custom_parent_ss.software_source.id}}'

      region: "{{  region | default('us-phoenix-1') }}"

      action: add_packages

      package_names: "{{list[(item|int):(item|int)+500]}}"

  with_sequence: start=0 end="{{list | length}}" stride=500

  tags:

    - ss

 

Installing Packages and Updates

Updating a managed instance group can be done by scheduling a job. This job then is executed once or on a regular schedule, for the current description i will focus on onetime update jobs only.

- set_fact:

    date: "{{ lookup('pipe','date +%Y-%m-%d') }}"

- name: shedule a patch job

  oci_os_management_scheduled_job:

    compartment_id: "{{ compartment_id }}"

    region: "{{  region | default('us-phoenix-1') }}"

    managed_instance_groups:

      - id: '{{ mns.managed_instance_group.id }}'

    display_name: '{{ mns.managed_instance_group.display_name }}'

    display_name: "patch_scheduler-{{ date }}"

    schedule_type: ONETIME

    time_next_execution: "2021-09-09T09:30:00+00:00"

    operation_type: UPDATEALL

    update_type: ALL

  register: schedule_job

 

 

 

 

Comments