Ansible is a great tool for configuring servers to the state you desire. You can create a playbook, and if correctly written, it always yields the same state no matter how many times you run it. This is called idempotency. You can also run an Ansible playbook with the --check option and verify what the playbook would change if it were run so that you don't unexpectedly make a change when you do not want to.
But what if you need to verify the configuration of existing servers that were not originally configured with Ansible? There are tools out there for this purpose, such as Chef's InSpec, testinfra, and serverspec. But, if you already know Ansible, you can use some of its built-in functionality to do this.
Example 1
In this example, I have six hosts of various Linux distributions and versions. Because the actual memory reported can vary, I'm going to check for memory between 800 MB and 1100 MB. The goal is for hosts that meet the criteria to pass with changed=0 and hosts that fail the criteria to output changed=1:
- name: assert
hosts: all
tasks:
- name: check if memory is between 800 and 1100MB
assert:
that:
- ansible_memtotal_mb | int >= 800
- ansible_memtotal_mb | int <= 1100
fail_msg: "Memory is {{ ansible_memtotal_mb }}MB not 1024MB"
register: result
changed_when:
- result.evaluated_to is defined
- result.evaluated_to == False
failed_when: False
The results:
[kpirkle@defiant config-verify]$ ansible-playbook assert.yml
PLAY [assert] ****************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [wookie]
ok: [ewok]
ok: [centurion]
ok: [web]
ok: [venture]
ok: [c3po]
TASK [check if memory is between 800 and 1100MB] *****************************************************************************************************************************************************************
ok: [venture] => {
"changed": false,
"failed_when_result": false,
"msg": "All assertions passed"
}
ok: [ewok] => {
"changed": false,
"failed_when_result": false,
"msg": "All assertions passed"
}
ok: [web] => {
"changed": false,
"failed_when_result": false,
"msg": "All assertions passed"
}
changed: [centurion] => {
"assertion": "ansible_memtotal_mb | int <= 1100",
"changed": true,
"evaluated_to": false,
"failed_when_result": false,
"msg": "Memory is 7737MB not 1024MB"
}
ok: [wookie] => {
"changed": false,
"failed_when_result": false,
"msg": "All assertions passed"
}
ok: [c3po] => {
"changed": false,
"failed_when_result": false,
"msg": "All assertions passed"
}
PLAY RECAP *******************************************************************************************************************************************************************************************************
c3po : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
centurion : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ewok : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
venture : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
wookie : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
assert
The function of the assert module, per the documentation, is to "assert given expressions are true."
In this case, five of the six passed - one failed because host centurion had more memory than the range I was checking. I used the fail_msg option of the assert module to provide useful information as to why it failed. There are also options to always display a custom message with msg and to pass a custom message when the assertion passes called success_msg.
changed_when
The changed_when option is not a module but rather a built-in function that alters how error handling is done for a task. In this case, we are saving the results from the assert test to the result variable. We then alter the changed value based on two tests: 1) does the evaluated_to variable exist, and 2) is it false? If it exists and is false, we know the test failed, and we return the result as "changed."
failed_when
To prevent the play from failing and ending the run of the playbook, set the failed_when option to false.
Example 2
Another useful configuration comparison strategy is to use a checksum tool like md5sum to compare files.
---
- name: compare
hosts: all
gather_facts: no
vars:
std_conf_crontab: 'c39252b11aad842fcb75e05c6a27eef8'
std_conf_lvm: '2d90187abd40dbcb6fc6de41640fd022'
std_conf_resolv: 'db323688118c844a76ebd6c70508b434'
tasks:
- name: compare config files
stat:
path: '{{ item.file }}'
checksum_algorithm: md5
register: result
changed_when: item.md5sum != result.stat.checksum
failed_when: False
loop:
- { file: /etc/crontab, md5sum: '{{ std_conf_crontab }}' }
- { file: /etc/lvm/lvm.conf, md5sum: '{{ std_conf_lvm }}' }
- { file: /etc/resolv.conf, md5sum: '{{ std_conf_resolv }}'}
The result:
[kpirkle@defiant config-verify]$ ansible-playbook compare.yml
PLAY [compare] ****************************************************************************************************************************
TASK [compare config files] ***************************************************************************************************************
changed: [wookie] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [centurion] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [web] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
changed: [wookie] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [centurion] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [venture] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [wookie] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [ewok] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [centurion] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [web] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
changed: [venture] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [web] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
changed: [ewok] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [ewok] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [venture] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [c3po] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
changed: [c3po] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [c3po] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
PLAY RECAP ********************************************************************************************************************************
c3po : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
centurion : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ewok : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
venture : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
wookie : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
In this example, we are setting variables for the md5sums of three files that we want to check for consistency. Our task then uses the stat module to calculate the MD5 checksum of each file and compare it to the defined variable. Once again, we are using the changed_when function to make the comparison and render a "changed" state when there isn't a match.
Example 3
My final example is a way to check the contents of a file for a specific item. One way to do this is by using the shell module and using grep.
---
- name: grep
hosts: all
gather_facts: no
tasks:
- name: grep for nameserver
shell: grep 'nameserver 192.168.0.1' /etc/resolv.conf
register: result
changed_when: result.rc != 0
failed_when: False
The result:
[kpirkle@defiant config-verify]$ ansible-playbook grep.yml
PLAY [grep] *******************************************************************************************************************************
TASK [grep for nameserver] ****************************************************************************************************************
ok: [wookie]
ok: [web]
ok: [centurion]
ok: [ewok]
changed: [venture]
ok: [c3po]
PLAY RECAP ********************************************************************************************************************************
c3po : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
centurion : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ewok : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
venture : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
wookie : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Here again, we are using the combination of changed_when and failed_when to manipulate the output to be useful. By registering the output of grep to the result variable, we can then check the return code of the command and find out if the desired nameserver string is present. More complex commands can be used if needed.
Wrap up
Hopefully, others find these tips useful. You can use Ansible as not only a tool to configure servers to the desired state but also as an investigative tool to verify the configuration of servers or possibly as an ad-hoc monitoring tool.
[ Need more on Ansible? Take a free technical overview course from Red Hat. Ansible Essentials: Simplicity in Automation Technical Overview. ]
Sobre o autor
Kent is a Linux Systems Engineer with over 20 years of experience with Linux and UNIX systems. His current focus is on Ansible, automation, and infrastructure-as-code. He is a member of the Red Hat Accelerators and is a Red Hat Certified Engineer.
Mais como este
A 5-step playbook for unified automation and AI
AI ambitions meet automation reality: The case for a unified automation platform
Technically Speaking | Taming AI agents with observability
Transforming Your Secrets Management | Code Comments
Navegue por canal
Automação
Últimas novidades em automação de TI para empresas de tecnologia, equipes e ambientes
Inteligência artificial
Descubra as atualizações nas plataformas que proporcionam aos clientes executar suas cargas de trabalho de IA em qualquer ambiente
Nuvem híbrida aberta
Veja como construímos um futuro mais flexível com a nuvem híbrida
Segurança
Veja as últimas novidades sobre como reduzimos riscos em ambientes e tecnologias
Edge computing
Saiba quais são as atualizações nas plataformas que simplificam as operações na borda
Infraestrutura
Saiba o que há de mais recente na plataforma Linux empresarial líder mundial
Aplicações
Conheça nossas soluções desenvolvidas para ajudar você a superar os desafios mais complexos de aplicações
Virtualização
O futuro da virtualização empresarial para suas cargas de trabalho on-premise ou na nuvem