てくなべ (tekunabe)

ansible / network automation / 学習メモ

Ansible のフィルターでリストから組み合わせを生成する(combinations, zip, zip_longest)

f:id:akira6592:20170604104846p:plain

■ はじめに

Ansible 2.3 でCombination Filters というリストの組み合わせを生成するフィルターが追加されました。 公式ドキュメントに使用例が載っていますが、いくつか実際に試してみて出力結果を含めて確認します。

Filters — Ansible Documentation

■ combinations で組み合わせを生成する

1つのリストを元に組み合わせを生成します。

使い方

例:リスト[1,2,3,4,5] から2つ選ぶ組み合わせを生成する

- name: give me combinations for sets of 2
  debug: msg="{{ [1,2,3,4,5]|combinations(2)|list }}"

実行結果

"msg": [
    [
        1,
        2
    ],
    [
        1,
        3
    ],
    [
        1,
        4
    ],
    [
        1,
        5
    ],
    [
        2,
        3
    ],
    [
        2,
        4
    ],
    [
        2,
        5
    ],
    [
        3,
        4
    ],
    [
        3,
        5
    ],
    [
        4,
        5
    ]
]

■ zip でリストを統合する

Pythonzip 関数のように、リストから組み合わせを生成します。 2つのリストの長さが異なる場合は、短いほうに合わせられます。

使い方

例:2つのリストが統合する

- name: give me shortest combo of 2 lists
  debug: msg="{{ [1,2,3]|zip(['a','b','c','d','e','f'])|list }}"

実行結果

"msg": [
    [
        1,
        "a"
    ],
    [
        2,
        "b"
    ],
    [
        3,
        "c"
    ]
]

■ zip_longest でリストを統合する

Pythonzip_longest関数のように、リストから組み合わせを生成します。 前述の zip とは異なり、2つのリストの長さが異なる場合は、長いほうに合わせられます。 足りない部分はfillvalueで指定したもので補完されます。

使い方

例:3つのリストを統合し、足りない部分は 'X' で補完する

- name: give me longest combo of 3 lists , fill with X
  debug: msg="{{ [1,2,3]|zip_longest(['a','b','c','d','e','f'], [21, 22, 23], fillvalue='X')|list }}"

実行結果

"msg": [
    [
        1,
        "a",
        21
    ],
    [
        2,
        "b",
        22
    ],
    [
        3,
        "c",
        23
    ],
    [
        "X",
        "d",
        "X"
    ],
    [
        "X",
        "e",
        "X"
    ],
    [
        "X",
        "f",
        "X"
    ]
]

■ さいごに

他の方法で代替できるケースもありそうですが、複雑になる場合はこれらのフィルターを利用するのが便利そうです。

以上です。

Netmikoで 「TELNET」経由のネットワーク機器自動化を試す

■ はじめに

Netmiko とは、Cisco IOS や Juniper Junos などのネットワーク機器にSSHでログインして操作するのを助けてくれるPythonのライブラリです。 例えば、ログインやコンフィギュレーションモードへの移行、ログアウトなどの操作は関数として抽象化されています。 IPアドレスやOSPFの設定など、細かい設定はコマンドを直接指定するタイプです。

github.com

上記リポジトリの説明にはSSH接続と記載されていますが、過去のリリースノート等を確認するとTELNETでも接続できるようです。 今回はCisco IOS機器へのTELNETを試してみます。

[参考] Netmiko 1.0 Release · Issue #245 · ktbyers/netmiko · GitHub

[通常の使い方] Netmiko Quickstart - Network to Code, LLC

【ご注意】
盗聴や改ざん等のセキュリティ上の観点により、
TELNETではなくSSHが推奨されます。
この記事はTELNETを推奨する意図はありません。
ネットワーク自動化系のライブラリやツールは、
CLIはSSH経由のものが多いため「TELNETが可能なものもある」
ということを知識、経験としておさえておくために
検証した結果を記事にしたものです。

■ 準備

Netmiko をインストールします。

pip install netmiko

■ コード

以下の要件のコードを記述します。

  • TELNETでログインして、vlan 1 の description を設定
  • 事前事後で running-config を確認

Netmiko の仕様として、通常通りSSHであれば device_type"cisco_ios" と指定するところ、 "cisco_ios_telnet" と指定するところがポイントです。

from netmiko import ConnectHandler

# デバイスのタイプやIPアドレス、認証情報をディクショナリで定義
cisco = {
    "device_type": "cisco_ios_telnet",
    "ip": "192.168.1.13",        # 接続先のCisco IOS機器
    "username": "testuser",      # ログインユーザー
    "password": "testpassword",  # ログインパスワード
    "secret": "enablepassword"   # enable パスワード
}

# TELNET接続して、コネクションオブジェクトを取得
net_connect = ConnectHandler(**cisco)

# 特権モードへ移行する
net_connect.enable()

print("--- [●事前確認] ---")
# show コマンドを実行
output = net_connect.send_command("show run int vlan 1")
# 結果を出力
print(output)

