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. ]


关于作者

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.  

UI_Icon-Red_Hat-Close-A-Black-RGB

按频道浏览

automation icon

自动化

有关技术、团队和环境 IT 自动化的最新信息

AI icon

人工智能

平台更新使客户可以在任何地方运行人工智能工作负载

open hybrid cloud icon

开放混合云

了解我们如何利用混合云构建更灵活的未来

security icon

安全防护

有关我们如何跨环境和技术减少风险的最新信息

edge icon

边缘计算

简化边缘运维的平台更新

Infrastructure icon

基础架构

全球领先企业 Linux 平台的最新动态

application development icon

应用领域

我们针对最严峻的应用挑战的解决方案

Virtualization icon

虚拟化

适用于您的本地或跨云工作负载的企业虚拟化的未来