てくなべ

インフラ、ネットワーク、自動化などの技術的なことを書いていきます。

Python 3 エンジニア認定基礎試験に合格しました

f:id:akira6592:20170612212149p:plain

2017/06/09 に「Python 3 エンジニア認定基礎試験」が開始されました。

www.pythonic-exam.com

6/10に受験して合格しましたので、感想や自分の勉強方法を書きたいと思います。

試験の感想

特にクセもなく、素直な問題が多く、基礎試験という名の通りとだ感じました。 公開されている各分野の出題率を確認するとわかりますが、基礎知識にあたるがよく出題されるようになっています。

勉強方法

利用した教材

試験の認定教材でもある、オライリーの「Pythonチュートリアル 第3版」を利用しました。 www.oreilly.co.jp

Pythonの公式サイトにもPythonチュートリアルのコンテンツがあるのですが、単純に紙が好きなので書籍を購入しました。 なお、書籍が想定しているバージョンは3.5.1です。

勉強の流れ

(1) 書籍の読み込み

Pythonチュートリアル 第3版」を一通り読み込みました。

なお、Pythonチュートリアルは、プログラミングの基礎についてはあまり書かれていません。 そのため、プログラミング自体の経験がない方は、必要に応じてもっと基礎的な内容を ていねいに説明した本を併せて読むほうが勉強を進めやすいかもしれません。

(2) 動作の確認

書籍を読んで気になった箇所はJupyter Notebook(厳密にはAzure Notebooks)に コードと自分用の説明を書いて「動くあんちょこ」としてまとめました。

(3) 問題の作成

まだこの試験対策の問題が見当たらなかったので、自分で問題を作って勉強仲間のコミュニティに出題していました。 正解と不正解の選択肢を考えて作ることで、勘違いしやすい仕様や曖昧な理解の箇所も整理できた気がします。


Pythonの基礎を身に付けるける良いきっかけになったと思います。 PythonにはNAPALMのように開発が活発なネットワーク機器向けのライブラリがありますし、Pythonが読めるとAnsibleの詳細な挙動も読み取れたりするので、今後もPythonとのお付き合いはこれからも続くと思います。

ネットワーク自動化関連の気になる書籍

ネットワーク自動化関連で、気になっている書籍が2つあるのでご紹介します。 どちらも現在のところ2017年8月発売予定となっています。

Network Programmability and Automation: Skills for the Next-generation Network Engineer

https://www.amazon.co.jp/dp/1491931256/

自動化に向けてネットワークエンジニアが身に着けるべきスキルについての書籍。 オライリーのサイトで、Early Release 版がチラ見できます。 https://library.oreilly.com/book/0636920042082/network-programmability-and-automation/toc

「そもそもJSONYAMLとは何か」といった説明もあり、結構基礎的なことも書かれているようです。


Mastering Python Networking

https://www.amazon.co.jp/dp/B06XPQV549/

Python をベースとしたネットワーク自動化についての書籍で、Ansibleについても書かれているようです。

以下引用です。

What you will learn

  • Review all the fundamentals of Python and the TCP/IP suite
  • Use Python to execute commands when the device does not support the API or programmatic interaction > with the device
  • Implement automation techniques by integrating Python with Cisco, Juniper, and Arista eAPI
  • Integrate Ansible using Python to control Cisco, Juniper, and Arista networks
  • Achieve network security with Python
  • Build Flask-based web-service APIs with Python
  • Construct a Python-based migration plan from a legacy to scalable SDN-based netwo

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


■ 準備

NAPAM のインストー

あらかじめ 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

■ さいごに

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