Nov 30, 2025 · 990 words · 5 min read
翻译说明:本章由人工智能辅助翻译自英文原版。 我们力求准确,但部分细微之处可能与原文有所不同。 英文原版请访问此处

2. 设计原则#

一支网络自动化团队花了六个月时间,构建了一个他们真正引以为傲的系统:从结构化数据模型中提取意图,通过模板引擎生成设备配置,根据策略库验证变更,并通过 NETCONF 推送配置,同时支持完整回滚。从架构角度来看,这个系统相当扎实。向领导层的演示也进展顺利。然后,他们把它交给了网络运营团队。

采用从未到来。运营人员继续使用 CLI。当被问及原因时,回答出奇地一致:“在它执行之前,我不知道它会做什么。““如果出了问题,我不知道发生了什么。““我看不懂输出内容。“自动化团队构建了一个技术上令人印象深刻、但在运营上不透明的系统。没有预演模式,没有人类可读的变更预览,没有运营人员熟悉的审计追踪。这个系统是一个黑盒,它要求信任,却没有赢得信任。六个月的工程努力就此搁置。

这种结果比大多数团队愿意承认的更为普遍。自动化的失败不仅仅因为 Bug 或糟糕的架构,更因为需要依赖它的人不信任它。我们将探索使网络自动化可靠、可扩展和安全的基础设计原则。这些不是抽象理论,而是自动化能否被采用并交付真实价值的分水岭。

这些原则中有许多也适用于其他软件项目。但网络自动化具有独特的特性,因为它支撑着关键基础设施。网络工程师几十年来依靠谨慎、精确和手动验证的模式来构建和维护这些系统。而现在,我们要求他们采用一种根本不同的模式。这需要信任

2.1 构建信任#

在深入探讨具体原则之前,让我们先谈谈信任。它是网络自动化成功的基石。没有信任,采用几乎不可能实现。

信任为何如此重要?因为没有它,网络工程师不会采用你的自动化。他们需要相信,自动化至少能提供与其所取代的手动流程同等水平的信心(以及其他额外好处)。

更棘手的是:自动化系统通常由可能没有深厚网络经验的工程师构建。因此,自动化必须通过嵌入强大、明确的特性来弥补这一差距,使其对网络工程团队来说安全、可靠且易于定制和管理。

可信自动化的重要性受到 Damien Garros 在 Autocon3 上的演讲《构建可信网络自动化》的启发。

简而言之,我们需要四个基本特性:

  • Predictable:一致、确定性的结果。工程师需要在按下"执行"之前知道会发生什么,并且每次都能得到相同的行为。
  • Reliable:优雅地处理错误,从故障中恢复,确保操作安全完成(或回滚),即便在意外情况下和大规模场景中也如此。
  • Usable:让工程师能够验证、推理和控制行为的界面,无需过度复杂,并配有护栏。
  • Understandable:不能是黑盒。必须以能建立人类信心的方式暴露意图、步骤、结果和决策。
graph BT
  %% ===== Middle Layer =====
  subgraph L2[**特性**]
    direction LR
    B1[可预测]:::layer2
    B2[可靠]:::layer2
    B3[可用]:::layer2
    B4[可理解]:::layer2
  end

  %% ===== Top Layer =====
  subgraph L1[" "]
    direction TB
    A[信任]:::layer1
  end

  %% ===== Connections: Behavior → Outcome =====
  B1 --> A
  B2 --> A
  B3 --> A
  B4 --> A

  %% ===== Styling =====
  classDef layer1 fill:#ffcccc,stroke:#b8860b,stroke-width:2px,color:#000;
  classDef layer2 fill:#ffe6cc,stroke:#4682b4,stroke-width:1.5px,color:#000;

随着 AI/ML 技术进入网络自动化领域,可预测性(即确定性行为)变得愈发重要,因为这些技术引入了一定的随机性。

这些特性是不可妥协的,不是事后补充。而且,将使用和依赖自动化的网络工程师必须认可它们。

我构建过许多网络自动化解决方案。没有什么比眼看着一个精心设计的方案因为网络运营人员不信任它而被忽视或放弃更令人沮丧的了。这些原则帮助你避免这种结果。

有了这些特性,让我们来探索支撑它们的原则。

2.2 设计原则#

以信任为基础,我们可以探索支撑可信自动化所需特性的设计原则。这些原则使自动化可靠、可预测且可扩展。

完整清单可能更长(我们稍后将在介绍软件工程原则时扩展它)。但有六个基本原则是每个网络自动化解决方案都应具备的:

  • Idempotency:相同操作执行多次等于相同的最终状态。减少副作用,消除歧义,使自动化更易理解且可安全重复。
  • Transactional:变更要么完整完成,要么安全失败。不存在部分、不一致或半途而废的状态。对可预测性至关重要,并支持全网回滚。
  • Intent-Driven:定义系统在不同条件下的期望状态。提升可预测性,降低认知负担,使系统更易用。
  • Dry Run 友好:在执行前展示将要做什么。通过让人类验证操作、可视化结果并在变更影响网络之前发现问题,使自动化更易用、更可信。
  • Versioning:支持数据和逻辑的版本控制。在时间线上前进或后退,恢复之前的状态,并行维护多个状态。强化可预测性,提升可靠性。
  • 可测试:在生产环境执行变更之前,必须有适当的测试环境。提升可靠性,帮助工程师理解系统行为。

