Ansible 101 - Kickstarter

75 minute read

Kickstart your experience with Ansible! Understand why you should use it and how it can benefit you. Learn the best practices and apply them immediately using simple examples.

With so much documentation available online, in books and in videos (e.g. YouTube), why would I write yet another document covering Ansible? Good question! Over the past years of using Ansible I have noticed that not a single source of documentation covered everything I needed. Either it had some good examples, some good explanations or some good points but there was always something missing or I still found myself pulling from multiple sources to get the full picture. Ultimately I wanted to combine all of this information into one document with focus on the best practices without trying to cover all possible options. It keeps it simple and provides what you really need to know.

NOTE: Many of the examples in this document use ‘localhost’ as the managed node. This is done for simplicity so you can run these commands without having to worry about spinning up any managed nodes. Just keep it simple for now and focus on the commands and the syntax and understanding what Ansible can do for you. Once you learn the commands, simply change ‘localhost’ to whatever managed nodes or groups that you want. The command and syntax stays the same. That’s the beauty of Ansible!


Ansible is automation software. It is used to configure systems to a specific desired state.

If that is confusing for you, no problem because I will help you understand.


Ansible is great for many things. Here is an article you should read that covers the basic use cases.


Basically Ansible Engine is installed on a server and from here can be executed to manage anything from your localhost to on what is called a Control Node.

# Control Node #   ---->  
#              #   ----> 
#  (Ansible)   #
#              #


To better understand Ansible, it is important to take the time and understand the following concepts and terminology.


Let me start by defining this word because it is often misunderstood.



Desired State


Control Node

A server simply containing an installation of Ansible. It will be used to control our automation tasks.

Also known as Master Node, Control Host, or Control Machine.

Managed Nodes

Also known as Target Nodes, Target Hosts, Remote Hosts, or Managed Hosts.


Within the context of deployment automation tools there is a concept simply described as agent. It refers to a running process on the managed nodes. Some automation tools require an agent to be installed and running on the managed nodes in order to handle some bi-directional communication between the control node and managed nodes. It is important to understand this because Ansible does not require an agent on the managed nodes. This maintains a simple architecture that enables faster implementations.

In short, Ansible is proud to be an agentless solution.

Batteries Included

Ansible features over 1,300+ modules in the core distribution, providing a great base to build automation upon. Ansible Galaxy also has over 4,000 community-provided roles that can be used immediately, tailored to your particular environment, or even used as templates for something new. From services and databases to cloud providers, with Ansible you don’t have to start from scratch.

TODO: Mention how modules already include idempotence and how many modules are included with Ansible.


The point of automation is not to introduce more technology that increases complexity and adds more code that cannot be read nor understood by anyone other then the original developer.

Reading Ansible code should be easy. Well-written code is human readable. Let me emphasize that human-readable does not mean developer-readable. The goal is to have self-documenting Playbooks. If things get a little complex in your Playbooks, then you are not writting correct code.

As Ansible is very flexible and easy to learn, it depends a lot on the developer to produce quality human-readable code. Here’s where it’s essential to train your team on proper best practices before you are about to dive into Ansible. Without proper training you will find yourself in a big mess and digging out of it can be very costly. I am writing this from experience. Trust me, learn the best practices and create a workflow that pushes you into creating good code. It’s easy to get lazy and say “it works, it’s good for now” or “we can make an exception here”.


“If at first you succeed, try, try again.”, John Wadleigh

TODO: explain

TODO: Strategy to test for idempotency using Tags - group a set of tasks that perform a specific operation using a Tag. When you are testing your Playbooks, run the set of tasks using the specific Tag. After the first run, the tasks will show changes to the server. After the second run, the tasks show “OK” or “Skipped” and no changes should be made. If changes are made or your tasks fail, then it has failed the idempotency test and you need to refactor your code so that running the tasks result in success and no changes to the server since the desired state has been established.


