てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] 小ネタ: 順番が違うだけで読みにくい Playbook

はじめに

頭の体操なので、実運用で利用するのはおすすめしませんシリーズです。

Playbook は YAML で書きます。ディクショナリの順番には処理上の意味はありません。ところが、慣例上は以下のように hosts から書くことが書くことが多いのでしょうか。

- hosts: localhost
  gather_facts: no
  
  # ...略...

今回はこれを、処理に影響を与えない範囲で読みにくく崩してみます。

読みにくい Playbook

- gather_facts: false
  tasks:
    - when: 1 == 1
      debug:
        msg: "hello!"
      name: debug test
  connection: local
  hosts: localhost

ディクショナリの順番を変えただけですが、読みにくくなったと思います。 gather_factstasksconnectionhosts は 同じ階層のディクショナリのキーなので、どういう順番でも処理上は関係ないわけです。

ちゃんと実行できます。

$ ansible-playbook -i localhost, 00.yml 

PLAY [localhost] ********************************************************************************

TASK [debug test] ********************************************************************************
ok: [localhost] => {
    "msg": "hello!"
}

PLAY RECAP ********************************************************************************
localhost           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

よく見かける Playbook

よく見かけるのは以下のような書き方ではないでしょうか。

- hosts: localhost
  gather_facts: false
  connection: local

  tasks:
    - name: debug test
      debug:
        msg: "hello!"
      when: 1 == 1
  • 各プレイでは hosts が最初、gather_facts などを続ける
  • 各タスクでは name が最初、when が最後の方

しっくりきますね。はやり、よく見かける順番で書くのがいいと思います。

ただ、when については最後の方に書く週間が不思議に感じられる方もいらっしゃるようです。プログラムでいうと if に相当するので、タスクの最初の方に定義するのが自然ではという考え方も分かる気がします。

参考

もっとひどいのはこちら tekunabe.hatenablog.jp

[Ansible] restconf_config モジュールで Cisco IOS XE のSyslogサーバー設定追加・削除してみる

■ はじめに

Ansible 2.8 では、RESTCONF でネットワーク機器の情報を取得したり、設定を変更したりできる RESTCONF モジュールが導入されました。

以前、本ブログでは、モジュールを利用してIOS-XE へ RESTCONF でアクセスしてインターフェース情報の取得をためした記事を投稿しました。

今回は、情報の取得ではなく、設定追加・削除を試します。restconf_config モジュールを利用し、送信先 Syslog サーバーの追加、削除をしてみます。

環境


■ 準備

Playook 実行に必要なファイルを作成していきます。

インベントリファイル

ホスト情報を示すインベントリファイルを作成します。

  • inventory
[ios]
iosao1 ansible_host=ios-xe-mgmt.cisco.com

変数定義ファイル

インベントリファイルで定義したグループ ios が利用する変数を定義するファイルを作成します。

  • group_vars/ios.yml
ansible_connection: httpapi
ansible_httpapi_port: 9443
ansible_network_os: restconf      # ios ではないので注意
ansible_user: root
ansible_password: p@ss9999
ansible_httpapi_use_ssl: yes
ansible_httpapi_validate_certs: no

各変数の意味は、前回の記事か、公式ドキュメントの httpapi コネクションプラグインのページを参照してください。


■ 追加(POST)する Playbook

追加 Playbook 作成

追加(POST)する Playbook を作成します。

Playbook 実行前は、ネットワーク機器側に送信先 Syslog サーバーの設定が一つもない状態です。 10.0.0.110.0.0.2 の2つ設定します。

  • config.yml
- hosts: ios
  gather_facts: no

  tasks:
    - name: restconf test
      restconf_config:
        path: /data/Cisco-IOS-XE-native:native/logging/host
        method: post
        content: "{{ content_data | to_json }}"
      vars:
        content_data:
            Cisco-IOS-XE-native:ipv4-host-list:
              - ipv4-host: 10.0.0.1
              - ipv4-host: 10.0.0.2

設定を追加したいので、method オプションには post を指定しています。

content オプションには、json データを指定します。restconf_config モジュールの Examples のよいうに、YAML 上に JSON を定義してもよいのですが、あまり好みではないため、普通に YAML の変数で定義したものを to_json フィルターに通しています。

追加 Playnook 実行

追加(POST)する Playbook を実行します。

$ ansible-playbook -i inventory config.yml 

PLAY [ios] ****************************************************************************************

TASK [restconf test] ******************************************************************************
changed: [iosao1]

PLAY RECAP ****************************************************************************************
ios         : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

なお、もう一度実行すると設定済みに POST することになるので、以下のエラーになります。

"msg": "resource '/data/Cisco-IOS-XE-native:native/logging/host' already exist"

普段使っているコマンドの感覚だとちょっと戸惑ってしまうかもしれません。

確認

設定されたことをネットワーク機器側で確認します。