# 実行したい設定コマンドをリストで定義
lines = ["int vlan 1", "description hogehoge"]
# 設定コマンドを実行(事前のconf tも自動で実行される)
output= net_connect.send_config_set(lines)

print("\n--- [●事後確認] ---")
# show コマンドを実行
output = net_connect.send_command("show run int vlan 1")
# 結果を出力
print(output)

# 切断
net_connect.disconnect()

■ 実行結果

以下のように description が正常に追加されました。

--- [●事前確認] ---
Building configuration...

Current configuration : 77 bytes
!
interface Vlan1
 ip address 10.0.101.1 255.255.255.0
 ip ospf cost 10
end


--- [●事後確認] ---
Building configuration...

Current configuration : 99 bytes
!
interface Vlan1
 description hogehoge
 ip address 10.0.101.1 255.255.255.0
 ip ospf cost 10
end

■ 本当にTELNETか確認

念のため、本当にTELNET接続しているのか確認します。ここでは wireshark でパケットキャプチャして確認します。

※自身の管理下の環境で確認しています。 f:id:akira6592:20170528205334p:plain

さらに 「Follow TCP Stream」で当該TELNETセッションを流れを確認します。

f:id:akira6592:20170528205345p:plain

■ さいごに

Netmiko を利用して、TELNET経由で Cisco IOS 機器へ簡単な設定をすることができました。 ただし、冒頭の注意書きの通りTELNETはセキュリティ観点で推奨されません。 もし、既存の自動化システムの調査していてNetmikoが利用されている場合、SSHだと決めつけずにtelnetかもしれないという確認のきっかけにはなるかと思います。

Ansible の napalm-ansible モジュール群でCisco IOS 機器の様々な情報を取得する

f:id:akira6592:20170527215846p:plain

■ はじめに

マルチベンダー対応のネットワーク機器制御ライブラリのNAPALMには、ansibleと連携するための napalm-ansible というモジュール群があります。 今回はその中の napalm_get_facts というモジュールを利用して Cisco IOS 機器の様々な情報を取得してみます。

[githubリポジトリ]

GitHub - napalm-automation/napalm-ansibleGitHub - napalm-automation/napalm-ansible

[参考情報(Aristaでの例)]

AnsibleとNAPALMの連携を試してみた – ネットワークエンジニアが日々の出来事を語る

なお、標準モジュールである ios_facts を利用する場合は、以下の記事をご参照ください。

tekunabe.hatenablog.jp


■ 準備

NAPALM のインストール

あらかじめ NAPALM をインストールします。

pip install napalm

napalm-ansible モジュール群のダウンロードと配置

git clone https://github.com/napalm-automation/napalm-ansible.git

napalm-automation ディレクトリ配下の library ディレクトリを、作成予定の playbook と同じディレクトリに配置します。


■ 基本情報の取得

まず、基本的な情報を取得してみます。

Playbook

以下のようなplaybook を作成します。ansible.cfg は特に作成していません。

---
- hosts: cisco   # イベントリファイルで定義済み
  gather_facts: no
  connection: local

  tasks:
    - name: get facts from device
      napalm_get_facts:
        hostname: "{{ inventory_hostname }}"
        provider: "{{ cli }}"
      register: result

    - name: debug
      debug:
        msg: "{{ result }}"

  vars:
    cli: # 認証情報
      username: "{{ ansible_user }}"          # ログインユーザー
      password: "{{ ansible_password }}"      # ログインパスワード
      dev_os: "ios"                           # デバイスのOS

なお、napalm_get_facts モジュールには filter オプションがあり、情報取得する対象を指定することができます。 デフォルトは facts となっており、ホスト名やシリアルナンバーなど基本的な情報を取得します。facts 以外については後述します。

実行結果

実行して結果を確認してみます。

[root@localhost ~]# ansible-playbook nf.yml

PLAY [cisco] **********************************************************************

TASK [get facts from device] ******************************************************
ok: [x.x.x.x]

TASK [debug] **********************************************************************
ok: [x.x.x.x] => {
    "changed": false,
    "msg": {
        "ansible_facts": {
            "facts": {
                "fqdn": "csr1.********.com",
                "hostname": "csr1",
                "interface_list": [
                    "GigabitEthernet1",
                    "GigabitEthernet2",
                    "GigabitEthernet3",
                    "GigabitEthernet4"
                ],
                "model": "CSR1000V",
                "os_version": "CSR1000V Software (X86_64_LINUX_IOSD-UNIVERSALK9-M),                                                                        SOFTWARE (fc3)",
                "serial_number": "XXXXXXXXXXX",
                "uptime": 1080,
                "vendor": "Cisco"
            }
        },
        "changed": false
    }
}

PLAY RECAP ************************************************************************
x.x.x.x             : ok=2    changed=0    unreachable=0    failed=0

基本的な情報が取得できました。


■ facts 以外の情報取得方法について

指定できる対象

