How to Build (And Actually Understand) a Solid Ansible Playbook

Published:16 February 2022 - 9 min. read

Azure Cloud Labs: these FREE, on‑demand Azure Cloud Labs will get you into a real‑world environment and account, walking you through step‑by‑step how to best protect, secure, and recover Azure data.

Are you new to Ansible and struggling to create your first Ansible Playbook? Going through an Ansible playbook example would be an excellent start to get your head over how an Ansible playbook works. Lucky for you, this tutorial has got you covered.

In this tutorial, you’ll learn how to build and create Ansible playbooks for service deployment while understanding the breakdown of what makes an Ansible playbook as a whole.

Without further delay, read on and start building!

Prerequisites

This tutorial comprises step-by-step instructions. If you’d like to follow along, be sure you have the following in place:

  • A remote Linux computer to test the tomcat installation – This tutorial uses Ubuntu 20.04.3 LTS as the remote node.
  • An inventory file and one or more hosts configured to run Ansible commands and playbooks. The remote Linux computer is called myserver, and this tutorial uses an inventory group called web.
  • Python v3.6 or later installed both on your Ansible controller host and the remote node machine – This tutorial uses Python v3.9 on an Ubuntu machine.

Declaring a Basic Ansible Playbook

Ansible playbooks let you deploy complex applications, offer reusable configuration management, multi-machine deployments, and perform multiple tasks on repetition. Ansible playbooks are written in YAML format containing multiple tasks and executed in sequential order.

To better understand how an Ansible playbook works, you’ll declare an Ansible playbook to install Apache on your remote node.

1. Log in to the Ansible Controller host using your favorite SSH client.

2. Next, create a file named apache.yml in your home directory with your preferred text editor. Populate the file with the below content (playbook), which starts and enables the Apache service.

The code below deploys the Ansible task to install Apache to the destination server address (web) with the remote user (ubuntu).

# Playbook apache.yml
---
- name: Installing Apache service 
  hosts: my_app_servers                                  # Define all the hosts
  remote_user: ubuntu                                    # Remote_user is ubuntu
  # Defining the Ansible task
  tasks:                                                 
  - name: Install the Latest Apache
    apt:
      name: httpd
      state: latest

3. Run the below ansible-playbook command to verify the playbook (apache.yml) to catch syntax errors (--syntax-check) and other problems.

Remember always to do this syntax check before you run your Ansible playbook. Doing so lets you avoid accidentally messing things up with your project.

ansible-playbook apache.yml --syntax-check

As you can see below, there are no errors displayed, which confirms that the Ansible playbook (apache.yml) has no syntax issues and can be executed safely.

Verifying Syntax Errors in Ansible Playbook (apache.yml)
Verifying Syntax Errors in Ansible Playbook (apache.yml)

4. Finally, run the command below to execute the Ansible playbook (apache.yml).

ansible-playbook apache.yml

If you see an OK status that specifies the Ansible task doesn’t need to perform any work. But if you get the changed status, the task is executed successfully on the remote node.

Executing the Ansible Playbook
Executing the Ansible Playbook

Preventing Ansible Playbook from Stopping Executing Tasks

Previously you learned how to create a basic Ansible playbook. But while you execute the playbook, Ansible may receive a non-zero return code from a command. Or a failure from a module stopping the playbook from executing further.

To prevent the failures of stopping other tasks from executing, consider using the error handling feature in the Ansible playbook. Error handling allows you to operate even when any task fails using rescue and always functions and the output you want.

Create a new playbook named main.yml and copy/paste the below code. The below Ansible Playbook performs various tasks while handling failed tasks with the Ansible built-in debug module (ansible.builtin.debug)

---
- name: update web servers
# Testing the Ansible Playbook on your local machine
  hosts: localhost
  remote_user: ubuntu
  tasks:
  - name: Ansible block to perform error handling 
    block:
 
      - name: Ansible task prints a message I execute normally
        ansible.builtin.debug:
          msg: 'I execute normally'
 
      - name: Ansible task will fail
        ansible.builtin.command: /bin/false
 
      - name: Ansible task will not proceed due to the previous task failing
        ansible.builtin.debug:
          msg: 'I never execute, '
 
    rescue:
      - name: Ansible task Print when errors
        ansible.builtin.debug:
          msg: 'I caught an error, can do stuff here to fix it
 
    always:
      - name: Ansible task will always get executed
        ansible.builtin.debug:
          msg: 'This executes always'

Now, run the below command to execute the playbook (main.yml)

ansible-playbook main.yml

