说到网络自动化的典型场景,没有一组命令模块就无法做到。 由于有了这些模块,Ansible允许您在网络设备上运行命令,就像直接从控制台输入命令一样。 同时,命令的输出不仅会滑入终端窗口而被遗忘,而且将来可以保存和使用。 可以将其写入变量,可以对其进行解析以用于后续任务,也可以将其保存以供将来保存在宿主变量中。

这篇文章的目的是表明任何重复性的网络管理任务都可以自动化,并且Ansible不仅可以让您管理配置,而且还可以摆脱日常工作并节省时间。
让我们分析使用网络命令模块的基本方法,包括使用register参数保存命令输出。 我们还将考虑如何使用hostvars扩展到多个网络设备,以及如何使用wait_for参数和其他三个相关参数来组织条件执行:interval,retries和match。
不同的网络平台都有自己的命令模块,在Red Hat Ansible Engine Networking附件扩展级别上都
支持所有命令模块:
命令模块基础
考虑一个使用eos_command模块仅运行show version命令的剧本:
--- - 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
这里我们有两个任务,第一个使用带有单个命令参数的eos_command模块。 由于我们只运行一个命令-show version-它可以在命令参数本身的同一行上指定。 如果有两个或两个以上的团队,则必须将每个团队放在命令:之后。 在此示例中,我们使用
register关键字保存show version命令的输出。 register参数(可以在任何Ansible任务中使用)设置用于保存任务输出的变量,以便以后使用。 在我们的示例中,此变量称为输出。
本示例中的第二个任务使用
调试模块显示新创建的输出变量的内容。 也就是说,如果您在EOS设备的命令行界面上输入“ show version”,则该数据与您在EOS设备的命令行界面上看到的数据相同。 不同之处在于我们的剧本会在您启动它的终端窗口中显示它们。 如您所见,调试模块使检查Ansible变量变得容易。
这是我们的剧本的输出:
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
从屏幕截图可以看出,我们两个任务都成功完成了。 由于第一个任务使用默认的消息详细级别,因此它仅表示eos主机以ok的结果完成了任务,以绿色表示执行成功。 第二个任务,带有调试模块,返回已执行命令的输出,以两种格式显示相同的信息:
stdout部分显示的内容与您在设备的命令行界面中看到的内容相同,但以长行的形式显示。 并且stdout_lines部分将此输出分成几行,以便于阅读。 该列表中的每个项目在命令输出中都是单独的一行。
比较设备上和Ansible中命令的输出:
Arista EOS中的团队输出 | Ansible中的stdout_lines |
eos>显示版本 Arista vEOS 硬件版本: 序列号: 系统MAC地址:0800.27ec.005e
软件映像版本:4.20.1F 架构:i386 内部内部版本:4.20.1F-6820520.4201F 内部内部版本ID:790a11e8-5aaf-4be7-a11a-e61795d05b91
正常运行时间:1天3小时56分钟 总内存:2017324 kB 可用内存:1116624 kB | “ Stdout_lines”:[ [ “ Arista vEOS”, “硬件版本:”, “序列号:”, “系统MAC地址:0800.27ec.005e”, “”, “软件映像版本:4.20.1F”, “架构:i386”, “内部版本: 4.20.1F-6820520.4201F“, “内部版本号: 790a11e8-5aaf-4be7-a11a-e61795d05b91“, “”, “正常运行时间:1天3小时23分钟”, “总内存:2017324 kB”, “可用内存:1111848 kB” ] |
如果您熟悉JSON和YAML,那么您可能已经注意到了一个奇怪的地方:stdout_lines以两个大括号开头:
"stdout_lines": [ [
两个大括号表示stdout_lines实际上返回线列表的列表。 如果您稍微修改我们的调试任务,则可以使用该芯片有选择地查看命令结果。 由于列表中只有一个行列表,因此该列表称为零(实际上,它是第一个,但计数是从头开始的)。 现在让我们看看如何从中提取单独的一行,例如,系统MAC地址。 在命令的输出中,该行是第四行,但是由于我们是从头开始计算的,因此最终需要列表0中的第3行,换句话说: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" }
列表编号的重点是什么,为什么还需要? 事实是,在同一任务中,您可以运行多个团队,例如,像这样(这里我们有三个团队):
--- - 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
输出如下所示:
"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" ] ]
在此,列表编号零是show version命令的输出,列表编号一是show ip int br输出,列表编号二是show int status输出。 即,列表号由命令执行的顺序确定。
Arista EOS团队 | 匹配输出列表 |
显示版本 | output.stdout_lines [0] |
显示ip int br | output.stdout_lines [1] |
显示int状态 | output.stdout_lines [2] |
命令模块缩放:主机变量
如果同时在多个设备上运行该剧本,会发生什么情况?
为了唯一,输出变量将作为清单中每个主机
的主机变量存储。 如果我们有三个开关,并且在它们上运行我们的剧本,我们将获得每个唯一主机的输出变量。 假设我们需要show ip int br命令的IP地址作为switch03上的Ethernet1端口。 由于show ip int br是作为任务一部分运行的第二个命令,并且Ethernet1数据包含在其输出的第二行中,因此我们将需要编写stdout_lines [1] [1]。 要访问特定主机的变量,我们使用hostvars关键字并按名称搜索所需的主机。
方法如下:
- name: debug hostvar debug: var: hostvars["switch03"].output.stdout_lines[1][1]
结果,输出完全包含了我们所需要的:
TASK [debug hostvar] *************************************************************** ok: [switch03] => { "hostvars[\"switch03\"].output.stdout_lines[1][1]": "Ethernet1 172.16.1.3/24 up up 1500" }
默认情况下,该任务使用当前主机的变量,但是hostvars允许您直接访问其他主机的变量。
带有命令模块的任务中的条件:wait_for参数
使用wait_for参数可以在执行命令后立即执行条件检查。 例如,只有在状态检查命令的输出包含某些文本的情况下,才能使任务被视为成功完成。 默认情况下,不使用wait_for参数,因此任务仅运行一次,如上例所示。 但是,如果您进行了明确设置,则任务将重新启动,直到满足条件或超出尝试次数限制为止(默认为10)。 如果启用命令日志记录,则可以在下面的剧本(专门编写以使条件永远不会发生)中看到,一切都会像这样发生。
--- - hosts: eos connection: network_cli tasks: - name: execute Arista eos command eos_command: commands: - show int status wait_for: - result[0] contains DURHAM
该剧本将运行show int status命令10次,因为其输出将永远不会包含DURHAM行。
您可以使用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
现在,让我们看一个真实剧本的示例,其中所有内容都配置为使用ip ospf area命令以外的设备建立OSPF邻居(邻接)。 我们应用此命令,然后使用wait_for参数检查输出中是否存在单词FULL:如果存在,则表明邻居已成功建立。 如果在10次尝试中未出现FULL,则该任务将失败。
--- - 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
使用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
我们在命令行中查看该剧本是否成功:
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
除了包含之外,还可以使用以下比较运算符:
- eq:-等于
- neq:-不相等
- gt:-更多
- ge:-大于或等于
- lt:-更少
- le:-小于或等于
另外,您可以与wait_for一起使用三个附加参数(在模块文档中进行了详细说明):
参量 | 内容描述 |
间隔 | 团队重复之间的时间。 |
重试 | 最高 任务以错误完成或满足条件之前的重复次数。 |
比赛 | 所有条件或至少一个条件的巧合。 |
让我们更详细地讨论match参数:
- 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
当match:指定了any时,如果结果包含FULL或172.16.1.2。,则认为任务成功。 如果指定match:all,则结果必须同时包含FULL和172.16.1.2。 默认情况下,使用match:all,因为如果您规定了多个条件,则很可能希望所有条件都得到满足,但至少不满足。
什么时候可以匹配:有什么用处吗? 假设您需要验证数据中心是否具有到Internet的双向连接。 数据中心连接到五个不同的Internet提供商,每个提供商都有其自己的BGP连接。 剧本可以检查这五个连接中的所有连接,如果其中至少有一个连接正常(而不是全部五个),则报告一切正常。 只要记住,任何一个都是逻辑“或”,所有都是逻辑“与”。
参量 | 内容描述 |
匹配:任意 | 逻辑“或” 至少需要一个条件 |
匹配:全部 | 逻辑“与” 所需的所有条件 |
负条件:建立逆逻辑
有时,重要的不是结论中的内容,而是结论中的内容。 当然,这里总是使用neq比较运算符,但是对于某些条件为负的情况,有更好的选择。 例如,如果您需要反转contains语句(类型为“命令的输出中不应包含诸如此类”),则可以使用register关键字保存输出,然后使用
when表达式在下一个任务中对其进行处理。 或者,例如,如果在不满足条件的情况下需要停止剧本,只需使用
fail或
assert模块专门退出并显示错误。 对于比较运算符neq,仅当您可以从输出(例如,从键值对或JSON)中提取确切值,而不仅仅是字符串或字符串列表时,才有用。 否则,将执行字符串的逐字符比较。
接下来是什么
阅读有关使用网络模块中的命令输出的
文档 。 它提供了在特定网络平台上处理JSON格式输出时使用ge,le和其他条件的有用示例。