Ansibleのダイナミックインベントリ機能を使って、NetBoxに登録しているホスト情報をAnsibleのターゲットホストとして利用してみる。
(ちなみにダイナミックインベントリ機能は書籍とかで存在は知ってはいたけど、実際に手を動かして使ってみるのが今回初めてという…)
NetBoxのdynamic inventory
NetBoxのAPIを使ったダイナミックインベントリは最低限以下の通り。
plugin: netbox.netbox.nb_inventory api_endpoint: http://192.168.0.19:28080 token: 0123456789abcdef0123456789abcdef01234567
まずはこれらが設定されていればOK
plugin
の指定。api_endpoint
にNetBoxのエンドポイントtoken
にトークン
playbook
とりあえずall
に対してping
モジュールを実行。
--- - name: dynamic inventory sample hosts: all gather_facts: false tasks: - name: ping ping:
実行
ここまでとても雑だけど、この内容でansible-playbook
を実行するとこの通り。
(a2.10) [zaki@cloud-dev netbox (master)]$ ansible-playbook -i netbox-dynamic-inventory.yml netbox-dynamic-inventory-sample.yml | cat PLAY [dynamic inventory sample] ************************************************ TASK [ping] ******************************************************************** fatal: [esxi]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: Permission denied (publickey,keyboard-interactive).' unreachable: true fatal: [instance-20200126-1250]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: Could not resolve hostname instance-20200126-1250: Name or service not known' unreachable: true fatal: [instance-20200127-2223]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: Could not resolve hostname instance-20200127-2223: Name or service not known' unreachable: true [DEPRECATION WARNING]: Distribution Ubuntu 18.04 on host cheddar should use /usr/bin/python3, but is using /usr/bin/python for backward compatibility with prior Ansible releases. A future Ansible release will default to using the discovered platform python for this host. See https://docs.ansible.com/ansible/ 2.10/reference_appendices/interpreter_discovery.html for more information. This feature will be removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. ok: [cheddar] ok: [cloud-dev] fatal: [client-dev]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.17 port 22: No route to host' unreachable: true fatal: [k8s-master01]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.121 port 22: No route to host' unreachable: true fatal: [k8s-master02]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.122 port 22: No route to host' unreachable: true ok: [kubernetes-master] ok: [kubernetes-worker0] ok: [kubernetes-worker1] ok: [manager] fatal: [k8s-master03]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.123 port 22: No route to host' unreachable: true ok: [manager-dev] ok: [registry] fatal: [k8s-worker01]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.125 port 22: No route to host' unreachable: true ok: [rhel8] fatal: [k8s-worker02]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.126 port 22: No route to host' unreachable: true fatal: [rhel7]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.26 port 22: No route to host' unreachable: true fatal: [wensley]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.10 port 22: Connection timed out' unreachable: true PLAY RECAP ********************************************************************* cheddar : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 client-dev : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 cloud-dev : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 esxi : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 instance-20200126-1250 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 instance-20200127-2223 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-master01 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-master02 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-master03 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-worker01 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-worker02 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 kubernetes-master : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 kubernetes-worker0 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 kubernetes-worker1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 manager : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 manager-dev : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 registry : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 rhel7 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 rhel8 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 wensley : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0
色が付いてないので見づらいけど、ok
になっている(疎通の確認ができた)ホストは以下の通り。
- cheddar
- cloud-dev
- kubernetes-master
- kubernetes-worker0
- kubernetes-worker1
- manager
- manager-dev
- registry
- rhel8
電源入れてるVMは以下の通り。
あとcheddar
はVMでなく物理のサーバーで別途動いている。
これはNetBoxに情報登録されている全てのDevicesと全てのVirtual Machinesの情報がターゲットホストになるように、Ansibleのダイナミックインベントリ機能でホスト情報が取得できている。
ちなみに、NetBoxにIPアドレスが設定されていないデバイスまたはVMの場合は、登録しているNameが名前解決できれば(あるいは$HOME/.ssh/config
に定義されていれば)アクセスできる。
要は、NetBoxに登録しているNameの内容でインベントリファイルが構成されるのと同等になる。
IPアドレスが付与されていればansible_host=
でアドレスが設定されている状態。
(動きから考えておそらく)
all
の場合の対象ホストを確認
all
に対するping
モジュール実行時のPLAY RECAPに出てるので明確だけど、追試。
以下のタスクを追加して実行。
- name: print ansible_play_hosts_all debug: msg: "{{ ansible_play_hosts_all }}" run_once: true
出力結果はこの通り。
TASK [print ansible_play_hosts_all] ******************************************* ok: [cheddar] => msg: - cheddar - esxi - wensley - client-dev - cloud-dev - instance-20200126-1250 - instance-20200127-2223 - k8s-master01 - k8s-master02 - k8s-master03 - k8s-worker01 - k8s-worker02 - kubernetes-master - kubernetes-worker0 - kubernetes-worker1 - manager - manager-dev - registry - rhel7 - rhel8
ansible_play_hosts_all
については以下参照。
ホストのグループ指定の設定
実際にはNetBoxに登録した全てのホストに同じ処理をすることはそうそう無く、「登録ホストのうち特定条件の一部のホスト群」という使い方をするはず。
例えば「〇〇Clusterのホストのうち、Roleに□□が設定されているもの」という具合。
まずインベントリファイルにgroup_by
パラメタを追加。
plugin: netbox.netbox.nb_inventory api_endpoint: http://192.168.0.19:28080 token: 0123456789abcdef0123456789abcdef01234567 group_by: - cluster
これで、Clusters情報を使ったホストのグルーピングが可能になる。
ただ、プレイブックのhosts
にどんなグループ名を指定すれば良いかが、ドキュメントをざっと見渡しても書き方やサンプルが載っていない。
そこで(ググった結果、金魚の人が1年以上前に実施していたやり方が見つかり)ansible-inventory --list
を使って指定のインベントリファイルだとグループ情報がどのように定義されているか確認。
(a2.10) [zaki@cloud-dev netbox (master)]$ ansible-inventory -i netbox-dynamic-inventory.yml --list [WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details { "_meta": { "hostvars": { ..snip.. } }, "all": { "children": [ "cluster_oci always free", "cluster_private-network-1", "cluster_vm network", "ungrouped" ] }, "cluster_oci always free": { "hosts": [ "instance-20200126-1250", "instance-20200127-2223" ] }, "cluster_private-network-1": { "hosts": [ "zzz" ] }, "cluster_vm network": { "hosts": [ "client-dev", "cloud-dev", "k8s-master01", "k8s-master02", "k8s-master03", "k8s-worker01", "k8s-worker02", "kubernetes-master", "kubernetes-worker0", "kubernetes-worker1", "manager", "manager-dev", "registry", "rhel7", "rhel8" ] }, "ungrouped": { "hosts": [ "cheddar", "esxi", "wensley" ] } } (a2.10) [zaki@cloud-dev netbox (master)]$
これでグループ名が判明……ん、、、
"cluster_vm network": {
cluster_vm network
って、、、
これはもしや、、、
グループ名のスペースは不可
--- - name: dynamic inventory sample hosts: 'cluster_vm network' gather_facts: false
みたいに指定しても、、、
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details [WARNING]: Could not match supplied host pattern, ignoring: cluster_vm [WARNING]: Could not match supplied host pattern, ignoring: network PLAY [dynamic inventory sample] *********************************************** skipping: no hosts matched
REST使ってるのでワンチャンいけるかな、と、+
とか%20
とか指定してみても、、、
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details [WARNING]: Could not match supplied host pattern, ignoring: cluster_vm+network PLAY [dynamic inventory sample] *********************************************** skipping: no hosts matched
[WARNING]: Could not match supplied host pattern, ignoring: cluster_vm%20network PLAY [dynamic inventory sample] *********************************************** skipping: no hosts matched
はい、グループ名にスペースが入るためAnsibleからは指定できない。
グループ名にスペースまたはハイフンは使用しないでください。
dynamic inventoryのグループ名
気を取り直し、NetBox側でCluster名のスペースを_
に変更。
ちなみにDevice RolesやSitesのように、Slug
を指定できるものは、そちらがスペース等入ってないようになっていればOK (Slug
で指定したものがキーになる)
これで再度ansible-inventory -i netbox-dynamic-inventory.yml --list
を確認すれば、
"all": { "children": [ "cluster_oci_always_free", "cluster_private_network_1", "cluster_vm_network", "ungrouped" ] }, "cluster_oci_always_free": { "hosts": [ "instance-20200126-1250", "instance-20200127-2223" ] }, "cluster_vm_network": { "hosts": [ "client-dev", "cloud-dev", "k8s-master01", "k8s-master02", "k8s-master03", "k8s-worker01", "k8s-worker02", "kubernetes-master", "kubernetes-worker0", "kubernetes-worker1", "manager", "manager-dev", "registry", "rhel7", "rhel8" ] }, "ungrouped": { "hosts": [ "cheddar", "esxi", "wensley" ] }
この通り。(一部省略)
例えばcluster_vm_network
をグループに指定すれば、ターゲットノードはClusterがvm_networkのホストのみになる。
hosts
に指定するグループの凡例は「"group_by
で指定した要素のキー名" + _
+ "指定した要素の値"」
--- - name: dynamic inventory sample hosts: cluster_vm_network gather_facts: false tasks: - name: ping ping:
このようにターゲットノードのグループ名を以下のように設定してansible-playbook
を実行すると、
(a2.10) [zaki@cloud-dev netbox (master)]$ ansible-playbook -i netbox-dynamic-inventory.yml netbox-dynamic-inventory-sample.yml -v Using /home/zaki/src/ansible-sample/netbox/ansible.cfg as config file Fetching: http://192.168.0.19:28080/api/docs/?format=openapi Fetching: http://192.168.0.19:28080/api/dcim/devices/?limit=0&exclude=config_context Fetching: http://192.168.0.19:28080/api/virtualization/virtual-machines/?limit=0&exclude=config_context Fetching: http://192.168.0.19:28080/api/dcim/sites/?limit=0 Fetching: http://192.168.0.19:28080/api/dcim/regions/?limit=0 Fetching: http://192.168.0.19:28080/api/tenancy/tenants/?limit=0 Fetching: http://192.168.0.19:28080/api/dcim/racks/?limit=0 Fetching: http://192.168.0.19:28080/api/dcim/rack-groups/?limit=0 Fetching: http://192.168.0.19:28080/api/dcim/device-roles/?limit=0 Fetching: http://192.168.0.19:28080/api/dcim/device-types/?limit=0 Fetching: http://192.168.0.19:28080/api/dcim/platforms/?limit=0 Fetching: http://192.168.0.19:28080/api/dcim/manufacturers/?limit=0 Fetching: http://192.168.0.19:28080/api/virtualization/clusters/?limit=0 Fetching: http://192.168.0.19:28080/api/ipam/services/?limit=0 PLAY [dynamic inventory sample] *********************************************** TASK [ping] ******************************************************************* ok: [cloud-dev] => changed=false ansible_facts: discovered_interpreter_python: /usr/bin/python ping: pong fatal: [client-dev]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.17 port 22: No route to host' unreachable: true fatal: [k8s-master01]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.121 port 22: No route to host' unreachable: true fatal: [k8s-master02]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.122 port 22: No route to host' unreachable: true fatal: [k8s-master03]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.123 port 22: No route to host' unreachable: true fatal: [k8s-worker01]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.125 port 22: No route to host' unreachable: true ok: [manager] => changed=false ansible_facts: discovered_interpreter_python: /usr/bin/python ping: pong ok: [manager-dev] => changed=false ansible_facts: discovered_interpreter_python: /usr/bin/python ping: pong fatal: [k8s-worker02]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.126 port 22: No route to host' unreachable: true fatal: [kubernetes-master]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.131 port 22: No route to host' unreachable: true fatal: [kubernetes-worker0]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.135 port 22: No route to host' unreachable: true fatal: [kubernetes-worker1]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.136 port 22: No route to host' unreachable: true ok: [rhel8] => changed=false ansible_facts: discovered_interpreter_python: /usr/libexec/platform-python ping: pong fatal: [registry]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.21 port 22: No route to host' unreachable: true fatal: [rhel7]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: connect to host 192.168.0.26 port 22: No route to host' unreachable: true PLAY RECAP ******************************************************************** client-dev : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 cloud-dev : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 k8s-master01 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-master02 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-master03 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-worker01 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 k8s-worker02 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 kubernetes-master : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 kubernetes-worker0 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 kubernetes-worker1 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 manager : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 manager-dev : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 registry : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 rhel7 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0 rhel8 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 (a2.10) [zaki@cloud-dev netbox (master)]$
この通り、Clusterがvm_network
のホストのみが処理対象となった。
(Clusterがoci_always_free
のホストが除外されている)
グループの複合条件
例えばcluster = vm_network
かつdevice_role = server
のホストをターゲットにしたい場合、cluster_vm_network
かつdevice_roles_server
となる。
複数グループのand
でターゲットホストを指定するには:&
で繋げばOK
--- - name: dynamic inventory sample hosts: cluster_vm_network:&device_roles_server gather_facts: false tasks: - name: ping ping:
このターゲットホストの内容で実行すると、
(a2.10) [zaki@cloud-dev netbox (master)]$ ansible-playbook -i netbox-dynamic-inventory.yml netbox-dynamic-inventory-sample.yml -v (中略) PLAY RECAP ******************************************************************** manager : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 manager-dev : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 registry : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0
この通り。
ORや否定の場合は以下の通り。
記述 | 内容 |
---|---|
group1:group2 |
group1 のホスト + group2 のホスト (OR) |
group1:!group2 |
group1 のホストのうちgroup2 には含まれないホスト |
group1:&group2 |
group1 のホストのうちgroup2 にも含まれるホスト (AND) |
まとめ
NetBoxのダイナミックインベントリを使うことで、NetBoxに登録しているホスト情報をAnsibleのターゲットノードにすることを確認。
今回はClustersとDevice Rolesをフィルタに使ってみたが、group_by
に指定できれば他にもtag
やtenant
などいろいろキーにして、自由にホスト情報をNetBoxから取り出すことができる。
E×celのIPアドレス管理台帳だと、こういったツール間連携は絶望的なので大抵の場合では手書きでアドレス情報を書き写したりする必要があったが、NetBoxとAPIを使って自動化に組み込むことが容易になる。
これまで、
とやってきて、今回はNetBoxをインプットとしてAnsibleを実行してみました。
どうでしょうか?
そろそろNetBoxを使ってみたくなったのではないでしょうか?(ニッコリ
環境
- NetBox: 2.10.3 (on Docker Compose)
- Ansible: 2.10.2 (Python3 on CentOS 7)
- netbox.netbox collection: 1.1.0