4. Junos PyEZ基础

Junos PyEZ 是 Juniper 官方提供的 Python 库,用于对 Junos 设备进行结构化、可编程的自动化操作。它把设备上的 CLI、NETCONF、RPC 等能力封装成 Python 对象,让运维人员可以用“配置对象 + 表视图 + RPC”的方式管理设备,而不必手工拼接 XML 或反复登录终端。本节围绕 PyEZ 的安装、设备连接、Config 与 Table/View 操作、RPC 调用,以及异常处理与超时控制展开,目标是让读者能写出一个可运行、可排错的 PyEZ 脚本。

安装与环境准备

PyEZ 通过 pip 安装,通常与 Junos 设备管理相关的依赖一起使用。推荐在虚拟环境中安装,避免与系统 Python 包冲突。安装命令如下:

python3 -m venv pyez-env
source pyez-env/bin/activate
pip install junos-eznc
pip install lxml ncclient paramiko

安装完成后,可用 python -c "import jnpr.junos; print(jnpr.junos.__version__)" 验证。PyEZ 依赖 ncclient 与 paramiko 完成 NETCONF over SSH 通信,依赖 lxml 解析 XML。若设备使用较新的 Junos 版本,建议同时确认设备已开启 NETCONF 服务:

set system services netconf ssh port 830

连接前需要准备设备地址、用户名、密码或 SSH 密钥。PyEZ 支持密码认证和密钥认证,生产环境更推荐密钥方式,避免明文密码出现在脚本中。

设备连接:Device 对象与连接参数

PyEZ 的核心入口是 jnpr.junos.Device。它既支持直接传入主机、用户、密码,也支持从 YAML 文件读取连接信息。下面是一个最小连接示例:

from jnpr.junos import Device

dev = Device(host='192.0.2.10', user='netops',
             password='ExamplePass', port=830,
             timeout=30, gather_facts=True)

dev.open()
print(dev.facts['hostname'])
print(dev.facts['model'])
dev.close()

其中 gather_facts=True 会在连接后自动采集设备基础信息,如主机名、型号、Junos 版本、序列号等,后续做条件判断时非常有用。timeout 控制连接与 RPC 的超时时间,单位是秒。若设备响应较慢或跨广域网管理,应适当调大。连接成功后,dev 对象会持有 NETCONF 会话,所有配置、表视图和 RPC 操作都基于该会话完成。

连接失败常见原因包括:NETCONF 未开启、端口被防火墙拦截、用户名密码错误、SSH 密钥未加载、设备管理地址不可达。排查时先确认 ssh user@host -p 830 能否建立连接,再检查 PyEZ 参数。

Config 操作:加载、提交与回滚

PyEZ 的 Config 类用于管理配置。它支持加载 set 命令、加载 XML 配置、比较候选配置与运行配置、提交以及回滚。下面示例演示用 set 命令方式修改设备描述并提交:

from jnpr.junos import Device
from jnpr.junos.utils.config import Config

dev = Device(host='192.0.2.10', user='netops',
             password='ExamplePass').open()
cu = Config(dev)

cu.lock()
cu.load('set system host-name PEZ-LAB-01', format='set')
cu.load('set interfaces ge-0/0/0 description PEZ-TEST',
        format='set')

if cu.diff():
    print(cu.diff())
    cu.commit(comment='PyEZ lab commit', timeout=120)
else:
    print('no change')

cu.unlock()
dev.close()

关键点有三个:第一,lock() 与 unlock() 保证配置互斥,避免多人同时改配置;第二,load() 只是把配置放入候选配置,不会立即生效;第三,commit() 才真正提交。diff() 用于查看候选配置与当前运行配置的差异,提交前先看 diff 是良好习惯。若提交后发现异常,可用 cu.rollback(1) 回滚到上一版本,再 commit()。对于批量配置,建议使用 format='xml' 或 format='text' 加载完整配置片段,减少逐条 set 带来的会话开销。

Table 与 View:结构化读取设备状态

PyEZ 的 Table/View 机制用于把设备返回的 XML 状态数据映射成 Python 对象。Table 定义“要取哪张表”,View 定义“每行要哪些字段”。例如读取接口状态:

from jnpr.junos import Device
from jnpr.junos.factory import loadyaml

globals().update(loadyaml('interfaces.yml'))

dev = Device(host='192.0.2.10', user='netops',
             password='ExamplePass').open()