前述のように filter オプションでは情報取得する対象を指定することができます。 指定できるものは Cisco IOS の場合は以下の通りです。

  • arp_table
  • bgp_neighbors
  • config
  • environment
  • facts
  • interfaces
  • interfaces_counters
  • interfaces_ip
  • lldp_neighbors
  • lldp_neighbors_detail
  • mac_address_table
  • ntp_servers
  • ntp_stats
  • optics
  • snmp_information

指定できる値は、napalm-automation 自身のドキュメントには記述されいませんが、 NAPALM本体の「Getters support matrix」のIOS(今回の場合)の列を見ると分かります。

Supported Devices — NAPALM 1 documentation

例) get_config に対応していれば filterconfig が指定でき、以下のように記述できる。

      napalm_get_facts:
        hostname: "{{ inventory_hostname }}"
        provider: "{{ cli }}"
        filter:
          - "config"

それぞれどのように取得できるのか試します。 ※あまり設定が入っていない機器で試したので情報が少ないです。

arp_table

今回の環境では特に通信していないため空となりました。

"arp_table": []

bgp_neighbors

今回の環境ではBGPは未設定のため空となりました。

"bgp_neighbors": {}

config

running-config が取得できました。

"config": {
                "candidate": "",
                "running": "Building configuration...\n\nCurrent configuration : 39iguration change at 11:25:09 UTC Sat May 27 2017\n!\nversion 16.3\n(略)
}

environment

ハードウェア状況の情報が取得できました。

"environment": {
    "cpu": {
        "0": {
            "%usage": 0.0
        }
    },
    "fans": {
        "invalid": {
            "status": true
        }
    },
    "memory": {
        "available_ram": 1765843468,
        "used_ram": 333704364
    },
    "power": {
        "invalid": {
            "capacity": -1.0,
            "output": -1.0,
            "status": true
        }
    },
    "temperature": {
        "invalid": {
            "is_alert": false,
            "is_critical": false,
            "temperature": -1.0
        }
    }
}

facts

再掲になりますが、ホスト名やシリアルナンバーなどの基本的な情報が取得できました。

"facts": {
    "fqdn": "csr1.********.com",
    "hostname": "csr1",
    "interface_list": [
        "GigabitEthernet1",
        "GigabitEthernet2",
        "GigabitEthernet3",
        "GigabitEthernet4"
    ],
    "model": "CSR1000V",
    "os_version": "CSR1000V Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), SOFTWARE (fc3)",
    "serial_number": "XXXXXXXXXXX",
    "uptime": 1200,
    "vendor": "Cisco"
}

interfaces

インターフェース情報が取得できました。

"interfaces": {
    "GigabitEthernet1": {
        "description": "N/A",
        "is_enabled": true,
        "is_up": true,
        "last_flapped": -1.0,
        "mac_address": "2C:C2:60:XX:XX:XX",
        "speed": 1000
    },
    "GigabitEthernet2": {
        "description": "N/A",
        "is_enabled": true,
        "is_up": true,
        "last_flapped": -1.0,
        "mac_address": "2C:C2:60:XX:XX:XX",
        "speed": 1000
    },
    "GigabitEthernet3": {
        "description": "N/A",
        "is_enabled": true,
        "is_up": true,
        "last_flapped": -1.0,
        "mac_address": "2C:C2:60:XX:XX:XX",
        "speed": 1000
    },
    "GigabitEthernet4": {
        "description": "N/A",
        "is_enabled": true,
        "is_up": true,
        "last_flapped": -1.0,
        "mac_address": "2C:C2:60:XX:XX:XX",
        "speed": 1000
    }
}

interfaces_counters

インターフェースのカウンタ情報が取得できました。

"interfaces_counters": {
    "GigabitEthernet1": {
        "rx_broadcast_packets": 0,
        "rx_discards": 0,
        "rx_errors": 753,
        "rx_multicast_packets": 0,
        "rx_octets": 68538,
        "rx_unicast_packets": 980,
        "tx_broadcast_packets": -1,
        "tx_discards": 0,
        "tx_errors": 0,
        "tx_multicast_packets": -1,
        "tx_octets": 199698,
        "tx_unicast_packets": 1358
    },
    (省略)
    }
}

interfaces_ip

IPアドレスが設定されているインターフェースの情報が取得できました。

"interfaces_ip": {
    "GigabitEthernet1": {
        "ipv4": {
            "10.0.0.51": {
                "prefix_length": 24
            }
        }
    }
}

lldp_neighbors

今回の環境では未設定のため空となりました。

"lldp_neighbors": {}

lldp_neighbors_detail

今回の環境では未設定のため空となりました。

"lldp_neighbors_detail": {}

mac_address_table

今回の環境では未通信のため空となりました。

"mac_address_table": []

ntp_servers

今回の環境では未設定のため空となりました。

"ntp_servers": {}

ntp_stats

今回の環境では未設定のため空となりました。

"ntp_stats": []

optics

