网络自动化秘籍-二-

145 阅读37分钟

网络自动化秘籍(二)

原文:zh.annas-archive.org/md5/9FD2C03E57DE97FDB15C42452017B0E9

译者:飞龙

协议:CC BY-NC-SA 4.0

第三章:使用 Ansible 自动化 Juniper 设备的服务提供商

在本章中,我们将概述如何在典型的服务提供商SP)环境中自动化运行 Junos OS 软件的 Juniper 设备。我们将探讨如何使用 Ansible 与 Juniper 设备交互,以及如何使用各种 Ansible 模块在 Juniper 设备上配置不同的服务和协议。我们将以以下示例网络图为基础进行说明,该示例网络图显示了基本 SP 网络的拓扑:

以下表格概述了我们示例拓扑中的设备及其各自的管理Internet ProtocolsIPs):

设备角色供应商管理(MGMT)端口MGMT IP
mxp01P 路由器Juniper vMX 14.1fxp0172.20.1.2
mxp02P 路由器Juniper vMX 14.1fxp0172.20.1.3
mxpe01PE 路由器Juniper vMX 14.1fxp0172.20.1.4
mxpe02PE 路由器Juniper vMX 17.1fxp0172.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 版本

查看以下视频以查看代码的实际操作:

bit.ly/3ajF4Mp

构建网络清单

在本操作中,我们将概述如何构建和组织 Ansible 清单,以描述先前概述的示例 SP 网络设置。Ansible 清单是 Ansible 的关键部分,因为它定义并分组应由 Ansible 管理的设备。

准备工作

我们创建一个新文件夹,用于存放本章中创建的所有文件。新文件夹名为ch3_junos

操作步骤...

  1. 在新文件夹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
  1. 创建一个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组,引用PEP组。

最后,我们创建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 ShellSSH)从 Ansible 连接和身份验证 Juniper 设备,以开始管理 Juniper 设备。我们将概述如何使用 SSH 密钥作为身份验证方法来建立 Ansible 和 Juniper 设备之间的通信。

准备工作

为了按照这个示例操作,应该按照上一个示例构建一个 Ansible 清单文件。Ansible 控制机器和网络中所有设备之间的 IP 可达性必须配置好。

如何操作...

  1. 在 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
  1. 捕获在上一步创建的公钥,如下所示:
$ cat Ansible_SSH_key.pub
 SSH-rsa SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0/wvdC5ycAanRorlfMYDMAv5OTcYAALlE2bdboajsQPQNEw1Li3N0J50OJBWXX+FFQuF7JKpM32vNQjQN7BgyaBWQGxv+Nj0ViVP+8X8Wuif0m6bFxBYSaPbIbGogDjPu4qU90Iv48NGOZpcPLqZthtuN7yZKPshX/0YJtXd2quUsVhzVpJnncXZMb4DZQeOin7+JVRRrDz6KP6meIylf35mhG3CV5VqpoMjYTzkDiHwIrFWVMydd4C77RQu27N2HozUtZgJy9KD8qIJYVdP6skzvp49IdInwhjOA+CugFQuhYhHSoQxRxpws5RZlvrN7/0h0Ahc3OwHaUWD+P7lz Ansible@centos7.localdomain
  1. 在 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 认证,就像之前的配方一样。

如何做...

  1. 创建一个名为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
  1. 使用以下代码更新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。

如何做...

  1. 使用以下参数更新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
  1. 创建一个名为pb_jnpr_basic_config.yml的新 playbook,其中包含以下任务,用于在 Juniper 设备上设置dnshostname和系统用户:
$ 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 模板和任务。以下步骤概述了创建角色以及使用该角色生成配置所需的步骤:

  1. 创建一个新的roles目录,并添加一个名为build_router_config的新角色,目录结构如下:
$ tree roles/
 roles/
 └── build_router_config
 ├── tasks
 └── templates
  1. 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}}"
  1. 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 %}
 }
}
  1. 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
  1. 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
  1. 使用以下任务更新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目录中。这是每个设备的finalassembled配置文件。我们将每个部分的临时配置片段存储在每个设备的临时文件夹中,并在播放手册运行结束时删除此临时文件夹。这是因为我们已经为设备组装了最终配置,不再需要这些配置片段。

