てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] molecule でネットワーク機器の設定後のテストを自動化する(いまいち編)

はじめに

molecule とは

molecule は、Ansible のロールをテストするフレームワークです。

これまで、バージョンアップするごとに結構インパクトの大きい変更があって、やや敬遠気味でした。バージョン 3.0 で、デフォルトの verifier(実際にテストを担うもの)が Ansible になったのをきっかけに重い腰を上げました。ちなみに少し前は デフォルトは testinfraでした。

ネットワーク機器もテストできるらしい

「nw機器とかもテストできる(ansibleで触れるものはだいたいいける)」という情報を Ansible ユーザー会の slack の #molecule チャンネルで得ました。そこで、ネットワーク機器へ設定するロールのテスト自動化を molecule でやってみることにしました。

なお、テスト対象はネットワーク機器ですので、おそらく一番メジャーな molecule の使い方である「テスト用の docker コンテナを作って設定してテストして破棄する」という方法とは異なる点が多いです。(driverに delegate を使うため)

(いまいち編、とは)

タイトルに「いまいち編」とつけているのは、molecule を触りはじめたばかりで、いろいろ至らない点がありそうだからです。なにかアドバイスございましたら、@akira6592までご連絡いただけると、とてもありがたいです。

動作確認環境

  • molecule 3.0.3
  • ansible 2.9.6


■ やりたいこと

ロールで行うこと

  • Cisco IOS の機器に、参照NTP サーバーの設定(10.0.0.123)をする

molecule 経由でテストすること

  • ロール実行後、show running-configntp server 10.0.0.123 があることを確認する

テストには様々な考え方があると思いますが、ここでは簡単なことを優先してこの内容にします。

シナリオ内のアクションでやること

default シナリオを利用します。必要なアクションに絞ったシナリオを別途作ったほうがよいのでしょうが・・。

各アクションでの処理内容は以下のとおりです。

シナリオ内のアクション 今回の処理内容
dependency なし
lint なし
cleanup NTPコンフィグの削除
destroy なし
syntax なし
create なし
prepare なし
converge ロールの実行(NTPサーバー設定)
idempotence 冪等性確認
side_effect なし
verify 意図したコンフィグ入ったか
cleanup (再実行) NTPコンフィグの削除
destroy (再実行) なし

コンテナを作って壊してと言う場合は、createdestroy を利用するようですが、今回のネットワーク機器はぽこぽこ作ったり壊したりできません。そのため、代わりに cleanup アクションを利用します。

また、今回は扱いませんが、Arista の cEOS-LabのようなコンテナのネットワークOSを利用して、テスト用の環境を作って壊してする場合は、createdestroy が良いのだと思います。


■ 環境の準備

molecule のインストール

以下コマンドで molecule インストールします。

pip install molecule

docker は利用しませんので pip install docker も不要です。


■ 各種実装

ロールやテストを実装していきます。

ロールディレクトリ一式の作成

molecule init コマンドで、molecule が利用するディレクトリを含めた一式を作成します。ロール名は set_ntp とします。

$ molecule init role set_ntp
--> Initializing new role set_ntp...
Initialized role in /Users/sakana/molecule/set_ntp successfully.

今回は使わないディレクトリが多いので削除します。

$ cd set_ntp
$ rm -fr defaults/ handlers/ meta/ handlers/ tests/ vars/ files/ templates/

構成はこうなります。

$ tree .
.
├── README.md
├── molecule
│   └── default
│       ├── INSTALL.rst
│       ├── converge.yml
│       ├── molecule.yml
│       └── verify.yml
└── tasks
    └── main.yml

ロール処理の実装

set_ntp ロールのメイン処理である、tasks/main.yml の内容を実装します。

Cisco IOS に対する NTP サーバーの設定なので、iso_ntp モジュールを利用します。

  • tasks/main.yml
---
- name: set ntp server
  ios_ntp:
    server: 10.0.0.123
    state: present

molecule の基本設定

molecule/default/molecule.yml で molecule の全体的な基本設定をします。

driver に docker ではなく、delegated を利用する点が一番のポイントです。

  • molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: delegated
platforms:
  - name: ios1
