Integration of Testing with Playbooks in Ansible

How can I best combine testing strategies with Ansible playbooks? This is a question that people have frequently asked. There are a lot of choices. Since Ansible is intended to be a "fail-fast" and organized system, it makes it simple to include testing in Ansible playbooks. In this article, we'll cover the proper level of testing that may be necessary, as well as various patterns for integrating infrastructure tests.
There will be fewer surprises when the code goes live if testing is part of your deployment procedure. In many circumstances, tests can also be used in production to stop unsuccessful updates from propagating throughout an installation. Run the steps on the localhost or testing servers with ease because it is push-based. You can add as many balances and checks as you want to your upgrading cycle using Ansible.
What is the Right Level of Testing?

Ansible resources represent desired states as models. As a result, checking that services are launched, packages are installed, and other similar actions shouldn't be required. The mechanism that will make sure these statements are true declaratively is called Ansible. Make these points clear in your playbooks instead.
tasks:
- ansible.builtin.service:
name: foo
state: started
enabled: yes
The actual best course of action is to request that the service be launched if you believe it might not have been. Ansible will yell adequately if the service cannot launch.
Using Check Mode as a Drift Test

Ansible's --check mode can be used as an additional layer of testing in the configuration described above. The --check flag to the ansible command will tell whether Ansible believes it would have needed to make any changes to get the system into the intended condition if a deployment playbook were to be performed against an existing system.
This can inform you in advance of the requirement to deploy onto the specified system. If you wish some actions to proceed normally even when the --check flag is used, such as calls to the script module, disable check mode for those operations: Normally, scripts and commands don't run in check mode.
roles:
- webserver
tasks:
- ansible.builtin.script: verify.sh
check_mode: no
Most Useful Modules in Testing

There are some playbook modules that are especially useful for testing. Here is an illustration of how to make sure a port is open:
tasks:
- ansible.builtin.wait_for:
host: "{{ inventory_hostname }}"
port: 22
delegate_to: localhost
Here is an illustration of how to use the URI module to ensure that a web service returns:
tasks:
- action: uri url=https://www.example.com return_content=yes
register: webpage
- fail:
msg: 'service is not cheerful'
when: "'AWESOME' not in webpage.content"
The script will immediately fail if it has a non-zero return code and is simple to deploy on a remote computer in any language:
tasks:
- ansible.builtin.script: test_script1
- ansible.builtin.script: test_script2 --parameter value --parameter2 value
Scripts pushed by the script module can reside in a role's "files/" directory if that role is being used.
Additionally, the assert module makes it very simple to verify several types of truth:
tasks:
- ansible.builtin.shell: /usr/bin/some-command --parameter value
register: cmd_result
- ansible.builtin.assert:
that:
- "'not ready' not in cmd_result.stderr"
- "'gizmo enabled' in cmd_result.stdout"
The "stat" module is an excellent option if you feel the need to check for the presence of files that aren't declaratively set by your Ansible configuration:
tasks:
- ansible.builtin.stat:
path: /path/to/something
register: p
- ansible.builtin.assert:
that:
- p.stat.exists and p.stat.isdir
It is unnecessary to check things like command return codes, as was already stated. Automatic checks are being made by Ansible. Consider using the user module to create a user's existence rather than checking for it.
Because Ansible is a fail-fast system, it will halt the playbook run if there is a problem creating that user. You are not required to look behind it.
Testing LifeCycle

If you include some level of fundamental application validation in your playbooks, they will be executed each time you deploy.
Therefore, prior to your production deploy, deploying into a local development VM and a staging environment will both verify that things are going as planned.
Your workflow might resemble this:
-
Use the exact same playbook everytime with tests that are embedded in the development.
-
Use that playbook for the deployment to a staging environment (with the exact same playbooks) that can simulate the production.
-
Run an integration test battery that is written by your QA team for staging.
- Deploy to the production, with the exact same integrated tests.
If you are a production web service, your QA team should create something akin to an integration test battery. This would typically not be something incorporated in your Ansible playbooks and would include things like Selenium testing or automated API tests.
Although it does make sense to incorporate some fundamental health checks into your playbooks, there are some circumstances in which it would be feasible to run a portion of the QA batteries against remote nodes. The subject of the next section is this.
How to integrate Testing with Rolling Updates

