如何使用Ansible获取物理网卡列表

如何通过Ansible获取物理NIC的列表

你想做什么?

过去,当我们针对 eth.* 进行配置时,它适用于所有物理网络接口的名称。然而,在如今纷乱的时代中,出现了 Consistent Network Device Naming(CNDN)和 Predictable Network Interface Names(PNIN)等方式,因此使用正则表达式来匹配网络接口名称可能会有些不保险(当然,如果禁用CNDN和PNIN,就可以回到过去)。因此,我们将尝试使用Ansible以现代的方式创建物理网络接口名称列表。

首先,需要预先创建ansible.cfg和清单。

[defaults]
inventory = inventory
localhost ansible_connection=local

查看ansible_interfaces

[root@localhost ~]# ansible all -m setup -a 'filter=ansible_interfaces'
localhost | success >> {
    "ansible_facts": {
        "ansible_interfaces": [
            "lo",
            "docker0",
            "eth1",
            "eth0"
        ]
    },
    "changed": false
}

暫時總結,已經獲取了所有網路介面的名稱,但是 lo 和 docker0 有點礙事。我們是否有辦法提取只有物理網路介面的線索,並在設置模塊中檢視可以得到的全部facts。

在setup模块中可以获取的事实

[root@localhost ~]# ansible all -m setup
localhost | success >> {
    "ansible_facts": {
...
        "ansible_docker0": {
            "active": false,
            "device": "docker0",
            "id": "8000.56847afe9799",
            "interfaces": [],
            "ipv4": {
                "address": "192.168.123.1",
                "netmask": "255.255.255.0",
                "network": "192.168.123.0"
            },
            "macaddress": "56:84:7a:fe:97:99",
            "mtu": 1500,
            "promisc": false,
            "stp": false,
            "type": "bridge"
        },
...
        "ansible_eth0": {
            "active": true,
            "device": "eth0",
            "ipv4": {
                "address": "10.0.2.15",
                "netmask": "255.255.255.0",
                "network": "10.0.2.0"
            },
            "ipv6": [
                {
                    "address": "fe80::a00:27ff:fe96:8299",
                    "prefix": "64",
                    "scope": "link"
                }
            ],
            "macaddress": "08:00:27:96:82:99",
            "module": "virtio_net",
            "mtu": 1500,
            "promisc": false,
            "type": "ether"
        },
        "ansible_eth1": {
            "active": true,
            "device": "eth1",
            "ipv4": {
                "address": "192.168.56.2",
                "netmask": "255.255.255.0",
                "network": "192.168.56.0"
            },
            "ipv6": [
                {
                    "address": "fe80::a00:27ff:fe66:2832",
                    "prefix": "64",
                    "scope": "link"
                }
            ],
            "macaddress": "08:00:27:66:28:32",
            "module": "virtio_net",
            "mtu": 1500,
            "promisc": false,
            "type": "ether"
        },
...
        "ansible_interfaces": [
            "lo",
            "docker0",
            "eth1",
            "eth0"
        ],
...
        "ansible_lo": {
            "active": true,
            "device": "lo",
            "ipv4": {
                "address": "127.0.0.1",
                "netmask": "255.0.0.0",
                "network": "127.0.0.0"
            },
            "ipv6": [
                {
                    "address": "::1",
                    "prefix": "128",
                    "scope": "host"
                }
            ],
            "mtu": 65536,
            "promisc": false,
            "type": "loopback"
        },
...
    },
    "changed": false
}

最好的选择似乎是存在 ansible_{{ 网络接口名称 }}.module 的选项。

在上面的例子中,ansible_{{ 网络接口名称 }}.type 为ether的也可能是物理接口,但是也存在反例。

[root@localhost ~]# ip link add name veth_left type veth peer name veth_right
[root@localhost ~]# ansible all -m setup -a 'filter=ansible_veth_left'
localhost | success >> {
    "ansible_facts": {
        "ansible_veth_left": {
            "active": false,
            "device": "veth_left",
            "macaddress": "aa:19:3b:6d:4f:b6",
            "mtu": 1500,
            "promisc": false,
            "type": "ether"
        }
    },
    "changed": false
}

[root@localhost ~]# ip link delete dev veth_left

决定将存在 ansible_{{ ネットワークインタフェース名 }}.module 的网络接口名列入列表。