csr1000v#sh run | inc logging host
logging host 10.0.0.1
logging host 10.0.0.2

無事に 10.0.0.110.0.0.2 が設定されました。 この時点では startup-config には保存されていません。

もちろん、restconf_get モジュールで情報取得して確認してもよいでしょう。

(補足)設定変更する場合は?

もし、設定済みの状態から設定変更する場合、は patchput メソッドを利用します。

    - name: restconf test
      restconf_config:
        path: /data/Cisco-IOS-XE-native:native/logging/host
        method: put
        content: "{{ content_data | to_json }}"
      vars:
        content_data:
          host:  # patch/put の場合はここから含める
            Cisco-IOS-XE-native:ipv4-host-list:
              - ipv4-host: 10.0.0.8
              - ipv4-host: 10.0.0.9

例えば、上記 の put する Playbook を実行すると、10.0.0.110.0.0.2消えて10.0.0.810.0.0.9 になります。no コマンドを意識しなくていいのは楽で良いです。(ただし毎回 chenged になる)


■ 削除(DELETE)する Playbook

削除 Playbook 作成

削除(DELETE)する Playbook を作成します。

Playbook 実行前は、さきほどの Playbook により、10.0.0.110.0.0.2 の2つが設定されている状態です。

  • config.yml
- hosts: ios
  gather_facts: no

  tasks:
    - name: restconf test
      restconf_config:
        path: /data/Cisco-IOS-XE-native:native/logging/host
        method: delete   # ポイント
        content: "{{ content_data | to_json }}"
      vars:
        content_data:
          Cisco-IOS-XE-native:ipv4-host-list:
            - ipv4-host: 10.0.0.1
            - ipv4-host: 10.0.0.2

追加(POST)の Playbook と違う点は、method オプションに delete を指定している点のみです。

削除 Playnook 実行

削除(DELETE)する Playbook を実行します。

$ ansible-playbook -i inventory config.yml 

PLAY [ios] ****************************************************************************************
TASK [restconf test] ******************************************************************************
changed: [iosao1]

PLAY RECAP **************************************************************************************
iosao1      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

もう一度実行すると chenged になりません。

確認

削除されたことをネットワーク機器側で確認します。

csr1000v#sh run | inc logging host
csr1000v#

無事に削除されました。


■ まとめ

restconf_config モジュールを利用して、Syslogサーバーの追加(POST)、削除(DELETE)をする簡単な例を試しました。

restconf_config モジュールmethod オプションには、他にも putpatch を指定することもできます。これらのメソッドにより、柔軟な設定変更ができます。RESTCONF なので、普段使っているコマンドとは勝手が違うものの、restconf_* モジュール は プラットフォームごとにモジュールを使い分ける必要がないというメリットもあります。

RESTCONF が利用できる機器では、設定変更の方法の選択肢になるのではないでしょうか。

show コマンド結果をパースする方法あれこれ(TextFSM / Genie Parser と Netmiko / Ansible の組み合わせ)

2019/09/05 に、ネットワークプログラマビリティ勉強会 #18で、「show コマンド結果をパースする方法あれこれ」という発表をさせていだきました。サンプルコードが中心だったため、コピペしやすいように、ブログ記事として書きおこします。



[2020/09/25 追記]

ansible.netcommon collection の 1.2.0 で、cli_parse モジュールが追加されました。コマンドの実行と本記事で紹介するパーサー TextFSM、ntc-templates、pyATS などとの連携してパースデータを取得できます。

[2021/01/29 追記] 先述の cli_parse モジュールが、ansible.utils collectionにも登場しました。今後はこちらでメンテナンスされているのかもしれません。


■ 1. はじめに

ネットワーク機器の通常の show コマンドの結果は、機械(プログラム)にとっては取り扱いにくくなりがちです。これを解決する、show コマンドの結果を構造化データ(JSONなど)にするパーサーをご紹介します。

具体的には、TextFSMGenie Parser という2つのパーサーです。また、自動化ツール NetmikoAnsible との組み合わせも、サンブルコードベースにご紹介します。すべて Python ベースのものです。

f:id:akira6592:20190906175638p:plain
パーサーと自動化ツールの関係

「そもそもネットワーク器機が構造化データを出力してくれればいいのでは」と思われるとも思いますが、運用中の機器にはそういった機能なまだまだ無かったりすることも多いのではないでしょうか。そんなときに、これらのパーサーが役に立ちます。


■ 2. パーサーとは

パーサーとは、show コマンドの結果を機械(プログラム)が取り扱いやすい構造化データに変換するものです。(今回の記事の文脈上の説明)

例えば、以下のような show ip interface brief の結果

csr1000v#show ip interface brief
Interface          IP-Address      OK? Method Status                Protocol
GigabitEthernet1   10.10.20.48     YES NVRAM  up                    up      
GigabitEthernet2   unassigned      YES NVRAM  administratively down down    
GigabitEthernet3   unassigned      YES NVRAM  administratively down down    

