晚上十点通知群里弹出一条消息核心机房的一台汇聚设备需要调整接口配置新增三个业务VLAN的透传同时把某个下行端口的速率从千兆改成万兆自协商。我打开终端先翻出设备运维台账再打开终端工具登录设备、进配置模式、敲命令、核对回显、保存配置。一轮下来二十分钟过去了如果还有第二台、第三台设备要做同样的操作那就得把同样的命令再敲一遍。这种日子过了小半年直到我开始认真用Python做网络设备自动配置才算是把自己从重复劳动里解放出来。这篇文章我不会讲什么高深的理论就结合我自己实际跑过的脚本和踩过的坑把从零开始做网络设备自动配置的完整路径梳理一遍。包括工具选型、环境准备、备份巡检、批量变更、回滚预案以及几个真正让我头疼过的问题。不管你是刚入行的网络工程师还是已经写了一段时间脚本但总觉得哪里不对这篇应该都能对得上胃口。1. 为什么说自动配置的难点从来不是写脚本很多人一听到用Python自动配置网络设备第一反应就是我要学编程。但实际上把脚本写出来只是最表层的一步真正难的在于想清楚你为什么要自动化、自动化的边界在哪里、出了问题怎么收场。1.1 手工配置的三个翻车现场先说说我经历过的手工配置翻车现场你应该也见过类似的情况。第一个翻车现场是命令打错。不是每个人都有完美的打字习惯特别是在连续配置几十台设备、每台设备要敲十几条命令的情况下很容易出现某条命令漏了个参数或者把switchport access vlan 10打成了switchport access vlan 1。这种错误在单台设备上不显眼但如果批量做了后面排查起来就是一场灾难。第二个翻车现场是配置漏项。设备配置通常不是一条命令就能搞定的往往是进入接口、设置模式、划分VLAN、配置描述、开启接口这一连串动作。手工操作的时候做到第五台设备可能就忘了给接口加描述或者忘了保存配置。这种漏项平时看不出来等设备重启后配置丢失或者排障时发现接口状态不对整个人都是崩溃的。第三个翻车现场是变更不可控。同一批设备、同一个变更需求不同的人去做可能给出完全不同的配置方式。有人说接口描述用中文有人说用英文有人说要加前缀有人说不用。结果就是设备配置风格五花八门后面运维排障的人根本不知道某些接口当初为什么要这么配。1.2 自动化到底改变了什么我说的自动化不是让你写个脚本去模拟手工过程而是把人从命令级操作中抽离出来去定义设备应该变成什么样。传统做法是登录设备 → 进入接口 → 敲命令 → 核对回显 → 保存配置自动化的做法是写一份配置模板 → 写一份设备清单 → 脚本自动登录设备 → 下发配置 → 校验结果 → 输出报告这两者最大的区别在于传统做法是人去适配设备每次操作都要心里默念下一步该敲什么自动化把操作逻辑固化成了代码同样的逻辑跑一百台设备和跑一台设备几乎不增加额外人力。1.3 哪些配置环节最适合先用脚本接管不是所有网络操作都适合立刻自动化。以我的经验下面这几类场景是收益最大、风险也相对可控的配置备份定期把所有设备的配置抓回来存档即使脚本有bug最坏的结果也就是备份失败不会影响现网业务。批量巡检检查接口状态、CPU利用率、内存使用率、VLAN配置一致性这些操作是只读的安全性最高。标准化变更比如批量创建VLAN、批量配置接口归属、批量下发ACL只要模板设计得合理变更结果可控。版本升级前的配置导出升级前的配置文件留档方便出问题后回退。先把这个思路理清楚再动手写代码你会少走很多弯路。我见过不少同行一上来就想写一个全自动配置平台结果光是处理各种厂商的命令差异就耗光了所有热情。2. 工具链怎么选除了Netmiko我还用了什么确定要搞自动化之后第一个问题就是选工具。这个领域里Python生态已经非常成熟了但很多人会因为选择太多而无从下手。我自己的选型经历大概是这样的先用了最底层的Paramiko后来换成Netmiko再后来引入了NAPALM和Nornir最后发现不同场景用不同工具才是正解。2.1 为什么建议跳过纯Paramiko时代Paramiko是一个Python实现的SSH协议库它可以让你通过SSH连接到任意设备并执行命令。很多网工的第一版脚本都是Paramiko写的我也一样。它的优点是灵活缺点同样是灵活——因为太底层了所以连等待设备输出完这种事情都要自己处理。看一段Paramiko的经典代码import paramiko import time ssh paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname192.0.2.10, usernameadmin, passwordpassword) # 这里需要手工等待一段时间否则命令还没执行完就开始读取了 time.sleep(1) stdin, stdout, stderr ssh.exec_command(display current-configuration) time.sleep(3) output stdout.read().decode() print(output) ssh.close()这段代码有两个让人头疼的问题一个是time.sleep()的等待时间全靠猜网络慢的时候输出没读完网络快的时候又白等半天另一个是设备交互式的命令比如进入系统视图逐条配置用exec_command很难优雅处理。我当时用Paramiko写了个配置脚本每次执行都要手动调整sleep时间还经常偶发读取不完整。直到同事推荐我试试Netmiko我才意识到原来这些坑早就有人填平了。2.2 Netmiko是大多数场景的首选Netmiko是基于Paramiko封装的一个高级库它最大的贡献就是解决了和设备交互这件事。它会自动检测设备的命令提示符发送命令后等到输出结束再返回完全不需要time.sleep去猜时间。用Netmiko连接设备并下发配置代码大概是这样的from netmiko import ConnectHandler device { device_type: huawei, host: 192.0.2.10, username: admin, password: password, port: 22, } with ConnectHandler(**device) as conn: output conn.send_command(display interface brief) print(output)短短几行就把设备的登录、交互、读取全部搞定了。Netmiko内置了非常多的设备类型常见的厂商设备基本都支持。send_command用于执行只读命令send_config_set用于下发配置。比如批量配置接口config_commands [ interface GigabitEthernet0/0/1, port link-type access, port default vlan 100, description to-core-switch, ] output conn.send_config_set(config_commands)Netmiko的另一个优势是它把一些通用行为处理得非常好比如进入配置模式的命令、退出配置模式的命令、保存配置的命令。你只需要告诉它厂商类型这些细节它都会自动处理。2.3 NAPALM当你想用统一接口操作不同厂商设备时Netmiko解决了怎么和设备交互但还没有解决怎么屏蔽厂商差异。不同厂商的配置命令差别非常大展示格式也不一样如果你要给跨厂商的网络做统一自动化逐台写命令适配会让代码非常臃肿。NAPALM则提供了一个统一的操作接口。它把获取接口信息、获取配置、备份配置、下发配置等能力都封装好了你只需要指定设备的厂商驱动就可以用同一套代码操作不同厂商的设备。from napalm import get_network_driver driver get_network_driver(ios) device driver( hostname192.0.2.10, usernameadmin, passwordpassword, optional_args{port: 22}, ) device.open() print(device.get_interfaces()) print(device.get_config(retrieverunning)) device.close()NAPALM还支持用结构化数据的方式来理解设备状态。比如get_interfaces()返回的不再是一堆终端文本而是Python字典你可以直接通过interfaces[GigabitEthernet0/0/1][is_up]判断接口状态不用去做文本解析。我在做跨厂商的配置一致性巡检时NAPALM帮了大忙。以前我需要为每种厂商各自写一套正则表达式来解析输出用了NAPALM之后解析逻辑是统一的代码量直接降了一个量级。2.4 Nornir与Ansible属于批量编排的下一层Netmiko和NAPALM都是单台设备的维度虽然你可以写个for循环连多台设备但并发控制、失败处理、结果汇总都需要自己实现。这时候就该批量编排工具上场了。Ansible是业界用得最多的自动化工具它通过playbook定义对哪些设备做什么操作本身是声明式的对不熟悉编程的人更友好。但它要求控制端有Python环境、被管设备支持SSH协议而且它的执行模型是推送式的和网络设备的交互体验比较独特。Nornir则是一个纯Python的自动化框架你可以直接在用Python写逻辑的同时享受批量并发、设备清单管理、结果追踪这些能力。它比Ansible更程序员友好不需要学习YAML的playbook语法所有逻辑都是Python代码。我个人在需要写复杂处理逻辑的场景会优先选Nornir在纯批量化下发且逻辑简单的场景用Ansible的现成模块或者直接用Netmiko循环。2.5 我实际使用的工具组合如果你问我个人目前的主力配置大概是这样的场景工具单台设备快速操作、日常排障Netmiko跨厂商配置一致性巡检NAPALM批量配置下发、并发执行Nornir定时备份、定期巡检Cron Netmiko 脚本图形化展示、多人协作基于Flask封装的内部小平台这个组合不是一步到位的而是踩了无数坑之后沉淀下来的。我的建议是你不需要一步到位把所有工具都学会先从Netmiko入手跑通一个备份脚本再逐步引入NAPALM做结构化采集最后再考虑批量编排。3. 拿到设备运维权限后先从备份和只读巡检做起工具选好了接下来要解决的是拿什么练手的问题。我的建议非常明确不要一上来就写批量配置下发脚本先从备份和只读巡检开始。原因很简单只读操作即使写错了也不会影响现网你可以在安全的前提下熟悉整个工具链。3.1 环境准备Python虚拟环境与依赖安装先创建独立的Python虚拟环境避免把系统Python环境搞乱mkdir network_automation cd network_automation python3 -m venv venv source venv/bin/activate然后安装核心依赖pip install netmiko napalm nornir这里多说一句强烈建议用虚拟环境。我早期踩过一个坑直接在系统Python环境里装了一堆库后来某个库版本升级把另一个库的依赖搞坏了导致好多脚本跑不起来。虚拟环境相当于给每个项目一个独立的箱子互不干扰。3.2 设备侧需要准备什么脚本要能连上设备设备侧得满足几个基本条件开启SSH服务现在基本没有设备不支持SSH了但默认端口和认证方式可能不同最好统一规划。准备专用账号给自动化脚本单独建一个账号权限按需分配。不要用最高权限账号跑巡检万一脚本有bug影响面会小很多。确认命令回显格式不同厂商的display或show命令输出格式不同要先手工跑一遍心里有数。另外我建议把设备密码放到独立配置文件中不要硬编码在脚本里。最简单的做法是放到环境变量或者一个本地配置文件中然后给文件设置严格权限。3.3 一个能反复执行的备份脚本这是我最推荐的第一个自动化脚本全设备配置备份。逻辑非常简单遍历设备清单逐台连接并抓取配置按日期保存到本地目录。from netmiko import ConnectHandler from datetime import datetime import os DEVICES [ { device_type: huawei, host: 192.0.2.10, username: admin, password: password, name: core-sw-01, }, { device_type: huawei, host: 192.0.2.11, username: admin, password: password, name: core-sw-02, }, ] save_dir fbackup/{datetime.now().strftime(%Y%m%d)} os.makedirs(save_dir, exist_okTrue) for device in DEVICES: try: with ConnectHandler(**device) as conn: config conn.send_command(display current-configuration) file_path os.path.join(save_dir, f{device[name]}.cfg) with open(file_path, w, encodingutf-8) as f: f.write(config) print(f{device[name]} 备份成功) except Exception as e: print(f{device[name]} 备份失败: {e})这个脚本很简单但已经涵盖了自动化的核心要素设备清单管理、连接处理、命令执行、结果保存、异常捕获。你可以在此基础上加日志、加邮件告警、加失败重试。3.4 只读巡检从文本输出到结构化状态备份配置只是第一步更实用的巡检脚本是去检查设备状态。比如我想检查所有设备的接口是否有哪些处于Down状态、CPU是否过高。Netmiko执行命令后返回的是文本要从中提取关键信息最常用的方式是textfsm模板解析或者直接用正则表达式。以接口状态为例from netmiko import ConnectHandler import re device { device_type: huawei, host: 192.0.2.10, username: admin, password: password, } with ConnectHandler(**device) as conn: output conn.send_command(display interface brief) # 逐行匹配接口状态 down_interfaces [] for line in output.splitlines(): match re.search(r(GigabitEthernet\S)\s(\S)\s(\S), line) if match: interface, status, protocol match.group(1), match.group(2), match.group(3) if status down or protocol down: down_interfaces.append(interface) if down_interfaces: print(f发现Down接口: {down_interfaces}) else: print(所有接口状态正常)实际生产中建议配合SNMP或者其他监控平台做趋势分析但小型网络里这种命令行巡检已经足够好用。4. 批量改配置的完整流程从设备清单到回滚预案备份和巡检跑通之后你才有资格去碰改配置这件事。批量变更的流程设计和单台操作完全不一样因为一旦下发错误可能就是几十台设备同时出问题。这一节直接把我实际执行批量配置时的完整流程拆给你看。4.1 第一步先定义设备清单而不是先写命令我的习惯是先把所有设备信息集中管理起来用一个单独的Python模块或者YAML文件维护设备清单。YAML格式对非程序员更友好我用得比较多。devices: - name: core-sw-01 host: 192.0.2.10 device_type: huawei role: core - name: access-sw-01 host: 192.0.2.20 device_type: huawei role: access然后用一段通用代码加载import yaml with open(devices.yaml, r, encodingutf-8) as f: config yaml.safe_load(f) devices config[devices]设备清单独立维护的好处是脚本逻辑和设备信息解耦。新增加设备不需要改代码改设备密码也不需要碰逻辑。4.2 第二步用配置模板管理变更内容而不是在代码里写死命令批量变更最怕两件事一是命令写错二是不同设备的差异参数搞混。我的做法是把变更内容抽象成模板用Python的字符串格式化或Jinja2模板引擎去渲染。举例我要给一批接入交换机创建VLAN 100并把这个VLAN加到所有上行口。设备之间的差异可能只是交换机编号或接口名我把这些参数留成变量vlan {{ vlan_id }} name VLAN_{{ vlan_id }} {% for port in uplink_ports %} interface {{ port }} port link-type trunk port trunk allow-pass vlan {{ vlan_id }} {% endfor %}在Python里渲染这个模板from jinja2 import Template template_str open(vlan_template.j2, encodingutf-8).read() template Template(template_str) rendered_config template.render( vlan_id100, uplink_ports[GigabitEthernet0/0/1, GigabitEthernet0/0/24], ) print(rendered_config)模板化之后的变更内容在正式下发前你还可以人工review看渲染出来的完整命令集是否正确。这比直接在脚本里写一大堆send_config_set([vlan 100, ...])要安全得多。4.3 第三步先单台试点再分批灰度我的强制原则是任何批量变更先在一台设备上试点确认无误后再扩展到一小批设备最后才全部执行。这个流程看起来慢实际上是最快的——因为一旦配置命令写错你只需要修复一台设备。试点脚本和批量脚本可以复用同一套函数from netmiko import ConnectHandler def apply_config(device, config_lines): with ConnectHandler(**device) as conn: output conn.send_config_set(config_lines) conn.save_config() return output # 先对试点设备执行 pilot_device { device_type: huawei, host: 192.0.2.20, username: admin, password: password, } apply_config(pilot_device, rendered_config.splitlines())试点通过后再对剩下的设备循环执行同时把每台设备的结果记录下来。4.4 第四步变更后立即校验而不是下完就走配置下发完成不等于变更成功还要做校验。我的校验动作一般包括三件事重新拉取配置确认配置确实在设备上生效了。检查关键接口状态确认没有因为配置导致接口Down或协议震荡。检查设备日志确认没有异常告警。最直接的方式是下发完再连一次设备执行几条只读命令with ConnectHandler(**device) as conn: result conn.send_command(display vlan) if 100 in result: print(VLAN 100 已生效) else: print(VLAN 100 未生效需要回滚)4.5 第五步回滚预案一定要先写进脚本网络变更最忌讳的是变完了再想要怎么回滚。我的做法是任何变更脚本执行前先把目标设备当前的配置文件抓取回来保存为回滚文件。如果变更后校验失败直接重新下发回滚文件里的原始配置。简化版的回滚逻辑def rollback(device, backup_config_file): with open(backup_config_file, r, encodingutf-8) as f: original_config f.read() with ConnectHandler(**device) as conn: # 进入配置模式并逐条下发原配置 conn.send_config_set(original_config.splitlines()) conn.save_config()当然回滚也有风险比如设备上新增的动态状态可能不在备份配置里。所以更稳妥的做法是在变更窗口内保留足够的观察时间发现问题立即回滚而不是等第二天再处理。5. 网络自动化的几个你以为没问题的坑工具选好了、脚本也能跑了但真正让网络自动化落地困难的往往是那些你以为没问题的小细节。我在这个过程中踩过的坑挑几个典型的说一下。5.1 SSHSession超时设备多的时候并发不能拍脑袋最早我用Netmiko写批量巡检脚本设备数量到了几十台的时候脚本越跑越慢有些设备直接连接超时。我以为是网络问题后来排查发现是并发模型不对——我用的是单线程循环逐台连接一台设备卡住后面全部排队。后来我引入Nornir它默认就是并发执行我用下面的配置把并发数调大runner: plugin: threaded options: num_workers: 20再跑批量巡检速度有了明显提升。不过这里有个新问题并发数不是越大越好。网络设备的管理CPU普遍不强如果你同时开几十个SSH会话去抓配置设备CPU会飙升甚至影响正常转发。稳妥的做法是num_workers设置从10开始试观察设备CPU占用再逐步调大。5.2 命令回显和分页一页显示不全怎么办网络设备的命令回显一般都有分页机制比如一次性显示几十行后会停下来等你按空格或回车。Netmiko对此是有处理的它会自动检测分页提示符并发空格键翻页大多数情况下没问题。但某些老设备或不常见厂商的设备分页提示符格式特殊Netmiko可能识别不了导致命令卡在中间。解决办法是在连接设备时禁用分页。不同的设备有不同的命令以常见厂商为例一般是执行screen-length 0 temporary或terminal length 0。在Netmiko里可以这样处理device { device_type: huawei, host: 192.0.2.10, username: admin, password: password, global_delay_factor: 2, } with ConnectHandler(**device) as conn: conn.send_command(screen-length 0 temporary) output conn.send_command(display current-configuration)global_delay_factor也是一个隐藏但重要的参数设备响应慢的场合把它调大能减少因超时导致命令读取不完整的问题。5.3 厂商命令差异同一件事不同说法这是跨厂商自动化最大的拦路虎。同样是配置接口IP地址不同厂商的命令风格差异很大。解决思路有两个一是用NAPALM这类提供统一抽象层的库把差异封装在驱动里。缺点是NAPALM支持的厂商和功能有限不是所有命令都覆盖。二是自己维护一份命令映射表把同一操作在不同厂商下的命令写清楚。我在做多厂商支持的时候就是这么干的COMMAND_MAP { huawei: { set_hostname: sysname {name}, create_vlan: vlan {vlan_id}, show_interface: display interface brief, }, ios: { set_hostname: hostname {name}, create_vlan: vlan {vlan_id}, show_interface: show ip interface brief, }, }然后用设备类型动态取命令模板脚本逻辑保持统一。5.4 密码和密钥管理别把密码硬编码在脚本里很多初学者的脚本长这样password 123456这在小规模实验环境里无所谓但一旦脚本要上生产、要共享给同事密码就会随着代码库到处流传迟早出问题。我现在统一用环境变量或者独立的配置密钥文件export NET_DEVICE_PASSWORDxxx脚本里读取环境变量import os password os.environ.get(NET_DEVICE_PASSWORD)更进一步可以引入密钥管理服务但网络自动化早期用环境变量加严格权限控制已经够用了。5.5 日志和审计自动化的锅谁来背自动化执行的速度快影响范围大所以日志和审计比手工操作更重要。我现在跑任何批量脚本都强制要求输出一份操作日志内容包括操作时间、操作人、操作对象、执行的命令、回显的关键信息、执行结果。import logging logging.basicConfig( filenamechange_audit.log, levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, ) logging.info(开始对设备 core-sw-01 执行 VLAN 100 创建) logging.info(下发配置: %s, rendered_config) logging.info(校验结果: VLAN 100 已生效)这套日志在你需要复盘变更、定位问题的时候价值会体现得非常明显。我经历过一次批量变更后网络出现间歇性丢包就是靠审计日志逐个回放当时下发的命令才找到是哪台设备的哪条命令引起的问题。6. 让脚本落地成每天在跑的东西我的几点体会到这一步你可能已经能写出不错的自动化脚本了但离真正落地到日常运维还差一步。这一节分享几个让我从写脚本走向跑服务的体会。6.1 给脚本加上可重复执行的属性运维脚本最大的特点是它不止跑一次而是要反复跑。所以脚本必须具备幂等性——也就是说跑第一次和跑第十次的结果是一样的。比如创建VLAN的脚本如果设备上已经有VLAN 100了第二次执行就不应该报错也不应该重复创建。做法是在下发前先查询一遍设备现状with ConnectHandler(**device) as conn: output conn.send_command(display vlan) if 100 not in output: conn.send_config_set([vlan 100, name VLAN_100]) else: print(VLAN 100 已存在跳过创建)这种先检查、再变更的写法能极大降低重复执行的风险。6.2 用定时任务把备份和巡检做成无人值守我最开始是手动跑备份脚本跑了几天就发现经常忘。后来把它挂到定时任务里每天早上自动执行一次配置备份有失败就发邮件告警。Crontab配置大概是这样的0 2 * * * cd /opt/network_automation /opt/network_automation/venv/bin/python backup_configs.py logs/backup.log 21选凌晨两点的原因是这个时间段网络流量相对小设备管理面的CPU也空闲执行时间较短不容易影响业务。定时任务跑起来之后你才真正体会到自动化的价值——不用每天惦记着今天备份了没而是每天早晨看一眼昨天的备份报告就够了。6.3 从一堆脚本到一个项目脚本数量多了之后我强烈建议把零散的单文件脚本重组成一个结构清晰的Python项目。我自己的目录结构大致如下network_automation/ ├── devices.yaml ├── templates/ │ ├── vlan_template.j2 │ └── interface_template.j2 ├── scripts/ │ ├── backup_configs.py │ ├── daily_inspection.py │ ├── apply_vlan.py │ └── rollback.py ├── logs/ └── backup/在这个过程中把公共的连接逻辑、设备加载逻辑、日志逻辑抽成公共函数避免每个脚本都重复写一遍ConnectHandler。代码质量和可维护性一下子就上去了。6.4 最后再分享一个小技巧先记录操作前状态无论是备份、巡检还是变更我都有一个习惯操作前先把设备的当前状态记录一份。比如变更前记录一下接口状态、VLAN列表、路由表摘要。操作之后再记录一份两个文件做对比能非常直观地看到这次变更到底改变了什么。对比用Linux自带的命令就能完成diff before_status.txt after_status.txt甚至可以用Python做个简单的差异输出import difflib before open(before_status.txt).readlines() after open(after_status.txt).readlines() diff difflib.unified_diff(before, after, lineterm) print(\n.join(diff))这个习惯让我避免了好几次明明改了配置但没达到预期效果的尴尬。你先记录、后对比设备配置的真实变化就藏不住。网络设备自动配置这条路我走下来最大的感受是脚本能力只是入场券真正让自动化发挥价值的是流程设计和风险控制。你把备份、巡检、变更、回滚这一套闭环都跑顺了之后Python对你来说就不再是写几行代码的问题而是重新定义自己和网络设备协作方式的问题。如果你正准备上手不妨从今晚的第一个备份脚本开始。