数据模型与网络自动化
传统人工 CLI 配置网络的模式,已经不在适用当代的网络,面临着兼容性,容错率低,效率低下等等问题,详细的内容可阅读这篇传统CLI面临的挑战
在这样的大背景下,各种网管协议应运而生。但这时就产生一个问题,以怎样的格式和内容去传递配置?
YANG 就是为了解决该问题而出现的,在解释 YANG 前,我们先来回忆下,传统 CLI 是如何下发配置的?
常常是由网络工程师通过 console/Telnet/SSH 等方式登录上设备,然后直接对设备进行配置,这时自然不用考虑怎样传参的问题,只要懂得设备的命令,直接上去敲就可以。
而 CLI 这样的配置,是一种无结构化的数据。
!
interface Bundle-Ether2
!
interface Bundle-Ether780
description evpn-vpws-test
!
interface MgmtEth0/RSP0/CPU0/0
vrf mgmt
ipv4 address 10.124.3.85 255.255.255.0
!
interface MgmtEth0/RSP0/CPU0/1
shutdown
!
interface TenGigE0/0/1/0
shutdown
!
interface TenGigE0/0/1/1
shutdown
!
interface TenGigE0/0/2/0
shutdown
!
这样无结构的数据对我们人来说是非常友好的,容易理解和阅读。
但由于换成了 NETCONF 这样的网管协议管理设备,这样的数据发挥不出任何优势,甚至无法被机器识别。因为使用 NETCONF 目的就是为了使用自动化,可编程化的方案代替人工,从而满足当下网络的各种业务场景。
此时配置结构化的数据就成了必然。
于是各个厂商开始对配置进行结构化的约定,在 NETOCNF 中,配置使用 XML 表示,进行参数的下发。
比如对接口的 MTU 进行更改:
<rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<edit-config>
<target>
<running/>
</target>
<config>
<top xmlns="http://example.com/schema/1.2/config">
<interface>
<name>Ethernet0/0</name>
<mtu>1500</mtu>
</interface>
</top>
</config>
</edit-config>
</rpc>
但此时又有一个问题,就是下发数据的格式和参数是由谁指定呢,比如这里 interface 命名的定义,类型的定义,层级的定义是如何确定的呢?
我怎么知道在下发接口配置时,就下发这样的配置内容呢?
答案在于各个厂商定义了一套约束标准,也可以理解成配置的模板。这些模板对数据进行约束,判断是否是合法的数据。这也就是数据模型的由来。
拿 NETCONF 举例,NETCONF 采用 C/S 的架构,Client 端在生成配置内容时会参考定义好的数据模型。Server 在接收时,同样会用这些数据模型进行校验。
具体些,比如针对下发 JSON 格式的配置,通过 JSON Scheme 定义的 JSD 对数据进行约束。
如左图中是具体的数据,右图中是对左侧数据具体进行约束的数据模型。比如这里限定了,productName 的类型是字符串,当前对象共有三个属性等。当传入其他不符合 scheme 的数据时,会进行的报错。
如果对 JSON-Scheme 感兴趣,可以去官网仔细了解下。
对于思科设备来说,这种由 JSON-Scheme 定义的文件叫 JSD,用于约束传入的数据类型,需要注意的是不同的厂商定义的 JSD 内容和格式是不一样的,甚至同一厂商定义的不同类型的 JSD 也不一样。
针对下发的 XML 格式的配置也采用相同的方法,各个厂商通过 XML Scheme 定义 XSD 对数据进行约束。
左面是 xml 格式的数据,右边是 XSD 文件对其进行约束,规定了属性的类型。同样不同厂商编写的 XSD 约束也是不一样的。
虽然说通过编写 XSD 和 JSD 的方式解决了如何约束配置数据的问题。
但由于不同厂商编写的数据模型没有统一的规范,导致各种各样的 JSD/XSD 出现,学习成本也很高。而且造成很严重的兼容性问题。比如 Cisco 的 JSD 或者 XSD 一定和 HUAWEI 不一样。甚至 Cisco 本身每个团队开发出的 JSD/XSD 的内容也不一样。
为了解决这个问题,由 IETF 主导,开发出了 YANG - Yet Another Next Generation. 被现在各个协议广泛使用。
YANG
YANG 的定义
YANG 是一种数据模型语言,用于在 NETCONF 等网络协议中,将想要操作的配置或状态数据进行模型化,用层次化的表现形式,对数据进行表述。对于 YANG 模型来说,每个都有唯一标识命名空间 URI,用于区分。
简单来说,通过 YANG,对下发配置的进行约束,如下的公式很好的描述了 YANG:
data(下发的数据)+ YANG = 下发给设备的配置
上图中很好的表示了 YANG 起到的作用,YANG 本身并不是数据,而更像是一种配置模板,起到约束数据的作用。
那么 YANG Model 一般是由谁定义的呢?
YANG Model 的定义,主要有两个角色:
-
标准化的 YANG Model ,由 IETF,OpenConfig 等机构进行规划定义。这类的 YANG 主要是考虑到多尝试的兼容性问题,而推出的统一的 YANG Module。所有厂商都需要支持。
-
各个厂商实现自定义私有的 YANG。这类 YANG Model 主要是为了厂商实现某些私有或特有功能 YANG. 比如 Cisco 中有许多私有的协议,如 EIGRP,BGP 的某些功能只有思科设备上有。
YANG 的结构
YANG Module 以层次化树形结构被组织起来,每个模块可以引入外部其他的模块,包含其子模块的数据。
简单来说,就是可以将模块作为参数,引入其他的模块进行使用。
YANG 定义了很多的内置类型,并提供了自定义类型的机制,类似于 C 中的 typedef
.
在 YANG 中定义了四种类型,用于将数据模型化:
Leaf Nodes:
一个节点用于表示数字或字符串等简单的数据。但只能表示一个值,不能拥有子节点。
YANG 表示:
leaf host-name {
type string;
description "Hostname for this system";
}
xml 表示:
<host-name>my.example.com</host-name>
json 表示:
{
"host-name": "my.example.com"
}
Leaf-List Nodes:
表示由 leaf node 构成的列表。
YANG 表示:
leaf-list domain-search {
type string;
description "List of domain names to search";
}
xml 表示:
<domain-search>high.example.com</domain-search>
<domain-search>low.example.com</domain-search>
<domain-search>everywhere.example.com</domain-search>
json 表示:
[
{"domain-search": "high.example.com"},
{"domain-search": "low.example.com"},
{"domain-search": "everywhere.example.com"},
]
Container Nodes:
类似于编程语言中的 MAP 形式,将多个 node 组装到一起。一个 container node 可以包含多个任意类型的 node 节点,如 leafs,lists,leaf-lists,及本身 container 的类型。
YANG 表示:
container system {
container login {
leaf message {
type string;
description
"Message given at start of login session";
}
}
}
xml 表示:
<system>
<login>
<message>Good morning</message>
</login>
</system>
json 表示:
{"system":{"login": {"message": "Good morning"}}}
List Nodes
由一个或多个 key leaf 和多个任意类型的子节点组成,类型包括,leafs,
lists, containers 等。
其中 key leaf 用于表示当前 list 的唯一性。
YANG 表示:
list user {
key "name";
leaf name {
type string;
}
leaf full-name {
type string;
}
leaf class {
type string;
}
}
这里的 name 作为唯一的标识符。
xml 表示:
<user>
<name>glocks</name>
<full-name>Goldie Locks</full-name>
<class>intruder</class>
</user>
<user>
<name>snowey</name>
<full-name>Snow White</full-name>
<class>free-loader</class>
</user>
<user>
<name>rzell</name>
<full-name>Rapun Zell</full-name>
<class>tower</class>
</user>
List Nodes 和 Leaf-List Nodes 的区别就是,Leaf-List Nodes 仅能包含类型是 Leaf Nodes 的节点,而 List Nodes 可以包含任意类型。
YANG 的其他特性
对于 YANG 来说,本身支持很多特性:
-
配置状态数据,对于定义那些不能配置的配置信息。
-
内置大量的基础类型,binary,bits,boolean 等等。
-
派生类型,自定义去定义如 binary 等类型。
-
可重用组,引用通过 grouping 陈述定义的组,用于解耦和封装。
-
支持 choices,类似于枚举。
-
使用
augment
对 model 进行约束 -
提供 RPC 调用
-
提供通知定义
更多的功能,可以参考 YANG - RFC 文档
PYANG - 更好的浏览 YANG Model
在了解 YANG 语言,提供的强大功能后。一般在项目中,会使用由 Python 开发的 Pyang
工具来浏览 YANG 模型.
简单提一下安装方法,目前 Pyang 支持 Python2,Python3.
可以通过 docker 打包 Python 镜像后,作为运行环境:
[root@localhost pyang-env]# ls
Dockerfile requirements.txt yang_modules
[root@localhost pyang-env]# cat Dockerfile
FROM python:3.8.5
ENV MY_PROXY_URL="http://xxx:80"
ENV HTTP_PROXY=$MY_PROXY_URL \
HTTPS_PROXY=$MY_PROXY_URL \
FTP_PROXY=$MY_PROXY_URL \
http_proxy=$MY_PROXY_URL \
https_proxy=$MY_PROXY_URL \
ftp_proxy=$MY_PROXY_URL
WORKDIR /src
COPY ./requirements.txt /
RUN pip install --no-cache-dir pyang
ENV MY_PROXY_URL=
ENV HTTP_PROXY=$MY_PROXY_URL \
HTTPS_PROXY=$MY_PROXY_URL \
FTP_PROXY=$MY_PROXY_URL \
http_proxy=$MY_PROXY_URL \
https_proxy=$MY_PROXY_URL \
ftp_proxy=$MY_PROXY_URL
使用 docker run -v /home/xx/pyang-env/yang_modules:/src -it --name pyang-env pyang-image /bin/bash
启动运行环境。
接着去 YANG 的 Github 中,下载由 IETF 或各个厂商开发后的 YANG Module 。这里以 IOS-XE 版本为 633 的 YANG Module 为例。
可以看到有很多类似的 YANG 文件:
这时就可以通过 Pyang 工具,来浏览内容:
root@a8af90280cf1:/src/633# pyang -f tree ietf-interfaces.yang
module: ietf-interfaces
+--rw interfaces
| +--rw interface* [name]
| +--rw name string
| +--rw description? string
| +--rw type identityref
| +--rw enabled? boolean
| +--rw link-up-down-trap-enable? enumeration {if-mib}?
+--ro interfaces-state
+--ro interface* [name]
+--ro name string
+--ro type identityref
+--ro admin-status enumeration {if-mib}?
+--ro oper-status enumeration
+--ro last-change? yang:date-and-time
+--ro if-index int32 {if-mib}?
+--ro phys-address? yang:phys-address
+--ro higher-layer-if* interface-state-ref
+--ro lower-layer-if* interface-state-ref
+--ro speed? yang:gauge64
+--ro statistics
+--ro discontinuity-time yang:date-and-time
+--ro in-octets? yang:counter64
+--ro in-unicast-pkts? yang:counter64
+--ro in-broadcast-pkts? yang:counter64
+--ro in-multicast-pkts? yang:counter64
+--ro in-discards? yang:counter32
+--ro in-errors? yang:counter32
+--ro in-unknown-protos? yang:counter32
+--ro out-octets? yang:counter64
+--ro out-unicast-pkts? yang:counter64
+--ro out-broadcast-pkts? yang:counter64
+--ro out-multicast-pkts? yang:counter64
+--ro out-discards? yang:counter32
+--ro out-errors? yang:counter32
还可以转换成 js 浏览:
pyang -f jstree ietf-interfaces.yang >> ietf-interfaces.html
更多命令可以查看帮助文档。
这时,对于网络工程师来说,可以将其从学习各厂商不同的配置命令转化到学习 Yang Module 中,更加聚焦功能,而不用在花费时间去学习相同的功能不同的命令。
YANG-Suite 介绍
YANG-Explorer 是一个用于浏览 YANG Model 的 WEB 服务,并提供生成 NETCONF RPC payload 等实用的功能。但由于其使用 Flash 编写,但在 2020 12 月后,Flash 已经被禁用,导致该工具无法使用。有兴趣的同学,可以安装老版本带 flash 的浏览器测试学习。
安装可以采用 docker:
YANG
docker pull robertcsapo/yang-explorer
docker run -it --rm -p 8088:8088 robertcsapo/yang-explorer
最新 CISCO 很开源了一个产品为 YANG-Suite,基于 YANG-Explorer 拓展了许多功能,并可以使用 docker-compose 启动,兼容性更好,预计未来会主流接受。
比如下面使用其生成 NETCONF RPC Payload
YANG 与 NETCONF
YANG 在早期是专为 NETCONF 而开发的一种语言,后来才被普及到各个语言中,关于 NETCONF 的介绍,可以参考这篇。
下面主要涉及具体的操作,使用的设备是 ASR9000(IOS-XR 6.3.3)版本。
由于 NETCONF 本身采用 C/S 架构,需要在设备端打开:
netconf agent ssh
在客户端方面,可以使用 ssh
进行测试:
NETCONF 会首先使用 Hello 建立链接,报告所拥有的能力。
[root@localhost ~]# ssh cisco@ip -p 830 -s netconf
Password:
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
<capability>urn:ietf:params:netconf:base:1.1</capability>
<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>
<capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>
<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>
<capability>urn:ietf:params:netconf:capability:confirmed-commit:1.1</capability>
<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>
<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04&deviations=cisco-xr-ietf-netconf-monitoring-deviations</capability>
<capability>http://cisco.com/ns/yang/cisco-xr-ietf-netconf-monitoring-deviations?module=cisco-xr-ietf-netconf-monitoring-deviations&revision=2016-02-16</capability>
<capability>http://cisco.com/ns/yang/Cisco-IOS-XR-Ethernet-SPAN-cfg?module=Cisco-IOS-XR-Ethernet-SPAN-cfg&revision=2015-11-09</capability>
<capability>http://cisco.com/ns/yang/Cisco-IOS-XR-Ethernet-SPAN-datatypes?module=Cisco-IOS-XR-Ethernet-SPAN-datatypes&revision=2015-11-09</capability>
<capability>http://cisco.com/ns/yang/Cisco-IOS-X
每个 capability 都可包含四部分内容:
- Model URI
- Module Name ,Revision Date
- Protocol Features
- Deviations - 修改自那个 Module
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring?module=ietf-netconf-monitoring&revision=2010-10-04&deviations=cisco-xr-ietf-netconf-monitoring-deviations</capability>
Model URI = urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring
Module Name = module=ietf-netconf-monitoring
Revision = 2010-10-04
Protocol Features = NULL
Deviations = cisco-xr-ietf-netconf-monitoring-deviations
使用 ncclient
操作 NETCONF 设备, 使用参见 ncclient-github
获取 running 配置:
from ncclient import manager
host = "10.1.1.22"
username = "cisco"
password = "cisco"
device_params = {"name": "iosxr"}
with manager.connect(host=host, port=830, username=user, hostkey_verify=False, password=password) as m:
c = m.get_config(source='running').data_xml
with open("%s.xml" % host, 'w') as f:
f.write(c)
上面演示了 NETCONF 中 get-config
操作,其余配置或过滤的功能,可参考文档。
需要注意一点的是,在配置时,payload 的生成一般通过上面介绍的 YANG-Suite 工具。
YANG 与 RESTCONF
RESTCONF 和 NETCONF 很像,简单来说,就是将 HTTP 融入了 NETCONF 中,采用 REST 风格替代 SSH 和 RPC 的交互方式。
更详细的内容,可参看这一篇。RESTCONF,下面主要集中在具体操作。
先来看下 RESTCONF URL 的内容:
https://<ADDRESS>/<ROOT>/<DATASTORE>/[YANGMODULE:]CONTAINER/<LEAF>[?<OPTIONS>]
- ADDRESS:表示 RESTCONF 代理的 IP
- ROOT:表示 restconf 请求的入口点
- DATASTORE:被查询的数据库
- [YANGMODULE:]CONTAINER - 使用基础的模块名称
: 在 Container 内的独立 node - ?
:返回结果的过滤参数
这里以 NSO - 思科的产品为例,直接操作设备也同理,比如 IOS-XE 的设备。
首先查询是否具有 RESTCONF 功能:
http://xx:8080/.well-known/host-meta
# Response 包含具有 RESTCONF 内容
<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
<Link rel='restconf' href='/restconf'/>
</XRD>
# 里面的内容表示 ROOT 的内容,作为 RESTCONF 的入口点。
查看 NETCONF 支持的能力:
http://xx:8080/restconf/data/netconf-state/
查看 RESTCONF 支持的能力:
http://xx:8080/restconf/data/restconf-state/
NOTE:
需要在 Headers 中,指定发送和接受数据的格式:
Content-type:
- application/yang-data+json
- application/yang-data+xml
Accept:
- application/yang-data+json
- application/yang-data+xml
查询 NSO 纳管的设备 - GET Method:
http://10.124.207.154:8080/restconf/data/tailf-ncs:devices/device=ASR9K/name/
修改接口描述 - Patch Method:
http://10.124.207.154:8080/restconf/data/tailf-ncs:devices/device=ASR9K/config/tailf-ned-cisco-ios-xr:interface/TenGigE/
{
"tailf-ned-cisco-ios-xr:TenGigE":
{
"id": "0/0/1/0",
"description": "restconf-test"
}
}
总结
YANG 的本质是一种对数据进行结构化描述的语言,本身不是数据,而是起到约束数据的作用。
至于为什么需要 YANG,原因在于传统 CLI 的方式,不在适合当代网络的要求。而且结构化,统一的数据更容易被机器所处理。
现在 YANG 被广泛使用,特别是可编程化的特点,像让自动化,动态编排服务,甚至网络自我调节与优化都成为了可能。
下图中很好的描述了 YANG 所发挥的作用,在设备上通过 YANG 的定义,提供如 RESTCONF,NETCONF 的接口,让其通过 HTTP 或 RPC 管理设备。
在控制器中,根据定义的 YANG Module 来开发各种客户端,去调用设备。