Playbooks are the files where the Ansible code is written. Playbooks are written in YAML format. YAML stands for Yet Another Markup Language. Playbooks are one of the core features of Ansible and tell Ansible what to execute. They are like a to-do list for Ansible that contains a list of tasks.
Playbooks are a completely different way to use Ansible than in ad-hoc task execution mode and thus are particularly powerful. That was enough for the introduction, let’s do some practical stuff and write our first Playbook.
Under our project named “superman”, I have created a folder named playbook exercise and we are going to create a file named web.yml which is the name of the playbook with the below command.
vim web.yml
In playbooks (yml is the extension for YAML file) where:
“-” represent a list in Yaml
A list can have another list or can have a dictionary.
Here is example we have 2 plays
Let’s create a web.yml as discussed above
#################web.yml#######################
---
- hosts: websrvgrp
become: yes
tasks:
- name: Install apache http SVC
yum:
name: httpd
state: present
- name: Start and enable Apache http SVC
service:
name: httpd
state: started
enabled: yes
Let’s understand what it is doing
Till now as we were using 2 centOS machines as web servers. Let’s now add another webserver which is an Ubuntu web server.
So above in this playbook we have one play (play starts with -hosts). This is going to install apache service on all the webservers as we have given the web servers group in hosts and then will start and enable the service on all web servers. So it contains 2 tasks.
- Playbooks are pretty much self-explanatory.
- To execute playbook, we use the below command
ansible-playbook web.yml
We will notice that httpd service is up and running on websrv01 and websrv02 but not on websrv03(commenting it now) as it is a ubuntu operating system. Later we will see how to tackle this scenario when we will jump to conditions.
Let’s execute the command again and see what happens:
On websrv01 we can see that we have executed 2 tasks:
- Installing httpd service
- Enabling it
However, there are 3 tasks shown in the response available.
Ansible setup module and fact variables
In the above screenshot, we can see the default task/first is being executed for all the web servers and that task is called Gathering Facts. Gathering facts is a default task and it generates fact variables.
Before ansible executes any task on the target machine it executes a module called setup module and this module collects all information about the target machine. We can also run the setup module directly on any server. Suppose for web server 01 we can run the setup module with the below command:
ansible -m setup websrv01
Response
ubuntu@ip-172-31-20-76:~/superman$ ansible -m setup websrv01
websrv01 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"172.31.34.188"
],
"ansible_all_ipv6_addresses": [
"fe80::803:edff:fea6:5756"
],
"ansible_apparmor": {
"status": "disabled"
},
"ansible_architecture": "x86_64",
"ansible_bios_date": "08/24/2006",
"ansible_bios_version": "4.2.amazon",
"ansible_cmdline": {
"BOOT_IMAGE": "/boot/vmlinuz-3.10.0-957.1.3.el7.x86_64",
"LANG": "en_US.UTF-8",
"console": "ttyS0,115200",
"crashkernel": "auto",
"ro": true,
"root": "UUID=f41e390f-835b-4223-a9bb-9b45984ddf8d"
},
"ansible_date_time": {
"date": "2020-02-09",
"day": "09",
"epoch": "1581276123",
"hour": "19",
"iso8601": "2020-02-09T19:22:03Z",
"iso8601_basic": "20200209T192203762837",
"iso8601_basic_short": "20200209T192203",
"iso8601_micro": "2020-02-09T19:22:03.762919Z",
"minute": "22",
"month": "02",
"second": "03",
"time": "19:22:03",
"tz": "UTC",
"tz_offset": "+0000",
"weekday": "Sunday",
"weekday_number": "0",
"weeknumber": "05",
"year": "2020"
},
"ansible_default_ipv4": {
"address": "172.31.34.188",
"alias": "eth0",
"broadcast": "172.31.47.255",
"gateway": "172.31.32.1",
"interface": "eth0",
"macaddress": "0a:03:ed:a6:57:56",
"mtu": 9001,
"netmask": "255.255.240.0",
"network": "172.31.32.0",
"type": "ether"
},
"ansible_default_ipv6": {},
"ansible_device_links": {
"ids": {},
"labels": {},
"masters": {},
"uuids": {
"xvda1": [
"f41e390f-835b-4223-a9bb-9b45984ddf8d"
]
}
},
"ansible_devices": {
"xvda": {
"holders": [],
"host": "",
"links": {
"ids": [],
"labels": [],
"masters": [],
"uuids": []
},
"model": null,
"partitions": {
"xvda1": {
"holders": [],
"links": {
"ids": [],
"labels": [],
"masters": [],
"uuids": [
"f41e390f-835b-4223-a9bb-9b45984ddf8d"
]
},
"sectors": "16775168",
"sectorsize": 512,
"size": "8.00 GB",
"start": "2048",
"uuid": "f41e390f-835b-4223-a9bb-9b45984ddf8d"
}
},
"removable": "0",
"rotational": "0",
"sas_address": null,
"sas_device_handle": null,
"scheduler_mode": "deadline",
"sectors": "16777216",
"sectorsize": "512",
"size": "8.00 GB",
"support_discard": "0",
"vendor": null,
"virtual": 1
}
},
"ansible_distribution": "CentOS",
"ansible_distribution_file_parsed": true,
"ansible_distribution_file_path": "/etc/redhat-release",
"ansible_distribution_file_variety": "RedHat",
"ansible_distribution_major_version": "7",
"ansible_distribution_release": "Core",
"ansible_distribution_version": "7.6",
"ansible_dns": {
"nameservers": [
"172.31.0.2"
],
"search": [
"us-east-2.compute.internal"
]
},
"ansible_domain": "us-east-2.compute.internal",
"ansible_effective_group_id": 0,
"ansible_effective_user_id": 0,
"ansible_env": {
"HOME": "/root",
"LANG": "C",
"LC_ALL": "C",
"LC_MESSAGES": "C",
"LOGNAME": "root",
"LS_COLORS": "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:",
"MAIL": "/var/mail/devops",
"PATH": "/sbin:/bin:/usr/sbin:/usr/bin",
"PWD": "/home/devops",
"SHELL": "/bin/bash",
"SHLVL": "1",
"SUDO_COMMAND": "/bin/sh -c echo BECOME-SUCCESS-kqwxxiryqpmggrtiiknmyptaijajuzxf ; /usr/bin/python /home/devops/.ansible/tmp/ansible-tmp-1581276122.5-11152747352541/AnsiballZ_setup.py",
"SUDO_GID": "1001",
"SUDO_UID": "1001",
"SUDO_USER": "devops",
"TERM": "xterm",
"USER": "root",
"USERNAME": "root",
"XDG_SESSION_ID": "8",
"_": "/usr/bin/python"
},
"ansible_eth0": {
"active": true,
"device": "eth0",
"features": {
"busy_poll": "off [fixed]",
"fcoe_mtu": "off [fixed]",
"generic_receive_offload": "on",
"generic_segmentation_offload": "on",
"highdma": "off [fixed]",
"hw_tc_offload": "off [fixed]",
"l2_fwd_offload": "off [fixed]",
"large_receive_offload": "off [fixed]",
"loopback": "off [fixed]",
"netns_local": "off [fixed]",
"ntuple_filters": "off [fixed]",
"receive_hashing": "off [fixed]",
"rx_all": "off [fixed]",
"rx_checksumming": "on [fixed]",
"rx_fcs": "off [fixed]",
"rx_gro_hw": "off [fixed]",
"rx_udp_tunnel_port_offload": "off [fixed]",
"rx_vlan_filter": "off [fixed]",
"rx_vlan_offload": "off [fixed]",
"rx_vlan_stag_filter": "off [fixed]",
"rx_vlan_stag_hw_parse": "off [fixed]",
"scatter_gather": "on",
"tcp_segmentation_offload": "on",
"tx_checksum_fcoe_crc": "off [fixed]",
"tx_checksum_ip_generic": "off [fixed]",
"tx_checksum_ipv4": "on [fixed]",
"tx_checksum_ipv6": "off [requested on]",
"tx_checksum_sctp": "off [fixed]",
"tx_checksumming": "on",
"tx_fcoe_segmentation": "off [fixed]",
"tx_gre_csum_segmentation": "off [fixed]",
"tx_gre_segmentation": "off [fixed]",
"tx_gso_partial": "off [fixed]",
"tx_gso_robust": "on [fixed]",
"tx_ipip_segmentation": "off [fixed]",
"tx_lockless": "off [fixed]",
"tx_nocache_copy": "off",
"tx_scatter_gather": "on",
"tx_scatter_gather_fraglist": "off [fixed]",
"tx_sctp_segmentation": "off [fixed]",
"tx_sit_segmentation": "off [fixed]",
"tx_tcp6_segmentation": "off [requested on]",
"tx_tcp_ecn_segmentation": "off [fixed]",
"tx_tcp_mangleid_segmentation": "off",
"tx_tcp_segmentation": "on",
"tx_udp_tnl_csum_segmentation": "off [fixed]",
"tx_udp_tnl_segmentation": "off [fixed]",
"tx_vlan_offload": "off [fixed]",
"tx_vlan_stag_hw_insert": "off [fixed]",
"udp_fragmentation_offload": "off [fixed]",
"vlan_challenged": "off [fixed]"
},
"hw_timestamp_filters": [],
"ipv4": {
"address": "172.31.34.188",
"broadcast": "172.31.47.255",
"netmask": "255.255.240.0",
"network": "172.31.32.0"
},
"ipv6": [
{
"address": "fe80::803:edff:fea6:5756",
"prefix": "64",
"scope": "link"
}
],
"macaddress": "0a:03:ed:a6:57:56",
"module": "xen_netfront",
"mtu": 9001,
"pciid": "vif-0",
"promisc": false,
"timestamping": [
"rx_software",
"software"
],
"type": "ether"
},
"ansible_fibre_channel_wwn": [],
"ansible_fips": false,
"ansible_form_factor": "Other",
"ansible_fqdn": "ip-172-31-34-188.us-east-2.compute.internal",
"ansible_hostname": "ip-172-31-34-188",
"ansible_hostnqn": "",
"ansible_interfaces": [
"lo",
"eth0"
],
"ansible_is_chroot": false,
"ansible_iscsi_iqn": "",
"ansible_kernel": "3.10.0-957.1.3.el7.x86_64",
"ansible_kernel_version": "#1 SMP Thu Nov 29 14:49:43 UTC 2018",
"ansible_lo": {
"active": true,
"device": "lo",
"features": {
"busy_poll": "off [fixed]",
"fcoe_mtu": "off [fixed]",
"generic_receive_offload": "on",
"generic_segmentation_offload": "on",
"highdma": "on [fixed]",
"hw_tc_offload": "off [fixed]",
"l2_fwd_offload": "off [fixed]",
"large_receive_offload": "off [fixed]",
"loopback": "on [fixed]",
"netns_local": "on [fixed]",
"ntuple_filters": "off [fixed]",
"receive_hashing": "off [fixed]",
"rx_all": "off [fixed]",
"rx_checksumming": "on [fixed]",
"rx_fcs": "off [fixed]",
"rx_gro_hw": "off [fixed]",
"rx_udp_tunnel_port_offload": "off [fixed]",
"rx_vlan_filter": "off [fixed]",
"rx_vlan_offload": "off [fixed]",
"rx_vlan_stag_filter": "off [fixed]",
"rx_vlan_stag_hw_parse": "off [fixed]",
"scatter_gather": "on",
"tcp_segmentation_offload": "on",
"tx_checksum_fcoe_crc": "off [fixed]",
"tx_checksum_ip_generic": "on [fixed]",
"tx_checksum_ipv4": "off [fixed]",
"tx_checksum_ipv6": "off [fixed]",
"tx_checksum_sctp": "on [fixed]",
"tx_checksumming": "on",
"tx_fcoe_segmentation": "off [fixed]",
"tx_gre_csum_segmentation": "off [fixed]",
"tx_gre_segmentation": "off [fixed]",
"tx_gso_partial": "off [fixed]",
"tx_gso_robust": "off [fixed]",
"tx_ipip_segmentation": "off [fixed]",
"tx_lockless": "on [fixed]",
"tx_nocache_copy": "off [fixed]",
"tx_scatter_gather": "on [fixed]",
"tx_scatter_gather_fraglist": "on [fixed]",
"tx_sctp_segmentation": "on",
"tx_sit_segmentation": "off [fixed]",
"tx_tcp6_segmentation": "on",
"tx_tcp_ecn_segmentation": "on",
"tx_tcp_mangleid_segmentation": "on",
"tx_tcp_segmentation": "on",
"tx_udp_tnl_csum_segmentation": "off [fixed]",
"tx_udp_tnl_segmentation": "off [fixed]",
"tx_vlan_offload": "off [fixed]",
"tx_vlan_stag_hw_insert": "off [fixed]",
"udp_fragmentation_offload": "on",
"vlan_challenged": "on [fixed]"
},
"hw_timestamp_filters": [],
"ipv4": {
"address": "127.0.0.1",
"broadcast": "host",
"netmask": "255.0.0.0",
"network": "127.0.0.0"
},
"ipv6": [
{
"address": "::1",
"prefix": "128",
"scope": "host"
}
],
"mtu": 65536,
"promisc": false,
"timestamping": [
"rx_software",
"software"
],
"type": "loopback"
},
"ansible_local": {},
"ansible_lsb": {},
"ansible_machine": "x86_64",
"ansible_machine_id": "05cb8c7b39fe0f70e3ce97e5beab809d",
"ansible_memfree_mb": 658,
"ansible_memory_mb": {
"nocache": {
"free": 847,
"used": 142
},
"real": {
"free": 658,
"total": 989,
"used": 331
},
"swap": {
"cached": 0,
"free": 0,
"total": 0,
"used": 0
}
},
"ansible_memtotal_mb": 989,
"ansible_mounts": [
{
"block_available": 1817589,
"block_size": 4096,
"block_total": 2094336,
"block_used": 276747,
"device": "/dev/xvda1",
"fstype": "xfs",
"inode_available": 4167226,
"inode_total": 4193792,
"inode_used": 26566,
"mount": "/",
"options": "rw,seclabel,relatime,attr2,inode64,noquota",
"size_available": 7444844544,
"size_total": 8578400256,
"uuid": "f41e390f-835b-4223-a9bb-9b45984ddf8d"
}
],
"ansible_nodename": "ip-172-31-34-188.us-east-2.compute.internal",
"ansible_os_family": "RedHat",
"ansible_pkg_mgr": "yum",
"ansible_proc_cmdline": {
"BOOT_IMAGE": "/boot/vmlinuz-3.10.0-957.1.3.el7.x86_64",
"LANG": "en_US.UTF-8",
"console": [
"tty0",
"ttyS0,115200n8",
"ttyS0,115200"
],
"crashkernel": "auto",
"ro": true,
"root": "UUID=f41e390f-835b-4223-a9bb-9b45984ddf8d"
},
"ansible_processor": [
"0",
"GenuineIntel",
"Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz"
],
"ansible_processor_cores": 1,
"ansible_processor_count": 1,
"ansible_processor_threads_per_core": 1,
"ansible_processor_vcpus": 1,
"ansible_product_name": "HVM domU",
"ansible_product_serial": "ec2b0d2a-f66f-0f3d-80ce-f42b61c10ba2",
"ansible_product_uuid": "EC2B0D2A-F66F-0F3D-80CE-F42B61C10BA2",
"ansible_product_version": "4.2.amazon",
"ansible_python": {
"executable": "/usr/bin/python",
"has_sslcontext": true,
"type": "CPython",
"version": {
"major": 2,
"micro": 5,
"minor": 7,
"releaselevel": "final",
"serial": 0
},
"version_info": [
2,
7,
5,
"final",
0
]
},
"ansible_python_version": "2.7.5",
"ansible_real_group_id": 0,
"ansible_real_user_id": 0,
"ansible_selinux": {
"config_mode": "enforcing",
"mode": "enforcing",
"policyvers": 31,
"status": "enabled",
"type": "targeted"
},
"ansible_selinux_python_present": true,
"ansible_service_mgr": "systemd",
"ansible_ssh_host_key_ecdsa_public": "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOhlDSZYvcL5I+TmxCLoj/k3cg+Euy88gzXkQtMVQA58NSwKUx9MSUFv3B+HTMrfGVtnBSCIdIuON45PmlMoqGo=",
"ansible_ssh_host_key_ed25519_public": "AAAAC3NzaC1lZDI1NTE5AAAAIIhFv3zJkEgmJyCSlhUodOtLrPbDaBWvtGkj3pBuw9Dq",
"ansible_ssh_host_key_rsa_public": "AAAAB3NzaC1yc2EAAAADAQABAAABAQClle+xrS0le1qzL5xjQ1ZgXZTiydFe9wrReyJPAW3oZzjpLiIqrj8l6rDMwIJtBLvm0JCOQxjgGCtWCtaZaSchmqQDSCymBGK04tmeylgvYOcz7G2MYTC6w0mdqE20szqhRxXnzaKhLgZTZ4NRJJnQvV8f/zlFLprWN0o/wjgidmczxMZX/UnR67lwtL4NAwNBQwVRJK6bhhfX7KfglkoEmX/Dks5MH8Z1SCIhzVoNkfZWidKsUi1Ptc/nTSz5tGSH9sr9xXcuj3TGFoBIsrvk4obLfYiEDIqSsAYlpBnrrl6hv9vasKr+nNzUoRy0sLoZxJj5e9unSRrxKB+qKJAl",
"ansible_swapfree_mb": 0,
"ansible_swaptotal_mb": 0,
"ansible_system": "Linux",
"ansible_system_capabilities": [
"cap_chown",
"cap_dac_override",
"cap_dac_read_search",
"cap_fowner",
"cap_fsetid",
"cap_kill",
"cap_setgid",
"cap_setuid",
"cap_setpcap",
"cap_linux_immutable",
"cap_net_bind_service",
"cap_net_broadcast",
"cap_net_admin",
"cap_net_raw",
"cap_ipc_lock",
"cap_ipc_owner",
"cap_sys_module",
"cap_sys_rawio",
"cap_sys_chroot",
"cap_sys_ptrace",
"cap_sys_pacct",
"cap_sys_admin",
"cap_sys_boot",
"cap_sys_nice",
"cap_sys_resource",
"cap_sys_time",
"cap_sys_tty_config",
"cap_mknod",
"cap_lease",
"cap_audit_write",
"cap_audit_control",
"cap_setfcap",
"cap_mac_override",
"cap_mac_admin",
"cap_syslog",
"35",
"36+ep"
],
"ansible_system_capabilities_enforced": "True",
"ansible_system_vendor": "Xen",
"ansible_uptime_seconds": 4261,
"ansible_user_dir": "/root",
"ansible_user_gecos": "root",
"ansible_user_gid": 0,
"ansible_user_id": "root",
"ansible_user_shell": "/bin/bash",
"ansible_user_uid": 0,
"ansible_userspace_architecture": "x86_64",
"ansible_userspace_bits": "64",
"ansible_virtualization_role": "guest",
"ansible_virtualization_type": "xen",
"discovered_interpreter_python": "/usr/bin/python",
"gather_subset": [
"all"
],
"module_setup": true
},
"changed": false
}
We can see that it generates a json output with all the variables that contains lots of information about the target machine. This json output contains lots of variables and these are called Fact Variables.
Thus, based on the values of fact variables we can take decisions inside playbook while writing different tasks.
So we can make use of ansible_os_family variable and depending on its value we can decide which module we can use to install the package on different types of target machines. Now, let’s write one more play for the database server (Centos7) as well. In this play, we are going to install MySQL service, start and enable this service. The package name for MySQL is maria-db.
Below is the updated playbook.
---
- hosts: websrvgrp
become: yes
tasks:
- name: Install apache http SVC
yum:
name: httpd
state: present
- name: Start and enable Apache http SVC
service:
name: httpd
state: started
enabled: yes
- hosts: dbsrvgrp
become: yes
tasks:
- name: Install mariadb SVC
package:
name: mariadb-server
state: present
- name: Start and enable mariadb SVC
service:
name: mariadb
state: started
enabled: yes
Let’s execute this playbook.
ansible-playbook playbook-exercise/web.yml
Maria db svc should be installed and enabled successfully.
Question: How to catch issues in the playbooks
1.ansible-playbook {{playbook name}} --syntax-check
2.ansible-playbook {{playbook name}} -C ### where -C stands for Dry run
However, dry run is not a guarantee that it will catch an error, it may or may not catch error
3. Increase the verbosity. There are max 4 level of verbosity
ansible-playbook -vvvv {{playbook name}}