在这个手册中,我们将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,就像之前的步骤一样。

如何做…

  1. 更新group_vars/all.yml YAML 文件,包括我们示例网络拓扑中所有点对点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
  1. pb_jnpr_basic_config.yml playbook 中更新以下任务,以在我们的 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 设备生成接口配置。我们使用以下步骤来完成这个任务:

  1. 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]}};
 }
 }
 }
  1. 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 接口成本。

如何做…

  1. 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;
 }
 }
 }
}
  1. 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 配置。

如何做...

  1. 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 %}
 }
}
  1. 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 地址族。

如何做...

  1. 更新group_vars/all.yml文件,包含以下 BGP 信息:
bgp_topo:
  rr: mxp01
  af:
  - inet
  - inet-vpn
  1. 对于 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
  1. 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 %}
}
  1. 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_varshost_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。

如何做...

  1. 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 模块。

如何做...

  1. 创建一个名为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
  1. 创建一个名为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 }}"
  1. 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
  1. 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,并为属于特定节点的所有接口创建一个新的列表数据结构。以下代码片段概述了mxpe01l3vpn_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 模块。

如何做...

  1. 创建一个新的 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:
  1. 使用以下任务更新pb_jnpr_facts.yml playbook,为清单中的每个节点创建一个信息报告:
 - 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
  1. 使用以下任务更新 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进行网络可达性。这将使我们能够验证样本网络拓扑中的网络可达性和流量转发。

准备工作

这个配方假设网络已经建立和配置,就像在所有先前的配方中所概述的那样。

如何做...

  1. 创建一个名为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
  1. 使用以下任务更新pb_junos_ping.yml playbook,创建一个自定义报告来捕获 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 设备上启用,以便按照此步骤进行操作。

如何做...

  1. 安装jxmlease Python 包,如下所示:
$ pip3 install jxmlease
  1. 创建一个名为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'] }}"
  1. 更新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。

如何操作...

  1. 安装junos-eznc Python 包,如下所示:
$ pip3 install junos-eznc
  1. 使用Ansible-galaxy安装Juniper.junos Ansible 角色,如下所示:
$ Ansible-galaxy install Juniper.junos
  1. 创建一个名为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
  1. 使用以下任务更新 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.20Management1172.20.1.35
Spine02脊柱交换机Arista vEOS 4.20Management1172.20.1.36
Leaf01叶子交换机Arista vEOS 4.20Management1172.20.1.41
Leaf02叶子交换机Arista vEOS 4.20Management1172.20.1.42
Leaf03叶子交换机Arista vEOS 4.20Management1172.20.1.43
Leaf04叶子交换机Arista vEOS 4.20Management1172.20.1.44

本章涵盖的主要配方如下:

  • 构建 Ansible 网络清单

  • 连接到并使用 Ansible 对 Arista 设备进行身份验证

  • 在 Arista 设备上启用可扩展操作系统EOSAPIeAPI

  • 在 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 虚拟化 EOSvEOS)运行 EOS 4.20.1F

观看以下视频以查看代码的实际操作:

bit.ly/3coydTp

构建 Ansible 网络清单

在本配方中,我们将概述如何构建和组织 Ansible 清单,以描述我们示例的叶脊直流DC)网络。Ansible 清单是 Ansible 的重要组成部分,它描述并分组应由 Ansible 管理的设备。

准备工作

我们需要创建一个新的文件夹,用于存放本章中将创建的所有文件。新文件夹的名称应为ch4_arista

如何做...

  1. 在新文件夹(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
  1. 创建一个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组,它引用了leafspine两个组。

最后,我们创建了ansible.cfg文件,并配置它指向我们的hosts文件,用作 Ansible 清单文件。此外,我们禁用了setup模块(通过将gathering设置为explicit),这在针对网络节点运行 Ansible 时是不需要的。

从 Ansible 连接和认证 Arista 设备

在这个示例中,我们将概述如何通过安全外壳SSH)从 Ansible 连接到 Arista 设备,以便从 Ansible 开始管理这些设备。我们将使用用户名和密码来对我们拓扑中的 Arista 设备进行认证。

