网络自动化秘籍(一)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

《网络自动化食谱》概述了网络自动化的各种主题以及如何使用软件开发实践来设计和操作不同的网络解决方案。我们使用 Ansible 作为我们的框架,介绍网络自动化的主题以及如何使用 Ansible 来管理不同厂商的设备。在第一部分中,我们概述了如何安装和配置 Ansible,专门用于网络自动化的目的。我们将探讨如何使用 Ansible 来管理来自 Cisco、Juniper、Arista 和 F5 等各种厂商的传统网络解决方案。接下来,我们将继续探讨如何利用 Ansible 来构建和扩展来自 AWS、Azure 和 Google Cloud Platform(GCP)等主要云服务提供商的网络解决方案。最后,我们概述了网络自动化中不同的支持开源项目,如 NetBox、Batfish 和 AWX。我们概述了如何将所有这些工具与 Ansible 集成,以构建一个完整的网络自动化框架。

通过本书,您将建立起如何将 Ansible 与不同厂商设备集成以及如何基于 Ansible 构建网络自动化解决方案的坚实基础。此外,您还将了解如何使用各种开源项目,并如何将所有这些解决方案与 Ansible 集成,以构建一个强大而可扩展的网络自动化框架。

本书适合对象

本书适合负责组织内部网络设备设计和操作的 IT 专业人员和网络工程师,他们希望扩展自己在使用 Ansible 自动化网络基础设施方面的知识。建议具备基本的网络和 Linux 知识。

本书内容

第一章《Ansible 的构建模块》着重介绍了如何安装 Ansible,并描述了 Ansible 的主要构建模块以及如何利用它们来构建高级的 Ansible playbook。

第二章《使用 Ansible 管理 Cisco IOS 设备》着重介绍了如何将 Ansible 与 Cisco IOS 设备集成,以及如何使用 Ansible 配置 Cisco IOS 设备。我们将探讨与 Cisco IOS 设备交互的核心 Ansible 模块。最后,我们还将探讨如何使用 Cisco PyATS 库以及如何将其与 Ansible 集成,以验证 Cisco IOS 和 Cisco IOS-XE 设备上的网络状态。

第三章《使用 Ansible 在服务提供商中自动化 Juniper 设备》描述了如何在服务提供商环境中将 Ansible 与 Juniper 设备集成,以及如何使用 Ansible 管理 Juniper 设备的配置。我们将探讨如何使用核心的 Ansible 模块来管理 Juniper 设备。此外,我们还将探讨 PyEZ 库,该库被 Juniper 定制的 Ansible 模块使用,以扩展 Ansible 在管理 Juniper 设备方面的功能。

第四章《使用 Arista 和 Ansible 构建数据中心网络》概述了如何将 Ansible 与 Arista 设备集成,以使用 EVPN/VXLAN 构建数据中心网络。我们将探讨如何使用核心的 Ansible 模块来管理 Arista 设备,以及如何使用这些模块来配置和验证 Arista 交换机上的网络状态。

第五章《使用 F5 LTM 和 Ansible 自动化应用交付》着重介绍了如何将 Ansible 与 F5 BIG-IP LTM 设备集成,以便为应用交付新的 BIG-IP LTM 设备,并将 BIG-IP 系统设置为应用交付的反向代理。

第六章《使用 NAPALM 和 Ansible 管理多厂商网络》介绍了 NAPALM 库,并概述了如何将该库与 Ansible 集成。我们将探讨如何利用 Ansible 和 NAPALM 来简化多厂商环境的管理。

第七章《使用 Ansible 部署和操作 AWS 网络资源》概述了如何将 Ansible 与您的 AWS 环境集成,以及如何使用 Ansible 描述您的 AWS 基础设施。我们探讨了如何利用核心 Ansible AWS 模块来管理 AWS 中的网络资源,以便使用 Ansible 构建 AWS 网络基础设施。

第八章《使用 Ansible 部署和操作 Azure 网络资源》概述了如何将 Ansible 与您的 Azure 环境集成,以及如何使用 Ansible 描述您的 Azure 基础设施。我们将探讨如何利用核心 Ansible Azure 模块来管理 Azure 中的网络资源,以便使用 Ansible 构建 Azure 网络解决方案。

第九章《使用 Ansible 部署和操作 GCP 网络资源》描述了如何将 Ansible 与您的 GCP 环境集成,以及如何使用 Ansible 描述您的 GCP 基础设施。我们探讨了如何利用核心 Ansible GCP 模块来管理 GCP 中的网络资源,以便使用 Ansible 构建 GCP 网络解决方案。

第十章《使用 Batfish 和 Ansible 进行网络验证》介绍了离线网络验证的 Batfish 框架以及如何将该框架与 Ansible 集成,以便使用 Ansible 和 Batfish 进行离线网络验证。

第十一章《使用 Ansible 和 NetBox 构建网络清单》介绍了 NetBox,这是一个完整的清单系统,用于记录和描述任何网络。我们概述了如何将 Ansible 与 NetBox 集成,以及如何使用 NetBox 数据构建 Ansible 动态清单。

第十二章《使用 AWX 和 Ansible 简化自动化》介绍了 AWX 项目,它扩展了 Ansible,并在 Ansible 之上提供了强大的 GUI 和 API,以简化组织内运行自动化任务。我们概述了 AWX 提供的额外功能以及如何使用它来管理组织内的网络自动化。

第十三章《Ansible 的高级技术和最佳实践》描述了可用于更高级 playbook 的各种最佳实践和高级技术。

为了充分利用本书

假定您具有关于不同网络概念的基本知识,例如开放最短路径优先OSPF)和边界网关协议BGP)。

假定您具有 Linux 的基本知识,包括如何在 Linux 机器上创建文件和文件夹以及安装软件的知识。

本书涵盖的软件/硬件操作系统要求
Ansible 2.9CentOS 7
Python 3.6.8

如果您使用的是本书的数字版本,我们建议您自己输入代码或通过 GitHub 存储库访问代码(链接在下一节中提供)。这样做将有助于避免与复制和粘贴代码相关的任何潜在错误。

下载示例代码文件

您可以从您在www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support注册,以便直接通过电子邮件接收文件。

您可以按照以下步骤下载代码文件:

  1. www.packt.com上登录或注册。

  2. 选择“支持”选项卡。

  3. 单击“代码下载”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

下载文件后,请确保使用最新版本的解压缩或提取文件夹:

  • WinRAR/7-Zip 适用于 Windows

  • Zipeg/iZip/UnRarX 适用于 Mac

  • 7-Zip/PeaZip 适用于 Linux