这些原则不是网络专有的,它们适用于任何 IT 领域的自动化。但在网络自动化中它们尤为关键,因为变更承载着重大运营风险。

这六个原则构成安全、可预测、可信网络自动化的基础,为后续引入的更高级概念奠定基础。在跨大型复杂基础设施扩展自动化时,它们变得更加重要。

graph BT
  %% ===== Bottom Layer =====
  subgraph L3[**原则**]
    direction LR
    C1[幂等]:::layer3
    C2[版本化]:::layer3
    C3[事务性]:::layer3
    C4[可测试]:::layer3
    C5[预演]:::layer3
    C6[意图驱动]:::layer3
  end

  %% ===== Middle Layer =====
  subgraph L2[**特性**]
    direction LR
    B1[可预测]:::layer2
    B2[可靠]:::layer2
    B3[可用]:::layer2
    B4[可理解]:::layer2
  end

  %% ===== Top Layer =====
  subgraph L1[" "]
    direction TB
    A[信任]:::layer1
  end

  %% ===== Connections: Foundation → Behavior =====
  C1 --> B1
  C1 --> B4

  C2 --> B3
  C2 --> B2

  C3 --> B1
  C3 --> B2

  C4 --> B2
  C4 --> B4

  C5 --> B3
  C5 --> B4

  C6 --> B1
  C6 --> B3

  %% ===== Connections: Behavior → Outcome =====
  B1 --> A
  B2 --> A
  B3 --> A
  B4 --> A

  %% ===== Styling =====
  classDef layer1 fill:#ffcccc,stroke:#b8860b,stroke-width:2px,color:#000;
  classDef layer2 fill:#ffe6cc,stroke:#4682b4,stroke-width:1.5px,color:#000;
  classDef layer3 fill:#ccffcc,stroke:#228b22,stroke-width:1.5px,color:#000;

每条设计原则都值得更多阐述。我们将按照其逻辑依赖关系来探索:首先定义我们想要实现什么,然后确保它安全、正确地发生,最后验证它是否有效。

2.2.1. 意图驱动#

为什么从意图开始? 在讨论如何执行自动化之前,我们需要理解我们要实现什么。意图驱动设计是其他一切的基础。

自动化入门的常见场景:执行简单操作的脚本或 Playbook,比如通过提供 FQDN 和接口名称来启用或禁用接口。但这带来了一个问题:下一个使用该接口的人不会知道它的 Desired State 是什么。它应该启用还是禁用?

这个问题同时影响手动和自动化操作。但在自动化中,问题会更严重。你无法从当前状态自信地推断网络意图,尤其是当大量临时变更已经被应用的时候。实际的网络配置是你的 Intent-Driven 逻辑的结果,而不是其可靠的记录。

意图在不同层面呈现,取决于你的可变性和定制化需求。简单环境可能需要较少的可变数据,可以依赖模板。某些数据可能需要跨团队评审,而另一些则可以按照约定和护栏进行更新。

有人可能会认为,他们的意图就是网络的 Actual State:“看看配置,那就是我的意图。“虽然这在技术上是一种意图,但你无法证明它与你的原始意图或实际意图相符、且没有被情况所改变,除非你有一个单独的来源进行比较。网络配置是你意图的输出,而不是意图本身。

因此,你的 Intent-Driven 数据的质量直接决定了你的自动化质量。这个概念与 Source of Truth (SoT)(SoT)密切相关,我们将在第 4 章详细介绍。

基础设施即代码(IaC):意图驱动自动化是 Infrastructure as Code (IaC) 的基础,即将网络基础设施定义视为软件代码。通过 IaC,你获得了版本控制、代码评审流程、测试能力,以及与处理软件变更相同的方式处理基础设施变更的能力。这将网络自动化与软件工程最佳实践相连接。IaC 将模板、清单和意图保存为版本控制下的文件。变更经过评审、测试和追踪,而不是通过一次性 CLI 编辑来应用。

基于意图,下一个原则确保一旦我们知道想要什么,就能安全、一致地执行。

2.2.2. 幂等性#

幂等性为何重要:幂等性确保重复执行产生相同的结果。这至关重要,因为自动化经常因意外、重试机制或设计而多次运行。没有幂等性,每次运行都可能产生不可预测的变更。

Idempotency 意味着多次运行相同操作得到相同的最终状态,没有意外的副作用。

例如,在自动化 ACL 配置中,如果我们定义一条规则,期望的是:

  • 如果规则不存在,在特定位置添加它。
  • 如果规则已存在,不添加重复项。
  • 如果稍后添加了另一条规则,之前的规则保持不变且不被重新排序。

听起来像常识,但现实更为复杂。系统必须实现逻辑来确保幂等性。例如,需要检查:

  • ACL 的当前状态是什么?
  • 该规则是否已经是其中的一部分?
  • 如果没有,需要应用什么差异?

没有幂等性,配置可能以不可预测的顺序应用,影响可重现性。

幂等性适用于网络自动化的不同维度。例如,通过 DHCP 或能够理解标识符请求与结果之间映射关系的 IPAM 系统请求 IP 地址时:

图 1:幂等性示例(来自 Damien Garros 的 Autocon3 演讲

正如你稍后会读到的,为了实现幂等性,更倾向于使用 Declarative 模型而非 Imperative 模型。定义最终状态而非步骤。这并不意味着使用命令式模式是不可能的,但更为复杂。

幂等性专注于单次执行内的一致性。事务性原则确保变更跨多个系统原子性地应用,防止部分或不一致的状态。

2.2.3. 事务性#

事务性为何重要:网络变更通常同时影响多台设备。没有事务性,执行到一半时的失败会让网络处于不一致状态:部分设备已更新,其他设备尚未更新。在分布式系统中尤其棘手,因为跨多个参与者的协调本质上就很复杂。

Transactional 是数据库中的常见概念,多个变更以原子方式应用以保持一致性。在网络(一个分布式系统)中,事务性更为复杂,但这不是新问题。2006 年,NETCONFRFC 6241)引入了网络范围的提交,在多台设备上全局应用变更,或者在验证失败时完全回滚。这种 Atomic Operation 防止了部分状态的出现,否则某些设备已更新而其他设备未更新将是排障的噩梦。然而,并非所有平台都支持 NETCONF,集成复杂性也差异显著。因此,虽然理论上问题已解决,但实际实施取决于你的基础设施。

提供事务性行为可以防止部分或中断的操作(难以排障且使系统处于不可预测状态),对于避免侵蚀对自动化的信任至关重要。一个部分设备应用了变更而其他设备未应用的网络,比完全回滚的网络更糟糕。

那么,通常如何将事务性应用于网络呢?这是多种技术的组合:

  • NETCONF:支持分布式管理协调的网络管理能力,但支持非常有限。
  • 数据库支持的事务:将配置变更存储在具有 ACID 属性的数据库中,然后以协调批次应用,并具备 Rollback 能力。
  • 变更通知队列:捕获所有预期变更,作为整体进行验证,然后在任何设备失败时以协调的 Rollback 机制执行。
  • 两阶段提交模式:首先在所有设备上准备变更(第一阶段),然后同时提交(第二阶段),如果第二阶段在任何设备上失败则 Rollback
  • 事务日志:记录每次变更尝试,包含足够细节以手动或自动撤销部分操作。

注意,分布式环境中的所有机制都需要回滚能力才能提供无缝回滚。你需要一个可以激活的提交或快照(对平台内部或外部)来恢复到之前的状态。这并不总是可用的。在真正的原子提交不可用的情况下,实施经过验证的"准备"阶段,并使用协调提交或分阶段发布来降低风险(仅在没有原生原子性时使用)。

这个原则自然地连接到 Versioning:当出现问题时,你需要确切地知道自己处于什么状态,以便可以回滚到已知良好的版本。

2.2.4. 版本化#

版本控制为何重要:每次变更都带有风险。版本控制让你确切知道什么变了、何时变了、谁变的,并提供在需要时回滚的能力。这是大规模安全运营的基础。

历史上,网络工程通过配置备份来管理变更:用于回滚的快照。然而,回滚后协调变更后状态极其复杂,因为备份缺乏上下文。

在软件工程中,Version Control System (VCS)(如 Git)是标准实践(我没有在任何没有某种 VCS 的环境中工作过)。这些系统支持:

  • 便捷协作:多个开发者贡献代码,并可以在不同分支上推进
  • 时间旅行:返回历史上的某个节点,了解什么时候发生了什么变化
  • 原子分组:多个文件变更可以作为单个单元捆绑在一起

Versioning 应用于网络自动化提供了显著好处:

  • 可审计性和可追溯性:轻松追踪谁在何时改变了什么,使系统透明
  • 跨团队协作:便于评审变更,支持基于团队的自动化开发
  • Atomic Operation:跨多个文件的相关变更可以一起应用,防止部分或不完整的状态
  • CI/CD 集成:每当检测到代码变更时,自动化流水线可以触发测试和验证

配置模板是受益于版本控制的典型数据示例。单个模板变更会影响整个网络中所有派生的配置,因此版本控制变得至关重要。一般来说,所有数据都应以版本化方式存储。通常会有版本化的 YAMLJavaScript Object Notation (JSON) 文件对任何类型的数据进行建模,或者对具有更复杂关系的数据进行建模。

GitOps:一种进一步发展版本控制的新兴模式:Git 仓库成为唯一真相之源,控制器持续比较 Git 中的 Desired State 与网络中的 Actual State,自动纠正偏差。在网络自动化中日益被采用,尤其是在云原生和 Kubernetes 集成环境中。

版本控制提供历史记录和审计追踪。下一个原则确保我们在部署之前验证变更能够正确工作。

2.2.5. 可测试性#

可测试性为何重要:大规模自动化会放大错误。影响一台设备的 Playbook Bug 是可控的;同样的 Bug 应用到 10,000 台设备就是灾难。测试是部署到生产环境之前的安全网。

测试在网络中尤为具有挑战性。通常你不拥有整个网络(想想互联网 BGP 对等,你不拥有对端),或者规模令人望而却步(想象测试一个拥有数千台交换机的数据中心 Fabric)。重现真实测试场景可能近乎不可能或代价高昂。

这并不意味着你没有选择。你可以在多个层次实施测试,在各种情况下验证自动化行为和网络状态。

测试金字塔提供了一个有用的框架:

graph BT
  %% Unit tests (base)
  U1["单元测试<br/>(数据质量、逻辑)"]:::unit

  %% Integration tests
  I1["集成测试<br/>(模拟环境)"]:::integration

  %% End-to-end tests (top)
  E1["端到端测试<br/>(实验室、验证)"]:::enduser

  %% Links
  U1 --> I1
  I1 --> E1

  %% Styling
  classDef unit fill:#a6e3a1,stroke:#3b7a57,stroke-width:2px,color:#000;
  classDef integration fill:#89b4fa,stroke:#1e3a8a,stroke-width:2px,color:#000;
  classDef enduser fill:#f9e2af,stroke:#b8860b,stroke-width:2px,color:#000;

AI 代码助手在测试生成方面越来越有用。如果你能阐明要测试什么,这些工具可以帮助快速创建全面的测试套件(并捕获你一眼可能看不出的逻辑缺陷)。

核心洞察:自动化需要持续测试以确保随着系统增长不出现故障。开发后期引入的回归比单元测试阶段捕获的要昂贵得多。随着自动化规模扩大,这一点变得不可妥协。

高级测试策略:

  • 混沌工程:有意注入故障(设备中断、网络延迟)以验证你的自动化和监控能否优雅地处理它们(Netflix 通过其 Chaos Monkey 推广了这种方法)。
  • 基于属性的测试:定义必须始终成立的属性(例如,“BGP 应始终在 30 秒内收敛”)并让测试框架生成场景来验证它们。

最后,在部署任何变更之前,运营人员需要了解将会发生什么。

2.2.6. 预演友好#

预演能力为何重要:即使有了前面所有的保障措施,人类也需要在自动化执行之前了解它将做什么。预演能力架起了信任与行动之间的桥梁:它在任何变更实施之前,向运营人员精确展示将要变更的内容。

在任何重大决策中,在执行之前了解行动计划至关重要。建造房屋时,你希望在批准施工之前看到它的样子。同样,在网络工程中,我们习惯于在部署之前审查执行计划的变更管理流程。

随着网络自动化的普及,变更的频率和范围可能急剧增加。在执行之前为运营人员提供清晰的可见性,对于建立信心和实现适当的审查至关重要。

有些工具提供了这种 Dry Run 能力,你也可以在需要时自行创建:

  • Ansible--check--diff 标志显示将被更改的内容
  • Terraformterraform plan 命令显示当前状态与 Desired State 之间的差异
  • 自定义网络 API:某些可能暴露"预演"或预览模式

以下是这种可见性的示例:

Ansible Diff 输出

- description: Uplink to CoreSwitch1
+ description: Uplink to CoreSwitch2
  mtu: 9216

Terraform Plan 输出

~ description = "Uplink to CoreSwitch1" -> "Uplink to CoreSwitch2"
  mtu          = 9216

自定义网络 API 预览响应

{
  "operation": "PATCH",
  "path": "/interfaces/Gi0-1",
  "changes": [
    {
      "field": "description",
      "current_value": "Uplink to CoreSwitch1",
      "proposed_value": "Uplink to CoreSwitch2",
    }
  ],
  "mtu": 9216
}

预演将自动化从"祈祷最好的结果"的方式转变为一个经过深思熟虑、可审查的流程。

高级预演概念:

  • 影响分析:不仅展示变更内容,还分析和传达业务影响(例如,“这将暂时影响 10% 的流量”)
  • 分阶段发布:通过先将变更推广到设备子集、验证影响后再进行全量部署,在规模上实现预演
  • 网络仿真:与网络测试工具结合,针对生产网络的副本执行预演

2.3. 架构决策模式#

除了基础原则之外,还有一些关键概念和考量,连接着理论与实际实施。这些模式帮助你做出关于自动化架构的战略决策。

2.3.1. 声明式 vs. 命令式#

声明式与命令式方法之间的选择是根本性的。根据用例,各有优势和权衡。管理基础设施配置可以采用两种方式:

  • Declarative 自动化定义期望的最终状态,系统负责找出如何实现。例如:“我希望接口 Gi0/1 的 MTU 为 9000。“自动化引擎确定当前状态并只应用必要的变更。专注于什么,即期望的结果。
  • Imperative 自动化指定要执行的确切步骤。例如:“显示接口配置,解析它,计算差异,按此顺序发送这些确切的 CLI 命令。“专注于如何,即具体操作。

声明式方法自然支持 IdempotencyDry Run 能力和 Transactional 行为,但需要支持它的基础设施和系统。

命令式方法提供精确控制,但更难实现幂等性,更容易产生意外副作用。然而,当你的目标系统缺乏声明式能力时,它们可能是唯一的选择。

关键权衡:声明式方法扩展性更好。当可用时,实现复杂工作流的复杂度保持恒定,而命令式方法随着工作流变得更复杂,复杂度会指数增长。

大多数现代自动化框架(TerraformAnsible、Kubernetes 或基于 Yet Another Next Generation (YANG) 的工具)趋向于 Declarative 模型,同时在需要时允许 Imperative 回退(例如,Ansible raw 模块、直接 Command Line Interface (CLI)、Netmiko)。

