PolarSPARC |
Introduction to Ansible - Part 3
Bhaskar S | 07/08/2018 |
Overview
In Part 2 of the series, we introduced Ansible Playbooks and got our feet wet with some basic capabilities.
In this part, we will continue to explore some more features of Ansible Playbooks.
Hands-on with Ansible Playbooks
Create and save a playbook file called playbook-4.yaml, with the following contents, in the directory /home/alice/Ansible:
The above playbook defines four user-defined variables, namely, target_host, home_var, shell_var, and name_var respectively. User-defined variables can be defined in the vars section of the playbook as shown above. In addition, they can be passed in using the --extra-vars command-line argument. For our example, we will be passing in the values for target_host and name_var using the command-line argument.
To test the above playbook playbook-4.yaml, execute the following command:
$ ansible-playbook playbook-4.yaml --extra-vars "target_host=host1 name_var=Alice"
The following would be a typical output:
PLAY [Playbook with 2 tasks] ******************************************************************************************************* TASK [Find user id] **************************************************************************************************************** changed: [192.168.100.11] TASK [Display user id and shell] *************************************************************************************************** ok: [192.168.100.11] => { "msg": [ "User -> vagrant", "Home -> /home/bswamina", "Shell -> /bin/bash", "Extra argument -> Alice" ] } PLAY RECAP ************************************************************************************************************************* 192.168.100.11 : ok=2 changed=1 unreachable=0 failed=0
One could also use the short form for the command-line argument -e instead of the longer --extra-vars argument.
When passing user-defined variables from the command-line, they must be specified as key-value pairs that are space separated and enclosed in double quotes as was done above.
In the above playbook, we use the lookup plugin to query values from external sources such as the shell.
In our example, we query the environment variables HOME and SHELL.
Create and save a simple html file called custom_index.html, with the following contents, in the directory /home/alice/Ansible:
Next, create and save a playbook file called playbook-5.yaml, with the following contents, in the directory /home/alice/Ansible:
The above playbook will install the popular open source webserver nginx, configure a custom index.html file, and restart nginx to reflect the changes.
If one is observant, they will notice the use of become clause at the very beginning of the playbook. Setting it to a value of yes means Ansible will execute every task in the context of the user root.
The first task uses the module apt to manage packages on Debian based target remote host(s). The apt module takes a package name and a package state. In our example, the package name is nginx with the state present. The first time this task executes, it installs the desired package. Any future runs will not reinstall the package since the package is already present.
The second task uses the module service to manage system services on target remote host(s). The service module takes a service name, a flag to indicate if the service will start on boot, and a service state. In our example, the package name is nginx, with enabled set to yes indicating it will start on boot and the service state of started.
The third task uses the module copy to copy a file to the target remote host(s) on a specified destination. The copy module takes a source file name, a destination location, and a file mode. In our example, the source file is custom_index.html that will be copied to the location /var/www/html/index.html on the target remote host.
We have a section called handlers that looks very similar to tasks in the playbook, except that they run only when notified. A handler runs only once (even when notified multiple times) at the end of a play after all the tasks in the play have completed execution.
A task notifies a handler using the notify clause and specifying the handlers name.
To test the above playbook playbook-5.yaml, execute the following command:
$ ansible-playbook playbook-5.yaml -e "target_host=host2"
The following would be a typical output:
PLAY [Setup and start nginx] ******************************************************************************************************* TASK [Gathering Facts] ************************************************************************************************************* ok: [192.168.100.12] TASK [Install nginx] *************************************************************************************************************** changed: [192.168.100.12] TASK [Start nginx] ***************************************************************************************************************** ok: [192.168.100.12] TASK [Customize nginx index.html] ************************************************************************************************** changed: [192.168.100.12] RUNNING HANDLER [restart nginx] **************************************************************************************************** changed: [192.168.100.12] PLAY RECAP ************************************************************************************************************************* 192.168.100.12 : ok=5 changed=3 unreachable=0 failed=0
Open a browser and access the URL http://192.168.100.12. The following is an illustration:
Create and save a playbook file called playbook-6.yaml, with the following contents, in the directory /home/alice/Ansible:
The above playbook demonstrates a looping construct using the with_items clause, to install multiple packages. Each list element specified under with_items will be made available in Ansible via a pre-defined variable called item.
By default, Ansible will fail on a target host when it encounters a task failure. To be precise, if there are two target hosts - host1 and host2 and a task encounters an error only on host1, it will continue with the tasks on host2. To change the behavior to continue on errors, one can specify the ignore_errors clause with a value of yes. This is what we have done in the second task (since we have intentionally specified invalid package names to force failure).
To test the above playbook playbook-6.yaml, execute the following command:
$ ansible-playbook playbook-6.yaml
The following would be a typical output:
PLAY [Setup sqlite and python-sqlite module] *************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************* ok: [192.168.100.11] TASK [Display debian packages to install] ****************************************************************************************** ok: [192.168.100.11] => (item=None) => { "msg": "Debian package - sqlite" } ok: [192.168.100.11] => (item=None) => { "msg": "Debian package - python-sqlite" } TASK [Install debian packages] ***************************************************************************************************** failed: [192.168.100.11] (item=[u'sqlite5', u'python-sqlite5']) => {"changed": false, "item": ["sqlite5", "python-sqlite5"], "msg": "No package matching 'sqlite5' is available"} ...ignoring PLAY RECAP ************************************************************************************************************************* 192.168.100.11 : ok=3 changed=0 unreachable=0 failed=0
There will be situations when we want to forcibly stop execution of the Ansible playbook when an error is encounter on any of the target host(s). This is when we use the any_errors_fatal clause with a value of yes.
Create and save a playbook file called playbook-7.yaml, with the following contents, in the directory /home/alice/Ansible:
To test the above playbook playbook-7.yaml, execute the following command:
$ ansible-playbook playbook-7.yaml
The following would be a typical output:
PLAY [Setup sqlite and python-sqlite module] *************************************************************************************** TASK [Gathering Facts] ************************************************************************************************************* ok: [192.168.100.12] ok: [192.168.100.11] TASK [Install debian packages] ***************************************************************************************************** failed: [192.168.100.12] (item=[u'sqlite5', u'python-sqlite5']) => {"changed": false, "item": ["sqlite5", "python-sqlite5"], "msg": "No package matching 'sqlite5' is available"} failed: [192.168.100.11] (item=[u'sqlite5', u'python-sqlite5']) => {"changed": false, "item": ["sqlite5", "python-sqlite5"], "msg": "No package matching 'sqlite5' is available"} NO MORE HOSTS LEFT ***************************************************************************************************************** PLAY RECAP ************************************************************************************************************************* 192.168.100.11 : ok=1 changed=0 unreachable=0 failed=1 192.168.100.12 : ok=1 changed=0 unreachable=0 failed=1
To display the list of all the supported Ansible modules along with a short description, execute the following command:
$ ansible-doc -l
The following would be a typical output (truncated for brevity):
a10_server Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' server object. a10_server_axapi3 Manage A10 Networks AX/SoftAX/Thunder/vThunder devices a10_service_group Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' service groups. a10_virtual_server Manage A10 Networks AX/SoftAX/Thunder/vThunder devices' virtual servers. accelerate Enable accelerated mode on remote node aci_aaa_user Manage AAA users (aaa:User) aci_aaa_user_certificate Manage AAA user certificates (aaa:UserCert) aci_access_port_to_interface_policy_leaf_profile Manage Fabric interface policy leaf profile interface selectors (infra:HPo... aci_aep Manage attachable Access Entity Profile (AEP) objects (infra:AttEntityP, i... aci_aep_to_domain Bind AEPs to Physical or Virtual Domains (infra:RsDomP) aci_ap Manage top level Application Profile (AP) objects (fv:Ap) aci_bd Manage Bridge Domains (BD) objects (fv:BD) aci_bd_subnet Manage Subnets (fv:Subnet) aci_bd_to_l3out Bind Bridge Domain to L3 Out (fv:RsBDToOut) aci_config_rollback Provides rollback and rollback preview functionality (config:ImportP) aci_config_snapshot Manage Config Snapshots (config:Snapshot, config:ExportP) aci_contract Manage contract resources (vz:BrCP) aci_contract_subject Manage initial Contract Subjects (vz:Subj) aci_contract_subject_to_filter Bind Contract Subjects to Filters (vz:RsSubjFiltAtt) aci_domain Manage physical, virtual, bridged, routed or FC domain profiles (phys:DomP... aci_domain_to_vlan_pool Bind Domain to VLAN Pools (infra:RsVlanNs) aci_encap_pool Manage encap pools (fvns:VlanInstP, fvns:VxlanInstP, fvns:VsanInstP) aci_encap_pool_range Manage encap ranges assigned to pools (fvns:EncapBlk, fvns:VsanEncapBlk) aci_epg Manage End Point Groups (EPG) objects (fv:AEPg) aci_epg_monitoring_policy Manage monitoring policies (mon:EPGPol) aci_epg_to_contract Bind EPGs to Contracts (fv:RsCons, fv:RsProv) aci_epg_to_domain Bind EPGs to Domains (fv:RsDomAtt) aci_fabric_node Manage Fabric Node Members (fabric:NodeIdentP) aci_filter Manages top level filter objects (vz:Filter) aci_filter_entry Manage filter entries (vz:Entry) aci_firmware_source Manage firmware image sources (firmware:OSource) aci_interface_policy_fc Manage Fibre Channel interface policies (fc:IfPol) aci_interface_policy_l2 Manage Layer 2 interface policies (l2:IfPol) aci_interface_policy_leaf_policy_group Manage fabric interface policy leaf policy groups (infra:AccBndlGrp, infra... aci_interface_policy_leaf_profile Manage fabric interface policy leaf profiles (infra:AccPortP) aci_interface_policy_lldp Manage LLDP interface policies (lldp:IfPol) aci_interface_policy_mcp Manage MCP interface policies (mcp:IfPol) aci_interface_policy_port_channel Manage port channel interface policies (lacp:LagPol) aci_interface_policy_port_security Manage port security (l2:PortSecurityPol) aci_interface_selector_to_switch_policy_leaf_profile Bind interface selector profiles to switch policy leaf profiles (infra:RsA... aci_l3out_route_tag_policy Manage route tag policies (l3ext:RouteTagPol) ... ... ...
To display the detailed documentation on a specific Ansible module, say for example the module debug, execute the following command:
$ ansible-doc debug
The following would be a typical output (truncated for brevity):
> DEBUG (/usr/lib/python2.7/dist-packages/ansible/modules/utilities/logic/debug.py) This module prints statements during execution and can be useful for debugging variables or expressions without necessarily halting the playbook. Useful for debugging together with the 'when:' directive. This module is also supported for Windows targets. * note: This module has a corresponding action plugin. OPTIONS (= is mandatory): - msg The customized message that is printed. If omitted, prints a generic message. [Default: Hello world!] - var A variable name to debug. Mutually exclusive with the 'msg' option. [Default: (null)] - verbosity A number that controls when the debug is run, if you set to 3 it will only run debug when -vvv or above [Default: 0] version_added: 2.1 NOTES: * This module is also supported for Windows targets. AUTHOR: Dag Wieers (@dagwieers), Michael DeHaan METADATA: status: - stableinterface supported_by: core EXAMPLES: # Example that prints the loopback address and gateway for each host - debug: msg: "System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}" - debug: msg: "System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}" when: ansible_default_ipv4.gateway is defined - shell: /usr/bin/uptime register: result - debug: var: result verbosity: 2 - name: Display all variables/facts known for a host debug: var: hostvars[inventory_hostname] verbosity: 4
References