Automatización de red con Ansible: módulo de comando

Hablando de escenarios típicos de automatización de red, uno no puede prescindir de un conjunto de módulos de comando. Gracias a estos módulos, Ansible le permite ejecutar comandos en equipos de red como si los estuviera ingresando directamente desde la consola. Al mismo tiempo, la salida de comandos no solo se desliza en la ventana del terminal para hundirse en el olvido, sino que se puede guardar y usar en el futuro. Puede escribirse en variables, analizarse para su uso en tareas posteriores o guardarse para futuras variables de host.



El objetivo de esta publicación es mostrar que cualquier tarea repetitiva de administración de red puede automatizarse, y que Ansible no solo le permite administrar configuraciones, sino que también ayuda a deshacerse de la rutina y ahorrar tiempo.

Analicemos las formas básicas de usar los módulos de comando de red, incluido guardar la salida de comandos usando el parámetro de registro. También consideramos cómo escalar a varios dispositivos de red usando hostvars y cómo organizar la ejecución condicional usando el parámetro wait_for y otros tres parámetros relacionados: intervalo, reintentos y coincidencia.

Las diferentes plataformas de red tienen sus propios módulos de comando, todos los cuales son compatibles con el nivel de extensión del complemento Red Hat Ansible Engine Networking:

Plataformas de redMódulos * os_command
Arista eoseos_command
Cisco IOS / IOS-XEios_command
Cisco IOS-XRiosxr_command
Cisco NX-OSnxos_command
Juniper Junosjunos_command
Vyosvyos_command

Conceptos básicos del módulo de comando


Considere un libro de jugadas que simplemente ejecute el comando show version usando el módulo 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 

Aquí tenemos dos tareas y la primera utiliza el módulo eos_command con un solo parámetro de comandos. Como solo ejecutamos un comando, show version, se puede especificar en la misma línea que el parámetro de comandos en sí. Si hay dos o más equipos, cada uno de ellos debe colocarse en una línea separada después de los comandos:. En este ejemplo, usamos la palabra clave de registro para guardar la salida del comando show version. El parámetro de registro (se puede usar en cualquier tarea de Ansible) establece la variable donde se guardará la salida de nuestra tarea para que se pueda usar más tarde. En nuestro ejemplo, esta variable se llama salida.

La segunda tarea en nuestro ejemplo utiliza el módulo de depuración para mostrar el contenido de la variable de salida recién creada. Es decir, estos son los mismos datos que vería en la interfaz de línea de comandos en el dispositivo EOS si ingresa "mostrar versión" allí. La diferencia es que nuestro libro de jugadas los mostrará en la ventana de terminal en la que lo inicias. Como puede ver, el módulo de depuración facilita la comprobación de las variables Ansible.

Aquí está el resultado de nuestro libro de jugadas:

 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 

Como se puede ver en la captura de pantalla, nuestras dos tareas funcionaron con éxito. Dado que la primera tarea usa el nivel de detalle predeterminado de los mensajes, simplemente dice que el host eos completó la tarea con el resultado correcto, destacando el éxito de la ejecución en verde. La segunda tarea, con el módulo de depuración, devuelve la salida del comando ejecutado, mostrando la misma información en dos formatos:

  • stdout
  • stdout_lines

La sección stdout muestra lo mismo que vería en la interfaz de línea de comando en el dispositivo, pero en forma de una línea larga. Y la sección stdout_lines divide esta salida en líneas para que sea conveniente leerla. Cada elemento de esta lista es una línea separada en la salida del comando.

Compare la salida del comando en el dispositivo y en Ansible:

Salida del equipo en Arista EOSstdout_lines en Ansible
eos> show vers
Arista vEOS
Versión de hardware:
Número de serie:
Dirección MAC del sistema: 0800.27ec.005e