某些工具(如 Ansible)根据模块类型可以双向工作。网络特定模块(如 cisco.ios.ios_interfaces)是声明式的,而 ansible.netcommon.cli_command 执行命令式命令。在以下示例中,你将看到两种方式:

- name: Ansible 中的命令式 vs. 声明式方法
  hosts: switches
  gather_facts: no
  vars:
    interface_name: GigabitEthernet0/1
    new_description: Uplink to CoreSwitch2
  tasks:
    - name: 命令式 - 执行精确命令
      ansible.netcommon.cli_command:
        command: |
          configure terminal
          interface {{ interface_name }}
          description {{ new_description }}
          no shutdown
          end
      # 重复此任务可能添加重复项,不满足幂等性

    - name: 声明式 - 定义期望状态
      cisco.ios.ios_interfaces:
        config:
          - name: "{{ interface_name }}"
            description: "{{ new_description }}"
            enabled: true
        state: merged
      # 重复此任务是安全的,它会收敛到期望状态

请记住,使用声明式方法时,你将逻辑卸载到外部系统,在某些场景中这可能不是可用选项。注意在前面的示例中如何利用 Ansible 模块 cisco.ios.ios_interfaces,该模块实现了提供声明式风格所需的逻辑。

声明式方法更安全、更可预测,但需要针对你特定设备的模块支持。

2.3.2. 可变 vs. 不可变基础设施#

概念:你是否允许对基础设施(网络)进行原地修改,还是在需要变更时完全替换基础设施?

  • 可变基础设施:传统方式,通过 Secure Shell (SSH) 连接到设备直接修改配置(或通过 NETCONF、gNMI 或任何 Application Programming Interface (API) 修改)。变更原地应用。
    • 优点:破坏性更小,开销更低。
    • 缺点:状态追踪更难,增加 Configuration Drift 风险。
  • 不可变基础设施:从不修改运行中的基础设施。而是创建具有期望变更的新基础设施并切换流量/连接。在云中(容器、虚拟机)大量使用,但在网络中不太常见。
    • 优点:状态可预测,更易验证,消除偏差。
    • 缺点:需要编排,恢复更复杂,资源开销更高。

在网络自动化中,我们通常处于混合状态:配置是可变的(我们原地更改),但不可变性原则应指导你的设计:版本化一切,追踪变更,并能够在需要时从头重建。

2.3.3. 绿地 vs. 棕地#

这不是一个技术术语,但在开发网络自动化解决方案时,理解这两种不同场景很有用:

  • Brownfield 环境存在遗留系统、手动流程、不一致的配置和部落知识。更难,因为你在自动化从未打算被自动化的复杂性:不一致的设计、缺失的数据、遗留技术、人的习惯和实时流量约束。但这是最常见的环境。
  • Greenfield 环境从头开始构建,采用自动化优先设计。你可以干净地实施所有原则:从第一天起的版本控制、声明式意图、干净的数据模型、全面的测试。理想但罕见。

让我们深入了解棕地环境为何如此复杂…

  • 网络设计不一致(或者即使曾经一致,现在也没有完全实施),只是一堆例外的组合。
  • 缺乏干净可靠的数据。网络本身或可能是某个过时的电子表格是参考。
  • 来自不同供应商和世代的某些网络设备不支持超出 CLI 的现代网络管理接口,这限制了声明式方法的实施。
  • 运营文化和对变更的恐惧。不要忘记人员是第一位的,你需要在被采用之前赢得信任。
  • 自动化必须与实时流量共存,没有干净的切割点。出错的余地极小。

然而,在这些情况下仍然有希望。以下三种方法有助于在这些场景中起步:

  1. 部分绿地方法:自动化新基础设施,同时逐步重构遗留系统。这展示了进展,而不是从最大复杂度开始。
  2. 增量目标选择:专注于更易于自动化且能带来快速成果的网络部分:
    • 为少数管理功能(AAA、NTP、SNMP)添加配置偏差检测和修复
    • 为单一设备类型自动化接口配置
    • 在扩展之前对单个子系统进行标准化
  3. 建立动力:每一个小成果都证明了自动化的价值,并为获得资金和扩展建立信任

2.3.4. 设备多样性与服务抽象#

并非所有网络设备或服务的工作方式都相同。不同的供应商、型号甚至软件版本都有独特的接口、能力和限制。你的自动化必须战略性地应对这种异构性。

两种主要方法:

  • 拥抱供应商特定的自动化:为每个供应商的独特能力编写定制自动化,同时不影响可重现性。优点:初期更简单,充分利用设备优势。缺点:形成孤岛,需求变化时迁移更困难。
  • 抽象掉差异:创建一个供应商无关的抽象层,标准化常见操作。优点:可移植性、统一接口。缺点:增加复杂性,可能失去设备特定能力。

最佳实践:分层方法 大多数成熟的运营使用两者结合:底层是供应商特定的驱动(每种设备类型一层),上层是通用的抽象层。这让你获得两种策略的好处。

真实案例:构建 DMVPN 替代方案 网络供应商提供 DMVPN(动态多点 VPN)用于实现 Hub-Spoke VPN 的可扩展性。另一种方式是设置许多简单的点对点 VPN 隧道。但如果没有自动化,这很繁琐(这也是该协议存在的原因)。通过自动化管理数千条隧道是可行的(且不会特别复杂),它通过编排而非协议复杂性提供了类似的可扩展性,因为几乎所有平台都支持基本的 VPN 隧道。你用自动化抽象替换了协议依赖,这通常是更好的权衡。

