In the previous post, we looked at running Ad-Hoc commands on remote systems. In this post, we will see how can a playbook (a set of instructions and data in yaml format) be written to execute a set of tasks. We will continue to use the inventory file that we defined in the previous post.
admin_server1 admin_server2 [application_servers] server_java_app1 server_php_app1 server_php_app2 [DB_servers] server_oracle_1 primary_dns='172.16.1.1' secondary_dns='172.16.1.2' server_mysql_1 [application_servers:children] thirdparty_applications secondparty_applications [thirdparty_applications] ftp_server_app1 monitoring_server_app [secondparty_applications] marketing_server_app [application_servers:vars] primary_dns=10.65.1.4 [DB_servers:vars] primary_dns=11.65.1.4 [all:vars] secondary_dns=172.65.1.3
Let’s look at the below example to understand the working of Ansible playbook. The playbook begins with three dashes, signaling the beginning of the document. This is followed by some yaml formatted data which are ‘hosts specification’, followed by a ‘set of tasks’ that should run on these hosts. We also see a ‘vars’ section where in, some variables are defined.
We already know from the previous post that, variables can be defined in the inventory file specific to hosts and groups. Variables can also be defined in a playbook. Variables defined in a playbook takes precedence over the inventory variables. The below example define a different primary and secondary DNS than the ones defined in the inventory. A typical task block includes a name section to describe the task, followed by the name of the ansible module with options. In the below example, we use the shell module to update the DNS and the service module to disable the NetworkManager service. These tasks are run on the group “application_servers” only and the user ID used to ssh to these machines is defined as “admin”.
--- - hosts: application_servers vars: primary_dns: 11.69.1.1 secondary_dns: 172.68.1.7 remote_user: admin tasks: - name: Update DNS records on remote systems shell: | echo "{{ primary_dns }}" > /etc/resolv.conf echo "{{ secondary_dns }}" >> /etc/resolv.conf echo "search unixutils.com" >> /etc/resolv.conf - name: stop and disable network manager service service: name: NetworkManager state: stopped disabled: yes
Running an Ansible playbook
Command: ‘ansible-playbook path/to/playbooks/myplaybook.yml -u username -k -K’
(we already know that -k and -K makes Ansible prompt for password and sudo password respectively)
Playbooks can contain multiple plays. You may have a playbook that targets first the application_servers, and then the DB_servers servers. For example:
--- - hosts: application_servers remote_user: root tasks: - name: ensure wget package is at the latest version yum: name: wget state: latest - hosts: DB_servers remote_user: admin tasks: - name: ensure openjdk-devel is at the latest version yum: name: java-1.8.0-openjdk-devel state: latest become: yes - name: ensure that firewalld is started service: name: firewalld state: started become: yes
NOTE: ‘become’ parameter when set to yes, makes the task run with sudo root privileges. we can also make the task run as a specific user by specifying the username. example ‘become: admin’. When the ‘become’ keyword is not specified, the task by default runs with the username that was used to ssh to the remote system.
Now let’s save this file as ‘test.yaml’ and run it on our inventory file ‘myinventory.ini’
Output:
[admin@unixutils ~]# ansible-playbook -i myinventory.ini test.yaml -k -K SSH password: SUDO password[defaults to SSH password]: PLAY [application_servers] ************************************************************************************************************** TASK [Gathering Facts] ****************************************************************************************************************** ok: [server_java_app1] ok: [server_php_app1] ok: [server_php_app2] ok: [marketing_server_app] ok: [monitoring_server_app] ok: [ftp_server_app1] TASK [ensure wget package is at the latest version] ************************************************************************************* changed: [marketing_server_app] ok: [ftp_server_app1] ok: [monitoring_server_app] PLAY [DB_servers] *********************************************************************************************************************** TASK [Gathering Facts] ****************************************************************************************************************** ok: [server_oracle_1] ok: [server_mysql_1] PLAY RECAP ****************************************************************************************************************************** ftp_server_app1 : ok=2 changed=0 unreachable=0 failed=0 marketing_server_app : ok=2 changed=1 unreachable=0 failed=0 monitoring_server_app : ok=2 changed=0 unreachable=0 failed=0 server_java_app1 : ok=2 changed=0 unreachable=0 failed=0 server_mysql_1 : ok=1 changed=1 unreachable=0 failed=0 server_oracle_1 : ok=1 changed=1 unreachable=0 failed=0 server_php_app1 : ok=2 changed=0 unreachable=0 failed=0 server_php_app2 : ok=2 changed=0 unreachable=0 failed=0 [admin@unixutils ~]#
Ansible reports the status of the tasks for each host. ‘ok’ indicates that a task was not run since the expected result of a task is already satisfied and in place and hence successful. Whereas, a ‘Changed’ status indicates that the task was run and it was successful.
In this post we looked at how to write a set of tasks in a single playbook. Ansible allows us to create ‘roles’ which are a set of tasks that can be re-used in multiple playbooks. This not only makes the code reusable but also helps in organizing the tasks. For instance, you can write an “add user” role to create user IDs, which can be imported into different playbooks to create a different set of users defined separately for the playbook. The idea of the role is to have the functionality of the code independent of the data that it uses to run the task. In future posts, we will look at how to define a role with examples.