Ansіble tutorial: master the lightweight software configuration management tool

Your one-stop guide to the popular DevOps configuration management tool from playbooks to filters

DevOpsUPDATED ON March 30, 2022

cover image for blog post on ansible and case study

This Ansible tutorial will take you through the step-by-step process of installing and setting up this incredibly useful open-source tool by RedHat. We’ll also go into detail on key Ansible features and components including:

  • Ansible Playbooks
  • Ansible Tags
  • Ansible Handlers
  • Ansible Variables
  • Ansible Facts
  • Ansible Conditions
  • Ansible Filters

By the end of the tutorial, you’ll have set up your own automated configuration management and be able to do so again and again in different environments. But before we get into the nitty-gritty of the installation, set-up and details of using Ansible in this way, let’s take a quick look at the role the provisioning, configuration management, and application-deployment tool plays as part of a DevOps architecture in enabling infrastructure as code.

Agile & DevOps teams and consultants

Supercharge your next cloud development project!

Ansible’s role in automated configuration management as part of a DevOps architecture

Ansible, originally written by Michael DeHaan and acquired by RedHat in 2015, plays a key role in DevOps architecture by allowing for automated software configuration management. A delightfully simple and lightweight DevOps automation engine, the use of Ansible can realise productivity gains across the automation of cloud resource provisioning, deployment and orchestration of Kubernetes clusters (you can also read our step-by-step tutorial on deploying Ceph in Kubernetes using Ceph-Ansible).

Ansible’s value is in how simple it is to use, despite packing enough punch to automate a complex multi-tier application environment.

While Ansible’s role, as mentioned, can also extend across provisioning and application deployment, enabling infrastructure-as-code, it is its role in configuration management that is the focus of this particular Ansible tutorial. Configuration management underpins DevOps efficiency gains by automating otherwise tedious manual tasks that detract from productivity and agility by sucking up time, resources and injecting the potential for human error.

RedHat’s Ansible is not the only software configuration management tool available to DevOps teams, but after testing several across different projects (Hashicorp’s Terraform, Puppet and Chef and native AWS options all have their strengths and use cases) it’s the one we have opted for at K&C as our standard go-to. It’s the lightest option and another major Ansible plus is that the tool doesn’t require a client-server implementation.

The video below is a great Ansible tutorial for beginners if you feel you would benefit from more general information on Ansible’s qualities and use cases.

Benefits of automated configuration management in DevOps

Automated configuration management using Ansible as part of your DevOps practises brings the following key benefits:

Efficiency gains

Greater control as a direct result of improved visibility and tracking.

Cost savings

Unnecessary duplication of configuration elements sucks up resources. Manual configuration management is tedious and repetitive, heightening the risk of human error, all of which also add to the bottom line of a development project’s expenses column.

Faster problem resolution

Highlights problems as soon as they appear, allowing for speedy intervention.

Improved system and process reliability

Timely detection and adjustment of problem configurations avoid their potential to negatively impact performance and make inefficient use of cloud resources.

Enhanced change management

Reduces the risk of product incompatibility and other potential issues.

Faster system restoration

If, or rather when, a process failure occurs, knowledge of the required state of configuration makes recovering that far quicker and easier.

Risks of not using automated configuration management

Exceeding budget

Neglecting the set-up of automated configuration management, using either Ansible or an alternative tool, or doing so incorrectly, runs the risk of making a major contribution to budget spiralling out of control. Unplanned time and resources being dedicated to intervention when things go wrong as a result of quality and schedule problems can see expenses quickly mount up.

‘Firefighting’ becomes the norm

Corrective action, or firefighting, can become a standard way of working for teams without strong DevOps automation processes and standards. It’s a massive energy vacuum that can be hugely detrimental to overall productivity as well as seriously compromising quality.

But that’s the doom and gloom out of the way! Since you are here, you are invested in automated software configuration management and have either settled upon or are strongly considering Ansible as your tool of choice. So let’s focus on the benefits that will bring and get you set up!

