网络自动化秘籍(二)
原文:
zh.annas-archive.org/md5/9FD2C03E57DE97FDB15C42452017B0E9译者:飞龙
第三章:使用 Ansible 自动化 Juniper 设备的服务提供商
在本章中,我们将概述如何在典型的服务提供商(SP)环境中自动化运行 Junos OS 软件的 Juniper 设备。我们将探讨如何使用 Ansible 与 Juniper 设备交互,以及如何使用各种 Ansible 模块在 Juniper 设备上配置不同的服务和协议。我们将以以下示例网络图为基础进行说明,该示例网络图显示了基本 SP 网络的拓扑:
以下表格概述了我们示例拓扑中的设备及其各自的管理Internet Protocols(IPs):
| 设备 | 角色 | 供应商 | 管理(MGMT)端口 | MGMT IP |
|---|---|---|---|---|
mxp01 | P 路由器 | Juniper vMX 14.1 | fxp0 | 172.20.1.2 |
mxp02 | P 路由器 | Juniper vMX 14.1 | fxp0 | 172.20.1.3 |
mxpe01 | PE 路由器 | Juniper vMX 14.1 | fxp0 | 172.20.1.4 |
mxpe02 | PE 路由器 | Juniper vMX 17.1 | fxp0 | 172.20.1.5 |
本章涵盖的主要操作如下:
-
构建网络清单
-
连接并对 Juniper 设备进行身份验证
-
在 Junos OS 设备上启用网络配置协议(NETCONF)
-
在 Juniper 设备上配置通用系统选项
-
在 Juniper 设备上配置接口
-
在 Juniper 设备上配置开放最短路径优先(OSPF)
-
在 Juniper 设备上配置多协议标签交换(MPLS)
-
在 Juniper 设备上配置边界网关协议(BGP)
-
在 Juniper 设备上部署配置
-
在 Juniper 设备上配置第 3 层虚拟专用网络(L3VPN)服务
-
使用 Ansible 收集 Juniper 设备信息
-
验证 Juniper 设备的网络可达性
-
从 Juniper 设备检索操作数据
-
使用 PyEZ 操作表验证网络状态
技术要求
本章的代码文件可以在此处找到:github.com/PacktPublishing/Network-Automation-Cookbook/tree/master/ch3_junos。
本章基于以下软件版本:
-
运行 CentOS 7 的 Ansible 机器
-
Ansible 2.9
-
Juniper Virtual MX (vMX)运行 Junos OS 14.1R8 和 Junos OS 17.1R1 版本
查看以下视频以查看代码的实际操作:
构建网络清单
在本操作中,我们将概述如何构建和组织 Ansible 清单,以描述先前概述的示例 SP 网络设置。Ansible 清单是 Ansible 的关键部分,因为它定义并分组应由 Ansible 管理的设备。
准备工作
我们创建一个新文件夹,用于存放本章中创建的所有文件。新文件夹名为ch3_junos。
操作步骤...
- 在新文件夹
ch3_junos中,我们创建一个hosts文件,内容如下:
$ cat hosts
[pe]
mxpe01 Ansible_host=172.20.1.3
mxpe02 Ansible_host=172.20.1.4
[p]
mxp01 Ansible_host=172.20.1.2
mxp02 Ansible_host=172.20.1.6
[junos]
mxpe[01:02]
mxp[01:02]
[core:children]
pe
p
- 创建一个
Ansible.cfg文件,如下所示:
$ cat Ansible.cfg
[defaults]
inventory=hosts
retry_files_enabled=False
gathering=explicit
host_key_checking=False
工作原理...
我们使用hosts文件构建 Ansible 清单,并定义多个组,以便对网络基础设施中的不同设备进行分组。
-
我们创建
PE组,引用拓扑中所有 MPLS Provider Edge (PE)节点。 -
我们创建
P组,引用拓扑中所有 MPLS Provider (P)节点。 -
我们创建
junos组,引用所有运行 Junos OS 的设备。 -
我们创建
core parent组,引用PE和P组。
最后,我们创建Ansible.cfg文件并配置它指向我们的hosts文件,用作 Ansible 清单文件。我们将gathering设置为explicit,以禁用默认运行的 setup 模块,该模块用于发现受管主机的事实。禁用 setup 模块是强制性的,因为针对网络设备运行 setup 模块时会失败。
我们可以通过输入以下命令来验证我们的 Ansible 清单是否结构良好并正确编写:
$ Ansible-inventory --list
"all": {
"children": [
"core",
"junos",
"ungrouped"
]
},
"core": {
"children": [
"p",
"pe"
]
},
"junos": {
"hosts": [
"mxp01",
"mxp02",
"mxpe01",
"mxpe02"
]
},
"p": {
"hosts": [
"mxp01",
"mxp02"
]
},
"pe": {
"hosts": [
"mxpe01",
"mxpe02"
]
}
连接和身份验证 Juniper 设备
在这个示例中,我们将概述如何通过Secure Shell(SSH)从 Ansible 连接和身份验证 Juniper 设备,以开始管理 Juniper 设备。我们将概述如何使用 SSH 密钥作为身份验证方法来建立 Ansible 和 Juniper 设备之间的通信。
准备工作
为了按照这个示例操作,应该按照上一个示例构建一个 Ansible 清单文件。Ansible 控制机器和网络中所有设备之间的 IP 可达性必须配置好。
如何操作...
- 在 Ansible 机器上,在我们的
ch3_junos工作目录中创建私钥和公钥,如下所示:
$ SSH-keygen -t rsa -b 2048 -f Ansible_SSH_key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in Ansible_SSH_key.
Your public key has been saved in Ansible_SSH_key.pub.
The key fingerprint is:
SHA256:aCqgMYKAWIkv3nVz/q9cYp+2n3doD9jpgw/jeWWcVWI Ansible@centos7.localdomain
- 捕获在上一步创建的公钥,如下所示:
$ cat Ansible_SSH_key.pub
SSH-rsa SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/wvdC5ycAanRorlfMYDMAv5OTcYAALlE2bdboajsQPQNEw1Li3N0J50OJBWXX+FFQuF7JKpM32vNQjQN7BgyaBWQGxv+Nj0ViVP+8X8Wuif0m6bFxBYSaPbIbGogDjPu4qU90Iv48NGOZpcPLqZthtuN7yZKPshX/0YJtXd2quUsVhzVpJnncXZMb4DZQeOin7+JVRRrDz6KP6meIylf35mhG3CV5VqpoMjYTzkDiHwIrFWVMydd4C77RQu27N2HozUtZgJy9KD8qIJYVdP6skzvp49IdInwhjOA+CugFQuhYhHSoQxRxpws5RZlvrN7/0h0Ahc3OwHaUWD+P7lz Ansible@centos7.localdomain
- 在 Juniper 设备上,添加一个名为
admin的新用户,并指定我们将使用 SSH 密钥对对该用户进行身份验证。将在 Ansible 机器上创建的公钥复制到设备上,如下所示:
[edit system login]
Ansible@mxpe01# show
user admin {
uid 2001;
class super-user;
authentication {
SSH-rsa " SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/wvdC5ycAanRorlfMYDMAv5OTcYAALlE2bdboajsQPQNEw1Li3N0J50OJBWXX+FFQuF7JKpM32vNQjQN7BgyaBWQGxv+Nj0ViVP+8X8Wuif0m6bFxBYSaPbIbGogDjPu4qU90Iv48NGOZpcPLqZthtuN7yZKPshX/0YJtXd2quUsVhzVpJnncXZMb4DZQeOin7+JVRRrDz6KP6meIylf35mhG3CV5VqpoMjYTzkDiHwIrFWVMydd4C77RQu27N2HozUtZgJy9KD8qIJYVdP6skzvp49IdInwhjOA+CugFQuhYhHSoQxRxpws5RZlvrN7/0h0Ahc3OwHaUWD+P7lz Ansible@centos7.localdomain"; ## SECRET-DATA
}
}
它是如何工作的...
我们首先在 Ansible 控制机器上使用SSH-keygen命令创建公钥和私钥,并指定以下选项:
-
我们使用
-t选项指定加密算法,并将其设置为rsa。 -
我们使用
-b选项指定加密密钥的大小,并将大小设置为2048位。 -
我们使用
-f选项指定保存私钥和公钥的位置,并指定将生成的公钥和私钥的名称,即Ansible_SSH_key。
一旦我们运行命令,我们将看到生成了以下两个文件(私钥和公钥),如下所示:
$ ls -la | grep Ansible_SSH_key
-rw------- 1 Ansible Ansible 1679 Dec 31 23:41 Ansible_SSH_key
-rw-r--r-- 1 Ansible Ansible 409 Dec 31 23:41 Ansible_SSH_key.pub
在我们的清单中的所有 Juniper 设备上,我们创建admin用户,并指定我们将使用 SSH 密钥进行身份验证。我们将在这个新用户的authentication部分下粘贴在 Ansible 控制机器上创建的公钥的内容。通过这个配置,任何拥有相应私钥的主机都可以作为admin用户进行身份验证并登录到 Juniper 设备。
为了测试和验证我们是否成功从计算节点登录到 Junos OS 设备,我们可以使用下面代码中显示的 Ansible 命令进行测试:
$ Ansible all -m ping -u admin --private-key Ansible_SSH_key -c network_cli
mxp02 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mxpe02 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mxpe01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mxp01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
我们使用-u选项指定连接设备的用户名,并使用–private-key选项指定私钥。最后,我们使用-c选项来指定用于连接到受管设备的连接插件,这种情况下,我们使用network_cli连接插件来与受管 Juniper 设备建立 SSH 会话。
还有更多...
为了在我们的 playbooks 中使用生成的 SSH 密钥,我们可以在 Ansible 中的主机或组变量中指定用户名和 SSH 私钥文件,以便对 Juniper 设备进行身份验证。在我们的情况下,我们将这些变量设置为junos组的组变量。我们创建group_vars目录,并创建junos.yml文件,并按照下面的代码指定变量:
$ cat group_vars/junos.yml
Ansible_user: admin
Ansible_SSH_private_key_file: Ansible_SSH_key
我们再次使用Ansible命令测试 Ansible 与我们的设备之间的连接;但是这次不指定任何参数,如下所示:
$ Ansible all -m ping -c network_cli
mxp02 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mxpe02 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mxpe01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
mxp01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
在 Junos OS 设备上启用 NETCONF
在这个示例中,我们将概述如何在 Junos OS 设备上启用 NETCONF 协议。这个任务非常关键,因为我们将在以后的所有示例中使用 NETCONF API 来管理 Juniper 设备。与传统的 SSH 访问方法相比,NETCONF API 提供了几个优势,这就是为什么我们将在与 Junos OS 设备的所有交互中使用它的原因。
准备工作
作为这个配方的先决条件,必须存在一个 Ansible 清单文件,并且必须部署和工作 SSH 认证,就像之前的配方一样。
如何做...
- 创建一个名为
pb_jnpr_net_build.yml的新 playbook,如下面的代码所示:
$ cat pb_jnpr_net_build.yml
- name: Build Juniper SP Network
hosts: junos
tasks:
- name: "Enable NETCONF"
junos_netconf:
netconf_port: 830
state: present
vars:
Ansible_connection: network_cli
tags: netconf
- 使用以下代码更新
group_vars/junos.yml文件中的连接详情:
$ cat group_vars/junos.yml
Ansible_network_os: junos
Ansible_connection: netconf
它是如何工作的...
为了开始通过 NETCONF 与 Junos OS 设备进行交互,我们首先需要启用它,因此我们需要最初通过 SSH 连接到设备并启用 NETCONF。这就是为什么我们使用network_cli Ansible 连接来通过传统 SSH 连接到 Junos OS 设备。为了使用network_cli连接插件,我们需要将Ansible_network_os设置为junos。
由于我们将在所有接下来的配方中使用 NETCONF API 与 Juniper 设备进行所有交互,因此我们只为此 playbook 中的junos_netconf任务启用了network_cli插件,通过vars属性。然而,对于我们将在此 playbook 中添加的所有未来任务,我们将使用group_vars/junos.yml文件中Ansible_connection属性中指定的netconf连接。
我们创建一个名为pb_jnpr_net_build.yml的新 playbook,在第一个任务中,我们使用junos_netconf模块在远程 Junos OS 设备上启用 NETCONF 协议。我们指定将使用的 NETCONF 端口(默认情况下为830),并且我们概述了这个配置必须通过state: present指令存在于远程设备上。
一旦我们运行 playbook,我们将看到所有的 Junos OS 设备都配置了 NETCONF,如下面的代码所示:
admin@mxpe01# show system services
SSH;
netconf {
SSH {
port 830;
}
}
在 Juniper 设备上配置通用系统选项
在这个配方中,我们将概述如何在 Juniper 设备上配置一些通用的系统选项,比如主机名和域名系统(DNS)服务器,并为用户进行配置。
准备工作
为了遵循这个配方,假设已经设置了一个 Ansible 清单,并且根据之前的配方,在所有 Juniper 设备上都启用了 NETCONF。
如何做...
- 使用以下参数更新
group_vars/all.yml文件,以定义各种系统级参数,如dns和系统用户,如下面的代码所示:
$ cat group_vars/all.yml
tmp_dir: ./tmp
config_dir: ./configs
global:
dns:
- 172.20.1.1
- 172.20.1.15
root_pwd: $1$ciI4raxU$XfCVzABJKdALim0aWVMql0
users:
- role: super-user
SSH_key: Ansible_SSH_key.pub
username: admin
- hash: $1$mR940Z9C$ipX9sLKTRDeljQXvWFfJm1
passwd: 14161C180506262E757A60
role: super-user
username: Ansible
- 创建一个名为
pb_jnpr_basic_config.yml的新 playbook,其中包含以下任务,用于在 Juniper 设备上设置dns、hostname和系统用户:
$ cat pb_jnpr_basic_config.yml
---
- name: Configure Juniper Devices
hosts: junos
tasks:
- name: "Conifgure Basic System config"
junos_system:
hostname: "{{ inventory_hostname }}"
name_servers: "{{ global.dns }}"
state: present
- name: "Configure Users"
junos_user:
name: "{{ item.username }}"
role: "{{ item.role }}"
SSHkey: "{{ lookup ('file', item.SSH_key) }}"
state: present
with_items: "{{ global.users | selectattr('SSH_key','defined') | list }}"
它是如何工作的...
Ansible 提供了声明性模块来配置 Juniper 设备上的各种系统级参数。junos_system Ansible 模块使我们能够设置 Juniper 设备上的主机名和 DNS 服务器。junos_user模块为我们提供了在 Juniper 设备上设置系统用户的基本参数的能力。在这个例子中,我们设置了所有使用 SSH 密钥作为他们的认证方法的用户,并且我们循环遍历users数据结构,并且只选择定义了SSH_key选项的用户。
一旦我们运行这个 playbook,我们可以看到设备上的配置已经更新,如下面的代码块所示:
$ Ansible@mxpe01# show system
host-name mxpe01;
}
name-server {
172.20.1.1;
172.20.1.15;
}
login {
user admin {
uid 2001;
class super-user;
authentication {
SSH-rsa "SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/wvdC5ycAanRorlfMYDMAv5OTcYAALlE2bdboajsQPQNEw1Li3N0J50OJBWXX+FFQuF7JKpM32vNQjQN7BgyaBWQGxv+Nj0ViVP+8X8Wuif0m6bFxBYSaPbIbGogDjPu4qU90Iv48NGOZpcPLqZthtuN7yZKPshX/0YJtXd2quUsVhzVpJnncXZMb4DZQeOin7+JVRRrDz6KP6meIylf35mhG3CV5VqpoMjYTzkDiHwIrFWVMydd4C77RQu27N2HozUtZgJy9KD8qIJYVdP6skzvp49IdInwhjOA+CugFQuhYhHSoQxRxpws5RZlvrN7/0h0Ahc3OwHaUWD+P7lz Ansible@centos7.localdomain"; ## SECRET-DATA
}
}
还有更多...
我们在本节中概述的声明性 Ansible 模块提供了一种简单的方法来配置 Juniper 设备的基本系统级参数。然而,它们可能不涵盖我们需要在 Juniper 设备上设置的所有参数。为了更好地控制和灵活地配置 Juniper 设备的系统级参数,我们可以使用 Jinja2 模板以及 Ansible 的template模块来生成我们部署所需的特定系统级配置。在本节中,我们将概述这种方法以实现这个目标,并且这是我们将在后续的配方中使用的方法来为其他设备生成配置。
我们将重用这种方法为 Juniper 设备的不同部分(如系统、接口、OSPF 和 MPLS)生成配置。我们将创建一个 Ansible 角色,以包含生成最终配置所需的所有 Jinja2 模板和任务。以下步骤概述了创建角色以及使用该角色生成配置所需的步骤:
- 创建一个新的
roles目录,并添加一个名为build_router_config的新角色,目录结构如下:
$ tree roles/
roles/
└── build_router_config
├── tasks
└── templates
- 在
tasks文件夹下,创建一个名为build_config_dir.yml的 YAML 文件,以创建存储将生成的配置的所需文件夹,如下所示:
$ cat roles/build_router_config/tasks/build_config_dir.yml
---
- name: Create Config Directory
file: path={{config_dir}} state=directory
run_once: yes
- name: Create Temp Directory per Node
file: path={{tmp_dir}}/{{inventory_hostname}} state=directory
- name: SET FACT >> Build Directory
set_fact:
build_dir: "{{tmp_dir}}/{{inventory_hostname}}"
- 在
templates文件夹下,创建一个名为junos的新文件夹,在该文件夹中创建一个名为mgmt.j2的新 Jinja2 模板,内容如下:
$ cat roles/build_router_config/templates/junos/mgmt.j2
system {
host-name {{inventory_hostname}};
no-redirects;
{% if global.dns is defined %}
name-server {
{% for dns_server in global.dns %}
{{dns_server}};
{% endfor %}
}
{% endif %}
root-authentication {
encrypted-password "{{ global.root_pwd}}"; ## SECRET-DATA
}
login {
{% for user in global.users if user.hash is defined %}
user {{ user.username }} {
class super-user;
authentication {
encrypted-password "{{user.hash}}"; ## SECRET-DATA
}
}
{% endfor %}
{% for user in global.users if user.SSH_key is defined %}
user {{ user.username }} {
class {{ user.role }};
authentication {
SSH-rsa "{{lookup('file',user.SSH_key)}}"; ## SECRET-DATA
}
}
{% endfor %}
}
}
- 在
tasks文件夹下,创建一个名为build_device_config.yml的新的 YAML 文件,其中包含以下任务来创建系统配置:
$ cat roles/build_router_config/tasks/build_device_config.yml
---
- name: "System Configuration"
template:
src: "{{Ansible_network_os}}/mgmt.j2"
dest: "{{build_dir}}/00_mgmt.cfg"
tags: mgmt
- 在
tasks文件夹下创建一个名为main.yml的文件,其中包含以下任务:
$ cat roles/build_router_config/tasks/main.yml
---
- name: Build Required Directories
import_tasks: build_config_dir.yml
- name: Build Device Configuration
import_tasks: build_device_config.yml
- name: "Remove Old Assembled Config"
file:
path: "{{config_dir}}/{{ inventory_hostname }}.cfg"
state: absent
- name: Build Final Device Configuration
assemble:
src: "{{ build_dir }}"
dest: "{{config_dir}}/{{ inventory_hostname }}.cfg"
- name: Remove Build Directory
file: path={{ tmp_dir }} state=absent
run_once: yes
- 使用以下任务更新
pb_jnpr_net_build.yml播放手册,以为我们清单中的所有 Juniper 设备生成配置:
$ cat pb_jnpr_net_build.yml
- name: Build Device Configuration
import_role:
name: build_router_config
vars:
Ansible_connection: local
tags: build
在这种方法中,我们创建一个名为build_router_config的角色,并创建一个名为mgmt.j2的新 Jinja2 模板,其中包含 Junos OS 系统级配置的模板。我们使用 Ansible template模块来使用group_vars/all.yml文件中定义的 Ansible 变量渲染 Jinja2 模板。为了保存每个设备的配置,我们创建configs文件夹目录,其中存储每个设备的最终配置。
由于我们将使用这种方法为每个部分(MGMT、OSPF、MPLS 等)生成配置,我们将每个部分分割为单独的 Jinja2 模板,并将每个部分生成为单独的文件。我们使用assemble模块将所有这些不同部分组合成单个配置文件,并将其存储在configs目录中。这是每个设备的final和assembled配置文件。我们将每个部分的临时配置片段存储在每个设备的临时文件夹中,并在播放手册运行结束时删除此临时文件夹。这是因为我们已经为设备组装了最终配置,不再需要这些配置片段。
在这个手册中,我们将Ansible_connection设置为local,因为我们不需要连接到设备来运行角色中的任何任务。我们只在 Ansible 控制机上生成配置,因此所有任务都需要在 Ansible 控制机上本地运行。因此,无需连接到远程管理节点。
运行播放手册后,我们可以看到configs目录中创建了以下配置文件:
$ tree configs/
configs/
├── mxp01.cfg
├── mxp02.cfg
├── mxpe01.cfg
└── mxpe02.cfg
我们可以看到为mxpe01设备生成的配置,如下所示:
$ cat configs/mxpe01.cfg
system {
host-name mxpe01;
no-redirects;
name-server {
172.20.1.1;
172.20.1.15;
}
root-authentication {
encrypted-password "$1$ciI4raxU$XfCVzABJKdALim0aWVMql0"; ## SECRET-DATA
}
login {
user Ansible {
class super-user;
authentication {
encrypted-password "$1$mR940Z9C$ipX9sLKTRDeljQXvWFfJm1"; ##
SECRET-DATA
}
}
user admin {
class super-user;
authentication {
SSH-rsa "SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/wvdC5ycAanRorlfMYDMAv5OTcYAALlE2bdboajsQPQNEw1Li3N0J50OJBWXX+FFQuF7JKpM32vNQjQN7BgyaBWQGxv+Nj0ViVP+8X8Wuif0m6bFxBYSaPbIbGogDjPu4qU90Iv48NGOZpcPLqZthtuN7yZKPshX/0YJtXd2quUsVhzVpJnncXZMb4DZQeOin7+JVRRrDz6KP6meIylf35mhG3CV5VqpoMjYTzkDiHwIrFWVMydd4C77RQu27N2HozUtZgJy9KD8qIJYVdP6skzvp49IdInwhjOA+CugFQuhYhHSoQxRxpws5RZlvrN7/0h0Ahc3OwHaUWD+P7lz Ansible@centos7.localdomain"; ## SECRET-DATA
}
}
}
}
在随后的步骤中,我们将概述如何使用另一个 Ansible 模块将生成的配置推送到 Juniper 设备。
另请参阅...
有关 Ansible template模块和该模块支持的不同参数的更多信息,请参考以下网址:docs.ansible.com/ansible/latest/modules/template_module.html。
有关 Ansible assemble模块和该模块支持的不同参数的更多信息,请参考以下网址:docs.ansible.com/ansible/latest/modules/assemble_module.html。
配置 Juniper 设备上的接口
在这个步骤中,我们将概述如何管理 Juniper 设备上的接口。这使我们能够为我们的接口设置不同的参数,如最大传输单元(MTU)和 Juniper 设备上的 IP 地址。
准备工作
为了按照这个步骤进行操作,假设已经设置了 Ansible 清单,并且在所有 Juniper 设备上启用了 NETCONF,就像之前的步骤一样。
如何做…
- 更新
group_vars/all.ymlYAML 文件,包括我们示例网络拓扑中所有点对点(P2P)和环回接口的以下数据:
p2p_ip:
mxp01:
- {port: ge-0/0/0, ip: 10.1.1.2 , peer: mxpe01, pport: ge-0/0/0, peer_ip: 10.1.1.3}
- {port: ge-0/0/1, ip: 10.1.1.4 , peer: mxpe02, pport: ge-0/0/0, peer_ip: 10.1.1.5}
- {port: ge-0/0/3, ip: 10.1.1.0 , peer: mxp02, pport: ge-0/0/3, peer_ip: 10.1.1.1}
mxp02:
<-- Output Trimmed for brevity ------>
mxpe01:
<-- Output Trimmed for brevity ------>
mxpe02:
<-- Output Trimmed for brevity ------>
xrpe03:
<-- Output Trimmed for brevity ------>
lo_ip:
mxp01: 10.100.1.254/32
mxp02: 10.100.1.253/32
mxpe01: 10.100.1.1/32
mxpe02: 10.100.1.2/32
xrpe03: 10.100.1.3/32
- 在
pb_jnpr_basic_config.ymlplaybook 中更新以下任务,以在我们的 Juniper 设备上设置接口:
- name: "Configure the Physical Interfaces"
junos_interface:
name: "{{ item.port }}"
enabled: true
description: "peer:{{item.peer}} remote_port:{{item.pport }}"
mtu: "{{ global.mtu | default(1500) }}"
with_items: "{{p2p_ip[inventory_hostname]}}"
tags: intf
- name: "Configure IP Addresses"
junos_l3_interface:
name: "{{ item.port }}"
ipv4: "{{ item.ip }}/{{ global.p2p_prefix }}"
state: present
with_items: "{{ p2p_ip[inventory_hostname] }}"
tags: intf
它是如何工作的…
我们在group_vars/all.yml文件中的两个主要数据结构中定义了示例网络拓扑中所有接口的所有数据。我们使用p2p_ip字典来模拟示例网络中所有 P2P IP 地址,并使用lo_ip字典来指定节点的环回 IP 地址。
我们使用junos_interface Ansible 模块来启用接口并为接口设置基本参数,如 MTU 和描述。我们循环遍历每个设备的p2p_ip数据结构,并为网络清单中的所有设备上的每个接口设置正确的参数。我们使用junos_l3_interface Ansible 模块在示例网络拓扑中的所有设备上设置正确的 IPv4 地址。
一旦我们运行了 playbook,我们可以看到接口已经按照要求配置,就像在mxpe01设备上显示的那样:
Ansible@mxpe01# show interfaces
ge-0/0/0 {
description "peer:mxp01 remote_port:ge-0/0/0";
mtu 1500;
unit 0 {
family inet {
address 10.1.1.3/31;
}
}
}
ge-0/0/1 {
description "peer:mxp02 remote_port:ge-0/0/0";
mtu 1500;
unit 0 {
family inet {
address 10.1.1.9/31;
}
}
}
还有更多…
如果我们需要对接口配置有更多的控制,并设置声明式 Ansible 模块中未涵盖的参数,我们可以使用 Jinja2 模板来实现这个目标。使用与我们在上一步骤中概述的完全相同的方法,我们可以为我们的 Juniper 设备生成所需的接口配置。
使用我们在上一步骤中创建的相同的 Ansible 角色,我们可以扩展它以为我们的 Juniper 设备生成接口配置。我们使用以下步骤来完成这个任务:
- 在
templates文件夹中创建一个名为intf.j2的新的 Jinja2 模板文件,包含以下数据:
$ cat roles/build_router_config/templates/junos/intf.j2
interfaces {
{% for intf in p2p_ip[inventory_hostname] | sort(attribute='port') %}
{{ intf.port.split('.')[0] }} {
description "peer:{{intf.peer}} -- peer_port: {{intf.pport}}"
unit 0 {
family inet {
address {{intf.ip}}/{{global.p2p_prefix}};
}
family mpls;
}
}
{% endif %}
{% endfor %}
lo0 {
unit 0 {
family inet {
address {{lo_ip[inventory_hostname]}};
}
}
}
- 在
tasks目录下更新build_device_config.yml文件,添加新任务以生成接口配置,如下所示:
$ cat roles/build_router_config/tasks/build_device_config.yml
<-- Output Trimmed for brevity ------>
- name: "Interface Configuration"
template:
src: "{{Ansible_network_os}}/intf.j2"
dest: "{{build_dir}}/01_intf.cfg"
tags: intf
这是在运行 playbook 后为mxp02设备生成的接口配置:
interfaces {
ge-0/0/0 {
description "peer:mxpe01 -- peer_port: ge-0/0/1"
unit 0 {
family inet {
address 10.1.1.8/31;
}
family mpls;
}
}
ge-0/0/1 {
description "peer:mxpe02 -- peer_port: ge-0/0/1"
unit 0 {
family inet {
address 10.1.1.10/31;
}
family mpls;
}
}
<-- Output Trimmed for brevity ------>
lo0 {
unit 0 {
family inet {
address 10.100.1.253/32;
}
}
}
在 Juniper 设备上配置 OSPF
在这个步骤中,我们将概述如何在 Juniper 设备上配置 OSPF 作为我们示例网络拓扑中的内部网关协议(IGP),以及不同的 OSPF 参数,如 OSPF 链路类型和 OSPF 接口成本。
如何做…
- 在
templates/junos目录中创建一个名为ospf.j2的新的 Jinja2 文件,包含以下数据:
$ cat roles/build_router_config/templates/junos/ospf.j2
protocols {
ospf {
area {{global.ospf_area}} {
{% for intf in p2p_ip[inventory_hostname]|sort(attribute='port') %}
interface {{ intf.port }} {
interface-type p2p;
metric {{intf.cost | default(100)}};
}
{% endfor %}
interface lo0.0 {
passive;
}
}
}
}
- 在
tasks文件夹中的junos_build_config.yml文件中,添加以下任务:
$ cat roles/build_router_config/tasks/build_device_config.yml
<-- Output Trimmed for brevity ------>
- name: "OSPF Configuration"
template:
src: "{{Ansible_network_os}}/ospf.j2"
dest: "{{config_dir}}/{{ inventory_hostname }}/02_ospf.cfg"
它是如何工作的…
我们使用在all.yml文件中声明的p2p_ip数据结构中声明的相同接口数据,以便在我们的示例网络中的网络设备上配置 OSPF。我们使用在templates/junos目录下定义的新的 Jinja2 模板来捕获需要在 Juniper 设备上实现的 OSPF 配置参数(OSPF 成本、OSPF 接口类型等)。
在tasks/Juniper_build_config.yml文件中,添加一个使用ospf.j2 Jinja2 模板来渲染 Jinja2 模板,并输出我们 Ansible 清单中每个设备的 OSPF 配置部分的新任务。
在运行带有新任务的 playbook 后,以下片段概述了为mxpe01设备生成的 OSPF 配置:
$ cat configs/mxpe01.cfg
<-- Output Trimmed for brevity ------>
protocols {
ospf {
area 0 {
interface ge-0/0/0 {
interface-type p2p;
metric 100;
}
interface ge-0/0/1 {
interface-type p2p;
metric 100;
}
interface lo0.0 {
passive;
}
}
}
}
在 Juniper 设备上配置 MPLS
在这个方法中,我们将概述如何在 Juniper 设备上配置 MPLS 和一些相关协议,如标签分发协议(LDP)和资源预留协议(RSVP)。我们将概述如何使用 Ansible 和 Jinja2 模板生成所需的 MPLS 配置。
如何做...
- 在
templates/junos目录下创建一个名为mpls.j2的新的 Jinja2 文件,包含以下数据:
$ cat roles/build_router_config/templates/junos/mpls.j2
protocols {
ldp {
{% for intf in p2p_ip[inventory_hostname]|sort(attribute='port') %}
interface {{intf.port}}.{{intf.vlan|default('0')}};
{% endfor %}
interface lo0.0;
}
rsvp {
{% for intf in p2p_ip[inventory_hostname]|sort(attribute='port') %}
interface {{intf.port}}.{{intf.vlan|default('0')}};
{% endfor %}
}
mpls {
{% for intf in p2p_ip[inventory_hostname]|sort(attribute='port') %}
interface {{intf.port}}.{{intf.vlan|default('0')}};
{% endfor %}
}
}
- 在
tasks文件夹中的build_device_config.yml文件中,添加以下任务:
$ cat roles/build_router_config/tasks/build_device_config.yml
<-- Output Trimmed for brevity ------>
- name: "MPLS Configuration"
template:
src: "{{Ansible_network_os}}/mpls.j2"
dest: "{{config_dir}}/{{ inventory_hostname }}/03_mpls.cfg"
它是如何工作的...
我们使用与配置接口和 OSPF 相同的方法,通过使用 Jinja2 模板为我们库存中的 Juniper 设备生成所需的 MPLS 配置,以下是mxpe02路由器的 MPLS 配置示例:
protocols {
ldp {
interface ge-0/0/0.0;
interface ge-0/0/1.0;
interface lo0.0;
}
rsvp {
interface ge-0/0/0.0;
interface ge-0/0/1.0;
}
mpls {
interface ge-0/0/0.0;
interface ge-0/0/1.0;
}
}
在 Juniper 设备上配置 BGP
在这个方法中,我们将概述如何在 Juniper 设备上配置 BGP。我们将概述如何设置 BGP 和 BGP路由反射器(RR)作为我们示例拓扑的一部分,以及支持虚拟专用网络(VPN)服务所需的所有 BGP 地址族。
如何做...
- 更新
group_vars/all.yml文件,包含以下 BGP 信息:
bgp_topo:
rr: mxp01
af:
- inet
- inet-vpn
- 对于 Ansible 库存中的每个节点,我们在
host_vars目录下创建一个名为bgp.yml的文件。该文件保存每个节点的 BGP 信息和 BGP 对等方。这是mxpe01设备的示例:
$ cat host_vars/mxpe01/bgp.yml
bgp_asn: 65400
bgp_peers:
- local_as: 65400
peer: 10.100.1.254
remote_as: 65400
- 在
templates/junos目录下创建一个名为bgp.j2的新的 Jinja2 文件,包含以下数据:
$ cat roles/build_router_config/templates/junos/bgp.j2
protocols {
{% if bgp_peers is defined %}
bgp {
group Core {
type internal;
local-address {{ lo_ip[inventory_hostname] | ipaddr('address')}};
{% if bgp_topo.rr == inventory_hostname %}
cluster {{ lo_ip[inventory_hostname].split('/')[0] }};
{% endif %}
{% for af in bgp_topo.af %}
{% if af == 'inet' %}
family inet {
unicast;
}
{% endif %}
{% if af == 'inet-vpn' %}
family inet-vpn {
unicast;
}
{% endif %}
<-- Output Trimmed for brevity ------>
{% endfor %}
{% for p in bgp_peers %}
neighbor {{ p.peer}};
{% endfor %}
}
}
{% endif %}
}
- 在
tasks文件夹中的build_device_config.yml文件中,添加以下突出显示的任务:
$ cat roles/build_router_config/tasks/build_device_config.yml
<-- Output Trimmed for brevity ------>
- name: "BGP Configuration"
template:
src: "{{Ansible_network_os}}/bgp.j2"
dest: "{{config_dir}}/{{ inventory_hostname }}/04_bgp.cfg"
它是如何工作的...
与之前的方法类似,我们使用 Jinja2 模板为 Juniper 设备生成 BGP 配置。然而,在本节中,我们在两个不同的位置声明 BGP 参数,即group_vars和host_vars目录。在group_vars/all.yml文件中,我们声明了 BGP 拓扑的整体参数,例如我们将使用的 RR 以及我们将配置哪些地址族。对于库存中的每个节点,我们在host_vars目录中创建一个目录,并在该目录中创建一个bgp.yml文件。这个新的 YAML 文件保存了我们库存中每个节点的 BGP 对等方。我们使用这两个位置定义的数据来为每个设备渲染 BGP 配置。
这是mxp01路由器的 BGP 配置示例,它是我们拓扑中的 RR:
protocols {
bgp {
group Core {
type internal;
local-address 10.100.1.254;
cluster 10.100.1.254;
family inet {
unicast;
}
family inet-vpn {
unicast;
}
neighbor 10.100.1.1;
neighbor 10.100.1.2;
neighbor 10.100.1.3;
}
}
}
在 Juniper 设备上部署配置
在这个方法中,我们将概述如何使用 Ansible 在 Juniper 设备上推送我们通过 Jinja2 模板在所有先前部分生成的配置。这使我们能够将我们创建的任何自定义配置推送到 Juniper 设备。
准备工作
此方法要求 Juniper 设备上启用 NETCONF。
如何做...
- 在
pb_junos_push_con文件中,添加以下任务:
$ cat pb_jnpr_net_build.yml
<-- Output Trimmed for brevity ------>
- name: "Deploy Configuration"
junos_config:
src: "{{config_dir}}/{{ inventory_hostname }}.cfg"
它是如何工作的...
在先前的方法中,我们使用assemble模块来将 Juniper 设备的不同部分的配置(如接口、OSPF、MPLS 和 BGP)分组到单个配置文件中。该文件存储在每个设备的configs文件夹中。
我们使用junos_config模块来将我们生成的配置文件推送到网络库存中的每个设备。我们可以使用update参数来控制我们要推送的配置如何与设备上的现有配置合并。它支持以下选项:
-
合并:这会导致我们文件中的配置与设备上的配置(候选配置)合并。这是默认选项。 -
覆盖/更新:这会导致我们文件中的配置覆盖受管设备上的完整配置。
我们可以使用check模式以干运行模式运行我们的 playbook。在这种情况下,我们将推送配置到设备,而不提交配置。这使我们能够检查将推送到设备的更改。可以按以下方式完成:
$ Ansible-playbook pb_jnpr_net_build.yml -l mxpe01 --check –diff
我们使用-check选项以检查模式(干运行)运行 playbook,并使用-diff选项以输出将推送到我们设备的更改。
还有更多...
junos_config模块还支持 Junos OS 支持的回滚功能,因此我们可以添加另一个任务来回滚配置并控制其运行,如下所示:
$ cat pb_jnpr_net_build.yml
<-- Output Trimmed for brevity ------>
- name: "Rollback config"
junos_config:
rollback: "{{ rollback | default('1') | int }}"
tags: rollback, never
在前面的 playbook 中,我们回滚到配置的上一个版本。但是,通过更改rollback属性中的数字,我们可以控制要回滚到的配置版本。此外,我们使用标签来仅在 playbook 运行期间指定rollback标签时执行此任务,如下面的代码片段所示:
$ Ansible-playbook pb_jnpr_net_build.yml --tags rollback -l mxpe01
我们可以指定另一个回滚点,如下所示:
$ Ansible-playbook pb_jnpr_net_build.yml --tags rollback -l mxpe01 –e rollback=2
另请参阅...
有关junos_config模块和此模块支持的不同参数的更多信息,请参阅以下网址:docs.ansible.com/ansible/latest/modules/junos_config_module.html.
在 Juniper 设备上配置 L3VPN 服务
在本教程中,我们将概述如何使用各种 Ansible 模块在 Juniper 设备上建模和配置 L3VPN。这使我们能够使用基础设施即代码(IaC)实践来建模我们的服务,并利用 Ansible 来部署和推送所需的配置,以在 Juniper 设备上部署 L3VPN。
准备工作
Juniper 设备上必须启用 NETCONF 才能在本教程中使用 Ansible 模块。
如何做...
- 创建一个名为
l3vpn.yml的新文件,内容如下:
---
l3vpns:
vpna:
state: present
rt: "target:{{bgp_asn}}:10"
rd: "1:10"
sites:
- node: mxpe01
port: ge-0/0/3.10
ip: 172.10.1.1/24
- node: mxpe02
port: ge-0/0/3.10
ip: 172.10.2.1/24
vpnb:
state: present
rt: "target:{{bgp_asn}}:20"
rd: "1:20"
sites:
- node: mxpe01
port: ge-0/0/3.20
ip: 172.20.1.1/24
- node: mxpe02
port: ge-0/0/3.20
ip: 172.20.2.1/24
- 创建一个名为
pb_junos_l3vpn.yml的新 playbook,其中包含以下任务,以配置 PE-客户边缘(CE)链路:
---
- name: "Deploy L3VPNs on Juniper Devices"
hosts: pe
vars_files:
- "l3vpn.yml"
tasks:
- name: "Set VPN Interfaces"
set_fact:
l3vpn_intfs: "{{ l3vpn_intfs|default([]) +
l3vpns[item.key].sites |
selectattr('node','equalto',inventory_hostname) | list}}"
with_dict: "{{l3vpns}}"
delegate_to: localhost
- name: "Configure Interfaces for L3VPN Sites"
junos_config:
lines:
- set interfaces {{ item.port.split('.')[0]}} vlan-tagging
- set interfaces {{ item.port}} vlan-id {{ item.port.split('.')[1] }}
loop: "{{ l3vpn_intfs }}"
- 在
pb_junos_l3vpn.yml中添加以下任务以设置 PE-CE 链路上的 P2P IP 地址:
- name: "Configure IP address for L3VPN Interfaces"
junos_l3_interface:
name: "{{ item.port.split('.')[0]}}"
ipv4: "{{ item.ip }}"
unit: "{{ item.port.split('.')[1] }}"
loop: "{{l3vpn_intfs}}"
tags: intf_ip
- 在
pb_junos_l3vpn.yml中添加以下任务以在 PE 节点上配置虚拟路由和转发(VRFs):
- name: "Configure L3VPNs"
junos_vrf:
name: "{{ item.key }}"
rd: "{{item.value.rd}}"
target: "{{ item.value.rt }}"
interfaces: "{{ l3vpns[item.key].sites |
map(attribute='port') | list }}"
state: "{{ item.value.state }}"
with_dict: "{{l3vpns}}"
when: inventory_hostname in (l3vpns[item.key].sites | map(attribute='node') | list)
tags: l3vpn
工作原理...
我们创建一个名为l3vpn.yml的新的 YAML 文件,描述和建模我们想要在拓扑上的所有 Juniper 设备上实现的 L3VPN 拓扑和数据。我们在新的 playbook 中包含这个文件,以便在我们的网络设备上配置 L3VPN。
在pb_junos_l3vpn.yml playbook 中,我们使用l3vpn.yml文件中的数据来捕获所需的数据以配置 L3VPN。
在我们的 playbook 中的第一个任务中,我们创建一个名为l3vpn_intfs的新变量,该变量捕获我们在l3vpn.yml文件中定义的所有 VPN 的每个 PE 设备上的所有 L3VPN 接口。我们在此文件中循环遍历所有 L3VPN,并为属于特定节点的所有接口创建一个新的列表数据结构。以下代码片段概述了mxpe01的l3vpn_intfs的新数据结构:
ok: [mxpe01 -> localhost] => {
"l3vpn_intfs": [
{
"ip": "172.10.1.1/24",
"node": "mxpe01",
"port": "ge-0/0/3.10"
},
{
"ip": "172.20.1.1/24",
"node": "mxpe01",
"port": "ge-0/0/3.20"
}
]
}
接下来,在我们的 playbook 中,我们将我们的 L3VPN 服务的配置分为多个任务:
-
我们使用
junos_config模块配置所有属于 L3VPN 的接口,以便在这些接口上配置虚拟局域网(VLANs)。 -
我们使用
junos_l3_interface模块在所有属于我们的 L3VPN 模型的接口上应用 IPv4 地址。 -
我们使用
junos_vrf模块根据我们的 L3VPN 数据模型在节点上配置正确的路由实例。
在运行此 playbook 后,以下概述了应用在mxpe01上的 L3VPN 配置:
Ansible@mxpe01> show configuration routing-instances
vpna {
instance-type vrf;
interface ge-0/0/3.10;
route-distinguisher 1:10;
vrf-target target:65400:10;
vrf-table-label;
}
vpnb {
instance-type vrf;
interface ge-0/0/3.20;
route-distinguisher 1:20;
vrf-target target:65400:20;
vrf-table-label;
}
另请参阅...
有关junos_vrf模块和该模块支持的不同参数以在 Juniper 设备上配置 L3VPN 的更多信息,请参考以下网址:docs.ansible.com/ansible/latest/modules/junos_vrf_module.html#junos-vrf-module.
使用 Ansible 收集 Juniper 设备信息
在这个配方中,我们将检索 Ansible 为 Juniper 设备收集的基本系统信息。这些基本系统信息为我们提供了有关 Juniper 设备的基本健康检查,我们可以用来验证其操作状态。
准备工作
Juniper 设备上必须启用 NETCONF 才能在此配方中使用 Ansible 模块。
如何做...
- 创建一个新的 playbook,
pb_jnpr_facts.yml,使用以下任务来收集信息:
$ cat pb_jnpr_facts.yml
---
- name: Collect and Validate Juniper Facts
hosts: junos
tasks:
- name: Collect Juniper Facts
junos_facts:
- 使用以下任务更新
pb_jnpr_facts.ymlplaybook,为清单中的每个节点创建一个信息报告:
- name: Create Facts Folder
file: path=device_facts state=directory
run_once: yes
- name: Create Basic Device Facts Report
blockinfile:
path: "device_facts/{{ inventory_hostname }}.txt"
block: |
device_name: {{ Ansible_net_hostname }}
model: {{ Ansible_net_system }} {{ Ansible_net_model }}
os_version: {{ Ansible_net_version }}
serial_number: {{ Ansible_net_serialnum }}
create: yes
- 使用以下任务更新 playbook 以验证核心接口的操作状态:
- name: Validate all Core Interface are Operational
assert:
that:
- Ansible_net_interfaces[item.port]['oper-status'] == 'up'
fail_msg: "Interface {{item.port}} is not Operational "
loop: "{{ p2p_ip[inventory_hostname] }}"
工作原理...
Ansible 提供了一个信息收集模块,用于收集 Juniper 设备的基本系统属性,并以一致和结构化的数据结构返回这些信息。我们可以使用该模块收集的信息来验证设备的基本属性和操作状态,并可以使用这些数据生成捕获设备状态的简单报告。
在这个配方中,我们使用junos_facts模块来收集所有 Juniper 设备的设备信息。该模块返回 Ansible 为每个设备收集的基本信息,并存储在多个变量中,如下所示:
"Ansible_net_serialnum": "VM5D112EFB39",
"Ansible_net_system": "junos",
"Ansible_net_version": "17.1R1.8",
"Ansible_network_os": "junos",
我们使用这些数据来使用blockinfile模块为每个设备构建信息报告,并使用这些数据来验证每个设备的核心接口的操作状态。
一旦我们运行了 playbook,我们就可以看到为每个设备生成了一个信息报告,如下所示:
$ tree device_facts/
device_facts/
├── mxp01.txt
├── mxp02.txt
├── mxpe01.txt
└── mxpe02.txt
$ cat device_facts/mxp01.txt
device_name: mxp01
model: junos vmx
os_version: 14.1R4.8
serial_number: VM5701F131C6
在最后一个任务中,我们使用assert模块来验证所有 Juniper 设备上的所有核心接口是否都处于操作状态。Ansible 将设备的所有接口的操作状态存储在Ansible_net_interfaces下。我们使用这个数据结构中的数据来验证操作状态是否正常。如果所有核心接口都处于操作状态,任务将成功,否则任务将失败。
另请参阅...
有关junos_facts模块和该模块支持的不同参数的更多信息,请参考以下网址:docs.ansible.com/ansible/latest/modules/junos_facts_module.html.
验证 Juniper 设备的网络可达性
在这个配方中,我们将概述如何使用 Ansible 在 Juniper 设备上验证通过ping进行网络可达性。这将使我们能够验证样本网络拓扑中的网络可达性和流量转发。
准备工作
这个配方假设网络已经建立和配置,就像在所有先前的配方中所概述的那样。
如何做...
- 创建一个名为
pb_junos_ping.yml的新 playbook,使用以下任务来 ping 样本网络中的所有核心 loopback:
---
- name: "Validate Core Reachability"
hosts: junos
tasks:
- name: "Ping Across All Loopback Interfaces"
junos_ping:
dest: "{{ item.value.split('/')[0] }}"
interface: lo0.0
size: 512
with_dict: "{{lo_ip}}"
vars:
Ansible_connection: network_cli
register: ping_rst
ignore_errors: yes
- 使用以下任务更新
pb_junos_ping.ymlplaybook,创建一个自定义报告来捕获 ping 的结果:
- name: Create Ping Report
blockinfile:
block: |
Node | Destination | Packet Loss | Delay |
-----| ------------| ------------| ------|
{% for node in play_hosts %}
{% for result in hostvars[node].ping_rst.results %}
{% if result.rtt is defined %}
{{ node }} | {{ result.item.value }} | {{ result.packet_loss }} | {{ result.rtt.avg }}
{% else %}
{{ node }} | {{ result.item.value }} | {{ result.packet_loss }} | 'N/A'
{% endif %}
{% endfor %}
{% endfor %}
path: ./ping_report.md
create: yes
run_once: yes
工作原理...
我们使用junos_ping模块从网络清单中的所有节点向group_vars/all.yml文件中定义的lo_ip数据结构中定义的所有环回接口执行 ping。此模块连接到每个设备并对所有目的地执行 ping,并验证 ping 数据包是否到达其预期目的地。此模块需要使用network_cli连接插件,因此我们将此参数作为任务变量提供,以覆盖组级别在组级别定义的 NETCONF 连接插件。
我们注册模块的输出,以便使用这些数据生成 ping 报告。最后,我们将ignore_errors设置为yes,以忽略可能遇到的任何 ping 任务失败,并确保我们将运行后续任务以创建报告。
我们使用blockinfile模块创建 Markdown 中的自定义报告。我们使用表格布局来捕获 ping 结果并显示捕获这些 ping 结果的表格。以下片段捕获了为mxpe01 ping 测试报告生成的表格:
$ cat ping_report.md
# BEGIN ANSIBLE MANAGED BLOCK
Node | Destination | Packet Loss | Delay |
-----| ------------| ------------| ------|
mxpe01 | 10.100.1.254/32 | 0% | 3.75
mxpe01 | 10.100.1.253/32 | 0% | 2.09
mxpe01 | 10.100.1.1/32 | 0% | 0.27
mxpe01 | 10.100.1.2/32 | 0% | 4.72
mxpe01 | 10.100.1.3/32 | 100% | 'N/A'
# END ANSIBLE MANAGED BLOCK
以下是 ping 结果的渲染 Markdown 表格:
另请参阅...
有关junos_ping模块和此模块支持的不同参数的更多信息,请参阅以下网址:docs.ansible.com/ansible/latest/modules/junos_ping_module.html.
从 Juniper 设备检索操作数据
在此步骤中,我们将概述如何在 Juniper 设备上执行操作命令并将这些输出存储为文本文件以进行进一步处理。
准备工作
NETCONF 必须在 Juniper 设备上启用,以便按照此步骤进行操作。
如何做...
- 安装
jxmleasePython 包,如下所示:
$ pip3 install jxmlease
- 创建一个名为
pb_get_ospf_peers.yml的新剧本,并填充以下任务以提取 OSPF 对等信息:
---
- name: "Get OSPF Status"
hosts: junos
tasks:
- name: "Get OSPF Neighbours Data"
junos_command:
commands: show ospf neighbor
display: xml
register: ospf_output
- name: "Extract OSPF Neighbour Data"
set_fact:
ospf_peers: "{{ ospf_output.output[0]['rpc-reply']\
['ospf-neighbor-information']['ospf-neighbor'] }}"
- 更新
pb_get_ospf_peers.yml剧本,使用以下任务验证所有节点上的所有 OSPF 对等关系是否处于Full状态:
- name: "Validate All OSPF Peers are in Full State"
assert:
that: item['ospf-neighbor-state'] == 'Full'
fail_msg: "Peer on Interface {{item['interface-name']}} is Down"
success_msg: "Peer on Interface {{item['interface-name']}} is UP"
loop: "{{ospf_peers}}"
工作原理...
使用 NETCONF API 与 Juniper 设备交互的一个优势是,我们可以获取我们在 Juniper 设备上执行的所有操作命令的结构化输出。设备通过 NETCONF 会话返回给我们的输出是 XML 格式的,Ansible 使用名为jxmlease的 Python 库来解码此 XML 并将其转换为 JSON 以进行更好的表示。这就是为什么我们的第一个任务是安装jxmlease Python 包。
我们使用junos_command模块向 Juniper 设备发送操作命令,并指定我们需要 XML 作为从节点返回的输出格式。Ansible 使用jxmlease包将此 XML 数据结构转换为 JSON。我们使用register关键字将此数据保存到名为ospf_output的新变量中。以下是从此命令返回的 JSON 数据的示例:
"msg": [
{
"rpc-reply": {
"ospf-neighbor-information": {
"ospf-neighbor": [
{
"activity-timer": "34",
"interface-name": "ge-0/0/0.0",
"neighbor-address": "10.1.1.2",
"neighbor-id": "10.100.1.254",
"neighbor-priority": "128",
"ospf-neighbor-state": "Full"
},
{
"activity-timer": "37",
"interface-name": "ge-0/0/1.0",
"neighbor-address": "10.1.1.8",
"neighbor-id": "10.100.1.253",
"neighbor-priority": "128",
"ospf-neighbor-state": "Full"
}
]
}
}
}
]
所有这些数据结构都包含在ospf_output.output[0]变量中,我们使用set_fact模块来捕获ospf-neigbour数据。之后,我们使用assert模块循环遍历此数据结构中的所有 OSPF 对等关系,并验证 OSPF 邻居状态是否等于Full。如果所有 OSPF 对等关系都处于Full状态,则任务将成功。但是,如果 OSPF 状态处于任何其他状态,则任务将失败。
还有更多...
如果我们需要以文本格式从 Juniper 设备获取操作数据以进行日志收集,我们可以使用junos_command模块,而不使用xml显示选项,如下所示在此新剧本中:
$ cat pb_collect_output.yml
---
- name: Collect Network Logs
hosts: junos
vars:
log_folder: "logs"
op_cmds:
- show ospf neighbor
tasks:
- name: "P1T1: Build Directories to Store Data"
block:
- name: "Create folder to store Device config"
file:
path: "{{ log_folder }}"
state: directory
run_once: yes
delegate_to: localhost
- name: "P1T2: Get Running configs from Devices"
junos_command:
commands: "{{ item }}"
loop: "{{ op_cmds }}"
register: logs_output
- name: "P1T3: Save Running Config per Device"
copy:
content: "{{ item.stdout[0] }}"
dest: "{{ log_folder }}/{{inventory_hostname}}_{{ item.item | regex_replace(' ','_') }}.txt"
loop: "{{ logs_output.results }}"
delegate_to: localhost
这个 playbook 将从所有设备收集show ospf neigbor命令,并将它们存储在一个名为logs的新文件夹中。运行 playbook 后,这是logs文件夹的内容:
$ tree logs
logs
├── mxp01_show_ospf_neighbor.txt
├── mxp02_show_ospf_neighbor.txt
├── mxpe01_show_ospf_neighbor.txt
└── mxpe02_show_ospf_neighbor.txt
我们可以检查其中一个文件的内容,以确认已捕获所需的输出:
$ cat logs/mxpe01_show_ospf_neighbor.txt
Address Interface State ID Pri Dead
10.1.1.2 ge-0/0/0.0 Full 10.100.1.254 128 35
10.1.1.8 ge-0/0/1.0 Full 10.100.1.253 128 37
使用 PyEZ 操作表验证网络状态
在这个配方中,我们将概述如何使用 Juniper 自定义 Ansible 模块来验证网络状态。我们将使用 Juniper PyEZ Python 库和 PyEZ 操作表和视图来验证 Junos OS 设备的操作状态。
准备工作
在遵循本配方时,Juniper 设备上必须启用 NETCONF。
如何操作...
- 安装
junos-ezncPython 包,如下所示:
$ pip3 install junos-eznc
- 使用
Ansible-galaxy安装Juniper.junosAnsible 角色,如下所示:
$ Ansible-galaxy install Juniper.junos
- 创建一个名为
pb_jnpr_pyez_table.yml的新 playbook,并填充以下任务以使用 PyEZ 表提取 BGP 对等信息:
$ cat pb_jnpr_pyez_table.yml
---
- name: Validate BGP State using PyEZ Tables
hosts: junos
roles:
- Juniper.junos
tasks:
- name: Retrieve BGP Neighbor Information Using PyEZ Table
Juniper_junos_table:
file: "bgp.yml"
register: jnpr_pyez_bgp
- 使用以下任务更新 playbook 以验证所有节点上的所有 BGP 对等体是否正常运行:
- name: Validate all BGP Peers are operational
assert:
that:
- item.peer in jnpr_pyez_bgp.resource | map(attribute='peer_id') | list
fail_msg: " BGP Peer {{ item.peer }} is Not Operational"
loop: "{{ bgp_peers }}"
工作原理...
除了我们在所有先前的配方中概述的预安装在 Ansible 中的内置 Juniper 模块之外,Juniper 还维护并不是 Ansible 发布的一部分的其他 Ansible 模块。这些模块打包在 Ansible Galaxy 中维护的 Ansible 角色中,所有这些模块都基于 Juniper PyEZ Python 库,该库也由 Juniper 开发和维护。
Juniper PyEZ Python 库提供了一个简单而强大的 API,以便与 Juniper 设备进行交互,并简化了使用 Python 管理 Juniper 设备的方法。由 Juniper 维护的 Ansible 模块都依赖于 PyEZ Python 库,因此我们需要执行的第一个任务是确保 PyEZ(junos-eznc)已安装在我们的 Ansible 控制机上。
由 Juniper 维护和开发的 Ansible 模块打包为 Ansible 角色,并且它们提供了多个具有额外功能的模块,与作为 Ansible 发布的一部分的内置 Juniper 模块相比。我们使用 Ansible Galaxy 安装此角色,以开始利用这些额外的模块。以下片段概述了此角色的额外模块:
$ tree ~/.Ansible/roles/Juniper.junos/library/
/home/Ansible/.Ansible/roles/Juniper.junos/library/
├── Juniper_junos_command.py
├── Juniper_junos_config.py
├── Juniper_junos_facts.py
├── Juniper_junos_jsnapy.py
├── Juniper_junos_ping.py
├── Juniper_junos_pmtud.py
├── Juniper_junos_rpc.py
├── Juniper_junos_software.py
├── Juniper_junos_srx_cluster.py
├── Juniper_junos_system.py
└── Juniper_junos_table.py
在这个配方中,我们概述了如何使用Juniper_junos_table Ansible 模块,该模块使用 PyEZ 表和视图来执行 Juniper 设备上的操作命令,并从 Juniper 设备中提取特定信息。它还将这些信息解析为一致的数据结构,我们可以在自动化脚本中利用。在我们的 playbook 中,我们的第一个任务是使用Juniper_junos_table模块,使用bgp.yml表定义(作为junos-eznc安装的一部分)来获取设备上的 BGP 对等体,并以一致的数据结构返回相关信息。以下片段概述了Juniper_junos_table返回的 BGP 数据,用于mxpe01上的 BGP 信息:
ok: [mxpe01] => {
"jnpr_pyez_bgp": {
"changed": false,
"failed": false,
"msg": "Successfully retrieved 1 items from bgpTable.",
"resource": [
{
"local_address": "10.100.1.1+179",
"local_as": "65400",
"local_id": "10.100.1.1",
"peer_as": "65400",
"peer_id": "10.100.1.254",
"route_received": [
"0",
"2",
"1",
"1"
]
}
],
}
}
我们 playbook 中的最后一个任务是使用assert模块来验证我们所有的 BGP 对等体(在host_vars目录下定义)是否存在于 BGP 表中返回的数据结构中,这表明所有的 BGP 对等体都是正常运行的。
另请参阅...
有关由 Juniper 维护的 Juniper Ansible 模块的更多信息,请参阅以下网址:www.juniper.net/documentation/en_US/junos-ansible/topics/reference/general/junos-ansible-modules-overview.html.
有关 PyEZ 表和视图的更多信息,请参考以下网址:www.Juniper.net/documentation/en_US/junos-pyez/topics/concept/junos-pyez-tables-and-views-overview.html。
第四章:使用 Arista 和 Ansible 构建数据中心网络
在本章中,我们将概述如何在典型的数据中心环境中自动化 Arista 交换机,采用叶脊架构。我们将探讨如何使用 Ansible 与 Arista 设备进行交互,以及如何使用各种 Ansible 模块在 Arista 交换机上部署虚拟局域网(VLANs)和虚拟可扩展局域网(VXLANs),并在 Arista 交换机上使用边界网关协议/以太网虚拟专用网络(BGP/EVPN)设置。我们将以以下示例网络图为基础,说明基本叶脊数据中心网络(DCN)的示例网络图:
以下表格概述了我们示例拓扑中的设备及其各自的管理互联网协议(IP):
| 设备 | 角色 | 供应商 | 管理(MGMT)端口 | MGMT IP |
|---|---|---|---|---|
| Spine01 | 脊柱交换机 | Arista vEOS 4.20 | Management1 | 172.20.1.35 |
| Spine02 | 脊柱交换机 | Arista vEOS 4.20 | Management1 | 172.20.1.36 |
| Leaf01 | 叶子交换机 | Arista vEOS 4.20 | Management1 | 172.20.1.41 |
| Leaf02 | 叶子交换机 | Arista vEOS 4.20 | Management1 | 172.20.1.42 |
| Leaf03 | 叶子交换机 | Arista vEOS 4.20 | Management1 | 172.20.1.43 |
| Leaf04 | 叶子交换机 | Arista vEOS 4.20 | Management1 | 172.20.1.44 |
本章涵盖的主要配方如下:
-
构建 Ansible 网络清单
-
连接到并使用 Ansible 对 Arista 设备进行身份验证
-
在 Arista 设备上启用可扩展操作系统(EOS)API(eAPI)
-
在 Arista 设备上配置通用系统选项
-
在 Arista 设备上配置接口
-
在 Arista 设备上配置底层 BGP
-
在 Arista 设备上配置覆盖 BGP/EVPN
-
在 Arista 设备上部署配置
-
在 Arista 设备上配置 VLAN
-
在 Arista 设备上配置 VXLAN 隧道
-
收集 Arista 设备信息
-
从 Arista 设备检索操作数据
技术要求
本章中所有配方的代码可以在以下 GitHub 存储库中找到:
github.com/PacktPublishing/Network-Automation-Cookbook/tree/master/ch4_arista.
本章基于以下软件版本:
-
运行 CentOS 7 的 Ansible 机器
-
Ansible 2.9
-
Arista 虚拟化 EOS(vEOS)运行 EOS 4.20.1F
观看以下视频以查看代码的实际操作:
构建 Ansible 网络清单
在本配方中,我们将概述如何构建和组织 Ansible 清单,以描述我们示例的叶脊直流(DC)网络。Ansible 清单是 Ansible 的重要组成部分,它描述并分组应由 Ansible 管理的设备。
准备工作
我们需要创建一个新的文件夹,用于存放本章中将创建的所有文件。新文件夹的名称应为ch4_arista。
如何做...
- 在新文件夹(
ch4_arista)中,我们创建一个带有以下内容的hosts文件:
$ cat hosts
[leaf]
leaf01 ansible_host=172.20.1.41
leaf02 ansible_host=172.20.1.42
leaf03 ansible_host=172.20.1.43
leaf04 ansible_host=172.20.1.44
[spine]
spine01 ansible_host=172.20.1.35
spine02 ansible_host=172.20.1.36
[arista:children]
leaf
spine
- 创建一个
ansible.cfg文件,如下面的代码块所示:
$ cat ansible.cfg
[defaults]
inventory=hosts
retry_files_enabled=False
gathering=explicit
host_key_checking=False
它是如何工作的...
定义 Ansible 清单是强制性的,以便描述和分类应由 Ansible 管理的网络中的设备。在 Ansible 清单中,我们还通过ansible_host参数指定 Ansible 将与这些受管设备通信的 IP 地址。
我们使用hosts文件构建了 Ansible 清单,并定义了多个组,以便对我们拓扑中的不同设备进行分组。这些组如下:
-
我们创建了
leaf组,它引用了我们拓扑中的所有leaf交换机。 -
我们创建了
spine组,它引用了我们拓扑中的所有spine交换机。 -
我们创建了
arista组,它引用了leaf和spine两个组。
最后,我们创建了ansible.cfg文件,并配置它指向我们的hosts文件,用作 Ansible 清单文件。此外,我们禁用了setup模块(通过将gathering设置为explicit),这在针对网络节点运行 Ansible 时是不需要的。
从 Ansible 连接和认证 Arista 设备
在这个示例中,我们将概述如何通过安全外壳(SSH)从 Ansible 连接到 Arista 设备,以便从 Ansible 开始管理这些设备。我们将使用用户名和密码来对我们拓扑中的 Arista 设备进行认证。
准备工作
为了按照这个示例进行操作,应该按照之前的示例构建一个 Ansible 清单文件。Ansible 控制机与网络中所有设备之间的 IP 可达性也必须得到实现。
操作步骤...
-
在
ch4_arista文件夹中创建一个group_vars文件夹。 -
在
group_vars文件夹中,创建一个名为arista.yml的文件,包含以下内容:
ansible_network_os: eos
ansible_connection: network_cli
ansible_user: ansible
ansible_ssh_pass: ansible123
- 在 Arista 交换机上,我们配置了用户名和密码,并启用了 SSH,如下所示:
!
username Ansible privilege 15 role network-admin secret sha512
$6$mfU4Ei0AORd6rage$5YObhOI1g0wNBK5onaKDpYJhLZ9138maJKgcOznzFdpM25Tf3rb0PWSojUSM
RQY0Y7.cexCFj5aFLY17tuNU1
!
!
management ssh
idle-timeout 300
authentication mode password
login timeout 300
!
- 在 Arista 交换机上,使用正确的 IP 地址配置管理接口,并将它们放置在所需的管理虚拟路由和转发(VRF)中,如下所示:
vrf definition MGMT
!
ip routing vrf MGMT
!
interface Management1
vrf forwarding MGMT
ip address *$Ansible_host$
* no lldp transmit
no lldp receive
!
工作原理...
我们在group_vars目录下的arista.yml文件中指定了我们将在所有 Arista 交换机上配置的用户名和密码。这将应用这些参数到我们清单中的所有 Arista 交换机。在 Arista 交换机上,我们设置了用户名和密码并启用了 SSH,并在管理接口上设置了正确的 IP 地址(在我们的清单中使用的ansible_host参数中使用的 IP 地址)。我们配置了管理 VRF,并将管理接口与此 VRF 关联起来。
我们在 Ansible 变量中以明文指定了 SSH 密码。这只适用于实验室设置;然而,在生产环境中,我们应该使用 Ansible vault 来保护任何敏感信息,就像在前面的章节中概述的那样。
在这个阶段,我们使用network_cli连接方法来使用 SSH 连接到 Arista 交换机。我们可以使用以下命令验证 Ansible 控制器是否能够到达并正确登录到设备:
$ ansible arista -m ping
leaf03 | SUCCESS => {
"changed": false,
"ping": "pong"
}
leaf04 | SUCCESS => {
"changed": false,
"ping": "pong"
}
<-- Output Omitted for brevity -->
在 Arista 设备上启用 eAPI
在这个示例中,我们将概述如何在 Arista 设备上启用 eAPI。eAPI 是 Arista 设备上的表述状态传输(RESTful)API,它简化了这些设备的管理,并提供了一个一致且强大的 API 来管理它们。这个任务非常关键,因为我们将在以后的示例中使用 eAPI 来管理 Arista 设备。
准备工作
作为这个示例的先决条件,必须存在一个 Ansible 清单文件。SSH 认证也应该已经部署并且正常工作,就像之前的示例一样。
操作步骤...
- 在
group_vars文件夹中创建一个名为all.yml的文件,其中包含以下管理 VRF 数据:
$ cat group_vars/all.yml
global:
mgmt_vrf: MGMT
- 创建一个名为
pb_eos_enable_eapi.yml的新 playbook,如下所示:
$ cat pb_eos_eanble_eapi.yml
---
- name: "Enable eAPI on Arista Switches"
hosts: arista
vars:
ansible_connection: network_cli
tasks:
- name: "Enable eAPI"
eos_eapi:
https_port: 443
https: yes
state: started
- 使用以下任务更新
pb_eos_enable_eapi.ymlplaybook,以在管理 VRF 下启用 eAPI:
- name: "Enable eAPI under VRF"
eos_eapi:
state: started
vrf: "{{global.mgmt_vrf}}"
- 在
group_vars文件夹中更新arista.yml文件,设置连接设置以使用 eAPI 作为连接插件:
$ cat group_vars/arista.yml
ansible_network_os: eos
ansible_connection: httpapi
ansible_httpapi_use_ssl: yes
ansible_httpapi_validate_certs: no
工作原理...
为了开始通过 eAPI 与 Arista 设备进行交互,我们首先需要启用它;因此,我们需要首先通过 SSH 登录到设备并启用 eAPI。这就是为什么在这个示例中,我们使用network_cli Ansible 连接来通过传统的 SSH 连接到 Arista 设备。由于我们将在所有未来的示例中使用 eAPI 与 Arista 设备进行交互,我们只在 playbook 级别的vars参数下启用了network_cli,以覆盖ansible_connection设置的任何组或主机级别的设置。
我们创建了一个名为pb_eos_enable_eapi.yml的新 playbook,在第一个任务中,我们使用了eos_eapi模块来在远程 Arista 设备上启用 eAPI 协议。我们指定将使用超文本传输安全协议(HTTPS)和标准的 HTTPS 端口443。在第二个任务中,我们使用了eos_eapi模块,以在特定的 VRF 下仅启用 eAPI,这是我们用来管理设备的管理 VRF。
最后,为了开始使用 eAPI 管理 Arista 设备,我们修改了 Ansible 连接设置,这些设置我们在group_vars/arista.yml文件中定义,并包括以下设置:
-
ansible_connection设置为httpapi。 -
ansible_httpapi_use_ssl设置为yes,以强制使用 HTTPS 而不是 HTTP。 -
ansible_httpapi_validate_certs设置为no,以禁用证书验证(因为我们使用的是 Arista 设备上的默认证书,该证书未经受信任的证书颁发机构(CA)签名)。
一旦我们运行了 playbook,我们将看到所有 Arista 设备都配置了 eAPI,如下面的代码块所示:
!
management api http-commands
no shutdown
!
vrf MGMT
no shutdown
!
我们可以通过以下命令验证我们是否使用了正确的连接设置,并且 Ansible 能够使用 eAPI 与 Arista 设备进行通信:
$ ansible all -m ping -l leaf01 -vvvv
<172.20.1.41> attempting to start connection
<172.20.1.41> using connection plugin httpapi
<172.20.1.41> loaded API plugin for network_os eos
**<172.20.1.41> ESTABLISH HTTP(S) CONNECTFOR USER: ansible TO** https://172.20.1.41:443
另请参阅...
有关eos_eapi模块以及该模块支持的不同参数的更多信息,请参考以下网址:docs.ansible.com/ansible/latest/modules/eos_eapi_module.html。
在 Arista 设备上配置通用系统选项
在这个示例中,我们将概述如何配置一些基本的系统选项,比如主机名和域名系统(DNS)服务器,并在 Arista 设备上配置用户。我们将了解如何使用各种 Ansible 模块设置所有这些系统级参数,并概述管理这些参数的不同方法。
准备工作
要按照这个示例进行操作,假定已经设置了 Ansible 清单,并且根据之前的示例在所有 Arista 设备上启用了 eAPI。
如何做...
- 更新
group_vars/all.yml文件,使用通用系统参数,如下面的代码块所示:
$ cat group_vars/all.yml
<-- Output Omitted for brevity -->
global:
dns:
- 172.20.1.1
- 172.20.1.15
site: DC1
users:
- password: ansible123
privilege: 15
role: network-admin
username: ansible
- 创建一个新的 playbook,
pb_arista_basic_config.yml,并添加以下任务来设置 DNS 和主机名:
$ cat pb_arista_basic_config.yml --- - name: "Configure Basic Configuration on Arista Fabric"
hosts: arista tasks: - name: "Conifgure Basic System config" eos_system: hostname: " {{global.site|lower}}-{{inventory_hostname}}" name_servers: "{{ global.dns }}" state: present
- 更新
pb_arista_basic_config.ymlplaybook,添加以下任务以在 Arista 设备上创建用户:
- name: "Configure Users"
eos_user:
name: "{{ item.username }}"
role: "{{ item.role | default('network-admin') }}"
privilege: "{{ item.privilege | default(15)}}"
configured_password: "{{ item.password }}"
state: present
loop: "{{ global.users }}"
工作原理...
Ansible 提供了不同的声明性模块,以管理 Arista 交换机上的不同资源。在这个示例中,我们概述了如何使用eos_system和eos_user Ansible 模块来配置 Arista 设备上的基本系统属性。我们首先在group_vars/all.yml文件中定义了要使用的数据,并包括我们想要配置的 DNS 和用户。我们创建了pb_arista_basic_config.yml playbook,其中包括了设置 Arista 交换机上基本设置所需的所有任务。
playbook 中的第一个任务使用了eos_system Ansible 模块,在所有 Arista 设备上设置了 DNS 和主机名。第二个任务使用了eos_user Ansible 模块,在 Arista 交换机上设置了系统用户。在最后一个任务中,我们循环遍历了在group_vars/all.yml文件中定义的users数据结构,以在这个list数据结构中为每个用户进行配置。
一旦我们运行了 playbook,我们可以看到 Arista 交换机的配置已更新,如下面的代码块所示:
!
hostname dc1-leaf01
ip name-server vrf default 172.20.1.1
ip name-server vrf default 172.20.1.15
!
还有更多...
在本节中,我们概述了声明性的 Ansible 模块,它们提供了一种简单的方法来配置 Arista 设备的基本系统级参数;但是,它们可能无法涵盖我们需要在 Arista 交换机上设置的所有参数。为了更多地控制和灵活地配置系统级参数,我们可以使用 Jinja2 模板以及template Ansible 模块来生成我们部署所需的特定系统级配置。在本节中,我们将概述这种方法以实现这一目标。这将是我们在后续的配方中使用的方法,用于生成其他配置部分的配置,这些部分没有内置的 Ansible 模块可以满足我们的所有要求。
我们将重用这种方法为 Arista 设备生成不同部分的配置,例如系统、接口和 BGP。我们将创建一个 Ansible 角色,以包含生成我们将推送到设备的最终配置所需的所有 Jinja2 模板和任务。以下过程概述了创建角色所需的步骤以及生成配置所需的 playbook:
我们将使用与第三章中使用的相同的角色结构和任务,使用 Ansible 自动化服务提供商中的 Juniper 设备,来生成 Juniper 设备的配置。唯一的区别在于我们将使用 Jinja2 模板来生成 Arista 设备的特定配置。
- 创建一个新的
roles目录,并添加一个名为dc_fabirc_config的新角色,具有以下目录结构:
$ tree roles/
roles/
└── dc_fabric_config
├── tasks
└── templates
- 在
tasks文件夹中,创建一个build_config_dir.yml文件,以创建所需的文件夹来存储将生成的配置,如下所示:
$ cat roles/dc_fabric_config/tasks/build_config_dir.yml
---
- name: Create Config Directory
file: path={{config_dir}} state=directory
run_once: yes
- name: Create Temp Directory per Node
file: path={{tmp_dir}}/{{inventory_hostname}} state=directory
- name: SET FACT >> Build Directory
set_fact:
build_dir: "{{tmp_dir}}/{{inventory_hostname}}"
- 在
templates文件夹中,创建一个名为eos的新文件夹,在该文件夹中创建一个名为mgmt.j2的新的 Jinja2 模板,如下面的代码块所示:
$ cat roles/dc_fabric_config/templates/eos/mgmt.j2
!
hostname {{global.site|lower}}-{{inventory_hostname}}
!
!
spanning-tree mode none
!
aaa authorization exec default local
!
{% for user in global.users%}
username {{user.name}} privilege {{user.privilege}} role
{{user.role|default('network-admin')}} secret {{user.password}}
{% endfor%}
!
{% for dns_server in global.dns%}
ip name-server vrf default {{ dns_server }}
{% endfor %}
!
- 在
tasks文件夹中,创建一个名为build_device_config.yml的新的 YAML 文件,以创建系统配置,如下面的代码块所示:
$ cat roles/dc_fabric_config/tasks/build_device_config.yml
---
- name: "System Configuration"
template:
src: "{{ansible_network_os}}/mgmt.j2"
dest: "{{build_dir}}/00_mgmt.cfg"
tags: mgmt
- 在
tasks文件夹中创建一个main.yml文件,其中包含以下任务:
$ cat roles/build_router_config/tasks/main.yml
---
- name: Build Required Directories
import_tasks: build_config_dir.yml
- name: Build Device Configuration
import_tasks: build_device_config.yml
- name: "Remove Old Assembled Config"
file:
path: "{{config_dir}}/{{ inventory_hostname }}.cfg"
state: absent
- name: Build Final Device Configuration
assemble:
src: "{{ build_dir }}"
dest: "{{config_dir}}/{{ inventory_hostname }}.cfg"
- name: Remove Build Directory
file: path={{ tmp_dir }} state=absent
run_once: yes
- 创建一个名为
pb_arista_dc_fabric.yml的新 playbook,以生成清单中所有arista设备的配置:
$ cat pb_arista_dc_fabric.yml
---
- name: "Build Arista DC Fabric"
hosts: arista
tasks:
- name: Generate DC Fabric Configuration
import_role:
name: dc_fabric_config
delegate_to: localhost
使用这种方法,我们创建了一个名为dc_fabric_config的角色,并创建了一个名为mgmt.j2的新的 Jinja2 模板,其中包含arista系统级配置的模板。我们使用template Ansible 模块来使用在group_vars/all.yml文件中定义的 Ansible 变量渲染 Jinja2 模板。为了保存每个设备的配置,我们创建了configs文件夹目录,用于存储每个设备的最终配置。
由于我们正在利用 Jinja2 方法来为每个部分(MGMT、接口、BGP 等)生成配置,我们将每个部分分成单独的 Jinja2 模板,并将每个部分生成到单独的文件中。我们使用assemble模块来将所有这些不同的部分组合成单个配置文件,我们将其存储在configs目录中,这是每个设备的最终和组装的配置文件。我们将每个设备的临时组装部分存储在临时文件夹中,并在 playbook 运行结束时删除此临时文件夹。
在此 playbook 中,我们在import_role任务中使用delegate_to localhost。由于在此角色中的所有任务中我们不需要连接到远程设备,因此所有这些任务应在 Ansible 控制机上运行,以便将文件存储在 Ansible 机器上。因此,我们使用delegate_to localhost 来在 Ansible 控制机上运行所有任务。
一旦我们运行pb_junos_net_build.yml playbook,我们可以看到在configs目录中创建了以下配置文件,目前只有配置的管理部分:
$ tree configs/
configs/
├── leaf01.cfg
├── leaf02.cfg
├── leaf03.cfg
├── leaf04.cfg
├── spine01.cfg
└── spine02.cfg
我们可以查看为其中一个设备(例如leaf01)生成的配置,如下面的代码块所示:
!
hostname dc1-leaf01
!
snmp-server enable traps
!
spanning-tree mode none
!
aaa authorization exec default local
!
username ansible privilege 15 role network-admin secret ansible123
!
ip name-server vrf default 172.20.1.1
ip name-server vrf default 172.20.1.15
!
在这个阶段,我们已经为清单中的所有arista交换机生成了系统配置;然而,我们仍然没有将这个配置推送到设备上。在后续的配方中,我们将概述如何将配置推送到arista设备。
在 Arista 设备上配置接口
在这个配方中,我们将概述如何在 Arista 设备上配置不同的接口参数,比如接口描述和 IP 地址信息。我们将概述如何使用各种 Ansible 模块与 Arista 设备上的接口进行交互,以及如何在样本网络拓扑中的所有 Arista 设备上设置接口。
准备工作
我们假设网络清单已经就位,并且在 Arista 交换机上已经启用了 eAPI,就像之前的配方一样。
操作步骤...
- 在
group_vars/all.yml文件中添加以下内容,描述样本 DC 网络中的接口:
p2p_ip:
leaf01:
- {port: Ethernet8, ip: 172.31.1.1 , peer: spine01, pport: Ethernet1, peer_ip: 172.31.1.0}
- {port: Ethernet9, ip: 172.31.1.11 , peer: spine02, pport: Ethernet1, peer_ip: 172.31.1.10}
leaf02:
< -- Output Omitted for brevity -->
leaf03:
< -- Output Omitted for brevity -->
leaf04:
< -- Output Omitted for brevity -->
spine01:
< -- Output Omitted for brevity -->
spine02:
< -- Output Omitted for brevity -->
lo_ip:
leaf01: 10.100.1.1/32
leaf02: 10.100.1.2/32
leaf03: 10.100.1.3/32
leaf04: 10.100.1.4/32
spine01: 10.100.1.254/32
spine02: 10.100.1.253/32
- 更新
pb_arista_basic_config.ymlplaybook,添加以下任务以启用接口并在所有布线接口上设置描述:
- name: "Configure the Physical Interfaces"
eos_interface:
name: "{{ item.port }}"
enabled: true
description: "{{global.site}} | Rpeer:{{item.peer}} | Rport:{{item.pport}}"
with_items: "{{p2p_ip[inventory_hostname]}}"
- 更新
pb_arista_basic_config.ymlplaybook,添加以下任务以在所有点对点(P2P)布线链路上设置 IPv4 地址:
- name: "Configure IP Addresses"
eos_l3_interface:
name: "{{ item.port }}"
ipv4: "{{ item.ip }}/{{ global.p2p_prefix }}"
state: present
with_items: "{{ p2p_ip[inventory_hostname] }}"
工作原理...
我们在group_vars/all.yml文件中的两个主要数据结构中定义了样本网络拓扑中所有接口的所有数据。我们使用p2p_ip字典来模拟样本网络中所有 P2P IP 地址,并使用lo_ip字典来指定节点的环回 IP 地址。
我们使用eos_interface Ansible 模块来启用接口并设置接口的基本参数,比如接口描述。我们遍历了每个设备的p2p_ip数据结构,并为网络清单中的所有设备上的每个接口设置了正确的参数。我们使用eos_l3_interface Ansible 模块在样本网络拓扑中的所有设备上设置了正确的 IPv4 地址。
更多信息...
如果我们需要更多地控制接口配置,并设置声明性 Ansible 模块中未涵盖的参数,我们可以使用 Jinja2 模板来实现这个目标。使用与前一节中系统配置相同的方法,我们可以为 Juniper 设备生成所需的接口配置。
使用前一节中创建的相同 Ansible 角色,我们可以扩展它以生成 Arista 设备的接口配置。我们使用以下步骤来完成这项任务:
- 在
templates文件夹中创建一个名为intf.js的新 Jinja2 模板文件,内容如下:
$ cat roles/dc_fabric_config/templates/eos/intf.j2
{% set node_intfs = p2p_ip[inventory_hostname] %}
{% for p in node_intfs| sort(attribute='port') %}
!
interface {{p.port}}
description "{{global.site}} | Rpeer: {{p.peer}} | Rport: {{p.pport}}"
no switchport
ip address {{p.ip}}/{{global.p2p_prefix}}
{% endfor %}
!
!
interface Loopback0
ip address {{lo_ip[inventory_hostname]}}
!
- 在
tasks目录中更新build_device_config.yml文件,添加新任务以生成接口配置:
$ cat roles/dc_fabric_config/tasks/build_device_config.yml
<-- Output Trimmed for brevity ------>
- name: "Interface Configuration"
template:
src: "{{ansible_network_os}}/intf.j2"
dest: "{{build_dir}}/01_intf.cfg"
tags: intf
- 一旦我们运行
pb_arista_dc_fabric.ymlplaybook,我们将为我们的设备生成配置,例如为leaf01更新了interface部分:
$ cat configs/leaf01.cfg
< -- Output Omitted for brevity -->
!
interface Ethernet8
description "DC1 | Rpeer: spine01 | Rport: Ethernet1"
no switchport
ip address 172.31.1.1/31
!
interface Ethernet9
description "DC1 | Rpeer: spine02 | Rport: Ethernet1"
no switchport
ip address 172.31.1.11/31
!
!
interface Loopback0
ip address 10.100.1.1/32
!
另请参阅...
有关eos_interface模块以及该模块支持的不同参数的更多信息,请参考以下网址:docs.ansible.com/ansible/latest/modules/eos_interface_module.html。
有关eos_l3_interface模块以及此模块支持的不同参数的更多信息,请参阅以下网址:docs.ansible.com/ansible/latest/modules/eos_l3_interface_module.html。
在 Arista 设备上配置底层 BGP
在这个教程中,我们将概述如何为我们样例叶/脊柱 DC 结构配置 eBGP 作为底层路由协议。我们将建立 eBGP 对等设置,使用叶交换机和脊柱交换机之间的 P2P IP 地址。BGP 自治系统号(ASN)分配如下表所示:
| 节点 BGP ASN |
|---|
| Spine01 65100 |
| Spine02 65100 |
| Leaf01 65001 |
| Leaf02 65002 |
| Leaf03 65003 |
| Leaf04 65004 |
准备工作
在这个教程中,我们假设接口和 IP 地址信息已根据以前的教程进行了配置。
操作步骤...
- 创建一个
host_vars目录,并为清单中的每个设备创建一个文件夹。在每个文件夹中,创建一个名为underlay_bgp.yml的新的 YAML 文件,其中包含 BGP 对等详细信息。以下是我们清单中leaf01设备的一个示例:
## Leaf01 BGP Data ###
bgp_asn: 65001
bgp_peers:
- peer: spine01
peer_ip: 172.31.1.0
remote_as: 65100
- peer: spine02
peer_ip: 172.31.1.10
remote_as: 65100
- 在
templates/eos目录中创建一个名为underlay_bgp.j2的新的 Jinja2 文件,其中包含以下数据。这个模板是我们将用来控制 DC 结构中 BGP 广播的prefix-list:
$ cat roles/dc_fabric_config/templates/eos/underlay_bgp.j2 {% set bgp_grp = 'LEAF' if 'spine' in inventory_hostname else 'SPINE' %}
!
route-map loopback permit 10
match ip address prefix-list loopback
!
{% if 'spine' in inventory_hostname %}
!
ip prefix-list loopback
{% for node,ip in lo_ip.items() | sort %}
{% if 'leaf' in node or inventory_hostname in node %}
seq {{loop.index + 10 }} permit {{ip}}
{% endif %}
{% endfor %}
!
{% else %}
!
ip prefix-list loopback
seq 10 permit {{lo_ip[inventory_hostname]}}
!
{% endif %}
- 在
templates/eos目录中更新underlay_bgp.j2Jinja2 文件,其中包含以下代码块中显示的 BGP 模板:
$ cat roles/dc_fabric_config/templates/eos/underlay_bgp.j2
!
router bgp {{bgp_asn}}
router-id {{lo_ip[inventory_hostname].split('/')[0]}}
maximum-paths 2
bgp bestpath tie-break router-id
neighbor {{ bgp_grp }} peer-group
neighbor {{ bgp_grp }} description "Peer Group for All {{bgp_grp}} Nodes"
neighbor {{ bgp_grp }} graceful-restart-helper
neighbor {{ bgp_grp }} send-community standard extended
neighbor {{ bgp_grp }} maximum-routes 100000 warning-only
{% for p in bgp_peers %}
neighbor {{ p.peer_ip}} peer-group {{ bgp_grp }}
neighbor {{ p.peer_ip}} remote-as {{p.remote_as}}
{% endfor %}
redistribute connected route-map loopback
!
address-family ipv4
neighbor {{ bgp_grp }} activate
neighbor {{ bgp_grp }} route-map loopback out
!
- 在
tasks文件夹中的build_config.yml文件中,添加以下任务以呈现底层 BGP 配置:
$ cat roles/dc_fabric_config/tasks/build_device_config.yml
< -- Output Omitted for brevity -->
- name: "Underlay BGP Configuration"
template:
src: "{{ansible_network_os}}/underlay_bgp.j2"
dest: "{{config_dir}}/{{ inventory_hostname }}/03_bgp.cfg"
工作原理...
根据我们的设计,我们将在叶和脊柱节点之间运行 eBGP,并且我们拓扑中的每个叶交换机都将有自己的 BGP ASN。描述这种设置的最佳方法是使用host_vars文件夹在每个主机基础上包含所有这些数据。我们为每个节点创建了一个文件夹,以在此文件夹下包含所有相关的主机数据。我们创建了一个 YAML 文件来保存每个设备的 BGP 信息,因此如果需要为另一个协议添加更多主机特定数据,我们可以轻松地添加一个新文件:
$ tree host_vars
host_vars
├── leaf01
│ └── underlay_bgp.yml
├── leaf02
│ └── underlay_bgp.yml
├── leaf03
│ └── underlay_bgp.yml
├── leaf04
│ └── underlay_bgp.yml
├── spine01
│ └── underlay_bgp.yml
└── spine02
└── underlay_bgp.yml
在tasks/build_device_config.yml文件中,我们添加了一个新任务,该任务使用underlay_bgp.j2 Jinja2 模板来呈现 Jinja2 模板,并输出我们 Ansible 清单中每个设备的底层 BGP 配置部分。
对于每个设备,我们生成了一个prefix-list来匹配将广播到其 eBGP 对等体的所有前缀,具体标准如下:
-
对于脊柱交换机,我们会广播所有叶回环 IP 地址以及脊柱回环接口。
-
对于叶交换机,我们只会广播回环 IP 地址。
在运行新任务的 playbook 后,以下片段概述了leaf01设备生成的 BGP 配置:
$ cat configs/leaf01/04_bgp.cfg
!
route-map loopback permit 10
match ip address prefix-list loopback
!
ip prefix-list loopback
seq 10 permit 10.100.1.1/32
!
router bgp 65001
router-id 10.100.1.1
maximum-paths 2
bgp bestpath tie-break router-id
neighbor SPINE peer-group
neighbor SPINE description "Peer Group for All SPINE Nodes"
neighbor SPINE graceful-restart-helper
neighbor SPINE send-community standard extended
neighbor SPINE maximum-routes 100000 warning-only
neighbor 172.31.1.0 peer-group SPINE
neighbor 172.31.1.0 remote-as 65100
neighbor 172.31.1.10 peer-group SPINE
neighbor 172.31.1.10 remote-as 65100
redistribute connected route-map loopback
!
address-family ipv4
neighbor SPINE activate
neighbor SPINE route-map loopback out
!
在 Arista 设备上配置覆盖 BGP EVPN
在这个教程中,我们将概述如何使用 Ansible 在我们的样例拓扑中为 VXLAN 隧道的叶脊柱 DC 结构配置覆盖 BGP EVPN 作为控制平面。
准备工作
这个教程假设 P2P IP 地址和回环接口已根据以前的教程进行了配置。此外,底层 BGP 配置应该已经根据以前的教程生成。
操作步骤...
- 在
templates/eos目录中创建一个名为overlay_bgp.j2的新的 Jinja2 文件,其中包含以下数据:
$ cat roles/dc_fabric_config/templates/eos/overlay_bgp.j2
{% set bgp_evpn_grp = 'LEAF_EVPN' if 'spine' in inventory_hostname else 'SPINE_EVPN' %}
service routing protocols model multi-agent
!
router bgp {{bgp_asn}}
neighbor {{ bgp_evpn_grp }} peer-group
neighbor {{ bgp_evpn_grp }} description "Peer Group for All {{bgp_evpn_grp}} EVPN Nodes"
neighbor {{ bgp_evpn_grp }} graceful-restart-helper
neighbor {{ bgp_evpn_grp }} send-community extended
neighbor {{ bgp_evpn_grp }} maximum-routes 100000 warning-only
neighbor {{ bgp_evpn_grp }} ebgp-multihop 2
neighbor {{ bgp_evpn_grp }} update-source Loopback0
{% for p in bgp_peers %}
neighbor {{ lo_ip[p.peer].split('/')[0]}} peer-group {{ bgp_evpn_grp }}
neighbor {{ lo_ip[p.peer].split('/')[0]}} remote-as {{p.remote_as}}
{% endfor %}
!
address-family evpn
neighbor {{ bgp_evpn_grp }} activate
!
address-family ipv4
no neighbor {{ bgp_evpn_grp }} activate
!
- 在
tasks文件夹中的build_config.yml文件中,添加以下突出显示的任务:
$ cat tasks/build_config.yml
< -- Output Omitted for brevity -->
- name: "Overlay BGP EVPN Configuration"
template:
src: "{{ansible_network_os}}/overlay_bgp.j2"
dest: "{{config_dir}}/{{ inventory_hostname }}/04_evpn.cfg"
工作原理...
在这个教程中,我们使用了类似的方法来配置底层 eBGP 的方法。我们构建了一个 Jinja2 模板,用于为清单中的 Arista 设备生成所需的 BGP EVPN 配置。以下代码块显示了leaf01交换机的 BGP EVPN 配置示例:
service routing protocols model multi-agent
!
router bgp 65001
neighbor SPINE_EVPN peer-group
neighbor SPINE_EVPN description "Peer Group for All SPINE_EVPN EVPN Nodes"
neighbor SPINE_EVPN graceful-restart-helper
neighbor SPINE_EVPN send-community extended
neighbor SPINE_EVPN maximum-routes 100000 warning-only
neighbor SPINE_EVPN ebgp-multihop 2
neighbor SPINE_EVPN update-source Loopback0
neighbor 10.100.1.254 peer-group SPINE_EVPN
neighbor 10.100.1.254 remote-as 65100
neighbor 10.100.1.253 peer-group SPINE_EVPN
neighbor 10.100.1.253 remote-as 65100
!
address-family evpn
neighbor SPINE_EVPN activate
!
address-family ipv4
no neighbor SPINE_EVPN activate
!
在 Arista 设备上部署配置
在这个教程中,我们将概述如何将配置推送到 Arista 设备。我们将使用在之前的教程中生成的配置来为我们的拓扑中的设备进行配置。我们将学习如何使用适当的 Ansible 模块与 Arista 配置进行交互,以便根据预期的网络设计正确配置设备。
准备工作
这个教程要求在 Arista 设备上启用 eAPI。
如何做...
在pb_arista_dc_fabric.yml文件中,添加以下任务来将配置部署到 Arista 交换机:
- name: "Deploy Configuration"
eos_config:
src: "{{config_dir}}/{{ inventory_hostname }}.cfg"
replace: config
save_when: changed
tags: deploy
工作原理...
在之前的教程中,我们生成了 Arista 交换机配置的不同部分,比如接口和底层/覆盖 BGP。我们使用assemble Ansible 模块将配置的不同部分组合成一个包含所有设备配置的单个配置文件。在这个教程中,我们使用了eos_config模块来将配置文件推送到 Arista 交换机。
在eos_config模块中,我们使用了src参数来指定我们想要加载到设备中的配置文件的位置。我们使用了replace指令和config选项来替换目标设备上的所有配置,使用src选项中指定的新配置。因此,设备上的配置完全由 Ansible 管理和控制。这也意味着,如果有任何在 Ansible playbook 之外实施的配置,一旦我们运行 playbook 并将新配置推送到设备上,该配置将被删除。
最后,我们使用save_when参数并将其设置为changed,以便将运行配置复制到startup-config并保存配置。只有在任务改变了设备上的配置时,我们才执行此操作。
另请参阅...
有关eos_config模块和该模块支持的不同参数的更多信息,请参考以下网址:docs.ansible.com/ansible/latest/modules/eos_config_module.html。
在 Arista 设备上配置 VLAN
在这个教程中,我们将概述如何在 Arista 交换机上配置 VLAN。我们将在数据中心网络中构建的 VLAN 如下表所示:
| 节点 | 接口 | 接口类型 | VLAN |
|---|---|---|---|
| Leaf01 | 以太网 1 | 接入 | 10 |
| Leaf02 | 以太网 1 | 接入 | 20 |
| Leaf03 | 以太网 1 | 接入 | 10 |
| Leaf03 | 以太网 2 | 接入 | 20 |
| Leaf04 | 以太网 1 | 接入 | 10 |
| Leaf04 | 以太网 2 | 接入 | 20 |
准备工作
这个教程假设底层和覆盖 BGP 配置已经按照之前的教程生成。
如何做...
- 创建一个名为
vlan_design.yml的新的 YAML 文件,用于保存我们数据中心网络设备的 VLAN 设计,如下所示的代码块:
$ cat vlan_design.yml
vlan_data:
leaf01:
- id: 10
description: DB
ports:
- Ethernet1
leaf02:
- id: 20
description: web
ports:
- Ethernet1
< -- Output Omitted for brevity -->
- 在
roles文件夹中创建一个新的角色provision_vlans,结构如下:
$ tree roles/provision_vlans/
roles/provision_vlans/
├── tasks
│ └── main.yml
├── templates
└── vars
└── main.yml
- 在
tasks/main.yml文件中,包括以下任务来配置我们数据中心网络中的 VLAN:
$ cat roles/provision_vlans/tasks/main.yml
---
- name: Deploy VLANs on DC Fabric
eos_vlan:
name: "VLAN_{{vlan.id}}_{{ vlan.description }}"
vlan_id: "{{ vlan.id }}"
state: present
interfaces: "{{ vlan.ports }}"
loop: "{{ vlan_data[inventory_hostname] }}"
loop_control:
loop_var: vlan
tags: vlans
- 创建一个新的 playbook,
pb_deploy_vlans.yml,使用该角色来为我们的数据中心网络中的 VLAN 进行配置,如下所示的代码块:
$ cat pb_deploy_vlans.yml
---
- name: Provision VLANs on DC Fabric
hosts: arista
vars_files: vlan_design.yml
tasks:
- name: Deploy Vlans on DC Fabric
import_role:
name: provision_vlans
when: inventory_hostname in vlan_data.keys()
工作原理...
为了在我们的数据中心网络中为 VLAN 进行配置,我们在一个名为vlan_design.yml的 YAML 文件中对我们的 VLAN 成员资格进行了建模和定义。该文件对我们网络中所有交换机上的所有 VLAN 进行了建模,存储在vlan_data字典中。该字典中的每个键都是设备,值是一个字典列表,每个字典对应一个单独的 VLAN 定义。
我们创建了一个特定的角色provision_vlans,用于在我们的网络中为 VLAN 进行配置,该角色中的初始任务使用了eos_vlan Ansible 模块来配置 VLAN。我们循环遍历每个节点特定的vlan_data并为这些 VLAN 进行配置。
我们创建了一个pb_deploy_vlans.yml playbook,使用这个角色来部署 VLAN。我们使用vars_files参数读取vlan_design.yml文件,并使用import_roles导入provision_vlans角色。我们使用when指令,只在我们 VLAN design文件中定义的设备上调用这个角色。
一旦我们运行我们的 playbook,我们可以看到 VLAN 已经部署到我们的 fabric 中,例如在leaf03中:
dc1-leaf03#sh vlan
VLAN Name Status Ports
----- -------------------------------- --------- -------------------------
1 default active Et3, Et4, Et5, Et6, Et7
10 VLAN_10_DB active Et1
20 VLAN_20_web active Et2
另请参阅...
有关eos_vlan模块以及此模块支持的不同参数的更多信息,请参阅以下网址:docs.ansible.com/ansible/latest/modules/eos_vlan_module.html。
在 Arista 设备上配置 VXLAN 隧道
在这个配方中,我们将概述如何使用 BGP EVPN 配置 VXLAN 隧道,跨我们的叶脉冲 fabric。在类似我们示例拓扑的 IP fabric 中,我们需要有 VXLAN 隧道来传输 L2 VLANs。以下表格概述了我们将在整个 fabric 中使用的 VLAN 到虚拟网络标识符(VNI)映射:
| VLAN | VNI |
|---|---|
| 10 | 1010 |
| 20 | 1020 |
准备工作
这个配方假设 BGP EVPN 已经部署在我们的 fabric 中,并且所有 VLAN 已经配置。
如何做...
- 在
provision_vlans角色的vars/main.yml文件中更新以下变量。这将定义存储 VXLAN 配置的目录:
$ cat roles/provision_vlans/vars/main.yml
---
config_dir: ./vxlan_configs
- 在我们的
provision_vlans角色中,创建一个templates文件夹。然后,在其中创建一个eos文件夹。之后,创建一个 Jinja2vxlan.j2文件,内容如下:
$ cat roles/provision_vlans/templates/eos/vxlan.j2
{% set vlans = vlan_data[inventory_hostname] %}
{% set all_vlans = vlans | map(attribute='id') | list %}
!
interface Vxlan1
vxlan source-interface Loopback0
{% for vlan in all_vlans %}
vxlan vlan {{ vlan }} vni 10{{vlan}}
{% endfor %}
!
router bgp {{bgp_asn}}
!
{% for vlan in all_vlans %}
vlan {{ vlan }}
rd {{lo_ip[inventory_hostname].split('/')[0]}}:10{{vlan}}
route-target both 10{{vlan}}:10{{vlan}}
redistribute learned
{% endfor %}
!
- 在
provision_vlans角色的tasks/main.yml文件中更新以下任务,以生成 VXLAN 配置:
- name: Create VXLAN Configs Folder
file: path={{config_dir}} state=directory
run_once: yes
delegate_to: localhost
tags: vxlan
- name: "Create VXLAN Configuration"
template:
src: "{{ansible_network_os}}/vxlan.j2"
dest: "{{config_dir}}/{{ inventory_hostname }}.cfg"
delegate_to: localhost
tags: vxlan
- 使用以下任务更新
tasks/main.yml文件,以在我们的 DC fabric 交换机上部署 VXLAN 配置:
- name: "Deploy Configuration"
eos_config:
src: "{{config_dir}}/{{ inventory_hostname }}.cfg"
save_when: changed
tags: vxlan
工作原理...
在上一个配方中,我们概述了如何在 DC fabric 中部署 VLAN。然而,在 IP fabric 中,我们需要有隧道来传输 L2 VLANs。在这个配方中,我们概述了如何使用 BGP EVPN 建立 VXLAN 隧道,以传输 L2 VLANs 并完成 DC fabric 中的 VLAN 配置任务。
由于 VXLAN 隧道与相应的 VLAN 紧密耦合,我们在provision_vlans角色中包括了 VXLAN 隧道的设置。我们使用 Jinja2 模板和template Ansible 模块来生成每个交换机上所需的 VXLAN 和 BGP 配置。我们创建了一个新的文件夹来存放我们将为每个交换机生成的 VXLAN 配置。我们利用template Ansible 模块,使用 Jinja2 模板渲染我们在vlan_design.yml文件中定义的 VLAN 数据,以生成每个交换机的 VXLAN 配置。
一旦我们运行更新后的 playbook,我们可以看到新的文件夹被创建,并且为所有交换机生成了配置:
$ tree vxlan_configs/
vxlan_configs/
├── leaf01.cfg
├── leaf02.cfg
├── leaf03.cfg
└── leaf04.cfg
以下代码块显示了为leaf01交换机生成的 VXLAN 配置的示例配置:
$ cat vxlan_configs/leaf01.cfg
interface Vxlan1
vxlan source-interface Loopback0
vxlan udp-port 4789
vxlan vlan 10 vni 1010
!
router bgp 65001
!
vlan 10
rd 10.100.1.1:1010
route-target both 1010:1010
redistribute learned
!
收集 Arista 设备事实
在这个配方中,我们将概述如何检索由 Ansible 收集的 Arista 设备的基本系统事实,运行 Arista EOS 软件。这些基本系统事实为我们提供了关于 Arista 设备的基本健康检查,我们可以用来验证其操作状态。
准备工作
在 Arista 设备上必须启用 eAPI,以便在这个配方中使用 Ansible 模块。
如何做...
- 创建一个新的 playbook,
pb_arista_facts.yml,包含以下任务来收集事实:
$ cat pb_jnpr_facts.yml
---
- name: Collect and Validate Arista DC Fabric Facts
hosts: arista
tasks:
- name: Collect Arista Device Facts
eos_facts:
- 使用以下任务更新
pb_arista_facts.ymlplaybook,以验证所有 fabric 接口的操作状态:
- name: Validate all DC Fabric Interface are Operational
assert:
that:
- ansible_net_interfaces[item.port].lineprotocol == 'up'
fail_msg: "Interface {{item.port}} is not Operational "
loop: "{{ p2p_ip[inventory_hostname] }}"
- 更新 playbook,添加以下任务来验证所有布线接口的正确 IP 地址分配:
- name: Validate all DC Fabric Interface are has Correct IP
assert:
that:
- ansible_net_interfaces[item.port].ipv4.address == item.ip
fail_msg: "Interface {{item.port}} has Wrong IP Address"
loop: "{{ p2p_ip[inventory_hostname] }}"
工作原理...
Ansible 提供了一个收集 Arista 设备基本系统属性的事实收集模块,并以一致和结构化的数据结构返回这些事实。我们可以使用这个模块收集的事实来验证设备的基本属性和操作状态。
在这个示例中,我们使用eos_facts模块来收集所有 Arista 设备的设备信息。这个模块返回了 Ansible 为每个设备收集的基本信息,并存储在多个变量中。我们感兴趣的主要变量是ansible_net_interfaces变量,它保存了设备上所有接口的操作状态。以下片段概述了存储在这个变量中的数据的样本:
"ansible_net_interfaces": {
"Ethernet8": {
"bandwidth": 0,
"description": "DC1 | Rpeer: spine01 | Rport: Ethernet1",
"duplex": "duplexFull",
"ipv4": {
"address": "172.31.1.1",
"masklen": 31
},
"lineprotocol": "up",
"macaddress": "50:00:00:03:37:66",
"mtu": 1500,
"operstatus": "connected",
"type": "routed"
}
}
我们使用 Ansible 检索的数据,并存储在ansible_net_interfaces变量中,以验证所有布线接口是否操作,并且它们是否按照我们的设计分配了正确的 IP 地址。我们使用assert模块来执行此验证,并循环遍历每个设备的p2p_ip数据结构,以验证只有我们的布线接口的状态。
参见...
有关eos_facts模块和该模块支持的不同参数的更多信息,请参阅以下网址:docs.ansible.com/ansible/latest/modules/eos_facts_module.html。
从 Arista 设备中检索操作数据
在这个示例中,我们将概述如何在 Arista 设备上执行操作命令,并使用输出来验证设备的状态。
准备工作
为了按照这个示例进行操作,Arista 设备上必须启用 eAPI。
操作步骤...
- 创建一个名为
pb_get_vlans.yml的新 playbook,并填充它以在所有叶子交换机上执行show vlan命令,并将输出存储在一个变量中:
---
- name: " Play 1: Retrieve All VLANs from Arista Switches"
hosts: leaf
vars_files: vlan_design.yml
tasks:
- name: "Get All VLANs"
eos_command:
commands: show vlan | json
register: show_vlan
- 更新
pb_get_vlans.ymlplaybook,并填充以下任务来比较和验证设备上配置的正确 VLAN:
- name: "Validate VLANs are Present"
assert:
that: (item.vlan | string) in show_vlan.stdout[0].vlans.keys()
fail_msg: "VLAN:{{ item.vlan }} is NOT configured "
success_msg: "VLAN:{{ item.vlan }} is configured "
loop: "{{ access_interfaces[inventory_hostname] }}"
delegate_to: localhost
工作原理...
我们使用eos_command Ansible 模块在 Arista 交换机上执行操作命令,并为了返回结构化输出,我们在命令中使用json关键字来返回操作命令的 JSON 输出(如果支持)。在这个例子中,我们发送了show vlan命令来获取设备上配置的 VLAN 列表,并将输出收集在show_vlan变量中。以下片段概述了我们从设备中获得的输出,这些输出存储在这个变量中:
ok: [leaf01] => {
"show_vlan": {
< -- Output Omitted for brevity -->
"stdout": [
{
"vlans": {
"1": {
"dynamic": false,
"interfaces": {
< -- Output Omitted for brevity -->
},
"name": "default",
"status": "active"
},
"10": {
"dynamic": false,
"interfaces": {
"Ethernet1": {
"privatePromoted": false
},
"Vxlan1": {
"privatePromoted": false
}
},
"name": "VLAN_10",
"status": "active"
}
}
}
]
我们使用assert模块来验证我们设计中定义的 VLAN(在vlans_design.yml文件中)是否都配置并且对每个交换机都是操作状态。我们将在这个文件中定义的 VLAN 与我们使用eos_command模块从设备中检索的输出(存储在show_vlan变量中)进行比较,以确保每个 VLAN 在交换机上都是活动的。
我们在assert语句中使用string Jinja2 过滤器,因为我们的vlan_design.yml文件中定义的 VLAN 是整数。然而,存储在show_vlan变量中的 VLAN 是字符串。因此,为了使assert语句成功,我们需要确保类型是相似的。
参见...
有关eos_command模块和该模块支持的不同参数的更多信息,请参阅以下网址:docs.ansible.com/ansible/latest/modules/eos_command_module.html。