关键原则:将实现细节(特定设备如何工作)与你想要实现的最终目标分离。这支持供应商独立性,并简化了对系统的推理。

2.3.5. 工具优先于设计的谬误#

虽然工具不可或缺,但它们不能替代良好的设计。一个常见错误:购买一个工具并期望它解决自动化问题。工具很重要,但它们放大了现有的架构和设计。一个设计良好的自动化策略配上平庸的工具,胜过设计糟糕的策略配上最好的工具。

设计和架构是战略,应该优先。工具是实现细节。在选择工具之前,先花时间了解你的需求、设计你的架构、定义你的原则。有时分布式架构是合适的,其他时候更简单、更强大的解决方案在结果与复杂性方面更符合你的需求。没有单一公式,但你必须认识到并有意识地做出选择。

还要记住,工具不是魔法盒子。你总是需要将自己的逻辑带入其中。定制化和领域特定的配置是不可避免的。

第 3 章中,我们将探索一个参考架构,重点介绍评估工具时需要考虑的主要构建模块。

2.3.6. 购买 vs. 自建#

一个常见的战略问题:应该购买现成解决方案、构建自定义自动化,还是采用混合方法?

简单规则:能买则买,必要时才建。反直觉地,构建通常比购买更昂贵。但购买你需要的东西并不总是可能的。

评估决策时:

  • 购买:当产品与你的需求高度吻合、支持你的架构,且成本/收益分析有利时使用
  • 自建:当你的需求是独特的、你有专业知识,或现成解决方案不符合你的原则时使用

这从根本上是一个设计决策,而非采购决策。它关乎你真正需要多少定制化和控制权。

在实践中,大多数组织采用混合方法:购买战略组件(编排平台、CI/CD 系统、数据存储),但构建领域特定的自动化(模板、工作流、验证逻辑)。你必须拥有领域特定层:通用方案很少能带来你所需的结果。

评估开源解决方案时,考虑可扩展性。你通常可以复用框架,并在其上构建自己的自定义层:例如,一个存储网络意图的数据源,用自定义数据模型扩展核心工具。另外,别忘了开源产品也可以获得支持和企业版,在需要时添加安全网。

2.3.7. 自动化治理#

谁决定什么要被自动化?谁批准对自动化逻辑的变更?谁可以授权一个作业模板在生产环境中针对一千台设备运行?这些问题很少有干净的技术答案,但在自动化达到任何重要规模之前,必须从组织层面回答这些问题。

无治理的自动化引入了与手动操作不同类型的风险。一个工程师手动犯错影响一台设备或一个会话。一个自动化工程师在 Playbook 模板中犯错,可能同时影响所有在范围内的设备。爆炸半径随自动化能力的扩大而扩大。

在这种背景下,治理意味着定义四件事:

  • 范围边界:哪些操作有资格进行自动化,哪些无论自动化成熟度如何都需要人工执行。BGP 策略变更、路由协议修改或安全策略更新,即使在其他栈都自动化的情况下,也可能保持人工门控,不是因为自动化无法做到,而是因为错误的后果足够大,值得在循环中保留一个人。

  • 逻辑审批流程:自动化逻辑的变更(新 Playbook、修改的模板、SoT 中更新的数据模型)应通过与软件代码审查相当的审查流程。这里的纪律与软件工程相同:在生产环境中运行的代码变更在到达生产环境之前需要审查。自动化代码也不例外。

  • 执行授权:谁可以针对什么范围、在什么条件下触发什么作业。这直接映射到执行层(第 5 章)和呈现层(第 8 章)中的基于角色的访问控制。治理模型必须在工具的访问控制中表达,而不仅仅在策略文档中。

  • 审计与问责:自动化必须产生与手动变更管理相同的审计追踪。一个执行事件必须可追溯到变更工单、审批人以及运行的特定版本自动化逻辑。在受监管的环境中,这不是可选的。

治理不是为了治理而官僚化。它是组织将信任逐步扩展到自动化的机制。从低风险、可逆、被充分理解的操作开始,随着自动化建立记录逐步扩大范围,比完全阻止自动化或在没有护栏的情况下部署都更为有效。

2.3.8. 这些原则为何重要:从失败中学习#

自动化会放大你输入的一切。干净、设计良好的流程变得更高效、更可靠。但糟糕的设计、不良的数据或有缺陷的逻辑也会规模化,更快地制造更大的问题。

Meta 宕机事件案例研究

2021 年 10 月,Meta(前身为 Facebook)经历了一次全球网络中断,使其系统下线数小时。原因是什么?自动化放大了一个错误配置。在一次例行流量切换期间,自动化系统基于不完整的策略验证进行了全局变更。配置在其网络中级联,造成全球传播的单点故障。