Start Small


Configuration Management (CM)


Continuous Delivery (CD)


Continuous Integration (CI)


Ansible Components

There are 3 main constructs to learn.

A Task defines a specific action to be performed. It says WHAT will be done.

A Play defines a set of tasks to be performed against a set of hosts. It says WHERE it will be done.

A Playbook contains one or more Plays.

Let’s look into these in more detail as well as introduce some constructs.


A Task performs a specific action.

The general structure of a task is shown below.

- name: Task Description
    arg1: "value1"
    arg2: "value2"
    arg3: "value3"
  directive1: value1
  directive2: value2


A Play performs one or more Tasks against one or more Managed Nodes.

For example, you might want to shutdown only your web tier.

Before we break it apart and understand each piece, let’s look at the overall construction of a Play.

- name: Start the Play          # describes WHAT we are doing
  hosts: application            # describes WHERE we are doing it (e.g. against all application hosts)
  become: false                 # describes HOW we are doing it (with priviledge escalation, by gather facts, serial batches, etc)
  gather_facts: true
  serial: 10

    app_path: /opt/app

    PATH: /my/folder:





TODO: explain environment section


A Playbook is a file containing one or more Plays.

- name: Start the first Play    # describes WHAT we are doing
  hosts: application            # describes WHERE we are doing it; what target hosts
  become: false                 # describes HOW we are doing it (with priviledge escalation, by gather facts, serial batches, etc)
  gather_facts: true
  serial: 10

  # vars, environment, pre_tasks, roles, tasks, post_tasks, etc.

- name: Start the second Play
  hosts: webservers
  become: true
  gather_facts: false
  serial: 5

  # vars, environment, pre_tasks, roles, tasks, post_tasks, etc.


TODO: move the details of a role to another section called ‘Role Template’ and talk about skeleton role and stuff. I feel the detail here is just too much too soon.

Even though Ansible is not at all a true object-oriented language we should still write using common object-oriented techniques. An Ansible Role is an object. Independent. Re-usable.

TODO: write a blog on ‘Ansible, the object oriented way!” and discuss class, subclassing, and inheratence, and so on but in the Ansible world.

├── defaults
│   └── main.yml
├── vars
│   └── main.yml
├── files
|   └── foobar.txt
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── tasks
│   └── main.yml
└── templates
    └── foobar.conf.j2


The main.yml is automatically added to the Play.


The main.yml is automatically added to the Play.

STYLE GUIDE: If your main.yml is becoming too large or you just want to organize your variables nicely, you may create other variable files within the vars folder however these are not automatically added to the Play. You must use include_vars within your tasks to explicitly add those variables to the Play. Additionally this makes the code more readable since it’s clear to the reader that these variables are only used for a specific set of tasks.


Extra files that are not templates. Do not place binary files in this folder, for that is not the purpose otherwise they will also go into your Version Control System (VCS). Binary files should typically be stored in a Maven artifact repository such as Nexus.

NOTE: Ansible searches this folder for any files referenced by your tasks. There is no need to explicitly state ‘files/myfile.txt’ in your tasks.


The main.yml is automatically added to the Play.


The main.yml is automatically added to the Play.


The main.yml is automatically added to the Play.



The terms Plugins and Modules in the IT world are generally misused and often not differentiated. For Ansible, they are different. They both refer to extra functionality that can be added to the base product.


Core Modules

Community Modules

Custom Modules

Variable Precidence and Scope

TODO: use example above where variables only work within the same Play



Connection Types



Generally using SSH.

TODO: mention these settings for SSH in this document

You want to be able to connect to your servers without having to enter a password every time. If you don’t already have ssh key authentication set up to your children nodes, then do the following…

Generate the ssh key on the master node:

root@master:~ $ ssh-keygen -t rsa -C ""

Then copy your public key to the servers with ssh-copy-id:

root@master:~ $ ssh-copy-id
root@master:~ $ ssh-copy-id

