Automatisation réseau avec Ansible: module de commande

En parlant de scénarios typiques d'automatisation de réseau, on ne peut pas se passer d'un ensemble de modules de commande. Grâce à ces modules, Ansible vous permet d'exécuter des commandes sur un équipement réseau comme si vous les saisissiez directement depuis la console. Dans le même temps, la sortie des commandes ne glisse pas simplement dans la fenêtre du terminal pour sombrer dans l'oubli, mais peut être enregistrée et utilisée à l'avenir. Il peut être écrit dans des variables, analysé pour être utilisé dans les tâches suivantes ou enregistré pour l'avenir dans des variables hôtes.



Le but de cet article est de montrer que toute tâche de gestion de réseau répétitive peut être automatisée, et qu'Ansible vous permet non seulement de gérer les configurations, mais aussi de vous débarrasser de la routine et de gagner du temps.

Analysons les façons de base d'utiliser les modules de commande réseau, y compris l'enregistrement de la sortie des commandes à l'aide du paramètre de registre. Nous considérons également comment évoluer vers plusieurs périphériques réseau à l'aide de hostvars et comment organiser l'exécution conditionnelle à l'aide du paramètre wait_for et de trois autres paramètres associés: interval, retries et match.

Différentes plates-formes réseau ont leurs propres modules de commande, qui sont tous pris en charge au niveau de l'extension Red Hat Ansible Engine Networking Add-on:

Plateformes réseauModules * os_command
Arista eoseos_command
Cisco IOS / IOS-XEios_command
Cisco IOS-XRiosxr_command
Cisco NX-OSnxos_command
Juniper Junosjunos_command
Vyosvyos_command

Principes de base du module de commande


Considérez un playbook qui exécute simplement la commande show version en utilisant le module eos_command:

--- - name: COMMAND MODULE PLAYBOOK hosts: eos connection: network_cli tasks: - name: EXECUTE ARISTA EOS COMMAND eos_command: commands: show version register: output - name: PRINT OUT THE OUTPUT VARIABLE debug: var: output 

