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:
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:
- Edit
/etc/ssh/sshd_config
on the container as described - Restart the SSH service after modifications
- 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"