ansible facts, loops and conditional judgments

Posted by bombas79 on Sat, 15 Jan 2022 01:58:38 +0100

1. Management facts

1.1 describe ansible facts

The fact that ansible is a variable that ansible automatically detects on the managed host. It can be directly referenced in the playbook

The facts include the following data:

  • Host name
  • Kernel version
  • network interface
  • IP address
  • Operating system version
  • Various environmental variables
  • Number of CPU s
  • Memory provided or available
  • disk space available

With the help of facts, we can more easily retrieve the status of the managed host and determine the operations to be performed based on these data, such as:

  • You can restart the server by running the conditional task based on the fact that it contains the current kernel version of the managed host
  • MySQL configuration files can be customized based on available memory through fact reporting
  • You can set the IPv4 address used in the configuration file based on the value of the fact

Before we run the first task of playbook, ansible will automatically run the setup module to collect the facts.

One way to view the facts collected for the managed host is to run a collection of facts and display ansible using the debug module_ Short playbook of facts variable value.

[root@ansible ansible]# cat playbook/test.yml 
---
- name: test
  hosts: all
  tasks:
    - name: print all facts
      debug:
        var: ansible_facts

Run the playbook, and the facts will be displayed in the output:

[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [test] ***********************************************************************************************
TASK [Gathering Facts] ************************************************************************************ok: [192.168.10.201]

TASK [print all facts] ************************************************************************************ok: [192.168.10.201] => {
    "ansible_facts": {
        "all_ipv4_addresses": [
            "192.168.10.201"
        ],
        "all_ipv6_addresses": [
            "fe80::20c:29ff:fe77:cc9a"
        ],
        "ansible_local": {},
        "apparmor": {
            "status": "disabled"
        },
        "architecture": "x86_64",
        "bios_date": "07/22/2020",
        "bios_version": "6.00",
        "cmdline": {
            "BOOT_IMAGE": "(hd0,msdos1)/vmlinuz-4.18.0-257.el8.x86_64",
            "crashkernel": "auto",
            "quiet": true,
            "rd.lvm.lv": "cs/swap",
            "resume": "/dev/mapper/cs-swap",
            "rhgb": true,
            "ro": true,
            "root": "/dev/mapper/cs-root"
     ....................................................................................Slightly

There are a lot of data we can use.

Here are some facts that we may collect from managed nodes and can use in playbook:
Example of Ansible fact

variablefact
ansible_facts['hostname']Short host name
ansible_facts['fqdn']Fully qualified domain name
ansible_facts['default_ipv4'] ['address']IPv4 address
ansible_facts['interfaces']Name list of all network interfaces
ansible_facts['devices'] ['vda'] ['partitions'] ['vda1'] ['size']/Size of dev/vda1 disk partition
ansible_facts['dns'] ['nameservers']DNS server list
ansible_facts['kernel']Currently running kernel version

When using facts in playbook, Ansible dynamically replaces the variable name of facts with the corresponding value:

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  tasks:
    - name: test print facts
      debug:
        msg: >
          The default IPv4 address of {{ ansible_facts.fqdn }}
          is {{ ansible_facts.default_ipv4.address }}

Run this playbook to see the results

[root@ansible ansible]# ansible-playbook playbook/test.yml

PLAY [test] ***********************************************************************************************
TASK [Gathering Facts] ************************************************************************************ok: [192.168.10.201]

TASK [test print facts] ***********************************************************************************ok: [192.168.10.201] => {
    "msg": "The default IPv4 address of localhost.localdomain is 192.168.10.201\n"
}

PLAY RECAP ************************************************************************************************192.168.10.201             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@ansible ansible]#

1.2 close fact collection

If we don't want to use the data in the fact, we can choose to turn off the fact collection function

---
- name: test
  hosts: all
  gather_facts: no

Just add gather to the playbook_ Facts: No

But even if play is set to gather_facts: no you can also manually collect facts at any time by running a task using the setup module:

---
- name: test
  hosts: all
  gather_facts: no
  tasks:
    - name: get facts
      setup:
    - name: test print facts
      debug:
        msg: >
          The default IPv4 address of {{ ansible_facts.fqdn }}
          is {{ ansible_facts.default_ipv4.address }}

1.3 creating custom facts

In addition to using the facts captured by the system, we can also customize the facts and store them locally on each managed host. These facts are consolidated into a standard list of facts collected when the setup module runs on the managed host. They enable the managed host to provide arbitrary variables to Ansible to adjust the behavior of play.

With custom facts, we can define specific values for the managed host for play to populate the configuration file or run tasks conditionally. Dynamic custom facts allow you to programmatically determine the values of these facts at play runtime, and even determine which facts to provide.

By default, the setup module starts from * * / etc / ansible / facts Load custom facts in the files and scripts in the D directory. The name of each file or script must be in The end of fact * * can only be used. The dynamic custom fact script must output the fact in JSON format and must be an executable file.

The following is a static custom fact file written in INI format. The custom fact file in INI format contains the top-level value defined by a part, followed by the key value pair for the fact to be defined:

[root@localhost ~]# mkdir -p /etc/ansible/facts.d/
[root@localhost ~]# cd /etc/ansible/facts.d/
[root@localhost facts.d]# vim test.fact
[root@localhost facts.d]# cat test.fact 
[packages]
web_package = httpd
db_package = mariadb-server
[root@localhost facts.d]#

Then, when we get the facts of the managed host on the ansible host, the customized facts will be displayed:

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  tasks:
    - name: test 
      debug:
        msg: >
          The package to install on {{ ansible_facts['hostname'] }}
          is {{ ansible_facts['ansible_local']['test']['packages']['web_package'] }}
[root@ansible ansible]# ansible-playbook playbook/test.yml

PLAY [test] ****************************************************************************************
TASK [Gathering Facts] ************************************************************************************ok: [192.168.10.201]

TASK [test] *****************************************************************************************ok: [192.168.10.201] => {
    "msg": "The package to install on localhost is httpd\n"
}

PLAY RECAP *****************************************************************************************192.168.10.201             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

1.4 using magic variables

Some variables are not facts or configured through the setup module, but are also automatically set by Ansible. These magic variables can also be used to obtain information related to a specific managed host.

There are four most commonly used:

Magic variableexplain
hostvarsA variable that contains a managed host and can be used to get the value of a variable of another managed host. If facts have not been collected for the managed host, it will not contain the facts for that host.
group_namesLists all groups to which the currently managed host belongs
groupsLists all groups and hosts in the list
inventory_hostnameContains the host name of the currently managed host configured in the manifest. For various reasons, it may be different from the host name reported in the fact

There are many other "magic variables". For more information, see the following link:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable.
One way to gain insight into their values is to use the debug module to report the contents of the hostvars variable for a specific host:

[root@ansible ansible]# ansible all -m debug -a 'var=hostvars["localhost"]'
192.168.10.201 | SUCCESS => {
    "hostvars[\"localhost\"]": {
        "ansible_check_mode": false,
        "ansible_connection": "local",
        "ansible_diff_mode": false,
        "ansible_facts": {},
        "ansible_forks": 5,
        "ansible_inventory_sources": [
            "/etc/ansible/inventory"
        ],
        "ansible_playbook_python": "/usr/bin/python3.6",
        "ansible_python_interpreter": "/usr/bin/python3.6",
        "ansible_verbosity": 0,
        "ansible_version": {
            "full": "2.9.23",
            "major": 2,
            "minor": 9,
            "revision": 23,
            "string": "2.9.23"
        },
        "group_names": [],
        "groups": {
            "all": [
                "192.168.10.201"
            ],
            "ungrouped": [],
            "web1": [
                "192.168.10.201"
            ]
        },
        "inventory_hostname": "localhost",
        "inventory_hostname_short": "localhost",
        "omit": "__omit_place_holder__cbc8c8b8658480e6dcb5fbc05337c84da4bf90d3",
        "playbook_dir": "/etc/ansible"
    }
}
[root@ansible ansible]#

2. Write cycle and condition tasks

1.1 iterative tasks using loops

By using loops, the task of using the first mock exam is not necessary. For example, instead of writing five tasks to ensure that there are five users, they just need to write one task to iterate over a list of five users to ensure that they all exist.

Ansible supports iterative tasks on a set of projects using the loop keyword. Loops can be configured to repeat tasks using items in the list, the contents of files in the list, a generated sequence of numbers, or a more complex structure.

Let's write a simple loop:

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  tasks:
    - name: test
      user:
        name: "{{ item }}"
        state: present
      loop:
        - tom1
        - tom2
        - tom3
[root@ansible ansible]#

In this way, they can create users one by one. Run the playbook to see the effect:

[root@ansible ansible]# ansible-playbook playbook/test.yml

PLAY [test] ***********************************************************************************************
TASK [Gathering Facts] ************************************************************************************ok: [192.168.10.201]

TASK [test] ***********************************************************************************************changed: [192.168.10.201] => (item=tom1)
changed: [192.168.10.201] => (item=tom2)
changed: [192.168.10.201] => (item=tom3)

PLAY RECAP ************************************************************************************************192.168.10.201             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@ansible ansible]#

We can also provide the list used by loop through a variable. In the following example, the variable users contains the user that needs to be created:

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  vars:
    users:
      - tom1
      - tom2
      - tom3
  tasks:
    - name: test
      user:
        name: "{{ item }}"
        state: present
      loop:
        "{{ users }}"
[root@ansible ansible]#

1.1.1 circular hash or dictionary list

The loop list does not need to be a simple list of values. In the following example, each item in the list is actually a hash or dictionary. Each hash or dictionary in the example has two keys, name and groups. The value of each key in the current item loop variable can be passed through item Name and item Groups variable.

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  tasks:
    - name: test
      user:
        name: "{{ item.name }}"
        state: present
        uid: "{{ item.uid }}"
      loop:
        - name: tom1
          uid: 3000
        - name: tom2
          uid: 3001
        - name: tom3
          uid: 3002
[root@ansible ansible]#

Then we run to see the effect

[root@ansible ansible]# ansible-playbook playbook/test.yml

PLAY [test] *****************************************************************************************

TASK [Gathering Facts] *****************************************************************************************ok: [192.168.10.201]

TASK [test] *****************************************************************************************changed: [192.168.10.201] => (item={'name': 'tom1', 'uid': 3000})
changed: [192.168.10.201] => (item={'name': 'tom2', 'uid': 3001})
changed: [192.168.10.201] => (item={'name': 'tom3', 'uid': 3002})

PLAY RECAP *****************************************************************************************192.168.10.201             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

1.2 running tasks conditionally

Ansible can use conditions to perform tasks or play when specific conditions are met. For example, a condition can be used to determine the available memory on the managed host before ansible installs or configures the service.

We can use conditions to distinguish different managed hosts and assign functional roles according to their conditions. Playbook variables, registered variables, and Ansible facts can all be tested by conditions. You can use operators that compare strings, numeric data, and Boolean values.

The following scenarios illustrate the use of conditions in Ansible:

  • You can define a hard limit (such as min_memory) in the variable and compare it with the available memory on the managed host.
  • Ansible can capture and evaluate the output of a command to determine whether a task has been completed before performing further operations. For example, if a program fails, it will pass through batch processing.
  • The Ansible fact can be used to determine the managed host network configuration and determine the template file to send (such as network binding or relay).
  • You can evaluate the number of CPU s to determine how to properly tune a Web server.
  • Compare the registered variables with predefined variables to determine whether the service has changed. For example, test MD5 of the service configuration file to verify and see if the service has changed.

1.2.1 conditional task statement

The when statement is used to run a task conditionally. It takes the condition to be tested as the value. If the conditions are met, run the task. If the conditions are not met, the task is skipped.

One of the simplest conditions that can be tested is whether a boolean variable is True or False. The when statement in the following example causes the task to run only_ my_ Run when task is True:

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  vars:
    run_my_task: true
  tasks:
    - name: test
      user:
        name: "{{ item.name }}"
        state: absent
      loop:
        - name: tom1
        - name: tom2
        - name: tom3
      when: run_my_task
[root@ansible ansible]# ansible-playbook playbook/test.yml

PLAY [test] *****************************************************************************************

TASK [Gathering Facts] *****************************************************************************************ok: [192.168.10.201]

TASK [test] *****************************************************************************************changed: [192.168.10.201] => (item={'name': 'tom1'})
changed: [192.168.10.201] => (item={'name': 'tom2'})
changed: [192.168.10.201] => (item={'name': 'tom3'})

PLAY RECAP *****************************************************************************************192.168.10.201             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@ansible ansible]#

The following example tests my_ Whether the service variable has a value. If there is a value, it will be my_ The value of service is used as the name of the package to install. If my is not defined_ Service variable, the task is skipped and no error is displayed.

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  vars:
    my_service: httpd
  tasks:
    - name: test
      yum:
        name: "{{ my_service }}"
        state: latest
      when: my_service is defined
[root@ansible ansible]# ansible-playbook playbook/test.yml

PLAY [test] *****************************************************************************************
TASK [Gathering Facts] 

*****************************************************************************************ok: [192.168.10.201]

TASK [test] *****************************************************************************************changed: [192.168.10.201]

PLAY RECAP *****************************************************************************************192.168.10.201             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

The following table shows some operations that can be used when processing conditions:
Example conditions

operationExample
Equal to (value is string)ansible_machine == "x86_64"
Equal to (value is numeric)max_memory == 512
less thanmin_memory < 128
greater thanmin_memory > 256
Less than or equal tomin_memory <= 256
Greater than or equal tomin_memory >= 512
Not equal tomin_memory != 512
Variable existsmin_memory is defined
Variable does not existmin_memory is not defined
The boolean variable is true. 1. True or yes evaluates to truememory_available
The boolean variable is False. 0, False, or no evaluate to Falsenot memory_available
The value of the first variable exists as the value in the list of the second variableansible_distribution in supported_distros

Multiple test conditions

A when statement can be used to evaluate multiple conditions. Use the and and or keywords to combine conditions and use parentheses to group conditions.

If the conditional statement is satisfied when any of the conditions is true, the or statement should be used. For example, if the computer is running Red Hat Enterprise linux or Fedora, the following conditions are met:

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  vars:
    my_service: httpd
  tasks:
    - name: test
      yum:
        name: "{{ my_service }}"
        state: latest
      when: ansible_distribution == "Redhat" or ansible_distribution == "Fedora"
[root@ansible ansible]# ansible-playbook playbook/test.yml

PLAY [test] *****************************************************************************************
TASK [Gathering Facts] 

*****************************************************************************************ok: [192.168.10.201]

TASK [test] *****************************************************************************************skipping: [192.168.10.201]		##Because I don't use Redhat or Fedora system, I skip it

PLAY RECAP *****************************************************************************************192.168.10.201             : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   

[root@ansible ansible]#

When using the and operation, both conditions must be true to satisfy the entire conditional statement. For example, if the remote host is Red Hat Enterprise Linux 7 5 host, and the installed kernel is the specified version, the following conditions will be met:

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  vars:
    my_service: httpd
  tasks:
    - name: test
      yum:
        name: "{{ my_service }}"
        state: latest
      when: ansible_distribution_version == "7.5" and ansible_kernel == "3.10.0-327.el7.x86_64"		
[root@ansible ansible]#

It can also be written as follows:

when:
  - ansible_distribution_version == "7.5"
  - ansible_kernel == "3.10.0-327.el7.x86_64"

This way is more readable

1.3 combined cycles and conditional tasks

Loops and conditions can be used in combination.

In the following example, the yum module will install the MariaDB Server package as long as the file system mounted on / has more than 300MB of free space. ansible_ Mount facts are a set of dictionaries that represent facts about a mounted file system. For each dictionary in the loop iteration list, the condition statement is satisfied only when the dictionary representing the mounted file system whose two conditions are true is found.

[root@ansible ansible]# cat playbook/test.yml
---
- name: test
  hosts: all
  vars:
    my_service: httpd
  tasks:
    - name: test
      yum:
        name: mariadb-server
        state: latest
      loop: "{{ ansible_mounts }}"
      when: item.mount == "/" and item.size_available > 300000000
[root@ansible ansible]#

When you combine when with loop for a task, the when statement is checked for each item.

Topics: Operation & Maintenance