を、以下のような機械が取り扱いやすいデータに変換します。

{
  "INTF": "GigabitEthernet1",
  "IPADDR": "10.10.20.48",
  "PROTO": "up",
  "STATUS": "up"
},
{
  "INTF": "GigabitEthernet2",
  "IPADDR": "unassigned",
  "PROTO": "down",
  "STATUS": "administratively down"
},
{
  "INTF": "GigabitEthernet3",
  "IPADDR": "unassigned",
  "PROTO": "down",
  "STATUS": "administratively down"
}

今回、この記事でパースする、サンプルの show コマンド結果はこちらです。 Cisco DevNet Sandbox CSRV1000V IOS-XE (16.11.01a) の環境を利用させていただきました。

csr1000v#show ip interface brief
Interface            IP-Address      OK? Method Status                Protocol
GigabitEthernet1     10.10.20.48     YES NVRAM  up                    up      
GigabitEthernet2     unassigned      YES NVRAM  administratively down down    
GigabitEthernet3     unassigned      YES NVRAM  administratively down down    
Loopback0            unassigned      YES unset  up                    up      
Loopback1            unassigned      YES unset  up                    up      
Loopback2            unassigned      YES unset  up                    up     


■ 3. TextFSM

3.1. TextFSM とは

TextFSM は、カスタマイズが容易なパーサーです。

  • ntc-templates のテンプレートを利用可
    • プラットフォーム数 23
    • 1コマンド数 290
    • (2019/09/03現在)
  • テンプレートは追加、カスタマイズ可能
  • pip install textfsm でインストール

テンプレートは以下のような形です。正規表現でパースする方法を定義します。

Value INTF (\S+)
Value IPADDR (\S+)
Value STATUS (up|down|administratively down)
Value PROTO (up|down)

Start
  ^${INTF}\s+${IPADDR}\s+\w+\s+\w+\s+${STATUS}\s+${PROTO} -> Record

3.2. TextFSM 単体の利用例

TextFSM 単体では、ネットワーク機器への接続機能はありません。そのためは何かしらの方法で取得したコマンド結果を用意しておきます。(コード内の raw_text_data に相当するデータ)

コード

import textfsm
from pprint import pprint

# テンプレートファイルの読み込み
template = open('./templates/cisco_ios_show_ip_interface_brief.template', 'r')
re_table = textfsm.TextFSM(template)

# raw_text_data は何かしらの方法で取得したコマンド結果
fsm_results = re_table.ParseText(raw_text_data)
 
results = list()
# ヘッダーとデータを結合
for item in fsm_results:
    results.append(dict(zip(re_table.header, item)))

# 表示
pprint(results)

なお、公式のサンプルはこちらです。

実行結果

以下のようにパースされます。ここまで、構造化されていれば、コードから取り扱いしやすいですね。

[{'INTF': 'GigabitEthernet1',
  'IPADDR': '10.10.20.48',
  'PROTO': 'up',
  'STATUS': 'up'},
 {'INTF': 'GigabitEthernet2',
  'IPADDR': 'unassigned',
  'PROTO': 'down',
  'STATUS': 'administratively down'},
 {'INTF': 'GigabitEthernet3',
  'IPADDR': 'unassigned',
  'PROTO': 'down',
  'STATUS': 'administratively down'},
 {'INTF': 'Loopback0', 'IPADDR': 'unassigned', 'PROTO': 'up', 'STATUS': 'up'},
 {'INTF': 'Loopback1', 'IPADDR': 'unassigned', 'PROTO': 'up', 'STATUS': 'up'},
 {'INTF': 'Loopback2', 'IPADDR': 'unassigned', 'PROTO': 'up', 'STATUS': 'up'}]

3.3. TextFMS + Netmiko 利用例

ネットワーク自動化ライブラリ Netmikoでは、Netmiko 2.0.0 から TextFSM が同梱されていて、簡単に連携できます。

なお、Netmiko インストール時に TextFSM も一緒にインストールされます。

コード

send_command の引数に、use_textfsm=True を付加するのがポイントです。

from netmiko import Netmiko
import os
from pprint import pprint

# 接続情報の定義
ios1 = {
  "host": "10.10.20.48",
  "device_type": "cisco_ios",
  "username": "testuser",
  "password": "testpass"
}

# テンプレートファイル格納ディレクトリを環境変数に指定
# デフォルトは ./ntc-templates/templates
os.environ["NET_TEXTFSM"] = "/home/npstudy/templates"

# 接続
net_connect = Netmiko(**ios1)
# コマンド実行、パース
result = net_connect.send_command("show ip interface brief", use_textfsm=True)
# 表示
pprint(result)

実行結果

以下のようにパースされます。