准备工作

为了按照这个示例进行操作,应该按照之前的示例构建一个 Ansible 清单文件。Ansible 控制机与网络中所有设备之间的 IP 可达性也必须得到实现。

操作步骤...

  1. ch4_arista文件夹中创建一个group_vars文件夹。

  2. group_vars文件夹中,创建一个名为arista.yml的文件,包含以下内容:

ansible_network_os: eos
ansible_connection: network_cli
ansible_user: ansible
ansible_ssh_pass: ansible123
  1. 在 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
!
  1. 在 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 认证也应该已经部署并且正常工作,就像之前的示例一样。

操作步骤...

  1. group_vars文件夹中创建一个名为all.yml的文件,其中包含以下管理 VRF 数据:
$ cat  group_vars/all.yml

global:
 mgmt_vrf: MGMT
  1. 创建一个名为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
  1. 使用以下任务更新pb_eos_enable_eapi.yml playbook,以在管理 VRF 下启用 eAPI:
 - name: "Enable eAPI under VRF"
 eos_eapi:
 state: started
 vrf: "{{global.mgmt_vrf}}"
  1. 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。

如何做...

  1. 更新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
  1. 创建一个新的 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
  1. 更新pb_arista_basic_config.yml playbook,添加以下任务以在 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_systemeos_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 设备的特定配置。

  1. 创建一个新的roles目录,并添加一个名为dc_fabirc_config的新角色,具有以下目录结构:
$ tree roles/
roles/
└── dc_fabric_config
 ├── tasks
 └── templates
  1. 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}}"
  1. 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 %}
!
  1. 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
  1. 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
  1. 创建一个名为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,就像之前的配方一样。

操作步骤...

  1. 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
  1. 更新pb_arista_basic_config.yml playbook,添加以下任务以启用接口并在所有布线接口上设置描述:
- 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]}}"
  1. 更新pb_arista_basic_config.yml playbook,添加以下任务以在所有点对点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 设备的接口配置。我们使用以下步骤来完成这项任务:

  1. 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]}}
!
  1. 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
  1. 一旦我们运行pb_arista_dc_fabric.yml playbook,我们将为我们的设备生成配置,例如为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 地址信息已根据以前的教程进行了配置。

操作步骤...

  1. 创建一个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
  1. 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 %}
  1. templates/eos目录中更新underlay_bgp.j2 Jinja2 文件,其中包含以下代码块中显示的 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
!
  1. 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 配置应该已经根据以前的教程生成。

操作步骤...

  1. 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
!
  1. 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 配置已经按照之前的教程生成。

如何做...

  1. 创建一个名为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 -->
  1. roles文件夹中创建一个新的角色provision_vlans,结构如下:
$ tree roles/provision_vlans/
 roles/provision_vlans/
 ├── tasks
 │ └── main.yml
 ├── templates
 └── vars
 └── main.yml
  1. 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
  1. 创建一个新的 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)映射:

VLANVNI
101010
201020

准备工作

这个配方假设 BGP EVPN 已经部署在我们的 fabric 中,并且所有 VLAN 已经配置。

如何做...

  1. provision_vlans角色的vars/main.yml文件中更新以下变量。这将定义存储 VXLAN 配置的目录:
$ cat roles/provision_vlans/vars/main.yml
 ---
 config_dir: ./vxlan_configs
  1. 在我们的provision_vlans角色中,创建一个templates文件夹。然后,在其中创建一个eos文件夹。之后,创建一个 Jinja2 vxlan.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 %}
 !
  1. 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
  1. 使用以下任务更新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 模块。

如何做...

  1. 创建一个新的 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:
  1. 使用以下任务更新pb_arista_facts.yml playbook,以验证所有 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] }}"
  1. 更新 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。

操作步骤...

  1. 创建一个名为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
  1. 更新pb_get_vlans.yml playbook,并填充以下任务来比较和验证设备上配置的正确 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