Versión de imagen del software: 4.20.1F
Arquitectura: i386
Versión de compilación interna: 4.20.1F-6820520.4201F
ID de compilación interna: 790a11e8-5aaf-4be7-a11a-e61795d05b91

Tiempo de actividad: 1 día, 3 horas y 56 minutos.
Memoria total: 2017324 kB
Memoria libre: 1116624 kB
"Stdout_lines": [
[
"Arista vEOS",
"Versión de hardware:",
"Número de serie:",
"Dirección MAC del sistema: 0800.27ec.005e",
"",
"Versión de imagen de software: 4.20.1F",
"Arquitectura: i386",
"Versión de compilación interna:
4.20.1F-6820520.4201F ",
"ID de compilación interna:
790a11e8-5aaf-4be7-a11a-e61795d05b91 ",
"",
"Tiempo de actividad: 1 día, 3 horas y 23 minutos",
"Memoria total: 2017324 kB",
"Memoria libre: 1111848 kB"
]

Si está familiarizado con JSON y YAML, entonces probablemente ya prestó atención a una rareza: stdout_lines comienza con dos corchetes de apertura:

 "stdout_lines": [ [ 

Dos corchetes de apertura indican que stdout_lines en realidad devuelve una lista de listas de líneas. Si modifica ligeramente nuestra tarea de depuración, este chip se puede usar para ver selectivamente los resultados del comando. Como solo hay una lista de líneas en nuestra lista, esta lista se llama cero (de hecho, es la primera, pero el recuento va desde cero). Ahora veamos cómo extraer una línea separada de ella, por ejemplo, Dirección MAC del sistema. En la salida del comando, esta línea es la cuarta consecutiva, pero dado que contamos desde cero, finalmente necesitamos la línea 3 de la lista 0, en otras palabras: 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" } 

¿Cuál es el punto de la numeración de listas y por qué es necesario? El hecho es que dentro de la misma tarea puede ejecutar varios equipos, por ejemplo, de esta manera (aquí tenemos tres equipos):

 --- - 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 

Así es como se ve la salida:

  "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" ] ] 

Aquí, la lista número cero es la salida del comando show version, la lista número uno es la salida show ip int br, la lista número dos es la salida del estado show int. Es decir, el número de lista está determinado por el orden en que se ejecutan los comandos.

Equipos Arista EOSListas de salida coincidentes
mostrar versiónoutput.stdout_lines [0]
show ip int broutput.stdout_lines [1]
muestra el estado intoutput.stdout_lines [2]

Escala del módulo de comando: variables de host


¿Y qué sucede si ejecuta el libro de jugadas en varios dispositivos al mismo tiempo?



Para ser único, la variable de salida se almacena como una variable de host para cada host en el inventario. Si tenemos tres interruptores y ejecutamos nuestro libro de jugadas sobre ellos, obtendremos la variable de salida para cada host único. Supongamos que necesitamos la dirección IP del comando show ip int br para el puerto Ethernet1 en el switch03. Dado que show ip int br es el segundo comando que se ejecuta como parte de la tarea, y los datos de Ethernet1 están contenidos en la segunda línea de su salida, necesitaremos escribir stdout_lines [1] [1]. Para acceder a las variables de un host en particular, utilizamos la palabra clave hostvars y buscamos el host que necesitamos por nombre.

Aquí se explica cómo hacerlo:

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

Como resultado, la salida contiene exactamente lo que necesitamos:

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

Por defecto, la tarea utiliza las variables del host actual, pero hostvars le permite acceder directamente a las variables de otro host.

Condiciones en tareas con módulos de comando: parámetro wait_for


El parámetro wait_for le permite implementar una verificación de condición inmediatamente después de ejecutar el comando. Por ejemplo, para hacer que la tarea se considere completada con éxito solo si la salida del comando de verificación de estado contiene cierto texto. De forma predeterminada, el parámetro wait_for no se usa, por lo que la tarea se ejecuta solo una vez, como en los ejemplos anteriores. Pero si lo configura explícitamente, la tarea se reiniciará hasta que se cumpla la condición o se exceda el límite de intentos (hay 10 por defecto). Si habilita el registro de comandos, puede ver que en el libro de jugadas a continuación (que está escrito especialmente para que la condición nunca se cumpla), todo sucede así.

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

Este libro de jugadas ejecutará el comando show int status 10 veces, porque su salida nunca contendrá una línea DURHAM.

Puede verificar esto usando el comando 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 

Ahora veamos un ejemplo de un libro de jugadas real, en el que todo está configurado para establecer una vecindad OSPF (adyacencia) con un dispositivo que no sea el comando ip ospf area. Usaremos este comando y luego usaremos el parámetro wait_for para verificar la presencia de la palabra FULL en la salida: si está allí, entonces el vecindario se ha establecido con éxito. Si FULL no aparece en 10 intentos, la tarea fallará.

 --- - 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 

Ejecute este libro de jugadas usando el comando 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 

Observamos la línea de comando y vemos que el libro de jugadas fue exitoso:

 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 

Además de contiene, puede usar los siguientes operadores de comparación:

  • eq: es igual
  • neq: no es igual
  • gt: - más
  • ge: - mayor o igual que
  • lt: - menos
  • le: - menor o igual que

Además, junto con wait_for, puede usar tres parámetros adicionales (descritos en detalle en la documentación de los módulos):

ParámetroDescripción
intervaloTiempo entre repeticiones de un equipo.
reintentosMax el número de repeticiones antes de que la tarea se complete con un error o se cumpla la condición.
partidoLa coincidencia de todas las condiciones o al menos una.

Consideremos con más detalle el parámetro de coincidencia:

  - 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 

Cuando se especifica match: any, la tarea se considera exitosa si el resultado contiene FULL o 172.16.1.2. Si se especifica match: all, el resultado debe contener FULL y 172.16.1.2. De forma predeterminada, se utiliza match: all, porque si prescribe varias condiciones, lo más probable es que desee que se ejecuten todas, y no al menos una.

Cuando puede coincidir: ¿alguno es útil? Suponga que necesita verificar que el centro de datos tenga una conexión bidireccional a Internet. Y el centro de datos está conectado a cinco proveedores de Internet diferentes, cada uno de los cuales tiene su propia conexión BGP. Un libro de jugadas puede verificar todas estas cinco conexiones, y si al menos una de ellas funciona, y no las cinco, informa que todo está en orden. Solo recuerda que cualquiera es un OR lógico, y todo es un AND lógico.

ParámetroDescripción
partido: cualquiera"OR" lógico
Se requiere al menos una condición
partido: todos"Y" lógico
Todas las condiciones requeridas

Condiciones negativas: construyendo la lógica inversa


A veces es importante no lo que está en la conclusión, sino lo que no está allí. Aquí, por supuesto, siempre es tentador usar el operador de comparación neq, pero para algunos escenarios con condiciones negativas hay mejores opciones. Por ejemplo, si necesita invertir la declaración contiene (de tipo, "la salida del comando no debe contener tal y tal"), puede usar la palabra clave de registro para guardar la salida y luego procesarla en la siguiente tarea usando la expresión when . O, por ejemplo, cuando necesite detener el libro de jugadas si no se cumplen las condiciones, simplemente use los módulos de fallo o aserción para salir específicamente con el error. En cuanto al operador de comparación neq, es útil solo cuando puede extraer el valor exacto de la salida (por ejemplo, de un par clave-valor o de JSON), y no solo una cadena o una lista de cadenas. De lo contrario, se realizarán comparaciones de cadenas carácter por carácter.

Que sigue


Lea la documentación sobre cómo trabajar con la salida de comandos en módulos de red. Proporciona ejemplos útiles del uso de ge, le y otras condiciones cuando se trabaja con salida de formato JSON en plataformas de red específicas.

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


All Articles