[{'intf': 'GigabitEthernet1',
  'ipaddr': '10.10.20.48',
  'proto': 'up',
  'status': 'up'},
 {'intf': 'GigabitEthernet2',
  'ipaddr': 'unassigned',
  'proto': 'down',
  'status': 'administratively down'},
 {'intf': 'GigabitEthernet3',
  'ipaddr': 'unassigned',
  'proto': 'down',
  'status': 'administratively down'},
 {'intf': 'Loopback0', 'ipaddr': 'unassigned', 'proto': 'up', 'status': 'up'},
 {'intf': 'Loopback1', 'ipaddr': 'unassigned', 'proto': 'up', 'status': 'up'},
 {'intf': 'Loopback2', 'ipaddr': 'unassigned', 'proto': 'up', 'status': 'up'}]

3.4. TextFMS + Ansible 利用例

今度は Ansible と組み合わせた利用例です。

Ansible から TextFSM のテンプレートを利用して、パースするには Ansible 2.4 で追加された parse_cli_textfsmフィルターを利用します。

このフィルターを利用するには、あらかじめ TextFSM をインストールしておく必要があります。

$ pip install textfsm

Playbook

- hosts: ios
  gather_facts: no
 
  vars:
    template_file: "./templates/cisco_ios_show_ip_interface_brief.template"
 
  tasks:
    - name: show
      ios_command:
        commands:
          - show ip interface brief
      register: result
 
    - name: show parsed result
      debug:
        msg: "{{ result.stdout[0] | parse_cli_textfsm(template_file) }}"

実行結果

以下のようにパースされます。

ok: [ios1] => {
  "msg": [
      {
          "INTF": "GigabitEthernet1",
          "IPADDR": "10.10.20.48",
          "PROTO": "up",
          "STATUS": "up"
      },
      {
          "INTF": "GigabitEthernet2",
          "IPADDR": "unassigned",
          "PROTO": "down",
          "STATUS": "administratively down"
      },
      {
          "INTF": "GigabitEthernet3",
          "IPADDR": "unassigned",
          "PROTO": "down",
          "STATUS": "administratively down"
      },
      {
          "INTF": "Loopback0",
          "IPADDR": "unassigned",
          "PROTO": "up",
          "STATUS": "up"
      },
      {
          "INTF": "Loopback1",
          "IPADDR": "unassigned",
          "PROTO": "up",
          "STATUS": "up"
      },
      {
          "INTF": "Loopback2",
          "IPADDR": "unassigned",
          "PROTO": "up",
          "STATUS": "up"
      }
  ]
}




■ 4. Genie Parser

4.1. Genie Parser とは

Genie Parser は、Cisco 機器に強いパーサーです。

4.2. Genie Parser 単体の利用例

Genie にはネットワーク機器への接続機能があります。testdeb と呼ばれる接続情報を YMAL で定義し、コードから呼び出します。

コード

  • testbed (接続情報)ファイル
testbed: 
  name: "testbed1"
devices:
  csr1000v:
      type: catalyst
      platform: iosxe
      os: "iosxe"
      alias: "ios1"
      tacacs:
          login_prompt: "login:"
          password_prompt: "Password:"
          username: testuser
      passwords:
          tacacs: testpass
      # enable: testpass
      # line: testpass
      connections:
          ssh:
              protocol: ssh
              ip: "10.10.20.48"
  • コード
from genie.conf import Genie
from pprint import pprint

# testbed (接続情報)を読み込む
testbed = Genie.init("./testbed.yml") 
device = testbed.devices["csr1000v"]

# 機器への接続
device.connect()

# コマンド実行、パース
output = device.parse("show ip interface brief")

# 表示
pprint(output)

実行結果

以下のようにパースされます。最初はログインや ter len 0 などのログが流れます。(device.connect(log_stdout=False) とすることで非表示することも可能。@ccieojisanさん、情報ありがとうございます)

[2019-09-04 15:30:20,523] +++ csr1000v logfile /tmp/csr1000v-cli-20190904T153020523.log +++
[2019-09-04 15:30:20,524] +++ Unicon plugin iosxe +++
Password: 
[2019-09-04 15:30:28,022] +++ connection to spawn: ssh -l developer 10.10.20.48, id: 4458345528 +++
[2019-09-04 15:30:28,023] connection to csr1000v

...(略)...

{'interface': {'GigabitEthernet1': {'interface_is_ok': 'YES',
                                    'ip_address': '10.10.20.48',
                                    'method': 'NVRAM',
                                    'protocol': 'up',
                                    'status': 'up'},
               'GigabitEthernet2': {'interface_is_ok': 'YES',
                                    'ip_address': 'unassigned',
                                    'method': 'NVRAM',
                                    'protocol': 'down',
                                    'status': 'administratively down'},
               'GigabitEthernet3': {'interface_is_ok': 'YES',
                                    'ip_address': 'unassigned',
                                    'method': 'NVRAM',
                                    'protocol': 'down',
                                    'status': 'administratively down'},
               'Loopback0': {'interface_is_ok': 'YES',
                             'ip_address': 'unassigned',
                             'method': 'unset',
                             'protocol': 'up',
                             'status': 'up'},
               'Loopback1': {'interface_is_ok': 'YES',
                             'ip_address': 'unassigned',
                             'method': 'unset',
                             'protocol': 'up',
                             'status': 'up'},
               'Loopback2': {'interface_is_ok': 'YES',
                             'ip_address': 'unassigned',
                             'method': 'unset',
                             'protocol': 'up',
                             'status': 'up'}}}