今回の環境ではSFP等のスロットはないため空となりました。

"optics": {}

snmp_information

今回の環境では未設定のため特に意味のない値となりました。

"snmp_information": {
    "chassis_id": "%SNMP agent not enabled",
    "community": {},
    "contact": "unknown",
    "location": "unknown"
}

■ さいごに

稼働中のネットワーク機器の情報を収集したいケースは割とよくあると思いますが、自作しなくてもここまでできますので、もし、取得したい情報がこのモジュールで取得できるのであれば、利用してみるのもよいのではないでしょうか。

なお、Ansible を利用せずに、pythonスクリプト中でなるべくパース処理の手間を省きたい場合は、TextFSMというライブラリと ntc-template というテンプレート集を利用するのが良いと思います。 github.com github.com

Ansible の ios_facts モジュールでCisco IOS 機器のシステム情報やインターフェース情報を取得する

f:id:akira6592:20170527151631p:plain

■ はじめに

Ansible には 2.2 から Cisco IOS の システム情報やインターフェース情報などを収集する ios_facts というモジュールが追加されています。 どのような結果になるのか確認するために試してみます。

[公式ドキュメント]

 ios_facts - Collect facts from remote devices running Cisco IOS — Ansible Documentation

[参考情報]

 AnsibleでCisco IOSの情報収集 - ios_facts - Qiita

なお、ところどころでモジュールのコードを確認していますが、今回は Ansible 2.3 の ios_facts.py を確認しています。

ansible/ios_facts.py at stable-2.3 · ansible/ansible · GitHub

この ios_facts という標準モジュールではなく、NAPALMと連携するモジュールを利用する場合は、以下の記事をご参照ください。

tekunabe.hatenablog.jp


■ 情報収集の対象範囲の指定について(gather_subset オプション)

gather_subset というオプションで情報収集する対象を指定できます。 デフォルトでは !config となっており、コンフィグが除外がされる形となります。

ドキュメントには、ほかのどのような指定ができるか記載されていませんが、モジュールのコードを読むと以下のオプションが指定できることが分かります。

  • all
  • config
  • hardware
  • interfaces

どの指定でどの情報が取得できるかは公式ドキュメントの「Return Values」に記載されています。

ios_facts - Collect facts from remote devices running Cisco IOS — Ansible Documentation


■ 全情報取得

今回は、gather_subset: all という指定をして全情報を取得してみます。

Playbook

以下のようなplaybook を作成します。ansible.cfg は特に作成していません。

- hosts: cisco          # インベントリファイルに定義済み
  gather_facts: no
  connection: local

  tasks:
    - name: facts
      ios_facts:
        gather_subset: all
        provider: "{{ cli }}"
      register: result
    - name: debug
      debug:
        msg: "{{ result }}"

  vars:
    cli: # 認証情報。各変数はインベントリファイルに定義済み
      host:     "{{ inventory_hostname }}"    # ホスト対象ホスト
      username: "{{ ansible_user }}"          # ログインユーザー
      password: "{{ ansible_password }}"      # ログインパスワード

実行結果

実行して結果を確認してみます。

[root@sv01 ansible]# ansible-playbook cf.yml

PLAY [cisco] **********************************************************************************************************************************

TASK [facts] **********************************************************************************************************************************
ok: [x.x.x.x]

TASK [debug] **********************************************************************************************************************************
ok: [x.x.x.x] => {
    "changed": false, 
    "msg": {
        "ansible_facts": {
"ansible_net_all_ipv4_addresses": [
    "10.0.0.51"
], 
            "ansible_net_all_ipv6_addresses": [], 
            "ansible_net_config": "Building configuration...\n\nCurrent configuration : 3940 bytes\n!\n! Last configuration change at 02:03:09 UTC Sat May 27 2017\n!\nversion 16.3\nservice timestamps debug datetime msec(略)\nend", 
"ansible_net_filesystems": [
    "bootflash:"
], 
"ansible_net_gather_subset": [
    "hardware", 
    "default", 
    "interfaces", 
    "config"
], 
            "ansible_net_hostname": "csr1", 
            "ansible_net_image": "bootflash:packages.conf", 
"ansible_net_interfaces": {
    "GigabitEthernet1": {
        "bandwidth": 1000000, 
        "description": null, 
        "duplex": "Full", 
        "ipv4": {
            "address": "10.0.0.51", 
            "masklen": 24
        }, 
        "lineprotocol": "up ", 
        "macaddress": "2cc2.60xx.xxxx", 
        "mediatype": "RJ45", 
        "mtu": 1500, 
        "operstatus": "up", 
        "type": "CSR vNIC"
    }, 
                "GigabitEthernet2": {
                    "bandwidth": 1000000, 
                    "description": null, 
                    "duplex": "Full", 
                    "ipv4": null, 
                    "lineprotocol": "up ", 
                    "macaddress": "2cc2.60xx.xxxx", 
                    "mediatype": "RJ45", 
                    "mtu": 1500, 
                    "operstatus": "up", 
                    "type": "CSR vNIC"
                }, 
                "GigabitEthernet3": {
                    "bandwidth": 1000000, 
                    "description": null, 
                    "duplex": "Full", 
                    "ipv4": null, 
                    "lineprotocol": "up ", 
                    "macaddress": "2cc2.60xx.xxxx", 
                    "mediatype": "RJ45", 
                    "mtu": 1500, 
                    "operstatus": "up", 
                    "type": "CSR vNIC"
                }, 
                "GigabitEthernet4": {
                    "bandwidth": 1000000, 
                    "description": null, 
                    "duplex": "Full", 
                    "ipv4": null, 
                    "lineprotocol": "up ", 
                    "macaddress": "2cc2.60xx.xxxx", 
                    "mediatype": "RJ45", 
                    "mtu": 1500, 
                    "operstatus": "up", 
                    "type": "CSR vNIC"
                }
            }, 
            "ansible_net_memfree_mb": 1723809, 
            "ansible_net_memtotal_mb": 2047264, 
            "ansible_net_model": null, 
"ansible_net_neighbors": {
    "null": [
        {
            "host": null, 
            "port": null
        }
    ]
}, 
            "ansible_net_serialnum": "9KXI0DXXXXX", 
            "ansible_net_version": "16.3.1"
        }, 
        "changed": false
    }
}

