Cover Photo

Getting Started with Ansible

22 May 2025

Image taken from the RedHat Blog

In this post, we will introduce Ansible: a tool for configuring multiple machines remotely. Once we’ve covered what Ansible is, we will set up a target environment using Docker and SSH. We will then use Ansible to install and setup an nginx server. This will demonstrate how Ansible works, and its key features.

What is Ansible?

Ansible is a configuration tool that allows us to define and remotely apply a desired state to managed nodes. This means we can configure a number of machines easily, with confidence that these machines are as expected.

Ansible is agent-less, meaning that it doesn’t need anything other than SSH to work. It is also idempotent, meaning that if no changes are needed, Ansible knows not to do anything.

Playbook

A Playbook is a YAML file that tells Ansible how to configure the targets.

It consists of “Plays”, which describes which “Tasks” should apply to which Nodes.

A Task is an operation that Ansible should perform.

In a Playbook, we refer to Nodes as “hosts”.

Tutorial

Creating the target machine

This step is to give us something to target. You can use any linux machine with SSH to target, but using a Docker container gives us a quick and easy way of testing out Ansible.

⚠️ For simplicity’s sake, we will use the root user and an SSH password, but this isn’t considered best practices, and shouldn’t be done in a real system.

First, ensure you have docker installed

docker -v

If you don’t, you will need to install it before continuing.

Let’s spin up a ubuntu Docker container, and use sleep infinity to keep the container running until we stop it.

We’ll also map the container’s port 80 to our host machine’s port 8080. This will allow us to communicate the nginx server we will install.

docker run -d --name ansible-test -p 8080:80 ubuntu:22.04 sleep infinity

Log into the VM to setup SSH

docker exec -it ansible-test bash

Inside the container:

apt update && apt install -y openssh-server sudo
passwd root      # set a root password (for testing, e.g., "root")

We will also allow SSH access to the root user. Once again, this is generally a bad idea, but for our example it will be OK.

apt install nano
nano /etc/ssh/sshd_config

Add these lines to this config:

PermitRootLogin yes
PasswordAuthentication yes

Close the file, and then run this to start SSH server

service ssh start

Press Ctrl+D to exit Docker.

Setup Ansible on your local machine

Now we are out of our container, we need to setup Ansible locally.

Get the container’s IP:

docker inspect -f '' ansible-test

You should see an IP like 172.17.0.2. Note this down, as we will need it later.

Install sshpass to allow us to use password with SSH.

sudo apt update
sudo apt install -y ansible sshpass

Create a hosts file with the content. Change the IP and password as needed.

[test]
172.17.0.2 ansible_user=root ansible_password=root ansible_ssh_pass=root ansible_ssh_common_args='-o StrictHostKeyChecking=no'

Run this command to check that Ansible can communicate with the container.

ansible -i hosts test -m ping

You should see a Pong from the container. This verifies that Ansible is working, and it can connect to the container. As we did not install anything on the container, it shows us that Ansible is agent-less: it works with only SSH access.

If you receive an error at this step, go to the Troubleshooting section

Creating a Playbook

Now we’ll define the state that we want the managed node to be in, which we will use Ansible to enforce.

Create a file called setup-nginx.yml.

---
- hosts: test
  become: yes
  tasks:
    - name: Ensure nginx is installed
      apt:
        name: nginx
        state: present
        update_cache: yes

This is our Playbook: It contains a single Play which targets our single managed node: ‘test’. It contains a single Task, with the human readable name “Ensure nginx is installed”. This uses apt to check that nginx is present, and will install it if not. update_cache: yes ensures that Ansible updates its local database of available packages before installing the package (similar to running apt update yourself). The become: yes line tells Ansible to use escalated privileges, as they are needed to install nginx.

Applying a Playbook

Once we have created a simple Playbook, we can apply it with

ansible-playbook -i hosts setup-nginx.yml

You should then see output like this

PLAY [test] *******************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************
ok: [172.17.0.2]

TASK [Ensure nginx is installed] **********************************************************************************
[WARNING]: Updating cache and auto-installing missing dependency: python3-apt
changed: [172.17.0.2]

PLAY RECAP ********************************************************************************************************
172.17.0.2                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

We can see that Ansible has changed the target machine, and also installed a missing dependency! How useful.

If we then run the command again

ansible-playbook -i hosts setup-nginx.yml

The output shows that Ansible has checked, and knows that no changes are made. (changed=0)

[...]
PLAY RECAP **********************************************************************************************************
172.17.0.2                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

This shows us that Ansible is idempotent: it won’t make changes that aren’t needed.

To test our nginx server, we can try to connect to it with

curl http://localhost:8080

However, we receive the error curl: (56) Recv failure: Connection reset by peer.

This is because the server isn’t running! We can either SSH in and start it… or update our Ansible config to start the server.

Modifying a Playbook

Edit setup-nginx.yml to look like this:

---
- hosts: test
  become: yes
  tasks:
    - name: Ensure nginx is installed
      apt:
        name: nginx
        state: present
        update_cache: yes

    - name: Ensure nginx is started and enabled
      service:
        name: nginx
        state: started
        enabled: yes

This now both installs and ensures the server is running. Again, if the service isn’t running, then Ansible knows to start it.

We can now apply this new config

ansible-playbook -i hosts setup-nginx.yml

In the output, we can see that once again nginx is already installed, but we see another task that starts the nginx server.


PLAY [test] ******************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
ok: [172.17.0.2]

TASK [Ensure nginx is installed] *********************************************************************************
ok: [172.17.0.2]

TASK [Ensure nginx is started and enabled] ***********************************************************************
ok: [172.17.0.2]

PLAY RECAP *******************************************************************************************************
172.17.0.2                 : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

And now if we try to access the server again with

curl http://localhost:8080

We see a response!

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

We can also see this if we use a browser to access localhost:8080:

alt text

Tidy up

Finally, stop the container with

docker rm -f ansible-test

If you wanted, you could also uninstall the packages we installed on your local machine:

  • ansible
  • sshpass

Conclusion

After setting up a test environment (a Docker container with SSH enabled), we have seen how to

  • Install Ansible
  • Add a host
  • Write a Play
  • Idempotently apply the Play
  • Apply a modified play
  • Verified that our changes were made successfully.

Of course, we’ve only brushed the surface of Ansible’s capabilities. You can read about inventories, roles, and templates on the Ansible documentation.

Troubleshooting

Permission Denied when trying to ping the container

If you receive a “Permission denied” error while trying to ping the container, ensure you have configured SSH as described. Remember to:

  1. Edit /etc/ssh/sshd_config on the container as described
  2. Restart the SSH service after modifications
  3. Install sshpass on your local machine

If this doesn’t work, make sure the password in the hosts file is correct.

Fingerprint Error when trying to ping the container

This is likely because the fingerprint was generated for an old container. If you think it’s safe, you can run this command to accept the new fingerprint:

ssh-keygen -f "/home/<your user>/.ssh/known_hosts" -R "172.17.0.2"

Published on 22 May 2025 Source on GitHub!