Ici, nous avons deux tâches et la première utilise le module eos_command avec un seul paramètre de commandes. Puisque nous exécutons une seule commande - show version - elle peut être spécifiée sur la même ligne que le paramètre de commandes lui-même. S'il y a deux équipes ou plus, chacune d'elles doit être placée sur une ligne distincte après les commandes:. Dans cet exemple, nous utilisons le mot clé register pour enregistrer la sortie de la commande show version. Le paramètre de registre (il peut être utilisé dans n'importe quelle tâche Ansible) définit la variable où la sortie de notre tâche sera enregistrée afin de pouvoir être utilisée plus tard. Dans notre exemple, cette variable est appelée sortie.

La deuxième tâche de notre exemple utilise le module de débogage pour afficher le contenu de la variable de sortie nouvellement créée. Autrement dit, ce sont les mêmes données que vous verriez sur l'interface de ligne de commande sur le périphérique EOS si vous y avez entré «afficher la version». La différence est que notre playbook les affichera dans la fenêtre du terminal sur laquelle vous le lancerez. Comme vous pouvez le voir, le module de débogage facilite la vérification des variables Ansible.

Voici la sortie de notre playbook:

 PLAY [eos] ************************************************************************* TASK [execute Arista eos command] ************************************************** ok: [eos] TASK [print out the output variable] *********************************************** ok: [eos] => { "output": { "changed": false, "failed": false, "stdout": [ "Arista vEOS\nHardware version: \nSerial number: \nSystem MAC address: 0800.27ec.005e\n\nSoftware image version: 4.20.1F\nArchitecture: i386\nInternal build version: 4.20.1F-6820520.4201F\nInternal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91\n\nUptime: 1 day, 3 hours and 23 minutes\nTotal memory: 2017324 kB\nFree memory: 1111848 kB" ], "stdout_lines": [ [ "Arista vEOS", "Hardware version: ", "Serial number: ", "System MAC address: 0800.27ec.005e", "", "Software image version: 4.20.1F", "Architecture: i386", "Internal build version: 4.20.1F-6820520.4201F", "Internal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91", "", "Uptime: 1 day, 3 hours and 23 minutes", "Total memory: 2017324 kB", "Free memory: 1111848 kB" ] ] } } PLAY RECAP ************************************************************************* eos : ok=2 changed=0 unreachable=0 failed=0 

Comme le montre la capture d'écran, nos deux tâches se sont déroulées avec succès. Étant donné que la première tâche utilise le niveau de détail par défaut des messages, elle indique simplement que l'hôte eos a terminé la tâche avec le résultat ok, soulignant le succès de l'exécution en vert. La deuxième tâche, avec le module de débogage, renvoie la sortie de la commande exécutée, affichant les mêmes informations sous deux formats:

  • stdout
  • stdout_lines

La section stdout montre la même chose que vous verriez dans l'interface de ligne de commande sur le périphérique, mais sous la forme d'une longue ligne. Et la section stdout_lines divise cette sortie en lignes afin qu'elle soit pratique à lire. Chaque élément de cette liste est une ligne distincte dans la sortie de la commande.

Comparez la sortie de la commande sur l'appareil et dans Ansible:

Sortie d'équipe dans Arista EOSstdout_lines dans Ansible
eos> montrer vers
Arista vEOS
Version du matériel:
Numéro de série:
Adresse MAC du système: 0800.27ec.005e

Version d'image du logiciel: 4.20.1F
Architecture: i386
Version de construction interne: 4.20.1F-6820520.4201F
ID de build interne: 790a11e8-5aaf-4be7-a11a-e61795d05b91

Disponibilité: 1 jour, 3 heures et 56 minutes
Mémoire totale: 2017324 ko
Mémoire libre: 1116624 kB
"Stdout_lines": [
[
"Arista vEOS",
"Version matérielle:",
"Numéro de série:",
"Adresse MAC système: 0800.27ec.005e",
"",
"Version d'image du logiciel: 4.20.1F",
"Architecture: i386",
"Version de build interne:
4.20.1F-6820520.4201F ",
"ID de build interne:
790a11e8-5aaf-4be7-a11a-e61795d05b91 ",
"",
"Disponibilité: 1 jour, 3 heures et 23 minutes",
"Mémoire totale: 2017324 ko",
"Mémoire libre: 1111848 ko"
]

Si vous connaissez JSON et YAML, vous avez probablement déjà fait attention à une bizarrerie: stdout_lines commence par deux crochets d'ouverture:

 "stdout_lines": [ [ 

Deux crochets ouvrants indiquent que stdout_lines renvoie en fait une liste de listes de lignes. Si vous modifiez légèrement notre tâche de débogage, cette puce peut être utilisée pour afficher sélectivement les résultats de la commande. Puisqu'il n'y a qu'une seule liste de lignes dans notre liste, cette liste est appelée zéro (en fait, c'est la première, mais le décompte part de zéro). Voyons maintenant comment en extraire une ligne distincte, par exemple, l'adresse MAC système. Dans la sortie de la commande, cette ligne est la quatrième d'affilée, mais comme nous comptons à partir de zéro, nous avons finalement besoin de la ligne 3 de la liste 0, en d'autres termes: output.stdout_lines [0] [3].

  - name: print out a single line of the output variable debug: var: output.stdout_lines[0][3]   debug-   : TASK [print out a single line of the output variable] ****************************** ok: [eos] => { "output.stdout_lines[0][3]": "System MAC address: 0800.27ec.005e" } 

À quoi sert la numérotation des listes et pourquoi est-elle même nécessaire? Le fait est qu'au sein d'une même tâche, vous pouvez exécuter plusieurs équipes, par exemple, comme ceci (nous avons ici trois équipes):

 --- - hosts: eos connection: network_cli tasks: - name: execute Arista eos command eos_command: commands: - show version - show ip int br - show int status register: output - name: print out command debug: var: output.stdout_lines 

Voici Ă  quoi ressemble la sortie:

  "output.stdout_lines": [ [ "Arista vEOS", "Hardware version: ", "Serial number: ", "System MAC address: 0800.27ec.005e", "", "Software image version: 4.20.1F", "Architecture: i386", "Internal build version: 4.20.1F-6820520.4201F", "Internal build ID: 790a11e8-5aaf-4be7-a11a-e61795d05b91", "", "Uptime: 1 day, 4 hours and 20 minutes", "Total memory: 2017324 kB", "Free memory: 1111104 kB" ], [ "Interface IP Address Status Protocol MTU", "Ethernet1 172.16.1.1/24 up up 1500", "Management1 192.168.2.10/24 up up 1500" ], [ "Port Name Status Vlan Duplex Speed Type Flags", "Et1 connected routed full unconf EbraTestPhyPort ", "Et2 connected 1 full unconf EbraTestPhyPort ", "Et3 connected 1 full unconf EbraTestPhyPort ", "Ma1 connected routed a-full a-1G 10/100/1000" ] ] 

Ici, la liste numéro zéro est la sortie de la commande show version, la liste numéro un est la sortie show ip int br, la liste numéro deux est la sortie show int status. C'est-à-dire que le numéro de liste est déterminé par l'ordre d'exécution des commandes.

Équipes Arista EOSListes de sortie correspondantes
afficher la versionoutput.stdout_lines [0]
show ip int broutput.stdout_lines [1]
afficher le statut intoutput.stdout_lines [2]

Mise à l'échelle du module de commande: variables hôtes


Et que se passe-t-il si vous exécutez le playbook sur plusieurs appareils en même temps?



Pour être unique, la variable de sortie est stockée en tant que variable hôte pour chaque hôte dans l'inventaire. Si nous avons trois commutateurs et que nous exécutons notre playbook sur eux, nous obtiendrons la variable de sortie pour chaque hôte unique. Supposons que nous ayons besoin de l'adresse IP de la commande show ip int br pour le port Ethernet1 sur switch03. Puisque show ip int br est la deuxième commande qui s'exécute dans le cadre de la tâche et que les données Ethernet1 sont contenues dans la deuxième ligne de sa sortie, nous devrons écrire stdout_lines [1] [1]. Pour accéder aux variables d'un hôte particulier, nous utilisons le mot-clé hostvars et recherchons l'hôte dont nous avons besoin par nom.

Voici comment procéder:

  - name: debug hostvar debug: var: hostvars["switch03"].output.stdout_lines[1][1] 

En conséquence, la sortie contient exactement ce dont nous avons besoin:

 TASK [debug hostvar] *************************************************************** ok: [switch03] => { "hostvars[\"switch03\"].output.stdout_lines[1][1]": "Ethernet1 172.16.1.3/24 up up 1500" } 

Par défaut, la tâche utilise les variables de l'hôte actuel, mais hostvars vous permet d'accéder directement aux variables d'un autre hôte.

Conditions dans les tâches avec modules de commande: paramètre wait_for


Le paramètre wait_for vous permet d'implémenter une vérification de condition immédiatement après l'exécution de la commande. Par exemple, pour que la tâche soit considérée comme terminée avec succès uniquement si la sortie de la commande de vérification d'état contient un certain texte. Par défaut, le paramètre wait_for n'est pas utilisé, donc la tâche ne s'exécute qu'une seule fois, comme dans les exemples ci-dessus. Mais si vous la définissez explicitement, la tâche sera redémarrée jusqu'à ce que la condition soit remplie ou que la limite de tentatives soit dépassée (il y en a 10 par défaut). Si vous activez la journalisation des commandes, vous pouvez voir que dans le playbook ci-dessous (qui est spécialement écrit pour que la condition ne soit jamais vérifiée), tout se passe exactement comme ça.

 --- - hosts: eos connection: network_cli tasks: - name: execute Arista eos command eos_command: commands: - show int status wait_for: - result[0] contains DURHAM 

Ce playbook exécutera la commande show int status 10 fois, car sa sortie ne contiendra jamais de ligne DURHAM.

Vous pouvez le vérifier en utilisant la commande show logging:

 Mar 24 20:33:52 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=17 start_time=1521923632.5 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:33:53 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=18 start_time=1521923633.71 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:33:54 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=19 start_time=1521923634.81 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:33:55 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=20 start_time=1521923635.92 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:33:56 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=21 start_time=1521923636.99 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:33:58 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=22 start_time=1521923638.07 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:33:59 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=23 start_time=1521923639.22 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:34:00 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=24 start_time=1521923640.32 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:34:01 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=25 start_time=1521923641.4 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status Mar 24 20:34:02 eos Aaa: %ACCOUNTING-6-CMD: admin vty6 192.168.2.1 stop task_id=26 start_time=1521923642.47 timezone=UTC service=shell priv-lvl=15 cmd=show interfaces status 

Voyons maintenant un exemple d'un véritable playbook dans lequel tout est configuré pour établir un voisinage OSPF (contiguïté) avec un périphérique autre que la commande ip ospf area. Nous appliquons cette commande puis utilisons le paramètre wait_for pour vérifier la présence du mot FULL dans la sortie: s'il est là, alors le voisinage a été correctement établi. Si FULL n'apparaît pas dans 10 tentatives, la tâche échouera.

 --- - hosts: eos connection: network_cli tasks: - name: turn on OSPF for interface Ethernet1 eos_config: lines: - ip ospf area 0.0.0.0 parents: interface Ethernet1 - name: execute Arista eos command eos_command: commands: - show ip ospf neigh wait_for: - result[0] contains FULL 

Exécutez ce playbook à l'aide de la commande ansible-playbook:

 âžś ansible-playbook ospf.yml PLAY [eos] ********************************************************************************************* TASK [turn on OSPF for interface Ethernet1] ******************************************************* changed: [eos] TASK [execute Arista eos command] **************************************************************** ok: [eos] PLAY RECAP ****************************************************************************************** eos : ok=2 changed=1 unreachable=0 failed=0 

Nous regardons la ligne de commande et voyons que le playbook a réussi:

 eos#show ip ospf neigh Neighbor ID VRF Pri State Dead Time Address Interface 2.2.2.2 default 1 FULL/DR 00:00:33 172.16.1.2 Ethernet1 

En plus de contient, vous pouvez utiliser les opérateurs de comparaison suivants:

  • eq: - est Ă©gal
  • neq: - pas Ă©gal
  • gt: - plus
  • ge: - supĂ©rieur ou Ă©gal Ă 
  • lt: - moins
  • le: - infĂ©rieur ou Ă©gal Ă 

De plus, avec wait_for, vous pouvez utiliser trois paramètres supplémentaires (décrits en détail dans la documentation des modules):

ParamètreLa description
intervalleTemps entre les répétitions d'une équipe.
réessaieMax le nombre de répétitions avant la fin de la tâche avec une erreur ou la condition est remplie.
correspondreLa coĂŻncidence de toutes les conditions ou au moins une.

Arrêtons-nous plus en détail sur le paramètre de correspondance:

  - name: execute Arista eos command eos_command: commands: - show ip ospf neigh match: any wait_for: - result[0] contains FULL - result[0] contains 172.16.1.2 

Lorsque match: any est spécifié, la tâche est considérée comme réussie si le résultat contient FULL ou 172.16.1.2. Si match: all est spécifié, le résultat doit contenir à la fois FULL et 172.16.1.2. Par défaut, match: all est utilisé, car si vous prescrivez plusieurs conditions, il est fort probable que vous souhaitiez qu'elles soient toutes exécutées, et pas au moins une.

Quand peut correspondre: tout est utile? Supposons que vous devez vérifier que le centre de données dispose d'une connexion bidirectionnelle à Internet. Et le centre de données est connecté à cinq fournisseurs Internet différents, chacun ayant sa propre connexion BGP. Un playbook peut vérifier l'ensemble de ces cinq connexions, et si au moins l'une d'entre elles fonctionne, et non les cinq, signalez que tout est en ordre. N'oubliez pas que tout est un OU logique et que tout est un ET logique.

ParamètreLa description
match: tout"OU" logique
Au moins une condition est requise
match: tous"Et" logique
Toutes conditions requises

Conditions négatives: construire la logique inverse


Parfois, ce n'est pas important ce qui est dans la conclusion, mais ce qui n'est pas là. Ici, bien sûr, il est toujours tentant d'utiliser l'opérateur de comparaison neq, mais pour certains scénarios avec des conditions négatives, il existe de meilleures options. Par exemple, si vous devez inverser l'instruction contains (de type "la sortie de la commande ne doit pas contenir tel ou tel"), vous pouvez utiliser le mot clé register pour enregistrer la sortie, puis la traiter dans la tâche suivante à l'aide de l' expression when . Ou, par exemple, lorsque vous devez arrêter le playbook si les conditions ne sont pas remplies, utilisez simplement les modules fail ou assert pour quitter spécifiquement avec l'erreur. Quant à l'opérateur de comparaison neq, il n'est utile que lorsque vous pouvez extraire la valeur exacte de la sortie (par exemple, à partir d'une paire clé-valeur ou de JSON), et pas seulement une chaîne ou une liste de chaînes. Sinon, une comparaison caractère par chaîne des chaînes sera effectuée.

Et ensuite


Lisez la documentation sur l'utilisation de la sortie des commandes dans les modules réseau. Il fournit des exemples utiles d'utilisation de ge, le et d'autres conditions lors de l'utilisation de la sortie au format JSON sur des plates-formes réseau spécifiques.

Source: https://habr.com/ru/post/fr438312/


All Articles