The rolling update method may be expanded, and you can also use the success or failure of that playbook run to decide whether you should add a machine to a load balancer or not.
The major accomplishment of embedded tests is this:
---
- hosts: webservers
serial: 5
pre_tasks:
- name: take out of load balancer pool
ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
roles:
- common
- webserver
- apply_testing_checks
post_tasks:
- name: add back to load balancer pool
ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
We can clearly see, in the example above, that the "take out of the pool" and "add back" stages would be swapped out by calls to the proper shell command or Ansible load balancer module. You might additionally include actions that initiate and close a machine outage window using a monitoring module.
As you can observe from the illustration mentioned above, tests serve as a gate, and if the "apply testing checks" step is skipped, the machine won't be allowed to rejoin the pool.
This method can be changed to remotely run a step against a machine from a testing machine:
---
- hosts: webservers
serial: 5
pre_tasks:
- name: take out of load balancer pool
ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
roles:
- common
- webserver
tasks:
- ansible.builtin.script: /srv/qa_team/app_testing_script.sh --server {{ inventory_hostname }}
delegate_to: testing_server
post_tasks:
- name: add back to load balancer pool
ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
delegate_to: 127.0.0.1
In the aforementioned example, before adding a remote node back to the pool, a script is executed against it from the testing server.
Ansible's automatically created retry file can be used to repeat the deployment only on the few failing servers in the event of a problem, allowing you to fix the issue quickly.
How to achieve Continuous Deployment

The aforementioned methods can be expanded, if desired, to provide continuous deployment procedures.
The process can look like this:
-
Write and use the automation for the deployment of local development VMs.
-
Have a CI system just like Jenkins deployed to a staging environment on each code change.
-
The deploy job calls the testing scripts to pass or fail a build on each deployment.
- If the deployment job succeeds, it runs the same deploy playbook against the production inventory.
The above method is used by some Ansible users to deploy a dozen or more times each hour without bringing down all of their infrastructures. To achieve this level, you must cultivate an automated quality assurance culture.
It can still be beneficial to use the rolling update patterns described in the previous section and add certain fundamental health checks utilizing modules like "script," "stat," "uri," and "assert," even if you are still performing a significant amount of manual quality assurance.
Frequently Asked Questions
Is Ansible free?
Ansible is actually a free and open-source tool. Also, it's pretty straightforward to set up and use.
Is Ansible a programming language?
Ansible is a configuration management tool that employs declarative markup to describe setups. It is implemented in the Python programming language.
Is Ansible easy to learn?
Ansible is quick and straightforward to set up and is effective, dependable, and robust. It runs on Linux, Mac, or BSD.
Should I learn Ansible or Python first?
Despite being developed in Python, Ansible is simple to use; you don't even need to be familiar with the language.
What is Ansible in AWS?
Ansible can be understood as an open-source tool that you can use to automate your AWS deployments.
Conclusion
In this article we studied testing strategies that can be embedded into Ansible playbooks. We also studied the modules that are useful for the implementation of these testing strategies. We have also seen the testing lifecycle and many more things in detail.
We hope that this article has provided you with the help to enhance your knowledge regarding Ansible and if you would like to learn more, check out our articles on Introduction to Ansible, and Ansible Doc.
Refer to our guided paths on Coding Ninjas Studio to learn more about DSA, Competitive Programming, JavaScript, System Design, etc. Enroll in our courses and refer to the mock test and problems available. Take a look at the interview experiences and interview bundle for placement preparations.
Do upvote our blog to help other ninjas grow.
Merry Learning!