One place for hosting & domains


      How To Test Ansible Roles with Molecule on Ubuntu 18.04

      The author selected the Mozilla Foundation to receive a donation as part of the Write for DOnations program.


      Unit testing in Ansible is key to making sure roles function as intended. Molecule makes this process easier by allowing you to specify scenarios that test roles against different environments. Using Ansible under the hood, Molecule offloads roles to a provisioner that deploys the role in a configured environment and calls a verifier (such as Testinfra) to check for configuration drift. This ensures that your role has made all of the expected changes to the environment in that particular scenario.

      In this guide, you will build an Ansible role that deploys Apache to a host and configures firewalld on CentOS 7. To test that this role works as intended, you will create a test in Molecule using Docker as a driver and Testinfra, a Python library for testing the state of servers. Molecule will provision Docker containers to test the role and Testinfra will verify that the server has been configured as intended. When you’re finished, you’ll be able to create multiple test cases for builds across environments and run these tests using Molecule.


      Before you begin this guide you’ll need the following:

      Step 1 — Preparing the Environment

      If you’ve followed the prerequisites, you should have Python 3, venv, and Docker installed and correctly configured. Let’s begin by creating a virtual environment to test Ansible with Molecule.

      Start by logging in as your non-root user and creating a new virtual environment:

      Activate it to ensure that your actions are restricted to that environment:

      • source my_env/bin/activate

      Next, in your activated environment, install the wheel package, which provides the bdist_wheel setuptools extension that pip uses to install Ansible:

      • python3 -m pip install wheel

      You can now install molecule and docker with pip. Ansible will be automatically installed as a dependency for Molecule:

      • python3 -m pip install molecule docker

      Here is what each of these packages will do:

      • molecule: This is the main Molecule package that you will use to test roles. Installing molecule automatically installs Ansible, along with other dependencies, and enables the use of Ansible playbooks to execute roles and tests.
      • docker: This Python library is used by Molecule to interface with Docker. You will need this since you're using Docker as a driver.

      Next, let's create a role in Molecule.

      Step 2 — Creating a Role in Molecule

      With your environment set up, you can use Molecule to create a basic role that you will use to test an installation of Apache. This role will create the directory structure and some initial tests, and specify Docker as the driver so that Molecule uses Docker to run its tests.

      Create a new role called ansible-apache:

      • molecule init role -r ansible-apache -d docker

      The -r flag specifies the name of the role while -d specifies the driver, which provisions the hosts for Molecule to use in testing.

      Change into the directory of the newly created role:

      Test the default role to check if Molecule has been set up properly:

      You will see output that lists each of the default test actions. Before starting the test, Molecule validates the configuration file molecule.yml to make sure everything is in order. It also prints this test matrix, which specifies the order of test actions:


      --> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml. Validation completed successfully. --> Test matrix └── default ├── lint ├── destroy ├── dependency ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify └── destroy ...

      We will discuss each test action in detail once you've created your role and customized your tests. For now, pay attention to the PLAY_RECAP for each test, and be sure that none of the default actions returns a failed status. For example, the PLAY_RECAP for the default 'create' action should look like this:


      ... PLAY RECAP ********************************************************************* localhost : ok=5 changed=4 unreachable=0 failed=0

      Let's move on to modifying the role to configure Apache and firewalld.

      Step 3 — Configuring Apache and Firewalld

      To configure Apache and firewalld, you will create a tasks file for the role, specifying packages to install and services to enable. These details will be extracted from a variables file and template that you will use to replace the default Apache index page.

      Still in the ansible-apache directory, create a tasks file for the role using nano or your favorite text editor:

      You'll see that the file already exists. Delete what's there and replace it with the following code to install the required packages and enable the correct services, HTML defaults, and firewall settings:


      - name: "Ensure required packages are present"
          name: "{{ pkg_list }}"
          state: present
      - name: "Ensure latest index.html is present"
          src: index.html.j2
          dest: /var/www/html/index.html
      - name: "Ensure httpd service is started and enabled"
          name: "{{ item }}"
          state: started
          enabled: true
        with_items: "{{ svc_list }}"
      - name: "Whitelist http in firewalld"
          service: http
          state: enabled
          permanent: true
          immediate: true

      This playbook includes 4 tasks:

      • "Ensure required packages are present": This task will install the packages listed in the variables file under pkg_list. The variables file will be located at ~/ansible-apache/vars/main.yml and you will create it at the end of this step.
      • "Ensure latest index.html is present": This task will copy a template page, index.html.j2, and paste it over the default index file, /var/www/html/index.html, generated by Apache. You will also create the new template in this step.
      • "Ensure httpd service is started and enabled": This task will start and enable the services listed in svc_list in the variables file.
      • "Whitelist http in firewalld": This task will whitelist the http service in firewalld. Firewalld is a complete firewall solution present by default on CentOS servers. For the http service to work, you will need to expose the required ports. Instructing firewalld to whitelist a service ensures that it whitelists all of the ports that the service requires.

      Save and close the file when you are finished.

      Next, let's create a templates directory for the index.html.j2 template page:

      Create the page itself:

      • nano templates/index.html.j2

      Paste in the following boilerplate code:


      <div style="text-align: center">
          <h2>Managed by Ansible</h2>

      Save and close the file.

      The final step in completing the role is writing the variables file, which provides the names of packages and services to our main role playbook:

      Paste over the default content with the following code, which specifies pkg_list and svc_list:


        - httpd
        - firewalld
        - httpd
        - firewalld

      These lists contain the following information:

      • pkg_list: This contains the names of the packages that the role will install: httpd and firewalld.
      • svc_list: This contains the names of the services that the role will start and enable: httpd and firewalld.

      Note: Make sure that your variables file doesn't have any blank lines or your test will fail during linting.

      Now that you've finished creating your role, let's configure Molecule to test if it works as intended.

      Step 4 — Modifying the Role for Running Tests

      In our case, configuring Molecule involves modifying the Molecule configuration file molecule.yml to add platform specifications. Because you're testing a role that configures and starts the httpd systemd service, you will need to use an image with systemd configured and privileged mode enabled. For this tutorial, you will use the milcom/centos7-systemd image available on Docker Hub. Privileged mode allows containers to run with almost all of the capabilities of their host machine.

      Let's edit molecule.yml to reflect these changes:

      • nano molecule/default/molecule.yml

      Add the highlighted platform information:


        name: galaxy
        name: docker
        name: yamllint
        - name: centos7
          image: milcom/centos7-systemd
          privileged: true
        name: ansible
          name: ansible-lint
        name: default
        name: testinfra
          name: flake8

      Save and close the file when you are done.

      Now that you've successfully configured the test environment, let's move on to writing the test cases that Molecule will run against your container after executing the role.

      Step 5 — Writing Test Cases

      In the test for this role, you will check the following conditions:

      • That the httpd and firewalld packages are installed.
      • That the httpd and firewalld services are running and enabled.
      • That the http service is enabled in your firewall settings.
      • That index.html contains the same data specified in your template file.

      If all of these tests pass, then the role works as intended.

      To write the test cases for these conditions, let's edit the default tests in ~/ansible-apache/molecule/default/tests/ Using Testinfra, we will write the test cases as Python functions that use Molecule classes.


      • nano molecule/default/tests/

      Delete the contents of the file so that you can write the tests from scratch.

      Note: As you write your tests, make sure that they are separated by two new lines or they will fail.

      Start by importing the required Python modules:


      import os
      import pytest
      import testinfra.utils.ansible_runner

      These modules include:

      • os: This built-in Python module enables operating-system-dependent functionality, making it possible for Python to interface with the underlying operating system.
      • pytest: The pytest module enables test writing.
      • testinfra.utils.ansible_runner: This Testinfra module uses Ansible as the backend for command execution.

      Under the module imports, add the following code, which uses the Ansible backend to return the current host instance:


      testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(

      With your test file configured to use the Ansible backend, let's write unit tests to test the state of the host.

      The first test will ensure that httpd and firewalld are installed:


      @pytest.mark.parametrize('pkg', [
      def test_pkg(host, pkg):
          package = host.package(pkg)
          assert package.is_installed

      The test begins with the pytest.mark.parametrize decorator, which allows us to parameterize the arguments for the test. This first test will take test_pkg as a parameter to test for the presence of the httpd and firewalld packages.

      The next test checks whether or not httpd and firewalld are running and enabled. It takes test_svc as a parameter:


      @pytest.mark.parametrize('svc', [
      def test_svc(host, svc):
          service = host.service(svc)
          assert service.is_running
          assert service.is_enabled

      The last test checks that the files and contents passed to parametrize() exist. If the file isn't created by your role and the content isn't set properly, assert will return False:


      @pytest.mark.parametrize('file, content', [
        ("/etc/firewalld/zones/public.xml", "<service name="http"/>"),
        ("/var/www/html/index.html", "Managed by Ansible")
      def test_files(host, file, content):
          file = host.file(file)
          assert file.exists
          assert file.contains(content)

      In each test, assert will return True or False depending on the test result.

      The finished file looks like this:


      import os
      import pytest
      import testinfra.utils.ansible_runner
      testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
      @pytest.mark.parametrize('pkg', [
      def test_pkg(host, pkg):
          package = host.package(pkg)
          assert package.is_installed
      @pytest.mark.parametrize('svc', [
      def test_svc(host, svc):
          service = host.service(svc)
          assert service.is_running
          assert service.is_enabled
      @pytest.mark.parametrize('file, content', [
        ("/etc/firewalld/zones/public.xml", "<service name="http"/>"),
        ("/var/www/html/index.html", "Managed by Ansible")
      def test_files(host, file, content):
          file = host.file(file)
          assert file.exists
          assert file.contains(content)

      Now that you've specified your test cases, let's test the role.

      Step 6 — Testing the Role with Molecule

      Once you initiate the test, Molecule will execute the actions you defined in your scenario. Let's now run the default molecule scenario again, executing the actions in the default test sequence while looking more closely at each.

      Run the test for the default scenario again:

      This will initiate the test run. The initial output prints the default test matrix:


      --> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml. Validation completed successfully. --> Test matrix └── default ├── lint ├── destroy ├── dependency ├── syntax ├── create ├── prepare ├── converge ├── idempotence ├── side_effect ├── verify └── destroy

      Let's go through each test action and the expected output, starting with linting.

      The linting action executes yamllint, flake8, and ansible-lint:

      • yamllint: This linter is executed on all YAML files present in the role directory.
      • flake8: This Python code linter checks tests created for Testinfra.
      • ansible-lint: This linter for Ansible playbooks is executed in all scenarios.


      ... --> Scenario: 'default' --> Action: 'lint' --> Executing Yamllint on files found in /home/sammy/ansible-apache/... Lint completed successfully. --> Executing Flake8 on files found in /home/sammy/ansible-apache/molecule/default/tests/... Lint completed successfully. --> Executing Ansible Lint on /home/sammy/ansible-apache/molecule/default/playbook.yml... Lint completed successfully.

      The next action, destroy, is executed using the destroy.yml file. This is done to test our role on a newly created container.

      By default, destroy is called twice: at the start of the test run, to delete any pre-existing containers, and at the end, to delete the newly created container:


      ... --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* ok: [localhost] => (item=None) ok: [localhost] TASK [Delete docker network(s)] ************************************************ skipping: [localhost] PLAY RECAP ********************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0

      After the destroy action is complete, the test will move on to dependency. This action allows you to pull dependencies from ansible-galaxy if your role requires them. In this case, our role does not:


      ... --> Scenario: 'default' --> Action: 'dependency' Skipping, missing the requirements file.

      The next test action is a syntax check, which is executed on the default playbook.yml playbook. It works in a similar way to the --syntax-check flag in the command ansible-playbook --syntax-check playbook.yml:


      ... --> Scenario: 'default' --> Action: 'syntax' playbook: /home/sammy/ansible-apache/molecule/default/playbook.yml

      Next, the test moves on to the create action. This uses the create.yml file in your role's Molecule directory to create a Docker container with your specifications:


      ... --> Scenario: 'default' --> Action: 'create' PLAY [Create] ****************************************************************** TASK [Log into a Docker registry] ********************************************** skipping: [localhost] => (item=None) skipping: [localhost] TASK [Create Dockerfiles from image names] ************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Discover local Docker images] ******************************************** ok: [localhost] => (item=None) ok: [localhost] TASK [Build an Ansible compatible image] *************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Create docker network(s)] ************************************************ skipping: [localhost] TASK [Create molecule instance(s)] ********************************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) creation to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] PLAY RECAP ********************************************************************* localhost : ok=5 changed=4 unreachable=0 failed=0

      After create, the test moves on to the prepare action. This action executes the prepare playbook, which brings the host to a specific state before running converge. This is useful if your role requires a pre-configuration of the system before the role is executed. Again, this does not apply to our role:


      ... --> Scenario: 'default' --> Action: 'prepare' Skipping, prepare playbook not configured.

      After prepare, the converge action executes your role on the container by running the playbook.yml playbook. If multiple platforms are configured in the molecule.yml file, Molecule will converge on all of these:


      ... --> Scenario: 'default' --> Action: 'converge' PLAY [Converge] **************************************************************** TASK [Gathering Facts] ********************************************************* ok: [centos7] TASK [ansible-apache : Ensure required packages are present] ******************* changed: [centos7] TASK [ansible-apache : Ensure latest index.html is present] ******************** changed: [centos7] TASK [ansible-apache : Ensure httpd service is started and enabled] ************ changed: [centos7] => (item=httpd) changed: [centos7] => (item=firewalld) TASK [ansible-apache : Whitelist http in firewalld] **************************** changed: [centos7] PLAY RECAP ********************************************************************* centos7 : ok=5 changed=4 unreachable=0 failed=0

      After coverge, the test moves on to idempotence. This action tests the playbook for idempotence to make sure no unexpected changes are made in multiple runs:


      ... --> Scenario: 'default' --> Action: 'idempotence' Idempotence completed successfully.

      The next test action is the side-effect action. This lets you produce situations in which you'll be able to test more things, like HA failover. By default, Molecule doesn't configure a side-effect playbook and the task is skipped:


      ... --> Scenario: 'default' --> Action: 'side_effect' Skipping, side effect playbook not configured.

      Molecule will then run the verifier action using the default verifier, Testinfra. This action executes the tests you wrote earlier in If all the tests pass successfully, you will see a success message and Molecule will proceed to the next step:


      ... --> Scenario: 'default' --> Action: 'verify' --> Executing Testinfra tests found in /home/sammy/ansible-apache/molecule/default/tests/... ============================= test session starts ============================== platform linux -- Python 3.6.5, pytest-3.7.3, py-1.5.4, pluggy-0.7.1 rootdir: /home/sammy/ansible-apache/molecule/default, inifile: plugins: testinfra-1.14.1 collected 6 items tests/ ...... [100%] ========================== 6 passed in 41.05 seconds =========================== Verifier completed successfully.

      Finally, Molecule destroys the instances completed during the test and deletes the network assigned to those instances:


      ... --> Scenario: 'default' --> Action: 'destroy' PLAY [Destroy] ***************************************************************** TASK [Destroy molecule instance(s)] ******************************************** changed: [localhost] => (item=None) changed: [localhost] TASK [Wait for instance(s) deletion to complete] ******************************* changed: [localhost] => (item=None) changed: [localhost] TASK [Delete docker network(s)] ************************************************ skipping: [localhost] PLAY RECAP ********************************************************************* localhost : ok=2 changed=2 unreachable=0 failed=0

      The test actions are now complete, verifying that your role worked as intended.


      In this article you created an Ansible role to install and configure Apache and firewalld. You then wrote unit tests with Testinfra that Molecule used to assert that the role ran successfully.

      You can use the same basic method for highly complex roles, and automate testing using a CI pipeline as well. Molecule is a highly configurable tool that can be used to test roles with any providers that Ansible supports, not just Docker. It's also possible to automate testing against your own infrastructure, making sure that your roles are always up-to-date and functional. The official Molecule documentation is the best resource for learning how to use Molecule.

      Source link

      How to Install and Configure Ansible on Ubuntu 18.04


      Configuration management systems are designed to make controlling large numbers of servers easy for administrators and operations teams. They allow you to control many different systems in an automated way from one central location.

      While there are many popular configuration management systems available for Linux systems, such as Chef and Puppet, these are often more complex than many people want or need. Ansible is a great alternative to these options because it requires a much smaller overhead to get started.

      In this guide, we will discuss how to install Ansible on an Ubuntu 18.04 server and go over some basics of how to use the software.

      How Does Ansible Work?

      Ansible works by configuring client machines from a computer that has the Ansible components installed and configured.

      It communicates over normal SSH channels to retrieve information from remote machines, issue commands, and copy files. Because of this, an Ansible system does not require any additional software to be installed on the client computers.

      This is one way that Ansible simplifies the administration of servers. Any server that has an SSH port exposed can be brought under Ansible’s configuration umbrella, regardless of what stage it is at in its life cycle. This means that any computer that you can administer through SSH, you can also administer through Ansible.

      Ansible takes on a modular approach, making it easy to extend to use the functionalities of the main system to deal with specific scenarios. Modules can be written in any language and communicate in standard JSON.

      Configuration files are mainly written in the YAML data serialization format due to its expressive nature and its similarity to popular markup languages. Ansible can interact with hosts either through command line tools or its configuration scripts, which are known as Playbooks.


      To follow this tutorial, you will need:

      • Two or more Ubuntu 18.04 servers. One of these will be used as your Ansible server, while the remainder will be used as your Ansible hosts. Each should have a non-root user with sudo privileges and a basic firewall configured. You can set this up by following our Initial Server Setup Guide for Ubuntu 18.04. Please note that the examples throughout this guide specify three Ansible hosts, but the commands and configurations shown can be adjusted for any number of clients.

      • SSH keys generated for the non-root user on your Ansible server. To do this, follow Step 1 of our guide on How to Set Up SSH Keys on Ubuntu 18.04. For the purposes of this tutorial, you can save the key pair to the default location (~/.ssh/id_rsa) and you do not need to password-protect it.

      Step 1 — Installing Ansible

      To begin using Ansible as a means of managing your various servers, you need to install the Ansible software on at least one machine.

      To get the latest version of Ansible for Ubuntu, you can add the project’s PPA (personal package archive) to your system. Before doing this, though, you should first update your package index and install the software-properties-common package. This software will make it easier to manage this and other independent software repositories:

      • sudo apt update
      • sudo apt install software-properties-common

      Then add the Ansible PPA by typing the following command:

      • sudo apt-add-repository ppa:ansible/ansible

      Press ENTER to accept the PPA addition.

      Next, refresh your system’s package index once again so that it is aware of the packages available in the PPA:

      Following this update, you can install the Ansible software:

      Your Ansible server now has all of the software required to administer your hosts.

      Step 2 — Configuring SSH Access to the Ansible Hosts

      As mentioned previously, Ansible primarily communicates with client computers through SSH. While it certainly has the ability to handle password-based SSH authentication, using SSH keys can help to keep things simple.

      On your Ansible server, use the cat command to print the contents of your non-root user’s SSH public key file to the terminal’s output:

      Copy the resulting output to your clipboard, then open a new terminal and connect to one of your Ansible hosts using SSH:

      • ssh sammy@ansible_host_ip

      Switch to the client machine’s root user:

      As the root user, open the authorized_keys within the ~/.ssh directory:

      • nano ~/.ssh/authorized_keys

      In the file, paste your Ansible server user’s SSH key, then save the file and close the editor (press CTRL + X, Y, then ENTER). Then run the exit command to return to the host’s non-root user:

      Lastly, because Ansible uses a python interpreter located at /usr/bin/python to run its modules, you’ll need to install Python 2 on the host in order for Ansible to communicate with it. Run the following commands to update the host’s package index and install the python package:

      • sudo apt update
      • sudo apt install python

      Following this, you can run the exit command once again to close the connection to the client:

      Repeat this process for each server you intend to control with your Ansible server. Next, we’ll configure the Ansible server to connect to these hosts using Ansible’s hosts file.

      Step 3 — Setting Up Ansible Hosts

      Ansible keeps track of all of the servers that it knows about through a hosts file. We need to set up this file first before we can begin to communicate with our other computers.

      Open the file with sudo privileges, like this:

      • sudo nano /etc/ansible/hosts

      Inside the file, you will see a number of example configurations that have been commented out (with a # preceding each line). These examples won’t actually work for us since the hosts listed in each one are made up. We will, however, keep these examples in the file to help us with configuration if we want to implement more complex scenarios in the future.

      The hosts file is fairly flexible and can be configured in a few different ways. The syntax we are going to use, though, looks like this:

      alias ansible_ssh_host=your_server_ip

      In this example, group_name is an organizational tag that lets you refer to any servers listed under it with one word, while alias is just a name to refer to one specific server.

      So, in our scenario, we are imagining that we have three servers we are going to control with Ansible. At this point, these servers are accessible from the Ansible server by typing:

      You should not be prompted for a password if you have set this up correctly. For the purpose of demonstration, we will assume that our hosts' IP addresses are,, and We will set this up so that we can refer to these individually as host1, host2, and host3, or as a group with the name servers.

      This is the block that we should add to our hosts file to accomplish this:


      host1 ansible_ssh_host=
      host2 ansible_ssh_host=
      host3 ansible_ssh_host=

      Hosts can be in multiple groups and groups can configure parameters for all of their members. Let's try this out now.

      With our current settings, if we tried to connect to any of these hosts with Ansible, the command would fail (assuming you are not operating as the root user). This is because your SSH key is embedded for the root user on the remote systems and Ansible will by default try to connect as your current user. A connection attempt will get this error:


      host1 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh.", "unreachable": true }

      On the Ansible server, we're using a user called sammy. Ansible will try to connect to each host with ssh sammy@server. This will not work if the sammy user is not on the remote system as well.

      We can create a file that tells all of the servers in the "servers" group to connect as the root user.

      To do this, we will create a directory in the Ansible configuration structure called group_vars. Within this folder, we can create YAML-formatted files for each group we want to configure:

      • sudo mkdir /etc/ansible/group_vars
      • sudo nano /etc/ansible/group_vars/servers

      We can put our configuration in here. YAML files start with "---", so make sure you don't forget that part.


      ansible_ssh_user: root

      Save and close this file when you are finished.

      If you want to specify configuration details for every server, regardless of group association, you can put those details in a file at /etc/ansible/group_vars/all. Individual hosts can be configured by creating files named after their alias under a directory at /etc/ansible/host_vars.

      Step 4 — Using Simple Ansible Commands

      Now that we have our hosts set up and enough configuration details to allow us to successfully connect to our hosts, we can try out our very first command.

      Ping all of the servers you configured by typing:

      Ping output

      host1 | SUCCESS => {
          "changed": false,
          "ping": "pong"
      host3 | SUCCESS => {
          "changed": false,
          "ping": "pong"
      host2 | SUCCESS => {
          "changed": false,
          "ping": "pong"

      This is a basic test to make sure that Ansible has a connection to all of its hosts.

      The all means all hosts. We could just as easily specify a group:

      We could also specify an individual host:

      We can specify multiple hosts by separating them with colons:

      • ansible -m ping host1:host2

      The -m ping portion of the command is an instruction to Ansible to use the "ping" module. These are basically commands that you can run on your remote hosts. The ping module operates in many ways like the normal ping utility in Linux, but instead it checks for Ansible connectivity.

      The ping module doesn't really take any arguments, but we can try another command to see how that works. We pass arguments into a script by typing -a.

      The "shell" module lets us send a terminal command to the remote host and retrieve the results. For instance, to find out the memory usage on our host1 machine, we could use:

      • ansible -m shell -a 'free -m' host1

      Shell output

      host1 | SUCCESS | rc=0 >>
                   total       used       free     shared    buffers     cached
      Mem:          3954        227       3726          0         14         93
      -/+ buffers/cache:        119       3834
      Swap:            0          0          0

      With that, your Ansible server configured and you can successfully communicate and control your hosts.


      In this tutorial, we have configured Ansible and verified that it can communicate with each host. We have also used the ansible command to execute simple tasks remotely.

      Although this is useful, we have not covered the most powerful feature of Ansible in this article: Playbooks. Ansible Playbooks are a powerful, simple way to manage server configurations and multi-machine deployments. For an introduction to Playbooks, see this guide. Additionally, we encourage you to check out the official Ansible documentation to learn more about the tool.

      Source link