4.3. Genie Parser + Netmiko 利用例

Netmiko 2.4.1 で Genie Parser との連携機能が追加されました。

Netmiko から Genie Parser を利用するには、あらかじめンストールしておく必要があります。

$ pip install genie

コード

send_command の引数に、use_genie=True を付加するのがポイントです。

from netmiko import Netmiko
import os
from pprint import pprint

# 接続情報の定義
ios1 = {
  "host": "10.10.20.48",
  "device_type": "cisco_ios",
  "username": "testuser",
  "password": "testpass"
}

# 接続
net_connect = Netmiko(**ios1)

# コマンド実行、パース
result = net_connect.send_command("show ip interface brief", use_genie=True)

# 表示
pprint(result)

実行結果

以下のようにパースされます。

{'interface': {'GigabitEthernet1': {'interface_is_ok': 'YES',
                                    'ip_address': '10.10.20.48',
                                    'method': 'NVRAM',
                                    'protocol': 'up',
                                    'status': 'up'},
               'GigabitEthernet2': {'interface_is_ok': 'YES',
                                    'ip_address': 'unassigned',
                                    'method': 'NVRAM',
                                    'protocol': 'down',
                                    'status': 'administratively down'},
               'GigabitEthernet3': {'interface_is_ok': 'YES',
                                    'ip_address': 'unassigned',
                                    'method': 'NVRAM',
                                    'protocol': 'down',
                                    'status': 'administratively down'},
               'Loopback0': {'interface_is_ok': 'YES',
                             'ip_address': 'unassigned',
                             'method': 'unset',
                             'protocol': 'up',
                             'status': 'up'},
               'Loopback1': {'interface_is_ok': 'YES',
                             'ip_address': 'unassigned',
                             'method': 'unset',
                             'protocol': 'up',
                             'status': 'up'},
               'Loopback2': {'interface_is_ok': 'YES',
                             'ip_address': 'unassigned',
                             'method': 'unset',
                             'protocol': 'up',
                             'status': 'up'}}}

4.4. Genie Parser + Ansible 利用例

今度は Ansible と組み合わせた利用例です。

Ansible から Genie Parser を利用するには、clay584.parse_genie というロールが必要です。Genie Parser に加えて、本ロールもあらかじめインストールしておきます。

$ pip install genie
$ ansible-galaxy install clay584.parse_genie

なお、本ロールは Ansible 2.7 以上が必要です。

Playbook

- hosts: ios
  gather_facts: no
  vars:  # 実行 show コマンドの定義
    show: show ip interface brief

  tasks:
    - name: Read in parse_genie role
      include_role:         # clay584.parse_genie ロールを利用
        name: clay584.parse_genie
        
     - name: ios command test
      ios_command:          # コマンド実行
        commands:
          - "{{ show }}"
      register: result      # 実行結果が変数 result に入る

     - name: parsed result  # parse_genie フィルターでパース
      debug:
        msg: "{{ result.stdout[0] | parse_genie(command=show, os='iosxe') }}"

実行結果

以下のようにパースされます。

ok: [ios1] => {
    "msg": {interface”: {
            "GigabitEthernet1": {
                "interface_is_ok": "YES",
                "ip_address": "10.10.20.48",
                "method": "NVRAM",
                "protocol": "up",
                "status": "up"
            },
            "GigabitEthernet2": {
                "interface_is_ok": "YES",
                "ip_address": "unassigned",
                "method": "NVRAM",
                "protocol": "down",
                "status": "administratively down"
            },
            "GigabitEthernet3": {
                "interface_is_ok": "YES",
                "ip_address": "unassigned",
                "method": "NVRAM",
                "protocol": "down",
                "status": "administratively down"
            },
            "Loopback0": {
                "interface_is_ok": "YES",
                "ip_address": "unassigned",
                "method": "unset",
                "protocol": "up",
                "status": "up"
            },
            "Loopback1": {
                "interface_is_ok": "YES",
                "ip_address": "unassigned",
                "method": "unset",
                "protocol": "up",
                "status": "up"
            },
            "Loopback2": {
                "interface_is_ok": "YES",
                "ip_address": "unassigned",
                "method": "unset",
                "protocol": "up",
                "status": "up"
            }
        }
    }
}


■ 5. まとめ


参考資料

[Ansible] ipaddr フィルターで特定の範囲のIPアドレスを抽出する

■ はじめに

Ansible には、IP アドレスに対してさまざまフィルターをかける ipaddrというフィルタープラグインがあります。