Below, you’ll notice that rescue functions in the Ansible block allowed Ansible tasks to continue and run, while other tasks terminated as they failed:

  • The first task executed, printing a message that says I execute normally.
  • The second task fails as the command specified is incorrect (ansible.builtin.command: /bin/false).
  • The third task won’t proceed because the second task failed.
  • The fourth and fifth tasks always run as they contain rescue and always functions.
Executing the main.yml Playbook
Executing the main.yml Playbook

Defining Ansible Role Tomcat for an Ansible Playbook

At this point, you now have a basic understanding of how to build Ansible playbooks to execute simple tasks. But when you need to perform a lot of deployment work or multiple tasks that can repeat many times over, then consider using Ansible roles.

Using Ansible roles is good practice because the content in roles can be reused and shared with others. You’ll see how Ansible roles work in action by creating an Ansible-playbook to deploy Apache Tomcat service on your remote node. But first, you’ll create an Ansible role folder.

1. Open the terminal in your Ansible controller host, then run the following commands to create a directory called ~/ansible_playbook_demo and switch to that directory. This directory will contain the playbook and all the required configuration files you’ll need to deploy Apache Tomcat.

mkdir ~/ansible_playbook_demo
cd ~/ansible_playbook_demo

2. Next, run the command below to create another directory named roles inside the ~/ansible_playbook_demo directory, and switch to that directory.

The ~/ansible_playbook_demo/roles directory will contain the Tomcat role you need to deploy.

By default, Ansible looks for roles in two locations in a directory called roles/ within the directory where playbook resides or in the /etc/Ansible/roles. If you wish to store roles at different paths, declare the paths using the - role: parameter in the playbook.

mkdir -p roles && cd roles

3. Finally, run the command below to create the folders required by the tomcat role.

The p flag tells the mkdir command to create the parent directory (tomcat) and the folders such as tasks, handlers, defaults, vars, and templates. Each of these folders is common for every Ansible role and will eventually contain a main.yml file to deploy the Tomcat role.

You can create the role with any name within the roles directory.

# task directory - contains the list of tasks that a role needs to execute
# handlers directory - is like normal tasks in an Ansible playbook. 
	# but they run only if the task contains a “notify” directive. 
# library directory - store any plugins or modules such as Python
# files directory - copy any files from the Ansible controller host to other nodes
# vars directory - contains all the variables you need to use in the main.yml file
# defaults directory - stores the variables that required by the role to execute
# templates - is a file that contains all your configuration parameters, 
	# but the dynamic values are given as variables in the Ansible  
mkdir -p tomcat/{tasks, handlers, defaults, vars, templates}

4. Finally, run the tree command below to verify all required folders in the roles directory.

tree

At this point, you should have the file structure shown below.

Verifying the files and directories under the roles folder
Verifying the files and directories under the roles folder

Building an Ansible Playbook

Now that you have set up the Ansible role tomcat folder, you’ll add a main.yml files inside each subdirectory of the ~/ansible_playbook_demo/roles/tomcat/ directory.

Ansible playbook automatically picks the values from the correct file (main.yml) from the relevant folder and deploys the Apache Tomcat on the remote node.

1. Open your favorite text editor, and create a file named main.yml in the tasks directory.

Populate the file with the below content (playbook), which starts and enables the Tomcat service.

---
# Installing the Java (Open Jdk) 
- name: Install Java 1.8
  apt: name=openjdk-8-jdk

# Adding the group tomcat
- name: add group "tomcat"
  group: name=tomcat

# Adding the user tomcat along with Ansible When condition
- name: add user "tomcat"
  user: name=tomcat group=tomcat home=/usr/share/tomcat createhome=no
  become: True
  become_method: sudo
  when: ansible_os_family == "Debian"

# Downloading the tomcat package using the Ansible variables
- name: Download Tomcat
  get_url: 
     url:  "{{ tomcat_download_url }}"
     dest: "{{ tomcat_download_location }}"

# Creating the tomcat directory
- name: Create a tomcat directory
  file:
    path: /usr/share/tomcat
    state: directory
    owner: tomcat
    group: tomcat

# Extracting the tomcat archive 
- name: Extract tomcat archive
  unarchive:
    src: "{{ tomcat_download_location }}"
    dest: /usr/share/tomcat
    owner: tomcat
    group: tomcat
    remote_src: yes
    extra_opts: "--strip-components=1"
    creates: /usr/share/tomcat/bin

# Copying the Ansible Jinja template (tomcat.service.j2) file to the destination node
- name: Copy tomcat service file
  template:
    src: templates/tomcat.service.j2
    dest: /etc/systemd/system/tomcat.service
  

# Starting and Enabling the tomcat service on the destination node.
- name: Start and enable tomcat
  service:
    daemon_reload: yes
    name: tomcat
    state: started
    enabled: yes
  when: ansible_service_mgr == "systemd"