PLAY RECAP ************************************************************************************************************************************
x.x.x.x              : ok=2    changed=0    unreachable=0    failed=0  

■ 実行されるshowコマンド

実行結果の雰囲気からおおよそ分かりますが、どのようなshowコマンドが実行されるのか、念のためコードを確認しました。以下のコマンドが仕込まれていました。

  • dir
  • show memory statistics
  • show version
  • show running-config
  • show interfaces
  • show ipv6 interface
  • show lldp
  • show lldp neighbors detail

■ さいごに

稼働中のネットワーク機器の情報を収集したいケースは割とよくあると思いますが、自作しなくてもここまでできますので、もし、取得したい情報がこのモジュールで取得できるのであれば、利用してみるのもよいのではないでしょうか。

なお、Ansible を利用せずに、pythonスクリプト中でなるべくパース処理の手間を省きたい場合は、TextFSMというライブラリと ntc-template というテンプレート集を利用するのが良いと思います。 github.com github.com

Ansible の標準lookup pluginの「dig」で名前解決する

f:id:akira6592:20170525222437p:plain

はじめに

Ansible には標準で「dig」という名前解決ができる lookup plugin があることを知りました。

Lookups — Ansible Documentation

基本的な書式は lookup('dig', 'example.com.') です。 公式ドキュメントに実行例がなかったので試してみることにします。

準備

内部で dnspython というパッケージを利用するのであらかじめインストールしておきます。

pip install dnspython

なお、dnspython をインストールしていない状態で playbook を実行すると以下のエラーが表示されます。

fatal: [localhost]: FAILED! => {"failed": true, "msg": "An unhandled exception occurred while running the lookup plugin 'dig'. Error was a <class 'ansible.errors.AnsibleError'>, original message: Can't LOOKUP(dig): module dns.resolver is not installed"}

■ Aレコードを解決

playbook

特にタイプの指定をしない場合はAレコードとなります。

---
- hosts: localhost
  gather_facts: no

  tasks:
    - debug: msg="The IPv4 address for example.com. is {{ lookup('dig', 'example.com.')}}"

実行結果

TASK [debug] *******************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "The IPv4 address for example.com. is 93.184.216.34"
}

■ TXTレコード

playbook(抜粋)

qtype でタイプを指定できます。

    - debug: msg="The TXT record for example.org. is {{ lookup('dig', 'example.org.', 'qtype=TXT') }}"

以下の書き方も同様の動作をします。

    - debug: msg="The TXT record for example.org. is {{ lookup('dig', 'example.org./TXT') }}"

実行結果(抜粋)

TASK [debug] *******************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "The TXT record for example.org. is v=spf1 -all"
}

■ MXレコードを解決

playbook(抜粋)

以下のように with_itemswantlist=True の組み合わせで、複数の値を利用したループ処理が行えます。

    - debug: msg="One of the MX records for gmail.com. is {{ item }}"
      with_items: "{{ lookup('dig', 'gmail.com./MX', wantlist=True) }}"

実行結果(抜粋)