過去記事参考: [ipaddr フィルターでプレフィックス表記とネットマスク表記を変換する)

このフィルターを使って、IPアドレスのリストの中から、特定の範囲のIPアドレスだけを抽出する方法を試します。

  • 動作確認環境: Ansible 2.8.4
  • 要 netaddr (pip install netaddr でインストール)

■ サンプル

簡単なサンプルで説明します。

Playbook

検証する Playbook は以下の通りです。

target_list に定義されているIPアドレスの中で ip_range (192.168.12.0/24) に含まれているものだけを debug モジュールで出力します。

---
- hosts: localhost
  connection: local
  gather_facts: no

  # 各種変数の定義
  vars: 
    ip_range: 192.168.12.0/24     # この範囲のIPアドレスだけ抽出したい
    target_list:                  # 調べたいIPアドレスのリスト
        - 192.168.12.0            # 範囲内
        - 192.168.12.1            # 範囲内
        - 192.168.12.254          # 範囲内
        - 192.168.12.255          # 範囲内
        - 192.168.15.0
        - 192.168.15.1
        - 192.168.15.254
        - 192.168.15.255

  # タスクの定義
  tasks:
    - name: net range test
      debug:
        msg: "{{ item }} は {{ ip_range }} の範囲内です"
      loop: "{{ target_list | ipaddr(ip_range) }}" # フィルター後にループ

実行結果

Playbook を実行します。

$ ansible-playbook -i localhost, test.yml 

PLAY [localhost] **************************************************************************************************

TASK [net range test] *********************************************************************************************
ok: [localhost] => (item=192.168.12.0) => {
    "msg": "192.168.12.0 は 192.168.12.0/24 の範囲内です"
}
ok: [localhost] => (item=192.168.12.1) => {
    "msg": "192.168.12.1 は 192.168.12.0/24 の範囲内です"
}
ok: [localhost] => (item=192.168.12.254) => {
    "msg": "192.168.12.254 は 192.168.12.0/24 の範囲内です"
}
ok: [localhost] => (item=192.168.12.255) => {
    "msg": "192.168.12.255 は 192.168.12.0/24 の範囲内です"
}

PLAY RECAP ********************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

192.168.12.0/24 の範囲内の IP アドレスだけ抽出されました。ループ前に抽出されるので、 そもそも 192.168.15.0 などはループの対象にはならず、skipped としても表示されません。

もしループ後に抽出したい場合は、以下の記事のように when で判断するのが良いと思います。このあたりはお好みで。

qiita.com

なお、192.168.12.0/24 ではなく 192.168.12.0/255.255.255.0 のようなマスク表記でも正常に抽出されました。 192.168.12.0/24192.168.13.0/24 のような範囲同士の比較、抽出もできるようようです。

■ まとめ

ipaddr フィルターで特定の範囲のIPアドレスを抽出する方法をご紹介しました。

ipaddr フィルタープラグインには、他にもさまざまな機能がありますので、IP アドレスに対してなにか加工したい場合は、このフィルターを調べてみると良いかもしれません。

[Ansible] TRANSFORM_INVALID_GROUP_CHARS を ignore に設定したときの表示が Ansible 2.8.2 から変更された

はじめに

Ansible 2.8.0 から、グループ名に利用できる文字が厳格化されました。 たとえば、ハイフンが入っていると不正とみなされ、WARNING が表示されます。

https://image.slidesharecdn.com/ansiblenight201905ansible2-190531065357/95/ansible-28-21-638.jpg?cb=1559302122

https://www.slideshare.net/akira6592/ansible28update/21

この挙動は、TRANSFORM_INVALID_GROUP_CHARS という設定項目で変更できます。

これを never に設定した時の WARNING の出方が、Ansible 2.8.2 で変わりました。

ansible.cfg で設定変更する場合は、以下のようにします。

[defaults]
force_valid_group_names=ignore

デフォルト(never)で表示される WARNING

デフォルトでは以下の表示になります。これは 2.8.0 でも 2.8.2 以降でも同じです。

