Skip to content

Latest commit

 

History

History
1047 lines (766 loc) · 51 KB

04.md

File metadata and controls

1047 lines (766 loc) · 51 KB

四、Python 自动化框架——Ansible 基础

前两章逐步介绍了与网络设备交互的不同方式。在第 2 章低层网络设备交互中,我们讨论了管理交互会话以控制交互的 Pexpect 和 Paramiko 库。在第 3 章、API 和意图驱动的网络中,我们开始从 API 和意图的角度思考我们的网络。我们研究了各种 API,它们包含定义良好的命令结构,并提供了从设备获取反馈的结构化方式。当我们从第 2 章底层网络设备交互转移到第 3 章API 和意图驱动网络时,我们开始思考我们对网络的意图,并逐渐用代码表达我们的网络

让我们进一步讨论将我们的意图转化为网络需求的想法。如果您从事过网络设计,那么流程中最具挑战性的部分可能不是不同的网络设备,而是确定业务需求并将其转化为实际的网络设计。您的网络设计需要解决业务问题。例如,您可能正在一个更大的基础架构团队中工作,该团队需要容纳一个蓬勃发展的在线电子商务站点,该站点在高峰时间响应速度较慢。如何确定网络是否存在问题?如果网站上的响应速度慢确实是由于网络拥塞,您应该升级网络的哪一部分?系统的其余部分能否利用更大的速度和进给?下图展示了我们在尝试将业务需求转化为网络设计时可能会经历的步骤的简单流程:

Business logic to network deployment

在我看来,网络自动化不仅仅是更快的配置。它还应该解决业务问题,并准确可靠地将我们的意图转化为设备行为。这些是我们在网络自动化之旅中应该牢记的目标。在本章中,我们将开始研究一个名为Ansible的基于 Python 的框架,它允许我们声明我们对网络的意图,并从 API 和 CLI 中提取更多内容。

更具声明性的框架

一天早上,你从一场关于潜在网络安全漏洞的噩梦中惊醒,满身冷汗。您意识到您的网络中包含有值得保护的宝贵数字资产。您一直在做网络管理员的工作,因此它非常安全,但您希望在网络设备周围设置更多安全措施以确保安全

首先,将目标分解为两个可执行的项目:

  • 将设备升级到软件的最新版本,这需要:
    1. 将图像上载到设备。
    2. 指示设备从新映像启动。
    3. 继续重新启动设备。
    4. 验证设备是否正在使用新软件映像运行。
  • 在网络设备上配置适当的访问控制列表,包括以下内容:
    1. 在设备上构造访问列表。
    2. 在接口上配置访问列表,在大多数情况下,访问列表位于接口配置部分,以便将其应用于接口。

作为一名专注于自动化的网络工程师,您希望编写脚本来可靠地配置设备并接收来自操作的反馈。您开始研究每个步骤所需的命令和 API,在实验室中验证它们,最后在生产中部署它们。在为操作系统升级和 ACL 部署做了大量工作之后,您希望这些脚本可以转移到下一代设备上。如果有一个工具可以缩短这个设计开发部署周期,那不是很好吗

在本章和第 5 章Python 自动化框架中,我们将使用名为Ansible的开源自动化工具。它是一个框架,可以简化从业务逻辑到网络命令的过程。它可以配置系统、部署软件和协调任务组合。Ansible 是用 Python 编写的,已成为网络设备供应商支持的领先自动化工具之一。

在本章中,我们将了解以下主题:

  • 一个简单易懂的例子

  • Ansible 的优势

  • 可移植建筑

  • Ansible Cisco 模块和示例

  • Ansible Juniper 模块和示例

  • Ansible Arista 模与示例

在撰写本书时,Ansible 2.5 版与 Python2.6 和 2.7 兼容,最近技术评论中提供了 Python3 支持。与 Python 一样,Ansible 的许多有用特性都来自社区驱动的扩展模块。即使使用 Python3 支持 Ansible 核心模块,许多扩展模块和生产部署仍处于 Python2 模式。将所有扩展模块从 Python2 升级到 Python3 需要一些时间。由于这个原因,在本书的其余部分,我们将使用 Python 2.7 和 Ansible 2.2。

为什么是 Ansible 2.2?2018 年 3 月发布的 Ansible 2.5 提供了许多新的网络模块功能,包括新的连接方法、语法和最佳实践。鉴于其相对较新的特性,大多数生产部署仍然是 2.5 版本之前的版本。然而,在本章中,您还将找到专门介绍 Ansible 2.5 示例的章节,供那些希望利用新语法和功能的人使用

For the latest information on Ansible Python 3 support, check out http://docs.ansible.com/ansible/python_3_support.html.

从前面的章节可以看出,我相信通过例子学习。就像 Ansible 的底层 Python 代码一样,Ansible 构造的语法也很容易理解,即使您以前没有使用过 Ansible。如果您对 YAML 或 Jinja2 有一些经验,您将很快得出语法和预期过程之间的关系。让我们先来看一个例子。

一个简单易懂的例子

与其他自动化工具一样,Ansible 在扩展其管理网络设备的能力之前,首先是管理服务器。在大多数情况下,服务器模块和网络模块之间的模块和 Ansible 所指的 playbook 是相似的,但有细微的区别。在本章中,我们将首先看一个服务器任务示例,然后与网络模块进行比较。

控制节点安装

首先,让我们澄清我们将在 Ansible 上下文中使用的术语。我们将把安装了 Ansible 的虚拟机称为控制机,将被管理的机器称为目标机器或被管理节点。Ansible 可以安装在大多数 Unix 系统上,只依赖于 Python 2.6 或 2.7。目前,官方不支持 Windows 操作系统作为控制计算机。Windows 主机仍然可以由 Ansible 管理,因为它们只是不支持作为控制计算机。

