LoginSignup
14
13

More than 5 years have passed since last update.

Ansibleで物理NICのリストを取得する方法

Posted at

Ansibleで物理NICのリストを取得する方法

何がしたいのか

大昔は、 eth.* を狙えば全ての物理ネットワークインタフェース名に当たるという時代でした。が、Consistent Network Device NamingやらPredictable Network Interface Namesが入り乱れる現代では、ネットワークインタフェース名を正規表現で当てに行くのは怪しいと言えます(もちろんCNDNやPNINを無効にすれば過去には戻れます)。ということで、現代風に物理ネットワークインタフェース名のリストをAnsibleで作ってみます。

前提として ansible.cfg とインベントリは予め作っておきます。

ansible.cfg
[defaults]
inventory = 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 とかが邪魔です。物理ネットワークインタフェースだけを抽出する手がかりがないか、setupモジュールで取れるfactsをひと通り眺めてみます。

setupモジュールで取れるfacts

長いのでだいぶ省略しています
[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_{{ ネットワークインタフェース名 }}.typeether であるものも物理インタフェースっぽいですが、反例が存在します。

[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 が存在する ネットワークインターフェース名 をリストにする事にします。

set_factwith_items

試しに、 ansible_interfaceswith_items で回しながら、 ansible_{{ ネットワークインタフェース名 }}.module が存在するものを set_fact してみます。

foo.yaml
---

- 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

set_fact タスクは when に従って lodocker0 をskipしていますが、 with_items で回したら set_fact が勝手にリストを作るなんて事はないようで、 pif_name の中身は期待通りではありません。

set_fact の結果を見てみる

set_fact タスクの結果を register して見てみましょう。

foo.yaml
---

- 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

余計なものが多いですが、 register した結果をどうにかすれば出来そうです。

set_fact の結果から要るものだけを取り出す

まず、前のタスクで when にヒットしたものだけを取り出します。方針として

  1. skipped が定義されていないもの
  2. skipped が定義されていないもの、または skipped が定義されていて true でないもの
  3. ansible_facts が定義されているもの

の3つがありそうですが、1はもしかしたら将来 skipしなかったタスクに skippedfalse で付く という仕様になるかもしれないのでパス、2は確実ですが条件式の記述が3よりめんどくさそうなので、3を使います。

foo.yaml
---

- 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

ジェネレータが返ってきてしまったのでリストにしてみます。
ついでに長くなってきたので改行しましょう。

foo.yaml
---

- 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 の各辞書から itemansible_facts.pif_name を取り出してそれだけをリストにすれば良さそうです。

foo.yaml
---

- 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

これでリストなりましたが、場合によってはうまくいきません。

caveat: Ansibleのfactのキー名

[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のキーを replace している為、最初の set_factwhen にヒットしなくなってしまうからです。探すキーを replace したものにしてあげましょう。

foo.yaml
---

- 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 のパクリです。

14
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13