我们希望听到您的声音!帮助我们深入了解 Ansible 生态系统的现状。
参与 2024 年 Ansible 项目调查

网络命令模块深入解析

网络命令模块深入解析

企业客户经常向 Ansible 网络团队咨询网络自动化的常见用例。在这篇博文中,我想讨论一组最常用(也是最通用)的网络模块:command 模块。命令模块允许您使用 Ansible 运行网络命令,就像网络工程师在命令行中键入命令一样。但是,使用 Ansible 时,输出不会像在终端窗口中一闪而过并永远丢失;它可以被存储并在后续任务中使用。它还可以被捕获到变量中,解析以供其他任务使用,并存储在主机变量中以供将来参考。今天我们将介绍网络 command 模块的基本用法,包括使用 register 参数保留命令输出。我们还将介绍使用 hostvars 扩展到多个网络设备以及使用 wait_for 参数和三个相关参数(intervalretriesmatch)添加条件要求。这篇博文的重点是任何可重复的网络操作任务都可以自动化。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 命令的输出。您可以在任务级别将 register 参数与任何 Ansible 任务一起使用。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]

扩展命令模块用法:主机变量

那么,如果我们同时在两个或多个网络设备上运行会发生什么?

diagram of Ansible running multiple network devices

变量 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,除了 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 命令执行 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 输出,如文档示例中所示。