网络命令模块深度解析
网络命令模块深度解析
企业客户经常向 Ansible 网络团队询问网络自动化的最常见用例。在这篇博文中,我想谈谈一组最常用(也是用途最广泛)的网络模块:command
模块。命令模块允许您使用 Ansible 运行网络命令,就像网络工程师在命令行中键入它们一样。但是,使用 Ansible,输出不会仅仅在终端窗口中一闪而过并永远丢失;它可以被存储并在后续任务中使用。它还可以捕获到变量中,解析以供其他任务使用,并存储在主机变量中以供将来参考。今天,我们将介绍网络 command
模块的基本用法,包括使用 register
参数保留命令输出。我们还将介绍使用 hostvars
扩展到多个网络设备以及使用 wait_for
参数和三个相关参数(interval
、retries
和 match
)添加条件要求。这篇博文的要点是,任何可重复的网络操作任务都可以自动化。Ansible 不仅仅是配置管理,它还允许网络运营商从日常任务中解放出来,节省时间。
有各种平台的命令模块,包括网络产品支持的所有模块 supported
网络平台 | *os_command 模块 |
---|---|
Arista EOS | eos_command |
Cisco IOS / IOS-XE | ios_command |
Cisco IOS-XR | iosxr_command |
Cisco NX-OS | nxos_command |
Juniper Junos | junos_command |
VyOS | vyos_command |
基本命令模块用法
这是一个使用 eos_command 运行 show version 的简单 playbook
--- - 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 和一个名为 commands 的参数。由于我只运行一个命令,因此我可以在与 commands 相同的行上键入 show version
。如果我有多个命令,我会在 commands 参数下方单独一行列出每个命令。在本例中,我使用 register 关键字 保存 show version 命令的输出。您可以在任务级别使用任何 Ansible 任务使用 register 参数。register 参数定义一个变量来存储任务的输出,以便在后续任务中使用。在我的 playbook 中,变量名为 output。
第二个任务使用 debug 模块 打印上一个任务中变量 output 的内容。在本例中,我将看到与在 EOS 设备上直接在命令行中键入“show version”相同的输出。我的 playbook 在我运行 playbook 的终端窗口中打印此输出。Ansible debug 模块 非常适合检查变量。
以下是运行 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
您可以在上面的输出中看到这两个任务都已成功执行。第一个任务使用默认详细程度没有输出,它只是返回执行任务的主机 eos,以及 ok 和绿色以指示成功。第二个任务使用 debug 模块返回已执行命令的输出。您会看到两种不同格式的信息
- stdout
- stdout_lines
stdout 返回人类操作员在命令行上看到的所有内容,作为一个大型字符串。stdout_lines 返回一个字符串列表,使信息更易于阅读。每个项目都是从命令返回的单独一行。
以下是查看其外观的输出
Arista EOS 命令行输出
eos>show vers 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 56 minutes Total memory: 2017324 kB Free memory: 1116624 kB
Ansible stdout_lines
"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" ]
熟悉 JSON 和 YAML 的工程师和人员已经注意到另一个有趣的细节:stdout_lines 以两个左方括号开头
"stdout_lines": [ [
两个左方括号表明 stdout_lines 实际上返回了一个字符串列表的列表。如果我们稍微修改一下 debug 任务,就可以使用此功能查看输出中的选择。由于输出在列表中只有一个列表,因此整个子列表被引用为列表零,或第一个列表。让我们查看返回值中的一行。我想获取我们测试的 系统 MAC 地址。查看上面的输出,系统 MAC 地址在第四行返回,对应于第 3 行(因为计算机从 0 开始计数)。这意味着我们要获取列表 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" }
为什么我们要让第一个列表为零,以及具有多个列表的用例是什么?可以使用一个命令任务运行多个命令。这是一个包含三个命令的 playbook
--- - 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 的输出现在如下所示
"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 命令 | 相关列表 |
---|---|
show version |
output.stdout_lines[0] |
show ip int br |
output.stdout_lines[1] |
show int status |
output.stdout_lines[2] |
扩展命令模块的使用:主机变量
那么,如果我们同时在两个或多个网络设备上运行会发生什么?
变量 output 作为每个清单主机上的 主机变量 唯一保存。如果我有三个交换机并针对它们运行此 playbook,那么每个唯一主机都会有一个 output 变量。为了进行演示,我们将从上面 show ip int br
命令中获取交换机 switch03 的以太网 1 端口的 IP 地址。show ip int br
对应于我们运行的第二个命令,以太网 1 接口显示在第 2 行,因此我们知道我们需要 stdout_lines[1][1]
。要引用有关特定主机的变量,我们使用关键字 hostvars 并对我们想要的主机进行字典查找。
debug 任务将如下所示
- 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
参数时,任务仅运行一次。但是,如果指定了 wait_for
参数,则任务将一直运行,直到满足条件或达到最大重试次数(默认值为 10 次重试)。如果我打开命令日志记录,我就可以轻松地通过一个专门用于演示目的的 playbook 来查看这一点。
--- - hosts: eos connection: network_cli tasks: - name: execute Arista eos command eos_command: commands: - show int status wait_for: - result[0] contains DURHAM
此 playbook 将运行 10 次 show int status,因为它永远不会在 show int status 的输出中找到 DURHAM 这个词。
show logging
命令显示该命令确实运行了 10 次
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
在这里,我们可以查看一个实际示例。对于此 playbook,所有内容都已配置为与另一台设备建立 OSPF 邻接关系,除了 ip ospf area
命令。我们将应用该命令,然后使用 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
命令执行 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
在命令行上检查确认 playbook 已成功运行
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
除了 contains
之外,我们还可以使用
-
eq
:等于 -
neq
:不等于 -
gt
:大于 -
ge
:大于或等于 -
lt
:小于 -
le
:小于或等于
还有三个参数可以与 wait_for 结合使用。所有这些都在各个模块页面上进行了记录
参数 | 描述 |
---|---|
interval | 每次命令重试之间的时间 |
retries | 我们重试任务直到失败(或满足条件)的次数 |
match | 匹配所有条件或仅匹配其中任何一个 |
让我们快速详细说明一下 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
(这是默认值),在任务成功通过之前,两者都必须为真。如果您有多个条件,则更有可能希望所有条件都满足,而不是仅满足其中一个。
在什么场景下需要使用match: any
呢?假设您需要确认数据中心的网络连接是否正常。对于这个特定的数据中心,您有五个 ISP(互联网服务提供商)以及五个数据中心与这些 ISP 之间独立的 BGP 连接。Ansible Playbook 可以检查所有五个 BGP 连接,并在其中**任何**一个连接正常工作时继续执行,而不是全部五个连接都正常工作。请记住,**any** 表示 OR(或),而**all** 表示 AND(与)。
参数 | 描述 |
---|---|
match: any | 隐式 OR,任何条件都可以满足 |
match: all | 隐式 AND,所有条件都必须满足 |
否定条件:处理逆向逻辑
有时您需要在命令输出中查找缺失或其他否定条件。在任何否定场景中使用neq
比较很诱人,但它并不总是正确的选择。如果您希望contains
的逆向逻辑(此命令的输出不应包含此内容),请考虑使用register关键字存储输出,然后在后续任务中使用when语句。如果您希望在条件不满足时停止 playbook,请考虑简单地使用fail 模块或assert 模块,您可以在其中故意失败。上面显示的neq
只有在您可以获取确切值(如果您能获得键值对或 JSON)而不是获取字符串或字符串列表时才有意义。否则,您将进行精确的字符串比较。
更进一步
阅读网络模块中关于处理命令输出的文档此处。
像 ge、le 等更具体的条件在某些网络平台的 JSON 输出上效果非常好,如文档中的示例所示。