Can We Help You With Your Next Software Development Project?

Flexible models to fit your needs!

Ansible tutorial: set-up, playbooks, tags, handlers, variables, facts, conditions and filters

A major plus of Ansible is that the tool’s learning curve isn’t steep and installation and set-up are relatively simple. But that doesn’t mean it won’t be even easier if you follow our detailed step-by-step tutorial. So, let’s get started and see how it works.


We set up ansible on the local machine. Personally, I am using Fedora release 22.

dnf install ansible

DNF is a new package manager, designed to replace yum.

The simplest thing Ansible can do, is running commands on servers. To do this, let’s write in the command line:

ansible all -i inventories/test.ini -u roman -m ping

all  a command running for servers in the inventory file

-i   a path to the inventory file

-u   a user, under which we will access the servers

-m  module

You can also run any arbitrary command:

ansible all -i inventories/test.ini -u roman -a 'date'

Displaying: | success | rc=0 >> 

Wed Jun 29 06:20:19 EDT 2015 | success | rc=0 >>

Wed Jun 29 06:20:19 EDT 2015

-a arbitrary command

The inventory file is a list of servers and groups

cat inventories/test.ini

ip, domaine     List of Servers

test1, test2     Groups

If you run:

ansible test1 -i inventories/test.ini -u roman -a 'date'

You see: | success | rc=0 >>

Wed Jun 29 06:31:10 EDT 2015

The command applies only to the servers of a certain group.


playbook is one of the main concepts in Ansible and refers to the files where Ansible code is written. Playbooks are YAML files where we use script to specify what actions must be executed on the servers. For example:

- hosts: docker
  - name: Installing Docker
    yum: name=docker state=latest
    become: yes
  - name: Uninstaling Software
    yum: name=sendmail state=absent
    become: yes

In the example above we will install the docker package for docker hosts group and remove sendmail packages

name      An arbitrary designation of the task

yum        yum module

become    Switching user to superuser

If the playbook becomes large, and we need to run an action located somewhere in the end of the file,then we’d have to wait for quite a while.

The video tutorial here is a good explanation of Ansible playbooks and why the tool works particularly well with Docker.


To shorten the waiting time, ansible offers tags. Tags are an attribute that can be set to an Ansible structure (plays, roles, tasks). Once tags or skip-tags, have been configured, they execute or skip over a sub-set of tasks when you run a playbook. This means the playbook only runs what you want it to. For example:

    copy: src={{ files_path }}/docker/docker-storage dest=/etc/sysconfig/docker-storage
    become: yes
    tags: copy_config

Write in the command line:

ansible-playbook --check inventories/docker.yml -i inventory.ini -t copy_config

PLAY [docker] *****************************************************************

GATHERING FACTS ***************************************************************

ok: [docker_ci]

TASK: [Copying docker-storage config] *****************************************

ok: [docker_ci]

PLAY RECAP ********************************************************************

docker_ci : ok=2 changed=0 unreachable=0 failed=0

ansible-playbook --check docker.yml -i inventory.ini -t copy_config --skip-tags

-check       Check, but do not apply changes

-t            Tag

-skip-tags    Will perform all tasks except those that are specified

Tags can be also specified as follows:

 - name: Copying docker-storage config
    copy: src={{ files_path }}/docker/docker-storage dest=/etc/sysconfig/docker-storage
    become: yes
    tags: [docker, config]

The video tutorial below is a useful resource on how to only run specific tasks in a playbook using tags:


Sometimes, having performed some changes, you need to restart the service. For this, ansible has handlers. Handlers are performed at the very end of the playbook, because playbook can have a lot of tasks that will restart the service.

- name: Copying nginx config
  template: src={{ templates_path }}/nginx/nginx.conf.j2 dest=/etc/nginx/nginx.conf
  become: yes
      - nginx restart
  tags: copy_nginx_config
    - name: nginx restart
      service: name=nginx state=restarted
      become: yes