TASK [debug] *******************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=10 alt1.gmail-smtp-in.l.google.com.) => {
    "item": "10 alt1.gmail-smtp-in.l.google.com.",
    "msg": "One of the MX records for gmail.com. is 10 alt1.gmail-smtp-in.l.google.com."
}
ok: [localhost] => (item=5 gmail-smtp-in.l.google.com.) => {
    "item": "5 gmail-smtp-in.l.google.com.",
    "msg": "One of the MX records for gmail.com. is 5 gmail-smtp-in.l.google.com."
}
ok: [localhost] => (item=40 alt4.gmail-smtp-in.l.google.com.) => {
    "item": "40 alt4.gmail-smtp-in.l.google.com.",
    "msg": "One of the MX records for gmail.com. is 40 alt4.gmail-smtp-in.l.google.com."
}
ok: [localhost] => (item=20 alt2.gmail-smtp-in.l.google.com.) => {
    "item": "20 alt2.gmail-smtp-in.l.google.com.",
    "msg": "One of the MX records for gmail.com. is 20 alt2.gmail-smtp-in.l.google.com."
}
ok: [localhost] => (item=30 alt3.gmail-smtp-in.l.google.com.) => {
    "item": "30 alt3.gmail-smtp-in.l.google.com.",
    "msg": "One of the MX records for gmail.com. is 30 alt3.gmail-smtp-in.l.google.com."
}

■ PTRレコード(逆引き)を解決

逆引きもできます。

    - debug: msg="Reverse DNS for 198.41.0.4 is {{ lookup('dig', '198.41.0.4/PTR') }}"

以下2つの書き方も同様の動作をします。

    - debug: msg="Reverse DNS for 198.41.0.4 is {{ lookup('dig', '4.0.41.198.in-addr.arpa./PTR') }}"
    - debug: msg="Reverse DNS for 198.41.0.4 is {{ lookup('dig', '4.0.41.198.in-addr.arpa.', 'qtype=PTR') }}"

実行結果(抜粋)

TASK [debug] *******************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "Reverse DNS for 198.41.0.4 is a.root-servers.net."
}

■ ネームサーバー指定でAレコードを解決

playbook(抜粋)

デフォルトではシステムで指定されているネームサーバーを利用しますが、@8.8.8.8のようにすることで個別に指定することができます。

    - debug: msg="Querying 8.8.8.8 for IPv4 address for example.com. produces {{ lookup('dig', 'example.com', '@8.8.8.8') }}"

実行結果(抜粋)

TASK [debug] *******************************************************************************************************************************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "msg": "Querying 8.8.8.8 for IPv4 address for example.com. produces 93.184.216.34"
}

 

■ その他のレコード

今回試したタイプ以外にもAAAAやMX、SRVなど様々なタイプに対応しています。 詳細は公式ドキュメントを参照してください。 Lookups — Ansible Documentation

■ さいごに

正直あまり活用方法が思い浮かびませんでしたが、何かの時に思い出していただければ幸いです。

書籍「[改訂第3版]Jenkins実践入門」を購入しました

本日5/24に、Jenkins実践入門の第3版が発売されましたので購入してきました。

gihyo.jp

まだ読んでいませんが、大きな特徴は Jenkins 2系に対応していることだと思います。
特に Jenkins 2.0 から正式に搭載された Pipeline については「第10章 Pipelineの設定」という章でで20ページ余りを割いて紹介されています。なお、Scripted Pipeline ではなく、2017年2月にGAとなったDeclarative Pipeline の解説となっています。

参考
www.kaizenprogrammer.com


少々おおざっぱではありますが、前の版(2015年6月発売)との見出しレベルの差分をとりました。
比較しやすくするため、表記ゆれは調整しています。
正確な見出しは以下のページをご参照のうえ、比較してください。


【見出し差分】

