Documentation dynamique

1. Données structurées et rôles

Des données structurées à partir des commandes show

A partir de cette commande show interface :

rtr2#show interfaces
GigabitEthernet1 is up, line protocol is up
  Hardware is CSR vNIC, address is 0e56.1bf5.5ee2 (bia 0e56.1bf5.5ee2)
  Internet address is 172.17.16.140/16
  MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec,
     reliability 255/255, txload 1/255, rxload 1/255
  Encapsulation ARPA, loopback not set
  Keepalive set (10 sec)
  Full Duplex, 1000Mbps, link type is auto, media type is Virtual
  output flow-control is unsupported, input flow-control is unsupported
  ARP type: ARPA, ARP Timeout 04:00:00
.
.
<output omitted for brevity>

Il s’agit d’obtenir des données structurées :

TASK [DISPLAY THE PARSED DATA] **************************************************************************************************************************************************************
ok: [rtr1] => {
    "interface_facts": [
        {
            "GigabitEthernet1": {
                "config": {
                    "description": null,
                    "mtu": 1500,
                    "name": "GigabitEthernet1",
                    "type": "CSR"
                }
            }
        },
        {
            "Loopback0": {
                "config": {
.
.
<output omitted for brevity>

Fabriquer une documentation dynamique avec le “parser” de commandes

La plupart des périphériques fondés sur la “ligne de commande (CLI)” supportent la commande show. Ces données sont bien formatées pour la lisibilité des humains mais la présentation est beaucoup moins facile à lire pour nos ordinateurs qui ont besoin de données structurées. Les résultats des commandes show devraient être transformés en types de données que les machines pourraient interpréter et à travers lesquelles elles pourraient naviguer.

Ansible network-engine est un rôle qui supporte deux “traducteurs” : command_parser et textfsm_parser. Ce sont des modules qui sont contruits dans le rôle network-engine pour transformer une entrée en texte brut (bien formaté) en donnée structurée.

Une introduction rapide aux rôles Ansible

Pour rappel on peut démarrer une automation avec Ansible à l’aide de deux fichiers :

  • un inventaire
  • un livre de jeu

Roles

  • Les rôles simplifient les livres de jeu.
  • Pensons que les rôles sont des fonctions pour des tâches répétitives.
  • Les rôles peuvent être partagés et distribués, ils sont semblables à des librairies.
  • Les rôles se conçoivent avec une nomenclature de dossiers et de fichiers définis par “convention”.

Voici la structure de deux rôles “common” et “ospf”. Chacun est organisé par “convention” en dossiers et fichiers convenus.

site.yml
roles/
   common/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/
   ospf/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/

Le fichier site.yml (on choisit ce que l’on veut comme nom de fichier pour un livre de jeu) est un livre de jeu qui joue les deux rôles.

# site.yml
---
- hosts: routers
  roles:
     - common
     - ospf

Ansible Galaxy

Ansible Galaxy est un service qui concentre les rôles Ansible à usage de découverte, de ré-utilisation et de partage.

Vous pouvez démarrer votre gestion Ansible à partir de rôles révisés et maintenus par la communauté Ansible, comme proposé ici avec le rôle ansible-network.network-engine.

---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

  roles:
    - ansible-network.network-engine

Le rôle network-engine peut être installé à partir d’Ansible Galaxy

Voyez le site http://galaxy.ansible.com.

Utiliser des “parsers” pour générer des rapports personnalisés

Sur la plupart des périphériques du réseau, la commande show convient pour la lecture des yeux mais certainement pas pour les données structurées.

Le rôle Ansible network-engine fournit deux outils de traitement :

  • TextFSM
  • Command Parser
---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

  roles:
    - ansible-network.network-engine

  tasks:
    - name: CAPTURE SHOW INTERFACES
      ios_command:
        commands:
          - show interfaces
      register: output

    - name: PARSE THE RAW OUTPUT
      command_parser:
        file: "parsers/show_interfaces.yml"
        content: "{{ output.stdout[0] }}"

Lab Fabriquer une documentation dynamique avec un parser de commande

La sortie non structurée des commandes sur les routeurs et commutateurs

Voici à quoi ressemble la sortie d’une commande show interfaces sur un périphérique Cisco IOS :

rtr2#show interfaces
GigabitEthernet1 is up, line protocol is up
  Hardware is CSR vNIC, address is 0e56.1bf5.5ee2 (bia 0e56.1bf5.5ee2)
  Internet address is 172.17.16.140/16
  MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec,
     reliability 255/255, txload 1/255, rxload 1/255
  Encapsulation ARPA, loopback not set
  Keepalive set (10 sec)
  Full Duplex, 1000Mbps, link type is auto, media type is Virtual
  output flow-control is unsupported, input flow-control is unsupported
  ARP type: ARPA, ARP Timeout 04:00:00
  Last input 00:00:00, output 00:00:00, output hang never
  Last clearing of "show interface" counters never
  Input queue: 0/375/0/0 (size/max/drops/flushes); Total output drops: 0
  Queueing strategy: fifo
  Output queue: 0/40 (size/max)
  5 minute input rate 1000 bits/sec, 1 packets/sec
  5 minute output rate 1000 bits/sec, 1 packets/sec
     208488 packets input, 22368304 bytes, 0 no buffer
     Received 0 broadcasts (0 IP multicasts)
     0 runts, 0 giants, 0 throttles
     0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored
     0 watchdog, 0 multicast, 0 pause input
     250975 packets output, 40333671 bytes, 0 underruns
     0 output errors, 0 collisions, 1 interface resets
     0 unknown protocol drops
     0 babbles, 0 late collision, 0 deferred
     0 lost carrier, 0 no carrier, 0 pause output
     0 output buffer failures, 0 output buffers swapped out
Loopback0 is up, line protocol is up
  Hardware is Loopback
  Internet address is 192.168.2.102/24
  MTU 1514 bytes, BW 8000000 Kbit/sec, DLY 5000 usec,
     reliability 255/255, txload 1/255, rxload 1/255
  Encapsulation LOOPBACK, loopback not set
  Keepalive set (10 sec)

.
.
<output omitted for brevity>

Imaginons que votre objectif est de préparer une liste de toutes les interfaces qui sont up, leur paramètre MTU et leur description. Imaginez que vous disposiez de 10 périphériques : il jouable de se connecter sur chacun de manière manuelle, à l’ancienne à moins que de passer par Bash, Python et SSH. Mais combien d’heure passerez-vous pour vous connecter à 150 périphériques ?

Dans ce lab, nous allons apprendre à automatiser ce scénario avec Ansible.

Étape 1

Veuillez commencer par créer un livre de jeu nommé interface_report.yml et d’y ajouter la définition du jeux suivants.

---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

Étape 2

Ensuite, il s’agira d’ajouter le rôle ansible-network.network-engine. Un rôle n’est rien d’autre qu’un niveau d’abstraction plus élevé du livre de jeu. Pensez qu’un rôle Ansible est un livre de jeu abstrait qui comprend des tâches répititives et spécifiques, à ré-utiliser. Mais avant tout, il est nécessaire d’installer localement le rôle.

ansible-galaxy install ansible-network.network-engine

Ce rôle Ansible ansible-network.network-engine rend le module command_parser disponible dans la suite du livre de jeu. Il s’agit de l’invoquer dans le livre de jeu avec la directive roles: dans un élément d’un liste.

---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

  roles:
    - ansible-network.network-engine

Étape 3

On peut alors ajouter une première tâche qui exécutera la commande show interfaces sur tous les routeurs et qui enregistrera la sortie dans une variable nommée “output”.

---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

  roles:
    - ansible-network.network-engine

  tasks:
    - name: CAPTURE SHOW INTERFACES
      ios_command:
        commands:
          - show interfaces
      register: output

N’hésitez pas à exécuter le livre de jeu en mode verbeux pour voir le résultat.

Étape 4

La prochaine tâche consiste à envoyer les données brutes venant de la tâche précédente auprès du module command_parser.

Note: Le fichier du parser, en argument du module, est un fichier YAML semblable à la structure d’un livre de jeu Ansible.

---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

  roles:
    - ansible-network.network-engine

  tasks:
    - name: CAPTURE SHOW INTERFACES
      ios_command:
        commands:
          - show interfaces
      register: output

    - name: PARSE THE RAW OUTPUT
      command_parser:
        file: "parsers/show_interfaces.yaml"
        content: "{{ output.stdout[0] }}"

Le module command_parser référence un fichier show_interfaces.yaml situé dans le dossier parsers. On peut l’obtenir ici. Voici la procédure pour le récupérer.

mkdir parsers
curl https://raw.githubusercontent.com/goffinet/networking-workshop/master/parsers/show_interfaces.yaml -o parsers/show_interfaces.yaml

Notez que ce fichier utilise des expressions rationnelles pour capturer les données intéressantes et les retourne dans une variable nommée interface_facts.

Étape 5

Veuillez ajouter une nouvelle tâche afin de visualiser ce qui retourné par le module command_parser.

---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

  roles:
    - ansible-network.network-engine

  tasks:
    - name: CAPTURE SHOW INTERFACES
      ios_command:
        commands:
          - show interfaces
      register: output

    - name: PARSE THE RAW OUTPUT
      command_parser:
        file: "parsers/show_interfaces.yaml"
        content: "{{ output.stdout[0] }}"

    - name: DISPLAY THE PARSED DATA
      debug:
        var: interface_facts

Étape 6

Veuillez exécuter le livre de jeu mais seulement sur un seul routeur comme rtr1 pour limiter la taille de la sortie.

ansible-playbook interface_report.yml --limit rtr1
PLAY [GENERATE INTERFACE REPORT] ************************************************************************************************************************************************************

TASK [CAPTURE SHOW INTERFACES] **************************************************************************************************************************************************************
ok: [rtr1]

TASK [PARSE THE RAW OUTPUT] *****************************************************************************************************************************************************************
ok: [rtr1]

TASK [DISPLAY THE PARSED DATA] **************************************************************************************************************************************************************
ok: [rtr1] => {
    "interface_facts": [
        {
            "GigabitEthernet1": {
                "config": {
                    "description": null,
                    "mtu": 1500,
                    "name": "GigabitEthernet1",
                    "type": "CSR"
                }
            }
        },
        {
            "Loopback0": {
                "config": {
                    "description": null,
                    "mtu": 1514,
                    "name": "Loopback0",
                    "type": "Loopback"
                }
            }
        },
        {
            "Loopback1": {
                "config": {
                    "description": null,
                    "mtu": 1514,
                    "name": "Loopback1",
                    "type": "Loopback"
                }
            }
        },
        {
            "Tunnel0": {
                "config": {
                    "description": null,
                    "mtu": 9976,
                    "name": "Tunnel0",
                    "type": "Tunnel"
                }
            }
        },
        {
            "Tunnel1": {
                "config": {
                    "description": null,
                    "mtu": 9976,
                    "name": "Tunnel1",
                    "type": "Tunnel"
                }
            }
        },
        {
            "VirtualPortGroup0": {
                "config": {
                    "description": null,
                    "mtu": 1500,
                    "name": "VirtualPortGroup0",
                    "type": "Virtual"
                }
            }
        }
    ]
}

PLAY RECAP **********************************************************************************************************************************************************************************
rtr1                       : ok=3    changed=0    unreachable=0    failed=0

Formidable ! Le livre de jeu transforme le texte brut de la commande show en données structurées : un liste de dictionnaires où chacun décrit les éléments nécessaires à l’élaboration d’un rapport.

Étape 7

Il s’agit de créer un dossier qui hébergera le rapport des périphériques.

mkdir intf_reports

Étape 8

Dans cette étape, on propose d’utiliser le module Ansible template pour générer le rapport avec les données collectées dans les tâches précédentes. Il s’agit de la même technique que celle utilisée dans le lab précédent qui consiste à créer un rapport pour chaque périphérique et un rapport consolidé avec le module Ansible assemble

---
- name: GENERATE INTERFACE REPORT
  hosts: cisco
  gather_facts: no
  connection: network_cli

  roles:
    - ansible-network.network-engine

  tasks:
    - name: CAPTURE SHOW INTERFACES
      ios_command:
        commands:
          - show interfaces
      register: output

    - name: PARSE THE RAW OUTPUT
      command_parser:
        file: "parsers/show_interfaces.yaml"
        content: "{{ output.stdout[0] }}"

    #- name: DISPLAY THE PARSED DATA
    #  debug:
    #    var: interface_facts

    - name: GENERATE REPORT FRAGMENTS
      template:
        src: interface_facts.j2
        dest: intf_reports/{{inventory_hostname}}_intf_report.md

    - name: GENERATE A CONSOLIDATED REPORT
      assemble:
        src: intf_reports/
        dest: interfaces_report.md
      delegate_to: localhost
      run_once: yes

Note: la tâche “DISPLAY THE PARSED DATA” a été commentée.

Voici ce modèle templates/interface_facts.j2 à créer dans le dossier templates/ :

{{ inventory_hostname.upper() }}
{{ '-' *  inventory_hostname|length }}
{% for intf in interface_facts %}
{% for intf_key,intf_value in intf.items() %}
{{intf_key}}:
{% for k,v in intf_value.items() %}
  Description: {{v.description}}
  Name: {{v.name}}
  MTU: {{v.mtu}}

{% endfor %}
{% endfor %}
{% endfor %}

Étape 9

Veuillez lancer ce livre de jeu :

ansible-playbook interface_report.yml
PLAY [GENERATE INTERFACE REPORT] ************************************************************************************************************************************************************

TASK [CAPTURE SHOW INTERFACES] **************************************************************************************************************************************************************
ok: [rtr1]
ok: [rtr3]
ok: [rtr4]
ok: [rtr2]

TASK [PARSE THE RAW OUTPUT] *****************************************************************************************************************************************************************
ok: [rtr3]
ok: [rtr2]
ok: [rtr1]
ok: [rtr4]

TASK [GENERATE REPORT FRAGMENTS] ************************************************************************************************************************************************************
changed: [rtr4]
changed: [rtr2]
changed: [rtr3]
changed: [rtr1]

TASK [GENERATE A CONSOLIDATED REPORT] *******************************************************************************************************************************************************
changed: [rtr3]
ok: [rtr1]
ok: [rtr4]
ok: [rtr2]

PLAY RECAP **********************************************************************************************************************************************************************************
rtr1                       : ok=4    changed=1    unreachable=0    failed=0
rtr2                       : ok=4    changed=1    unreachable=0    failed=0
rtr3                       : ok=4    changed=2    unreachable=0    failed=0
rtr4                       : ok=4    changed=1    unreachable=0    failed=0

Étape 10

La commande cat nous offre le résultat attendu.

cat interfaces_report.md
RTR1
----
GigabitEthernet1:
  Description:
  Name: GigabitEthernet1
  MTU: 1500

Loopback0:
  Description:
  Name: Loopback0
  MTU: 1514

Loopback1:
  Description:
  Name: Loopback1
  MTU: 1514

Tunnel0:
  Description:
  Name: Tunnel0
  MTU: 9976

Tunnel1:
  Description:
  Name: Tunnel1
  MTU: 9976

VirtualPortGroup0:
  Description:
  Name: VirtualPortGroup0
  MTU: 1500

RTR2
----
GigabitEthernet1:
  Description:
  Name: GigabitEthernet1
  MTU: 1500

Loopback0:
  Description:
  Name: Loopback0
  MTU: 1514

Loopback1:
  Description:
  Name: Loopback1
  MTU: 1514
.
.
<output omitted for brevity>

Licence

License MIT github.com/network-automation/linklight