[Ansible]Config ContextでNetBoxをパラメータ保管場所にする

# はじめに

NetBoxとAnsibleの連携機能紹介の2記事目になります。

1記事目はこちら

https://qiita.com/miyuk/items/e67faab0ac62632b0dcf

今回は、NetBoxの`Config Context`機能を使ってAnsibleのInventory情報とパラメータを取得する方法となります。
ちなみに、Ansibleは`2.9.x`、NetBoxは`3.0.x`を使用しています。

# 利用前提

## requirements.{txt|yml}

AnsibleからNetBoxの情報を取得するためには、`netbox.netbox`コレクションが必要になります。
そのため、事前に下記コマンドでコレクションとPythonモジュールをインストールしておきます。

```bash
pip install pynetbox
ansible-galaxy collection install netbox.netbox
```

## NetBox接続情報を用意

NetBoxに接続するための`サーバ接続先`と`API Token`を用意してAnsibleを実行するサーバの環境変数に定義しておきます。
NetBox TokenはNetBoxのユーザのプロファイルから作成できます。

```bash:.bashrc
export NETBOX_API=<NetBox IPアドレス or FQDN>
export NETBOX_TOKEN=<NetBox Token>
```


# NetBoxのパラメータをダイナミックインベントリとして取得

## ダイナミックインベントリファイル作成

NetBoxのデバイス情報をダイナミックインベントリとして取得するには、インベントリファイルを作成する必要があります。
pluginは`netbox`も利用できますが、ドキュメントの最新版では`netbox.netbox.nb_inventory`にリダイレクトされていたので今後はこちらに統合されるのではないかなと思います。

https://docs.ansible.com/ansible/latest/collections/netbox/netbox/nb_inventory_inventory.html

```yaml:netbox_inventory.yml
---
plugin: netbox.netbox.nb_inventory
validate_certs: no # 証明書無視
group_names_raw: yes # 取得したときのグループ名prefixに「group_by」がつかないようにする
strict: yes # APIエラーなどがあれば、以降の処理を止める
device_query_filters:
  - has_primary_ip: yes # デバイス情報にprimary ipがないとインベントリとして読み込まない(primary ip=ansible_hostとなる)
group_by:
  - device_roles # device_roleをAnsibleのグループとする
config_context: yes # config_contextを読み込む
flatten_config_context: yes # config_contextをデバイスパラメータの第一階層にマージする
flatten_custom_fields: yes # カスタムフィールドデバイスパラメータの第一階層にマージする
flatten_local_context: yes # デバイスに設定されているlocal_contextをデバイスパラメータの第一階層にマージする
```

v0.2.1(2020/5/20)以前は、`flatten_config_context`がなかったので`compose`ディレクティブを使って`hoge: config_context.hoge`とする必要があったらしいですが、このオプションができたことによってかなり柔軟性は上がったのではないかと思います。

## インベントリ情報取得テスト

実際にNetBoxからデータをどのように取れているかは`ansible-inventory`コマンドを使って確かめます。

```bash
ansible-inventory -i netbox_inventory.yml -y
```

```yaml:結果
all:
  children:
    center_router:
      hosts:
        rt-center:
          ansible_become: true
          ansible_become_method: enable
          ansible_connection: ansible.netcommon.network_cli
          ansible_host: x.x.x.x
          ansible_network_os: cisco.ios.ios
          device_roles:
          - center_router
          device_types:
          - csr
          is_virtual: false
          local_context_data:
          - null
          locations: []
          manufacturers:
          - cisco
          ospf:
          - interface:
              name: GigabitEthernet2
              ospf_cost: 10
              shutdown: false
            route:
              nexthop: x.x.x.x
              target: x.x.x.x/24
          platforms:
          - ios-xe
          primary_ip4: x.x.x.x
          regions: []
          services: []
          sites:
          - center
          status:
            label: Active
            value: active
          tags: []
          - secondary
    ungrouped: {}
```

あとは、上記ファイルをインベントリとしてPlaybookを実行してあげるとNetBoxから自動でインベントリ情報取得して利用されます。

# Config Contextによるパラメータ定義

先ほどのインベントリ取得で、`ospf`だったり、`ansible_network_os`があったと思いますがこれはNetBox側が勝手に作成してくれるものではありません。(将来的にそうなったら嬉しいのですが・・・)