根本原因是骨干路由器上有缺陷的配置变更,破坏了数据中心之间的通信。这种级联故障在全球范围内停止了服务,也影响了内部工具,使诊断和恢复更加复杂。Meta 澄清说,问题不是由恶意活动引起的,也没有用户数据被泄露。然而,这次中断突显了其自动化策略中可能存在的关键缺口:

  • 无幂等性:变更在不检查当前状态的情况下应用
  • 无事务性:部分设备收到了变更,其他没有(部分失败)
  • 测试不足:该场景未在预生产环境中被发现
  • 无预演能力:变更在没有预览或验证的情况下应用
  • 版本控制不足:无法快速识别和回滚问题变更
  • 可观测性不足:无法足够快地检测到故障
  • 无优雅降级:变更在没有爆炸半径控制的情况下全球级联
  • 责任不清:自动化没有清晰的决策层级

与原则的映射:我们六个核心设计原则中的每一个都直接应对了这些故障之一。这证明它们不是可选的"好则有之”,而是必不可少的保障措施。

教训:不要自动化混乱并期望以后修复。而是:

  1. 从你理解并能验证的流程开始
  2. 增量式自动化,从每一步中学习
  3. 将每一次自动化故障视为学习机会
  4. 内置保障措施:速率限制、回滚机制、可观测性、爆炸半径控制
  5. 分离关注点,使一个区域的故障不会全球级联

注意这如何与第 1 章中介绍的人员、流程和技术方法相联系。

我们探索的原则(Intent-DrivenIdempotencyTransactionalVersioning、可测试性和 Dry Run 能力)直接应对这些故障模式。它们不是可选功能,而是必须从一开始就内置的基本保障措施。

2.4. 软件工程原则#

除了网络特定的设计原则之外,更广泛的软件工程原则在构建可维护和可扩展的自动化系统中扮演着关键角色。如果你有软件工程背景,你会认出其中大部分;这里的价值在于了解它们如何具体应用于网络自动化。

并非十二条都在这个领域具有同等分量。四条针对关键基础设施自动化特有的故障模式:最小惊讶原则(行为出乎意料的自动化会在信任建立之前摧毁第 2.1 节中建立的运营信任)、防御性和健壮编程(网络以部分、非确定性方式失败,自动化必须在设计上预测到这一点,而非作为例外处理)、快速失败、可见失败(早期检测失败可以在跨设备规模化之前控制爆炸半径)、关注点分离(NAF 框架在第 3 章直接实例化的结构原则——分离意图、执行逻辑和呈现不是通用的良好实践,而是使各模块可独立演进的特定结构)。其余八个原则是标准的软件卫生:正确、重要且值得了解,但不是网络自动化特别失败或成功的原因。

我们参照 Robert C. Martin 的《代码整洁之道》和《架构整洁之道》的启发,将其组织为两类:

  • 整洁代码原则:如何编写可读、可维护且正确的自动化逻辑。
  • 整洁架构原则:如何构建系统,使组件保持独立、可测试且可演进。

专注于将这些原则应用于网络自动化,你可以在 Ken Celenza 撰写的 Network to Code 博客系列中找到这些原则的良好示例。

2.4.1. 整洁代码原则#

本节专注于软件组件的构建方式。

为读者编写代码

你编写的代码将来会被多次阅读:被你自己(调试时)或被他人阅读。而他们很可能没有你最初的上下文。通过有意义的名称、注释和结构在代码中清晰地表达你的意图(请不要过度使用注释,谨慎使用)。自动化代码不仅仅是给机器的指令,也是给人类的一种文档形式。

DRY(不要重复自己)

避免在自动化代码库中重复逻辑。而是将通用模式提取为可复用的模板、函数或工作流。

与其在十个不同的 Playbook 中编写设备特定的配置逻辑,不如创建一个带有差异变量的共享模板。这减少了 Bug,使更新更容易。这个原则也适用于数据:使用利用继承和多态的适当数据结构来创建更具可扩展性和可复用性的数据模型。

当你违反 DRY 时,在一处修复需要在另外五处修复,你不可避免地会漏掉其中一处。

单一职责原则(SRP)

每个模块、函数或工作流应该有一个变更的理由。在网络自动化中,这意味着:

  • 配置模板渲染器不应同时处理设备发现
  • 验证工作流不应同时执行变更

当每个组件有单一职责时,故障被隔离,测试更简单,变更风险更低。显然,会有一个组合函数(或编排)将所有这些功能连接在一起。

快速失败、可见失败

尽早发现问题并清晰地暴露它们。在自动化中:

  • 在输入时立即验证数据(不要等到部署时)
  • 使 Dry Run 输出明确且明显
  • 以完整上下文记录失败,而非模糊的错误代码
  • 出现问题时立即向运营人员发出警告

尽早捕获问题减少了爆炸半径和响应时间。

安全

加密、认证、最小权限和审计追踪必须从一开始就嵌入自动化系统,而不是事后添加。

在网络自动化中:每个变更都应可审计,凭证不应硬编码,访问控制应遵循最小权限原则。一个拥有完整网络访问权限的自动化系统是等待爆发的安全灾难。

我们将在第 12 章深入探讨安全和合规考量。

最小惊讶原则

自动化应以用户期望的方式运行。令人惊讶或违反直觉的行为会侵蚀信任。

例如,如果自动化任务名为"deploy_interface”,运营人员期望它创建一个接口,而不是删除一个。意外行为会让用户沮丧并导致失误。

防御性和健壮编程

内置重试、Circuit Breaker 模式、Compensation Logic 和回退机制。分布式系统会失败。为此设计,而非抵制它。如果设备暂时无法访问,使用指数退避重试,而不是立即失败。如果变更途中失败,要有回滚计划。