notify Specifies the name of the handler

The following video tutorial focuses specifically on the use of Ansible handlers:


Ansible has variables. Variables are used in Ansible to manage differences between systems. The tool allows you to execute tasks and playbooks on multiple systems with a single command and creating variables allows for the representation of variations between what is executed on different systems. They are created using standard YAML syntax, including lists and dictionaries.

To omit the same values, let’s ​​define the variables:

- hosts: docker
    files_path: /path/to/file
  - name: Copying docker-storage config
    copy: src={{ files_path }}/docker/docker-storage dest=/etc/sysconfig/docker-storage
    become: yes

{{}} Variables are defined in such brackets

For more information on how to use variables in Ansible playbooks:


Ansible facts are variables retrieved from remote systems and contain information about the servers as well as IP addresses, OS installed, Ethernet devices, mac address, time/date related data and hardware information. To see the facts about a certain machine, we write:

ansible test1 -i inventories/test.ini -m setup | success >> {

«ansible_facts»: {

«ansible_all_ipv4_addresses»: [



«ansible_all_ipv6_addresses»: [],

«ansible_architecture»: «x86_64»,

«ansible_bios_date»: «NA»,

«ansible_bios_version»: «NA»,

«ansible_cmdline»: {

«quiet»: true


«ansible_date_time»: {

«date»: «2015-08-25»,

«day»: «25»,

«epoch»: «1440503957»,

«hour»: «07»,

«iso8601»: «2015-08-25T11:59:17Z»,

«iso8601_micro»: «2015-08-25T11:59:17.235193Z»,

«minute»: «59»,

«month»: «08»,

«second»: «17»,

«time»: «07:59:17»,

«tz»: «EDT»,

«tz_offset»: «-0400»,

«weekday»: «Вторник»,

«year»: «2015»



Facts are collected every time Ansible runs. If you put gather_facts:no in playbook, the facts will not be collected:

- hosts: docker
  gather_facts: no
  - name: Adding epel repo
    copy: src={{ files_path }}/repo/epel.repo dest={{ repos }}/epel.repo
    become: yes
    when: ansible_distribution == "CentOS" and ansible_distribution_major_version == "7"
    tags: epel_repo

We should keep cycles in mind, as you may often need to install lots of packages, which overwhelms playbook with multiple similar tasks. For this, Ansible provides the key with_items:

- name: Installing basic packages
    yum: name={{ item }} state=latest
        - mc
        - nano
        - atop
        - htop
        - iptraf-ng

There is another option of usage, by applying several parameters:

  - name: Adding users
    user: name={{ }} state=present group={{ }}
      - { name: testuser1, group: test }
      - { name: testuser2, group: test }
    become: yes

This video tutorial nicely covers both Ansible facts and variables:


Ansible playbooks can contain multiple sets of variables responsible for different tasks. Where there is a mix of variables, each representing different entities like software, servers, network devices etc., conditional statements come into play.

Ansible uses the ‘when’ conditional to determine the outcome of a variable instead of the ‘if-else’ statements in standard programming languages. Basic use of the when condition controls if a task or role runs or is skipped. This is most commonly used to change play behaviour based on facts from the destination system.

If the condition matches, then the task runs the when condition:

  - name: "shutdown Debian flavored systems"
    command: /sbin/shutdown -t now
    when: ansible_os_family == "Debian"
  - name: "shutdown CentOS 6 and 7 systems"
    command: /sbin/shutdown -t now
    when: ansible_distribution == "CentOS" and
          (ansible_distribution_major_version == "6" or ansible_distribution_major_version == "7")

The following video tutorial is an excellent additional guid to using a simple when clause as an Ansible conditional:


Ansible also has filters, which allow for the manipulation of data. Using filters, you can do things such as converting JSON data into YAML data, split URLs to extract hostnames, add or multiply integers or even obtain the SHA1 hash of a string. The ansible-base repo offers filters as plug-ins and you can even create your own custom plug-in filters.

- hosts: localhost
    numbers: [ 4, 9, 6, 8, 2, 9 ]
    path: /etc/passwd
  - debug: msg={{numbers | min }}
  - debug: msg={{numbers | max }}
  - debug: msg={{numbers | unique }}
  - debug: msg={{ ['a', 'b', 'c' ] | random }}
  - debug: msg={{ path | basename }}
  - debug: msg={{ path | dirname }}
  - debug: msg={{ "~/Documents" | expanduser }}

GATHERING FACTS ***************************************************************

ok: [localhost]

TASK: [debug msg={{numbers | min }}] ******************************************

ok: [localhost] => {

«msg»: «2»


TASK: [debug msg={{numbers | max }}] ******************************************

ok: [localhost] => {

«msg»: «9»


TASK: [debug msg={{numbers | unique }}] ***************************************

ok: [localhost] => {

«msg»: «[4,»


TASK: [debug msg=b] ***********************************************************

ok: [localhost] => {

«msg»: «a»


TASK: [debug msg={{ path | basename }}] ***************************************

ok: [localhost] => {

«msg»: «passwd»


TASK: [debug msg={{ path | dirname }}] ****************************************

ok: [localhost] => {

«msg»: «/etc»


TASK: [debug msg=/home/roman/Documents] ***************************************

ok: [localhost] => {

«msg»: «/home/roman/Documents»


min           Displays the minimum value

max          Displays the maximum value

unique        Displays the unique value

random       Displays a random value

basename     Displays the file name

dirname      Displays the directory name

expanduser   Discloses tildes

Registration of the results:

  - shell: ls /home
    register: home_dirs
  - debug: var=home_dirs

TASK: [debug var=home_dirs] ***************************************************

ok: [localhost] => {

«var»: {

«home_dirs»: {

«changed»: true,

«cmd»: «ls /home»,

«delta»: «0:00:00.004283»,

«end»: «2015-08-25 16:35:28.429512»,

«invocation»: {

«module_args»: «ls /home»,

«module_name»: «shell»


«rc»: 0,

«start»: «2015-08-25 16:35:28.425229»,

«stderr»: «»,

«stdout»: «lost+foundnromanntestntestuser1ntestuser2»,

«stdout_lines»: [







«warnings»: [] }




Now we take the value from stdout_lines

  - shell: ls /home
    register: home_dirs
  - debug: var=home_dirs 
  - name: Adding home dirs to cron
    cron: name="Backup" minute="0" hour="6,3" job="backup_dir /home/{{ item }}"
    with_items: home_dirs.stdout_lines

For more use cases and further details, refer to the Ansible documentation on how to use filters to manipulate data.

How to control bloated Ansible playbooks

If your playbook grows to an unwieldy size, you can remove pieces to other files:

- hosts: docker
  - ../vars/basic_vars.yml
  - include: includes/basic.yml
  - include: includes/handlers.yml
- include: includes/httpd_servers.yml

The inclusions are an ordinary YAML file:

cat includes/basic.yml

— name: Uninstaling Software

yum: name={{ item }} state=absent


— sendmail

— exim

— httpd

become: yes

K&C: your flexible DevOps outsourcing & consulting resource

K&C offers DevOps teams outsourcing and consulting services. A Munich-based IT outsourcer of over 20 years experience with nearshored tech talent centres in Krakow, Kyiv, Minsk and Sofia, we have worked with some of Europe’s biggest brands, SMEs and exciting start-ups.

We’ve been supporting the digital success of our partners for over 2 decades now by sticking to a simple commitment of matching our excellence in our technology stack with excellence in communication and delivery management. Our job is to make sure you have one less worry, outstanding IT products and services, so you can focus on what you do best. The technology expertise you need, when you need it. Scale your IT resource up and down without overheads.

K&C - Creating Beautiful Technology Solutions For 20+ Years . Can We Be Your Competitive Edge?

Drop us a line to discuss your needs or next project