そのため、`Config Context`機能を使ってNetBox上でユーザが定義してあげる必要があります。

`Config Context`は、下記オブジェクトにアサインして、そのオブジェクトが紐づいているデバイスに対してJSON形式でパラメータが適用されるかたちとなります。

アサインされるオブジェクトは下記の通り(ver.3.0.x時点)

- Regions	
- Site Groups	
- Sites	
- Device Types	
- Roles	
- Platforms	
- Cluster Groups	
- Clusters	
- Tenant Groups	
- Tenants	
- Tags	

また、Priorityの概念もありパラメータが被った場合でもPriority値が高いものが優先的にマージされる仕組みとなっています。

## 汎用的なパラメータ定義

Ansibleからデバイスに接続するための`ansible_connection`のようなパラメータは対応する機器種別のすべてのデバイスに適用してあげる必要があります。
そういった場合は、以下画像のように「`Platform`が`ios-xe`のデバイスすべてにCiscoの接続情報を定義」とすることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335255/e1cf312b-af44-92ff-8bc1-547e61842a1a.png)

## 状況によって変化するパラメータ定義

次に、一時的にパラメータが変更となるような場合についても先ほど紹介したPriority値を使うことでConfig Contextのパラメータを変更せずに柔軟に変更することもできます。

例として、OSPFの設定で一時的に経路変更が必要となった場合の`Config Context`設定を紹介したいと思います。

事前に、経路変更を示すタグを作成したうえで下記2つの`Config Context`を作成します。

- 通常状態の`Config Context`
    - Priority: **1000**
    - Assignments: **対象ルータのDevice Role**
- フェイルオーバー時の`Config Context`
    - Priority: **2000**
    - Assignments: **対象ルータのDevice Roleと経路変更タグ**

こうすることで、対象デバイスに対して経路変更タグをつけ外しするだけでパラメータを変更することができます。

### 通常時のOSPF設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335255/45e55e24-a754-0244-b270-19145bf9dc1e.png)

### 経路変更時のOSPF設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335255/c85a92e9-fa3c-a043-e6f4-65ceb8d5c52a.png)

### 経路変更タグが付いているときの`Config Context`結果
![screencapture-54-168-175-45-dcim-devices-4-config-context-2022-01-17-21_30_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/335255/14c52fb7-2abf-0d69-a64b-fcb5f85b42b6.png)

# デバイス情報以外のNetBoxデータの取得

Playbookの中でデバイス情報以外のNetBox情報を取得したい時があるかと思います。

その際は、APIのURLとTOKENは環境変数に登録されていること前提としたうえで`netbox.netbox.nb_lookup`プラグインを使うことで取得できます。

例として、下記PlaybookにてadminユーザのユーザIDが取得できます。


```yaml:playbook.yml
・・・
  tasks:
    - name: パラメータに必要な情報を取得
      set_fact:
        admin_user_id: |
          {{
              query('netbox.netbox.nb_lookup',
                'users',
                api_filter='name=admin',
                validate_certs=false)
              [0].value.id
          }}
```

# おわりに

`Config Context`は、シンプルながらもとてもパワフルな機能です。
今回紹介しなかったですが、`local_context`や`custom_fields`とマージして利用することもできるので柔軟にパラメータを管理できるかと思います。

ただし、NetBoxはユーザの閲覧権限を柔軟に設定することができないため、パスワードのような機密情報を`Config Context`で管理するのはやめておいた方がいいかと思います。

NetBoxは過去、`Secrets`というSecret情報を保存するための仕組みを用意していましたがどこかのタイミング(v2.10.4?)で標準搭載から外れてアドオン提供になっています。

また、公式ドキュメントからも`Hashicorp Vault`使った方がいいよと言われており、プラグイン開発が活発になっているのでこちらを検討した方がよさそうですね。

https://github.com/netbox-community/netbox/pull/7069

https://github.com/netbox-community/netbox/discussions/7980


# 参考URL

- https://netbox.readthedocs.io/en/stable/models/extras/configcontext/
- https://docs.ansible.com/ansible/latest/collections/netbox/netbox/nb_inventory_inventory.html
- https://docs.ansible.com/ansible/latest/collections/netbox/netbox/nb_lookup_lookup.html
- https://zaki-hmkc.hatenablog.com/entry/2021/01/29/002540
- https://tekunabe.hatenablog.jp/entry/2019/12/21/ansible_netbox_inventory