てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] Playbook 実行結果ログを JSON で出力して jq で抽出する

これは Ansible Advent Calendar 2020 (Qiita版) の 21日目の記事です。

■ はじめに

Ansible には callback plugin という仕組みがあり、結果出力の形式を変更できます。

JSON Callback Plugin もあり、Playbook の実行結果を JSON で得て、jq などと組み合わせて必要な箇所だけ切り取って表示できます。

この記事では、簡単なサンプルでご紹介します。

  • 動作確認環境
    • ansible 2.9.14

前提とする Playbook

以下の Playbook で試すこととします。Cisco IOS のネットワーク機器に show ip route コマンドを実行するだけです。

---
- hosts: ios1
  gather_facts: false

  tasks:
    - name: show ip route
      ios_command:
        commands:
          - show ip route

まずは普通に

比較用に普通実行、つまりデフォルトの callback plugin で実行した場合は以下のようになります。

$  ansible-playbook -i ../inventory.ini iproute.yml

PLAY [ios1] ***********************************************************************************************************************

TASK [show ip route] **********************************************************************************************************************
ok: [ios1]

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

デバッグ表示用のタスクはないので、ただ ok と表示されるだけです。

json callback plugin の場合

今度は ANSIBLE_STDOUT_CALLBACK=json を指定して、実行します。stdoutstdout_lines に実行結果が含まれていることが分かります。

全出力

まず、jq で絞らず、そのまま出力する方法です。

$ ANSIBLE_STDOUT_CALLBACK=json ansible-playbook -i ../inventory.ini iproute.yml
{
    "custom_stats": {},
    "global_custom_stats": {},
    "plays": [
        {
            "play": {
                "duration": {
                    "end": "2020-12-20T12:16:22.379916Z",
                    "start": "2020-12-20T12:16:04.563589Z"
                },
                "id": "acde4800-1122-37cb-6431-00000000002c",
                "name": "ios1"
            },
            "tasks": [
                {
                    "hosts": {
                        "ios1": {
                            "_ansible_no_log": false,
                            "action": "ios_command",
                            "changed": false,
                            "invocation": {
                                "module_args": {
                                    "auth_pass": null,
                                    "authorize": null,
                                    "commands": [
                                        "show ip route"
                                    ],
                                    "host": null,
                                    "interval": 1,
                                    "match": "all",
                                    "password": null,
                                    "port": null,
                                    "provider": null,
                                    "retries": 10,
                                    "ssh_keyfile": null,
                                    "timeout": null,
                                    "username": null,
                                    "wait_for": null
                                }
                            },
                            "stdout": [
                                "Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP\n       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area \n       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2\n       E1 - OSPF external type 1, E2 - OSPF external type 2\n       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2\n       ia - IS-IS inter area, * - candidate default, U - per-user static route\n       o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP\n       a - application route\n       + - replicated route, % - next hop override, p - overrides from PfR\n\nGateway of last resort is 10.10.20.254 to network 0.0.0.0\n\nS*    0.0.0.0/0 [1/0] via 10.10.20.254, GigabitEthernet1\n      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks\nC        10.10.20.0/24 is directly connected, GigabitEthernet1\nL        10.10.20.48/32 is directly connected, GigabitEthernet1"
                            ],
                            "stdout_lines": [
                                [
                                    "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 10.10.20.254 to network 0.0.0.0",
                                    "",
                                    "S*    0.0.0.0/0 [1/0] via 10.10.20.254, GigabitEthernet1",
                                    "      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks",
                                    "C        10.10.20.0/24 is directly connected, GigabitEthernet1",
                                    "L        10.10.20.48/32 is directly connected, GigabitEthernet1"
                                ]
                            ]
                        }
                    },
                    "task": {
                        "duration": {
                            "end": "2020-12-20T12:16:22.379916Z",
                            "start": "2020-12-20T12:16:04.580088Z"
                        },
                        "id": "acde4800-1122-37cb-6431-00000000002e",
                        "name": "show ip route"
                    }
                }
            ]
        }
    ],
    "stats": {
        "ios1": {
            "changed": 0,
            "failures": 0,
            "ignored": 0,
            "ok": 1,
            "rescued": 0,
            "skipped": 0,
            "unreachable": 0
        }
    }
}

jq で絞る

今度は jq と組み合わせて結果の一部のみを表示する方法です。 ここでは、1つめのタスクのホスト ios1 の実行結果の stdout_lines[0] をターゲットにします。

 % ANSIBLE_STDOUT_CALLBACK=json ansible-playbook -i ../inventory.ini iproute.yml | jq ".plays[0].tasks[0].hosts.ios1.stdout_lines[0]" 
[
  "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 10.10.20.254 to network 0.0.0.0",
  "",
  "S*    0.0.0.0/0 [1/0] via 10.10.20.254, GigabitEthernet1",
  "      10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks",
  "C        10.10.20.0/24 is directly connected, GigabitEthernet1",
  "L        10.10.20.48/32 is directly connected, GigabitEthernet1"
]

抽出されました。

callback plugin の指定方法

今回は、環境変数で callback plugin を指定しましたが、ansible.cfg でも指定できます。

詳細は、公式ドキュメントをご参照ください。

docs.ansible.com

おわりに

Playbook の実行結果を抽出したり、プログラム的に処理させたいときは json callback plugin は便利だと思います。

参考

rheb.hatenablog.com

tekunabe.hatenablog.jp