てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] NetBox モジュールで Site や Device を登録する(Collection モジュール編)


これは エーピーコミュニケーションズ Advent Calendar 2019 の15日目の記事です。


■ はじめに

NetBox とは、IPAM、ラックやデバイス管理機能を持ったWeb UI 付きのオープンソースのソフトウェアです。

f:id:akira6592:20191213162158p:plain:w400
Device 管理画面

API を備えていて、Ansible の NetBox モジュールから様々な操作ができます。

ここでは、NetBox の環境を構築した後に、Device を登録するのに必要な一連の作業(Manufacture や Device Role など含む)を Ansible から行う方法をご紹介します。


【目次】


■ NetBox モジュールの基本

Playbook を作成する前に、予め押さえておきたいポイントを説明します。

大きく2種類に分かれる

NetBox モジュール群は以下の2種類あります。

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 が表示されたらしばらく待ってから再度試します。

f:id:akira6592:20191213163800p:plain:w400
ログインは右上から

f:id:akira6592:20191213163831p:plain:w400
ユーザー名とパスワードを入力してログイン

f:id:akira6592:20191213163855p:plain:w400
ログイン成功

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 が登録された状態ががゴールです。

f:id:akira6592:20191213162225p:plain:w400
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 上に登録したい 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 追加画面で色を選ぶ項目があるので、ここから適当に色を拾いました。

f:id:akira6592:20191213162958p:plain:w200
(参考)色の選択

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 にまとめてしまいます。

冒頭の collectionfragmentedpacket.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 側の確認

ちゃんとオブジェクトが作れたか確認します。

f:id:akira6592:20191213162350p:plain:w500
Site が登録された

f:id:akira6592:20191213162418p:plain:w500
Manufacturer が登録された

f:id:akira6592:20191213162448p:plain:w500
Device Type が登録された

f:id:akira6592:20191213162512p:plain:w500
Device Role が登録された

f:id:akira6592:20191213162543p:plain
Device が登録された(完成!)

無事にすべて想定通りに登録できました。

■ まとめ

Ansible で NetBox の Device を登録できることを確認しました。

Device の登録の前にも、Site や Device Type などの登録も予め必要でしたが、Collection モジュールを利用することで、一連の作業が自動化できました。

前述のように、Ansible には NetBox をインベントリとして利用する機能もあるので、NetBox と Ansible はなかなか相性がよいような印象です。