You have done your automation in Ansible? Cool! But do you test, if the deployment really works? Do you check if your server is really listening to the correct port and delivers the correct content? This guide will dig into functional testing with Ansible and demonstrate how you can check if everything went well.
Why should you test?
Ansible works the way, that you define a desired state, and it will ensure, that this state is present. So, why should you test this state again? There are a couple of scenarios, where it is helpful, that you can check the state in different ways.
A service may start like defined in Ansible, but crash some seconds later, unrecognized. This can happen with containers, but also with systemd services. Even if the service is running, there is no guarantee, that it is reachable from different endpoints, since firewalls, SELinux and permissions can interfere.
Lastly, some good functional testing can also be used for your development pipelines. If done right, you can test the status for different deployments and ensure that your playbook really works as intended.
How to functional test with Ansible?
Now that we know the "why", let's dig into the "how". Shall we? If you haven't done anything in Ansible, I strongly recommend to check out the "Ansible - Getting Started" guide and "Ansible - Playbooks" article, beforehand.
Let's start with a very simple web server. The example code is really simple and can be used on any CentOS, Red Hat Enterprise Linux, Alma Linux or Fedora machine.
You need to write a simple playbook (webserver.yml) like the one below.
Executing this playbook is also very easy. I am running this on localhost, but you can use it from a control host and manage one or more machines similarly.
# Run the playbook $ ansible-playbook -K -k -i localhost, webserver.yml
The result will be, that the machine is reachable on port 80/TCP, hopefully. So, we can test this (manually for now).
# Test if everything is fine $ curl localhost My cool WebPage
The initial work is done with our little playbook example. But is there a way to test, if the server is really listening without doing it manually?
We can start with a simple check, that tests, if the desired port is reachable and working.
Let's extend our example playbook with a new task at the end.
This example will check if there is something active on port 80 for 300 seconds. The wait_for module can do even more, like waiting for a service or an existing path on the disk.
The server seems fine and started. We tested the content is shipped with
curl and the above example. But... there is an Ansible way, to "curl". Let's extend the above playbook with some more code, so we don't need to curl the website or test it in a browser.
This will already do the trick, but it's only working for localhost. It would be better to check the actual host. This is also quite easy.
In the above example, the control node will execute the URI module and fetch the URL from the machine, defined in your inventory. This even works in our simple example, where the control node and managed node are the same thing.
SELinux and permissions can be a problem, but also connections to a database or some issues in the deployed code can lead to unexpected behavior. In the last example, we just checked if the server delivers a website. But we also need to check, if the content is correct. This can be done with Ansible, too.
I assume, you expected this - let's put in another task and adjust our last task. Shall we?
The assert module can be used to test for all kind of things. It works mostly like the "when" statement, but the task will fail, if the statement is not met.
Update (Thanks to the feedback from a reader)
In general, you will these tests something like this:
VARIABLE is JINJA_TEST. The VARIABLE can be anything you want, and the JINJA_TEST can be used as described in the documentations.
You can also use Ansible to check your server for common security issues. If you don't want to have MariaDB available in the public web, you can test this.
Sure, you can configure your server with Ansible to close a specific port, but what about "the other one" who has started a container with MySQL in it? Let's see how you can test this.
You may remember the wait_for module from one of the above sections. We can re-use it to check for absent/closed ports, too.
This can be very useful, if you want to reconfigure a server, that should not listen to one port any longer, but another one instead. You can also do security compliance tests with it to prove that telnet or postfix is not exposed/published on a system.
The full example
Everything from the above combined will look something like the below example.
You can extend this to your needs, put it into dedicated playbooks or roles, and re-use the content whenever you need it. But you can be sure, that you don't need to manually check again, after Ansible has deployed something. You can be sure, that it works.
There are even more options to check your deployment for validity, test if a task was really done and is working as expected, or even check for security. The below list will give some ideas, where you can start digging.
- NMAP Module to scan for ports
- Package Facts Module to check if packages are present
- Service Facts Module to check of services are in a proper state
- get_url Module to download something and store it locally
- stat and slurp Module to check file permissions and content
There is even more that might be helpful, depending on your Ansible setup and the environment, you are managing.
Links & Docs
You can find a couple of Links and videos about this topic in the web, too.
As you can see, functional testing is possible with Ansible and also very helpful. I am a big fan of having a couple of tests ready, so I can check my deployments occasionally or whenever I change some code.