设置事实和使用项

让我们尝试使用 with_items 迭代 ansible_interfaces,同时检查 ansible_{{ 网络接口名称 }}.module 是否存在,然后使用 set_fact 进行设定。

---

- hosts: localhost
  tasks:
    - set_fact:
        pif_name: "{{ item }}"
      when: "ansible_{{ item }}.module is defined"
      with_items: ansible_interfaces

    - debug: var=pif_name
[root@localhost ~]# ansible-playbook foo.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [set_fact ] *************************************************************
skipping: [localhost] => (item=lo)
skipping: [localhost] => (item=docker0)
ok: [localhost] => (item=eth1)
ok: [localhost] => (item=eth0)

TASK: [debug var=pif_name] ****************************************************
ok: [localhost] => {
    "var": {
        "pif_name": "eth0"
    }
}

PLAY RECAP ********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

设置事实是,根据条件“when”跳过“lo”和“docker0”。但是,如果使用“with_items”进行循环,设置事实不会自动创建列表,所以“pif_name”的内容并不如预期。

查看 set_fact 的结果

让我们使用 register 将任务结果设置为一个事实来查看。

---

- hosts: localhost
  tasks:
    - set_fact:
        pif_name: "{{ item }}"
      register: set_fact_result
      when: "ansible_{{ item }}.module is defined"
      with_items: ansible_interfaces

    - debug: var=set_fact_result
[root@localhost ~]# ansible-playbook foo.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [set_fact ] *************************************************************
skipping: [localhost] => (item=lo)
skipping: [localhost] => (item=docker0)
ok: [localhost] => (item=eth1)
ok: [localhost] => (item=eth0)

TASK: [debug var=set_fact_result] *********************************************
ok: [localhost] => {
    "var": {
        "set_fact_result": {
            "changed": false,
            "msg": "All items completed",
            "results": [
                {
                    "changed": false,
                    "skipped": true
                },
                {
                    "changed": false,
                    "skipped": true
                },
                {
                    "ansible_facts": {
                        "pif_name": "eth1"
                    },
                    "invocation": {
                        "module_args": "",
                        "module_name": "set_fact"
                    },
                    "item": "eth1"
                },
                {
                    "ansible_facts": {
                        "pif_name": "eth0"
                    },
                    "invocation": {
                        "module_args": "",
                        "module_name": "set_fact"
                    },
                    "item": "eth0"
                }
            ]
        }
    }
}

PLAY RECAP ********************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

虽然有很多多余的东西,但如果能够处理注册的结果,似乎可以实现。

从set_fact的结果中提取所需的项目

首先,根据先前的任务,只提取与”when”相符的内容。作为方针。

    1. 跳过被未定义的事物。

 

    1. 跳过被未定义或定义为false的事物。

 

    定义了ansible_facts的事物。

可能有三种选项,但是第一种可能在将来被跳过的任务将会带有”skipped”为false的规范,所以跳过;第二种是确定的,但是与第三种相比条件语句的编写可能比较麻烦,所以选择第三种。

---

- hosts: localhost
  tasks:
    - set_fact:
        pif_name: "{{ item }}"
      register: set_fact_result
      when: "ansible_{{ item }}.module is defined"
      with_items: ansible_interfaces

    - set_fact:
        pif_names: "{{ set_fact_result.results|selectattr('ansible_facts', 'defined') }}"

    - debug: var=pif_names
[root@localhost ~]# ansible-playbook foo.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [set_fact ] *************************************************************
skipping: [localhost] => (item=lo)
skipping: [localhost] => (item=docker0)
ok: [localhost] => (item=eth1)
ok: [localhost] => (item=eth0)

TASK: [set_fact ] *************************************************************
ok: [localhost]

TASK: [debug var=pif_names] ***************************************************
ok: [localhost] => {
    "var": {
        "pif_names": "<generator object _select_or_reject at 0x22995a0>"
    }
}

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0

鉴于生成器返回了,那么我们把它转为列表吧。
顺带一提,由于句子变长了,我们来换行一下吧。

---

- hosts: localhost
  tasks:
    - set_fact:
        pif_name: "{{ item }}"
      register: set_fact_result
      when: "ansible_{{ item }}.module is defined"
      with_items: ansible_interfaces

    - set_fact:
        pif_names: >-
          {{ set_fact_result.results
             | selectattr('ansible_facts', 'defined')
             | list }}

    - debug: var=pif_names