provisioner:
  name: ansible
  inventory:
    hosts:
      all:
        children:
          ios:  
            hosts:
              ios1:
                ansible_host: 10.0.0.254  # 対象のNW機器
            vars:
              ansible_connection: network_cli
              ansible_network_os: ios
              ansible_user: user1
              ansible_password: xxx_dummy_xxx
              ansible_python_interpreter: ~/envs/nwlab/bin/python
verifier:
  name: ansible

provisioner.inventory 配下は、YAML でインベントリを書くときと同じ書式のようです。

今回は、検証用のネットワーク機器を動的プロビジョニングして設定、と言う流れではなく、すでにあるネットワーク機器へ設定します。そのため platforms も provisioner も同じネットワーク機器を指すことになります。

[2021/09/11 追記] 以下のように、既存のインベントリファイルや変数定義ファイルを指定することもできます。

provisioner:
  name: ansible
  inventory:
    links:
      hosts: ../../../inventory/hosts
      group_vars: ../../../inventory/group_vars/
      host_vars: ../../../inventory/host_vars/

公式ドキュメントより抜粋)

ロール呼び出しの実装: converge.yml

molecule init コマンドで自動生成された molecule/default/converge.yml は、set_ntp ロールを呼び出して、設定変更を担当します。

これはこのままで大丈夫です。

  • molecule/default/converge.yml
---
- name: Converge
  hosts: all
  gather_facts: false
  
  tasks:
    - name: "Include set_ntp"
      include_role:
        name: "set_ntp"

テストの実装: verify.yml

molecule/default/verify.yml も自動生成されます。が、中身はダミーの assert なので、実際にテストしたい内容に書き換えます。

ここでは、show running-configntp server 10.0.0.123 があることを確認します。

  • molecule/default/verify.yml
---
- name: Verify
  hosts: all
  gather_facts: false

  tasks:
    - name: show run
      ios_command:
        commands:
          - show running-config
      register: resutl_show_run

    - name: assert ntp sever
      assert:
        that:
          - ntp_conf in resutl_show_run.stdout_lines[0]
      vars:
        ntp_conf: ntp server 10.0.0.123

cleanup の実装: cleaup.yml

cleanup は、default シナリオで test したときに、最初の方と最後の方に呼ばれるアクションです。今回はコンフィグの掃除をします。具体的には ntp server 10.0.0.123 を削除します。

このファイルは自動では作成されませんので、自分で molecule/default/cleaup.yml として作成します。

  • molecule/default/cleaup.yml
---
- hosts: all
  gather_facts: false

  tasks:
    - name: delete ntp config
      ios_config:
        lines:
          - no ntp server 10.0.0.123

これで準備と各種実装は完了です。


■ テストの実行

いよいよテストの実行です。

一通りのアクションを実行してテストするため molecule test コマンドを実行します。 実行する場所は ロールのトップです。

(少しログが長いですが、 の箇所でコメントします)

$ pwd
/Users/sakana/molecule/set_ntp
$ molecule test
--> Test matrix
    
└── default
    ├── dependency
    ├── lint
    ├── cleanup
    ├── destroy
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    ├── cleanup
    └── destroy
    
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.
Skipping, missing the requirements file.
--> Scenario: 'default'
--> Action: 'lint'
--> Lint is disabled.
--> Scenario: 'default'
--> Action: 'cleanup'cleanup(NTPコンフィグの削除)の実行
    
    PLAY [all] *********************************************************************
    
    TASK [delete ntp config] *******************************************************
    changed: [ios1]
    
    PLAY RECAP *********************************************************************
    ios1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
--> Scenario: 'default'
--> Action: 'destroy'
Skipping, destroy action has no playbook.
--> Scenario: 'default'
--> Action: 'syntax'
    
    playbook: /Users/sakana/molecule/set_ntp/molecule/default/converge.yml
--> Scenario: 'default'
--> Action: 'create'
Skipping, create action has no playbook.
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.
--> Scenario: 'default'
--> Action: 'converge'converge(メイン処理、ntp server 10.0.0.123)の実行
    
    PLAY [Converge] ****************************************************************
    
    TASK [Include set_ntp] *********************************************************
    
    TASK [set_ntp : set ntp server] ************************************************
    changed: [ios1]
    
    PLAY RECAP *********************************************************************
    ios1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