intf = InterfacesTable(dev).get()
for item in intf:
    print(item.name, item.admin, item.oper, item.mtu)
dev.close()

对应的 YAML 定义示例:

InterfacesTable:
  rpc: get-interface-information
  args:
    terse: True
  item: physical-interface
  key: name
  view: InterfacesView

InterfacesView:
  fields:
    name: name
    admin: admin-status
    oper: oper-status
    mtu: mtu

Table 通过 rpc 指定底层 RPC,item 指定 XML 中重复的行节点,key 指定主键,view 指定字段映射。这样读取接口、路由、ARP、BGP 邻居等状态时,代码更简洁,也更容易做批量判断。若字段路径较深,可在 View 中使用 XPath 表达式,例如 oper: oper-status 或 addr: address/address。

RPC 调用:直接执行 Junos RPC

当 Table/View 不覆盖所需数据时,可以直接调用 RPC。PyEZ 的 dev.rpc 提供动态方法,方法名对应 Junos RPC 名称,短横线转下划线。例如获取设备运行时间:

rsp = dev.rpc.get_system_uptime_information()
print(rsp.findtext('system-uptime-information/up-time'))

rsp2 = dev.rpc.get_interface_information(terse=True)
print(rsp2.findtext('.//interface-name'))

RPC 返回的是 lxml 元素对象,可用 find、findall、findtext 和 XPath 提取数据。若需要执行 CLI 命令,可用 dev.cli('show version', format='text'),但结构化场景优先使用 RPC,因为返回 XML 更稳定、更易解析。

异常处理与超时控制

PyEZ 常见异常包括 ConnectError、ConfigError、RpcError、TimeoutError 等。生产脚本必须捕获并区分处理,避免一个设备失败导致整批任务中断。示例:

from jnpr.junos import Device
from jnpr.junos.exception import ConnectError, RpcError, TimeoutError

try:
    dev = Device(host='192.0.2.10', user='netops',
                 password='ExamplePass', timeout=20).open()
    rsp = dev.rpc.get_software_information()
    print(rsp.findtext('.//junos-version'))
except ConnectError as e:
    print('连接失败:', e)
except TimeoutError as e:
    print('超时:', e)
except RpcError as e:
    print('RPC 错误:', e)
finally:
    if 'dev' in locals() and dev.connected:
        dev.close()

超时控制分两层:连接超时由 Device(timeout=...) 控制;单次 RPC 超时可在调用时传入 dev.rpc.get_xxx(timeout=60)。对于批量任务,建议为每台设备设置独立超时,并配合重试次数。重试时不要盲目重放配置提交,应先判断是否已提交成功,避免重复变更。

Junos PyEZ 脚本运行链路与关键控制点 Python 脚本 Device / Config Table / View / RPC NETCONF over SSH 端口 830 ncclient + paramiko Junos 设备 候选配置 / 运行配置 RPC 响应 XML 结果处理 解析 / 判断 / 记录 提交 / 回滚 Config 操作状态 lock() 锁定候选配置 load() 写入候选配置 diff() 查看差异 commit() 提交生效 rollback 回滚 异常与超时控制点 ConnectError NETCONF 不可达 / 认证失败 TimeoutError 连接或 RPC 超时 RpcError RPC 返回错误 ConfigError 加载 / 提交失败

一个可复用的连接与配置检查片段

把上述要点组合起来,可以得到一个较完整的脚本骨架:连接设备、采集 facts、读取接口表、按需加载配置、提交前看 diff、异常分类处理、最后关闭会话。实际使用时,建议把设备清单、用户名、超时、重试次数放入 YAML 或环境变量,避免硬编码。对于批量设备,逐台串行执行虽然简单,但效率较低;可结合线程池并发,但要注意每台设备独立会话、独立超时和独立异常处理,避免共享 Device 对象。

PyEZ 的 Table/View 适合“读状态”,Config 适合“改配置”,RPC 适合“取 Table 未覆盖的数据”。三者配合使用,才能覆盖日常自动化运维的大部分场景。
提交配置前务必先执行 diff() 并确认变更范围;批量任务中不要因为单台设备超时就重复提交,应先判断提交状态,再决定是否回滚或重试。

掌握本节内容后,读者应能独立完成 PyEZ 环境安装、设备连接、配置加载与提交、接口状态表读取、RPC 调用以及异常与超时处理。这些能力是后续编写批量巡检、配置合规、故障自愈等自动化脚本的基础。