To overly simplify the concept of Ansible, let’s say it allows to SSH from any machine (
control node
) to a group of servers (
managed nodes
) to run the same commands, as long as Ansible is installed on the control node. The magic part is that you don’t have to deploy any agent on the the managed nodes. I will not cover the functioning of Ansible in this blog post,
the official documentation
is a must-read.
I recently had to deploy Autonomous Health Framework on a group of new servers : finally I got the simple use-case I was waiting for.
OK that’s great … But is there a way to reuse this list of tasks, and define some variables, for example ? Yes it is possible using roles :
Roles let you automatically load related vars_files, tasks, handlers, and other Ansible artifacts based on a known file structure. Once you group your content in roles, you can easily reuse them and share them with other users.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
Fortunately, there is a command to create a role,
ansible-galaxy
. It generates the directory structure for a new role :
# ansible-galaxy init ora_inst_ahf
- Role ora_inst_ahf was created successfully
The result is a directory tree compliant with Ansible role standards :
tree ora_inst_ahf/
ora_inst_ahf/
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
8 directories, 8 files
In this blog post, I will only focus on 2 files : the commands I want to perform on all servers will go in file
tasks/main.yml
, and the related variables will go in
defaults/main.yml
Here is
the official description of those 2 files
:
tasks/main.yml – the main list of tasks that the role executes.
defaults/main.yml – default variables for the role […]. These variables have the lowest priority of any variables available, and can be easily overridden by any other variable, including inventory variables.
Source :
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
What is the installation scenario for Autonomous Health Framework ?
Now that we’ve got the basic concepts of Ansible, let’s list the necessary steps to install Autonomous Health Framework (AHF) :
-
Check if the server has a compatible OS distribution and version
-
Check if AHF is already installed and get its current version
-
Uninstall an eventual existing installation of AHF
-
Transfer the up-to-date AHF archive on the server
-
Unzip it
-
Run the AHF installer
-
Clean up the installer once AHF is installed
-
Clean up the README file once AHF is installed
-
Run
orachk
(This is not mandatory, but it will validate the installation has been performed correctly
-
Show the
orachk
results
How to go from this list to a list of Ansible tasks ?
All these steps will be translated into a list of Ansible tasks, each of these tasks consisting in running the relevant module. There is
a huge amount of modules
available, and almost all the steps listed above can be executed by a module.
Before jumping in the creation of tasks, useful variables can be defined in file
defaults/main.yml
so they can be modified easily if something changes :
# defaults file for ora_inst_ahf
# variables
os_distrib: RedHat # Actually, not only RH, but also OEL, SuSE, Solaris ...
ahf_archive_repo: /tmp
ahf_archive_name: AHF-LINUX_v20.2.3.zip # Downloadable on MOS note 30166242
ahf_archive_version: "{{ (ahf_archive_name | regex_replace('v|.zip', '_')).split('_')[2] }}"
ahf_install_dir: /u01/app/oracle/local
With these variables and a clear idea of the installation procedure, it’s time to translate every step in Ansible tasks.
Check if the server has a compatible OS distribution and version
The
setup module
gets useful pieces of information (known as “facts”) from remote hosts. Parameter
filter
is used to get only what is necessary : the info related to the OS distribution.
- name: Check OS distribution
setup:
filter: distribution
failed_when: ansible_distribution != os_distrib # Var. "os_distrib" defined in defaults/main.yml
The use of directive
failed_when
means that this tasks will fail if the condition is met. So if the OS distribution is not equal to variable
os_distrib
, the task will fail. And all other tasks will not be executed.
According to
AHF documentation
, it supports Linux RedHat 4 minimum, so it is also necessary to check the OS version :
- name: Check OS version
setup:
filter: distribution
failed_when: ansible_distribution_version is version('4', '<')
Check if AHF is already installed and get its current version
The
stat module
gets information about a file, just like
stat
Unix command. The result of this state module is registered in variable
stat_ahf
.
- name: Check if AHF is already installed
stat:
path: "{{ ahf_install_dir }}/ahf/oracle.ahf/bin/tfactl"
register: stat_ahf
Since the information about a potential
tfactl
binary has been retrieved in variable
stat_ahf
, we can now get the installed version of AHF if the
tfactl
binary exists. The result is stored in variable
ahf_current_version
.
- name: Get current version of already installed AHF
shell: "set -o pipefail && {{ ahf_install_dir }}/ahf/oracle.ahf/bin/tfactl version | cut -d' ' -f3"
register: ahf_current_version
when: stat_ahf.stat.exists
The
version
built-in test exists in Ansible to compare version numbers. In this case, if the installed AHF version is greater or equal to the version about to be deployed, then the play will stop, thanks to
module meta
, using parameter
end_play
.
- name: End execution if AHF is already installed with this version or higher
meta: end_play
when:
- stat_ahf.stat.exists
- ahf_current_version.stdout is version(ahf_archive_version, '>=')
Uninstall an eventual existing installation of AHF
Uninstalling AHF is pretty straightforward
by running a simple command
. And there is not (yet?) a dedicated module to install AHF 😉 … This is where
shell module
comes in handy : it will execute any shell command passed in argument.
Never the less, it should not be used to execute an action for which a module already exists, because most of the time, using the dedicated module would be more convenient and secure.
- name: Uninstall existing ahf installation
shell: "{{ ahf_install_dir }}/ahf/oracle.ahf/bin/tfactl uninstall -silent"
when: stat_ahf.stat.exists
Transfer the up-to-date AHF archive on the server + Unzip it
The
unzip module
is very interesting, because it copies an archive from the control node to the managed node, and then unzips it. It avoids running 2 different steps.
It is necessary to specify the source of the zip file (in this case, located in the control node), a destination for the unzipped directory, and the permission (mode) of the resulting directory.
- name: Unzip ahf archive
unarchive:
src: "{{ ahf_archive_repo }}/{{ ahf_archive_name }}"
dest: "{{ ahf_install_dir }}"
mode: 0755
Run the AHF installer
Installing AHF is also pretty straightforward
by running a simple command
. Once again, the shell module turns out to be useful :
- name: Run ahf installer
shell: "{{ ahf_install_dir }}/ahf_setup -ahf_loc {{ ahf_install_dir }}/ahf"
Clean up the installer once AHF is installed
Once AHF is installed, the installer can be deleted. File module is used once again to remove this file.
- name: Clean up ahf installer
file:
path: "{{ ahf_install_dir }}/ahf_setup"
state: absent
Clean up the README file once AHF is installed
Same here, once AHF is installed, README file can be deleted. File module is used once again to remove this file.
- name: Clean up ahf README.txt
file:
path: "{{ ahf_install_dir }}/README.txt"
state: absent
Run orachk
Here is the most tricky part of this role. We could simply run
orachk
using the shell module : I tried with AHF version 20.2 and it worked. But then I downloaded the latest version 20.3, re-ran this role and I got a timeout ! In fact, the latest
orachk
now prompts 2 questions in the command line, and expects interactive inputs :
This computer is for [S]ingle instance database or part of a [C]luster to run RAC database [S|C] [C]: S
orachk did not find the inventory location on flora-server from environment. Does flora-server have Oracle software installed [y/n][n]? n
This can be solved with
module expect
. To use it, provide the expected strings (or part of them, or matching regex) and the strings to respond with :
- name: Run orachk with option nordbms
expect:
command: "{{ ahf_install_dir }}/ahf/oracle.ahf/bin/orachk -nordbms"
responses:
'This computer is for ': "S"
'orachk did not find the inventory location on ': "n"
timeout: null
register: orachk_output
The output of the
orachk
is in variable
orachk_output
using the keyword
register
.
Show orachk results
And finally, the
debug module
will simply print the content of variable
orachk_output
:
- name: Show orachk result
debug:
msg: "{{ orachk_output.stdout_lines }}"
How to actually install AHF using Ansible now ?
Now the complete
tasks/main.yml
file looks like this :
# tasks file for ora_inst_ahf
- name: Check OS distribution
setup:
filter: distribution
failed_when: ansible_distribution != os_distrib
- name: Check OS version
setup:
filter: distribution
failed_when: ansible_distribution_version is version('4', '<')
- name: Check if AHF is already installed
stat:
path: "{{ ahf_install_dir }}/ahf/oracle.ahf/bin/tfactl"
register: stat_ahf
- name: Get current version of already installed AHF
shell: "set -o pipefail && {{ ahf_install_dir }}/ahf/oracle.ahf/bin/tfactl version | cut -d' ' -f3"
register: ahf_current_version
when: stat_ahf.stat.exists
- name: End execution if AHF is already installed with this version or higher
meta: end_play
when:
- stat_ahf.stat.exists
- ahf_current_version.stdout is version(ahf_archive_version, '>=')
- name: Uninstall existing ahf installation
shell: "{{ ahf_install_dir }}/ahf/oracle.ahf/bin/tfactl uninstall -silent"
when: stat_ahf.stat.exists
- name: Unzip ahf archive
unarchive:
src: "{{ ahf_archive_repo }}/{{ ahf_archive_name }}"
dest: "{{ ahf_install_dir }}"
mode: 0755
- name: Create ahf installation directory
file:
path: "{{ ahf_install_dir }}/ahf"
state: directory
mode: 0755
- name: Run ahf installer
shell: "{{ ahf_install_dir }}/ahf_setup -ahf_loc {{ ahf_install_dir }}/ahf"
- name: Clean up ahf installer
file:
path: "{{ ahf_install_dir }}/ahf_setup"
state: absent
- name: Clean up ahf README.txt
file:
path: "{{ ahf_install_dir }}/README.txt"
state: absent
- name: Run orachk with option nordbms
expect:
command: "{{ ahf_install_dir }}/ahf/oracle.ahf/bin/orachk -nordbms"
responses:
'This computer is for ': "S"
'orachk did not find the inventory location on ': "n"
timeout: null
register: orachk_output
- name: Show orachk result
debug:
msg: "{{ orachk_output.stdout_lines }}"
SSH password:
PLAY [flora-server] *************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************
ok: [flora-server]
TASK [ora_inst_ahf : Check OS distribution] *************************************************************************
ok: [flora-server]
TASK [ora_inst_ahf : Check OS version] ******************************************************************************
ok: [flora-server]
TASK [ora_inst_ahf : Check if AHF is already installed] *************************************************************
ok: [flora-server]
TASK [ora_inst_ahf : Get current version of already installed AHF] **************************************************
changed: [flora-server]
TASK [ora_inst_ahf : Uninstall existing ahf installation] ***********************************************************
changed: [flora-server]
TASK [ora_inst_ahf : Unzip ahf archive] *****************************************************************************
changed: [flora-server]
TASK [ora_inst_ahf : Create ahf installation directory] *************************************************************
changed: [flora-server]
TASK [ora_inst_ahf : Run ahf installer] *****************************************************************************
ok: [flora-server]
TASK [ora_inst_ahf : Clean up ahf installer] ************************************************************************
changed: [flora-server]
TASK [ora_inst_ahf : Clean up ahf README.txt] ***********************************************************************
changed: [flora-server]
TASK [ora_inst_ahf : Run orachk with option nordbms] ****************************************************************
changed: [flora-server]
TASK [ora_inst_ahf : Show orachk result] ****************************************************************************
ok: [flora-server] => {
"msg": [
"This computer is for [S]ingle instance database or part of a [C]luster to run RAC database [S|C] [C]:
orachk did not find the inventory location on flora-server from environment.
Does flora-server have Oracle software installed [y/n][n]? ",
"Checking Status of Oracle Software Stack - Clusterware, ASM, RDBMS",
". ",
". . . . . . . . ",
"-------------------------------------------------------------------------------------------",
" Oracle Stack Status ",
"-------------------------------------------------------------------------------------------",
" Host Name CRS Installed ASM HOME RDBMS Installed CRS UP ASM UP RDBMS UP DB Instance Name",
"-------------------------------------------------------------------------------------------",
[...]
"UPLOAD [if required] - /u01/app/oracle/local/ahf/oracle.ahf/data/flora-server/orachk/orachk_flora-server_110220_184819.zip"
PLAY RECAP *********************************************************************************************
flora-server : ok=11 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
With no manual action, the Autonomous Health Framework can now be quickly and easily deployed on a group of servers, using one command.
So what now ?
When thinking about how to “Ansible” a process, it really helps me to first split all the required steps into small actions, to have a clear idea of what I want to achieve. Then, for each action, I look for the most relevant module, if any. Then, I (try to) read carefully the module documentation, to make sure I understand it (and sometimes it is a lot of work). But every day I feel more comfortable writing Ansible roles like this one, and automate more DBA tasks.
Hi Douglas!
Thank you very much for your valuable feedback. I agree with both suggestions : checking the version before attempting any installation is a way more logical (and indeed idempotent) method. And using ‘tfactl uninstall’ command is the right way to uninstall.
I’ll modify my role accordingly. Thanks again 🙂
↓
Good work!
I learnt something new about Ansible. I had not considered using ‘meta end_play’ when making a script idempotent. I usually waste a lot of time and code putting in unneeded when clauses on subsequent tasks. This will be a great time-saver for me.
I share your frustration with products changing the way they run between releases. This makes Ansible scripts fragile.
Your method of extracting the product version is much succinct than what I came up with. I do in three (3) steps what you did in two (2).
↓
Thank you! I am glad it can help!
And by the way, after using “meta: end_play”, I realized it ends the whole play, for all the managed nodes. I think using “meta: end_host” would be even more useful in this case.
↓
I think that using “meta: end_host” in a role would introduce a subtle bug. But this would be useful for a playbook with multiple hosts.
My understanding of the purpose of roles is that a playbook can use multiple ones. The order of roles in a playbook would produce different results. For example, playbook consisting of roles for database server followed by ora_inst_ahf would work fine, whereas the opposite order would fail if the AHF was installed with the latest version and the database server was not configured correctly.
I did not think about using this role in a playbook made up of different roles, yet. But if I do this, then I’ll definitely end up with an incomplete installation, depending on the order. Thank you for the hint!
Hi Jose!
Thank you for your comment.
This playbook file is stored in the same directory as the role directory “ora_inst_ahf”. If you store it elsewhere, then you’ll get an error message when you run the playbook, similar to:
ERROR! the role ‘ora_inst_ahf’ was not found in /home/[your_user]/roles:/home/[your_user]/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:/home/[your_user]
↓
The views expressed on this blog are my own and do not necessarily reflect the views of Oracle.