5. 执行#
这个 Playbook 已经完美运行了几个月:实验室里的十台接入交换机,端到端两分钟,每次结果干净。当团队决定将其推广到全部 800 台交换机的完整清单时,没有人预料到会出问题。前 600 台设备顺利更新。然后任务变慢了,接着停滞了。RADIUS 服务器突然收到 150 个并发 SSH 认证请求,开始拒绝连接。Ansible 在 150 台设备的执行中途挂住。一名工程师强制终止了任务。
接下来的问题才是更难解决的:没有人知道哪 600 台设备已经更新,哪 150 台还没有。没有记录任何执行状态。他们重新运行 Playbook,寄希望于幂等性能够救他们,这次确实救了。但这个事件揭示了一件重要的事:执行层是为实验室设计的,而不是为网络设计的。速度、并行性、错误边界、状态追踪,这些都没有被考虑进去。自动化本身是有效的;但执行架构不是。
大多数人认为网络自动化就是:“获取相同的数据,不以人类身份连接 CLI,在网络上做些事情。” 我敢打赌大多数开始网络自动化的人都是从这里起步的。是的,这就是执行模块所做的事。但正如你在本书中所看到的,网络自动化比这第一步要宏大得多,执行只是架构中的一个组件。
执行模块直接与网络交互以进行最危险的操作(例如配置变更或重启)。所以不要跳过本章;把它做对至关重要。
在本章中,我们将介绍这个模块提供的目标和支柱,以及实现它们所需的内部能力。
5.1. 基础概念#
5.1.1. 背景#
执行定义了如何执行操作。做什么来自意图模块,何时来自编排。
在早期的网络自动化项目中,一个为给定设备和接口名弹跳接口的脚本可以是第一个胜利。这段旅程可以成长为更复杂的系统。
5.1.2. 目标#
执行系统实际上需要做什么?五件事至关重要:
获取正确的数据。 在执行任何操作之前,你需要知道执行什么(意图)、在哪里执行(哪些设备)以及如何访问它们(凭证、连接详情)。这意味着从真实数据源(更多内容见第 4 章)拉取清单和获取预期状态,有时还需要查询可观测性数据以了解当前状况。没有这种集成,你的执行引擎只是在盲目地运行命令。为了你的心理健康,避免这种情况。
在需要时启动,无论现在还是之后。 有时你需要立即执行:工程师点击"部署"并期望立即发生。其他时候,执行应该等待,在维护窗口期间部署,等待设备上线,或响应来自可观测性的事件。系统需要同时支持同步和异步触发。
随时间追踪状态。 全网操作不是即时的。你可能在数小时内向数百台设备部署。哪些设备成功了?哪些失败了?哪些还在等待?状态管理还支持幂等性(运行相同任务两次,得到相同结果)、回滚(撤销刚才做的事)和恢复(在失败后从中断处继续)。没有状态追踪,每次执行都是一次孤注一掷的赌博。
在规模下可靠执行。 适用于 5 台设备的脚本在 500 台时往往会崩溃(或降级)。你需要并行执行、错误处理、重试、速率限制和预演能力。系统应该优雅地处理部分失败,提供清晰的错误反馈,绝不让网络处于未定义状态。不同操作需要不同策略。有些快速并行,有些缓慢串行。
与任何网络设备或平台协作。 你的网络可能有 Cisco、Arista、Juniper、云 Application Programming Interface (API)、Linux 主机、防火墙、负载均衡器。每种设备说不同的协议,有不同的操作模式。你的执行层需要为所有这些提供适配器,提供统一接口,使上层自动化不必关心差异。
有了这些目标,你实际需要哪些架构能力?
5.1.3. 支柱#
每个目标转化为具体能力:
数据集成层。 你的执行引擎不是孤立存在的。它需要对真实数据源(清单、凭证、意图)、可观测性系统(当前状态、健康指标)以及可能的其他系统(工单、变更管理、审批工作流)的程序化访问。这意味着实现 Application Programming Interface (API) 客户端、安全处理认证、适当缓存数据,并在开始执行之前验证你拥有所需的一切。
灵活的触发机制。 多种启动执行的方式很重要。同步触发包括 Representational State Transfer (REST) Application Programming Interface (API)(直接调用)、Webhooks(外部系统集成)和远程过程调用。异步触发包括事件监听器(响应可观测性告警、设备状态变更或外部事件)、调度器(类 cron 的定期执行或一次性计划任务)和消息队列消费者。基本链式调用也很有用:一个执行完成后触发另一个。
状态管理基础设施。 这超出了"设备是否有此配置"的范畴。你需要执行状态(哪些任务正在运行、等待、完成、失败)、预期状态(应该配置什么)和实际状态(已配置什么)。这需要持久化存储、原子操作的事务支持、防止并发冲突的锁定机制,以及清晰的状态模型。基础设施必须处理状态在多个执行工作者之间共享的分布式场景。
健壮的执行引擎。 这是系统的核心。它支持命令式工作流(运行命令 1,然后命令 2,然后命令 3)和声明式方法(让设备看起来像这样,自己想清楚步骤)。引擎处理并发(并行或批量对多台设备执行)、实现指数退避的重试逻辑、提供预演能力(预测会发生什么而不实际执行)、捕获详细执行日志,并优雅地处理部分失败。错误处理至关重要:当某些失败时,应该中止一切、继续处理其他设备,还是重试?
协议抽象层。 网络设备是一团异构的混乱。有些说 Secure Shell (SSH) 并期望 Command Line Interface (CLI) 命令。其他使用 NETCONF 或 RESTCONF。现代设备支持 gRPC Network Management Interface (gNMI) 用于流式遥测和配置。云平台暴露 Representational State Transfer (REST) API。你的执行系统需要为所有这些提供适配器,向上层逻辑提供统一接口。这一层处理连接池、会话管理、认证、命令格式化和响应解析。好的抽象意味着你可以添加对新设备类型的支持,而无需重写整个自动化。
5.1.4. 职责范围#
执行模块位于规划和行动之间。它从意图(应该配置什么)和编排(何时及如何做)接收指令,然后直接与网络设备交互以使变更发生。
职责范围内:
- 通过任何支持的协议连接网络设备
- 执行配置变更、操作命令、文件传输、重启
- 管理执行状态和追踪进度
- 处理错误、重试和回滚
- 向编排提供反馈
职责范围外:
- 决定配置什么(那是意图的职责)
- 编排复杂的多步骤工作流并决定何时触发(那是编排的职责)
- 执行结果的长期存储和分析(那是可观测性的职责)
将执行想象成汽车的引擎:它提供动力和运动,但不决定去哪里或何时转弯。这些决定来自司机(编排),参照地图(意图)。
5.2. 功能详述#
五个关键功能区协同工作,安全可靠地修改网络状态:
- 数据集成:从上游系统获取清单、凭证、意图和可观测性数据
- 触发:通过同步或异步机制启动执行
- 状态管理:追踪执行进度,支持幂等性和回滚
- 引擎:以适当并发和错误处理执行任务的核心逻辑
- 网络适配器:与多样化网络设备通信的协议专属接口
这些组件形成一个管道:数据集成提供输入,触发启动流程,引擎使用网络适配器执行任务,状态管理全程追踪一切。
graph LR
subgraph 目标
G1[获取正确数据]
G2[在需要时启动]
G3[随时间追踪状态]
G4[在规模下可靠执行]
G5[与任何网络设备协作]
end
subgraph 支柱
P1[数据集成层]
P2[灵活触发机制]
P3[状态管理基础设施]
P4[健壮执行引擎]
P5[协议抽象层]
end
subgraph 功能
F1[数据集成]
F2[触发]
F3[状态管理]
F4[引擎]
F5[网络适配器]
end
G1 --> P1 --> F1
G2 --> P2 --> F2
G3 --> P3 --> F3
G4 --> P4 --> F4
G5 --> P5 --> F5
内部架构如下:
graph TD
A[数据集成] --> C[引擎]
B[触发] --> C[引擎]
C --> E[状态管理]
E --> C
C --> D[网络适配器]
classDef component fill:#e1f5ff,stroke:#4a90e2,stroke-width:2px;
class A,B,C,D,E component;
5.2.1. 数据集成#
在执行任何操作之前,你需要数据。执行引擎从多个来源拉取信息,以了解做什么和在哪里做。
5.2.1.1. 清单#
清单数据定义目标设备。我们在第 4 章中介绍了这个主题,但至少需要:
- 目标:到达设备的 IP 地址或 FQDN
- 平台/操作系统:设备类型、厂商、操作系统版本(决定使用哪种协议和命令)
- 凭证:用户名/密码、Secure Shell (SSH) 密钥、Application Programming Interface (API) 令牌、证书路径
- 连接参数:Secure Shell (SSH) 端口、超时值、Application Programming Interface (API) 端点
- 元数据:站点位置、角色(Spine/Leaf/Edge)、环境(生产/暂存)
清单数据应来自真实数据源。仅使用文件只在非常小的环境中效果良好。你的执行引擎在运行时查询真实数据源 Application Programming Interface (API)(或通过编排触发时接收数据)。有些系统为提高性能缓存清单,有可配置的刷新间隔,或利用事件驱动的清单将触发器与关联信息结合起来。
永远不要在清单文件或日志中存储凭证。使用密钥管理系统(HashiCorp Vault、AWS Secrets Manager、CyberArk),在执行时注入凭证。真实数据源应提供指向敏感数据的指针,并在运行时获取它。你的执行引擎应支持多种凭证来源和每设备凭证覆盖。
5.2.1.2. 预期数据#
预期数据是你想要配置或变更的内容。这来自你的意图/真实数据源模块,可能包括:
- 配置制品:可直接部署的制品,可以是直接与 API 一起使用的结构化数据,也可以是构建配置的一组 CLI 命令。
- 命令:用于设备操作的特定 Command Line Interface (CLI) 命令或 Application Programming Interface (API) 调用。
- 文件:升级用的软件镜像、导入用的配置文件。
执行引擎应该自己获取意图数据,还是应该由编排传入?两种方式都可以。直接获取意图将执行与真实数据源耦合,但确保数据始终是新鲜的。将意图作为参数接收使执行更通用,但需要编排来处理数据获取。
我的建议是直接消费配置制品。意图模块应负责用自己的逻辑生成配置制品,使用表示状态的结构化数据(VLAN 配置、路由策略、ACL)渲染配置模板。
配置制品可能在生成时刻和被执行器消费时刻之间变得过时。如果在预生成制品被缓存后 SoT 数据发生变化,执行器可能会应用过时的配置。过时窗口是从上次 SoT 提交到执行触发之间的时间。对于在触发时获取制品(而非从缓存中获取)的自动化,这个窗口很小。对于按计划预先渲染制品并存储供以后使用的管道,窗口可能增长到数小时。在数据新鲜度很重要的场景下,编排器或执行触发器应始终从 SoT 而非缓存文件拉取新鲜制品。
5.2.1.3. 观测数据#
可观测性验证通常属于编排的职责,编排可以决定是否继续。在编排尚不存在的简单情况下,执行可以承担这个角色。
以下是可观测性数据在执行流程附近使用的一些用例:
- 执行前验证:设备是否可达?是否有足够的磁盘空间用于升级?是否有会被中断的活跃会话?
- 优雅降级:在重启交换机之前,查询可观测性查看是否有冗余连接。如果没有,延迟或中止。
- 条件逻辑:只有在满足特定条件时才应用配置(CPU 低于阈值、无活跃告警、在时间窗口内)
- 消耗容量:在执行消耗操作之前,确保可用容量足以吸收变更而不破坏 SLO。
这需要与你的可观测性系统集成。执行引擎可能查询 Application Programming Interface (API) 获取当前指标、检查设备状态或等待就绪信号。编排也可以进行这种集成,然后根据新鲜度和风险门控执行。
一个常见模式:Ansible 的"网络健康检查"模块或 Nornir 的数据收集任务等工具在执行前后运行可观测性查询,比较结果以检测意外变化。“前后快照"方法能发现那些否则不会被注意到的回归。
5.2.2. 触发#
执行不会自己启动。需要某些东西来触发它。现代执行系统支持多种触发机制:
同步(立即、阻塞):
- Representational State Transfer (REST) Application Programming Interface (API) 调用:外部系统或用户调用 Application Programming Interface (API) 端点,执行运行,响应包含结果。简单直接。
- Webhooks:外部系统(Term "git" not found、工单、CI/CD)在事件发生时发送 HTTP 请求。常见模式:Git 推送触发配置部署。
- Command Line Interface (CLI) 命令:工程师运行直接调用执行的命令(
ansible-playbook、terraform apply、自定义脚本)。这些 CLI 命令是这些工具提供的轻量级展示层的一部分(更多内容见第 8 章)。
异步(延迟或事件驱动):
- 事件监听器:响应来自可观测性的事件(设备宕机、阈值超出)、消息队列或外部系统。Ansible 事件驱动自动化(EDA)明确构建了这种模式:监听事件、匹配规则、自动触发 Playbook。
- 消息队列:任务提交到队列(RabbitMQ、Kafka、AWS SQS),工作者拉取并执行它们。支持缓冲、优先级队列和速率限制。
正确的方法取决于你的用例。立即变更(修复生产问题)需要同步触发。响应式自动化(响应告警)使用事件监听器。规模考量也很重要,我们将在第 11 章中介绍。
触发通常来自编排,但如果编排不存在,其他模块可以直接触发执行。例如,直接从展示层作为人工暴露的任务触发,或从真实数据源模块触发。
一个重要的考量:什么算作执行?对我来说,执行不只是简单任务,它可以包括不需要复杂工作流(那是编排的工作)的多个链式任务。边界变得模糊了(再次如此)。例如,这个序列仍然可以是一个执行流:升级固件、等待设备重启、验证操作、更新清单。但如果它需要人工验证或基于更广泛验证的条件分支,它就属于编排模块。
5.2.3. 状态管理#
状态管理是关于追踪你在执行中的位置和世界看起来是什么样子,以便你能做出明智的决策。有两个主要类别:
执行状态(追踪自动化本身):
- 哪些设备已经被处理?
- 哪些任务成功、失败或等待中?
- 如果任务以瞬态错误失败,我们可以重试吗?
- 如果执行被中断,我们能从中断处恢复吗?
实际基础设施状态(追踪目标配置状态):
- 当前配置了什么?
两种方法存在:
无状态/无代理(如 Ansible)。 每次执行独立运行。运行之间没有持久状态。每次执行从头开始:收集当前状态、计算差异、应用变更。操作更简单(没有状态数据库),但效率较低(每次重新发现一切),也没有内置回滚。
有状态(如 Terraform)。 系统维护一个持久状态文件,追踪上次部署的内容。每次运行时,将预期状态(你的配置)与记录状态(上次部署的内容)和实际状态(设备上的内容)进行比较。这支持精确的变更规划、高效执行(只变更不同的内容)和回滚(恢复到以前的状态)。但现在你有一个需要保护、锁定并在多个操作者之间同步的状态文件。
事务性执行是当你需要比"尽力而为"更强的安全保证时的下一步。事务将多个设备变更分组为一个单元:要么一切都被应用和验证,要么系统回滚到以前的状态。这需要三件事:
- 明确的边界(哪些操作在事务内)
- 变更前状态的持久记录(用于回滚)
- 锁定或租约机制,防止并发变更破坏原子性
在实践中,大多数网络自动化使用"类事务"行为而非严格的 ACID 语义,因为不是所有设备都支持原生提交/回滚。尽管如此,你可以通过以下方式近似事务:获取快照、使用候选配置(如果支持)、强制排他锁,以及将回滚路径作为执行流程的头等公民。
挑战是什么?状态同步和共享。如果多人或多系统修改网络设备,状态会变得过时。Terraform 通过远程状态存储和锁定来解决这个问题。Ansible 通过无状态来避免这个问题,但牺牲了效率。中间地带:临时缓存当前状态,每次操作前验证,构建"最终一致性"模式,其中短暂的不匹配是可接受的。
5.2.3.1. 幂等性#
幂等性意味着多次运行相同的自动化产生相同的结果。应用一次 VLAN 配置:VLAN 被创建。再次应用:什么都不变(VLAN 已经存在)。这对可靠性至关重要:如果执行在中途失败,你可以安全地重新运行它,而不会创建重复或破坏东西。
工具如何实现幂等性:
内置模块(如 Ansible):大多数 Ansible 模块在设计上是幂等的。
ios_vlan模块在创建 VLAN 之前检查它是否存在。如果它已经以正确配置存在,Ansible 报告"ok”(无变更)。这需要模块作者实现检查逻辑。状态比较(如 Terraform):Terraform 比较预期状态和当前状态,计算差异,只应用差异。如果你在配置没有变更的情况下两次运行
terraform apply,第二次运行什么都不做。声明式 API(如 NETCONF/YANG):有些协议原生处理幂等性。NETCONF 的
<edit-config>和merge操作本质上是幂等的:它将你的配置与现有配置合并,按需创建或更新。手动检查(如原始脚本):如果你在编写 Python 或 Go 脚本,你自己实现幂等性:查询当前状态,与预期状态比较,只在有差异时做变更。
幂等性比看起来要难。如果 VLAN 存在但设置错误,应该更新它(可能中断流量)还是报告冲突?瞬态失败怎么办(设备暂时不可达)?重试逻辑必须区分"操作已完成"(幂等成功)和"操作失败"(真实错误)。
完美的幂等性增加了开销。每个操作都需要先查询当前状态。对于大规模部署,这会减慢速度。有些团队接受"基本幂等"(99% 的时间有效)而非"完美幂等"(始终有效,但运行慢)。
幂等性是声明式方法的要求。必须有人承担提供幂等性和为其他所有人隐藏复杂性的负担。
5.2.4. 引擎#
执行引擎是核心逻辑,它接受一个任务(“在这 50 台交换机上配置这个 VLAN”)并安全高效地执行它。这不是编排。编排模块跨时间和依赖关系协调多个执行任务。引擎只是将一个任务做好(或简单的任务链)。
它做什么:
- 接受任务定义(做什么,哪些设备)
- 将其分解为原子操作(每设备操作)
- 以适当的并发执行操作(串行、并行、批量)
- 处理错误、重试和回滚
- 报告进度和结果
执行引擎可能并行配置 100 台路由器,但它不决定何时配置它们、根据业务逻辑应用哪些配置,或基于结果接下来做什么。那些是编排的关切。执行是拉车的马;编排是工头。
也就是说,执行引擎通常支持简单链式调用:“在相同设备上先做任务 A,然后任务 B。“这是基本的顺序排列,不是完整的编排。当你需要复杂工作流(等待外部批准、根据结果分支、跨多个系统协调)时,你需要真正的编排层(更多内容见第 7 章)。
5.2.4.1. 语言#
如何定义执行逻辑?不同的语言风格有不同的权衡:
| 风格 | 示例 | 优势 | 权衡 |
|---|---|---|---|
| 领域专用语言(DSL) | Ansible(YAML)、Terraform(HCL) | 入门门槛低、意图自文档化、内置执行语义 | 灵活性有限、复杂场景调试困难、条件逻辑可能变得笨拙 |
| 通用编程语言 | Nornir(Python)、自定义 Python/Go 脚本 | 完全灵活、强调调试工具、易于复用库 | 技能要求更高、需要维护更多代码、跨团队标准化程度低 |
许多团队使用 DSL(Ansible)处理常见模式,对于复杂边缘情况则用自定义代码(Python 模块、插件)。这在可访问性和强大功能之间取得平衡。人的因素通常决定哪种方法胜出,更多内容见第 13 章。
5.2.4.2. 命令式与声明式#
命令式:你逐步指定如何做某事。
声明式:你指定最终状态应该是什么,工具自己想办法。
简而言之,在适合的场景优先使用声明式。
看到差异的好方法是看 Ansible,它两种都支持:
命令式 Ansible:
- name: Create VLAN 100 cisco.ios.ios_command: commands: - vlan 100 - name Engineering你在字面上告诉 Ansible 要运行哪些命令。如果 VLAN 存在,这仍然会运行(尽管根据模块可能是幂等的)。
声明式 Ansible:
- name: Ensure VLAN 100 exists cisco.ios.ios_vlans: config: - vlan_id: 100 name: Engineering state: merged你描述预期状态。Ansible 想清楚要运行哪些命令。如果 VLAN 100 已经以正确名称存在,Ansible 什么都不做。
| 方法 | 优势 | 权衡 |
|---|---|---|
| 声明式 | 本质上幂等;意图更易读;由于工具处理边缘情况,操作者错误更少 | 高度依赖模块/提供者质量;对执行路径控制较少;当内部抽象时故障排查可能更难 |
| 命令式 | 完全控制每个步骤;执行流程明确;在几乎任何有 CLI 访问的环境中都能工作 | 更多代码和测试工作;幂等性由你负责;随着边缘情况积累,长期维护迅速增长 |
大多数团队在可能时使用声明式,必要时使用命令式。请记住:声明式意味着别人承担了这个负担,即使从外部看起来简单。
5.2.4.3. 串行与并行#
运行任务有两个选择:
串行执行:一次处理一台设备。配置设备 1,等待完成,配置设备 2,以此类推。安全(你立即看到失败并可以停止),但慢(100 台设备 = 单台设备时间的 100 倍)。
并行执行:同时处理多台设备。一次配置设备 1-10,然后 11-20,以此类推。
flowchart TD
subgraph 串行
S1[设备 1] --> S2[设备 2] --> S3[设备 3]
end
subgraph 并行
P0[开始] --> P1[设备 1]
P0 --> P2[设备 2]
P0 --> P3[设备 3]
end
Nornir 存在的原因:Ansible 最初是串行的。对于规模化网络自动化,这非常慢。Ansible 添加了
strategy: free和后来的forks来支持并行性,但它仍然从根本上为顺序执行设计。Nornir 从一开始就以并行性为核心构建:它默认使用线程对多台设备并发执行。这使其对大型设备数量快 10-100 倍。
并行执行的考量:
- 控制平面影响:同时访问 1000 台设备可能使管理网络、数据源和认证系统或设备控制平面超载。
- 依赖处理:如果设备相互依赖(Spine 先于 Leaf),你需要排序。纯并行无法工作。
- 错误爆炸半径:如果你的自动化有 bug,并行执行在你注意到之前就将其部署到许多设备。串行执行在设备 1 上失败,你在损坏设备 2-100 之前停下来。
我的建议:使用带批量的并行执行。以 10-50 台设备为一组处理(我曾称之为"波次”),在继续之前验证每批的成功。这平衡了速度与安全。更多部署策略内容见第 10 章。
5.2.4.4. 预演(计划模式)#
预演是执行引擎的"计划"阶段:模拟变更而不触碰设备,然后呈现具体的差异和风险。好的预演拉取当前状态,计算将运行的确切设备级操作,并验证前提条件(可达性、schema、依赖排序)。它应该快速、确定性和可重现,以便审查者可以信任它。
示例(Terraform 为 AWS VPC 计划可审查的变更):
Terraform will perform the following actions:
# aws_vpc.main will be created
+ resource "aws_vpc" "main" {
+ cidr_block = "10.10.0.0/16"
+ enable_dns_support = true
+ enable_dns_hostnames = true
+ tags = {
+ "Name" = "core-vpc"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.这是审查者签字确认的内容:将会变更的确切资源和属性,而不触碰云。
在实践中,预演是使审批有意义、回滚不太可能的原因。它给人类提供了将要发生什么的清晰视图,也给引擎一个机会提前拒绝不安全或不一致的变更。如果平台支持候选配置或提交检查,引擎应该使用它们。否则,预演是计算出的差异加上预检查,不是保证。
根据我的经验,在自动化项目的早期阶段提供预演能力非常方便,可以获得网络工程师的支持。之后,其重要性会降低。
5.2.4.5. 弹性#
网络执行本质上是不可靠的(就像大多数分布式系统一样):设备重启、连接出现故障,控制平面被压垮。其他依赖关系,如意图模块,也可能有临时问题。弹性执行优雅地处理这些:
重试逻辑:
- 瞬态失败(连接超时、临时 CPU 峰值)应触发带指数退避的重试
- 区分可重试错误(超时)和永久错误(认证失败、语法错误)
- 限制重试次数以避免无限循环
超时策略:
- 连接超时:等待设备响应多长时间才放弃
- 任务超时:完整操作可以运行多长时间(防止在卡住的设备上挂起)
- 全局超时:整个作业的最大执行时间
错误处理:
- 快速失败:一台设备失败,中止一切(安全但低效)
- 继续:记录失败,继续处理其他设备(高效但可能扩散损害)
- 基于阈值:如果 > 10% 的设备失败,停止(均衡)
回滚能力:
- 变更前获取配置快照
- 如果执行失败,自动恢复快照
- 支持预演模式:显示会变更什么而不实际变更
熔断器:
- 如果一台设备持续失败,标记为不健康并临时跳过
- 防止浪费时间反复尝试连接宕机的设备
检查点:
- 定期保存进度
- 如果执行崩溃,从最后一个检查点恢复而不是从头开始
构建这一切很难。大多数团队从基本的重试逻辑开始,在生产中遇到失败时再添加复杂性。
一个有用的模式:将"安全模式"视为头等执行状态。渲染预期配置、解析当前配置、收集事实,然后只有在验证门控通过后才应用(merged/replaced/overridden)。这在触碰生产之前给你确定性的检查点。
5.2.5. 网络适配器#
网络设备说不同的协议。网络适配器层抽象了这些差异,使上层不需要关心他们是在通过 Secure Shell (SSH) 与 Cisco 交换机还是通过 Representational State Transfer (REST) Application Programming Interface (API) 与 Arista 交换机通信。
5.2.5.1. 接口#
不同设备支持不同的管理接口:
| 接口 | 描述 | 示例库/工具 | 典型用途 |
|---|---|---|---|
| Secure Shell (SSH) / Command Line Interface (CLI) | 最通用,最不结构化(文本输入/文本输出) | Netmiko(Python)、Paramiko(Python)、scrapli(Python)、scrapligo(Go) | 遗留平台、厂商专属操作命令 |
| NETCONF / RESTCONF | 结构化模型驱动管理(基于 YANG) | ncclient(Python)、scrapli-netconf(Python)、nemith/netconf(Go) | 声明式配置和标准化数据模型 |
| gRPC Network Management Interface (gNMI) / gNOI | 基于 gRPC 的配置/状态接口和操作 RPC | pygnmi(Python)、gnmic(Go CLI)、openconfig/gnmi(Go) | 流式遥测和现代操作工作流 |
| Representational State Transfer (REST) API | HTTP/JSON 或 XML API,通常特定于平台 | requests/httpx(Python)、net/http + OpenAPI 生成的客户端(Go) | 控制器和云/网络平台 API |
| JSON-RPC / 厂商 gRPC | 特定网络操作系统使用的结构化 RPC 模式 | Arista eAPI(JSON-RPC)、厂商 gRPC SDK | 带结构化载荷的快速远程过程执行 |
有些库提供跨平台的公共接口抽象层,例如:
- NAPALM(带多厂商支持的网络自动化和可编程性抽象层):提供跨厂商统一 API 的 Python 库。
- 支持 Cisco、Arista、Juniper 等
- 底层使用适当协议(Secure Shell (SSH)、Application Programming Interface (API)、NETCONF)
- 用例:需要一致自动化代码的多厂商环境
- Pybatfish:不是设备传输库,而是执行安全性的有力伴侣。一个实用模式是"用 NAPALM 或 Ansible 计划/应用,在变更前后用 pybatfish 验证网络行为”。
为什么使用抽象层?与其为 Cisco 和 Juniper 编写不同的代码,NAPALM 给你一个 API。调用 get_facts(),NAPALM 想清楚如何获取设备事实,无论是 Cisco IOS 设备(通过 Secure Shell (SSH))、Arista EOS(通过 eAPI)还是 Juniper Junos(通过 NETCONF)。权衡是:抽象隐藏了厂商特定功能。对于常见操作(获取配置、推送配置、获取事实),这很好。对于更高级的操作或奇特的厂商功能,你会退回到原生接口,因为它可能没有被实现。
5.2.5.2. 操作#
网络执行不只是配置管理:
| 操作类型 | 覆盖内容 | 说明 |
|---|---|---|
| 配置变更 | 推送完整配置/片段/声明式状态;merge/replace/delete 模式 | 最常见的操作类型,工具支持最好 |
| 零接触配置(Zero Touch Provisioning (ZTP)) | 首次启动时的自动化接入 | 需要 DHCP + 引导服务器(TFTP/HTTP/HTTPS)和设备支持 |
| 文件传输 | 上传镜像、下载日志/备份 | 常见协议:SCP、SFTP、TFTP、HTTP |
| 设备操作 | 重启/重载、操作命令(ping/traceroute)、备份 | 常用于第 2 天运营和修复 |
| 回滚 | 将配置恢复到以前的已知良好状态 | 某些平台原生回滚;其他平台恢复备份 |
每种操作类型都有其自己的错误模式,需要特定处理。软件升级需要预检查(磁盘空间是否足够?)、后检查(设备是否正确启动?)和回滚计划(如果升级失败,重新加载旧镜像)。结果应暴露给编排进行决策,同时执行仍然可以处理本地护栏和重试。
验证和渲染的归属: 真实数据源拥有意图和(可选)预渲染配置。执行拥有设备级安全检查和操作验证(可达性、前后检查)。编排决定何时验证以及对结果做什么(批准、暂停、回滚或升级)。如果你想要一个简单规则:改变工作流的验证属于编排;改变设备操作的验证属于执行。
5.2.6. 解决方案#
网络执行有许多工具,比较它们有助于理解权衡:
| 工具 | 执行模型和优势 | 最适合 | 主要局限性 |
|---|---|---|---|
| Ansible | DSL(YAML),无代理,大型模块生态系统,对混合服务器/网络自动化很强 | 需要快速上手和广泛平台支持的团队 | 大规模下需要调优;复杂逻辑在 YAML 中可能难以维护 |
| Terraform | 声明式 IaC(HCL),强差异/计划引擎,有状态工作流,出色的云集成 | 已经在基础设施上标准化 Terraform 的团队 | 网络提供者成熟度不一;状态管理增加运营开销;第 2 天运营较弱 |
| Salt | 基于 Python 的代理或无代理选项,事件驱动架构,强规模特性 | 现有 Salt 环境或事件密集型操作 | 较小的网络自动化社区,入门门槛较高 |
| Nornir | Python 优先框架,线程并行性,高度灵活,调试更容易且快速 | 有 Python 能力且有自定义或性能敏感需求的团队 | 现成组件较少;需要更多工程所有权 |
| 自定义 Python/Go | 对域特定逻辑的最大控制和设计自由 | 边缘情况、内部平台和高度专业化工作流 | 你拥有一切:标准、可靠性模式、测试和生命周期支持 |
| 厂商控制器 | 带厂商原生工作流的意图和策略控制器(示例:Cisco Catalyst Center、Aruba Central、Juniper Mist) | 围绕一个厂商生态系统标准化且有强大控制器 API 的团队 | 多厂商环境中可移植性较低 |
许多团队根据用例和网络基础设施使用多种工具。
5.2.7. 零接触配置#
零接触配置(ZTP)是一种独特的执行模式,设备在第一次启动时自动获取并应用完整配置,无需任何人工干预。数据流与第 2 天运营有结构性差异,值得作为一种命名的架构模式来处理。
ZTP 流程
flowchart LR
A[设备启动\n无配置] --> B[DHCP 分配\n管理 IP\n和引导 URL]
B --> C[设备从服务器\n获取引导配置]
C --> D[设备认证到\nSoT 和管理网络]
D --> E[编排器检测到\n新设备,触发\n完整配置作业]
E --> F[执行器从 SoT\n应用完整意图]
核心洞见是引导步骤故意最小化:刚好足以让设备接入管理网络并能够认证。之后完整配置作为标准执行器作业运行,从真实数据源拉取完整意图。这意味着 ZTP 复用与第 2 天运营相同的执行管道,而不需要单独的配置系统。
常见 ZTP 模式
| 模式 | 工作原理 | 权衡 |
|---|---|---|
| DHCP + 静态文件 | DHCP 指向每设备的静态配置文件(按 MAC 或序列号匹配) | 实现简单;不从 SoT 拉取;规模增大时会崩溃 |
| DHCP + 动态生成 | 引导服务器在请求时查询 SoT 并生成设备专属初始配置 | 从第一天起由 SoT 驱动;要求设备引导之前 SoT 中已有该设备记录 |
| 操作系统镜像 + 配置 | 设备在启动时同时下载操作系统镜像和配置(需要操作系统配置的设备所需) | 处理裸机或出厂重置设备;增加引导服务器复杂性 |
ZTP 引入了一个排序依赖:设备必须在物理启动之前在真实数据源中注册,否则引导服务器没有数据来生成配置。在园区场景中,这通过 ServiceNow 到 Nautobot 的同步来处理:当交换机作为资产添加到 ServiceNow 中(在到达现场之前),Nautobot 自动创建带有位置、角色和厂商的设备记录。当交换机启动时,SoT 已经准备好了。
5.2.8. 混合与云执行#
执行器越来越需要同时在两种根本不同的环境中运作:传统网络设备和云原生平台。这些环境有不同的 API 范式、认证模型和故障模式。将它们视为相同会导致脆弱的自动化;将它们视为完全独立的系统则会重复工作并造成不一致的运营。
分歧问题
| 维度 | 网络设备 | 云平台 |
|---|---|---|
| API 风格 | SSH、NETCONF、gNMI(有状态,长连接) | REST/HTTP(无状态,短连接) |
| 认证模型 | 共享凭证(用户名/密码、SSH 密钥) | 临时令牌、服务账号、实例角色(AWS SigV4、Azure AD、GCP 服务账号) |
| 操作完成 | 通常同步(命令在会话结束前成功或失败) | 通常异步(API 返回"已接受",需要轮询最终状态) |
| 失败模糊性 | 丢失的 NETCONF 会话是明确的失败 | 云 API 超时可能应用也可能未应用变更;你必须查询才能知道 |
| 幂等性方法 | 默认命令式;声明式需要仔细的模块设计 | 大多数平台原生声明式(Terraform、CloudFormation、Pulumi) |
推荐架构
保持意图(SoT)和编排统一。在园区交换机上创建 VLAN 的同一业务请求也可能需要在 AWS 中创建安全组。真实数据源同时持有两者。编排器协调两者。只有在网络适配器层(5.2.5),执行路径才会分叉:一个适配器通过 NETCONF/Ansible 处理园区交换机;另一个通过 Terraform 或云提供商 API 处理云环境。
这意味着云 API 变更和网络设备变更的爆炸半径受相同的审批和审计跟踪约束,这是正确的架构结果,尽管协议完全不同。
密钥管理器策略必须同时适应长效网络设备凭证和短效云令牌。对于云平台,凭证应在作业开始时生成,永远不存储。大多数云提供商为此提供了机制(AWS 实例配置文件、Azure 托管身份、GCP 工作负载身份)。网络设备凭证保留在 Vault 中,在运行时注入。执行器无论目标类型如何,都不应在作业运行之间持有凭证。
5.2.9. 安全考量#
执行器是你架构中对网络具有写入权限的组件。它可以同时向数百台设备推送配置、重启服务、升级固件并修改路由策略。这使其安全态势比几乎任何其他组件都更具影响性,然而从架构角度来看,它却往往是最被忽视的。受损或配置错误的执行器不是数据泄露风险;它是网络中断风险。
凭证管理
网络设备的凭证永远不应出现在 Playbook、模板、作业定义或版本控制中。模式很简单:将密钥存储在专用的密钥管理器(HashiCorp Vault、AWS Secrets Manager、CyberArk)中,在运行时注入到执行器的环境中。执行器在作业开始时获取凭证;它不会永久持有它们。
这也意味着凭证轮换在运营上变得安全。当设备密码按照应有的定期计划轮换时,执行器在下次运行时获取新凭证,无需任何部署或重配置。
每操作最小权限
并非每次执行都需要相同级别的访问。读操作(收集事实、预演检查、部署前验证)应使用只读凭证。写操作应使用范围限定于变更类型的凭证:部署 VLAN 变更的作业不应持有允许 BGP 配置变更的凭证。在网络平台支持基于角色访问的地方(Arista 角色、Cisco 特权级别、NETCONF 访问控制),使用它来限制任何单个受损会话的爆炸半径。
在园区示例中,这意味着 Arista 交换机上的 VLAN 部署作业使用允许 VLAN 和接口配置的角色,但不能触及路由协议配置或管理平面设置。
关注点分离
谁可以触发执行和谁可以修改自动化逻辑应该是具有不同访问控制的不同角色。批准和启动 VLAN 部署作业的操作者不需要,也不应该有权访问修改实现它的 Ansible 角色或 Playbook 模板。在 AWX 和 AAP 中,这通过角色分配来强制:「作业启动者」可以启动预批准的作业模板;「项目编辑者」可以更改底层自动化逻辑。
当自动化用于高影响操作时,这种分离最为重要。启动跨 800 台交换机固件升级的人不应该同时是编写升级 Playbook 的人,或者至少第二位审查者应该在 Playbook 可用于生产之前批准了它。
审计跟踪要求
每个执行事件必须记录足够的细节以重建发生了什么:谁触发了作业、使用了哪个作业模板、哪些设备是目标、传递了哪些参数、每台设备的结果是什么,以及在什么时间戳。日志必须在适用于组织的合规期间保留,对受监管行业的变更管理通常为 12 到 24 个月。
在大多数企业环境中,这不是可选的。变更管理流程需要可追溯的记录,将变更工单与特定执行事件与特定设备变更集连接起来。从一开始就将这个审计跟踪设计到执行器中,而不是事后添加。
自动化基础设施的网络分段
执行器及其控制平面(AWX、Ansible 控制节点)应位于管理网络段。到设备的执行流量应尽可能通过带外管理网络流动,而不是通过执行器可能正在修改的同一数据平面。触发作业的入站访问应需要认证,不应从不受信任的网络访问。
要避免的一个实际故障模式:可以从生产用户 VLAN 访问的执行器意味着该 VLAN 中任何受损的主机都可能触发网络变更。管理平面访问属于管理平面网络。
5.3. 实施示例#
5.3.1. 用例:跨异构园区的自动化 VLAN 部署#
我们继续第 4 章的园区网络。VLAN 服务定义(其 ID、子网、每厂商配置模板和目标交换机组)现已存储在真实数据源中。本章重点介绍执行模块如何获取该意图并可靠地跨全部 800 台交换机部署它。
场景:业务团队为新应用请求一个新 VLAN。请求通过包含详情的工单系统提来:VLAN ID、名称、园区站点和审批。网络运营在 Arista、HPE 和 Cisco 接入/汇聚交换机上部署这个 VLAN,验证连接,并报告成功。
需求:
- 并行向多台交换机部署 VLAN 配置
- 验证前提条件(VLAN 不已存在,交换机可达)
- 执行预演显示会变更什么
- 在失败时带回滚能力执行实际部署
- 验证部署后状态(VLAN 是活跃的,无错误)
5.3.2. 解决方案架构#
在深入执行细节之前,先放大整个架构:
- 真实数据源:NetBox(存储清单、VLAN 定义)
- 编排/触发:AWX 工作流模板协调(API 启动、Webhook 启动、计划启动)
- 可观测性:提供实时数据用于决策
- 执行:
- 执行引擎:Ansible 作业模板/任务(预检查、预演、部署、验证、回滚)
- 数据集成:Ansible/AWX 中的 NetBox 清单插件
- 网络适配器:Cisco IOS XE、Arista EOS 和 HPE/Aruba 的 Ansible 模块
- 状态管理:每阶段的 AWX 作业和工作流状态 + 每主机结果,失败路径上有回滚任务
此解决方案仅供说明目的;不是通用建议。
5.3.3. 实施流程#
这作为带多个任务节点的 AWX 工作流运行:
- NetBox 中的 VLAN 意图变更通过 Webhook 触发 AWX
- AWX 工作流启动并运行清单同步(NetBox)
- 预检查作业验证可达性、现有 VLAN 状态和站点护栏
- 预演作业渲染厂商专属任务而不应用变更
- 审批节点(可选)门控生产执行
- 部署作业跨厂商并行批量应用 VLAN 意图
- 验证作业确认运营和预期状态
- 失败时,回滚作业仅针对受影响范围运行
- 可观测性收集前后数据以支持最终验证
- 最终验证运行,发布执行摘要
flowchart TD
A[NetBox VLAN 意图变更] --> B[Webhook 到 AWX]
B --> C[AWX 工作流启动]
C --> E[从 NetBox 同步清单]
E --> F[预检查作业任务]
F --> G[预演作业任务]
G --> H[审批节点]
H --> I[部署作业任务]
I --> J[验证作业任务]
J --> N[收集网络数据]
N --> L[最终验证]
L --> M[更新工单和报告]
F -->|失败| X[停止并返回验证错误]
I -->|失败主机| Y[针对受影响设备的回滚作业任务]
Y --> J
F --> N
classDef sot fill:#cfe8ff,stroke:#0b5fb3,stroke-width:2px;
classDef orch fill:#ffe0cc,stroke:#c24b00,stroke-width:2px;
classDef exec fill:#d7f4e1,stroke:#0f7a3b,stroke-width:2px;
classDef obs fill:#ffd6d6,stroke:#b22222,stroke-width:2px;
class A sot;
class B,C,E,H,L,M orch;
class F,G,I,J,Y,X exec;
class N obs;
5.3.4. 解决方案摘要#
此实现演示了所有关键执行能力:
- 数据集成:使用动态清单从 NetBox 拉取清单和意图
- 触发:AWX 工作流支持 API/Webhook/计划,加上审批门控执行
- 状态管理:AWX 工作流/作业状态追踪进度和故障域;回滚路径是明确的
- 引擎:Ansible 处理并行执行(通过
forks/批量配置)、错误处理和预演 - 网络适配器:厂商原生资源模块将一个意图映射到不同平台(
cisco.ios.ios_vlans、arista.eos.eos_vlans和 HPE/Aruba 集合模块) - 可观测性:前后数据收集支持最终验证和报告
弹性功能:
- 预检查防止向不可达设备部署或创建重复 VLAN
- 预演在应用前显示变更
- 安全检查点在应用状态(
merged、replaced、overridden)之前使用资源模块状态(rendered、parsed、gathered) - 幂等模块使重新运行安全
- 后验证捕获静默失败
- 回滚:专用 AWX 回滚节点将回滚限制在受影响设备范围
规模考量:
- 通过
forks: 50并行执行同时处理 50 台交换机 - 对于更大的部署(500 台以上交换机),批量分组并使用 Ansible Tower/AWX 进行工作流管理
- 如果控制平面或认证系统无法处理负载,添加速率限制
5.4. 总结#
执行模块是网络自动化变得具体的地方:配置被推送,设备被重启,变更发生。但可靠的执行不仅仅是通过 Secure Shell (SSH) 发送命令。
它从数据集成和触发开始,然后依赖状态管理(幂等性、回滚和类事务安全)、有能力的引擎(串行/并行选择、预演/计划、弹性)和隐藏厂商间协议差异的网络适配器层。可观测性为验证和护栏提供反馈,而编排决定何时继续或停止。工具选择不如执行模式重要:集成数据、有意触发、确定性执行、验证结果,并保持干净的回滚路径。
从小开始,有意扩展:一个工作流、一个设备类别和清晰的前后检查。随着爆炸半径增长,添加批量、速率限制和更强的验证。执行是强大而危险的;彻底测试、逐步推出、持续监控,并始终为回滚做好计划。
参考与延伸阅读#
- Network Programmability and Automation: Skills for the Next-Generation Network Engineer, 第二版. O’Reilly Media, 2023. Matt Oswalt, Christian Adell, Scott S. Lowe, and Jason Edelman. 第 10 章(自动化工具)和第 12 章(自动化架构)。
- Network Automation Cookbook, 第二版. Packt Publishing, 2024. Christian Adell and Jeff Kala.
- Ansible 网络自动化文档:https://docs.ansible.com/ansible/latest/network/
- Mastering Python Networking, 第四版. Packt Publishing, 2023. Eric Chou
- NAPALM 文档:https://napalm.readthedocs.io/
- Nornir 文档:https://nornir.readthedocs.io/
- Terraform 网络基础设施自动化:https://www.terraform.io/use-cases/network-infrastructure-automation
💬 Found something to improve? Send feedback for this chapter