Now you can test the connectivity:

root@master:~ $ ansible all -m ping | success >> {
    "changed": false, 
    "ping": "pong"
} | success >> {
    "changed": false, 
    "ping": "pong"


Microsoft Windows uses winrm for connectivity.

In the future they might move to ssh, read this article from the Powershell blog for more information.

Command-Line Tools (Common)


ansible command is used specifically for performing single ad-hoc commands against multiple managed nodes. It can be very powerful and useful for many reasons. Instead of logging into each managed node and performing the necessary task, just run one command and Ansible performs the task against all of the managed nodes. In fact, if one managed node fails, fix the issue and then re-run the ansible command and will only perform a change to the systems that are not in the desired state.

NOTE: Does not use ansible.cfg! NOTE: Does not gather facts, unless you explicitly run that ad-hoc command.


ansible-playbook command is used for performing multiple commands against multiple managed nodes. This is the main command that uses a Playbook to determine WHAT we do, WHERE we do it and HOW we do it.

NOTE: Uses ansible.cfg if it exists NOTE: Performs fact gathering by default, unless you explicitly disable it within the playbook.


ansible-galaxy command integrates with the community-driven Ansible Galaxy website and allows pulling and pushing Ansible solutions.


ansible-vault command manages secrets (e.g. credential information)


ansible-doc command is very useful for quickly checking Ansible documentation without having to navigate through the online documentation. For example, you might want to quickly check the syntax for a specific command.

NOTE: For your information, the command is basically pulling the information from the source code (using Python’s docstring). All Ansible source code (modules, plugins, everything) complies with Python PEP 257 (Python style guides) and use Docstrings for documentation purposes.

To get documentation for a specific module:

ansible-doc <module-name>

To get only a code snippet for a specific module. This can be useful when you want a code snippet to quickly copy and paste and edit.

ansible-doc -s <module-name>

To get all available modules and plugins. Warning, it takes time to return the full list. Notice that when the list returns you can use your normal vi commands to search and find a specific string.

ansible-doc -l

To list available plugins for a specific type, use the type argument. Here we get the list of all available callback plugins.

ansible-doc --type=callback -l

To get specific callback plugin information on the default callback plugin:

ansible-doc --type=callback default

NOTE: When you run the ansible adhoc command with a specific module but without arguments, the module will fail and let you know what parameter is required. However it only reports one error. In other words, there might be 3 parameters required but you will not know that until you add each one and run the ad-hoc command again and again.

More information can be found on Ansible documentation.

Command-Line Tools (Others)

Some other command-line tools exist but they are of less importance when you want to quickly learn Ansible. However they are listed here for you.


Pulls playbooks from a VCS repo and executes them for the local host

Start by getting overall help with using th

ansible-pull --help

Usage: ansible-pull -U <\repository> [options] [<\playbook.yml>]


A great little tool with some gems that can help you understand your Ansible inventory.

Generate a graph of your inventory. This is very useful and has similar output as the tree command for file systems.

ansible-inventory all --graph
ansible-inventory all --list


View, edit, and manage ansible configuration.

Dump only the configuration options (no descriptions) and default value.

ansible-config dump

List all information about all configuration options. You can use normal search mechanisms which makes it fast to find a specific configuration option and proper syntax.

ansible-config list


  • REPL console for executing Ansible tasks
  • Understand core components of Ansible


Often you need to know basic information about the Managed Node or perhaps even the Control Node. For example, you might want to know if the Managed Node is a CentOS, Fedora or Red Hat system and perform a different task based on that knowledge.

Ansible has a simple solution to gather system facts

  • Gather facts from target nodes
      $ ansible localhost -m setup
  • Disable fact gathering for improved performance ```yaml
    • hosts: whatever gather_facts: false ```

Command: ansible

Run ad-hoc Ansible commands.

The following sections provide the basic operations that are commonly used with the ad-hoc Ansible command. You should know how to perform these basic actions - they are really useful to do quick changes to your managed nodes in parallel.

Managing files and directories

  • Copy file(s) to target nodes
      $ ansible localhost -m copy -a "src=/etc/hosts dest=/tmp/hosts"
  • Change ownership and permissions of files on target nodes
      $ ansible localhost -m file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"
  • Create/delete directories
      $ ansible localhost -m file -a "dest=/path/to/c mode=755 owner=mdehaan group=mdehaan state=directory"
      $ ansible localhost -m file -a "dest=/path/to/c state=absent"
  • Create a file (with specific owner, group and mode) - here we will need to provide the root password
      $ ansible localhost -m file -a "path=./test owner=root group=admin state=touch mode=755" --become --ask-become-pass
  • Create a file based on a template
      # file: ./templates/motd.j2
      Welcome, I am templated with a value of a=, b=, and c=
      $ ansible localhost -m template -a "src=./templates/motd.j2 dest=/etc/motd"

Managing packages

$ ansible localhost -m yum -a "name=acme state=present"
$ ansible localhost -m yum -a "name=acme state=latest"
$ ansible localhost -m yum -a "name=acme state=absent"

Managing services

$ ansible localhost -m service -a "name=httpd state=restarted"

Managing users and groups

$ ansible localhost -m user -a "name=myuser password=mypswd"
$ ansible localhost -m user -a "name=myuser state=absent"





  • Use both static and dynamic inventories to define groups of hosts


Example using both static and dynamic inventories.

  1. Define your static inventory ```yaml [node1] localhost

[node2] localhost

[node3] localhost

[node4] localhost

[nodes:children] node1 node2 node3 node4

[nodes:vars] db_user=guest

[infrastructure:children] nodes

2. Define your dynamic inventory

3. Configure Ansible

4. List hosts

## Modules

Understand how to use the the most common modules

### stat

- stat:
    path: /path/to/file
    register: result
- fail:
    msg: "not exists"
  when: not result.stat.exists


  • Inclusions (–include_vars:)
  • Register variables
  • Variable scope
  • Variable precedence - you need to know what is the weakest and strongest levels. Make sure you understand why the extra vars must always win precedence!
    • Weakest = role defaults
    • Strongest (always wins) = extra vars

Error Handling

NOTE: Please read the section on Make it idempotent


  • Create and use templates to create customized configuration files
  • Jinja2

Task Control


  • Selectively run specific tasks in playbooks using tags


  • Trigger tasks based on state changes


  • Use conditionals to control play execution
  • Selectively run specific tasks in playbooks using WHEN conditional statement


This section covers performance and optimizations with Ansible.


First thing to clarify: Ansible performs tasks in parallel. However, Ansible does not take your entire playbook and run it in parallel against multiple managed nodes.

Parallelism is not at the playbook level. It is at the task level.

Why? Because each task is performing an action that can either be performed on the localhost, on a single managed node, or on many managed nodes. For this reason, tasks determine the level of parallelism.

Ansible uses forks in order to run the same task against multiple target hosts. The default is set to a very conservative 5.

If you need more, you need to tell Ansible. If we have 50 target hosts, we might increase the total forks to 50 to be able to run this task in parallel. If we don’t, then Ansible will run the task in batches of 10 at a time until all target hosts have been processed.

In short, running tasks in parallel is something naturally done by Ansible whenever you run your plays against many managed nodes.

ansible-playbook all -i inventory -f 50 playbook.yml


This section covers delegating tasks to specific hosts.

serial, max_fail_percentage

TODO: create example against localhost with just serial and then with just max_fail_percentage and then both together

- hosts: webservers
  serial: 10
- hosts: webservers
  max_fail_percentage: 30
- hosts: webservers
  max_fail_percentage: 30
  serial: 10



If you want to perform a task on one host and yet reference other hosts, use delegate_to. This is useful for example to check service availability from a different host.

- hosts: webservers
    - name: Start service
        name: httpd
        state: started
    - name: Wait for httpd to start
        host: ""
        port: 80
        timeout: 300
      delegate_to: localhost

STYLE GUIDE: You might see ‘delegate_to:’ instead of ‘delegate_to: localhost’. You could use either but you really shouldn’t. When using localhost a very quick DNS lookup is performed to translate ‘localhost’ to However we gain readability. I prefer using localhost as I generally cringe when I see IP numbers in playbooks.

STYLE GUIDE: local_action vs delegate_to: localhost
TODO: show examples how these differ because it is common to mix them within playbooks and why one should use delegate_to instead of localhost for readability and consistency (add ansible-lint warning!?)


run_once: True also can be specified with the expression local_action

TODO: run_once does not have to be used with local_action! it can also be used with delegate_to or even on its own!

In this case the task will only be executed due to the run_once directive The run_once directive can also help prevent race conditions when running tasks against multiple targets in parallel (see ‘forks’).

Here we use run_once to send mail from just one of the target hosts - we don’t care from which one because they all have mail configured.

- name: Send one mail
    subject: "Mail subject"
    to: ""
    body: ""
  run_once: True

Here we use run_once in conjunction with delegate_to so we perform the send mail operation locally and just once.

- name: Send one mail
    subject: "Mail subject"
    to: ""
    body: ""
  run_once: True
  delegate_to: localhost

STYLE GUIDE: Notice the small change between the last two examples. We only added deletage_to but the format and style of the task remains consistent and easy to read. This is again why I prefer using delegate_to.


- hosts: app_servers
    - name: gather facts from db servers
      delegate_to: ""
      delegate_facts: True
      with_items: ""

Vault (TODO: add simple example)

Ansible allows the management of your secret information (credentials, etc).
- Create, edit and encrypt files for use with Ansible
- Referencing variables within playbooks
- Running a playbook with vault
    ansible-playbook localhost playbook.yml --ask-vault-pass
    ansible-playbook localhost playbook.yml --vault-password-file ~/.vault_pass.txt
    ansible-playbook localhost playbook.yml --vault-password-file ~/
    The password should be a string stored as a single line in the file.


This is a great example playbook for anyone learning Ansible. It hits on some important concepts. Be sure you know how to write something like this.

- hosts: web
  name: Install the web server and start it
  become: yes
      - httpd
      - mod_wsgi
    apache_test_message: This is a test message
    apache_max_keep_alive_requests: 115

    - name: Install the apache web server
        name: ""
        state: present
      with_items: ""
      notify: restart apache service

    - name: Generate apache configuration file from jinja2 template
        src: templates/httpd.conf.j2
        dest: /etc/httpd/conf/httpd.conf
      notify: restart apache service

    - name: Generate a basic homepage from jinja2 template
        src: templates/index.html.j2
        dest: /var/www/html/index.html

    - name: Start the apache web server
        name: httpd
        state: started
        enabled: yes

    - name: restart apache service
        name: httpd
        state: restarted
        enabled: yes


When using Ansible, there are many included modules (batteries included!) with many of them provided by the Ansible Community. So it’s important to understand the level of support from Red Hat that you have when using specific Ansible modules.

First, to understand how your modules are supported please reference the following website to first determine which category your modules fall under.

Next, determine the level of support for your modules by looking at the following Customer Portal article.

You will notice, at the time of writing this article, there are 4 categories of modules:

  • Core
  • Network
  • Certified (Coming Soon)
  • Community


It is critical to understand how to debug issues with Ansible. I have documented all the methods that I use Name of Link.

Advanced Ansible

For more advanced Ansible, take a look at some of the related articles on this site.

Blog Articles

Some other useful blog articles:

15 Things You Should Know About Ansible

Vagrant Examples

Good examples of using Vagrant to startup various software.