网络自动化运维实战:把交换机当代码来管
一份面向「网络设备运维管理」的完整学习资料。用通俗的语言和生动的例子,讲清楚落地一套 IAC(基础设施即代码)+ CI/CD 自动化方案需要的基础架构、软件和方法。读完你将从「只会敲命令行」进阶到「合并即部署」的高手思维。
热身一、为什么网络工程师也要学 IAC + CI/CD
先讲一个真实的场景。周五晚上 22:30,你接到告警:某分公司 30 台接入交换机要批量把管理 VLAN 从 10 改成 99。你打开 SecureCRT,一台一台 SSH 进去,敲 conf t、vlan 99……敲到第 12 台时手滑,把 vlan 10 删了——整栋楼的管理通道瞬间断了。你冷汗直下,赶紧回滚。
这不是你技术差,而是「手工 CLI 运维」这件事本身就反人性、不可复制、不可回滚。软件工程师早就不这么干了:他们把代码写进 Git,提交后由流水线自动测试、自动发布,出问题一键回退。我们要做的,就是把这套成熟的方法「平移」到网络设备上。这就是 IAC + CI/CD 的本质。
传统手工运维的五大痛点
- 配置漂移:设备真实状态和你的 Excel/CMDB 对不上,时间一久谁都不敢动。
- 人工易错且不可重复:逐台敲命令,复制粘贴改 IP 漏一位就是事故,且无法保证每台都一致。
- 无法审计追溯:谁、什么时候、为什么改了这台设备?只能靠记忆或翻聊天记录。
- 没有预演环境:变更直接上生产,大多数网络设备没有数据库那样的「事务回滚」,敲错就是全网中断。
- 规模不可扩展:设备从 50 台涨到 5000 台,手工模式直接崩溃。
IAC + CI/CD 就是这五个痛点的「对症药方」:用单一事实来源根除漂移,用代码保证一致,用 Git 提供审计,用仿真预演拦截错误,用流水线把「一次改一台」变成「一次改五千台」。下一章先把名词讲透。
概念二、四个核心名词,用大白话讲清楚
1. 基础设施即代码(IaC)
传统上,网络「应该长什么样」装在老网的脑子里、写在Word 里、散落在几十个设备里。IaC 的意思是:把期望的网络状态全部写成机器可读的文本文件(模板 + 变量),存进 Git。设备当前配置由这些文本「渲染」出来。想改网络?改文本,而不是登设备。
2. 持续集成 / 持续部署(CI/CD)
CI(持续集成):你每提交一次配置改动,流水线自动帮你做语法检查、模板渲染、离线仿真预演,像「自动质检员」。CD(持续部署):质检通过后,流水线把配置下发到设备,并完成部署后校验。合起来就是「提交即被自动检验并(在审批后)自动上线」。
3. 单一事实来源(SoT, Single Source of Truth)
这是网络自动化最容易被忽视、却最关键的基石。SoT 是一个系统,唯一、权威地保存「网络的设计意图」:有哪些设备、接口怎么连、IP 怎么分、VLAN 叫什么。所有配置都由 SoT 里的数据「算」出来。这样设备状态和 SoT 永远一致,漂移从此消失。代表软件:NetBox、Nautobot。
4. GitOps
GitOps 是 CI/CD 的进阶形态:Git 仓库里的状态就是生产环境的「唯一事实」。只要把改动合并进主分支,系统就自动把网络变成那个样子;仓库和真实网络不一致时,会自动纠偏(或报警)。可以理解为「合并即部署」的终极形态。
架构三、整体架构:一套能跑起来的五层模型
无论你管 50 台还是 5000 台设备,落地这套方案的骨架都长下面这样。从上到下是一条「提交 → 校验 → 下发 → 回采」的闭环:
① 单一事实来源 SoT(NetBox / Nautobot)
网络的「设计蓝图」:设备清单、接口、IP、VLAN、BGP 邻居、拓扑。CI 阶段只读它来渲染配置。
② Git 仓库(配置即代码)
存放 Jinja2 模板、变量文件、渲染出的配置、CI 定义(.gitlab-ci.yml / workflows)。提供 MR/PR 评审与完整追溯。
③ CI/CD 引擎(流水线编排)
监听 Git 事件,串起「lint → 渲染 → 仿真预演 → 审批 → 灰度 → 部署 → 校验」各阶段,保证可重复、零接触。
④ 测试校验层(贯穿 CI,防事故闸门)
静态 lint、离线仿真(Batfish 可达性/ACL 分析)、动态验证(pyATS、NAPALM、SuzieQ),在合并前拦截 90% 的隐患。
⑤ 自动化执行层(真机下发)
把已校验的配置通过 SSH / NETCONF / gNMI 推到设备,并做 post-check 回采,结果回写 SoT 与 ITSM。
数据是怎么流动的
网工在本地改模板/变量 → git commit && git push 并发起 MR → CI 引擎触发 → 从 SoT 拉取意图数据 + 用模板渲染出目标配置 → Batfish/pyATS 在仿真里预演「改完之后网络还通不通」 → 评审通过并合并 → CD 由执行层把配置下发真机 → 部署后 pyATS/SuzieQ 回采校验,并把结果回写 SoT/ITSM。
入门四、工具初体验:先跑通最小闭环
别一上来就搭全套。入门阶段的目标:用最少的工具,亲手把「一台交换机」的配置改对、改可重复。你只需要三样东西:Git、Ansible、一个 Jinja2 模板。
Step 1:把配置写进 Git
先在本地建仓库,把设备信息和模板分开存。这是「数据」和「逻辑」分离的第一步,也是后面能规模化的关键。
# 项目结构
network-lab/
├── inventory.ini # 设备清单(连接信息)
├── templates/
│ └── ios.j2 # Jinja2 配置模板
├── group_vars/
│ └── switches.yml # 变量:主机名、VLAN、IP 等
└── site.yml # Ansible 剧本
Step 2:写一份「会呼吸」的配置模板
模板里用 {{ 变量 }} 占位,真正的值放在变量文件里。这样 100 台设备共用一个模板,只改数据。
# templates/ios.j2
hostname {{ inventory_hostname }}
!
vlan {{ mgmt_vlan }}
name MANAGEMENT
!
interface {{ uplink }}
description uplink-to-core
ip address {{ uplink_ip }} 255.255.255.0
!
snmp-server community {{ snmp_ro }} RO
Step 3:用 Ansible 把配置推上去
inventory 告诉 Ansible「连谁、怎么连」;playbook 描述「要做什么」。
# inventory.ini
[switches]
sw1 ansible_host=192.0.2.1
[switches:vars]
ansible_network_os=cisco.ios.ios
ansible_user=admin
ansible_connection=network_cli
# site.yml
- name: 给交换机打基础配置
hosts: switches
gather_facts: false
connection: network_cli
tasks:
- name: 用模板渲染并下发配置(变更后写存档)
cisco.ios.ios_config:
content: "{{ lookup('ansible.builtin.template', 'templates/ios.j2') }}"
backup: true
save_when: modified
ansible-galaxy collection install cisco.ios,再执行 ansible-playbook -i inventory.ini site.yml --check --diff。注意 --check --diff 只「预演」并打印差异,不下发——这是你第一个「安全闸门」。到此,你已经体验了 IAC 的雏形:配置来自模板+数据,改动可重复、可预览。但还差「自动质检」和「单一事实来源」。这就进入进阶篇。
进阶五、软件工具栈详解:每层该装什么
5.1 单一事实来源:NetBox 与 Nautobot
NetBox v4.6(Apache-2.0)
IPAM/DCIM 起家,社区最大、插件最多(NAPALM、Config Context)。适合「先把网络登记清楚、求稳」的团队。通过 REST/GraphQL API 把数据喂给 Ansible 渲染配置。
Nautobot v2.4(Open Core)
NetBox 的「企业进化版」,自带 Golden Configuration(配置合规对比)、SSoT(多源同步)、Data Validation Engine、Jobs(平台内跑 Python)。适合需要内置合规与深度自动化的中大型团队。
商用补充还有 Cisco NSO、IP Fabric、Device42。选型建议:纯登记/小团队用 NetBox;要合规审计/中大型用 Nautobot。
5.2 执行层三剑客:Ansible / Nornir / OpenTofu
Ansible(ansible-core 2.19,声明式推送首选)
agentless(无需在设备上装 agent),厂商集合丰富:cisco.ios、arista.eos、junipernetworks.junos,还有多厂商统一的 napalm_* 模块。新版本默认用 ansible-navigator。下面是一段真实可跑的 Cisco IOS playbook:
- name: 配置接口与主机名
hosts: ios
gather_facts: false
connection: network_cli
tasks:
- name: 设置主机名
cisco.ios.ios_config:
lines: hostname {{ inventory_hostname }}
- name: 配置上行口(parents 定位父级配置节)
cisco.ios.ios_config:
lines:
- description uplink-to-core
- ip address 172.31.1.1 255.255.255.0
parents: interface GigabitEthernet0/1
Nornir(v3.6.0,Python 原生、跑得飞快)
被称为「自动化界的 Flask」。没有伪语言,全程 Python,内置多线程并发,比 Ansible 快、可编程性更强。适合高速批量变更和数据驱动任务:
from nornir import InitNornir
from nornir_scrapli.tasks import send_configs
from nornir_utils.plugins.functions import print_result
nr = InitNornir(config_file="nornir.yaml") # 内含 threaded 并发数
cmds = ["vlan 10", "name DATA", "vlan 20", "name VOICE"]
def push_vlans(task):
task.run(task=send_configs, configs=cmds)
results = nr.run(task=push_vlans)
print_result(results)
Terraform / OpenTofu(声明式编排)
OpenTofu 是 Terraform 最后一个开源版本(1.5)的社区分支,由 Linux 基金会托管(已进入 CNCF Sandbox),MPL-2.0 许可,可无缝替代。Terraform 自 2023-08 起改用 BUSL 商业许可。它俩负责「编排」:声明网络资源(VLAN、接口、云 VPC)的期望状态并管理其生命周期。下面用 CiscoDevNet 的 iosxe provider 管理一台交换机的 VLAN:
terraform {
required_providers {
iosxe = { source = "CiscoDevNet/iosxe", version = "~> 0.1" }
}
}
provider "iosxe" {
address = "https://192.0.2.1"
username = "admin"
password = var.ios_password # 用变量,别硬编码
}
resource "iosxe_vlan" "example" {
vlan_id = 123
name = "Vlan123"
shutdown = false
}
三者怎么选?一张表说清
| 维度 | Ansible | Nornir | Terraform / OpenTofu |
|---|---|---|---|
| 学习曲线 | 低(写 YAML) | 中(需 Python) | 中(HCL + 状态模型) |
| 速度 | 中(进程开销) | 高(原生并发) | 高(并行 + 增量) |
| 可编程性 | 低(DSL) | 极高(纯 Python) | 中(声明式) |
| 网络生态 | 极丰富(厂商集合 + napalm) | 丰富(scrapli/napalm) | 中(交换机 provider 渐增) |
| 适合场景 | 配置推送、合规、异构网络 | 高速批量变更、数据任务 | 资源编排、云/Underlay 生命周期 |
5.3 别忽视的 Python 基座
- Netmiko:基于 SSH 的多厂商连接库,是 Ansible 网络模块和 Nornir 插件的底层基座。
- NAPALM:多厂商统一抽象层,统一
get_facts / get_config / load_merge / commit / diff,让「对比一台设备的实际配置和预期配置」变得简单。 - pyntc:更高层统一 API,封装 Netmiko/NAPALM,简化连接与配置备份。
进阶NetBox 深度专章:把「网络意图」变成单一事实来源
在进阶篇里我们说过 SoT 是「设计蓝图」。这一章把 NetBox 拆开讲:它到底长什么样、怎么让 Ansible 自动读它、怎么从「改 NetBox」变成「改全网」。把 NetBox 想成网络的「户籍系统」——每台设备、每个接口、每个 IP 都在这里有正式户口,谁改了、改成啥,一查便知。版本基线:NetBox v4.x(PostgreSQL 15+ 自 v4.7 起),pynetbox v7.x。
1. 核心数据模型:一套映射真实网络的「对象语言」
NetBox 的精妙在于它的数据模型刻意贴合真实网络,而不是拍脑袋的表。理解这几组对象,你就理解了「网络意图」怎么被机器读懂:
- DCIM(数据中心基础设施):
Site(站点)→Rack(机柜)→Device(设备,关联型号、角色、平台、租户)→Interface(接口,可分 L2/L3、起 VLAN、绑 LAG、挂 IP)→Cable(连线,含走线 SVG)。 - IPAM(IP 地址管理):
Prefix(CIDR 段,可绑 VRF/VLAN/站点)、IP Address(挂在接口上)、VLAN/VLANGroup、VRF(含 import/export RT)、ASN。一句话:IP 不是孤立的数字,而是「挂在某个接口、属于某个 VLAN、在某个站点」的对象。 - Circuits(线路):
Provider→Circuit(运营商线路)→CircuitTermination(终结到某台设备的接口)。 - BGP:核心只建模
ASN;完整的 BGP 邻居/Peer Group/路由策略由社区插件netbox-bgp提供。
Device+Cable,地址规划是 Prefix+IP Address,VLAN 分配是 VLAN 挂在 Interface 上——全部是带关系的结构化数据,能被 API 一键拉出来渲染成配置。2. Config Context:按层级自动合并的「配置变量」
Config Context 是 NetBox 最被低估的杀手锏。它允许你把 NTP 服务器、Syslog 地址、BGP 参数等「变量」按「区域 / 站点 / 设备角色 / 平台」等维度挂载 JSON,并且按权重自动合并:低权重先合并,高权重覆盖冲突键,非冲突键保留。这样「全局默认 + 站点覆盖 + 单设备特例」三层变量天然成立。
// 区域级 weight=1000
{"ntp-servers":["172.16.10.22","172.16.10.33"],
"syslog-servers":["172.16.9.100","172.16.9.101"]}
// 单站点 weight=2000,覆盖 syslog
{"syslog-servers":["192.168.43.107"]}
// 渲染结果(自动合并后)
{"ntp-servers":["172.16.10.22","172.16.10.33"],
"syslog-servers":["192.168.43.107"]}
还可以用 ConfigContextProfile 配 JSON Schema 做数据形状校验,防止有人把变量写错格式。
3. REST + GraphQL API:让脚本自动「读蓝图」
NetBox 提供 REST(pynetbox 客户端)和 GraphQL(一次取嵌套数据)。下面两段是日常最高频的用法:
# pynetbox:拉某站点所有设备及其接口 IP
import pynetbox
nb = pynetbox.api('http://netbox', token='TOKEN')
for dev in nb.dcim.devices.filter(site='branch-sh', has_primary_ip=True):
print(dev.name, dev.primary_ip4)
for iface in dev.interfaces.all():
for ip in iface.ip_addresses.all():
print(" ", iface.name, ip.address)
# GraphQL:一次嵌套查出设备+接口+IP(requests 直连)
import requests
q = """query{device_list(filters:{site:{name:{exact:"branch-sh"}}}){
name primary_ip4{address}
interfaces{name ip_addresses{address}}}}"""
r = requests.post('http://netbox/graphql/',
headers={'Authorization': f'Token {TOKEN}'}, json={'query': q})
print(r.json()['data']['device_list'])
4. 把 NetBox 当 SoT 驱动自动化(两步走)
第一步:动态清单。用官方 netbox.netbox.nb_inventory 插件,Ansible 直接拿 NetBox 当实时设备源,还能按角色/站点自动分组,并把 Config Context 注入成主机变量:
plugin: netbox.netbox.nb_inventory
api_endpoint: http://netbox
validate_certs: false
config_context: true # 注入 context 变量
group_by: [device_roles, platforms]
compose:
ansible_network_os: platform.slug
query_filters:
- site: branch-sh
- has_primary_ip: true
第二步:读 NetBox 渲染配置。下面这段脚本从 NetBox 取设备与合并后的 Context,用 Jinja2 渲染出交换机配置,全流程无需登录任何设备:
import pynetbox, jinja2
nb = pynetbox.api('http://netbox', token='TOKEN')
dev = nb.dcim.devices.get(name='branch-sh-sw1')
ctx = dev.config_context # 已合并的 context
tpl = jinja2.Environment(loader=jinja2.FileSystemLoader('.')).get_template('switch.j2')
open(f'{dev.name}.cfg','w').write(tpl.render(host=dev, ctx=ctx))
# switch.j2
hostname {{ host.name }}
{% for s in ctx['ntp-servers'] %}ntp server {{ s }}
{% endfor %}{% for s in ctx['syslog-servers'] %}logging host {{ s }}
{% endfor %}{% for v in host.interfaces.all() %}{% if v.tagged_vlans %}! int {{ v.name }} -> {{ v.tagged_vlans | map(attribute='vid') | list }}
{% endif %}{% endfor %}
5. 关键插件与生态
- netbox-napalm-plugin:封装 NAPALM,做配置备份与「运行态 vs 期望态」合规校验。
- netbox-bgp:完整 BGP 建模(Sessions / Communities / Routing Policy / Prefix Lists)。
- Batfish:NetBox 本身无官方插件,通常由流水线里的 pybatfish 对导出的配置做预验(见 Batfish 专章)。
- Golden Config / SSoT:核心开源 NetBox 无原生 Golden Config,需自组 Oxidized + diff;商业 NetBox Assurance 提供漂移检测。SSoT 聚合多系统真相。
6. NetBox vs Nautobot:到底选哪个
| 能力 | NetBox | Nautobot |
|---|---|---|
| 配置合规 | ❌ 核心无(商业 Assurance) | ✅ Golden Config App |
| 自动化引擎 | Custom scripts + webhook | ✅ Jobs(内建调度/审批/钩子) |
| 扩展模型 | Plugins(边缘扩展) | ✅ Apps(一等公民) |
| Git 数据源 | 外部工具 | ✅ 原生(context/Jobs/模板存 Git) |
| Secrets | 社区插件 | ✅ 核心框架(接 Vault) |
| GraphQL | ✅ v3 起(附加) | ✅ 1.0 起 GraphQL-first |
选型建议:小团队 / 只想把网络登记清楚 → NetBox(社区最大、最轻、集成最广);中大型 / 要内置合规审计、把自动化「跑在 SoT 里」→ Nautobot(Golden Config 做预期配置生成与差异比对,Jobs 直接在平台内执行 Python)。
7. 完整小例子:分公司网络怎么建模与消费
在 NetBox 里建模:Site branch-sh → Rack R1 → 设备 branch-sh-core(role=core)、branch-sh-sw1(role=access);接口各配 10.1.0.1/31;VLAN USERS(100)、MGMT(99);Prefix 10.1.0.0/24 绑 VLAN;netbox-bgp 建 BGP Session(local_as 65001 ↔ 65002);Config Context(role=core)给 NTP/Syslog/BGP 变量。CI 里用 nb_inventory 按 role 分组,用 branch-sh.j2 渲染两台设备配置,经 NAPALM 推送并回读比对,偏差触发 webhook 告警。从此改网络只需改 SoT,设备状态永远跟 NetBox 一致。
进阶Ansible 进阶专章:从「能跑」到「能扛生产」
入门篇你用 Ansible 跑通了一台交换机。进阶篇要解决三个问题:环境可复现(执行环境)、配置严格幂等(资源模块)、能管上千台异构设备(角色分层 + 动态清单 + 规模化)。版本基线:ansible-core 2.19、Ansible 发行版 12–13、cisco.ios 11.5.0、netbox.netbox 3.23、ansible-builder 3.x、AWX 24.6。
1. 执行环境(EE):让「我本地能跑」=「生产也能跑」
网络集合依赖一堆特定 Python 库(pyats、ncclient、netaddr)和系统包,宿主机直装必然版本冲突。EE 把这些连同 ansible-core 一起打包成容器镜像,实现「开发 = 生产」的可移植、可审计运行环境。用 ansible-builder 定义:
---
version: 3
build_arg_defaults:
ANSIBLE_GALAXY_CLI_COLLECTION_OPTS: '--pre'
dependencies:
ansible_core: { package_pip: ansible-core==2.19 }
ansible_runner: { package_pip: ansible-runner }
galaxy:
collections: [cisco.ios, arista.eos, junipernetworks.junos, vyos.vyos, netbox.netbox]
python: [pyats, netaddr]
system: [bindep.txt]
images:
base_image: { name: registry.access.redhat.com/ubi9/ubi:latest }
additional_build_steps:
append_final:
- RUN echo "net-ee ready"
构建:ansible-builder build --tag my-org/net-ee:2.19 --container-runtime podman → podman push。日常用 ansible-navigator 在 EE 内运行:ansible-navigator run site.yml -i inventory --ee my-org/net-ee:2.19 -m stdout,或 ansible-navigator doc cisco.ios.ios_l3_interfaces 查模块文档、ansible-navigator inventory -i inventory --list 探索分组。
2. 资源模块(Resource Modules):严格幂等的正确姿势
新手最爱用 ios_config: lines:,但它对命令顺序/注释敏感、非严格幂等,跑两次可能结果不同,还会造成「配置漂移」假象。高手一律用资源模块:声明接口/Vlan/BGP 的「期望状态」,Ansible 自己算差量。下面这段跑两次结果完全一致:
- name: L2 access/trunk
cisco.ios.ios_l2_interfaces:
config:
- name: GigabitEthernet1/0/1
access: { vlan: 100 }
- name: GigabitEthernet1/0/24
trunk: { allowed_vlans: "100-110", native_vlan: 99 }
- name: L3 SVI
cisco.ios.ios_l3_interfaces:
config:
- name: Vlan100
ipv4: [ { address: "10.100.0.1/24" } ]
- name: BGP
cisco.ios.ios_bgp_global:
config:
as_number: "65001"
bgp: { router_id: { address: "10.100.0.1" } }
neighbors:
- neighbor: "10.0.0.2"
remote_as: "65002"
activate: true
ios_config: lines:;② cli_command/ios_command 只读、永不幂等;③ check_mode 下 ios_config 可能误报 changed;④ 网络无 setup facts,需显式 gather_facts: false 再 ios_facts;⑤ 只读任务加 changed_when: false,避免误触发 serial 熔断。3. 角色与 group_vars / host_vars:数据逻辑彻底分离
管上千台异构设备的秘诀:把「逻辑」放进 roles/,把「数据」放进 group_vars/ + host_vars/。层次是 group_vars/ios.yml(平台默认)→ group_vars/access_sw.yml(接入组)→ host_vars/sw-branch-01.yml(单设备覆盖)。新增设备只加一个 host_vars,playbook 一行不用改。
4. 动态清单:让 NetBox 直接当设备源
# inventory/netbox.yml
plugin: netbox.netbox.nb_inventory
api_endpoint: "{{ lookup('env','NETBOX_API') }}"
token: "{{ lookup('env','NETBOX_TOKEN') }}"
validate_certs: false
group_by: [device_roles, sites, platforms]
query_filters:
- status: active
compose:
ansible_host: primary_ip4
5. 规模化与可靠性:分批、容错、本地渲染
- serial 分批:
serial: 20避免一次推千台引发的网络风暴;配合max_fail_percentage控制熔断。 - delegate_to:
delegate_to: localhost+run_once: true做本地渲染/网管校验。 - 本地渲染再推送:先用
template在 localhost 生成期望配置,再推送,便于 diff 审计与--check。 - 错误处理:用
block/rescue/always在失败时回滚或告警。
block:
- cisco.ios.ios_config: { lines: [...] }
rescue:
- debug: { msg: "下发失败,触发回滚/告警" }
always:
- meta: { refresh_inventory: false }
6. AWX / Ansible Automation Platform:把执行层「平台化」
- 调度:为 Job Template 设周期/一次性调度,带时区与通知。
- 审批节点:Workflow 里放「审批节点」卡点,高危变更需人工点批准才继续下游。
- 凭据管理:集中存 SSH/enable 密码、Vault、云 API,注入不明文落盘;配合 RBAC 与 Job Slicing 实现千台并发。
7. 完整例子:分公司接入交换机批量部署
目标:给一批接入交换机批量部署「VLAN + 接入口 + 上联 Trunk + STP + SNMP + AAA/RADIUS」。
# group_vars/access_sw.yml(纯数据)
mgmt_vlan: 99
vlans:
- { vlan_id: 100, name: USERS }
- { vlan_id: 110, name: VOICE }
access_ports: [GigabitEthernet1/0/1, GigabitEthernet1/0/2]
uplink: GigabitEthernet1/0/24
snmp: { community: "{{ vault_snmp_ro }}", location: "Branch-01" }
aaa:
radius_servers: ["10.200.0.10"]
key: "{{ vault_radius_key }}"
stp_priority: 4096
# roles/access_switch/tasks/main.yml
- name: 创建 VLAN
cisco.ios.ios_vlans: { config: "{{ vlans }}", state: merged }
- name: 接入端口 L2
cisco.ios.ios_l2_interfaces:
config: [ { name: "{{ item }}", access: { vlan: 100 } } ]
loop: "{{ access_ports }}"
- name: 上联 Trunk
cisco.ios.ios_l2_interfaces:
config: [ { name: "{{ uplink }}", trunk: { native_vlan: "{{ mgmt_vlan }}", allowed_vlans: "100-110" } } ]
- name: STP 优先级
cisco.ios.ios_config:
lines: ["spanning-tree vlan 1-4094 priority {{ stp_priority }}"]
- name: SNMP
cisco.ios.ios_snmp_server:
config: { location: "{{ snmp.location }}", communities: [ { name: "{{ snmp.community }}", ro: true } ] }
- name: AAA/RADIUS
cisco.ios.ios_config:
lines:
- "aaa new-model"
- "radius-server host {{ aaa.radius_servers[0] }} key {{ aaa.key }}"
- "aaa authentication login default group radius local"
- name: 保存
cisco.ios.ios_config: { save_when: changed }
# site.yml
- hosts: access_sw
gather_facts: false
roles: [access_switch]
serial: 20
执行:ansible-navigator run site.yml -i inventory/netbox.yml --ee my-org/net-ee:2.19 -m stdout,生产前务必加 --check --diff 验证幂等。
进阶OpenTofu 专章:用「声明式」编排网络资源
OpenTofu 和 Terraform 负责的是 Ansible 不擅长的「资源生命周期」:创建/销毁 VPC、子网、专线、交换机上的 VLAN。它和 Ansible 是黄金搭档——一个管「要什么」,一个管「怎么配」。先厘清身世,再上代码。
1. OpenTofu 是谁:许可证与版本
| 项 | Terraform | OpenTofu |
|---|---|---|
| 许可证 | BUSL-1.1(2023-08 起,非 OSI 开源) | MPL-2.0(从 TF 1.5.x 分叉) |
| 治理 | HashiCorp(IBM) | Linux 基金会,CNCF Sandbox(2025-04 加入) |
| 版本 | 1.14 GA(2025-11) | 1.11.0(2025-12)、1.12.0(2026-05) |
OpenTofu 1.11 亮点:ephemeral resources / write-only 属性(口令只存内存、不进 state)、enabled meta-argument。1.12 亮点:prevent_destroy 可动态取值、-json-into 同时出人读与机读产物、provider 并发安装。从 Terraform 迁移只需 5 步(备份 state → 装 tofu → tofu init → tofu plan 应为 No changes → tofu apply),可回滚。
2. 核心概念与最小 HCL
HCL 声明式:provider(与 API 对话的插件)→ resource(受管对象)/ data source(只读查询)→ state(真实资源与配置的映射,含锁)→ plan(预演 diff)/ apply → module(复用单元)。远端 state 用 S3 backend + 原生锁:
terraform {
required_version = ">= 1.11.0"
required_providers {
aws = { source = "hashicorp/aws", version = "~> 6.0" }
}
backend "s3" {
bucket = "net-tfstate"
key = "prod/network.tfstate"
region = "ap-east-1"
use_lockfile = true # OpenTofu 原生 S3 锁(建议开桶版本控制)
}
}
variable "vpc_cidr" { type = string, default = "10.20.0.0/16" }
provider "aws" { region = "ap-east-1" }
resource "aws_vpc" "core" {
cidr_block = var.vpc_cidr
tags = { Name = "core-vpc" }
}
output "vpc_id" { value = aws_vpc.core.id }
3. 网络相关 provider 与真实示例
网络 provider 越来越丰富:CiscoDevNet/iosxe(交换机 VLAN/接口,NETCONF)、CiscoDevNet/iosxr、CiscoDevNet/nxos、vmware/nsxt、云网络 hashicorp/aws、azurerm、google、专线 equinix/equinix、megaport/megaport。
# 示例 A:IOS-XE 建 VLAN + trunk 口(单 provider 管多台)
provider "iosxe" {
username = var.dev_user
password = var.dev_pass
devices = [
{ name = "leaf-01", host = "10.1.1.11" },
{ name = "leaf-02", host = "10.1.1.12" },
]
}
locals { vlans = { 100 = "WEB", 101 = "APP", 102 = "DB" } }
resource "iosxe_vlan" "v" {
for_each = local.vlans
device = "leaf-01"
vlan_id = each.key
name = each.value
shutdown = false
}
resource "iosxe_interface_switchport" "uplink" {
device = "leaf-01"
type = "TenGigabitEthernet"
name = "1/0/1"
mode_trunk = true
nonegotiate = true
trunk_allowed_vlans = join(",", keys(local.vlans))
trunk_native_vlan = 999
}
# 示例 B:AWS VPC + 子网 + 路由表(多 AZ)
resource "aws_subnet" "private" {
for_each = { "a" = "10.20.1.0/24", "b" = "10.20.2.0/24" }
vpc_id = aws_vpc.core.id
cidr_block = each.value
availability_zone = "ap-east-1${each.key}"
}
resource "aws_route_table" "private" { vpc_id = aws_vpc.core.id }
resource "aws_route" "to_dc" {
route_table_id = aws_route_table.private.id
destination_cidr_block = "192.168.0.0/16"
transit_gateway_id = var.tgw_id
}
resource "aws_security_group" "app" {
vpc_id = aws_vpc.core.id
ingress { from_port = 443, to_port = 443, protocol = "tcp", cidr_blocks = [var.vpc_cidr] }
}
# 示例 C:NSX-T Overlay 段
data "nsxt_policy_transport_zone" "overlay" { display_name = "TZ-Overlay" }
resource "nsxt_policy_segment" "app" {
display_name = "seg-app"
transport_zone_path = data.nsxt_policy_transport_zone.overlay.path
connectivity_path = data.nsxt_policy_tier1_gateway.t1.path
subnet { cidr = "172.16.10.1/24", dhcp_ranges = ["172.16.10.100-172.16.10.200"] }
}
4. 模块化:把「网络蓝图」拆成可复用 module
# sites/sh-dc1/main.tf —— 一站一目录,各自 state
module "fabric" {
source = "../../modules/spine_leaf"
for_each = {
sh-dc1 = { asn_base = 65100, loopback_cidr = "10.0.0.0/24", p2p_cidr = "10.1.0.0/22" }
bj-dc2 = { asn_base = 65200, loopback_cidr = "10.0.1.0/24", p2p_cidr = "10.1.4.0/22" }
}
site = each.key
spines = 2
leaves = 4
asn_base = each.value.asn_base
loopback_cidr = each.value.loopback_cidr
p2p_cidr = each.value.p2p_cidr
lifecycle { enabled = var.build_fabric } # OpenTofu 1.11 新语法
}
5. OpenTofu 在 CI/CD 里:plan 即「评审单」
# GitLab CI
image: ghcr.io/opentofu/opentofu:1.12.0
variables: { TF_IN_AUTOMATION: "true", TF_INPUT: "0" }
stages: [validate, plan, apply]
before_script: [ "cd $SITE_DIR", "tofu init -input=false -lock-timeout=120s" ]
validate: { stage: validate, script: ["tofu fmt -check -recursive", "tofu validate"] }
plan:
stage: plan
script:
- tofu plan -out=tfplan -lock-timeout=120s -parallelism=10 -detailed-exitcode -json-into=plan.json || [ $? -eq 2 ]
- tofu show -no-color tfplan > plan.txt
artifacts: { paths: [tfplan, plan.txt, plan.json], expire_in: 1 week }
apply:
stage: apply
when: manual
environment: { name: prod }
script: ["tofu apply -input=false -auto-approve tfplan"]
rules: [{ if: '$CI_COMMIT_BRANCH == "main"' }]
要点:plan 产物即评审对象(贴 MR 评论、用 OPA/Conftest 做策略校验);-detailed-exitcode(0 无变更 / 2 有变更)驱动流水线分支;state 一律远端 + 锁;对 NETCONF 设备把 -parallelism 调小。GitHub Actions 写法同构(用 opentofu/setup-opentofu + Environment protection rules 做审批)。
6. OpenTofu vs Ansible:各管一摊
| 维度 | OpenTofu | Ansible |
|---|---|---|
| 模型 | 声明式 + 状态文件,有 drift 检测 | 过程式/幂等任务,无全局 state |
| 强项 | 创建/销毁资源对象(VPC、子网、专线、API 对象) | day-2 配置、模板渲染、巡检、多步编排 |
| 弱项 | 复杂时序/命令式操作 | 生命周期与依赖图、销毁即回收 |
7. 完整例子:spine-leaf underlay(loopback + P2P + eBGP)
# modules/spine_leaf/main.tf(节选)
locals {
spine_ids = range(1, var.spines + 1)
leaf_ids = range(1, var.leaves + 1)
devices = concat([for s in local.spine_ids : "spine-0${s}"],
[for l in local.leaf_ids : "leaf-0${l}"])
asn = merge({ for s in local.spine_ids : "spine-0${s}" => var.asn_base },
{ for l in local.leaf_ids : "leaf-0${l}" => var.asn_base + l })
loopback = { for i, d in local.devices : d => cidrhost(var.loopback_cidr, i + 1) }
links = { for pair in setproduct(local.spine_ids, local.leaf_ids) :
"s${pair[0]}-l${pair[1]}" => {
spine = "spine-0${pair[0]}", leaf = "leaf-0${pair[1]}"
net = cidrsubnet(var.p2p_cidr, 7, (pair[0]-1)*var.leaves + pair[1]-1)
port = pair[1] } }
}
resource "iosxe_interface_loopback" "lo0" {
for_each = local.loopback
device = each.key
name = "0"
description = "${var.site} router-id"
ipv4_address = each.value
ipv4_address_mask = "255.255.255.255"
}
resource "iosxe_interface_ethernet" "p2p" {
for_each = merge(
{ for k, v in local.links : "${k}-spine" => { dev = v.spine, name = "1/0/${v.port}", ip = cidrhost(v.net, 0), peer = v.leaf } },
{ for k, v in local.links : "${k}-leaf" => { dev = v.leaf, name = "1/0/1", ip = cidrhost(v.net, 1), peer = v.spine } })
device = each.value.dev
type = "TenGigabitEthernet"
name = each.value.name
description = "to ${each.value.peer}"
switchport = false
shutdown = false
ipv4_address = each.value.ip
ipv4_address_mask = "255.255.255.254" # /31
}
resource "iosxe_bgp_neighbor" "underlay" {
for_each = merge(
{ for k, v in local.links : "${k}-spine" => { dev = v.spine, ip = cidrhost(v.net, 1), remote = local.asn[v.leaf], asn = local.asn[v.spine] } },
{ for k, v in local.links : "${k}-leaf" => { dev = v.leaf, ip = cidrhost(v.net, 0), remote = local.asn[v.spine], asn = local.asn[v.leaf] } })
device = each.value.dev
asn = tostring(each.value.asn)
ip = each.value.ip
remote_as = tostring(each.value.remote)
fall_over_bfd = true
timers_keepalive_interval = 3
timers_holdtime = 9
depends_on = [iosxe_interface_ethernet.p2p]
}
output "loopbacks" { value = local.loopback } # 供 overlay 模块 / Ansible inventory 使用
auto_commit、delete_mode 行为,避免 destroy 误删整段 YANG;生产核心设备用 1.12 的 prevent_destroy = var.is_prod 防误删;设备口令用 1.11 的 write-only 属性避免落盘 state。高手Batfish 验证专章:合并前就能「跑」一遍网络
Ansible 能帮你下发配置,但它无法回答「改完之后网络还通不通」。Batfish 填补的就是这个缺口:它不登录任何设备,只离线解析你的配置文件,在内存里把整张网络「跑」起来,然后用断言回答可达性、BGP 邻居、ACL、环路等问题。版本基线:Batfish 服务 + pybatfish 0.36.0。
1. 核心能力:一套「厂商中立模型」
Batfish 解析 Cisco IOS/XR/NX-OS、Juniper、Arista、Cumulus、SONiC、F5、Palo Alto 等配置,构建厂商无关模型(VI model),再用 Question 查询行为。主要 Question:
- 路由/会话:
bgpSessionStatus、ospfSessionStatus、bgpEdges - 可达性/转发:
reachability、traceroute、loop、fibRoutes、forwarding - ACL/策略:
aclReachability、filterLineReachability、aclExplanations - 配置合规:
undefinedReferences、unusedStructures、assert
2. pybatfish 骨架
from pybatfish.client.session import Session
from pybatfish.question import bfq
from pybatfish.datamodel.flow import PathConstraints
bf = Session(host="localhost") # 连 Batfish 服务(docker: batfish/allinone)
bf.set_network("dc1"); bf.init_snapshot("/path/snapshot", name="s1", overwrite=True)
3. 四个真实场景:CI 里一行 assert 拦住事故
# 场景1:某流量从 nodeA 必须能到 nodeB
ans = bfq.reachability(
pathConstraints=PathConstraints(startLocation="nodeA", endLocation="nodeB"),
actions="SUCCESS").answer().frame()
assert not ans.empty, "A→B 不可达,拦截合并"
# 场景2:全部 BGP / OSPF 邻居必须建立
bgp = bfq.bgpSessionStatus().answer().frame()
assert bgp["Established_Status"].isin(["ESTABLISHED"]).all(), "存在未建立的 BGP 邻居"
assert bfq.ospfSessionStatus().answer().frame()["Session_Status"].eq("FULL").all()
# 场景3:ACL 不会误阻断关键流量(检测永远不可达的行)
acl = bfq.aclReachability().answer().frame()
assert acl[acl["Unreachable_Lines"] != ""].empty, "ACL 含永远不可达行,可能误杀"
# 场景4:无转发环路 + 无未定义引用
assert bfq.loop().answer().frame().empty, "网络存在转发环路"
assert bfq.undefinedReferences().answer().frame().empty, "配置引用了不存在的 ACL/route-map"
4. Batfish 在 CI/CD 里的位置:合并前预演
MR/PR 触发 → 渲染配置 → Batfish 解析并跑断言 → 失败即阻断。它与 NVIDIA Air 数字孪生互补:Batfish 做逻辑/策略正确性(离线、秒级、零成本),Air 做运行时仿真(真正启动 Cumulus/SONiC 跑 show/ping)。两者都是「质量门禁」的前置关卡,一个省钱、一个更真。
高手六、CI/CD 流水线:让机器人替你上线
进阶篇你还是会手动敲 ansible-playbook。高手的做法是:把这条命令塞进流水线,每次提交自动跑。核心思想八个字——「先验证,后上线」。用离线仿真替代「登设备试错」,把事故挡在合并之前。
6.1 流水线的标准阶段
ansible --check --diff 只比对不下发,预览逐行变更。when: manual 作为人工卡点(审批门禁)。6.2 一段可直接抄的 GitLab CI
stages: [lint, render, validate, deploy, verify]
variables: { ANSIBLE_FORCE_COLOR: "true" }
yaml_lint:
stage: lint
image: cytopia/yamllint
script: [yamllint -c .yamllint.yml ., ansible-lint || true]
render_configs:
stage: render
image: python:3.11
script: [pip install jinja2 netutils, python render.py -i inventory.yml -t templates/ -o build/]
artifacts: { paths: [build/] }
batfish_check: # 离线仿真,MR 触发
stage: validate
image: python:3.11
services: [{ name: batfish/allinone }]
script: [pip install pybatfish==0.36.0, python tests/batfish_validate.py]
rules: [{ if: $CI_PIPELINE_SOURCE == "merge_request_event" }]
ansible_dryrun: # 干跑,只对比不下发
stage: validate
image: ansible-net:latest
script: [ansible-playbook -i inventory.yml site.yml --check --diff]
rules: [{ if: $CI_PIPELINE_SOURCE == "merge_request_event" }]
deploy_canary: # 灰度,manual = 审批门禁
stage: deploy
script: [ansible-playbook -i canary.yml site.yml]
when: manual
rules: [{ if: $CI_COMMIT_BRANCH == "main" }]
deploy_prod:
stage: deploy
script: [ansible-playbook -i prod.yml site.yml]
when: manual
environment: { name: production }
post_verify: # 部署后校验
stage: verify
script: [pytest tests/test_post_deploy.py -q]
6.3 防事故神器:测试与校验工具
Batfish(离线仿真)
AWS 维护,不需要真实设备就能解析多厂商配置,验证可达性、BGP/OSPF 会话、ACL 语义、转发环路。合并前发现 90% 隐患。最小用法:起 batfish/allinone 容器 → init_snapshot → 断言。
from pybatfish.client.session import Session
from pybatfish.client.asserts import (
assert_no_undefined_references,
assert_no_forwarding_loops,
assert_no_unestablished_bgp_sessions)
bf = Session(host="batfish")
bf.set_network("net")
bf.init_snapshot("build", name="candidate", overwrite=True)
assert_no_undefined_references()
assert_no_forwarding_loops()
assert_no_unestablished_bgp_sessions()
pyATS / Genie(状态验证)
Cisco 内部千万级 CI 框架。Genie 提供厂商无关解析器,最适合「部署前学基线、部署后比差异」。配 testbed.yaml 定义设备连接,用 genie run 跑现成用例。
# test_post_deploy.py(pytest 写法)
def test_bgp_up(conn):
out = conn.execute("show ip bgp summary")
assert "Established" in out
def test_uplink_up(conn):
out = conn.execute("show interface Gi0/1")
assert "line protocol is up" in out
再加上 ansible --check --diff 的逐行 diff、NAPALM 的 intended-vs-actual 合规对比、SuzieQ 的持续可观测校验,就构成了一道道「防事故闸门」。GitHub Actions 的写法与此同构,只是把 stages 写成 jobs + steps,思路完全一致。
高手业界实践专章:NVIDIA 与 Cisco 是怎么做的
前面讲的都是「工具怎么用」。这一章看两家头部公司真实的网络自动化 CI/CD 落地,把抽象方法论变成可参照的范本。你会发现:无论多牛的团队,骨架都逃不出「Git 唯一入口 + 自动校验 + 审批门禁 + 质量门禁」。
1. NVIDIA:用数字孪生把网络变更「先彩排再上线」(2025)
NVIDIA 在 2025 年的技术博客里公开了他们用 GitLab CI 管理数据中心网络的完整做法,核心是把 Cumulus Linux + NVIDIA Air 数字孪生接入 CI/CD。收益是四点:速度、质量(自动检查减错)、可靠性、可扩展性。
他们的开发团队真实流水线分七步:
工程上他们把代码拆成多仓库:AIR 自动化代码库 + 网络配置渲染库(Jinja2 模板)分离。Air 提供 REST API(底层)+ Python SDK(封装),可启动/注入/验证/销毁实验室。关键经验:把 CI/CD 变成「部署生产的唯一途径」,任何绕过流水线的手工改动都视为违规。
2. Cisco:Network Automation Delivery Model(NADM)官方交付模型
Cisco DevNet 给出的官方 CI/CD 架构更强调「组件化交付」:
- Git 作为唯一入口:所有源码入库,每次 commit 触发自动构建。
- 版本库 + Artifactory + Issue 系统(JIRA)+ 自动构建测试系统 Jenkins:Artifactory 存二进制/包,保证「测的是同一份、部署的也是同一份」。
- 分阶段流水线:组件先在各自 CI 独立测试,再汇入主部署管线做集成/E2E;单组件升级(新 NED/NSO 版本)分钟级验证。
- 质量门禁:CI/CD 是部署到生产的唯一途径;「完成定义」必须含通过 CI/CD 与测试;管线软件视作关键基础设施,专人维护。
- 与 NSO / pyATS 的关系:NSO 作为持续演进项目,Function Pack、NED、NSO 版本任一变更即触发 Jenkins 集成测试;pyATS 做设备级实测,与 Batfish 的离线验证形成「逻辑层 + 实测层」双层校验。
3. 其他公司的公开经验(简)
- Meta/Facebook:NetNORAD 主动探测丢包,并发 NSDI'22 论文讲 Change Cube 意图驱动遥测,强调变更追踪与意图分层验证。
- Google:Network as Code,网络配置纳入版本与流水线,CI/CD 参考架构见 GKE 文档。
- Cloudflare:强调小步增量发布、边缘自动化与一键回滚降低风险。
4. NVIDIA vs Cisco 对比 + 普通团队可抄清单
| 维度 | NVIDIA | Cisco |
|---|---|---|
| 仿真 | NVIDIA Air 数字孪生(运行时) | NSO 集成 + pyATS 实测 |
| 配置生成 | Jinja2 模板渲染 | NSO Function Pack / 服务模型 |
| 编排 | GitLab CI | Jenkins + Artifactory |
| 入口 | 多仓库 Git | Git 唯一入口 |
| 验证侧重 | 拓扑/布线/可达性仿真 | NSO 组件集成 / E2E |
2) CI 第一步:YAML/Jinja2 语法 + Batfish
init_snapshot 解析,捕获 undefinedReferences;3) 加断言:BGP/OSPF 全建立、关键路径
reachability、aclReachability、无 loop;4) 有预算再上数字孪生(Air/容器仿真)跑 show/ping 做运行时验证;
5) 构建产物进 Artifactory,部署经同一管线,设「唯一入口 + 质量门禁」;
6) 专人维护管线,把「通过验证」写入变更完成定义。
落地七、从 0 到 1 演进路线(别想一步登天)
很多团队一上来就想搞 GitOps,结果半年没落地。正确姿势是逐级演进,每一级都解决上一级的痛点,再自然进入下一级。
| 阶段 | 特征 | 痛点(催生下一跃) | 跃迁条件 |
|---|---|---|---|
| ① 手动 CLI | 登录设备手敲命令 | 易错、不可追溯、不可复现 | 事故频发,想「可重复」 |
| ② 脚本化 | Python/Expect 批量执行 | 脚本无版本控制、难维护 | 需要审计与回滚 |
| ③ 模板化 | Jinja2 + 变量生成配置 | 模板与数据仍靠人工渲染 | 引入 Git 与 SoT |
| ④ IaC | Ansible/Terraform 声明式 | 缺自动化校验,仍手敲执行 | 接入 CI 做 lint/校验 |
| ⑤ CI/CD | MR + 自动校验 + 灰度 + 回滚 | 仍有人工触发部署 | 信任自动化、测试覆盖够 |
| ⑥ GitOps | 合并即部署,仓库即生产 | 需强审批与可观测 | 成熟测试网 + 变更窗口 |
一个可参照的真实落地案例
NVIDIA 在 2025 年的技术博客里披露:他们用 GitLab CI 管理数据中心网络——工程师提交 Jinja2 模板与变量,流水线自动渲染配置、跑校验,评审通过后由 Ansible 下发,并用数字孪生(NVIDIA Air)做预演。核心经验是:把 CI/CD 变成「部署生产的唯一途径」,任何绕过流水线的手工改动都被视为违规。Cisco DevNet 也给出同样的「网络自动化交付模型」:Git 是单一入口,CI 是质量门禁,CD 是受控执行。
精进八、工程实践与避坑清单
分支与审批
main分支受保护,改动走 feature 分支 → MR。- MR 关联工单与变更窗口;CI 全绿 + 至少 1 人批准才放行。
- 高危变更双人复核,
when: manual做人工卡点。
密钥管理
- 设备口令 / API Token 存 HashiCorp Vault 或 CI Secret,运行时注入。
- 严禁硬编码进仓库;用最小权限账号。
- 变量文件里的敏感项用
var.ios_password引用。
回滚方案
- Ansible/Terraform 天然幂等,重放旧 commit 即可回退。
- Git
revert旧提交;设备侧用commit confirmed 600自动回滚。 - 预演失败即阻断,绝不带病上线。
变更窗口与幂等
- 流水线只在变更窗口期放行
deploy_prod,避开业务高峰。 - 幂等性是安全重试/回滚的基石:重复执行结果一致。
- 下发后接监控做健康检查,异常自动告警。
shutdown 或 no shutdown),别留歧义。路径九、分级学习路线与资源
按你当前水平对号入座,循序渐进。别贪多,先把一层的工具跑顺,再进下一层。
打地基(1–2 周)
Git 基础(commit/branch/MR)、Linux 命令行、Python 基础、Ansible 跑通一台交换机、Jinja2 模板。目标:手动改文本 → 自动下发一台设备。
建体系(3–6 周)
部署 NetBox 登记网络、Ansible 集合与 napalm、Nornir 并发、OpenTofu 编排 VLAN、变量与模板分层。目标:数据驱动、多设备管理。
上流水线(6–12 周)
GitLab CI/GitHub Actions 流水线、Batfish 仿真、pyATS/pytest 校验、审批门禁、回滚与密钥管理、灰度发布。目标:合并即受控上线。
推荐官方资料(都附链接,按需深挖)
- Ansible 网络:cisco.ios.ios_config 模块文档
- Nornir:nornir.readthedocs.io
- OpenTofu:opentofu.org(含 1.11 发布说明)
- NetBox:NetBox 文档 v4.6;Nautobot:docs.nautobot.com
- Batfish:batfish.org + pybatfish 断言
- pyATS/Genie:Cisco DevNet
- CI/CD 模型:Cisco DevNet CI/CD 交付模型、NVIDIA 2025 实践文
- GitHub Actions:官方中文文档
附录十、术语表(速查)
| 术语 | 含义 |
|---|---|
| IaC | Infrastructure as Code,基础设施即代码,把网络状态写成文本由机器管理。 |
| CI / CD | 持续集成 / 持续部署,提交即自动质检、受控上线。 |
| SoT | Single Source of Truth,单一事实来源,权威记录网络意图的系统(NetBox/Nautobot)。 |
| GitOps | 以 Git 仓库为生产唯一事实,合并即部署、状态自动纠偏。 |
| Jinja2 | Python 模板引擎,用变量渲染出设备配置。 |
| network_cli | Ansible 经 SSH/CLI 连接网络设备的方式。 |
| dry-run / --check | 预演模式,只计算差量并打印,不下发。 |
| 幂等性 | 重复执行结果一致,是安全重试与回滚的基础。 |
| gNMI / NETCONF | 现代网络设备可编程接口(取代纯 CLI)。 |
| OpenTofu | Terraform 的开源分支(LF/CNCF),MPL-2.0 许可。 |