ansible的事实,循环和条件判断

1. 管理事实

1.1描述ansible事实

​ ansible的事实是ansible在受管主机上自动检测到的变量。可以直接再playbook中直接引用的

事实中包括一下一些数据:

  • 主机名称
  • 内核版本
  • 网络接口
  • IP地址
  • 操作系统版本
  • 各种环境变量
  • CPU数量
  • 提供的或可用的内存
  • 可用磁盘空间

借助事实,我们可以更加方便的检索受管主机的状态,并根据这些数据来确定要执行的操作,例如:

  • 可以根据含有受管主机当前内核版本的事实运行条件任务,以此来重启服务器
  • 可以根据通过事实报告的可用内存来自定义MySQL配置文件
  • 可以根据事实的值设置配置文件中使用的IPv4地址

在我们运行playbook的第一个任务之前,ansible会自动运行setup模块来收集事实。

查看为受管主机收集的事实的一种方式是,运行一个收集事实并使用debug模块显示ansible_facts变量值的简短playbook。

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

运行该playbook,则事实就会显示在输出内容中:

[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"
     …………………………………………………………………………略略

其中的内容有很多我们可以用到的数据。

下面有一些我们可能从受管节点收集的并可在playbook中使用的一些事实:
Ansible事实的示例

变量事实
ansible_facts[‘hostname’]短主机名
ansible_facts[‘fqdn’]完全限定域名
ansible_facts[‘default_ipv4’] [‘address’]IPv4地址
ansible_facts[‘interfaces’]所有网络接口的名称列表
ansible_facts[‘devices’] [‘vda’] [‘partitions’] [‘vda1’] [‘size’]/dev/vda1磁盘分区的大小
ansible_facts[‘dns’] [‘nameservers’]DNS服务器列表
ansible_facts[‘kernel’]当前运行的内核版本

在playbook中使用事实时,Ansible将事实的变量名动态替换为对应的值:

[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 }}

运行此playbook查看结果

[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 关闭事实收集

​ 如果我们不想用到事实中的数据,我们可以选择关闭事实收集的功能

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

只需要在playbook中加入gather_facts: no即可

不过即使play设置了gather_facts: no 也可以随时通过运行使用setup模块的任务来手动收集事实:

---
- 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创建自定义事实

​ 除了使用系统捕获的事实外,我们还可以自定义事实,并将其本地存储在每个受管主机上。这些事实整合到setup模块在受管主机上运行时收集的标准事实列表中。它们让受管主机能够向Ansible提供任意变量,以用于调整play的行为。

​ 有了自定义事实,我们可以为受管主机定义特定的值,供play用于填充配置文件或有条件地运行任务。动态自定义事实允许在play运行时以编程方式确定这些事实的值,甚至还可以确定提供哪些事实。

​ 默认情况下,setup模块从各受管主机的**/etc/ansible/facts.d目录下的文件和脚本中加载自定义事实。各个文件或脚本的名称必须以.fact**结尾才能被使用。动态自定义事实脚本必须输出JSON格式的事实,而且必须是可执行文件。

​ 以下是采用INI格式编写的静态自定义事实文件。INI格式的自定义事实文件包含由一个部分定义的顶层值,后跟用于待定义的事实的键值对:

[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]#

然后我们在ansible主机上获取受管主机的事实时,就会显示我们所自定义的事实:

[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 使用魔法变量

一些变量并非事实或通过setup模块配置,但也由Ansible自动设置。这些魔法变量也可用于获取与特定受管主机相关的信息。

最常用的有四个:

魔法变量说明
hostvars包含受管主机的变量,可以用于获取另一台受管主机的变量的值。 如果还没有为受管主机收集事实,则它不会包含该主机的事实。
group_names列出当前受管主机所属的所有组
groups列出清单中的所有组和主机
inventory_hostname包含清单中配置的当前受管主机的主机名称。 因为各种原因有可能与事实报告的主机名称不同

另外还有许多其他的“魔法变量”。有关更多信息,请参见以下链接:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable。
若要深入了解它们的值,一个途径是使用debug模块报告特定主机的hostvars变量的内容:

[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. 编写循环和条件任务

1.1 利用循环迭代任务

​ 通过利用循环,我们无需编写多个使用同一模块的任务。例如,他们不必编写五个任务来确保存在五个用户,而是只需编写一个任务来对含有五个用户的列表迭代,从而确保它们都存在。

​ Ansible支持使用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]#

这样就可以让他们进行逐一的创建用户,执行一下这个playbook看看效果:

[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]#

我们也可以通过一个变量提供loop所使用的列表。在以下示例中,变量users含有需要创建的用户:

[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 循环散列或字典列表

loop列表不需要是简单值列表。在以下示例中,列表中的每个项实际上是散列或字典。示例中的每个散列或字典具有两个键,即namegroups,当前item循环变量中每个键的值可以分别通过item.nameitem.groups变量来检索。

[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]#

然后我们运行查看效果

[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 有条件地运行任务

​ Ansible可使用conditionals在符合特定条件时执行任务或play。例如,可以利用一个条件在Ansible安装或配置服务前确定受管主机上的可用内存。

​ 我们可以利用条件来区分不同的受管主机,并根据它们所符合的条件来分配功能角色。Playbook变量、注册的变量和Ansible事实都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。

以下场景说明了在Ansible中使用条件的情况:

  • 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
  • Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
  • 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
  • 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
  • 将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。
1.2.1 条件任务语句

when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。

可以测试的一个最简单条件是某一布尔变量是True还是False。以下示例中的when语句导致任务仅在run_my_taskTrue时运行:

[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]#

以下示例测试my_service变量是否具有值。若有值,则将my_service的值用作要安装的软件包的名称。如果未定义my_service变量,则跳过任务且不显示错误。

[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   

下表显示了在处理条件时可使用的一些运算:
示例条件

操作示例
等于(值为字符串)ansible_machine == “x86_64”
等于(值为数字)max_memory == 512
小于min_memory < 128
大于min_memory > 256
小于等于min_memory <= 256
大于等于min_memory >= 512
不等于min_memory != 512
变量存在min_memory is defined
变量不存在min_memory is not defined
布尔变量是True。1、True或yes的求值为Truememory_available
布尔变量是False。0、False或no的求值为Falsenot memory_available
第一个变量的值存在,作为第二个变量的列表中的值ansible_distribution in supported_distros
1.2.2 测试多个条件

一个when语句可用于评估多个条件。使用andor关键字组合条件,并使用括号分组条件。

如果任一条件为真时满足条件语句,则应当使用or语句。例如,如果计算机上运行的是红帽企业linux或Fedora,则下述条件得到满足:

[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]		##因为我用的不是Redhat or Fedora系统,所以跳过

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

[root@ansible ansible]#

使用and运算时,两个条件都必须为真,才能满足整个条件语句。例如,如果远程主机是红帽企业Linux7.5主机,并且安装的内核是指定版本,则将满足以下条件:

[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]#

也可以这样写:

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

这样的方式更加的可读

1.3 组合循环和有条件任务

​ 循环和条件可以组合使用。

​ 在下例中,yum模块将安装mariadb-server软件包,只要/上挂载的文件系统具有超过300MB的可用空间。ansible_mounts事实是一组字典,各自代表一个已挂载文件系统的相关事实。循环迭代列表中每一字典,只有找到了代表两个条件都为真的已挂载文件系统的字典时,条件语句才得到满足。

[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]#

对某个任务结合使用whenloop时,将对每个项检查when语句。