追加/変更 第2版 追加/変更 第3版
変更 Windows Server 2012にインストールする 変更 Windows Server 2016にインストールする
追加 Dockerにインストールする
変更 Jenkinsをインストールせずにクラウドで使えるDEV@cloud by CloudBees 変更 Jenkins in the Cloud
変更 JDK/Ant/Mavenを自動的にインストールする 変更 JDK/Ant/Maven/Gitを自動的にインストールする
追加 Jenkins1系から2系へのアップデート
変更 SubversionからJenkinsのビルドをトリガーする 変更 GitからJenkinsビルドをトリガーする
変更 Subversionにプロジェクトデータを登録する 変更 GitHubにプロジェクトデータを登録する
変更 trunk,branches,tagsとは 変更 git-flow/GitHub Flow
追加 Column 単体テストフレームワーク
追加 Column カバレッジ測定ツール
追加 Column 静的コード解析ツール
変更 ビルドパイプラインの設定 変更 パラメータを指定してビルドする
変更 Build Pipeline Pluginのインストールと設定 変更 パラメータビルドとは
変更 ビルドパイプラインの設定 変更 パラメータビルドの設定
変更 ビルドパイプラインの実行 変更 上流ジョブのパラメータを下流ジョブに引継ぐ
追加 ビルドの手動実行
追加 ビルドの昇格
追加 Workflow Plugin
追加 Slackに通知する
追加 ChatOps/Hubot
追加 Pipelineの設定
追加 Jenkins Pipelineとは
追加 Jenkinsを設定するときの問題点
追加 Jenkins Pipelineによるコードによる設定
追加 Jenkins Pipelineの構文
追加 Pipelineを実行する
追加 Pipelineのインストールとジョブ作成
追加 Pipelineを実行する
追加 Pipelineの高度な設定をする
追加 Snippet GeneratorによるPipelineスクリプトの自動生成
追加 Pipelineの中で定義済みのジョブを呼び出す
追加 Pipelineを利用した並列処理
追加 SCMからPipelineスクリプトを読み込む
追加 Multibranch Pipelineでビルドする
追加 Shared Libraryでコードを共有する
追加 Column Blue Ocean
変更 分散ソースコード管理システムとの連携 変更 さまざまなソースコード管理システムと連携する
追加 Subversionとの連携
追加 Team Foundation Version Controlとの連携
変更 Mercurialと連携させる 変更 おすすめプラグイン「URL SCM plugin」
追加 Gitと連携させる
追加 Column おすすめプラグイン「URL SCM Plugin」
変更 マスターとスレーブとは 変更 マスターとビルドエージェントとは
変更 マスターとスレーブの4つの設定方法 変更 マスターとビルドエージェントの設定方法
変更 スレーブサーバとしてWindowsを利用する 変更 ビルドエージェントとしてWindowsを利用する
変更 スレーブサーバとしてLinuxMac OSを利用する 変更 ビルドエージェントとしてLinuxmacOSを利用する
変更 おすすめプラグイン「Jenkins MSBuild Plugin」 変更 Column おすすめプラグインMSBuild Plugin」
追加 ビルドエージェントとしてDockerコンテナを利用する
追加 Column コンテナとは? ~Docker
追加 ビルドの実行環境を制御する
追加 Column おすすめプラグイン「TextFinder Plugin」
変更 パッケージリポジトリによるパッケージ管理 変更 パッケージリポジトリと連携する
追加 Tracと連携させる
追加 Backlogと連携させる
追加 コンテナ仮想化 ~Docker
変更 安定して利用するための5つの運用管理 変更 安定して利用するための6つの運用管理
変更 Backup Pluginでバックアップする 変更 ThinBackup Pluginでバックアップする
追加 Jenkinsのエキスパートになる ~Jenkins認定試験

 

ここ最近、Jenkins の書籍は出版されていなかったので、買うのを控えていた方はこの機会に検討されてみてはいかがでしょうか。

Ansible でネットワーク機器のコマンド結果をパースしてくれるフィルタープラグイン「ansible_helpers」を試してみた

f:id:akira6592:20170521163508p:plain

■ はじめに

Ansible はCisco IOS や、Juniper JUNOSなど様々なネットワーク機器に対応するモジュールがあります。 show系のコマンドを実行して結果を取得することもできますが、取得した結果を良い感じにパースしてくれるフィルタープラグイン 「ansible_helpers」を見つけたので試してみます。

作者はnetmikoの作者でもある Kirk Byers (@ktbyers) さんです。

▼本プラグインを知るきっかけとなったツイート

 

■ 仕組み

google/textfsm というネットワーク機器のコマンドの結果をパースする python ライブラリと、 そのテンプレート集(networktocode/ntc-templates )を組み合わせているようです。

 

■ インストー

ktbyers/ansible_helpers にインストール手順が記載されていますので、その手順を参考にインストールします。

 

■ その1: show ip int brief で試す

まず、サンプルにあるように、Cisco IOSshow ip int brief コマンドを試してみます。

playbook

以下の playbook を用意します。

---
- name: parse sample
  hosts: cisco
  gather_facts: no
  connection: local


  vars:
    cli:   # 接続情報を辞書で定義(認証情報はインベントリファイルから取得)
      host:     "{{ inventory_hostname }}"    # ホスト対象ホスト
      username: "{{ ansible_user }}"          # ログインユーザー
      password: "{{ ansible_password }}"      # ログインパスワード

    platform: cisco_ios           # 対象機器プラットフォームの定義
    command: show ip int brief    # 実行コマンドの定義


  tasks:
    - ios_command:
        provider: "{{ cli }}"
        commands: "{{ command }}"
      register: output

    - name: parse output
      debug:
        msg: "{{ output | net_textfsm_parse(platform, command) }}"  # フィルター使用箇所

実行結果

以下のようにパースしてくれました。

[root@controller ~]# ansible-playbook cat_int.yml

PLAY [parse sample] ***************************

TASK [ios_command] *************************************************************
ok: [192.168.1.254]

TASK [parse output] ************************************************************
ok: [192.168.1.254] => {
    "msg": [
        {
            "intf": "GigabitEthernet1",
            "ipaddr": "10.0.0.51",
            "proto": "up",
            "status": "up"
        },
        {
            "intf": "GigabitEthernet2",
            "ipaddr": "10.0.2.1",
            "proto": "up",
            "status": "up"
        },
        {
            "intf": "GigabitEthernet3",
            "ipaddr": "unassigned",
            "proto": "up",
            "status": "up"
        },
        {
            "intf": "GigabitEthernet4",
            "ipaddr": "10.0.4.1",
            "proto": "up",
            "status": "up"
        }
    ]
}

