これは エーピーコミュニケーションズ Advent Calendar 2019 の15日目の記事です。
■ はじめに
NetBox とは、IPAM、ラックやデバイス管理機能を持ったWeb UI 付きのオープンソースのソフトウェアです。
API を備えていて、Ansible の NetBox モジュールから様々な操作ができます。
ここでは、NetBox の環境を構築した後に、Device を登録するのに必要な一連の作業(Manufacture や Device Role など含む)を Ansible から行う方法をご紹介します。
【目次】
■ NetBox モジュールの基本
Playbook を作成する前に、予め押さえておきたいポイントを説明します。
大きく2種類に分かれる
NetBox モジュール群は以下の2種類あります。
- Ansible 2.8 で追加された標準モジュール
- Ansible 2.9 以降で利用できる Collection モジュール(要別途インストール)
Collection とは、Ansible 2.9 から本格的(?)には始まった、新しいコンテンツ配布形式です。Ansible Galaxy 経由で入手する点ではロールと同じですが、Collection では、プラグイン、モジュール、ロールなどがセットです。
Ansible のリリースサイクルに依存せずにモジュールをリリースするためなのか、現状は Collection モジュールの方が開発が進んでいます。
現時点で、標準モジュールが 5個なのに対して、Collection モジュール(v0.1.1)は 32個です。
今回は、Collection モジュールの方を利用します。
NetBox の REST API を利用する
NetBox モジュールは、NetBox 本体に備わっている REST API を利用する仕組みになっています。 モジュールには、NetBox のホスト名またはIPアドレスや、API を利用するためのトークンを指定します。
具体的な指定方法は「接続情報の定義」で後述します。
■ 環境の準備
- Ansible 2.9.1
- NetBox 2.6.7
- pynetbox 4.2.2
- Python 3.6
NetBox の準備
検証用の NetBox を準備します。ありがたいことに docker イメージがあるのでそれを利用します。(Play with Docker でも正常に起動しました。)
$ git clone -b release https://github.com/netbox-community/netbox-docker.git $ cd netbox-docker $ docker-compose pull $ docker-compose up -d
[2020/02/16 更新] 公式手順で利用するブランチが、master
から release
に変更されたのでコマンドを修正
起動したら Web ブラウザ で http://localhost:32768/
にアクセスしてログインできることを確認します。デフォルトの docker-compose.yml
では、ホスト側のポートが固定されていないので、こちらの記事を参考にして固定するのも良いと思います。
初期ユーザー名、パスワードともに admin
です。起動に少し時間がかかるようなので、502 Bad Gateway
が表示されたらしばらく待ってから再度試します。
Ansible 環境の準備
NetBox モジュールを利用するには、NetBox API のクライアントライブラリである pynetbox
が必要なのでインストールします。
$ pip install pynetbox Collecting pynetbox ...(略)...
venv で環境を分離している場合、どの venv にインストールしたのかをあとで Ansible 側に教えてあげる必要があるので、覚えておきましょう。
Collection モジュールのインストール
Collection モジュールは標準では入っていないので、ansible-galaxy collection install
コマンドでインストールします。
$ ansible-galaxy collection install fragmentedpacket.netbox_modules Process install dependency map Starting collection install process Installing 'fragmentedpacket.netbox_modules:0.1.1' to '/home/vagrant/.ansible/collections/ansible_collections/fragmentedpacket/netbox_modules'
デフォルトでは、 ~/.ansible/collections
配下にインストールされます。
なお、一部で Collection モジュールは mazer
というツールでインストールするという説明しているページがありますが、mazer
の機能は ansible-galaxy
コマンドに統合されたので、mazer
はすでに非推奨扱いです。
[2020/07/01 追記]
現在は netbox.netbox
という collection の方で開発が行われているようです。
■ 目指すゴール
管理用IPアドレスを持った device を登録します。
NetBox 上、以下の画面のように 4つの Device が登録された状態ががゴールです。
画面にもあるように Device には Site や Device Role、Device Type などの関連オブジェクト割り当てます。そのため、これらの関連オブジェクトを予め作成しておく必要があります。
関連オブジェクトは以下のようなものです。
オブジェクト名 | 例 |
---|---|
Site | 地理的な管理単位 |
Manufactures | Cisco、Juniper、Aristaなど |
Device Types | catalyst など |
Device Roles | core、distribution、aggretation など |
NetBox では他にも、ラック管理など、様々な機能がありますが今回は利用しません。
■ Playbook 類の作成
Playbook と、Playbook の実行に必要なファイルを作成します。
- 今回作成するファイル一覧
. ├── host_vars │ └── netbox01.yml ├── inventory.ini ├── netbox.yml └── object_vars.yml
インベントリファイルの作成
NetBox のホスト情報を定義する、インベントリファイルを作成します。
NetBox の API の URL は別途定義するので、ここで定義する名前は何でも構いません。ここでは netbox01
とします。
inventory.ini
[netbox] netbox01
変数定義ファイルの作成
NetBox モジュールで利用する変数を定義するファイルを作成します。
接続情報の定義
NetBox ホスト netbox01
が利用する接続情報(url、トークン)をホスト変数として定義します。一応 netbox
というグループで囲って、あとで Playbook で hosts: netbox
と指定するすることにします。
host_vars/netbox01.yml
--- netbox_url: http://localhost:32768 netbox_token: 0123456789abcdef0123456789abcdef01234567
netbox_url
:- NetBox のAPI に接続するためのURLを指定します。
netbox_token
- NetBox のAPI に接続するためのトークンを指定します。詳細は、NetBox の公式ドキュメントを参照してください。上記ファイルで定義している
0123456789abcdef0123456789abcdef01234567
は、docker イメージを利用した場合のものです。
- NetBox のAPI に接続するためのトークンを指定します。詳細は、NetBox の公式ドキュメントを参照してください。上記ファイルで定義している
オブジェクト情報の定義
NetBox 上に登録したい device などの各オブジェクトを定義する変数定義ファイルを作成します。
object_vars.yml
--- # Site の定義 site: my_site # Manufacture の定義 manufactures: - name: Cisco - name: Arista # Device Type の定義 device_types: - model: Catalyst slug: Catalyst manufacturer: Cisco - model: veos slug: veos manufacturer: Arista # Device Role の定義 device_roles: - name: core slug: core color: 4caf50 # green - name: aggregation slug: aggregation color: 2196f3 # blue # Device の定義 devices: # Cisco - name: cat1 device_type: Catalyst device_role: core management: # 管理インターフェース interface: GigabitEthernet0/0 address: 10.0.1.1/24 site: my_site - name: cat2 device_type: Catalyst device_role: core management: # 管理インターフェース interface: GigabitEthernet0/0 address: 10.0.1.2/24 site: my_site # Arista - name: veos1 device_type: veos device_role: aggregation management: # 管理インターフェース interface: Management1 address: 10.0.3.1/24 site: my_site - name: veos2 device_type: veos device_role: aggregation management: # 管理インターフェース interface: Management1 address: 10.0.3.2/24 site: my_site
device_roles
モジュールの color
オプションで指定ているのは、16進で表した色指定です。NetBox の Device Roles 追加画面で色を選ぶ項目があるので、ここから適当に色を拾いました。
Playbook の作成
いよいよ、実際の作業を定義する Playbook を作成します。
利用するモジュール
今回 Playbook で利用するモジュールは以下のとおりです。
モジュール名 | 概要 | ドキュメント(v0.1.1) |
---|---|---|
netbox_manufacturer |
Manufacture (Cisco、Juniper、Aristaなど) を管理する | 埋め込みドキュメント |
netbox_device_role |
Device Role (switch、firewall など) を管理する | 埋め込みドキュメント |
netbox_device_type |
Device Type (catalyst など) を管理する | 埋め込みドキュメント |
netbox_device |
Device を管理する。今回は作成と、Primary adderss の選出に利用 | 埋め込みドキュメント |
netbox_device_interface |
Device のインターフェースを管理する | 埋め込みドキュメント |
netbox_ip_address |
IPアドレスを管理する。今回は Device のインターフェースの割当に利用 | 埋め込みドキュメント |
これらのモジュールは Collection モジュールのため、公式ドキュメントには掲載されていません。そのため詳細情報は、モジュールのコードに埋め込まれているドキュメント用の部分を確認するか、ansible-doc
コマンドで名前空間を含めたモジュール名を指定して表示させます。
ansible-doc
利用例:
$ ansible-doc fragmentedpacket.netbox_modules.netbox_manufacturer
なお、Primary adderss というのは Device の代表アドレスです。Primary adderss を設定すると、Ansible から NetBox をインベントリーとして利用する際に、自動的に ansible_host
変数に割り当ててくれるので便利です。この件については別途ブログで取り上げる予定です。
[2019/12/21 追記]
投稿しました
[Ansible] NetBox をインベントリーとして利用する - てくなべ (tekunabe)
Playbook
role など、何かしらの方法で分割したほうがよさそうな分量ですが、説明の簡略化のため 1つの Playbook にまとめてしまいます。
冒頭の collection
で fragmentedpacket.netbox_modules
を指定することにより、モジュール利用時に Collection モジュールを名前空間なしで指定できるようになります。例えば、名前空間ありで指定する場合は fragmentedpacket.netbox_modules.netbox_manufacturer
とするところ、名前空間なしでは単にnetbox_manufacturer
と指定できます。詳細は公式ドキュメント参照。
netbox.yml
- hosts: netbox connection: local gather_facts: no # Collection モジュールの読み込み collections: - fragmentedpacket.netbox_modules vars: # pynetbox をインストールした venv 環境の python インタープリターを指定 ansible_python_interpreter: ~/ansible291/bin/python # オブジェクト定義ファイルの読み込み vars_files: - object_vars.yml tasks: # site - name: site netbox_site: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: name: "{{ site }}" # manufacturer 追加 - name: add manufacturers netbox_manufacturer: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: name: "{{ item.name }}" loop: "{{ manufactures }}" # device role 追加 - name: add device role netbox_device_role: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: name: "{{ item.name }}" slug: "{{ item.slug }}" color: "{{ item.color }}" # vm_role: true loop: "{{ device_roles }}" # device type 追加 - name: add device types netbox_device_type: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: model: "{{ item.model }}" slug: "{{ item.slug }}" manufacturer: "{{ item.manufacturer }}" # vm_role: true loop: "{{ device_types }}" # device 追加 - name: add devices netbox_device: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: name: "{{ item.name }}" device_type: "{{ item.device_type }}" device_role: "{{ item.device_role }}" site: "{{ item.site }}" loop: "{{ devices }}" # device に interface 追加 - name: add management interfaces netbox_device_interface: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: device: "{{ item.name }}" name: "{{ item.management.interface }}" loop: "{{ devices }}" # device interface に IP addresses 割り当て - name: assign ip addresses netbox_ip_address: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: interface: device: "{{ item.name }}" name: "{{ item.management.interface }}" address: "{{ item.management.address }}" loop: "{{ devices }}" # Primary adderss を選出 - name: elect primary addresses netbox_device: netbox_url: "{{ netbox_url }}" netbox_token: "{{ netbox_token }}" data: name: "{{ item.name }}" primary_ip4: "{{ item.management.address }}" loop: "{{ devices }}"
■ Playbook の実行
それでは、 Playbook を実行します。
実行ログ(クリックして広げる)
$ ansible-playbook -i inventory.ini netbox.yml PLAY [netbox] ********************************************************************************************** TASK [site] ************************************************************************************************ changed: [netbox01] TASK [add manufacturers] *********************************************************************************** changed: [netbox01] => (item={'name': 'Cisco'}) changed: [netbox01] => (item={'name': 'Arista'}) TASK [add device role] ************************************************************************************* changed: [netbox01] => (item={'name': 'core', 'slug': 'core', 'color': '4caf50'}) changed: [netbox01] => (item={'name': 'aggregation', 'slug': 'aggregation', 'color': '2196f3'}) TASK [add device types] ************************************************************************************ changed: [netbox01] => (item={'model': 'Catalyst', 'slug': 'Catalyst', 'manufacturer': 'Cisco'}) changed: [netbox01] => (item={'model': 'veos', 'slug': 'veos', 'manufacturer': 'Arista'}) TASK [add devices] ***************************************************************************************** changed: [netbox01] => (item={'name': 'cat1', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'cat2', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.2/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos1', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos2', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.2/24'}, 'site': 'my_site'}) TASK [add management interfaces] *************************************************************************** changed: [netbox01] => (item={'name': 'cat1', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'cat2', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.2/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos1', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos2', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.2/24'}, 'site': 'my_site'}) TASK [assign ip addresses] ********************************************************************************* changed: [netbox01] => (item={'name': 'cat1', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'cat2', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.2/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos1', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos2', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.2/24'}, 'site': 'my_site'}) TASK [elect primary addresses] ***************************************************************************** changed: [netbox01] => (item={'name': 'cat1', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'cat2', 'device_type': 'Catalyst', 'device_role': 'core', 'management': {'interface': 'GigabitEthernet0/0', 'address': '10.0.1.2/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos1', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.1/24'}, 'site': 'my_site'}) changed: [netbox01] => (item={'name': 'veos2', 'device_type': 'veos', 'device_role': 'aggregation', 'management': {'interface': 'Management1', 'address': '10.0.3.2/24'}, 'site': 'my_site'}) PLAY RECAP ************************************************************************************************* netbox01 : ok=8 changed=8 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 $
なお、べき等性があるため、再度実行すると changed
ではなく ok
になります。
2回目の実行ログ(クリックして広げる)
$ ansible-playbook -i inventory.ini netbox.yml TASK [site] ************************************************************************************************ ok: [netbox01] TASK [add manufacturers] *********************************************************************************** ok: [netbox01] => (item={'name': 'Cisco'}) ok: [netbox01] => (item={'name': 'Arista'}) ...(略)...
■ NetBox 側の確認
ちゃんとオブジェクトが作れたか確認します。
無事にすべて想定通りに登録できました。
■ まとめ
Ansible で NetBox の Device を登録できることを確認しました。
Device の登録の前にも、Site や Device Type などの登録も予め必要でしたが、Collection モジュールを利用することで、一連の作業が自動化できました。
前述のように、Ansible には NetBox をインベントリとして利用する機能もあるので、NetBox と Ansible はなかなか相性がよいような印象です。