[root@localhost ~]# ansible-playbook foo.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [set_fact ] *************************************************************
skipping: [localhost] => (item=lo)
skipping: [localhost] => (item=docker0)
ok: [localhost] => (item=eth1)
ok: [localhost] => (item=eth0)

TASK: [set_fact ] *************************************************************
ok: [localhost]

TASK: [debug var=pif_names] ***************************************************
ok: [localhost] => {
    "var": {
        "pif_names": [
            {
                "ansible_facts": {
                    "pif_name": "eth1"
                },
                "invocation": {
                    "module_args": "",
                    "module_name": "set_fact"
                },
                "item": "eth1"
            },
            {
                "ansible_facts": {
                    "pif_name": "eth0"
                },
                "invocation": {
                    "module_args": "",
                    "module_name": "set_fact"
                },
                "item": "eth0"
            }
        ]
    }
}

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0

已经变得相当像了。剩下的工作就是从 pif_names 的每个字典中提取 item 或 ansible_facts.pif_name,将它们放入一个列表中应该就可以了。

---

- hosts: localhost
  tasks:
    - set_fact:
        pif_name: "{{ item }}"
      register: set_fact_result
      when: "ansible_{{ item }}.module is defined"
      with_items: ansible_interfaces

    - set_fact:
        pif_names: >-
          {{ set_fact_result.results
             | selectattr('ansible_facts', 'defined')
             | map(attribute='ansible_facts.pif_name')
             | list }}

    - debug: var=pif_names
[root@localhost ~]# ansible-playbook foo.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [set_fact ] *************************************************************
skipping: [localhost] => (item=lo)
skipping: [localhost] => (item=docker0)
ok: [localhost] => (item=eth1)
ok: [localhost] => (item=eth0)

TASK: [set_fact ] *************************************************************
ok: [localhost]

TASK: [debug var=pif_names] ***************************************************
ok: [localhost] => {
    "var": {
        "pif_names": [
            "eth1",
            "eth0"
        ]
    }
}

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0

这样生成了一个列表,但是根据情况可能不会成功。

注意事项:Ansible的facts的键名

[root@localhost ~]# ifdown eth0
[root@localhost ~]# ip link set dev eth0 name bar-eth0
[root@localhost ~]# ansible-playbook foo.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [set_fact ] *************************************************************
skipping: [localhost] => (item=lo)
skipping: [localhost] => (item=bar-eth0)
ok: [localhost] => (item=eth1)
skipping: [localhost] => (item=docker0)

TASK: [set_fact ] *************************************************************
ok: [localhost]

TASK: [debug var=pif_names] ***************************************************
ok: [localhost] => {
    "var": {
        "pif_names": [
            "eth1"
        ]
    }
}

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0

bar-eth0没有出现。这是因为Ansible替换了fact的键,导致第一个set_fact的when条件不再匹配。让我们将要搜索的键替换为其他内容。

---

- hosts: localhost
  tasks:
    - set_fact:
        pif_name: "{{ item }}"
      register: set_fact_result
      when: "ansible_{{ item|replace('-', '_') }}.module is defined"
      with_items: ansible_interfaces

    - set_fact:
        pif_names: >-
          {{ set_fact_result.results
             | selectattr('ansible_facts', 'defined')
             | map(attribute='ansible_facts.pif_name')
             | list }}

    - debug: var=pif_names
[root@localhost ~]# ansible-playbook foo.yaml

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [set_fact ] *************************************************************
skipping: [localhost] => (item=lo)
ok: [localhost] => (item=bar-eth0)
ok: [localhost] => (item=eth1)
skipping: [localhost] => (item=docker0)

TASK: [set_fact ] *************************************************************
ok: [localhost]

TASK: [debug var=pif_names] ***************************************************
ok: [localhost] => {
    "var": {
        "pif_names": [
            "bar-eth0",
            "eth1"
        ]
    }
}

PLAY RECAP ********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0

在这里,bar-eth0 出现了。

请您提供以下信息作为参考。

请您提供以下内容作为参考。

这个逻辑是 Stack Overflow 的山寨品。

广告
将在 10 秒后关闭
bannerAds