2. Next, create another file named main.yml in the vars directory and copy/paste the below code.

The code below contains the variables tomcat_download_url and tomcat_download_location. These variables are set with assigned values that the Ansible role picks from the main.yml file in the tasks directory while running the playbook.

tomcat_download_url: https://dlcdn.apache.org/tomcat/tomcat-10/v10.0.14/bin/apache-tomcat-10.0.14.tar.gz
tomcat_download_location: /tmp/apache-tomcat-10.0.14.tar.gz

3. Lastly, create a template file named tomcat.service.j2 in the templates directory and populate with the below code.

Below is the tomcat.service.j2 template that Ansible renders when you execute the playbook. Ansible then copies the data from this template to the destination node in the /etc/systemd/system/tomcat.service directory.

# Unit defines the name of the service you need to deploy, which is Tomcat. 
[Unit]
Description=Tomcat
After=network.target

# Service section allows you to define the type of service.
# Forking allows you to start the tomcat service as a system start-up 
# and keeps the service running in the background.
[Service]
Type=forking

User=tomcat
Group=tomcat

# Environment contains the list of environment variables, such as 
# JAVA_HOME, etc., that Tomcat requires to run.

Environment=JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
Environment=CATALINA_HOME=/usr/share/tomcat
Environment=CATALINA_BASE=/usr/share/tomcat
Environment=CATALINA_PID=/usr/share/tomcat/temp/tomcat.pid

ExecStart=/usr/share/tomcat/bin/startup.sh
ExecStop=/usr/share/tomcat/bin/shutdown.sh

# Install section defines if the specified Unit is enabled or disabled
# WantedBY automatically starts the Tomcat at boot up.
[Install]
	WantedBy=multi-user.target

Deploying an Ansible Playbook with the tomcat Ansible Role

You’ve managed to set up and configure the Ansible role file structure and files/folder within the Tomcat Ansible role. But unless you run the playbook, the role you created is not doing anything, so you’ll quickly deploy the Ansible role using the Ansible playbook.

Create a YML file with a name you prefer, and copy/paste the below code. For this example, the file is named ~/ansible_playbook_demo/tomcat-setup.yml.

The code below deploys the Ansible role (tomcat) that you configured in the “Defining Ansible Role Tomcat for an Ansible Playbook” section to the destination server address (web). Notice you deploy the Ansible role with the remote user (ubuntu) with admin access.

You can use roles in three ways: play level with the roles option, tasks level with include_role, and import_role options.

- name: Tomcat deployment playbook
  hosts: web
  remote_user: ubuntu
  roles:
    - tomcat

Validating the Ansible playbook using the --check flag with the ansible-playbook command shown below is a good practice before actually executing the playbook. The --check flag tells Ansible to perform a simulation without running the playbook.
ansible-playbook tomcat-setup.yml –check

Now, run the below command to execute the playbook

ansible-playbook tomcat-setup.yml
Deploying the Ansible Playbook with the Tomcat Ansible Role
Deploying the Ansible Playbook with the Tomcat Ansible Role

Verifying the Tomcat Service Works on a Remote Machine

You’ve executed your Ansible playbook and deployed Apache Tomcat on the remote machine. So the next step is to test if the Tomcat service is running successfully. You’ll verify the Tomcat service’s status and see if you can access the Apache Tomcat landing page.

Open the terminal in the remote node, and execute the below command to verify the Tomcat service. The tomcat command will provide the status of the Tomcat service.

service tomcat status

Below, you can see that status shows Tomcat service is running.

Verifying the Tomcat service on the remote machine
Verifying the Tomcat service on the remote machine

Now, run the curl command below to access Apache Tomcat’s landing page (localhost:8080).

curl localhost:8080

Notice an output of an HTML code below, which indicates you’ve accessed Apache Tomcat’s landing page. This output confirms that your Ansible playbook works perfectly in deploying Apache Tomcat service.

Verifying the Tomcat service landing page on the remote machine
Verifying the Tomcat service landing page on the remote machine

Conclusion

In this tutorial, you’ve taken advantage to learn how to build a rock-solid Ansible playbook using various Ansible features such as Ansible role, variables, and so on. Using the same Ansible playbook you built, you also learned to deploy Apache tomcat.

Now that you have sound knowledge of the Ansible playbook, which application do you plan to deploy with Ansible playbooks? Or perhaps learn how to leverage Ansible variables in roles and playbooks?

Hate ads? Want to support the writer? Get many of our tutorials packaged as an ATA Guidebook.

Explore ATA Guidebooks

Looks like you're offline!