Ansible + AWX で始めるネットワーク運用自動化

## はじめに

JANOG 47 にて [エンタープライズ向けVPNサービスNW 運用自動化への挑戦](https://www.janog.gr.jp/meeting/janog47/vpnaut/) という発表がありました。
私はまだこの発表を見ていない（リアルタイムで見逃した & 執筆時点（2021年2月8日時点）でアーカイブがまだ公開されていない）のですが、資料の中で [Ansible](https://www.ansible.com/) と [AWX](https://github.com/ansible/awx) を用いたネットワークオペレーションの自動化の話があり、弊社でも Ansible + AWX を用いて運用自動化を試みているので、せっかくならその中で得たノウハウなどをまとめようかなと思いました。

## 何を自動化したいのか？

自動化するにあたって「何を自動化したいか？」「何故自動化したいか？」は重要です。弊チームで自動化したいことは主に以下の2つです。

- お客様からのオーダに伴うネットワーク機器への設定変更（事前・事後の確認を含む）
- メンテナンス等でのネットワークの系切替や設定変更（事前・事後の確認を含む）

これらは定期的に発生する作業であり、「定型業務」であるため自動化しやすいものです。

### 手続き的な方法で自動化する

近年 Kubernetes のような宣言的な設定管理を、ネットワークでもできないかという試みがあります。特に自動化をする際に「宣言的」か「手続き的（命令的）」かの議論がセットになることが多い気がします。
今回は宣言的なことは一切せず、これまで手動でやってきた手続き的な方法・命令をそのまま自動化することに専念しました。

例えば、ネットワーク機器に対して何かしら設定変更を行う際に、基本的には「事前確認」→「設定変更」→「事後確認」の順で行うことが多いと思います。
これを **Ansible で自動化した際にも、手動で行ってきた事前・事後の確認と全く同じことを Ansible で自動で行います。**

こうした設計にすることで、 **実績のある既存の作業手順書と同等のことができる** ようになります。

ただし、そのため Ansible 本来のメリットである **冪等性** は失われています（例えば同じ playbook を2回連続で行った場合に成功するもの / 失敗するものがある）。

## Ansible / AWX 設計

ざっくり以下のようなディレクトリ構造になっています。

**:warning: 本記事の内容は [一般的な Ansible の Best Practices](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_best_practices.html) から逸脱していますのでご注意ください**

```bash
.
├── README.md
├── hosts                           # Ansible Inventory
│   ├── lab                         #   - 検証環境用インベントリ
│   │   ├── group_vars              #     - group_vars ディレクトリ
│   │   ├── host_vars               #     - host_vars ディレクトリ
│   │   └── inventory               #     - インベントリファイル
│   └── production                  #   - 本番環境用インベントリ
├── playbooks                       # Ansible playbook
│   ├── xxxxx.yml                   #   - 用途毎に playbook を分ける
│   ├── yyyyy.yml                   # 
│   └── ...                         # 
├── plugins                         # Ansible plugin
│   └── filter                      #   - filter_plugins
│   └── ...                         #   - ...
└── roles                           # Ansible role
    ├── routerA                     #   routerA に関する role
    │   └── tasks                   #     routerA に関する task
    │   │   ├── xxxxx               #     - xxxxx playbook 関連の tasks
    │   │   │   ├── configure.yml   #       - 設定変更用 task
    │   │   │   ├── post-check.yml  #       - 事後確認用 task
    │   │   │   └── pre-check.yml   #       - 事前確認用 task
    │   │   ├── yyyyy               #     - yyyyy playbook 関連の tasks
    │   │   ├── ...                 #     - ...
    │   │   └── main.yml            #     - main.yml（ここには何も task を書かない（role を読み込んだ時に自動で実行されてしまうため））
    └── ...
```

### inventory

- 検証環境・本番環境ともに同じ role / playbook で実行できるようにしています

### role

- **`roles/<ROLE_NAME>/tasks/main.yml` には何もタスクを記述しない** というルールにしています
    - role を読み込んだ際に自動でタスクが実行されてしまうため
    - Router A, B それぞれで事前確認 → Router A, B それぞれで設定変更 → Router A, B それぞれで事後確認、といったことができない
    - role を分ければ実現可能だが、共通で使いたい変数等（接続の仕方等）があるので role は分けない
- `roles/<ROLE_NAME>/tasks/<SUB_ROLE>/xxx.yml` にタスクを記述し、playbook から import するようにしています

### playbook

- `playbooks/xxx.yml` に playbook を記述するようにしています
    - 必要なオペレーション毎に playbook を分ける
- role の `tasks/main.yml` には何もタスクを記述せず、playbook から必要なタスクを直接 import するようにしています

### AWX

- AWX の設定（Project / Inventory / Job Template / WorkFlow Template / Notification ...）は基本的に Ansible で管理しており、CI/CD で変更できるようになっています
    - 下記の記事や [awx collection](https://galaxy.ansible.com/awx/awx) の [ソースコード](https://github.com/ansible/awx/tree/devel/awx_collection/plugins/modules) を参考にしました
        - [[Ansible] tower モジュールの最近の事情と Collection 版モジュールの使い方（AWX10.0時点） - てくなべ (tekunabe)](https://tekunabe.hatenablog.jp/entry/2020/04/03/ansible_tower_awx_collection)
        - [[Ansible] Collection 版の tower モジュールでワークフローを作成する（tower_workflow_job_template/tower_workflow_job_template_node） - てくなべ (tekunabe)](https://tekunabe.hatenablog.jp/entry/2020/04/03/ansible_tower_awx_workflo)
    - （長らく `tower_workflow_job_template_node` で Approval Node が作成できなかったが、いつの間にかできるようになっていた）
- AWX の承認機能や、Survey 機能なども使っています
    - 事前確認が終わったら承認フローを挟んで設定変更するようにする、など

### assert モジュール

- 事前確認 / 事後確認などで、コマンドの実行結果の妥当性をテストするために、assert モジュールを使っています
    - 参考）[ネットワークのテスト自動化に利用できそうなツールまとめ - てくなべ (tekunabe)](https://tekunabe.hatenablog.jp/entry/2019/01/16/nw_auto_test_too)

## 課題

運用を開始してから1年弱になりますが、以下のような課題を抱えています。

- テスト自動化できていない
    - 現状 CI では Lint しかできておらず、テストコードはほとんど書けていない
    - そもそもネットワーク機器に対して操作するツールのテストコードの書きにくさもある
- assert モジュールによるチェックが力技になりがち
    - Ansible でデータを取得してパースして正常かどうか判定するロジックの可読性が低い
    - Ansible をプログラミング言語的に使うことが良くない
- 宣言的でない
    - 将来的には宣言的に設定変更を行いたいが... 現状厳しそう

## 参考

- [エンタープライズ向けVPNサービスNW 運用自動化への挑戦](https://www.janog.gr.jp/meeting/janog47/vpnaut/)
- [Best Practices — Ansible Documentation](https://docs.ansible.com/ansible/2.9/user_guide/playbooks_best_practices.html)
- [ネットワークのテスト自動化に利用できそうなツールまとめ - てくなべ (tekunabe)](https://tekunabe.hatenablog.jp/entry/2019/01/16/nw_auto_test_too)
- [[Ansible] tower モジュールの最近の事情と Collection 版モジュールの使い方（AWX10.0時点） - てくなべ (tekunabe)](https://tekunabe.hatenablog.jp/entry/2020/04/03/ansible_tower_awx_collection)
- [[Ansible] Collection 版の tower モジュールでワークフローを作成する（tower_workflow_job_template/tower_workflow_job_template_node） - てくなべ (tekunabe)](https://tekunabe.hatenablog.jp/entry/2020/04/03/ansible_tower_awx_workflo)