这些模式在第 7 章(编排)中具体出现,其中 Saga 补偿模式处理部分工作流失败,重试/退避逻辑内置于弹性功能中。
“发送时保守,接受时宽容。” 波斯特尔定律(RFC 761)

在网络自动化中:

  • 保守发送:确保你发送给 API 或设备的数据遵循严格的模式和契约
  • 宽容接受:准备好处理变体(例如,以整数或字符串形式的属性,带有转换)以最大化与不同系统版本的互操作性

这个原则连接了整洁代码和架构。它同时影响你编写集成逻辑的方式和你构建系统接口的方式。

2.4.2. 整洁架构原则#

接下来,我们探索支配网络自动化解决方案各组件如何组合在一起的原则。

KISS(保持简单)

更简单更易于理解、测试和维护。在设计、实施和架构中避免过度工程化。简单性减少 Bug,提高可维护性,改善可读性,使系统更易于扩展或调试。

简单不意味着简陋。它意味着选择满足需求的最直接方法,而不过度工程化或添加过早的抽象。

在网络自动化中,这意味着(在可能的情况下)优先选择直接的 Declarative 方法而非复杂的命令式脚本,优先考虑清晰度、小型可组合组件、可预测行为以及他人(包括未来的你)可以轻松理解的解决方案。

关注点分离

明确分离数据(配置)、逻辑(工作流)和呈现(API/用户界面)。这防止了紧耦合,使每个层能够独立演进。

在网络自动化中:

  • 数据层:以结构化形式存储的网络意图
  • 逻辑层:自动化引擎和验证规则
  • 呈现层:供运营人员使用的 API、CLI、仪表板

这种分离允许你在不影响底层逻辑的情况下改变运营人员与自动化的交互方式。这种分离直接映射到 NAF 构建模块:数据层是真相之源(第 4 章),逻辑层跨越执行(第 5 章)、可观测性(第 6 章)和编排(第 7 章),呈现层是第 8 章。第 3 章介绍完整的 NAF 框架。

可观测性

自动化必须被检测以测量其自身行为、检测故障并触发纠正措施。你无法优化无法测量的东西。追踪技术指标和面向业务的指标(例如,自动化举措的投资回报率)。

第 6 章中,我们将介绍我们在网络中关心的不同类型的可观测性数据:指标、日志、追踪、网络流量、告警(以及更多),以及如何利用它们在各个层面提供有用信息。

没有可观测性,你就是盲目飞行。你不会知道自动化是否在正确工作,还是只是看起来在工作。记住:自动化系统本身也会失败,需要监控。对自动化工具的检测程度要与对网络的检测程度一样彻底。

可扩展性

以未来为出发点进行设计。新的供应商、新技术和新需求将会到来。架构应该允许这些变化,而不需要完全重写。

在实践中:为供应商特定驱动使用插件架构,避免对网络拓扑的硬编码假设,并保持接口稳定,同时允许实现演进。

最小耦合、最大内聚

定义系统通信的清晰契约:模式、验证规则和向后兼容策略。这些契约使组件能够独立演进。

在网络自动化中,如果你的编排系统通过定义良好的 REST API 与设备驱动通信,只要 API 契约得到维护,任何一层都可以独立演进。

始终以 API 优先设计方式对待每个系统:先设计 API(而非实现)。这确保系统可以独立开发,并在不破坏其他组件的情况下被替换。


这些高级原则将在后续章节中深入探讨,届时我们将讨论在大型(及中小型)组织中扩展自动化。现在,理解这些原则与我们之前探索的设计原则相互补充即可:它们共同构成了大规模可信、可维护网络自动化的基础。

2.5. 小结#

本章确立了信任是成功网络自动化的基础。信任来自四个核心特性:PredictableReliableUsableUnderstandable

这些特性由六个基础设计原则支撑:

  1. 意图驱动:在定义如何实现之前,定义你想要实现什么
  2. 幂等:重复执行产生一致的结果
  3. 事务性:变更完整完成或安全失败,绝不部分完成
  4. 版本化:追踪所有变更,保留完整历史和审计追踪
  5. 可测试:在部署到生产环境之前验证行为
  6. 预演友好:在执行之前预览变更

除了这些核心原则之外,我们探索了架构决策模式(声明式 vs. 命令式、绿地 vs. 棕地、设备抽象)和软件工程原则(整洁代码和整洁架构),它们在真实系统中使这些模式可操作。

这些原则不是抽象理论:它们在你将使用的工具和框架中有具体的实现。在本书的其余部分,我们将看到架构思维(第 3 章)如何将这些原则应用于更大的系统,以及各构建模块(第 4-9 章)如何使它们可操作。

关键收获:

  • 从原则开始,而非从工具开始
  • Predictable 的结果设计,而非为复杂性设计
  • 持续测量和改进

当你持续如此,信任自然随之而来,有了信任,就有了在整个组织中扩展自动化的能力。

你现在理解了使自动化值得信赖的原则。在第 3 章架构思维)中,我们将看到如何将这些原则构建为可扩展的系统。你将学习如何设计能够随组织成长而不变得难以管理的自动化:本章所学原则的实用架构视角,将系统性地应用于实践。

💬 Found something to improve? Send feedback for this chapter