该书的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Network-Automation-Cookbook。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还提供了来自我们丰富书籍和视频目录的其他代码包,可在**github.com/PacktPublishing/**上找到。去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789956481_ColorImages.pdf

代码演示

代码演示视频基于 Ansible 版本 2.8.5。该代码还经过了 2.9.2 版本的测试,可以正常运行。

访问以下链接,查看代码运行的视频:

bit.ly/34JooNp

惯例使用

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词,数据库表名,文件夹名,文件名,文件扩展名,路径名,虚拟 URL,用户输入和 Twitter 句柄。这是一个例子:“将下载的WebStorm-10*.dmg磁盘映像文件挂载为系统中的另一个磁盘。”

一段代码设置如下:

$ cat ansible.cfg

[defaults]
 inventory=hosts
 retry_files_enabled=False
 gathering=explicit
 host_key_checking=False

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

- name: Configure ACL on IOS-XR
  hosts: all
  serial: 1
  tags: deploy
  tasks:
    - name: Backup Config
      iosxr_config:
        backup:
 when: not ansible_check_mode    - name: Deploy ACLs
      iosxr_config:
        src: acl_conf.cfg
        match: line
 when: not ansible_check_mode

任何命令行输入或输出都写成如下形式:

$ python3 -m venv dev
$ source dev/bin/activate

粗体:表示一个新术语,一个重要单词,或者您在屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“从管理面板中选择系统信息。”

警告或重要说明会出现在这样的地方。提示和技巧会出现在这样的地方。

章节

在本书中,您会经常看到几个标题(准备工作如何做…它是如何工作的…还有更多…另请参阅)。

为了清晰地说明如何完成一个配方,使用以下各节:

准备工作

本节告诉您在配方中可以期待什么,并描述如何设置配方所需的任何软件或初步设置。

如何做…

本节包含了遵循配方所需的步骤。

它是如何工作的…

本节通常包括对上一节发生的事情的详细解释。

还有更多…

本节包含有关配方的其他信息,以使您对配方更加了解。

另请参阅

本节提供了有用的链接,指向配方的其他有用信息。

第一章:Ansible 的构建块

Ansible 是一个非常受欢迎的自动化框架,长期以来一直被用于自动化 IT 运营。它简化了不同基础设施节点的管理,并将业务逻辑转化为明确定义的程序,以实现这些业务逻辑。Ansible 是用 Python 编写的,主要依赖 SSH 与基础设施节点通信以执行指令。它从 Ansible 1.9 开始支持网络设备,并且随着 Ansible 2.9 的到来,它对网络设备的支持已经得到了大幅增强。它可以使用 SSH 或者如果网络供应商在其设备上支持 API 的话,还可以通过 API 与网络设备进行交互。它还提供了多个优势,包括以下内容:

  • **易学习曲线:**编写 Ansible Playbooks 需要了解 YAML 和 Jinja2 模板,这些都很容易学习,其描述性语言易于理解。

  • **无需代理:**无需在远程管理设备上安装代理即可控制该设备。

  • **可扩展:**Ansible 配备了多个模块,可在受控节点上执行各种任务。它还支持编写自定义模块和插件,以扩展 Ansible 的核心功能。

  • **幂等性:**除非需要改变设置以达到期望的状态,否则 Ansible 不会改变设备的状态。一旦设备处于期望的状态,运行 Ansible Playbooks 不会改变其配置。

在本章中,我们将介绍 Ansible 的主要组件,并概述 Ansible 支持的不同功能和选项。以下是将涵盖的主要内容:

  • 安装 Ansible

  • 构建 Ansible 的清单

  • 使用 Ansible 的变量

  • 构建 Ansible 的 Playbook

  • 使用 Ansible 的条件语句

  • 使用 Ansible 的循环

  • 使用 Ansible Vault 保护秘密

  • 使用 Jinja2 与 Ansible

  • 使用 Ansible 的过滤器

  • 使用 Ansible 标签

  • 定制 Ansible 的设置

  • 使用 Ansible 角色

本章的目的是基本了解我们将在本书中利用的不同 Ansible 组件,以便与网络设备进行交互。因此,本章中的所有示例都不是专注于管理网络设备。相反,我们将专注于理解 Ansible 中的不同组件,以便在下一章中有效地使用它们。

技术要求

以下是安装 Ansible 和运行所有 Ansible Playbooks 的要求:

  • 具有以下发行版之一的 Linux 虚拟机VM):

  • Ubuntu 18.04 或更高版本

  • CentOS 7.0 或更高版本

  • 虚拟机的互联网连接

设置 Linux 机器超出了本教程的范围。然而,使用Vagrant创建和设置 Ansible 虚拟机是设置具有任何操作系统版本的 Linux 虚拟机的最简单方法。

安装 Ansible

安装 Ansible 的机器(称为 Ansible 控制机)应该运行在任何 Linux 发行版上。在本教程中,我们将概述如何在 Ubuntu Linux 机器或 CentOS 机器上安装 Ansible。

准备工作

要安装 Ansible,我们需要使用 Ubuntu 18.04+或 CentOS 7+操作系统的 Linux 虚拟机。此外,这台机器需要有互联网访问权限才能安装 Ansible。

如何做…

Ansible 是用 Python 编写的,所有模块都需要在 Ansible 控制机上安装 Python。我们的第一个任务是确保 Python 已安装在 Ansible 控制机上,如下所述。

  1. 大多数 Linux 发行版默认安装了 Python。但是,如果 Python 未安装,以下是在 Linux 上安装它的步骤:
    • 在 Ubuntu 操作系统上,执行以下命令:
# Install python3
$sudo apt-get install python3

# validate python is installed 
$python3 --version
Python 3.6.9
    • 在 CentOS 操作系统上,执行以下命令:
# Install python
$sudo yum install pytho3

# validate python is installed 
$python3 --version
Python 3.6.8
  1. 在验证了 Python 已安装后,我们可以开始安装 Ansible:
    • 在 Ubuntu 操作系统上,执行以下命令:
# We need to use ansible repository to install the latest version of Ansible
$ sudo apt-add-repository ppa:ansible/ansible

# Update the repo cache to use the new repo added
$ sudo apt-get update

# We install Ansible
$ sudo apt-get install ansible
    • 在 CentOS 操作系统上,执行以下命令:
# We need to use latest epel repository to get the latest ansible 
$ sudo yum install epel-release

# We install Ansible
$ sudo yum install ansible

它是如何工作的…

安装 Ansible 的最简单方法是使用特定于我们的 Linux 发行版的软件包管理器。我们只需要确保已启用所需的存储库以安装最新版本的 Ansible。在 Ubuntu 和 CentOS 中,我们需要启用提供最新版本 Ansible 的额外存储库。在 CentOS 中,我们需要安装和启用企业 Linux 额外软件包EPEL)存储库,该存储库提供额外的软件包,并为 CentOS 提供最新的 Ansible 软件包。

使用此方法,我们将安装 Ansible 和运行 Ansible 模块所需的所有必需系统软件包。在 Ubuntu 和 CentOS 中,此方法还将安装 Python 2 并使用 Python 2 运行 Ansible。我们可以通过运行以下命令验证 Ansible 已安装并使用的版本:

$ ansible --version
ansible 2.9
 config file = /etc/ansible/ansible.cfg
 configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
 ansible python module location = /usr/lib/python2.7/site-packages/ansible
 executable location = /usr/bin/ansible
 python version = 2.7.5 (default, Aug 7 2019, 00:51:29) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

此外,我们可以通过尝试使用ping模块连接到本地机器来检查 Ansible 是否按预期工作,如下所示:

$ ansible -m ping localhost

localhost | SUCCESS => {
 "changed": false,
 "ping": "pong"
}

使用此方法,我们可以看到它存在以下问题:

  • 它使用 Python 2 作为执行环境,但我们想使用 Python 3。

  • 它更新了系统上安装的 Python 软件包,这可能是不可取的。

  • 它不提供我们所需的细粒度,以便选择要使用的 Ansible 版本。使用此方法,我们将始终安装最新版本的 Ansible,这可能不是我们所需要的。

它是如何工作的…

为了在 Python 3 环境中安装 Ansible 并对部署的 Ansible 版本进行更多控制,我们将使用 pip Python 程序来安装 Ansible,如下所示:

  • 如果尚未安装 Python 3,请按以下方式安装:
# Ubuntu
$ sudo apt-get install python3

# CentOS
sudo yum install python3
  • 安装python3-pip软件包:
# Ubuntu
$ sudo apt-get install python3-pip

# CentOS
$ sudo yum install python3-pip
  • 安装 Ansible:
# Ubuntu and CentOS
# This will install ansible for the current user ONLY
$ pip3 install ansible==2.9 --user

# We Can install ansible on the System Level
$ sudo pip3 install ansible==2.9
  • 我们可以验证 Ansible 已成功安装,如下所示:
$$ ansible --version
ansible 2.9
 config file = None
 configured module search path = ['/home/vagrant/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
 ansible python module location = /home/vagrant/.local/lib/python3.6/site-packages/ansible
 executable location = /home/vagrant/.local/bin/ansible
 python version = 3.6.8 (default, Aug 7 2019, 17:28:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]

使用此方法安装 Ansible 可确保我们使用 Python 3 作为执行环境,并允许我们控制要安装的 Ansible 版本,如所示的示例。

我们将使用此方法作为我们的 Ansible 安装方法,并且所有后续章节将基于此安装过程。

在第十三章中,Ansible 的高级技术和最佳实践,我们将概述使用 Python 虚拟环境安装 Ansible 的另一种方法。

另请参阅…

有关安装 Ansible 的更多信息,请查看以下网址:

docs.ansible.com/ansible/latest/installation_guide/intro_installation.html

构建 Ansible 的清单

安装完 Ansible 后,我们需要定义 Ansible 的清单,这是一个定义 Ansible 将管理的节点的文本文件。在本教程中,我们将概述如何创建和构造 Ansible 的清单文件。

准备工作

我们需要创建一个包含本章中将概述的所有代码的文件夹。我们创建一个名为ch1_ansible的文件夹,如下所示:

$ mkdir ch1_ansible
$ cd ch1_ansible

如何做…

执行以下步骤创建清单文件:

  1. 创建名为hosts的文件:
$ touch hosts
  1. 使用任何文本编辑器打开文件并添加以下内容:
$ cat hosts

[cisco]
csr1 ansible_host=172.10.1.2
csr2 ansible_host=172.10.1.3

[juniper]
mx1 ansible_host=172.20.1.2
mx2 ansible_host=172.20.1.3

[core]
mx1
mx2

[edge]
csr[1:2]

[network:children]
core
edge

Ansible 清单文件可以有任何名称。但是,作为最佳实践,我们将使用名称hosts来描述清单中的设备。

它是如何工作的…

Ansible 清单文件定义了将由 Ansible 管理的主机(在上面的示例中,这是csr1-2mx1-2),以及如何根据不同标准将这些设备分组到自定义定义的组中。组用[]定义。这种分组有助于我们定义变量并简化设备之间的分隔以及 Ansible 与它们的交互。我们如何分组设备是基于我们的用例的,因此我们可以根据供应商(Juniper 和 IOS)或功能(核心和边缘)对它们进行分组。

我们还可以使用在清单文件中概述的 children 为组构建层次结构。以下图显示了主机是如何分组的以及组层次结构是如何构建的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 Ansible 的变量

Ansible 使用 Ansible 变量存储其管理的节点的信息。Ansible 变量可以在多个位置声明。然而,为了遵循 Ansible 的最佳实践,我们将概述 Ansible 寻找用于在清单文件中声明的节点的变量的两个主要部分。

准备工作

为了按照这个步骤进行,必须已经按照之前的步骤定义了 Ansible 清单文件。

操作步骤

在清单文件中,我们定义主机并将主机分组。现在我们定义了 Ansible 搜索组变量和主机变量的两个目录:

  1. 创建两个文件夹,group_varshost_vars
$ cd ch1_ansible $ mkdir group_vars host_vars
  1. group_vars内创建ios.ymljunos.yml文件:
$ touch group_vars/cisco.yml group_vars/juniper.yml
  1. host_vars内创建mx1.ymlcsr1.yml
$ touch host_vars/csr1.yml host_vars/mx1.yml
  1. 填充所有文件中的变量,如下所示:
$echo 'hostname: core-mx1' >> host_vars/mx1.yml
$echo 'hostname: core-mx2' >> host_vars/mx2.yml
$echo 'hostname: edge-csr1' >> host_vars/csr1.yml
$echo 'hostname: edge-csr2' >> host_vars/csr2.yml
$echo 'os: ios' >> group_vars/cisco.yml
$echo 'os: junos' >> group_vars/juniper.yml

工作原理

我们创建了以下目录和文件结构来存储我们的变量,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

group_vars目录内的所有文件包含我们在清单中定义的组的组变量,并且适用于该组内的所有主机。至于host_vars内的文件,它们包含每个主机的变量。使用这种结构,我们可以将多个主机的变量分组到一个特定的组文件中,并且特定于主机的变量将被放在一个特定于该主机的单独文件中。

还有更多…

除了host_varsgroup_vars,Ansible 还支持使用其他技术定义变量,包括以下内容:

  • 在 play 中使用vars关键字指定多个变量

  • 使用vars_files在文件中定义变量,并在运行 playbook 时让 Ansible 从该文件中读取这些变量

  • 使用--e选项在命令行指定变量

除了我们可以指定的用户定义变量之外,Ansible 还有一些默认变量,它会动态构建用于清单的变量。以下表格捕获了一些最常用的变量:

inventory_hostname主机在清单中定义的名称(例如,csr1mx1
play_hostsplay 中包含的所有主机的列表
group_names特定主机所属的所有组的列表(例如,对于csr1,这将是[edge,Cisco,network])

构建 Ansible 的 playbook

Ansible playbook 是 Ansible 中声明我们想要在我们管理的主机上执行的操作的基本元素。Ansible playbook 是一个以 YAML 格式编写的文件,定义了将在我们管理的设备上执行的任务列表。在这个步骤中,我们将概述如何编写一个 Ansible playbook 以及如何定义将被此 playbook 定位的主机。

准备工作

为了按照这个步骤进行,必须已经定义了 Ansible 清单文件,并根据之前的步骤创建了所有组和主机特定的变量文件。

操作步骤

  1. ch1_ansible文件夹内创建一个名为playbook.yml的新文件,并在此文件中加入以下行:
$  cat playbook.yml

---
 - name: Initial Playbook
 hosts: all
 gather_facts: no
 tasks:
 - name: Display Hostname
 debug:
 msg: "Router name is {{ hostname }}"
 - name: Display OS
 debug:
 msg: "{{ hostname }} is running {{ os }}"
  1. 按照以下步骤运行 playbook:
$ ansible-playbook -i hosts playbook.yml

工作原理

Ansible playbook 是一系列 play 的列表,每个 play 针对清单文件中定义的特定主机组。每个 play 可以有一个或多个任务针对此 play 中的主机执行。每个任务运行一个特定的 Ansible 模块,该模块有一些参数。playbook 的一般结构如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在上述 playbook 中,我们在{{ }}括号中引用了我们在前面步骤中定义的变量。Ansible 从group_varshost_vars中读取这些变量,我们在这个 playbook 中使用的模块是debug模块,它将在终端输出中显示为msg参数中指定的自定义消息。playbook 运行如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们在ansible-playbook命令中使用-i选项来指向 Ansible 清单文件,这将作为我们构建清单的源。

在这个 playbook 中,我使用了all关键字来指定清单中的所有主机。这是 Ansible 为清单中的所有主机动态构建的一个众所周知的组名。

使用 Ansible 的条件语句

Ansible 的核心功能之一是条件任务执行。这为我们提供了根据我们指定的条件/测试来控制在给定主机上运行哪些任务的能力。在这个步骤中,我们将概述如何配置条件任务执行。

准备工作

为了按照这个步骤进行操作,必须存在一个已配置的 Ansible 清单文件,如前面的步骤所述。此外,所有主机的 Ansible 变量应该按照前面的步骤所述进行定义。

如何做…

  1. ch1_ansible文件夹中创建一个名为ansible_cond.yml的新 playbook。

  2. 按照这里所示在新的 playbook 中加入以下内容:

---
 - name: Using conditionals
 hosts: all
 gather_facts: no
 tasks:
 - name: Run for Edge nodes Only
 debug:
 msg: "Router name is {{ hostname }}"
 when: "'edge' in group_names"

 - name: Run for Only MX1 node
 debug:
 msg: "{{ hostname }} is running {{ os }}"
 when:
 - inventory_hostname == 'mx1'
  1. 按照这里所示运行 playbook:
$ ansible-playbook -i hosts ansible_cond.yml

它是如何工作的…

Ansible 使用when语句为任务提供条件执行。when语句应用于任务级别,如果when语句中的条件评估为true,则任务将针对给定的主机执行。如果为false,则该任务将跳过该主机。运行上述 playbook 的输出如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

when语句可以像第一个任务中那样接受一个条件,也可以像第二个任务中那样接受一个条件列表。如果when是一个条件列表,所有条件都需要为真才能执行任务。

在第一个任务中,when语句用*““括起来,因为语句以字符串开头*。然而,在第二个语句中,我们使用了一个普通的when语句,没有*””*,因为when语句以变量名开头。

另请参阅…

有关 Ansible 条件语句的更多信息,请查看以下网址:

docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html

使用 Ansible 的循环

在某些情况下,我们需要在 Ansible playbook 中运行一个任务来循环处理一些数据。Ansible 的循环允许我们多次循环遍历一个变量(字典或列表)以实现这种行为。在这个步骤中,我们将概述如何使用 Ansible 的循环。

准备工作

为了按照这个步骤进行操作,必须存在一个已配置的 Ansible 清单文件,如前面的步骤所述。

如何做…

  1. ch1_ansible文件夹中创建一个名为ansible_loops.yml的新 playbook。

  2. group_vars/cisco.yml文件中,加入以下内容:

snmp_servers:
 - 10.1.1.1
 - 10.2.1.1
  1. group_vars/juniper.yml文件中,加入以下内容:
users:
 admin: admin123
 oper: oper123
  1. ansible_loops.yml文件中,加入以下内容:
---
 - name: Ansible Loop over a List
 hosts: cisco
 gather_facts: no
 tasks:
 - name: Loop over SNMP Servers
 debug:
 msg: "Router {{ hostname }} with snmp server {{ item }}"
 loop: "{{ snmp_servers}}"

 - name: Ansible Loop over a Dictionary
 hosts: juniper
 gather_facts: no
 tasks:
 - name: Loop over Username and Passowrds
 debug:
 msg: "Router {{ hostname }} with user {{ item.key }} password {{ item.value }}"
 with_dict: "{{ users}}"
  1. 按照这里所示运行 playbook:
$ ansible-playbook ansible_loops.yml -i hosts

它是如何工作的…

Ansible 支持对两种主要的可迭代数据结构进行循环:列表和字典。当我们需要对列表进行迭代时(snmp_servers是一个列表数据结构),我们使用loops关键字,当我们循环遍历字典时(users是一个字典数据结构,其中用户名是键,密码是值),我们使用with_dicts。在这两种情况下,我们使用item关键字来指定当前迭代中的值。在with_dicts的情况下,我们使用item.key来获取键,使用item.value来获取值。

以下是前面剧本运行的输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

另请参阅…

有关不同的 Ansible循环结构的更多信息,请参考以下网址:

docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html

使用 Ansible Vault 保护秘密

当我们处理需要在 Ansible 剧本中引用的敏感材料时,比如密码,我们不应该将这些数据保存为纯文本。Ansible Vault 提供了一种加密这些数据并在运行剧本时安全解密和访问的方法。在这个示例中,我们将概述如何使用 Ansible Vault 来保护 Ansible 中的敏感信息。

如何做…

  1. 创建一个名为decrypt_passwd的新文件,如下所示:
$ echo 'strong_password' > decrypt_passwd
  1. 使用ansible-vault创建一个名为secrets的新文件,如下所示:
$ ansible-vault create --vault-id=decrypt_passwd secrets
  1. 将以下变量添加到这个新的secrets文件中:
ospf_password: ospf_P@ssw0rD
bgp_password: BGP_p@ssw0rd
  1. 创建一个名为ansible_vault.yml的新剧本,如下所示:
---
 - name: Using Ansible vault
 hosts: all
 gather_facts: no
 vars_files:
 - secrets
 tasks:
 - name: Output OSPF passowrd
 debug:
 msg: "Router {{ hostname }} ospf Password {{ ospf_password }}"
 when: inventory_hostname == 'csr1'

 - name: Output BGP passowrd
 debug:
 msg: "Router {{ hostname }} BGP Password {{ bgp_password }}"
 when: inventory_hostname == 'mx1'
  1. 按照这里所示的方式运行剧本:
$ ansible-playbook --vault-id=decrypt_passwd ansible_vault.yml -i hosts

它是如何工作的…

我们使用ansible-vault命令创建一个使用--vault-id指定的密钥加密的新文件。我们将这个密钥/密码放在另一个文件中(在我们的示例中称为decrypt_passwd),并将此文件作为vault-id的参数传递。在这个文件中,我们可以放置尽可能多的变量。最后,我们使用vars_files将这个文件作为变量文件包含在剧本中。如果我们尝试在没有解密的情况下读取秘密文件,其内容如下:

$ cat secrets
$ANSIBLE_VAULT;1.1;AES256
61383264326363373336383839643834386661343630393965656135666336383763343938313963
3538376230613534323833356237663532666363626462640a663839396230646634353839626461
31336461386361616261336534663137326265363261626536663564623764663861623735633865
3033356536393631320a643561623635363830653236633833383531366166326566623139633838
32633335616663623761313630613134636635663865363563366564313365376431333461623232
34633838333836363865313238363966303466373065356561353638363731616135386164373263
666530653334643133383239633237653034

为了让 Ansible 解密这个文件,我们必须通过--vault-id选项提供解密密码(在这个示例中存储在decrypt_passwd文件中)。当我们运行ansible-playbook时,必须提供这个解密密码,否则ansible-playbook会失败,如下所示:

### Running the Ansible playbook without --vault-id 
$ansible-playbook ansible_vault.yml -i hosts
ERROR! Attempting to decrypt but no vault secrets found

还有更多…

如果我们不想在文本文件中指定加密/解密密码,我们可以在运行剧本时使用--ask-vault-passansible-playbook命令一起输入密码,如下所示:

### Running the Ansible playbook with --ask-vault-pass
$ansible-playbook ansible_vault.yml -i hosts --ask-vault-pass
Vault password:

使用 Jinja2 与 Ansible

Jinja2 是 Python 的一个强大的模板引擎,受到 Ansible 的支持。它还用于生成任何基于文本的文件,如 HTML、CSV 或 YAML。我们可以利用 Ansible 变量来使用 Jinja2 生成网络设备的自定义配置文件。在这个示例中,我们将概述如何在 Ansible 中使用 Jinja2 模板。

准备工作

为了按照这个示例进行操作,必须存在并按照前面的示例配置好 Ansible 清单文件。

如何做…

  1. group_vars目录中创建一个名为network.yml的新文件:
$ cat group_vars/network.yml

---
ntp_servers:
 - 172.20.1.1
 - 172.20.2.1
  1. 创建一个新的templates目录,并创建一个名为ios_basic.j2的新文件,内容如下:
$ cat templates/ios_basic.j2
hostname {{ hostname }}
!
{% for server in ntp_servers %}
ntp {{ server }}
{% endfor %}
!
  1. templates目录中创建一个名为junos_basic.j2的新文件,内容如下:
$ cat templates/junos_basic.j2
set system host-name {{ hostname }}
{% for server in ntp_servers %}
set system ntp server {{ server }}
{% endfor %}
  1. 创建一个名为ansible_jinja2.yml的新剧本,内容如下:
---
 - name: Generate Cisco config from Jinja2
 hosts: localhost
 gather_facts: no
 tasks:
 - name: Create Configs Directory
 file: path=configs state=directory

 - name: Generate Cisco config from Jinja2
 hosts: cisco
 gather_facts: no
 tasks:
 - name: Generate Cisco Basic Config
 template:
 src: "templates/ios_basic.j2"
 dest: "configs/{{inventory_hostname}}.cfg"
 delegate_to: localhost

 - name: Generate Juniper config from Jinja2
 hosts: juniper
 gather_facts: no
 tasks:
 - name: Generate Juniper Basic Config
 template:
 src: "templates/junos_basic.j2"
 dest: "configs/{{inventory_hostname}}.cfg"
 delegate_to: localhost
  1. 按照这里所示的方式运行 Ansible 剧本:
$ ansible-playbook -i hosts ansible_jinja2.yml

它是如何工作的…

我们创建了network.yml文件,以便将适用于该组下所有设备的所有变量进行分组。之后,我们创建了两个 Jinja2 文件,一个用于 Cisco IOS 设备,另一个用于 Juniper 设备。在每个 Jinja2 模板中,我们使用{{}}引用 Ansible 变量。我们还使用 Jinja2 模板引擎支持的for循环构造{% for server in ntp_servers %},以循环遍历ntp_servers变量(这是一个列表),以访问此列表中的每个项目。

Ansible 提供了template模块,它有两个参数:

  • src:这引用了 Jinja2 模板文件。

  • dest:这指定将生成的输出文件。

在我们的情况下,我们使用{{inventory_hostname}}变量,以使输出配置文件对我们清单中的每个路由器都是唯一的。

默认情况下,template模块在远程管理节点上创建输出文件。但是,在我们的情况下,由于受管设备是网络节点,这是不可能的。因此,我们使用delegate_to: localhost选项,以便在 Ansible 控制机上本地运行此任务。

playbook 中的第一个 play 创建configs目录,用于存储网络设备的配置文件。第二个 play 在 Cisco 设备上运行模板模块,第三个 play 在 Juniper 设备上运行template任务。

以下是 Cisco 设备的配置文件:

$ cat configs/csr1.cfg
hostname edge-csr1
!
ntp 172.20.1.1
ntp 172.20.2.1
!

这是 Juniper 设备的配置文件:

$ cat configs/mx1.cfg
set system host-name core-mx1
set system ntp server 172.20.1.1
set system ntp server 172.20.2.1

另请参阅…

有关 Ansible 模板模块的更多信息,请参考以下网址:

docs.ansible.com/ansible/latest/modules/template_module.html

使用 Ansible 的过滤器

Ansible 的过滤器主要源自 Jinja2 过滤器,所有 Ansible 过滤器都用于转换和操作数据(Ansible 的变量)。除了 Jinja2 过滤器外,Ansible 还实现了自己的过滤器来增强 Jinja2 过滤器,同时还允许用户定义自己的自定义过滤器。在本教程中,我们将概述如何配置和使用 Ansible 过滤器来操作我们的输入数据。

如何做…

  1. 安装python3-pip和 Python 的netaddr库,因为我们将使用需要 Python 的netaddr库的 Ansible IP 过滤器:
# On ubuntu
$ sudo apt-get install python3-pip

# On CentOS
$ sudo yum install python3-pip

$ pip3 install netaddr
  1. 创建一个名为ansible_filters.yml的新 Ansible playbook,如下所示:
---
 - name: Ansible Filters
 hosts: csr1
 vars:
 interfaces:
 - { port: FastEthernet0/0, prefix: 10.1.1.0/24 }
 - { port: FastEthernet1/0, prefix: 10.1.2.0/24 }
 tasks:
 - name: Generate Interface Config
 blockinfile:
 block: |
 hostname {{ hostname | upper }}
 {% for intf in interfaces %}
 !
 interface {{ intf.port }}
 ip address {{intf.prefix | ipv4(1) | ipv4('address') }} {{intf.prefix | ipv4('netmask') }}
 !
 {% endfor %}
 dest: "configs/csr1_interfaces.cfg"
 create: yes
 delegate_to: localhost

工作原理…

首先,我们使用blockinfile模块在 Ansible 控制机上创建一个新的配置文件。此模块与template模块非常相似。但是,我们可以直接在block选项中编写 Jinja2 表达式。我们在 playbook 中使用vars参数定义了一个名为interfaces的新变量。此变量是一个列表数据结构,其中列表中的每个项目都是一个字典数据结构。此嵌套数据结构指定了每个接口上使用的 IP 前缀。

在 Jinja2 表达式中,我们可以看到我们使用了一些过滤器,如下所示:

  • {{ hostname | upper}}upper是一个 Jinja2 过滤器,将输入字符串的所有字母转换为大写。通过这种方式,我们将主机名变量的值传递给此过滤器,输出将是此值的大写版本。

  • {{intf.prefix | ipv4(1) | ipv4('address') }}:在这里,我们两次使用了 Ansible IP 地址过滤器。ipv4(1)接受输入 IP 前缀并输出此前缀中的第一个 IP 地址。然后,我们使用另一个 IP 地址过滤器ipv4('address'),以便仅获取 IP 前缀的 IP 地址部分。因此,在我们的情况下,我们取10.1.1.0/24,我们输出第一个接口的10.1.1.1

  • {{intf.prefix | ipv4('netmask') }}:在这里,我们使用 Ansible IP 地址过滤器来获取 IP 地址前缀的子网掩码,因此在我们的情况下,我们得到/24子网并将其转换为255.255.255.0

此 playbook 运行后,csr1路由器的输出文件如下所示:

$ cat configs/csr1_interfaces.cfg
# BEGIN ANSIBLE MANAGED BLOCK
hostname EDGE-CSR1
!
interface FastEthernet0/0
 ip address 10.1.1.1 255.255.255.0
!
!
interface FastEthernet1/0
 ip address 10.1.2.1 255.255.255.0
!
# END ANSIBLE MANAGED BLOCK

使用 Ansible 标签

Ansible 标签是一个强大的工具,允许我们在大型 Ansible playbook 中为特定任务打标签,并灵活选择基于我们指定的标签在给定 playbook 中运行哪些任务。在这个示例中,我们将概述如何配置和使用 Ansible 标签。

操作步骤…

  1. 创建一个名为ansible_tags.yml的新的 Ansible playbook,如下所示:
---
 - name: Using Ansible Tags
 hosts: cisco
 gather_facts: no
 tasks:
 - name: Print OSPF
 debug:
 msg: "Router {{ hostname }} will Run OSPF"
 tags: [ospf, routing]

 - name: Print BGP
 debug:
 msg: "Router {{ hostname }} will Run BGP"
 tags:
 - bgp
 - routing

 - name: Print NTP
 debug:
 msg: "Router {{ hostname }} will run NTP"
 tags: ntp
  1. 按照以下示例运行 playbook:
$ ansible-playbook ansible_tags.yml -i hosts --tags routing
  1. 再次运行 playbook,这次使用标签,如下所示:
$ ansible-playbook ansible_tags.yml -i hosts --tags ospf

$ ansible-playbook ansible_tags.yml -i hosts --tags routing

工作原理…

我们可以使用标签来标记具有特定标签的任务和 play,以控制执行哪些任务或 play。这在开发 playbook 时给予我们更多的控制,允许我们运行相同的 playbook,但每次运行时都可以控制我们部署的内容。在这个示例中的 playbook 中,我们已经将任务标记为 OSPF、BGP 或 NTP,并将routing标签应用到了 OSPF 和 BGP 任务。这允许我们有选择性地运行 playbook 中的任务,如下所示:

  • 如果没有指定标签,这将运行 playbook 中的所有任务,行为不会改变,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 使用ospf标签,我们将只运行标记有这个标签的任何任务,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 使用routing标签,我们将运行所有标记有这个标签的任务,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

另请参阅…

有关 Ansible 标签的更多信息,请参考以下 URL:

docs.ansible.com/ansible/latest/user_guide/playbooks_tags.html

自定义 Ansible 的设置

Ansible 有许多设置可以通过一个名为ansible.cfg的配置文件进行调整和控制。这个文件有多个选项,控制着 Ansible 的许多方面,包括 Ansible 的外观和它如何连接到受控设备。在这个示例中,我们将概述如何调整一些默认设置。

操作步骤…

  1. 创建一个名为ansible.cfg的新文件,如下所示:
[defaults]
inventory=hosts
vault_password_file=decryption_password
gathering=explicit

工作原理…

默认情况下,Ansible 的设置由位于/etc/ansible目录中的ansible.cfg文件控制。这是 Ansible 的默认配置文件,控制着 Ansible 与受控节点的交互方式。我们可以直接编辑这个文件。但是,这将影响到我们在 Ansible 控制机上使用的任何 playbook,以及这台机器上的任何用户。一个更灵活和定制的选项是在项目目录中包含一个名为ansible.cfg的文件,其中包含了需要从默认参数中修改的所有选项。在上面的示例中,我们只概述了其中的一小部分选项。

  • inventory: 这个选项修改了 Ansible 搜索的默认清单文件,以便找到它的清单(默认情况下,这是/etc/ansible/hosts)。我们调整这个选项,让 Ansible 使用我们的清单文件,并在每次运行 playbook 时停止使用-i操作符来指定我们的清单。

  • vault_password_file:这个选项设置了用于加密和解密ansible-vault密码的秘密密码文件。这个选项消除了在使用ansible-vault加密变量时需要使用--vault-id操作符运行 Ansible playbook 的需要。

  • gathering = explicit:默认情况下,Ansible 在运行 playbook 时运行一个设置模块来收集有关受控节点的事实。由于这个设置模块需要受控节点上的 Python 解释器,因此这个设置模块与网络节点不兼容。通过将事实收集设置为explicit,我们禁用了这个默认行为。

另请参阅…

有关 Ansible 配置设置的更多信息,请参考以下 URL:

docs.ansible.com/ansible/latest/reference_appendices/config.html#ansible-configuration-settings

使用 Ansible 角色

Ansible 角色促进了代码的可重用性,并提供了一种简单的方法来打包 Ansible 代码,以便可以共享和使用。Ansible 角色是所有所需的 Ansible 任务、处理程序和 Jinja2 模板的集合,它们以特定的结构打包在一起。角色应设计为提供特定的功能/任务。在这个示例中,我们将概述如何创建一个 Ansible 角色以及如何在我们的 playbooks 中使用它。

操作步骤…

  1. ch1_ansible文件夹中,创建一个名为roles的新文件夹,并创建一个名为basic_config的新角色,如下所示:
$ mkdir roles
$ cd roles
$ ansible-galaxy init basic_config
  1. 使用以下变量更新basic_config/vars/main.yml文件:
$ cat roles/basic_config/vars/main.yml

---
config_dir: basic_config
  1. 使用以下任务更新basic_config/tasks/main.yml文件:
$ cat roles/basic_config/tasks/main.yml

---
 - name: Create Configs Directory
 file:
 path: "{{ config_dir }}"
 state: directory
 run_once: yes

 - name: Generate Cisco Basic Config
 template:
 src: "{{os}}.j2"
 dest: "{{config_dir}}/{{inventory_hostname}}.cfg"
  1. basic_config/templates文件夹内,创建以下结构:
$ tree roles/basic_config/templates/

roles/basic_config/templates/
├── ios.j2
└── junos.j2

$ cat roles/basic_config/templates/ios.j2
hostname {{ hostname }}
!
{% for server in ntp_servers %}
ntp {{ server }}
{% endfor %}
  1. 创建一个新的 playbook,pb_ansible_role.yml,内容如下以使用我们的角色:
$ cat pb_ansible_role.yml
---
 - name: Build Basic Config Using Roles
 hosts: all
 connection: local
 roles:
 - basic_config

工作原理…

在这个示例中,我们首先在主文件夹中创建roles目录。默认情况下,使用角色时,Ansible 会按照以下顺序在以下位置查找角色:

  • 当前工作目录中的roles文件夹

  • /etc/ansible/roles

因此,我们在当前工作目录(ch1_ansible)中创建roles文件夹,以承载我们将在此文件夹中创建的所有角色。我们使用ansible-galaxy命令和init选项以及角色名称(basic_config)创建角色,这将在roles文件夹内创建以下角色结构:

$ tree roles/
roles/
└── basic_config
 ├── defaults
 │   └── main.yml
 ├── files
 ├── handlers
 │   └── main.yml
 ├── meta
 │   └── main.yml
 ├── README.md
 ├── tasks
 │   └── main.yml
 ├── templates
 ├── tests
 │   ├── inventory
 │   └── test.yml
 └── vars
 └── main.yml

从前面的输出可以看出,使用ansible-galaxy命令创建了这个文件夹结构,这个命令根据最佳实践角色布局构建了角色。并非所有这些文件夹都需要具有我们可以使用的功能角色,以下列表概述了通常使用的主要文件夹:

  • tasks文件夹:这包含了main.yml文件,列出了在使用此角色时应执行的所有任务。

  • templates文件夹:这包含我们将作为此角色一部分使用的所有 Jinja2 模板。

  • vars文件夹:这包含了我们想要定义并在角色中使用的所有变量。vars文件夹中的变量在运行 playbook 时评估变量时具有非常高的优先级。

  • handlers文件夹:这包含了main.yml文件,其中包含了作为此角色一部分应运行的所有处理程序。

我们创建的角色只有一个目的,那就是为我们的设备构建基本配置。为了完成这个任务,我们需要定义一些 Ansible 任务,并使用一些 Jinja2 模板来生成设备的基本配置。我们在tasks/main.yml文件中列出了所有需要运行的任务,并在templates文件夹中包含了所有必要的 Jinja2 模板。我们在vars文件夹中定义了我们将在角色中使用的任何必需变量。

我们创建一个新的 playbook,将使用我们的新角色来生成设备的配置。我们在roles参数中调用我们想要运行的所有角色作为 playbook 的一部分。在我们的情况下,我们只想要运行一个角色,即basic_config角色。

运行我们的 playbook 后,我们可以看到一个名为basic_config的新目录被创建,其中包含以下内容:

$ tree basic_config/
basic_config/
├── csr1.cfg
├── csr2.cfg
├── mx1.cfg
└── mx2.cfg    

另请参阅

有关 Ansible 角色的更多信息,请参阅以下网址:

docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html

第二章:使用 Ansible 管理 Cisco IOS 设备

在本章中,我们将概述如何使用 Ansible 自动化 Cisco 基于 IOS 的设备。我们将探索 Ansible 中可用的不同模块,以自动化配置并从 Cisco IOS 设备收集网络信息。本章将基于以下示例网络图,并将介绍如何使用 Ansible 实现此网络设计:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以下表格概述了 Cisco 节点上的管理 IP 地址,Ansible 将使用这些地址连接到设备:

设备角色供应商管理端口管理 IP
access01访问交换机Cisco IOS 15.1Ethernet0/0172.20.1.18
access02访问交换机Cisco IOS 15.1Ethernet0/0172.20.1.19
core01核心交换机Cisco IOS 15.1Ethernet0/0172.20.1.20
core02核心交换机Cisco IOS 15.1Ethernet0/0172.20.1.21
wan01WAN 路由器Cisco IOS-XE 16.6.1GigabitEthernet1172.20.1.22
wan02WAN 路由器Cisco IOS-XE 16.6.1GigabitEthernet1172.20.1.23

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

  • 构建 Ansible 网络清单

  • 连接到 Cisco IOS 设备

  • 配置基本系统信息

  • 在 IOS 设备上配置接口

  • 在 IOS 设备上配置 L2 VLAN

  • 配置干线和访问接口

  • 配置接口 IP 地址

  • 在 IOS 设备上配置 OSPF

  • 收集 IOS 设备信息

  • 在 IOS 设备上验证网络可达性

  • 从 IOS 设备检索操作数据

  • 使用 pyATS 和 Ansible 验证网络状态

技术要求

本章的代码文件可以在这里找到:

github.com/PacktPublishing/Network-Automation-Cookbook/tree/master/ch2_ios

本章基于的软件版本如下:

  • Cisco IOS 15.1

  • Cisco IOS-XE 16.6.1

  • Ansible 2.9

  • Python 3.6.8

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

bit.ly/34F8xPW

构建 Ansible 网络清单

在这个配方中,我们将概述如何构建和组织 Ansible 清单,以描述前一节中概述的网络设置。

准备工作

确保 Ansible 已经安装在控制机上。

操作步骤…

  1. 创建一个名为ch2_ios的新目录。

  2. 在这个新文件夹里,创建hosts文件,内容如下:

$ cat hosts
 [access]
 access01 Ansible_host=172.20.1.18
 access02 Ansible_host=172.20.1.19

[core]
 core01 Ansible_host=172.20.1.20
 core02 Ansible_host=172.20.1.21

[wan]
 wan01 Ansible_host=172.20.1.22
 wan02 Ansible_host=172.20.1.23

[lan:children]
 access
 core

[network:children]
 lan
 wan
  1. 创建Ansible.cfg文件,内容如下:
$ cat Ansible.cfg

[defaults]
 inventory=hosts
 retry_files_enabled=False
 gathering=explicit

它是如何工作的…

我们使用hosts文件构建了 Ansible 清单,并以以下方式定义了多个组,以便对我们拓扑中的不同设备进行分组:

  • 我们创建了access组,其中包括我们拓扑中的访问交换机(access01access02)。

  • 我们创建了core组,将所有作为访问交换机上所有 VLAN 的 L3 终止的核心交换机分组在一起。

  • 我们创建了wan组,将所有我们的 Cisco IOS-XE 路由器分组在一起,它们将作为我们的 wan 路由器。

  • 我们创建了另一个名为lan的组,将访问组和核心组分组在一起。

  • 我们创建了network组,将lanwan组分组在一起。

最后,我们创建了Ansible.cfg文件,并配置它指向我们的hosts文件,以用作 Ansible 清单文件。我们禁用了设置模块,在针对网络节点运行 Ansible 时不需要它。

连接到 Cisco IOS 设备

在这个配方中,我们将概述如何通过 SSH 从 Ansible 连接到 Cisco IOS 设备,以便从 Ansible 开始管理设备。

准备工作

为了按照这个配方进行操作,应该按照前面的配方构建一个 Ansible 清单文件。必须配置 Ansible 控制机与网络中所有设备之间的 IP 可达性。

操作步骤…

  1. ch2_ios目录中,创建groups_vars文件夹。

  2. group_vars文件夹中,创建以下内容的network.yml文件:

$cat network.yml
Ansible_network_os: ios
Ansible_connection: network_cli
Ansible_user: lab
Ansible_password: lab123
Ansible_become: yes
Ansible_become_password: admin123
Ansible_become_method: enable
  1. 在所有 IOS 设备上,确保配置以下内容以设置 SSH 访问:
!
 hostname <device_hostname>
 !
 ip domain name <domain_name>
 !
 username lab secret 5 <password_for_lab_user>.
 !
 enable secret 5 <enable_password>.
 !
 line vty 0 4
 login local
 transport input SSH
 !
  1. 从配置模式在 Cisco IOS 设备上生成 SSH 密钥,如下面的代码片段所示:
(config)#crypto key generate rsa
 Choose the size of the key modulus in the range of 360 to 4096 for your
 General Purpose Keys. Choosing a key modulus greater than 512 may take
 a few minutes.
How many bits in the modulus [512]: 2048
 % Generating 2048 bit RSA keys, keys will be non-exportable...
 [OK] (elapsed time was 0 seconds)
  1. 使用以下突出显示的参数更新Ansible.cfg文件:
$ cat Ansible.cfg
[defaults]
 host_key_checking=False

工作原理…

在我们的示例网络中,我们将使用 SSH 来建立 Ansible 与我们的 Cisco 设备之间的连接。在这个设置中,Ansible 将使用 SSH 来建立与我们的 Cisco 设备的连接,以开始对其进行管理。我们将使用用户名/密码身份验证来验证我们的 Ansible 控制节点与我们的 Cisco 设备。

在 Cisco 设备上,我们必须确保存在 SSH 密钥,以便在 Cisco 设备上有一个功能性的 SSH 服务器。以下代码片段概述了在生成 SSH 密钥之前 Cisco 设备上的 SSH 服务器的状态:

wan01#show ip SSH
SSH Disabled - version 2.0
%Please create RSA keys to enable SSH (and of atleast 768 bits for SSH v2).
Authentication methods:publickey,keyboard-interactive,password
Authentication Publickey Algorithms:x509v3-SSH-rsa,SSH-rsa
Hostkey Algorithms:x509v3-SSH-rsa,SSH-rsa
Encryption Algorithms:aes128-ctr,aes192-ctr,aes256-ctr
MAC Algorithms:hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha1-96
KEX Algorithms:diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Authentication timeout: 120 secs; Authentication retries: 3
Minimum expected Diffie Hellman key size : 2048 bits
IOS Keys in SECSH format(SSH-rsa, base64 encoded): NONE

一旦我们创建了 SSH 密钥,Cisco 设备上的 SSH 服务器就可以运行,并准备好接受来自 Ansible 控制节点的 SSH 连接。

在 Ansible 机器上,我们在network.yml文件中包含了与受管设备建立 SSH 连接所需的所有变量。根据我们的清单文件,网络组包括拓扑中的所有设备,因此我们在此文件中配置的所有属性将应用于清单中的所有设备。以下是我们在文件中包含的属性的详细信息:

  • Ansible_connection: 这确定了 Ansible 如何连接到设备。在这种情况下,我们将其设置为network_cli,以指示我们将使用 SSH 连接到网络设备。

  • Ansible_network_os: 当使用network_cli作为连接插件连接到网络设备时,我们必须指示 Ansible 将连接到哪个网络 OS,以便使用正确的 SSH 参数与设备连接。在这种情况下,我们将其设置为ios,因为我们拓扑中的所有设备都是基于 IOS 的设备。

  • Ansible_user: 此参数指定 Ansible 将用于与网络设备建立 SSH 会话的用户名。

  • Ansible_password: 此参数指定 Ansible 将用于与网络设备建立 SSH 会话的密码。

  • Ansible_become: 这指示 Ansible 在配置或执行受管设备上的show命令时,使用enable命令进入特权模式。在我们的情况下,我们将其设置为yes,因为我们需要特权模式来配置设备。

  • Ansible_become_password: 这指定了用于在受管 IOS 设备上进入特权模式的enable密码。

  • Ansible_become_method: 此选项指定进入特权模式时要使用的方法。在我们的情况下,这是 IOS 设备上的enable命令。

在这个示例中,我已经以明文形式定义了 SSH 密码和enable密码,仅仅是为了简单起见;然而,这是极不鼓励的。我们应该使用Ansible-vault来保护密码,就像在上一章的Ansible Vault示例中所概述的那样。

在 Cisco 设备上,我们设置了所需的用户名和密码,以便 Ansible 可以打开 SSH 连接到受管的 Cisco IOS 设备。我们还配置了一个enable密码,以便能够进入特权模式,并进行配置更改。一旦我们将所有这些配置应用到设备上,我们就准备好设置 Ansible 了。

在任何 SSH 连接中,当 SSH 客户端(在我们的情况下是 Ansible 控制节点)连接到 SSH 服务器(在我们的情况下是 Cisco 设备)时,服务器会在客户端登录之前向客户端发送其公钥的副本。这用于在客户端和服务器之间建立安全通道,并向客户端验证服务器,以防止任何中间人攻击。因此,在涉及新设备的新 SSH 会话开始时,我们会看到以下提示:

$SSH lab@172.20.1.18
The authenticity of host '172.20.1.18 (172.20.1.18)' can't be established.
RSA key fingerprint is SHA256:KnWOalnENZfPokYYdIG3Ogm9HDnXIwjh/it3cqdiRRQ.
RSA key fingerprint is MD5:af:18:4b:4e:84:19:a6:8d:82:17:51:d5:ee:eb:16:8d.
Are you sure you want to continue connecting (yes/no)?

当 SSH 客户端启动 SSH 连接到客户端时,SSH 服务器会向客户端发送其公钥,以便向客户端进行身份验证。客户端在其本地已知的hosts文件(在~/.SSH/known_hosts/etc/SSH/SSH_known_hosts文件中)中搜索公钥。如果在其本地已知的hosts文件中找不到此计算机的公钥,它将提示用户将此新密钥添加到其本地数据库中,这就是我们在启动 SSH 连接时看到的提示。

为了简化 Ansible 控制节点与其远程管理的hosts之间的 SSH 连接设置,我们可以禁用此主机检查。我们可以通过在Ansible.cfg配置文件中将host_key_checking设置为False来告诉 Ansible 忽略主机密钥并不将其添加到已知的hosts文件中。

禁用主机密钥检查不是最佳实践,我们只是将其显示为实验室设置。在下一节中,我们将概述在 Ansible 和其远程管理设备之间建立 SSH 连接的另一种方法。

还有更多…

如果我们需要验证我们将连接到的 SSHhosts的身份,并因此启用host_key_checking,我们可以使用 Ansible 自动将远程管理的hosts的 SSH 公钥添加到~/.SSH/known_hosts文件中。我们创建一个新的 Ansible playbook,将在 Ansible 控制机器上使用ssk-keyscan命令连接到远程设备。然后收集远程机器的 SSH 公钥并将其添加到~/.SSH/known_hosts文件中。该方法在此处概述:

  1. 创建一个新的playbook pb_gather_SSH_keys.yml文件,并添加以下 play:
- name: "Play2: Record Keys in Known Hosts file"
 hosts: localhost
 vars:
 - hosts_file: "~/.SSH/known_hosts"
tasks:
 - name: create know hosts file
 file:
 path: "{{ hosts_file }}"
 state: file
 changed_when: false
  1. 更新 playbook 并在同一 playbook 中添加另一个 play 以保存和存储远程管理节点的 SSH 公钥:
- name: "Play2: Record Keys in Known Hosts file"
 hosts: localhost
 vars:
 - hosts_file: "~/.SSH/known_hosts"
 tasks:
 - name: create know hosts file
 file:
 path: "{{ hosts_file }}"
 state: file
 changed_when: false
 - name: Populate the known_hosts file
 blockinfile:
 block: |
 {% for host in groups['all'] if  hostvars[host].SSH_keys.stdout != '' 
%}
 {{ hostvars[host].SSH_keys.stdout}}
 {% endfor %}
 path: "{{ hosts_file }}"
 create: yes

在我们的新 playbook 中,我们通过将hosts参数设置为all来针对所有受管设备进行 play。在此 play 中,我们有一个单独的任务,我们在 Ansible 控制节点上运行(使用delegate_to localhost)以发出SSH-keyscan命令,该命令返回远程设备的 SSH 公钥,如下所示:

$ SSH-keyscan 172.20.1.22

# 172.20.1.22:22 SSH-2.0-Cisco-1.25
 172.20.1.22 SSH-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTwrH4phzRnW/RsC8eXMh/accIErRfkgffDWBGSdEX0r9EwAa6p2uFMWj8dq6kvrREuhqpgFyMoWmpgdx5Cr+10kEonr8So5yHhOhqG1SJO9RyzAb93H0P0ro5DXFK8A/Ww+m++avyZ9dShuWGxKj9CDM6dxFLg9ZU/9vlzkwtyKF/+mdWNGoSiCbcBg7LrOgZ7Id7oxnhEhkrVIa+IxxGa5Pwc73eR45Uf7QyYZXPC0RTOm6aH2f9+8oj+vQMsAzXmeudpRgAu151qUH3nEG9HIgUxwhvmi4MaTC+psmsGg2x26PKTOeX9eLs4RHquVS3nySwv4arqVzDqWf6aruJ

在此任务中,我们使用delegate_to等于localhost,因为 Ansible 将尝试连接到远程设备并默认在远程设备上发出命令。在我们的情况下,这不是我们需要的;我们需要从 Ansible 控制节点发出此命令。因此,我们使用delegate_to等于localhost来强制执行此行为。

我们通过将hosts设置为“localhost”在 Ansible 控制主机上运行第二个 play,并执行任务以创建已知的主机文件(如果尚未存在),并使用SSH_keys变量在此文件中填充我们在第一个 play 中捕获的数据。我们在 Ansible 控制机器上运行此 playbook,以在运行任何 playbook 之前存储来自远程管理节点的 SSH 密钥。

配置基本系统信息

在这个示例中,我们将概述如何在 Cisco IOS 设备上配置基本系统参数,例如设置主机名、DNS 服务器和 NTP 服务器。根据我们在本章开头概述的网络设置,我们将在所有 Cisco IOS 设备上配置以下信息:

  • DNS 服务器 172.20.1.250 和 172.20.1.251

  • NTP 服务器 172.20.1.17

准备就绪

必须存在一个 Ansible 清单文件,以及通过 SSH 连接到 Cisco IOS 设备的 Ansible 配置。

如何操作…

  1. group_vars/network.yml文件中,添加以下系统参数:
$ cat group_vars/network.yml
<-- Output Trimmed for brevity ------>
name_servers:
 - 172.20.1.250
 - 172.20.1.251
ntp_server: 172.20.1.17
  1. 创建一个名为pb_build_network.yml的新播放文件,包含以下信息:
$ cat pb_build_network.yml
 ---
- name: "PLAY 1: Configure All Lan Switches"
 hosts: lan
 tags: lan
 tasks:
 - name: "Configure Hostname and Domain Name"
 ios_system:
 hostname: "{{ inventory_hostname }}"
 domain_name: "{{ domain_name }}"
 lookup_enabled: no
 name_servers: "{{ name_servers }}"
 - name: "Configure NTP"
 ios_ntp:
 server: "{{ ntp_server }}"
 logging: true
 state: present

它是如何工作的…

network.yml文件中,我们将name_servers变量定义为 DNS 服务器列表,并定义ntp_servers变量,它定义了我们要在 IOS 设备上配置的 NTP 服务器。在network.yml文件中定义这些参数将这些变量应用于网络组中的所有设备。

我们创建了一个播放文件,第一个播放目标是lan组中的所有hosts(包括访问设备和核心设备),在这个播放中,我们引用了两个任务:

  • ios_system:这在设备上设置主机名和 DNS 服务器。

  • ios_ntp:这在 IOS 设备上配置 NTP 并启用 NTP 事件的日志记录。

这两个模块都是声明性的 Ansible 模块,我们只需确定与我们基础设施相关的状态。Ansible 将此声明转换为必要的 IOS 命令。这些模块检索设备的配置,并将当前状态与我们的预期状态进行比较(在它们上配置了 DNS 和 NTP),然后,如果当前状态与这些模块定义的预期状态不符,Ansible 将对设备应用所需的配置。

当我们在所有 LAN 设备上运行这些任务时,以下配置将被推送到设备上:

!
 ip name-server 172.20.1.250 172.20.1.251
 no ip domain lookup
 ip domain name lab.net
 !
 ntp logging
 ntp server 172.20.1.17
 !

另请参阅…

有关ios_systemios_ntp模块以及这些模块支持的不同参数的更多信息,请参考以下网址:

在 IOS 设备上配置接口

在这个示例中,我们将概述如何在基于 Cisco IOS 的设备上配置基本接口属性,例如设置接口描述、接口最大传输单元(MTU)和启用interfaces。我们将配置拓扑中的所有链路的链路 MTU 为 1,500,并且设置为全双工。

准备工作

要按照这个示例进行操作,假设已经设置了 Ansible 清单,并且 Ansible 控制节点与已经放置的 Cisco 设备之间具有 IP 可达性。

如何操作…

  1. group_vars/network.yml文件中,添加以下内容来定义通用接口参数:
$ cat group_vars/network.yml
<-- Output Trimmed for brevity ------>
intf_duplex: full
intf_mtu: 1500
  1. group_vars文件夹下创建一个新文件lan.yml,包含以下数据来定义我们的 Cisco 设备上的interfaces
$ cat group_vars/lan.yaml

interfaces:
 core01:
 - name: Ethernet0/1
 description: access01_e0/1
 mode: trunk
 - name: Ethernet0/2
 description: access02_e0/1
 mode: trunk
 - name: Ethernet0/3
 description: core01_e0/3
 mode: trunk
 <--   Output Trimmed for brevity ------>
 access01:
 - name: Ethernet0/1
 description: core01_e0/1
 mode: trunk
 - name: Ethernet0/2
 description: core02_e0/1
 mode: trunk
 - name: Ethernet0/3
 description: Data_vlan
 mode: access
 vlan: 10
  1. 更新pb_build_network.yml播放文件,增加以下任务来设置interfaces
 - name: "P1T3: Configure Interfaces"
 ios_interface:
 name: "{{ item.name }}"
 description: "{{ item.description }}"
 duplex: "{{ intf_duplex }}"
 mtu: "{{ intf_mtu }}"
 state: up
 loop: "{{ interfaces[inventory_hostname] }}"
 register: ios_intf

它是如何工作的…

在这个示例中,我们概述了如何在 IOS 设备上配置物理接口。我们首先声明适用于所有接口的通用参数(接口双工和 MTU)。这些参数在network.yml文件下定义。接下来,我们在lan.yml文件下定义了所有我们的 LAN 设备的特定接口参数,以应用于所有设备。所有这些参数都在interfaces字典数据结构中声明。

我们更新我们的手册,增加了一个新任务,用于配置网络中所有 LAN 设备的物理参数。我们使用ios_interface模块来配置所有interface参数,并使用interfaces数据结构在每个节点上循环所有interfaces。我们将状态设置为up,以指示interface应该存在并且可操作。

另请参阅…

有关ios_interface模块以及这些模块支持的不同参数的更多信息,请参考以下网址:docs.Ansible.com/Ansible/latest/modules/ios_interface_module.html

在 IOS 设备上配置 L2 VLAN

在本配方中,我们将概述如何根据本章介绍中讨论的网络拓扑在 Cisco IOS 设备上配置 L2 VLAN。我们将概述如何将 VLAN 声明为 Ansible 变量,以及如何使用适当的 Ansible 模块在网络上提供这些 VLAN。

准备工作

我们将在本章讨论的先前配方的基础上继续构建,以继续配置样本拓扑中所有 LAN 设备上的 L2 VLAN。

如何做…

  1. 使用以下代码更新group_vars/lan.yml文件中的 VLAN 定义:
$ cat group_vars/lan.yaml

vlans:
 - name: Data
 vlan_id: 10
 - name: Voice
 vlan_id: 20
 - name: Web
 vlan_id: 100
  1. 使用以下任务更新pb_build.yml playbook 以提供 VLAN:
 - name: "P1T4: Create L2 VLANs"
 ios_vlan:
 vlan_id: "{{ item.vlan_id }}"
 name: "{{ item.name  }}"
 loop: "{{ vlans }}"
 tags: vlan

它是如何工作的…

group_vars/lan.yml文件中,我们定义了一个vlans列表数据结构,其中包含我们需要应用于所有核心和接入交换机的 VLAN 定义。此变量将对所有核心和接入交换机可用,并且 Ansible 将使用此变量来在远程设备上提供所需的 VLAN。

我们使用另一个声明性模块ios_vlan,它接受 VLAN 定义(名称和 VLAN ID)并在远程托管设备上配置这些 VLAN。它从设备中提取现有配置,并将其与需要存在的设备列表进行比较,仅推送增量。

我们使用loop结构遍历vlans列表中的所有项目,并在所有设备上配置所有相应的 VLAN。

在设备上运行此任务后,以下是一个接入交换机的输出:

access01#sh vlan
VLAN Name                             Status    Ports
---- -------------------------------- --------- -------------------------------
1    default                          active    Et1/0, Et1/1, Et1/2, Et1/3
10   Data                             active    Et0/3
20   Voice                            active
100  Web                              active

配置干道和接入接口

在这个配方中,我们将展示如何在基于 Cisco IOS 的设备上配置接入和干道接口,以及如何将接口映射到接入 VLAN,以及如何在干道上允许特定的 VLAN。

准备工作

根据我们的样本拓扑,我们将配置设备上的接口。如表所示,我们只显示access01core01的 VLAN - 其他设备是完全相同的副本:

设备接口模式VLANs
Core01Ethernet0/1干道10,20,100
Core01Ethernet0/2干道10,20,100
Core01Ethernet0/3干道10,20,100,200
Access01Ethernet0/1干道10,20,100
Access01Ethernet0/2干道10,20,100
Access01Ethernet0/3接入10

如何做…

  1. group_vars下创建一个新的core.yml文件,并包括以下core_vlans定义:
core_vlans:
 - name: l3_core_vlan
 vlan_id: 200
 interface: Ethernet0/3
  1. 使用以下任务更新pb_build_network.yml playbook 以配置所有干道端口:
 - name: "Configure L2 Trunks"
 ios_l2_interface:
 name: "{{ item.name }}"
 mode: "{{ item.mode }}"
 trunk_allowed_vlans: "{{ vlans | map(attribute='vlan_id') | join(',') }}"
 state: present
 loop: "{{ interfaces[inventory_hostname] |
selectattr('mode','equalto','trunk') | list }}"
 - name: "Enable dot1q Trunks"
 ios_config:
 lines:
 - switchport trunk encapsulation dot1q
 parents: interface {{item.name}}
 loop: "{{ interfaces[inventory_hostname] |
selectattr('mode','equalto','trunk') | list }}"
 tags: dot1q
  1. 使用以下任务更新 playbook 以配置所有接入端口:
 - name: "Configure Access Ports"
 ios_l2_interface:
 name: "{{ item.name }}"
 mode: "{{ item.mode}}"
 access_vlan: "{{ item.vlan }}"
 state: present
 loop: "{{ interfaces[inventory_hostname] |
selectattr('mode','equalto','access') | list }}"

它是如何工作的…

我们在lan.yml文件中使用相同的数据结构,该数据结构定义了 LAN 网络中的所有接口并描述其类型(接入/干道)。对于接入端口,我们定义了哪个接入接口属于哪个 VLAN。我们将引用此列表数据结构来配置lan组中所有设备上的接入和干道端口。

我们的layer2网络中的接口有以下两个选项之一:

接入

  • 我们使用ios_l2_interfaceaccess_vlan参数在接口上配置正确的接入 VLAN。

  • 我们使用selectattr jinja2过滤器仅选择每个设备的接入接口,并且仅匹配模式等于access的一个接口,并且我们循环遍历每个设备的此列表。

干道

  • 我们使用ios_l2_interfacetrunk_allowed_vlans参数将所有 VLAN 添加到干道端口上,包括接入和核心交换机。

  • 我们使用 Jinja2 的mapjoin过滤器创建允许的 VLAN 列表,并将此过滤器应用于vlans列表数据结构。这将输出类似于以下内容的字符串:10,20,100

  • 我们使用selectattr Jinja2 过滤器从每个节点的接口数据结构中仅选择 trunk 端口。

  • 我们需要将这些 trunk 端口配置为dot1q端口;但是,ios_l2_interface上仍未启用此属性。因此,我们使用另一个模块ios_config发送所需的 Cisco IOS 命令来设置dot1q trunk。

以下输出概述了作为示例应用于access01设备的配置,用于访问和 trunk 端口:

!
interface Ethernet0/3   >> Access Port
 description Data_vlan
 switchport access vlan 10
 switchport mode access

 !
interface Ethernet0/1    >> Trunk Port
 description core01_e0/1
 switchport trunk encapsulation dot1q
 switchport trunk allowed vlan 10,20,100
 switchport mode trunk

另请参阅…

有关ios_l2_interface和这些模块支持的不同参数的更多信息,请参阅以下网址:

docs.Ansible.com/Ansible/latest/modules/ios_l2_interface_module.html

配置接口 IP 地址

在本教程中,我们将探讨如何在 Cisco IOS 设备上配置接口 IP 地址。我们将使用示例拓扑在两个核心交换机上配置 VLAN 接口。我们将概述如何在核心交换机之间为所有 VLAN 接口配置 VRRP。我们将配置以下 IP 地址:

接口前缀VRRP IP 地址
VLAN1010.1.10.0/2410.1.10.254
VLAN2010.1.20.0/2410.1.20.254
VLAN10010.1.100.0/2410.1.100.254

准备工作

本教程假定接口和 VLAN 已根据本章中的先前教程进行了配置。

如何做…

  1. 更新group_vars/core.yml文件,使用以下数据定义 SVI 接口:
$ cat group_vars/core.yml
<-- Output Trimmed for brevity ------>
svi_interfaces:
 - name: Vlan10
 ipv4: 10.1.10.0/24
 vrrp: yes
 ospf: passive
 -  name: Vlan20
 ipv4: 10.1.20.0/24
 vrrp: yes
 ospf: passive
 -  name: Vlan100
 ipv4: 10.1.100.0/24
 vrrp: yes
 ospf: passive
  1. host_vars文件夹下创建core01.ymlcore02.yml文件,并添加以下内容:
$ cat host_vars/core01.yml
 hst_svi_id: 1
 hst_vrrp_priority: 100
$ cat host_vars/core02.yml
 hst_svi_id: 2
 hst_vrrp_priority: 50
  1. 更新pb_build_network.yml playbook,添加以下任务以创建和启用 L3 SVI 接口:
- name: "PLAY 2: Configure Core Switches"
 hosts: core
 tags: l3_core
 tasks:
<-- Output Trimmed for brevity ------>
 - name: "Create L3 VLAN Interfaces"
 ios_l3_interface:
 name: "{{item.name }}"
 ipv4: "{{item.ipv4 | ipv4(hst_svi_id)}}"
 loop: "{{svi_interfaces}}"
 tags: l3_svi
 - name: "Enable the VLAN Interfaces"
 ios_interface:
 name: "{{ item.name }}"
 state: up
 loop: "{{ svi_interfaces }}"
  1. 更新 playbook,添加以下任务以在 SVI 接口上设置 VRRP 配置:
 - name: "Create VRRP Configs"
 ios_config:
 parents: interface {{ item.name }}
 lines:
 - vrrp {{item.name.split('Vlan')[1]}} priority {{ hst_vrrp_priority }}
 - vrrp {{item.name.split('Vlan')[1]}} ip {{item.ipv4 | ipv4(254)|ipaddr('address')}}
 loop: "{{svi_interfaces | selectattr('vrrp','equalto',true) | list }}"

它是如何工作的…

在本节中,我们正在为核心交换机上的 L3 VLAN 接口配置 IP 地址,并在所有 L3 VLAN 接口上配置 VRRP 以提供 L3 冗余。

我们正在使用一个名为svi_interfaces的新列表数据结构,它描述了所有带有 L3 IP 地址的 SVI 接口,以及一些额外的参数来控制这些接口上配置的 VRRP 和 OSPF。我们还在每个核心路由器上设置了两个新变量,hst_svi_idhst_vrrp_priority,我们将在 playbook 中使用它们来控制每个核心交换机上的 IP 地址,以及 VRPP 优先级。

我们使用ios_l3_interface Ansible 模块在 VLAN 接口上设置 IPv4 地址。在每个核心交换机上,我们循环遍历svi_interfaces数据结构,对于每个 VLAN,我们在相应的 VLAN 接口上配置 IPv4 地址。我们使用 Ansible 的ipaddr过滤器确定每个路由器上配置的 IP 地址,以及hst_svi_id参数{{item.ipv4 | ipv4(hst_svi_id)}}。例如,对于 VLAN10,我们将为core01分配10.1.10.1/24,为core02分配10.1.10.2/24

在首次创建 Cisco IOS 设备上的 VLAN 接口时,它们处于关闭状态,因此我们需要启用它们。我们使用ios_interface模块启用接口。

对于 VRRP 部分,我们将再次使用ios_config模块在所有 VLAN 接口上设置 VRRP 配置,并使用hst_vrrp_priority正确设置core01作为所有 VLAN 的主 VRRP。

在运行 playbook 后,以下是推送到设备上的配置示例:

Core01
 ========
 !
 interface Vlan10
 ip address 10.1.10.1 255.255.255.0
 vrrp 10 ip 10.1.10.254
 !
Core02
 =======
 !
 interface Vlan10
 ip address 10.1.10.2 255.255.255.0
 vrrp 10 ip 10.1.10.254
 vrrp 10 priority 50

另请参阅…

有关ios_l3_interface和这些模块支持的不同参数的更多信息,请参阅以下网址:

docs.Ansible.com/Ansible/latest/modules/ios_l3_interface_module.html

在 IOS 设备上配置 OSPF

在本教程中,我们将概述如何使用 Ansible 在 Cisco IOS 设备上配置 OSPF。使用我们的示例网络拓扑,我们将在核心交换机和 WAN 路由器之间设置 OSPF,并通过 OSPF 广告 SVI 接口。

准备工作

本教程假设所有接口已经配置了正确的 IP 地址,并且遵循了前面教程中概述的相同流程。

操作步骤

  1. 使用以下数据更新group_vars/core.yml文件,定义核心交换机和 WAN 路由器之间的核心链路:
core_l3_links:
 core01:
 - name: Ethernet1/0
 description: wan01_Gi2
 ipv4: 10.3.1.0/30
 ospf: yes
 ospf_metric: 100
 peer: wan01
 core02:
 - name: Ethernet1/0
 description: wan02_Gi2
 ipv4: 10.3.1.4/30
 ospf: yes
 ospf_metric: 200
 peer: wan02
  1. 更新pb_build_network.ymlplaybook,添加以下任务来设置 OSPF:
- name: "PLAY 2: Configure Core Switches"
 hosts: core
 tags: l3_core
 tasks:
< -------- Snippet -------- >
 - name: "P2T9: Configure OSPF On Interfaces"
 ios_config:
 parents: interface {{ item.name }}
 lines:
 - ip ospf {{ ospf_process }} area {{ ospf_area }}
 - ip ospf network point-to-point
 - ip ospf cost {{item.ospf_metric | default(ospf_metric)}}
 loop: "{{ (svi_interfaces + core_l3_links[inventory_hostname]) | selectattr('ospf') | list }}"
 - name: "P2T10: Configure OSPF Passive Interfaces"
 ios_config:
 parents: router ospf {{ ospf_process }}
 lines: passive-interface {{item.name}}
 loop: "{{ (svi_interfaces + core_l3_links[inventory_hostname]) | selectattr('ospf','equalto','passive') | list }}"

工作原理

我们在core.yml文件中创建了另一个字典数据结构,描述了核心交换机和 WAN 路由器之间的 L3 链路。我们指定它们是否会运行 OSPF 以及这些链路上的 OSPF 度量。

目前,Ansible 没有提供用于管理基于 IOS 设备的 OSPF 配置的声明性模块。因此,我们需要使用ios_config模块推送所需的配置。我们使用ios_config创建了两个单独的任务,以便在每个设备上推送与 OSPF 相关的配置。在第一个任务中,我们在每个接口下配置了接口相关的参数,并循环遍历了svi_interfacecore_l3_interfaces数据结构,以在所有 OSPF 启用的接口上启用 OSPF。我们使用 Jinja2 的selectattr过滤器来选择所有具有设置为yes/true的 OSPF 属性的接口。

在最后一个任务中,我们将被动接口配置应用到所有已启用被动标志的接口上。我们使用 Jinja2 的selectattr过滤器来仅选择那些被动参数设置为yes/true的接口。

收集 IOS 设备信息

在本教程中,我们将概述如何使用 Ansible 从 Cisco 设备中收集设备信息。这些信息包括序列号、IOS 版本以及设备上的所有接口。Ansible 在托管的 IOS 设备上执行多个命令以收集这些信息。

准备工作

Ansible 控制器必须与托管网络设备具有 IP 连接,并且 IOS 设备上必须启用 SSH。

操作步骤

  1. 在与以下信息相同的ch2_ios文件夹中创建一个名为pb_collect_facts.yml的新 playbook:
---
- name: "PLAY 1: Collect Device Facts"
 hosts: core,wan
 tasks:
 - name: "P1T1: Gather Device Facts"
 ios_facts:
 register: device_facts
 - debug: var=device_facts

工作原理

我们对corewan组中的所有节点运行这个新的 playbook,并使用ios_facts模块从托管的 IOS 设备中收集信息。在本教程中,我们使用 debug 模块打印从ios_facts模块收集的信息。以下是从ios_facts模块中发现的信息的一个子集:

ok: [core01 -> localhost] => {
 "Ansible_facts": {
 "net_all_ipv4_addresses": [
 "172.20.1.20",
< ---------- Snippet ------------ >
 "10.1.100.1"
 ],
 "net_hostname": "core01",
 "net_interfaces": {
 < ---------- Snippet ------------ >
 "Vlan10": {
 "bandwidth": 1000000,
 "description": null,
 "duplex": null,
 "ipv4": [
 {
 "address": "10.1.10.1",
 "subnet": "24"
 }
 ],
 "lineprotocol": "up",
 "macaddress": "aabb.cc80.e000",
 "mediatype": null,
 "mtu": 1500,
 "operstatus": "up",
 "type": "Ethernet SVI"
 },

 },
 "net_iostype": "IOS",
 "net_serialnum": "67109088",
 "net_system": "ios",
 "net_version": "15.1",
 }
 < ------------ Snippet ------------ >
 }

从前面的输出中,我们可以看到ios_facts模块从设备中捕获的一些主要信息,包括以下内容:

  • net_all_ipv4_addresses:这个列表数据结构包含了在 IOS 设备上所有接口上配置的所有 IPv4 地址。

  • net_interfaces:这个字典数据结构捕获了该设备上所有接口的状态和操作状态,以及其他重要信息,比如描述和操作状态。

  • net_serialnum:这捕获了设备的序列号。

  • net_version:这捕获了设备上运行的 IOS 版本。

还有更多…

使用从ios_facts模块收集的信息,我们可以为网络的当前状态生成结构化报告,并在进一步的任务中使用这些报告。在本节中,我们将概述如何修改我们的 playbook 来构建这个报告。

pb_collect_facts.ymlplaybook 中添加一个新任务,如下所示:

- name: "P1T2: Write Device Facts"
 blockinfile:
 path: ./facts.yml
 create: yes
 block: |
 device_facts:
 {% for host in play_hosts %}
 {% set node = hostvars[host] %}
 {{ node.Ansible_net_hostname }}:
 serial_number: {{ node.Ansible_net_serialnum }}
 ios_version: {{ node.Ansible_net_version }}
 {% endfor %}
 all_loopbacks:
 {% for host in play_hosts %}
 {% set node = hostvars[host] %}
 {% if node.Ansible_net_interfaces is defined %}
 {% if node.Ansible_net_interfaces.Loopback0 is defined %}
 - {{ node.Ansible_net_interfaces.Loopback0.ipv4[0].address }}
 {% endif %}
 {% endif %}
 {% endfor %}
 run_once: yes
 delegate_to: localhost

我们使用blockinfile模块构建一个名为facts.yml的 YAML 文件。我们在blockinfile模块中使用 Jinja2 表达式来自定义和选择我们想要从ios_facts任务捕获的 Ansible 事实中捕获的信息。当我们运行pb_collect_facts.yml playbook 时,我们生成了facts.yml文件,其中包含以下数据:

device_facts:
 wan01:
 serial_number: 90L4XVVPL7V
 ios_version: 16.06.01
 wan02:
 serial_number: 9UOFOO7FH19
 ios_version: 16.06.01
 core01:
 serial_number: 67109088
 ios_version: 15.1
 core02:
 serial_number: 67109104
 ios_version: 15.1
all_loopbacks:
 - 10.100.1.3
 - 10.100.1.4
 - 10.100.1.1
 - 10.100.1.2

另请参阅…

有关ios_facts和这些模块支持的不同参数的更多信息,请参考以下 URL:

docs.Ansible.com/Ansible/latest/modules/ios_facts_module.html

验证 IOS 设备的网络可达性

在这个示例中,我们将概述如何使用 Ansible 通过ping来验证网络可达性。 ICMP 允许我们验证网络上的正确转发。使用 Ansible 执行此任务为我们提供了一个强大的工具来验证正确的流量转发,因为我们可以同时从每个节点执行此任务并收集所有结果以供进一步检查。

准备工作

这个示例是基于章节介绍中概述的网络设置构建的,我假设网络已经根据本章中的所有先前示例构建好了。

如何做…

  1. 创建一个名为pb_net_validate.yml的新 playbook,并添加以下任务以存储所有 SVI IP 地址:
---
 - name: "PLay 1: Validate Network Reachability"
 hosts: core,wan
 vars:
 host_id: 10
 packet_count: 10
 tasks:
 - name: "Get all SVI Prefixes"
 set_fact:
 all_svi_prefixes: "{{ svi_interfaces | selectattr('vrrp') |
 map(attribute='ipv4') | list }}"
 run_once: yes
 delegate_to: localhost
 tags: svi
  1. 更新pb_net_validate.yml playbook,以 ping 所有 SVI interfaces的以下任务:
 - name: "Ping Hosts in all VLANs"
 ios_ping:
 dest: "{{ item | ipaddr(10) | ipaddr('address') }}"
 loop: "{{ all_svi_prefixes }}"
 ignore_errors: yes
 tags: svi

它是如何工作的…

在这个 playbook 中,我们使用ios_ping模块,该模块登录到 Ansible 清单中定义的每个节点,并 pingdest属性指定的目的地。在这个示例 playbook 中,我们想要验证对数据、语音和 Web VLAN 中的单个主机的网络可达性,并选择这些 VLAN 中的第十个主机(只是一个例子)。为了构建我们在第一个任务中设置的所有 VLAN 前缀,我们添加一个名为all_svi_prefixes的新变量,并使用多个jinja2过滤器仅收集运行 VRRP 的那些前缀(以删除任何核心 VLAN)。我们仅获取这些 SVI interfaces的 IPv4 属性。在运行第一个任务后,以下是此新变量的内容:

ok: [core01 -> localhost] => {
 "all_svi_prefixes": [
 "10.1.10.0/24",
 "10.1.20.0/24",
 "10.1.100.0/24"
 ]
}

我们将这个新的列表数据结构提供给ios_ping模块,并指定我们需要在每个子网中 ping 第十个主机。只要 ping 成功,任务就会成功。但是,如果从路由器/交换机到此主机存在连接问题,任务将失败。我们使用ignore_errors参数来忽略可能由于主机不可达/关闭而发生的任何失败,并运行任何后续任务。以下代码片段概述了成功运行:

TASK [P1T2: Ping Hosts in all VLANs] *****************************
 ok: [core01] => (item=10.1.10.0/24)
 ok: [core02] => (item=10.1.10.0/24)
 ok: [wan01] => (item=10.1.10.0/24)
 ok: [wan02] => (item=10.1.10.0/24)
 ok: [core01] => (item=10.1.20.0/24)
 ok: [core02] => (item=10.1.20.0/24)
 ok: [core01] => (item=10.1.100.0/24)
 ok: [wan01] => (item=10.1.20.0/24)
 ok: [wan02] => (item=10.1.20.0/24)
 ok: [core02] => (item=10.1.100.0/24)
 ok: [wan01] => (item=10.1.100.0/24)
 ok: [wan02] => (item=10.1.100.0/24)

从 IOS 设备检索操作数据

在这个示例中,我们将概述如何在 IOS 设备上执行操作命令,并将这些输出存储到文本文件中以供进一步处理。这允许我们在执行任何部署后的预验证或后验证期间捕获来自 IOS 设备的任何操作命令,以便我们可以比较结果。

准备工作

为了按照这个示例进行操作,应该有一个 Ansible 清单文件,并且网络应该已经按照先前的示例设置好了。

如何做…

  1. 创建一个名为pb_op_cmds.yml的新 playbook,并填充以下任务以创建保存设备输出的目录结构:
---
 - name: "Play 1: Execute Operational Commands"
 hosts: network
 vars:
 config_folder: "configs"
 op_folder: "op_data"
 op_cmds:
 - show ip ospf neighbor
 - show ip route
 tasks:
 - name: "P1T1: Build Directories to Store Data"
 block:
 - name: "Create folder to store Device config"
 file:
 path: "{{ config_folder }}"
 state: directory
 - name: "Create Folder to store operational commands"
 file:
 path: "{{ op_folder }}"
 state: directory
 run_once: yes
 delegate_to: localhost
  1. 更新pb_op_cmds.yml playbook,并填充以下任务以从设备中检索运行配置:
 - name: "P1T2: Get Running configs from Devices"
 ios_command:
 commands: show running-config
 register: show_run
 - name: "P1T3: Save Running Config per Device"
 copy:
 content: "{{ show_run.stdout[0] }}"
 dest: "{{ config_folder }}/{{ inventory_hostname }}.cfg"
  1. 更新 playbook 并填充以下任务以从设备中检索操作命令并保存它:
 - name: "P1T4: Create Folder per Device"
 file:
 path: "{{ op_folder}}/{{ inventory_hostname }}"
 state: directory
 delegate_to: localhost
 - name: "P1T5: Get Operational Data from Devices"
 ios_command:
 commands: "{{ item }}"
 register: op_output
 loop: "{{ op_cmds }}"
 - name: "P1T6: Save output per each node"
 copy:
 content: "{{ item.stdout[0] }}"
 dest: "{{ op_folder}}/{{ inventory_hostname }}/{{item.item | replace(' ', '_')}}.txt"
 loop: "{{ op_output.results }}"

它是如何工作的…

在这个示例中,我们使用ios_command模块来在 IOS 设备上执行操作命令,并将它们保存到文本文件中。为了实现这个目标,我们执行以下步骤:

  • 我们创建将存储输出的文件夹,并创建一个名为configs的文件夹来存储所有设备的运行配置。我们还创建一个op_data文件来存储我们将从设备获取的操作命令的输出。

  • 然后我们在清单中的所有 IOS 设备上执行show running命令,并将输出注册到一个名为show_run的新变量中。

  • 我们使用复制模块将上一个任务的输出保存到每个设备的文件中。命令运行的输出保存在stdout变量中。由于我们执行了单个命令,stdout变量只有一个项目(stdout[0])。

一旦我们执行了这个任务,我们可以看到configs文件夹如下所示:

$ tree configs/
 configs/
 ├── access01.cfg
 ├── access02.cfg
 ├── core01.cfg
 ├── core02.cfg
 ├── isp01.cfg
 ├── wan01.cfg
 └── wan02.cfg

接下来,我们为每个节点创建一个文件夹,以存储我们将在 IOS 设备上执行的多个show命令的输出。

我们使用ios_command模块在设备上执行show命令,并将所有输出保存在一个名为op_output的新变量中。我们使用复制执行命令show ip route,并创建一个名为show_ip_route.txt的文件来保存此命令的输出。

运行此任务后,我们可以看到op_data文件夹的当前结构如下:

$ tree op_data/
 op_data/
 ├── access01
 │ ├── show_ip_ospf_neighbor.txt
 │ └── show_ip_route.txt
 ├── access02
 │ ├── show_ip_ospf_neighbor.txt
 │ └── show_ip_route.txt
 ├── core01
 │ ├── show_ip_ospf_neighbor.txt
 │ └── show_ip_route.txt
 ├── core02
 │ ├── show_ip_ospf_neighbor.txt
 │ └── show_ip_route.txt
 ├── isp01
 │ ├── show_ip_ospf_neighbor.txt
 │ └── show_ip_route.txt
 ├── wan01
 │ ├── show_ip_ospf_neighbor.txt
 │ └── show_ip_route.txt
 └── wan02
 ├── show_ip_ospf_neighbor.txt
 └── show_ip_route.txt

我们可以检查其中一个文件的内容,以确认所有数据都已存储:

$ head op_data/core01/show_ip_ospf_neighbor.txt

Neighbor ID     Pri   State           Dead Time   Address         Interface
10.100.1.3        0   FULL/  -        00:00:37    10.3.1.2        Ethernet1/0
10.100.1.2        0   FULL/  -        00:00:36    10.1.200.2      Vlan200

使用 pyATS 和 Ansible 验证网络状态

在这个示例中,我们将概述如何使用 Ansible 和 Cisco pyATS Python 库在 Cisco 设备上执行和解析操作命令。使用这些解析的命令,我们可以验证网络的各个方面。

准备工作

这个示例假设网络已经按照之前的所有示例中概述的方式构建和配置。

如何做…

  1. 安装 pyATS 所需的 Python 库:
$ sudo pip3 install pyats genie
  1. 创建roles目录,然后创建带有以下数据的requirements.yml文件:

 $ cat roles/requirements.yml
- src: https://github.com/CiscoDevNet/Ansible-pyats
 scm: git
 name: Ansible-pyats
  1. 按照以下代码安装Ansible-pyats角色:
 $ Ansible-galaxy install -r requirements.yml
  1. 创建一个名为pb_validate_pyats.yml的新 playbook,并填充以下任务以收集wan设备的ospf neighbor
---
 - name: Network Validation with pyATS
 hosts: wan
 roles:
 - Ansible-pyats
 vars:
 Ansible_connection: local
 tasks:
 - pyats_parse_command:
 command: show ip ospf neighbor
 register: ospf_output
 vars:
 Ansible_connection: network_cli
  1. 使用以下任务更新 playbook 以提取 OSPF 对等体信息的数据:
 - name: "FACT >> Pyats OSPF Info"
 set_fact:
 pyats_ospf_data: "{{ ospf_output.structured.interfaces }}"

 - name: " FACT >> Set OSPF peers"
 set_fact:
 OSPF_PEERS: "{{ wan_l3_links[inventory_hostname] | selectattr('ospf','equalto',true) | list }}"
  1. 使用以下任务更新 playbook 以验证 OSPF 对等体和 OSPF 对等状态:
 - name: Validate Number of OSPF Peers
 assert:
 that:
 - pyats_ospf_data | length == OSPF_PEERS | length
 loop: "{{ OSPF_PEERS }}"

 - name: Validate All Peers are in Full State
 assert:
 that:
 - pyats_ospf_data[item.name] | json_query('neighbors.*.state') | first == 'FULL/ -'
 loop: "{{ OSPF_PEERS }}"

工作原理…

在这个示例中,我们将探讨如何使用pyATS框架进行网络验证。pyATS是由思科开发的用于网络测试的测试框架的开源 Python 库。Genie是另一个 Python 库,提供了将基于 CLI 的输出转换为我们可以在自动化脚本中使用的 Python 数据结构的解析能力。思科发布了一个使用 pyATS 和 Genie 库的 Ansible 角色。在这个角色中,有多个模块可以用来构建更健壮的 Ansible 验证 playbook,以验证网络状态。为了开始使用这个角色,我们需要执行以下步骤:

  1. 使用python-pip安装pyatsenie Python 包。

  2. 使用 Ansible-galaxy 安装Ansible-pyats角色。

在这个示例中,我们使用了Ansible-pyats角色中的一个模块,即pyats_parse_command。该模块在远程管理设备上执行操作命令,并返回该命令的 CLI 输出和解析的结构化输出。以下代码片段概述了此模块在wan01设备上的ip ospf neigbor命令返回的结构化数据:

"structured": {
 "interfaces": {
 "GigabitEthernet2": {
 "neighbors": {
 "10.100.1.1": {
 "address": "10.3.1.1",
 "dead_time": "00:00:37",
 "priority": 0,
 "state": "FULL/ -"
 }
 }
 }
 }
}

我们将此模块返回的数据保存到ospf_output变量中,并使用set_fact模块来捕获此模块返回的结构化数据,然后将其保存到一个新变量pyats_ospf_data中。然后,我们使用set_fact模块来过滤在wan_l3_interfaces中定义的链接,只保留为 OSPF 启用的端口。

使用pyats_parse_command返回的结构化数据,我们可以验证这些数据,并使用assert模块将其与我们的 OSPF 对等体定义进行比较,以验证正确的 OSPF 对等体数量及其状态。

为了提取 OSPF 对等体状态,我们使用json_query过滤器来过滤返回的数据,并仅提供每个邻居的 OSPF 状态。

我们在 play 级别将Ansible_connection设置为local,并在pyats_parse_command任务级别将其设置为network_cli,因为我们只需要在此任务中连接到设备。所有其他任务可以在 Ansible 机器上本地运行。

另请参阅…

有关 PyATS 和 Genie 库以及如何将它们用于网络测试的更多信息,请参考以下网址:

developer.cisco.com/docs/pyats/#!introduction/pyats-genie

有关json_query及其语法的更多信息,请参考以下网址:

docs.Ansible.com/Ansible/latest/user_guide/playbooks_filters.html#json-query-filter

jmespath.org/tutorial.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值