PLAY RECAP *********************************************************************
192.168.1.254             : ok=2    changed=0    unreachable=0    failed=0

(参考) 実際のコマンド実行結果

ネットワーク機器自身でのコマンド実行結果は以下の通りです。

csr1#show ip int brief
Interface              IP-Address      OK? Method Status                Protocol
GigabitEthernet1       10.0.0.51       YES NVRAM  up                    up
GigabitEthernet2       10.0.2.1        YES NVRAM  up                    up
GigabitEthernet3       unassigned      YES NVRAM  up                    up
GigabitEthernet4       10.0.4.1        YES NVRAM  up                    up
csr1#

 

■ その2: show ip route編 で試す

続けて、例にはない show ip route も試してみます。

playbook

以下の playbook を用意します。(command の定義以外は先ほどと同じです)

---
- name: parse sample
  hosts: cisco
  gather_facts: no
  connection: local


  vars:
    cli:   # 接続情報を辞書で定義(認証情報はインベントリファイルから取得)
      host:     "{{ inventory_hostname }}"    # ホスト対象ホスト
      username: "{{ ansible_user }}"          # ログインユーザー
      password: "{{ ansible_password }}"      # ログインパスワード

    platform: cisco_ios           # 対象機器プラットフォームの定義
    command: show ip route        # 実行コマンドの定義


  tasks:
    - ios_command:
        provider: "{{ cli }}"
        commands: "{{ command }}"
      register: output

    - name: parse output
      debug:
        msg: "{{ output | net_textfsm_parse(platform, command) }}"  # フィルター使用箇所

実行結果

以下のようにパースしてくれました。

[root@controller ~]# ansible-playbook cat_route.yml

PLAY [parse sample] ***************************

TASK [ios_command] *************************************************************

ok: [192.168.1.254]

TASK [parse output] ************************************************************
ok: [192.168.1.254] => {
    "msg": [
        {
            "distance": "",
            "mask": "/24",
            "metric": "",
            "network": "10.0.2.0",
            "nexthopif": "GigabitEthernet2",
            "nexthopip": "",
            "protocol": "C",
            "type": "",
            "uptime": ""
        },
        {
            "distance": "",
            "mask": "/32",
            "metric": "",
            "network": "10.0.2.1",
            "nexthopif": "GigabitEthernet2",
            "nexthopip": "",
            "protocol": "L",
            "type": "",
            "uptime": ""
        },
        {
            "distance": "110",
            "mask": "/24",
            "metric": "2",
            "network": "10.0.3.0",
            "nexthopif": "GigabitEthernet2",
            "nexthopip": "10.0.2.2",
            "protocol": "O",
            "type": "",
            "uptime": "00:37:41"
        },
        {
            "distance": "",
            "mask": "/24",
            "metric": "",
            "network": "10.0.4.0",
            "nexthopif": "GigabitEthernet4",
            "nexthopip": "",
            "protocol": "C",
            "type": "",
            "uptime": ""
        },
        {
            "distance": "",
            "mask": "/32",
            "metric": "",
            "network": "10.0.4.1",
            "nexthopif": "GigabitEthernet4",
            "nexthopip": "",
            "protocol": "L",
            "type": "",
            "uptime": ""
        },
        {
            "distance": "110",
            "mask": "/32",
            "metric": "3",
            "network": "10.10.10.10",
            "nexthopif": "GigabitEthernet2",
            "nexthopip": "10.0.2.2",
            "protocol": "O",
            "type": "",
            "uptime": "00:37:41"
        }
    ]
}

PLAY RECAP *********************************************************************
192.168.1.254             : ok=2    changed=0    unreachable=0    failed=0

(参考) 実際のコマンド実行結果

ネットワーク機器自身でのコマンド実行結果は以下の通りです。

csr1#sh ip route
Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP
       a - application route
       + - replicated route, % - next hop override, p - overrides from PfR

Gateway of last resort is not set

      10.0.0.0/8 is variably subnetted, 6 subnets, 2 masks
C        10.0.2.0/24 is directly connected, GigabitEthernet2
L        10.0.2.1/32 is directly connected, GigabitEthernet2
O        10.0.3.0/24 [110/2] via 10.0.2.2, 00:33:38, GigabitEthernet2
C        10.0.4.0/24 is directly connected, GigabitEthernet4
L        10.0.4.1/32 is directly connected, GigabitEthernet4
O        10.10.10.10/32 [110/3] via 10.0.2.2, 00:33:38, GigabitEthernet2

csr1#

■ まとめ

パース処理のキモとなる Pythonライブラリである TextFSM 自身は以前からもありましたが、 Python のプログラム内ではなく、Ansible と連携して使いたいという時には、 この ansible_helpers が便利なのではないでしょうか。

また、TextFSMのテンプレート集には、 Cisco IOSだけでも 30 以上あります。パース処理は自力で作るとなかなか大変ですので、ここにあるものを参考にするのも良いのではないでしょうか。