[DEPRECATION WARNING]: The TRANSFORM_INVALID_GROUP_CHARS settings is set to allow bad characters in group names by default, this will change, but still be user 
configurable on deprecation. This feature will be removed in version 2.10. Deprecation warnings can be disabled by setting deprecation_warnings=False in 
ansible.cfg.
 [WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details

never 設定時

設定確認

$ ansible-config dump --only-changed
TRANSFORM_INVALID_GROUP_CHARS(/home/akira/ansible/ansible.cfg) = ignore

2.8.0 - 2.8.1 での表示

1つのめの WARNING は表示されたままです。

[DEPRECATION WARNING]: The TRANSFORM_INVALID_GROUP_CHARS settings is set to allow bad characters in group names by default, this will change, but still be user 
configurable on deprecation. This feature will be removed in version 2.10. Deprecation warnings can be disabled by setting deprecation_warnings=False in 
ansible.cfg.

2.8.2 以降では WARNING なし

WARNINGは表示されなくなります。

[Ansible] ipaddr フィルターでプレフィックス表記とネットマスク表記を変換する

■ はじめに

Ansible には、IP アドレスに対してさまざまフィルターをかける ipaddrというフィルタープラグインがあります。

このフィルターを使って、プレフィックス表記とネットマスク表記を相互に変換する方法をまとまめす。

  • プレフィックス表記からネットマスク表記
    • 例: 172.16.1.1/24 → 172.16.1.1/255.255.255.0
  • ネットマスク表記からプレフィックス表記
    • 例: 172.16.1.1/255.255.255.0 → 172.16.1.1/24

ipaddr('netmask')でネットマスクを、ipaddr('prefix')プレフィックスを取得するのがポイントです。

  • 動作確認環境: Ansible 2.8.4
  • 要 netaddr (pip install netaddr でインストール)


■ フィルターの書き方

プレフィックス表記からネットマスク表記

 "{{ '172.16.1.1/24' | replace(item | ipaddr('prefix'), '') }}{{ item | ipaddr('netmask') }}"

変換後: 172.16.1.1/255.255.255.0

ホストアドレスにもネットワークアドレスにも対応させるために、少々まどろこっしい書き方になっています。

本当は、ipaddr フィルターで直接 172.16.1.1/24172.16.1.1 にしてからネットマスクを付加したかったのですが、コードを見てもやり方が見つかりませんでした。そのため、まず replaceプレフィックスを削除しています。

なお、スラッシュ区切りではなく、スペース区切りで良ければ以下のようにシンプルなフィルターでOKです。

"{{ '172.16.1.1/24' | ipaddr('ip_netmask') }}"

変換後: 172.16.1.1 255.255.255.0

ただし、ネットワークアドレスをフィルタすると空になってしまうので、以下のようにする必要があります。

"{{ '172.16.1.0/24' | ipaddr('network_netmask') }}"

変換後: 172.16.1.0 255.255.255.0

ネットマスク表記からプレフィックス表記

 "{{ '172.16.1.1/255.255.255.0' | replace(item | ipaddr('netmask'), '') }}{{ item | ipaddr('prefix') }}"

変換後: 172.16.1.1/24

少々まどろっこしくなっているのは前述の事情のとおりです。

なお、ホストアドレスだけが対象であれば、以下のようにシンプルなフィルターでOKです。

"{{ '172.16.1.1/255.255.255.0' | ipaddr('ip/prefix') }}"

変換後: 172.16.1.1/24

ネットワークアドレスだけが対象であれば、以下のとおりです。

"{{ '172.16.1.0/255.255.255.0' | ipaddr('network/prefix') }}"

変換後: 172.16.1.0/24


■ サンプル

いくつかインプットのパターンを用意した Playbook で試します。

Playbook

Playbook はこちらです。

  • netconv.yml
- hosts: localhost
  gather_facts: no
  connection: local

  tasks:
    - name: prefix > netmask
      debug:
        msg: "{{ item | replace(item | ipaddr('prefix'), '') }}{{ item | ipaddr('netmask') }}"
      loop:
        - 172.16.1.0/24     # ネットワークアドレス
        - 172.16.1.1/24
        - 172.16.1.254/24
        - 172.16.1.255/24   # ブロードキャストアドレス

    - name: mask > netprefix
      debug:
        msg: "{{ item | replace(item | ipaddr('netmask'), '') }}{{ item | ipaddr('prefix') }}"
      loop:
        - 172.16.1.0/255.255.255.0     # ネットワークアドレス
        - 172.16.1.1/255.255.255.0
        - 172.16.1.254/255.255.255.0
        - 172.16.1.255/255.255.255.0   # ブロードキャストアドレス

実行結果

実行します。

$ ansible-playbook -i localhost, netconv.yml 

(抜粋)

TASK [prefix > netmask] ***********************************
ok: [localhost] => (item=172.16.1.0/24) => {
    "msg": "172.16.1.0/255.255.255.0"
}
ok: [localhost] => (item=172.16.1.1/24) => {
    "msg": "172.16.1.1/255.255.255.0"
}
ok: [localhost] => (item=172.16.1.254/24) => {
    "msg": "172.16.1.254/255.255.255.0"
}
ok: [localhost] => (item=172.16.1.255/24) => {
    "msg": "172.16.1.255/255.255.255.0"
}

TASK [mask > netprefix] ***********************************
ok: [localhost] => (item=172.16.1.0/255.255.255.0) => {
    "msg": "172.16.1.0/24"
}
ok: [localhost] => (item=172.16.1.1/255.255.255.0) => {
    "msg": "172.16.1.1/24"
}
ok: [localhost] => (item=172.16.1.254/255.255.255.0) => {
    "msg": "172.16.1.254/24"
}
ok: [localhost] => (item=172.16.1.255/255.255.255.0) => {
    "msg": "172.16.1.255/24"
}


■ まとめ

ipaddr フィルタープラグインを使って、プレフィックス表記とネットマスク表記を相互に変換する方法をまとめました。ipaddr フィルタープラグインには、他にもさまざまな機能がありますので、IP アドレスに対してなにか加工したい場合は、このフィルターを調べてみると良いかもしれません。

※ もっと良い方法をご存知の方、@akira6592 までご連絡いただけると幸いです。


参考

[Ansible] 実行ログで全タスク中の何タスク目なのかを表示する counter_enabled コールバックプラグインを検証する

■ はじめに

ansible-playbook コマンドを実行中、流れる実行ログを眺めながら「あとどのくらいのタスクが残っているんだろう?」と考えたことはありませんでしょうか。

counter_enabled というコールバックプラグインを利用すると、全タスク中の何タスク目なのかが分かるようになります。

f:id:akira6592:20190829090543p:plain
カウンター表示

ansible.cfg では以下のように設定します。

[defaults]
stdout_callback = counter_enabled

環境変数でも設定できます。

export ANSIBLE_STDOUT_CALLBACK=counter_enabled

この記事では、counter_enabled コールバックプラグインを検証した結果をまとめます。(ちょっとした注意点もあります)

  • 動作確認環境: Ansible 2.8.4


■ 検証

設定の変更(stdout_callback = counter_enabled)

ansible.cfgcounter_enabled コールバックプラグインを利用するように変更します。

[defaults]
stdout_callback = counter_enabled

デフォルトから設定が変更されたことを確認します。

$ ansible-config dump --only-changed
DEFAULT_STDOUT_CALLBACK(/Users/ansible/ansible.cfg) = counter_enabled
$ 

使用する Playbook

以下の Playbook で検証します。ループも仕込んでみます。

  • counter_test.yml
- hosts: localhost
  gather_facts: no
  connection: local

  tasks:
    - name: task1
      debug:
        msg: Hello, Kingyo 1.

    - name: task2
      debug:
        msg: "Hello, {{ item }}"
      loop:
        - Kingyo2-1
        - Kingyo2-2
        - Kingyo2-3

    - name: task3
      debug:
        msg: Hello, Kingyo 3.

Playbook 実行

それでは、 Playbook を実行します。

$ ansible-playbook -i localhost, counter_test.yml 

PLAY [localhost] **********************************************************************************

TASK 1/3 [task1] **********************************************************************************
ok: 1/1 [localhost] => {
    "msg": "Hello, Kingyo 1."
}

TASK 2/3 [task2] **********************************************************************************

TASK 3/3 [task3] **********************************************************************************
ok: 1/1 [localhost] => {
    "msg": "Hello, Kingyo 3."
}

PLAY RECAP ****************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    rescued=0    ignored=0   

TASK 1/3TASK 2/3TASK 3/3 のように、全タスク中の何タスク目なのかが表示さました、

ところがループしたタスクの実行ログが表示されなくなってしまいました。 別のモジュールを利用して試した限り、ループ自体は実行されているようです。バグなのか仕様なのか、現在のところは不明です。それらしい issue はありませんでした。

デフォルトだと

比較のために、コールバックプラグインを指定しない場合の実行ログも掲載します。TASK 1/3TASK 2/3TASK 3/3 といった表示はありません。

PLAY [localhost] *************************************************************************************************

TASK [task1] *****************************************************************************************************
ok: [localhost] => {
    "msg": "Hello, Kingyo 1."
}

TASK [task2] *****************************************************************************************************
ok: [localhost] => (item=Kingyo2-1) => {
    "msg": "Hello, Kingyo2-1"
}
ok: [localhost] => (item=Kingyo2-2) => {
    "msg": "Hello, Kingyo2-2"
}
ok: [localhost] => (item=Kingyo2-3) => {
    "msg": "Hello, Kingyo2-3"
}

TASK [task3] *****************************************************************************************************
ok: [localhost] => {
    "msg": "Hello, Kingyo 3."
}


■ まとめ

counter_enabled コールバックプラグインを利用して、Playbook の実行ログで全タスク中の何タスク目なのかが表示されることを確認しました。

ただ、私が試したPlaybook、環境(Ansible 2.8.4)では、ループするタスクの実行ログが表示されないという現象に出会いました。それらしい issue はありませんでした。

また、今回は検証していませんが、ロールと併用するととうまくカウントしてくれないといった現象もあるようです。 https://github.com/ansible/ansible/issues/53907

他にもimport_* include_* との併用時にもうまくカウントできるか気になります。(こちらも未検証)

カウントできることは便利だと思いますので、特性をおさえたうで、活用を検討してみてはいかがでしょうか。

参考

Ansible には他にも様々なコールバックプラグイン があります。ご自身にあったもの探してみてはいかがでしょうか。