--> Scenario: 'default'
--> Action: 'idempotence'    ★ 冪等性確認(もう一度実行してchangedにならないこと)
Idempotence completed successfully.
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.
--> Scenario: 'default'
--> Action: 'verify'verify(テスト、コンフィグの確認)の実行
--> Running Ansible Verifier
    
    PLAY [Verify] ******************************************************************
    
    TASK [show run] ****************************************************************
    ok: [ios1]
    
    TASK [assert ntp sever] ********************************************************
    ok: [ios1] => {
        "changed": false,
        "msg": "All assertions passed"
    }
    
    PLAY RECAP *********************************************************************
    ios1                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
Verifier completed successfully.
--> Scenario: 'default'
--> Action: 'cleanup'cleanup(NTPコンフィグの削除)の再実行
    
    PLAY [all] *********************************************************************
    
    TASK [delete ntp config] *******************************************************
    changed: [ios1]
    
    PLAY RECAP *********************************************************************
    ios1                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
--> Scenario: 'default'
--> Action: 'destroy'
Skipping, destroy action has no playbook.
--> Pruning extra files from scenario ephemeral directory

生成されるファイル

テストを実行すると ~/.cache/molecule/ロール名/default 配下に色々ファイルが生成されるようです。

参考までに載せておきます。

ansible.cfg

Playbook 実行時に利用される ansible.cfg のようです。

# Molecule managed

[defaults]
ansible_managed = Ansible managed: Do NOT edit this file manually!
display_failed_stderr = True
forks = 50
retry_files_enabled = False
host_key_checking = False
nocows = 1
interpreter_python = auto
[ssh_connection]
scp_if_ssh = True
control_path = %(directory)s/%%h-%%p-%%r

内容は、molecule.yml 内の provisioner.config_options でカスタマイズできるようです。

Ansible Provisioner の例から抜粋

provisioner:
  name: ansible
  config_options:
    defaults:
      fact_caching: jsonfile
    ssh_connection:
      scp_if_ssh: True

inventory/ansible_inventory.yml

molecule.yml 内の platforms の設定と関係がありそうです。

# Molecule managed

---
all:
  hosts:
    ios1: &id001 {}
  vars:
    molecule_ephemeral_directory: '{{ lookup(''env'', ''MOLECULE_EPHEMERAL_DIRECTORY'')
      }}'
    molecule_file: '{{ lookup(''env'', ''MOLECULE_FILE'') }}'
    molecule_instance_config: '{{ lookup(''env'', ''MOLECULE_INSTANCE_CONFIG'') }}'
    molecule_no_log: '{{ lookup(''env'', ''MOLECULE_NO_LOG'') or not molecule_yml.provisioner.log|default(False)
      | bool }}'
    molecule_scenario_directory: '{{ lookup(''env'', ''MOLECULE_SCENARIO_DIRECTORY'')
      }}'
    molecule_yml: '{{ lookup(''file'', molecule_file) | molecule_from_yaml }}'
ungrouped:
  hosts:
    ios1: *id001
  vars: {}

state.yml

どのような用途なのかは不明です。

# Molecule managed

---
converged: false
created: false
driver: null
is_parallel: false
prepared: null
run_uuid: b4424680-8cbf-4d14-b073-757caba749b5

inventory/hosts

molecule.yml 内の provisioner.inventory.host 内の内容と同じでした。テスト実行後、削除されました。

molecule.yml

(自分で書いたほうの) molecule.yml のデフォルト値を含めた内容でした。こちらもテスト実行後、削除されました。


■ おわりに

molecule を利用して、ネットワーク機器への設定のテストの自動化を試してみました。

私の理解が浅いのと、おそらくメジャーではない使い方という点で、少々ぎこちない実装があったり、molecule の機能を活用できていない点があるかもしれません。

また、今回レベルのテスト内容であれば、とくに molecule を利用するまでもない気もします。ですが、他のテストとやり方を合わせるという事情があるのであれば、molecule でやるならこう、というやり方をある程度抑えておくのもいいかなと思いました。

参考

公式ドキュメント molecule.readthedocs.io

delegated の使い方の参考になりました sky-joker.tech

qiita.com

qiita.com

qiita.com

www.slideshare.net