As Windows 10 starts to adopt the Windows Subsystem for Linux, Ansible might soon be ready to run on Windows as well. For more information, please check the Ansible documentation for Windows (https://docs.ansible.com/ansible/2.4/intro_windows.html). 

关于托管节点需求,您可能会注意到一些文档提到 Python 2.4 或更高版本是一项需求。对于使用 Linux 等操作系统管理目标节点来说,这是正确的,但显然并非所有网络设备都支持 Python。我们将看到如何通过控制节点上的本地执行绕过网络模块的这一要求。

For Windows, Ansible modules are implemented in PowerShell. Windows modules in the core and extra repository live in a Windows/subdirectory if you would like to take a look. 

我们将在 Ubuntu 虚拟机上安装 Ansible。有关在其他操作系统上安装的说明,请查阅安装文档(http://docs.ansible.com/ansible/intro_installation.html )。在以下代码块中,您将看到安装软件包的步骤:

$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible

We can also use pip to install Ansible: pip install ansible. My personal preference is to use the operating system's package management system, such as Apt on Ubuntu. 

我们现在可以进行如下快速验证:

$ ansible --version
ansible 2.6.1
  config file = /etc/ansible/ansible.cfg 

现在,让我们看看如何在同一个控制节点上运行不同版本的 Ansible。如果您想在不进行永久安装的情况下试用最新的开发功能,那么这是一个非常有用的功能。如果要在没有根权限的控制节点上运行 Ansible,也可以使用此方法

As we saw from the output, at the time of writing this book, the latest release is 2.6.1. Feel free to use this version, but given the relatively new release, we will focus on Ansible version 2.2 in this book.

从源代码运行不同版本的 Ansible

您可以从源代码签出中运行 Ansible(我们将在第 11 章、使用 Git中将 Git 视为一种版本控制机制):

$ git clone https://github.com/ansible/ansible.git --recursive
$ cd ansible/
$ source ./hacking/env-setup
...
Setting up Ansible to run out of checkout...
$ ansible --version
ansible 2.7.0.dev0 (devel cde3a03b32) last updated 2018/07/11 08:39:39 (GMT -700)
 config file = /etc/ansible/ansible.cfg
...

要运行不同的版本,我们只需对不同的分支或标签使用git checkout,然后再次执行环境设置:

$ git branch -a
$ git tag --list 
$ git checkout v2.5.6
...
HEAD is now at 0c985fe... New release v2.5.6
$ source ./hacking/env-setup
$ ansible --version
ansible 2.5.6 (detached HEAD 0c985fee8a) last updated 2018/07/11 08:48:20 (GMT -700)
 config file = /etc/ansible/ansible.cfg

If the Git commands seem a bit strange to you, we will cover Git in more detail in Chapter 11, Working with Git. 

一旦我们达到您需要的版本,如 Ansible 2.2,我们就可以运行该版本的核心模块更新:

$ ansible --version
ansible 2.2.3.0 (detached HEAD f5be18f409) last updated 2018/07/14 07:40:09 (GMT -700)
...
$ git submodule update --init --recursive
Submodule 'lib/ansible/modules/core' (https://github.com/ansible/ansible-modules-core) registered for path 'lib/ansible/modules/core'

让我们来看看我们将在本章中使用的实验室拓扑结构,以及 T0。T1。

实验室设置

在本章和第 5 章Python 自动化框架中,我们的实验室将安装一台安装了 Ansible 的 Ubuntu 16.04 控制节点机器。该控制机器将可访问由 IOSv 和 NX OSv 设备组成的 VIRL 设备的管理网络。当目标机器是主机时,我们的 playbook 示例还将有一个单独的 Ubuntu VM:

Lab topology 

现在,我们准备看我们的第一个 Ansible 剧本示例

你的第一本 Ansible 剧本

我们的第一个剧本将在控制节点和远程 Ubuntu 主机之间使用。我们将采取以下步骤:

  1. 确保控制节点可以使用基于密钥的授权。
  2. 创建一个库存文件。
  3. 创建一个剧本。
  4. 执行并测试它。

公钥授权

首先要做的是将 SSH 公钥从控制计算机复制到目标计算机。完整的公钥基础结构教程不在本书的范围内,但下面是有关控制节点的快速演练:

$ ssh-keygen -t rsa <<<< generates public-private key pair on the host machine if you have not done so already
$ cat ~/.ssh/id_rsa.pub <<<< copy the content of the output and paste it to the ~/.ssh/authorized_keys file on the target host

You can read more about PKI at https://en.wikipedia.org/wiki/Public_key_infrastructure.

因为我们使用的是基于密钥的身份验证,所以可以在远程节点上关闭基于密码的身份验证,从而更加安全。您现在可以使用私钥ssh从控制节点到远程节点,而无需提示输入密码。

Can you automate the initial public key copying? It is possible, but is highly dependent on your use case, regulation, and environment. It is comparable to the initial console setup for network gears to establish initial IP reachability. Do you automate this? Why or why not?

库存文件

如果我们没有远程目标要管理,我们就不需要 Ansible,对吗?一切都始于我们需要在远程主机上执行某些任务这一事实。在 Ansible 中,我们指定潜在远程目标的方式是使用库存文件。我们可以将该清单文件作为/etc/ansible/hosts文件,或者在 playbook 运行时使用-i选项指定该文件。就我个人而言,我更喜欢把这个文件放在我的剧本所在的同一个目录中,并使用-i选项。

Technically, this file can be named anything you like as long as it is in a valid format. However, the convention is to name this file hosts. You can potentially save yourself and your colleagues some headaches in the future by following this convention.

清单文件是一个简单的纯文本 INI 样式(https://en.wikipedia.org/wiki/INI_file )说明目标的文件。默认情况下,目标可以是 DNS FQDN 或 IP 地址:

$ cat hosts
192.168.199.170

我们现在可以使用命令行选项测试 Ansible 和hosts文件:

$ ansible -i hosts 192.168.199.170 -m ping
192.168.199.170 | SUCCESS => {
 "changed": false,
 "ping": "pong"
}

By default, Ansible assumes that the same user executing the playbook exists on the remote host. For example, I am executing the playbook as echou locally; the same user also exists on my remote host. If you want to execute as a different user, you can use the -u option when executing, that is, -u REMOTE_USER.

示例中的前一行读取主机文件作为库存文件,并在名为192.168.199.170的主机上执行ping模块。平(http://docs.ansible.com/ansible/ping_module.html 是一个简单的测试模块,它连接到远程主机,验证可用的 Python 安装,并在成功后返回输出pong。

You may take a look at the ever-expanding module list (http://docs.ansible.com/ansible/list_of_all_modules.html) if you have any questions about the use of existing modules that were shipped with Ansible.

如果您得到主机密钥错误,通常是因为主机密钥不在known_hosts文件中,并且通常在~/.ssh/known_hosts下。您可以通过 SSH 连接到主机并在添加主机时应答yes,也可以通过以下代码选中/etc/ansible/ansible.cfg或~/.ansible.cfg来禁用此功能:

[defaults]
host_key_checking = False

现在我们已经验证了库存文件和 Ansible 包,我们可以制作我们的第一个剧本了。

我们的第一本剧本

剧本是 Ansible 的蓝图,描述您希望使用模块对主机执行的操作。在与 Ansible 合作时,我们将在这里花费大部分时间作为操作员。如果你正在建造一个树屋,剧本将是你的手册,模块将是你的工具,而库存将是你在使用工具时要处理的组件。

剧本设计为人类可读,采用 YAML 格式。我们将了解 Ansible 体系结构部分中使用的常用语法。现在,我们的重点是运行一个示例剧本,以获得 Ansible 的外观和感觉。

Originally, YAML was said to mean Yet Another Markup Language, but now, http://yaml.org/ has repurposed the acronym to be YAML ain't markup language.

让我们看看这个简单的 6 行剧本,df_playbook.yml:

---
- hosts: 192.168.199.170

 tasks:
 - name: check disk usage
 shell: df > df_temp.txt

在剧本中,可以有一个或多个剧本。在这种情况下,我们有一个剧本(第二到第六行)。在任何游戏中,我们都可以有一个或多个任务。在我们的示例剧本中,我们只有一个任务(第四行到第六行)。name字段以人类可读的格式指定任务的目的,并且使用了shell模块。模块采用一个参数df。shell模块读入参数中的命令并在远程主机上执行。在本例中,我们执行df命令检查磁盘使用情况,并将输出复制到名为df_temp.txt的文件中。

我们可以通过以下代码执行剧本:

$ ansible-playbook -i hosts df_playbook.yml
PLAY [192.168.199.170] *********************************************************

TASK [setup] *******************************************************************
ok: [192.168.199.170]

TASK [check disk usage] ************************************************
changed: [192.168.199.170]

PLAY RECAP *********************************************************************
192.168.199.170 : ok=2 changed=1 unreachable=0 failed=0

如果您登录到托管主机(192.168.199.170,对于我来说),您将看到df_temp.txt文件包含df命令的输出。整洁,嗯?

您可能已经注意到,在我们的输出中实际上执行了两个任务,尽管我们在剧本中只指定了一个任务;默认情况下,会自动添加设置模块。它由 Ansible 执行以收集有关远程主机的信息,这些信息可在以后的剧本中使用。例如,安装模块收集的事实之一是操作系统。收集有关远程目标的事实的目的是什么?您可以将此信息用作同一剧本中其他任务的条件。例如,playbook 可以包含安装软件包的附加任务。根据安装模块中收集的操作系统事实,它可以专门为基于 Debian 的主机使用apt,为基于 Red Hat 的主机使用yum。

If you are curious about the output of a setup module, you can find out what information Ansible gathers via $ ansible -i hosts <host> -m setup.

在引擎盖下面,我们的简单任务实际上发生了一些事情。控制节点将 Python 模块复制到远程主机,执行模块,将模块输出复制到临时文件,然后捕获输出并删除临时文件。目前,我们可以放心地忽略这些基本细节,直到我们需要它们。

重要的是,我们必须充分理解我们刚刚经历的简单过程,因为我们将在本章后面的部分再次提到这些要素。我特意选择了一个在这里展示的服务器示例,因为当我们需要偏离网络模块时(请记住,我们提到的 Python 解释器很可能不在网络设备上),这将更有意义。

祝贺你完成了你的第一个 Ansible 剧本!我们将更深入地研究 ANTIAL 架构,但现在让我们来看看为什么 AnEIT 很适合网络管理。还记得 Ansible 模块是用 Python 编写的吗?这是 Pythonic 网络工程师的一个优势,对吗?

Ansible 的优势

除了 Ansible 之外,还有许多基础设施自动化框架,即 Chef、Puppet 和 SaltStack。每个框架都提供了自己独特的功能和模型;没有一个适合所有组织的正确框架。在本节中,我想列出 Ansible 相对于其他框架的一些优势,以及为什么我认为这是网络自动化的一个好工具。

我列出了 Ansible 的优点,而没有将其与其他框架进行比较。其他框架可能采用一些相同的理念或 Ansible 的某些方面,但它们很少包含我将要提到的所有特性。我相信,正是以下所有功能和理念的结合,使 Ansible 成为网络自动化的理想选择。

无代理

与一些同行不同,Ansible 不需要严格的主客户机模型。无需在与服务器通信的客户端上安装软件或代理。除了许多平台默认使用的 Python 解释器之外,不需要其他软件

对于网络自动化模块,Ansible 不依赖远程主机代理,而是使用 SSH 或 API 调用将所需更改推送到远程主机。这进一步减少了对 Python 解释器的需要。这对于网络设备管理来说意义重大,因为网络供应商通常不愿意在其平台上安装第三方软件。另一方面,SSH 已经存在于网络设备上。在过去的几年中,这种想法已经发生了一些变化,但是总体上 SSH 是所有网络设备的共同特征,而配置管理代理支持则不是。正如您在第 2 章、低层网络设备交互**ons中所记得的,较新的网络设备还提供了 API 层,Ansible 也可以利用该层

由于远程主机上没有代理,Ansible 使用推送模式将更改推送到设备,而不是代理从主服务器提取信息的拉送模式。在我看来,推送模型更具确定性,因为一切都源于控制机器。在 pull 模型中,pull的时间可能因客户而异,因此会导致时间变化

同样,在使用现有网络设备时,无代理的重要性再怎么强调也不为过。这通常是网络运营商和供应商接受 Ansible 的主要原因之一。

幂等元

根据维基百科,幂等性是数学和计算机科学中某些运算的属性,可以多次应用,而不会改变最初应用以外的结果(https://en.wikipedia.org/wiki/Idempotence )。在更常见的术语中,这意味着反复运行相同的过程不会在第一次之后改变系统。Ansible 的目标是幂等,这有利于需要特定操作顺序的网络操作。

幂等性的优点与我们编写的 Pexpect 和 Paramiko 脚本相比是最好的。请记住,编写这些脚本是为了发出命令,就像工程师坐在终端上一样。如果要执行脚本 10 次,脚本将进行 10 次更改。如果我们通过 Ansible playbook 编写相同的任务,将首先检查现有的设备配置,并且 playbook 将仅在更改不存在时执行。如果我们执行 playbook 10 次,更改将仅在第一次运行期间应用,接下来的 9 次运行将抑制配置更改

幂等意味着我们可以重复执行剧本,而不用担心会有不必要的更改。这很重要,因为我们需要自动检查状态一致性,而不需要任何额外的开销。

简单且可扩展

Ansible 是用 Python 编写的,使用 YAML 作为 playbook 语言,这两种语言都被认为相对容易学习。还记得 Cisco IOS 语法吗?这是一种特定于域的语言,仅在您管理 Cisco IOS 设备或其他类似结构的设备时适用;它不是超出其有限范围的通用语言。幸运的是,与其他一些自动化工具不同,Ansible 没有额外的领域特定语言或 DSL 可供学习,因为 YAML 和 Python 都被广泛用作通用语言。

正如您从前面的示例中看到的,即使您以前没有见过 YAML,也很容易准确地猜测剧本想要做什么。Ansible 还使用 Jinja2 作为模板引擎,这是 PythonWeb 框架(如 Django 和 Flask)使用的一个常用工具,因此知识是可转移的。

Ansible 的可扩展性怎么强调都不过分。如前一个示例所示,Ansible 从自动化服务器(主要是 Linux)工作负载开始。然后,它将分支到使用 PowerShell 管理 Windows 计算机。随着越来越多的业内人士开始适应 Ansible,网络成为一个开始受到更多关注的话题。Ansible 雇佣了合适的人员和团队,网络专业人员开始参与,客户开始要求供应商提供支持。从 Ansible 2.0 开始,网络自动化与服务器管理一起成为一级公民。生态系统是活的、良好的,每一次释放都在不断改善。

与 Python 社区一样,Ansible 社区也很友好,态度也包括新成员和想法。我有作为一个 noob 的第一手经验,尝试理解贡献过程,并希望编写要合并到上游的模块。我可以证明一个事实,那就是我一直觉得自己的观点受到欢迎和尊重。

这种简单性和可扩展性非常适合未来的验证。技术世界正在快速发展,我们一直在努力适应它。一次学习一项技术并继续使用它,不管最新的趋势如何,这不是很好吗?显然,没有人有水晶球来准确预测未来,但 Ansible 的业绩记录很好地说明了未来的技术适应。

网络供应商支持

让我们面对现实吧,我们不是生活在真空中。业内流传着一个笑话,OSI 层应该包括第 8 层(金钱)和第 9 层(政治)。每天,我们都需要使用各种供应商生产的网络设备

以 API 集成为例。在前面的章节中,我们看到了 Pexpect 和 API 方法之间的区别。API 显然在网络自动化方面占上风。然而,API 接口并不便宜。每个供应商都需要投入时间、金钱和工程资源来实现集成。供应商支持某项技术的意愿在我们的世界中非常重要。幸运的是,所有主要供应商都支持 Ansible,越来越多的可用网络模块(清楚地表明了这一点 http://docs.ansible.com/ansible/list_of_network_modules.html )。

为什么供应商比其他自动化工具更支持 Ansible?无代理当然会有所帮助,因为将 SSH 作为唯一的依赖项会大大降低进入门槛。在供应商方面工作过的工程师都知道,特性请求过程通常需要几个月的时间,必须跨越许多障碍。任何时候添加一个新特性,都意味着在回归测试、兼容性检查、集成审查等方面花费更多的时间。降低进入门槛通常是获得供应商支持的第一步。

Ansible 是基于 Python 的,这是一种被许多网络专业人士喜爱的语言,这一事实是对供应商支持的另一个巨大推动。对于像 Juniper 和 Arista 这样已经投资 PyEZ 和 Pyeapi 的供应商来说,他们可以轻松地利用现有的 Python 模块,并将其功能快速集成到 Ansible 中。正如您将在第 5 章Python 自动化框架中看到的,我们可以使用现有的 Python 知识轻松编写自己的模块。

Ansible 在专注于网络之前已经有了大量社区驱动的模块。贡献过程在某种程度上是经过烘焙和建立的,或者像开源项目一样经过烘焙。核心 Ansible 团队熟悉与社区合作提交和贡献。

增加网络供应商支持的另一个原因还与 Ansible 使供应商能够在模块上下文中表达自己的优势有关。在下一节中,我们将看到,除了 SSH 之外,Ansible 模块还可以在本地执行,并使用 API 与这些设备通信。这确保了供应商在通过 API 提供其最新和最强大的功能后,可以立即表达这些功能。对于网络专业人士而言,这意味着您可以在使用 Ansible 作为自动化平台时使用尖端功能来选择供应商。

我们花了相当大的篇幅讨论供应商支持,因为我觉得这是 Ansible 故事中经常被忽略的部分。有供应商愿意支持该工具,这意味着作为网络工程师的你可以在晚上睡觉,因为你知道网络中的下一件大事将很有可能获得可靠的支持,而且随着网络的发展,你不会被当前的供应商所束缚。

可移植建筑

Ansible 架构由剧本、剧本和任务组成。请看我们之前使用的df_playbook.yml:

Ansible playbook

整个文件称为剧本,其中包含一个或多个剧本。每个游戏可以包含一个或多个任务。在我们的简单示例中,我们只有一个剧本,其中包含一个任务。在本节中,我们将了解以下内容:

  • YAML:该格式在 Ansible 中广泛使用,用于表示剧本和变量。
  • 清单:清单是您可以在基础架构中指定主机并对其进行分组的地方。您还可以选择在清单文件中指定主机和组变量。
  • 变量:每个网络设备是不同的。它具有不同的主机名、IP、邻居关系等。变量允许一组标准的重头戏,同时仍然适应这些差异。
  • 模板:模板在网络中并不新鲜。事实上,您可能正在使用一个,而没有将其作为模板。当我们需要提供新设备或更换 RMA(退货授权)时,我们通常会做什么?我们复制旧配置并替换主机名和环回 IP 地址等差异。Ansible 使用 Jinja2 对模板格式进行了标准化,我们稍后将对此进行深入探讨。

在第 5 章Python 自动化框架中,我们将介绍一些更高级的主题,如条件、循环、块、处理程序、剧本角色,以及如何将它们包含在网络管理中。

亚马尔

YAML 是用于 Ansible playbooks 和其他一些文件的语法。官方 YAML 文档包含语法的完整规范。这是一个紧凑的版本,因为它涉及 Ansible 最常见的用法:

  • YAML 文件以三个破折号(---开头)
  • 空白缩进是用来表示结构排列时的缩进,就像 Python 一样
  • 注释以散列(#符号)开头
  • 列表成员由前导连字符(-表示,每行一个成员
  • 列表也可以用方括号([]表示,元素之间用逗号(,分隔)
  • 字典由键:值对表示,用冒号分隔
  • 字典可以用大括号表示,元素之间用逗号(,分隔)
  • 字符串可以不加引号,但也可以用双引号或单引号括起来

如您所见,YAML 很好地映射到 JSON 和 Python 数据类型。如果我将df_playbook.yml改写为df_playbook.json,它会是这样的:

        [
          {
            "hosts": "192.168.199.170",
            "tasks": [
            "name": "check disk usage",
            "shell": "df > df_temp.txt"
           ]
          }
        ]

这显然不是一个有效的剧本,但在使用 JSON 格式进行比较时,可以帮助理解 YAML 格式。大多数时候,注释(#、列表(-)和字典(key:value)是你在剧本中看到的内容。

存货

默认情况下,Ansible 查看剧本中指定主机的/etc/ansible/hosts文件。如前所述,我发现通过-i选项指定主机文件更具表现力。到目前为止,我们一直在这样做。要扩展上一个示例,我们可以编写清单主机文件,如下所示:

[ubuntu]
192.168.199.170

[nexus]
192.168.199.148
192.168.199.149

[nexus:vars]
username=cisco
password=cisco

[nexus_by_name]
switch1 ansible_host=192.168.199.148
switch2 ansible_host=192.168.199.149

正如您可能已经猜到的,方括号标题指定了组名,因此稍后在剧本中我们可以指向该组。例如,在cisco_1.yml和cisco_2.yml中,我可以对nexus组下指定的所有主机进行操作,组名为nexus:

---
- name: Configure SNMP Contact
hosts: "nexus"
gather_facts: false
connection: local
<skip>

主机可以存在于多个组中。该组也可以嵌套为children:

[cisco]
router1
router2

[arista]
switch1
switch2

[datacenter:children]
cisco
arista

在前面的示例中,数据中心组包括cisco和arista成员。

我们将在下一节讨论变量。但是,也可以选择在清单文件中指定属于主机和组的变量。在我们的第一个清单文件示例中,[nexus:vars为整个 nexus 组指定变量。ansible_host变量为同一行上的每个主机声明变量。

有关库存文件的更多信息,请查看官方文件(http://docs.ansible.com/ansible/intro_inventory.html )。

变量

在上一节中,我们讨论了一些变量。因为我们的托管节点并不完全相同,所以我们需要通过变量来适应差异。变量名应该是字母、数字和下划线,并且应该始终以字母开头。变量通常在三个位置定义:

  • 剧本
  • 库存文件
  • 要包含在文件和角色中的单独文件

让我们看一个在剧本中定义变量的例子,cisco_1.yml:

---
- name: Configure SNMP Contact
hosts: "nexus"
gather_facts: false
connection: local

vars:
cli:
host: "{{ inventory_hostname }}"
username: cisco
password: cisco
transport: cli

tasks:
- name: configure snmp contact
nxos_snmp_contact:
contact: TEST_1
state: present
provider: "{{ cli }}"

register: output

- name: show output
debug:
var: output

您可以看到在vars部分下声明的cli变量,该变量正在nxos_snmp_contact任务中使用。

For more information on the nxso_snmp_contact module, check out the online documentation (http://docs.ansible.com/ansible/nxos_snmp_contact_module.html).

要引用变量,可以使用双花括号的 Jinja2 模板系统约定。除非用它开始一个值,否则不需要在花括号周围加引号。我通常会发现它更容易记住,并在变量值周围加上引号。

您可能也注意到了{{ inventory_hostname }}引用,它没有在剧本中声明。它是 Ansible 自动为您提供的默认变量之一,有时也称为魔术变量。

There are not many magic variables, and you can find the list in the documentation (http://docs.ansible.com/ansible/playbooks_variables.html#magic-variables-and-how-to-access-information-about-other-hosts)).

在上一节中,我们在库存文件中声明了变量:

[nexus:vars]
username=cisco
password=cisco

[nexus_by_name]
switch1 ansible_host=192.168.199.148
switch2 ansible_host=192.168.199.149

要使用清单文件中的变量而不是在剧本中声明它们,让我们在主机文件中添加[nexus_by_name]的组变量:

[nexus_by_name]
switch1 ansible_host=192.168.199.148
switch2 ansible_host=192.168.199.149

[nexus_by_name:vars]
username=cisco
password=cisco

然后,修改剧本以匹配我们在cisco_2.yml中看到的内容,以引用变量:

---
- name: Configure SNMP Contact
hosts: "nexus_by_name"
gather_facts: false
connection: local

vars:
  cli:
     host: "{{ ansible_host }}"
     username: "{{ username }}"
     password: "{{ password }}"
     transport: cli

tasks:
  - name: configure snmp contact
  nxos_snmp_contact:
    contact: TEST_1
    state: present
    provider: "{{ cli }}"

  register: output

- name: show output
  debug:
    var: output

请注意,在本例中,我们指的是清单文件中的nexus_by_name组、ansible_host主机变量以及username和password组变量。这是一种将用户名和密码隐藏在写保护文件中并发布剧本的好方法,无需担心暴露敏感数据。

To see more examples of variables, check out the Ansible documentation (http://docs.ansible.com/ansible/playbooks_variables.html).

要访问嵌套数据结构中提供的复杂变量数据,可以使用两种不同的符号。注意到在nxos_snmp_contact任务中,我们在一个变量中注册了输出,并使用调试模块显示它。在 playbook 执行过程中,您将看到如下内容:

 TASK [show output] 
 *************************************************************
 ok: [switch1] => {
 "output": {
 "changed": false,
 "end_state": {
 "contact": "TEST_1"
 },
 "existing": {
 "contact": "TEST_1"
 },
 "proposed": {
 "contact": "TEST_1"
 },
 "updates": []
 }
 }

为了访问嵌套数据,我们可以使用以下符号,如cisco_3.yml中所述:

msg: '{{ output["end_state"]["contact"] }}'
msg: '{{ output.end_state.contact }}'

您将只收到指示的值:

TASK [show output in output["end_state"]["contact"]] 
***************************
ok: [switch1] => {
 "msg": "TEST_1"
}
ok: [switch2] => {
 "msg": "TEST_1"
}

TASK [show output in output.end_state.contact] 
*********************************
ok: [switch1] => {
 "msg": "TEST_1"
}
ok: [switch2] => {
 "msg": "TEST_1"
}

最后,我们提到的变量也可以存储在单独的文件中。为了了解如何在角色或包含的文件中使用变量,我们应该再举几个例子,因为它们一开始就有点复杂。我们将在第 5 章、Python 自动化框架中看到更多角色示例——超越基础。

带 Jinja2 的模板

在上一节中,我们使用了具有 Jinja2 语法{{ variable }}的变量。虽然您可以在 Jinja2 中做很多复杂的事情,但幸运的是,我们只需要一些基本的东西就可以开始了。

Jinja2 (http://jinja.pocoo.org/) is a full-featured, powerful template engine that originated in the Python community. It is widely used in Python web frameworks such as Django and Flask.

现在,只要记住 Ansible 使用 Jinja2 作为模板引擎就足够了。我们将根据情况重新讨论 Jinja2 过滤器、测试和查找的主题。您可以在此处找到有关 Ansible Jinja2 模板的更多信息:http://docs.ansible.com/ansible/playbooks_templating.html 。

可移植网络模块

Ansible 最初用于管理具有完整操作系统(如 Linux 和 Windows)的节点,后来扩展到支持网络设备。你可能已经注意到了我们迄今为止用于网络设备的剧本中的细微差别,比如gather_facts: false和connection: local的台词;我们将在以下几节中更仔细地了解这些差异。

当地联系和事实

Ansible 模块是默认情况下在远程主机上执行的 Python 代码。由于大多数网络设备不直接公开 Python,或者根本不包含 Python,因此我们几乎总是在本地执行 playbook。这意味着首先在本地解释剧本,然后根据需要推出命令或配置。

回想一下,远程主机事实是通过默认添加的设置模块收集的。由于我们在本地执行 playbook,因此安装模块将在本地主机而不是远程主机上收集事实。这当然不是必需的,因此当连接设置为本地时,我们可以通过将事实收集设置为 false 来减少这一不必要的步骤。

由于网络模块在本地执行,因此对于提供备份选项的模块,文件也在控制节点上本地备份。

Ansible 2.5 中最重要的变化之一是引入了不同的通信协议(https://docs.ansible.com/ansible/latest/network/getting_started/network_differences.html#multiple-通信协议。现在的连接方式包括network_cli、netconf、httpapi和local。如果网络设备通过 SSH 使用 CLI,则在其中一个设备变量中将连接方法指示为network_cli。然而,由于这是一个相对较新的变化,您可能仍然会在许多现有的剧本中看到本地连接

提供程序参数

正如我们从第 2 章、*底层网络设备交互、*和第 3 章、API 和意图驱动网络中看到的,网络设备可以通过 SSH 或 API 连接,具体取决于平台和软件版本。所有核心网络模块都实现一个provider参数,该参数是用于定义如何连接到网络设备的参数集合。有些模块只支持cli,有些则支持其他值,如 Arista EAPI 和 Cisco NXAPI。这就是 Ansible 的“让供应商发光”理念的体现。该模块将提供支持哪种传输方法的文档。

从 Ansible 2.5 开始,建议使用connection变量指定运输方法。您将开始看到 provider 参数逐渐从未来的 Ansible 版本中淘汰。以ios_command模块为例,https://docs.ansible.com/ansible/latest/modules/ios_command_module.html#ios-命令模块,提供程序参数仍然有效,但被标记为已弃用。我们将在本章后面看到一个例子

provider传输支持的一些基本论点如下:

  • host:定义远程主机

  • port:定义要连接的端口

  • username:这是要验证的用户名

  • password:这是需要验证的密码

  • transport:这是连接的传输类型

  • authorize:这将为需要权限提升的设备启用权限提升

  • auth_pass:定义权限提升密码

如您所见,并非所有参数都需要指定。例如,对于我们以前的剧本,我们的用户在登录时始终具有管理员权限,因此我们不需要指定authorize或auth_pass参数。

这些参数只是变量,因此它们遵循相同的变量优先级规则。例如,如果我将cisco_3.yml更改为cisco_4.yml,并遵守以下优先级:

    ---
    - name: Configure SNMP Contact
      hosts: "nexus_by_name"
      gather_facts: false
      connection: local

      vars:
        cli:
          host: "{{ ansible_host }}"
          username: "{{ username }}"
          password: "{{ password }}"
          transport: cli

      tasks:
        - name: configure snmp contact
          nxos_snmp_contact:
            contact: TEST_1
            state: present
            username: cisco123
            password: cisco123
            provider: "{{ cli }}"

          register: output

        - name: show output in output["end_state"]["contact"]
          debug:
            msg: '{{ output["end_state"]["contact"] }}'

        - name: show output in output.end_state.contact
          debug:
            msg: '{{ output.end_state.contact }}'

任务级别上定义的用户名和密码将覆盖 playbook 级别上的用户名和密码。尝试连接时,我将收到以下错误,因为设备上不存在该用户:

PLAY [Configure SNMP Contact] 
**************************************************

TASK [configure snmp contact] 
**************************************************
fatal: [switch2]: FAILED! => {"changed": false, "failed": true, 
"msg": "failed to connect to 192.168.199.149:22"}
fatal: [switch1]: FAILED! => {"changed": false, "failed": true, 
"msg": "failed to connect to 192.168.199.148:22"}
to retry, use: --limit 
@/home/echou/Master_Python_Networking/Chapter4/cisco_4.retry

PLAY RECAP 
*********************************************************************
switch1 : ok=0 changed=0 unreachable=0 failed=1
switch2 : ok=0 changed=0 unreachable=0 failed=1

Ansible Cisco 的例子

Cisco 对 Ansible 的支持按操作系统 IOS、IOS-XR 和 NX-OS 分类。我们已经看到了许多 NX-OS 示例,因此在本节中,让我们尝试管理基于 IOS 的设备。

我们的主机文件将由两台主机组成,R1和R2:

[ios_devices]
R1 ansible_host=192.168.24.250
R2 ansible_host=192.168.24.251

[ios_devices:vars]
username=cisco
password=cisco

我们的剧本cisco_5.yml将使用ios_command模块执行任意show commands:

    ---
    - name: IOS Show Commands
      hosts: "ios_devices"
      gather_facts: false
      connection: local

      vars:
        cli:
          host: "{{ ansible_host }}"
          username: "{{ username }}"
          password: "{{ password }}"
          transport: cli

      tasks:
        - name: ios show commands
          ios_command:
            commands:
              - show version | i IOS
              - show run | i hostname
            provider: "{{ cli }}"

          register: output

        - name: show output in output["end_state"]["contact"]
          debug:
            var: output

结果是我们预期的show version和show run输出:

 $ ansible-playbook -i ios_hosts cisco_5.yml

 PLAY [IOS Show Commands] 
 *******************************************************

 TASK [ios show commands] 
 *******************************************************
 ok: [R1]
 ok: [R2]

 TASK [show output in output["end_state"]["contact"]] 
 ***************************
 ok: [R1] => {
 "output": {
 "changed": false,
 "stdout": [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 
 12.4(25g), RELEASE SOFTWARE (fc1)",
 "hostname R1"
 ],
 "stdout_lines": [
 [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 
 12.4(25g), RELEASE SOFTWARE (fc1)"
 ],
 [
 "hostname R1"
 ]
 ]
 }
 }
 ok: [R2] => {
 "output": {
 "changed": false,
 "stdout": [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 
 12.4(25g), RELEASE SOFTWARE (fc1)",
 "hostname R2"
 ],
 "stdout_lines": [
 [
 "Cisco IOS Software, 7200 Software (C7200-A3JK9S-M), Version 
 12.4(25g), RELEASE SOFTWARE (fc1)"
 ],
 [
 "hostname R2"
 ]
 ]
 }
 }

 PLAY RECAP 
 *********************************************************************
 R1 : ok=2 changed=0 unreachable=0 failed=0
 R2 : ok=2 changed=0 unreachable=0 failed=0

我想指出以下几个例子:

  • NXOS 和 IOS 之间的剧本大体相同
  • 语法nxos_snmp_contact和ios_command模块遵循相同的模式,唯一的区别是模块的参数
  • IOS 版本的设备已经很旧了,不了解 API,但是模块仍然有相同的外观和感觉

正如您可以从前面的示例中看到的,一旦我们了解了剧本的基本语法,细微的差异取决于我们想要执行的任务的不同模块。

Ansible 2.5 连接示例

从 2.5 版开始,我们简要介绍了 Ansible playbooks 中添加的网络连接更改。随着这些变化,Ansible 还发布了一份网络最佳实践文档https://docs.ansible.com/ansible/latest/network/user_guide/network_best_practices_2.5.html 。让我们根据最佳实践指南构建一个示例。对于我们的拓扑,我们将重用第 2 章、低层网络设备交互中的拓扑,使用两个 IOSv 设备。由于本例涉及多个文件,因此将这些文件分组到名为ansible_2-5_example的子目录中

我们的资源清册文件缩减为组和主机名称:

$ cat hosts
[ios-devices]
iosv-1
iosv-2

我们已经创建了一个包含两个文件的host_vars目录。每个名称对应于清单文件中指定的名称:

$ ls -a host_vars/
. .. iosv-1 iosv-2

主机的变量文件包含以前包含在 CLI 变量中的内容。ansible_connection的附加变量指定network_cli作为传输:

$ cat host_vars/iosv-1
---
ansible_host: 172.16.1.20
ansible_user: cisco
ansible_ssh_pass: cisco
***ansible_connection: network_cli***
ansible_network_os: ios
ansbile_become: yes
ansible_become_method: enable
ansible_become_pass: cisco

$ cat host_vars/iosv-2
---
ansible_host: 172.16.1.21
ansible_user: cisco
ansible_ssh_pass: cisco
***ansible_connection: network_cli***
ansible_network_os: ios
ansbile_become: yes
ansible_become_method: enable
ansible_become_pass: cisco

我们的剧本将使用启用了backup选项的ios_config模块。注意本例中使用的when条件,因此如果有其他主机使用不同的操作系统,则不会应用此任务:

$ cat my_playbook.yml
---
- name: Chapter 4 Ansible 2.5 Best Practice Demonstration
 ***connection: network_cli***
 gather_facts: false
 hosts: all
 tasks:
 - name: backup
 ios_config:
 backup: yes
 register: backup_ios_location
 ***when: ansible_network_os == 'ios'***

运行 playbook 时,将创建一个新的备份文件夹,为每个主机备份配置:

$ ansible-playbook -i hosts my_playbook.yml

PLAY [Chapter 4 Ansible 2.5 Best Practice Demonstration] ***********************

TASK [backup] ******************************************************************
ok: [iosv-2]
ok: [iosv-1]

PLAY RECAP *********************************************************************
iosv-1 : ok=1 changed=0 unreachable=0 failed=0
iosv-2 : ok=1 changed=0 unreachable=0 failed=0

$ ls -l backup/
total 8
-rw-rw-r-- 1 echou echou 3996 Jul 11 19:01 iosv-1_config.2018-07-11@19:01:55
-rw-rw-r-- 1 echou echou 3996 Jul 11 19:01 iosv-2_config.2018-07-11@19:01:55

$ cat backup/iosv-1_config.2018-07-11@19\:01\:55
Building configuration...

Current configuration : 3927 bytes
!
! Last configuration change at 01:46:00 UTC Thu Jul 12 2018 by cisco
!
version 15.6
service timestamps debug datetime msec
service timestamps log datetime msec
...

此示例说明了基于网络最佳实践的network_connection变量和推荐结构。我们将在第 5 章、Python 自动化框架【超越基础】中探讨如何将变量卸载到host_vars目录和条件。此结构也可用于本章中的 Juniper 和 Arista 示例。对于不同的设备,我们将只对network_connection使用不同的值

Ansible Juniper 示例

Ansible JUniter 模块需要 JUniter PyEZ 包和 NETCONF。如果您一直遵循第 3 章、API 和意图驱动网络中的 API 示例,那么您就可以开始了。如果没有,请参阅该部分的安装说明以及一些测试脚本,以确保 PyEZ 正常工作。还需要名为jxmlease的 Python 包:

$ sudo pip install jxmlease

在主机文件中,我们将指定设备和连接变量:

[junos_devices]
J1 ansible_host=192.168.24.252

[junos_devices:vars]
username=juniper
password=juniper!

在我们的 Juniper playbook 中,我们将使用junos_facts模块收集设备的基本事实。这个模块相当于设置模块,如果我们需要根据返回的值采取行动,它将派上用场。注意此处示例中传输和端口的不同值:

    ---
    - name: Get Juniper Device Facts
      hosts: "junos_devices"
      gather_facts: false
      connection: local

      vars:
        netconf:
          host: "{{ ansible_host }}"
          username: "{{ username }}"
          password: "{{ password }}"
          port: 830
          transport: netconf

      tasks:
        - name: collect default set of facts
          junos_facts:
            provider: "{{ netconf }}"

          register: output

        - name: show output
          debug:
            var: output

执行时,您将从Juniper设备接收此输出:

PLAY [Get Juniper Device Facts] 
************************************************

TASK [collect default set of facts] 
********************************************
ok: [J1]

TASK [show output] 
*************************************************************
ok: [J1] => {
"output": {
"ansible_facts": {
"HOME": "/var/home/juniper",
"domain": "python",
"fqdn": "master.python",
"has_2RE": false,
"hostname": "master",
"ifd_style": "CLASSIC",
"model": "olive",
"personality": "UNKNOWN",
"serialnumber": "",
"switch_style": "NONE",
"vc_capable": false,
"version": "12.1R1.9",
"version_info": {
"build": 9,
"major": [
12,
1
],
"minor": "1",
"type": "R"
}
},
"changed": false
 }
}

PLAY RECAP 
*********************************************************************
J1 : ok=2 changed=0 unreachable=0 failed=0

Ansible Arista 示例

我们将看到的最后一个剧本示例是 Arista 命令模块。在这一点上,我们非常熟悉我们的剧本语法和结构。Arista 设备可以使用cli或eapi配置为使用传输,因此,在本例中,我们将使用cli。

这是主机文件:

[eos_devices]
A1 ansible_host=192.168.199.158

剧本也与我们之前看到的类似:

    ---
 - name: EOS Show Commands
 hosts: "eos_devices"
 gather_facts: false
 connection: local

 vars:
 cli:
 host: "{{ ansible_host }}"
 username: "arista"
 password: "arista"
 authorize: true
 transport: cli

 tasks:
 - name: eos show commands
 eos_command:
 commands:
 - show version | i Arista
 provider: "{{ cli }}"
 register: output

 - name: show output
 debug:
 var: output

输出将显示标准输出,正如我们在命令行中所期望的:

 PLAY [EOS Show Commands] 
 *******************************************************

 TASK [eos show commands] 
 *******************************************************
 ok: [A1]

 TASK [show output] 
 *************************************************************
 ok: [A1] => {
 "output": {
 "changed": false,
 "stdout": [
 "Arista DCS-7050QX-32-F"
 ],
 "stdout_lines": [
 [
 "Arista DCS-7050QX-32-F"
 ]
 ],
 "warnings": []
 }
 }

 PLAY RECAP 
 *********************************************************************
 A1 : ok=2 changed=0 unreachable=0 failed=0

总结

在本章中,我们对开放源码自动化框架 Ansible 进行了一次盛大的参观。与基于 Pexpect 和 API 驱动的网络自动化脚本不同,Ansible 提供了一个称为 playbook 的更高层抽象来自动化我们的网络设备。

Ansible 最初用于管理服务器,后来扩展到网络设备;因此,我们看了一个服务器示例。然后,我们比较和对比了网络管理剧本的不同之处。稍后,我们查看了 Cisco IOS、Juniper JUNOS 和 Arista EOS 设备的示例剧本。如果您使用的是 Ansible 2.5 版及更高版本,我们还研究了 Ansible 推荐的最佳实践

在第 5 章Python 自动化框架——超越基础中,我们将利用在本章中获得的知识,开始研究 Ansible 的一些更高级功能。