正如我们在前两章已经讨论过的,Ansible 不能做任何事情,除非你告诉它它负责什么主机。这当然是合乎逻辑的——无论使用和设置有多容易,您都不希望任何自动化工具能够简单地控制网络上的每一台设备。因此,至少,您必须告诉 Ansible 它将在哪些主机上自动执行任务,而这,用最基本的术语来说,就是库存。
然而,库存不仅仅是一系列自动化目标。Ansible 清单可以几种格式提供;它们可以是静态的,也可以是动态的,它们可以包含定义 Ansible 如何与每个主机(或主机组)交互的重要变量。因此,他们应该有自己的一章,在这一章中,我们将对库存以及如何在您使用 Ansible 自动化基础架构时最大限度地利用它们进行实际探索。
在本章中,我们将涵盖以下主题:
- 创建清单文件并添加主机
- 生成动态清单文件
- 使用模式的特殊主机管理
本章假设您已经使用 Ansible 设置了控制主机,详见第 1 章、ansi ble 入门,并且您使用的是最新版本—本章中的示例使用 Ansible 2.9 进行了测试。本章还假设您至少有一个额外的主机要测试,理想情况下,这应该是基于 Linux 的。虽然我们将在本章中给出主机名的具体示例,但是您可以自由地用自己的主机名和/或 IP 地址来替换它们,并且如何做到这一点的详细信息将在适当的地方提供。
本章的代码包可在此处获得:https://github . com/packt publishing/Ansible-2-cook book/tree/master/Chapter % 203。
每当您在 Ansible 中看到对“创建库存”的引用时,通常可以很安全地假设它是静态库存。Ansible 支持两种类型的库存——静态和动态,我们将在本章后面介绍这两种库存中的后一种。静态库存本质上是静态的;它们是不变的,除非有人去手动编辑它们。当您开始测试 Ansible 时,这非常好,因为它为您提供了一种非常快速和简单的方法来快速启动和运行。即使在小型、封闭的环境中,静态清单也是管理环境的好方法,尤其是在基础架构变化不频繁的情况下。
大多数 Ansible 安装将在/etc/ansible/hosts
中查找默认清单文件(尽管该路径可在 Ansible 配置文件中配置,如第 2 章、了解 Ansible 的基础知识中所述)。欢迎您填写此文件或为每次行动手册运行提供您自己的清单,通常会看到行动手册旁边提供的清单。毕竟,很少有“一刀切”的行动手册,尽管您可以按组细分库存(稍后将详细介绍),但在给定的行动手册旁边提供更小的静态库存文件通常也同样容易。正如您将在本书的前几章中看到的,如果不使用默认值,大多数 Ansible 命令使用-i
标志来指定清单文件的位置。假设这看起来像下面的例子:
$ ansible -i /home/cloud-user/inventory all -m ping
您将会遇到的大多数静态清单文件都是以 INI 格式创建的,不过需要注意的是,其他格式也是可能的。在 INI 格式的文件之后,您会发现最常见的格式是 YAML 格式的文件——您可以使用的清单文件类型的更多详细信息可以在这里找到:https://docs . ansi ble . com/ansi ble/latest/user _ guide/intro _ inventory . html。
在本章中,我们将提供一些 INI 和 YAML 格式的清单文件的例子供您考虑,因为您必须了解这两者。就我个人而言,我已经和 Ansible 合作了很多年,并且处理过 INI 格式的文件或者动态清单,但是他们说知识就是力量,所以稍微了解一下这两种格式没有坏处。
让我们从创建静态清单文件开始。该清单文件将与默认清单分开。
使用以下 INI 格式的代码在/etc/ansible/my_inventory
中创建库存文件:
target1.example.com ansible_host=192.168.81.142 ansible_port=3333
target2.example.com ansible_port=3333 ansible_user=danieloh
target3.example.com ansible_host=192.168.81.143 ansible_port=5555
清单主机之间的空白行不是必需的,它们的插入只是为了使清单在本书中更易读。这个库存文件非常简单,不包含任何分组;但是,在引用清单时,您仍然可以使用特殊的all
组一起引用所有主机,该组是隐式定义的,无论您如何格式化和划分清单文件。
前面文件中的每一行都包含一个清单主机。第一列包含 Ansible 将使用的清单主机名(可通过我们在第 2 章、了解 Ansible 的基础知识中讨论的inventory_hostname
神奇变量访问)。其后同一行上的所有参数都是分配给主机的变量。这些变量可以是用户定义的变量,也可以是我们在这里设置的特殊变量。
有许多这样的变量,但前面的例子具体包括以下内容:
ansible_host
:如果无法直接访问清单主机名—例如,可能因为它不在 DNS 中,则此变量包含 Ansible 将连接到的主机名或 IP 地址。ansible_port
:默认情况下,Ansible 会尝试通过端口 22 进行 SSH 的所有通信——如果您有一个 SSH 守护程序在另一个端口上运行,您可以使用这个变量告诉 Ansible。ansible_user
:默认情况下,Ansible 将尝试使用运行 Ansible 命令的当前用户帐户连接到远程主机—您可以通过多种方式覆盖这一点,这是其中之一。
因此,前面三个主机可以总结如下:
- 应使用端口
3333
上的192.168.81.142
IP 地址连接target1.example.com
主机。 target2.example.com
主机也应该连接到端口3333
,但这次使用的是danieloh
用户,而不是运行 Ansible 命令的帐户。- 应使用端口
5555
上的192.168.81.143
IP 地址连接target3.example.com
主机。
这样,即使没有进一步的构造,您也可以开始看到静态 INI 格式清单的威力。
现在,如果您想创建与前面完全相同的清单,但这次将其格式化为 YAML,您可以指定如下:
---
ungrouped:
hosts:
target1.example.com:
ansible_host: 192.168.81.142
ansible_port: 3333
target2.example.com:
ansible_port: 3333
ansible_user: danieloh
target3.example.com:
ansible_host: 192.168.81.143
ansible_port: 5555
您可能会遇到包含参数的清单文件示例,如ansible_ssh_port
、ansible_ssh_host
和ansible_ssh_user
–这些变量名(以及类似的其他名称)在 2.0 之前的 Ansible 版本中使用过。其中许多都保持了向后兼容性,但是您应该尽可能地更新它们,因为这种兼容性可能会在将来的某个时候被删除。
现在,如果使用简单的shell
命令在 Ansible 中运行前面的清单,结果将如下所示:
$ ansible -i /etc/ansible/my_inventory.yaml all -m shell -a 'echo hello-yaml' -f 5
target1.example.com | CHANGED | rc=0 >>
hello-yaml
target2.example.com | CHANGED | rc=0 >>
hello-yaml
target3.example.com | CHANGED | rc=0 >>
hello-yaml
这涵盖了创建简单静态清单文件的基础知识。现在,让我们在本章的下一部分通过将主机组添加到清单中来对此进行扩展。
很少有一个行动手册适合整个基础架构,尽管很容易告诉 Ansible 为不同的行动手册使用备用清单,但这可能会非常混乱,非常快,可能会有数百个小清单文件散布在您的网络中。你可以想象这将变得多么难以控制,Ansible 应该让事情变得更容易控制,而不是相反。一个可能的简单解决方案是开始将组添加到您的清单中。
让我们假设您有一个简单的三层 web 体系结构,每层都有多个主机来实现高可用性和/或负载平衡。该体系结构中的三层可能如下:
- 前端服务器
- 应用服务器
- 数据库服务器
有了这个架构,让我们开始为它创建一个清单,再次混合 YAML 和 INI 格式,给你两者的体验。为了保持示例清晰简洁,我们假设您可以使用所有服务器的完全限定域名(FQDN)访问所有服务器,因此不会在这些清单文件中添加任何主机变量。当然没有什么能阻止你这么做,每个例子都不一样。
首先,让我们使用 INI 格式为三层前端创建清单。我们将这个文件称为hostsgroups-ini
,这个文件的内容应该是这样的:
loadbalancer.example.com
[frontends]
frt01.example.com
frt02.example.com
[apps]
app01.example.com
app02.example.com
[databases]
dbms01.example.com
dbms02.example.com
在前面的清单中,我们创建了三个组,分别叫做frontends
、apps
和databases
。请注意,在 INI 格式的清单中,组名放在方括号内。在每个组名下面是属于每个组的服务器名,因此前面的示例显示了每个组中的两台服务器。请注意顶部的异常值loadbalancer.example.com
—该主机不在任何组中。所有未分组的主机必须位于 INI 格式文件的最顶端。
在我们继续之前,值得注意的是,库存也可以包含组组,这对于不同部门处理某些任务非常有用。前面的清单是正确的,但是如果我们的前端服务器建立在 Ubuntu 上,而应用和数据库服务器建立在 CentOS 上呢?我们处理这些主机的方式会有一些根本的不同——例如,我们可能使用 Ubuntu 上的apt
模块来管理包,而使用 CentOS 上的yum
模块。
当然,我们可以使用从每台主机收集的事实来处理这种情况,因为这些将包含操作系统细节。我们还可以创建清单的新版本,如下所示:
loadbalancer.example.com
[frontends]
frt01.example.com
frt02.example.com
[apps]
app01.example.com
app02.example.com
[databases]
dbms01.example.com
dbms02.example.com
[centos:children]
apps
databases
[ubuntu:children]
frontends
通过使用组定义中的children
关键字(在方括号内),我们可以创建组的组;因此,我们可以执行巧妙的分组来帮助我们的剧本设计,而不必多次指定每个主机。
这种 INI 格式的结构非常易读,但是当它被转换成 YAML 格式时需要一些时间来适应。接下来列出的代码显示了前面清单的 YAML 版本,就 Ansible 而言,这两个版本是相同的,但是您可以自行决定使用哪种格式:
all:
hosts:
loadbalancer.example.com:
children:
centos:
children:
apps:
hosts:
app01.example.com:
app02.example.com:
databases:
hosts:
dbms01.example.com:
dbms02.example.com:
ubuntu:
children:
frontends:
hosts:
frt01.example.com:
frt02.example.com:
您可以看到children
关键字仍然在 YAML 格式的清单中使用,但是现在的结构比 INI 格式更有层次。缩进对您来说可能更容易理解,但是请注意主机最终是如何在相当高的缩进级别定义的——根据您想要的方法,这种格式可能更难扩展。
当您想要使用前面清单中的任何组时,您只需在行动手册或命令行中引用它。例如,在我们运行的最后一节中,我们可以使用以下内容:
$ ansible -i /etc/ansible/my_inventory.yaml all -m shell -a 'echo hello-yaml' -f 5
注意那一行中间的all
关键字。这就是特殊的all
组,它隐含在所有库存中,并在您之前的 YAML 示例中明确提到。如果我们想要运行相同的命令,但这次仅在先前 YAML 清单中的centos
组主机上运行,我们将运行该命令的变体:
$ ansible -i hostgroups-yml centos -m shell -a 'echo hello-yaml' -f 5
app01.example.com | CHANGED | rc=0 >>
hello-yaml
app02.example.com | CHANGED | rc=0 >>
hello-yaml
dbms01.example.com | CHANGED | rc=0 >>
hello-yaml
dbms02.example.com | CHANGED | rc=0 >>
hello-yaml
正如您所看到的,这是一种管理清单的强大方法,并且可以轻松地在您想要的主机上运行命令。创建多个组的可能性使生活变得简单而容易,尤其是当您想要在不同的服务器组上运行不同的任务时。
除了开发清单之外,值得注意的是,有一个快速简写符号,可以用来创建多个主机。让我们假设您有 100 台应用服务器,它们都是按顺序命名的,如下所示:
[apps]
app01.example.com
app02.example.com
...
app99.example.com
app100.example.com
这是完全可能的,但是手工创建将是乏味且容易出错的,并且将产生一些非常难以阅读和解释的清单。幸运的是,Ansible 提供了一个快速的简写符号来实现这一点,下面的清单片段实际上生成了一个清单,其中包含我们可以手动创建的 100 台相同的应用服务器:
[apps]
app[01:100].prod.com
还可以使用字母范围和数字范围—扩展我们的示例以添加一些缓存服务器,您可能会看到以下内容:
[caches]
cache-[a:e].prod.com
这与手动创建以下内容相同:
[caches]
cache-a.prod.com
cache-b.prod.com
cache-c.prod.com
cache-d.prod.com
cache-e.prod.com
既然我们已经完成了对各种静态清单格式以及如何创建组(实际上是子组)的探索,那么让我们在下一节中展开之前对主机变量的简要介绍。
我们已经谈到了主机变量,我们在本章前面已经看到了它们,当时我们使用它们来覆盖连接细节,例如要连接的用户帐户、要连接的地址和要使用的端口。但是,您可以使用 Ansible 和清单变量做更多的事情,需要注意的是,它们不仅可以在主机级别定义,还可以在组级别定义,这再次为您提供了一些非常强大的方法,可以从一个中央清单高效地管理您的基础架构。
让我们以前面的三层示例为基础,假设我们需要为两个前端服务器分别设置两个变量。这些不是特殊的 Ansible 变量,而是完全由我们自己选择的变量,稍后我们将在针对该服务器运行的行动手册中使用这些变量。假设这些变量如下:
https_port
,定义前端代理应该监听的端口lb_vip
,它定义了前端服务器前面负载平衡器的 FQDN
让我们看看这是如何做到的:
- 我们可以简单地将这些添加到清单文件
frontends
部分的每个主机中,就像我们之前对 Ansible 连接变量所做的那样。在这种情况下,我们的 INI 格式清单的一部分可能如下所示:
[frontends]
frt01.example.com https_port=8443 lb_vip=lb.example.com
frt02.example.com https_port=8443 lb_vip=lb.example.com
如果我们对这个清单运行一个特别的命令,我们可以看到这两个变量的内容:
$ ansible -i hostvars1-hostgroups-ini frontends -m debug -a "msg=\"Connecting to {{ lb_vip }}, listening on {{ https_port }}\""
frt01.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
frt02.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
这正如我们所期望的那样工作,但是这种方法效率很低,因为您必须向每台主机添加相同的变量。
- 幸运的是,您可以将变量分配给主机组,也可以单独分配给主机。如果我们编辑前面的清单来实现这一点,
frontends
部分现在将如下所示:
[frontends]
frt01.example.com
frt02.example.com
[frontends:vars]
https_port=8443
lb_vip=lb.example.com
注意到可读性有多强了吗?然而,如果我们对新组织的清单运行与之前相同的命令,我们会发现结果是一样的:
$ ansible -i groupvars1-hostgroups-ini frontends -m debug -a "msg=\"Connecting to {{ lb_vip }}, listening on {{ https_port }}\""
frt01.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
frt02.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
- 有时您希望为单个主机处理主机变量,有时组变量更相关。由你来决定哪一个更适合你的场景;但是,请记住,主机变量可以组合使用。还值得注意的是,主机变量会覆盖组变量,因此如果我们需要将连接端口更改为
frt01.example.com
上的8444
,我们可以这样做:
[frontends]
frt01.example.com https_port=8444
frt02.example.com
[frontends:vars]
https_port=8443
lb_vip=lb.example.com
现在,如果我们用新的清单再次运行我们的 ad hoc 命令,我们可以看到我们已经覆盖了一台主机上的变量:
$ ansible -i hostvars2-hostgroups-ini frontends -m debug -a "msg=\"Connecting to {{ lb_vip }}, listening on {{ https_port }}\""
frt01.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8444"
}
frt02.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
当然,当只有两台主机时,单独为一台主机执行此操作可能看起来有点毫无意义,但是当您有一个包含数百台主机的清单时,这种覆盖一台主机的方法会突然变得非常有价值。
- 为了完整起见,如果我们将之前定义的主机变量添加到 YAML 版本的清单中,
frontends
部分将显示如下(为了节省空间,清单的其余部分已被删除):
frontends:
hosts:
frt01.example.com:
https_port: 8444
frt02.example.com:
vars:
https_port: 8443
lb_vip: lb.example.com
运行与之前相同的临时命令,您可以看到结果与我们的 INI 格式清单相同:
$ ansible -i hostvars2-hostgroups-yml frontends -m debug -a "msg=\"Connecting to {{ lb_vip }}, listening on {{ https_port }}\""
frt01.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8444"
}
frt02.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
- 到目前为止,我们已经介绍了几种向您的清单提供主机变量和组变量的方法;然而,还有另一种方法值得特别一提,随着你的库存变得更大、更复杂,它将对你变得有价值。
现在,我们的例子很小很紧凑,只包含少数几个组和变量;但是,当您将其扩展到完整的服务器基础架构时,使用单一平面清单文件可能会再次变得难以管理。幸运的是,Ansible 也提供了一个解决方案。两个特殊命名的目录host_vars
和group_vars
会自动搜索适当的变量内容(如果它们存在于行动手册目录中)。我们可以通过使用这种特殊的目录结构重新创建前面的前端变量示例来测试这一点,而不是将变量放入清单文件中。
让我们从为此目的创建一个新的目录结构开始:
$ mkdir vartree
$ cd vartree
- 现在,在这个目录下,我们将为变量再创建两个目录:
$ mkdir host_vars group_vars
- 现在,在
host_vars
目录下,我们将用需要代理设置的主机名称创建一个文件,并附加.yml
(即frt01.example.com.yml
)。该文件应包含以下内容:
---
https_port: 8444
- 同样,在
group_vars
目录下,创建一个以我们要分配变量的组(即frontends.yml
)命名的 YAML 文件,其内容如下:
---
https_port: 8443
lb_vip: lb.example.com
- 最后,我们将像以前一样创建库存文件,只是它不包含任何变量:
loadbalancer.example.com
[frontends]
frt01.example.com
frt02.example.com
[apps]
app01.example.com
app02.example.com
[databases]
dbms01.example.com
dbms02.example.com
为了清楚起见,您的最终目录结构应该如下所示:
$ tree
.
├── group_vars
│ └── frontends.yml
├── host_vars
│ └── frt01.example.com.yml
└── inventory
2 directories, 3 files
- 现在,让我们尝试运行我们熟悉的即席命令,看看会发生什么:
$ ansible -i inventory frontends -m debug -a "msg=\"Connecting to {{ lb_vip }}, listening on {{ https_port }}\""
frt02.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
frt01.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8444"
}
如您所见,这与之前完全一样,无需进一步说明,Ansible 已经遍历了目录结构并摄取了所有变量文件。
- 如果您有数百个变量(或者需要更细粒度的方法),您可以用以主机和组命名的目录替换 YAML 文件。让我们重新创建目录结构,但现在改为使用目录:
$ tree
.
├── group_vars
│ └── frontends
│ ├── https_port.yml
│ └── lb_vip.yml
├── host_vars
│ └── frt01.example.com
│ └── main.yml
└── inventory
注意我们现在是如何用frontends
组和frt01.example.com
主机命名目录的?在frontends
目录中,我们已经将变量分成了两个文件,这对于将变量按组进行逻辑组织非常有用,尤其是当您的行动手册变得越来越大、越来越复杂时。
文件本身只是我们之前文件的改编:
$ cat host_vars/frt01.example.com/main.yml
---
https_port: 8444
$ cat group_vars/frontends/https_port.yml
---
https_port: 8443
$ cat group_vars/frontends/lb_vip.yml
---
lb_vip: lb.example.com
即使使用这种更精细的目录结构,运行 ad hoc 命令的结果仍然是一样的:
$ ansible -i inventory frontends -m debug -a "msg=\"Connecting to {{ lb_vip }}, listening on {{ https_port }}\""
frt01.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8444"
}
frt02.example.com | SUCCESS => {
"msg": "Connecting to lb.example.com, listening on 8443"
}
- 在我们结束本章之前,需要注意的最后一件事是,如果您在组级别和子组级别定义了同一个变量,则子组级别的变量优先。这并不像听起来那么明显。考虑一下我们之前的清单,其中我们使用子组来区分 CentOS 和 Ubuntu 主机—如果我们向
ubuntu
子组和frontends
组(即ubuntu
组的子添加一个同名变量,结果会是什么?清单如下所示:
loadbalancer.example.com
[frontends]
frt01.example.com
frt02.example.com
[frontends:vars]
testvar=childgroup
[apps]
app01.example.com
app02.example.com
[databases]
dbms01.example.com
dbms02.example.com
[centos:children]
apps
databases
[ubuntu:children]
frontends
[ubuntu:vars]
testvar=group
现在,让我们运行一个特别的命令来看看testvar
实际上设置了什么值:
$ ansible -i hostgroups-children-vars-ini ubuntu -m debug -a "var=testvar"
frt01.example.com | SUCCESS => {
"testvar": "childgroup"
}
frt02.example.com | SUCCESS => {
"testvar": "childgroup"
}
需要注意的是frontends
组是该库存中ubuntu
组的子组(因此,组定义为[ubuntu:children]
,因此我们在frontends
组级别设置的变量值获胜,因为这是该场景中的子组。
到目前为止,您应该已经对如何处理静态清单文件有了一个很好的了解。然而,如果不考虑动态库存,对 Ansible 库存能力的研究是不完整的,我们将在下一节中进行详细介绍。
在云计算和基础设施即代码的时代,您可能希望自动化的主机每天都在变化,如果不是每小时的话!保持静态可更新库存可能成为一项全职工作,因此,在许多大规模场景中,尝试持续使用静态库存变得不现实。
这就是 Ansible 的动态库存支持的来源。简而言之,Ansible 几乎可以从任何可执行文件中收集其清单数据(尽管您会发现大多数动态清单都是用 Python 编写的)——唯一的要求是可执行文件以指定的 JSON 格式返回清单数据。如果您愿意,您可以自由创建自己的清单脚本,但谢天谢地,已经为您创建了许多清单脚本,涵盖了大量潜在的清单源,包括亚马逊 EC2、微软 Azure、红帽卫星、LDAP 目录和许多其他系统。
写一本书的时候,很难确定用哪个动态清单脚本作为例子,因为不是每个人都会有一个亚马逊 EC2 账户,他们可以自由使用来测试(例如)。因此,我们将通过示例的方式使用皮匠供应系统,因为这是免费提供的,并且易于在 CentOS 系统上推广。对于那些感兴趣的人来说,Cobbler 是一个动态配置和构建 Linux 系统的系统,它可以处理这方面的所有方面,包括域名系统、DHCP、PXE 引导等。因此,如果您要使用它来调配基础架构中的虚拟机或物理机,那么也将它用作您的库存来源将是有意义的,因为一开始,Cobbler 就负责构建系统,因此知道所有的系统名称。
这个例子将为您演示使用动态清单的基本原理,然后您可以继续使用动态清单脚本用于其他系统。让我们从这个过程开始,首先安装补鞋匠—这里概述的过程在 CentOS 7.8 上进行了测试:
- 你的第一个任务是使用
yum
安装相关的补鞋匠包。请注意,在撰写本文时,CentOS 7 提供的 SELinux 策略不支持 Cobbler 的功能,并且阻止了某些方面的工作。虽然这不是您应该在生产环境中做的事情,但是启动和运行该演示的最简单方法是禁用 SELinux:
$ yum install -y cobbler cobbler-web
$ setenforce 0
- 接下来,通过检查
/etc/cobbler/settings
中的设置,确保cobblerd
服务被配置为侦听回送地址—文件的相关片段显示在此处,应该如下所示:
# default, localhost
server: 127.0.0.1
This is not a public listening address, so please do not use 0.0.0.0
. You can also set it to the IP address of the Cobbler server.
- 完成此步骤后,您可以使用
systemctl
启动cobblerd
服务:
$ systemctl start cobblerd.service
$ systemctl enable cobblerd.service
$ systemctl status cobblerd.service
- 随着 pipper 服务的启动和运行,我们现在将逐步完成向 pipper 添加一个分发版的过程,以创建一些脱离的主机。这个过程相当简单,但是您确实需要添加一个内核文件和一个初始内存磁盘文件。假设您已经在 CentOS 7 上安装了 Cobbler,那么从您的
/boot
目录中获取这些最简单的来源。在用于本演示的测试系统上,使用了以下命令,但是,您必须将vmlinuz
和initramfs
文件名中的版本号替换为系统/boot
目录中的相应版本号:
$ cobbler distro add --name=CentOS --kernel=/boot/vmlinuz-3.10.0-957.el7.x86_64 --initrd=/boot/initramfs-3.10.0-957.el7.x86_64.img
$ cobbler profile add --name=webservers --distro=CentOS
这个定义是非常初级的,不一定能够产生有效的服务器映像;然而,对于我们的简单演示来说,这就足够了,因为我们可以基于这个基于概念 CentOS 的映像添加一些系统。请注意,我们正在创建的配置文件名称webservers
,稍后将成为我们动态库存中的库存组名称。
- 现在让我们将这些系统添加到皮匠。以下两个命令将使用我们之前创建的
webservers
配置文件向我们的鞋匠系统添加两个名为frontend01
和frontend02
的主机:
$ cobbler system add --name=frontend01 --profile=webservers --dns-name=frontend01.example.com --interface=eth0
$ cobbler system add --name=frontend02 --profile=webservers --dns-name=frontend02.example.com --interface=eth0
注意,要让 Ansible 工作,它必须能够到达--dns-name
参数中指定的这些 FQDNs。为了实现这一点,我还在皮匠系统上为这两台机器添加了条目/etc/hosts
,以确保我们以后可以找到它们。这些条目可以指向你选择的任何两个系统,因为这只是一个测试。
至此,您已经成功安装了 Cobbler,创建了一个概要文件,并向该概要文件中添加了两个假设的系统。我们流程的下一个阶段是下载并配置 Ansible 动态清单脚本来处理这些条目。为此,让我们从这里给出的流程开始:
- 从 GitHub Ansible 存储库中下载 pipper 动态清单文件和相关的配置文件模板。请注意,Ansible 提供的大多数动态清单脚本也有一个模板化的配置文件,其中包含您可能需要设置的参数,以使动态清单脚本正常工作。对于我们的简单示例,我们将把这些文件下载到我们当前的工作目录中:
$ wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/cobbler.py
$ wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/cobbler.ini
$ chmod +x cobbler.py
重要的是要记住让您下载的任何动态清单脚本都是可执行的,如前所示;如果您不这样做,那么 Ansible 将无法运行脚本,即使其他一切都设置得很完美。
- 编辑
cobbler.ini
文件,并确保它指向本地主机,因为在这个例子中,我们将在同一个系统上运行 Ansible 和 Pipper。在现实生活中,你可以把它指向你的鞋匠系统的远程网址。这里显示了配置文件的一个片段,让您了解要配置什么:
[cobbler]
# Specify IP address or Hostname of the cobbler server. The default variable is here:
host = http://127.0.0.1/cobbler_api
# (Optional) With caching, you will have responses of API call with the cobbler server quicker
cache_path = /tmp
cache_max_age = 900
- 现在,您可以按照习惯的方式运行 Ansible 即席命令,唯一不同的是,这次您将指定动态清单脚本的文件名,而不是静态清单文件的名称。假设您已经在我们之前输入的两个地址设置了主机,那么您的输出应该如下所示:
$ ansible -i cobbler.py webservers -m ping
frontend01.example.com | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
frontend02.example.com | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
就这样!您刚刚在 Ansible 中实现了第一个动态清单。当然,我们知道许多读者不会使用 Copper,而其他一些动态库存插件要开始使用就有点复杂了。例如,亚马逊 EC2 动态清单脚本要求您提供亚马逊网络服务(或合适的 IAM 帐户)的身份验证详细信息,并安装 Python boto
和boto3
库。你怎么知道要做这些?幸运的是,所有这些都记录在动态清单脚本或配置文件的标题中,所以我能给出的最基本的建议是:每当您下载新的动态清单脚本时,一定要在您最喜欢的编辑器中检查文件本身,因为它们的需求很可能已经为您记录下来了。
在本书的这一部分结束之前,让我们先来看看其他一些处理库存的便捷提示,从下一部分中使用多个库存来源开始。
到目前为止,在本书中,我们已经使用 Ansible 命令中的-i
开关指定了我们的库存文件(静态或动态)。可能不明显的是,您可以多次指定-i
开关,因此可以同时使用多个库存。这使您能够同时从静态和动态清单中跨主机运行行动手册(或临时命令)等任务。Ansible 将确定需要做什么——静态清单不应该被标记为可执行,因此不会被如此处理,而动态清单将被标记为可执行。这个小而聪明的技巧使您能够轻松地组合多个库存来源。让我们在下一节继续研究静态库存组与动态库存组的结合使用,这是多库存功能的扩展。
当然,混合库存的可能性带来了一个有趣的问题——如果你定义了动态库存和静态库存,那么它们会发生什么变化?答案是,Ansible 将两者结合在一起,这带来了一种有趣的可能性。正如您所观察到的,我们的皮匠库存脚本从我们称之为webservers
的皮匠配置文件中生成了一个名为webservers
的 Ansible 组。这对于大多数动态库存供应商来说很常见;大多数库存来源(例如,offer 和 Amazon EC2)不支持 Ansible,因此不提供 Ansible 可以直接使用的组。因此,大多数动态清单脚本将使用来自清单源的某些方面的信息来产生分组,皮匠机器概要就是这样一个例子。
让我们通过混合静态清单来扩展上一节中的鹅卵石示例。假设我们想让我们的webservers
机器成为一个名为centos
的组的子组,这样我们就可以在未来将所有的 CentOS 机器组合在一起。我们知道我们只有一个名为webservers
的皮匠概要文件,理想情况下,我们不想开始干扰皮匠设置来做一些仅仅与 Ansible 相关的事情。
答案是创建一个包含两个组定义的静态清单文件。第一个名称必须与您希望从动态清单中获得的组名称相同,但您应该将其留空。当 Ansible 合并静态和动态库存内容时,它将重叠这两个组,因此将来自 pipper 的主机添加到这些webservers
组中。
第二组定义应说明webservers
是centos
组的子组。生成的文件应该如下所示:
[webservers]
[centos:children]
webservers
现在让我们在 Ansible 中运行一个简单的临时ping
命令,看看它如何一起评估这两个库存。请注意,我们将如何指定运行ping
的centos
组,而不是webservers
组。我们知道,皮匠没有centos
组,因为我们从未创建过一个,并且我们知道,当您将两个库存组合在一起时,该组中的任何主机都必须通过webservers
组,因为我们的静态库存中没有主机。结果如下所示:
$ ansible -i static-groups-mix-ini -i cobbler.py centos -m ping
frontend01.example.com | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
frontend02.example.com | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
从前面的输出可以看出,我们引用了两种不同的库存,一种是静态的,另一种是动态的。我们组合了多个组,将只存在于一个库存源中的主机与只存在于另一个库存源中的组组合在一起。如您所见,这是一个极其简单的示例,很容易将其扩展为组合静态和动态主机列表,或者向来自动态清单的主机添加自定义变量。
这是 Ansible 的一个小技巧,鲜为人知,但随着库存的扩大和增长,它会变得非常强大。当我们完成本章时,您会发现我们非常精确地指定了我们的库存主机,无论是单独指定还是按组指定;例如,我们明确告诉ansible
对webservers
组中的所有主机运行 ad hoc 命令。在下一节中,我们将在此基础上研究 Ansible 如何管理一组使用模式指定的主机。
我们已经确定,您通常希望针对库存的某一部分运行临时命令或行动手册。到目前为止,我们已经非常精确地做到了这一点,但是现在让我们通过查看 Ansible 如何与模式一起工作来确定命令(或行动手册)应该运行在哪个主机上来扩展这一点。
作为一个起点,让我们再次考虑我们在本章前面定义的清单,目的是探索主机组和子组。为方便起见,此处再次提供了库存内容:
loadbalancer.example.com
[frontends]
frt01.example.com
frt02.example.com
[apps]
app01.example.com
app02.example.com
[databases]
dbms01.example.com
dbms02.example.com
[centos:children]
apps
databases
[ubuntu:children]
frontends
为了演示通过模式选择主机/组,我们将使用--list-hosts
开关和ansible
命令来查看 Ansible 将对哪些主机进行操作。欢迎您扩展示例以使用ping
模块,但为了节省空间并保持输出简洁易读,我们将在这里使用--list-hosts
:
- 我们已经提到了指定清单中所有主机的特殊
all
组:
$ ansible -i hostgroups-children-ini all --list-hosts
hosts (7):
loadbalancer.example.com
frt01.example.com
frt02.example.com
app01.example.com
app02.example.com
dbms01.example.com
dbms02.example.com
星号字符与all
具有相同的效果,但需要在单引号中引用,以便 shell 正确解释命令:
$ ansible -i hostgroups-children-ini '*' --list-hosts
hosts (7):
loadbalancer.example.com
frt01.example.com
frt02.example.com
app01.example.com
app02.example.com
dbms01.example.com
dbms02.example.com
- 使用
:
指定逻辑OR
,意思是“应用于该组或该组中的主机”,如本例所示:
$ ansible -i hostgroups-children-ini frontends:apps --list-hosts
hosts (4):
frt01.example.com
frt02.example.com
app01.example.com
app02.example.com
- 使用
!
排除特定组-您可以将其与其他字符(如:
组合,以显示(例如)除apps
组中的主机之外的所有主机。同样,!
是 shell 中的一个特殊字符,因此您必须用单引号将模式字符串括起来才能使用,如本例所示:
$ ansible -i hostgroups-children-ini 'all:!apps' --list-hosts
hosts (5):
loadbalancer.example.com
frt01.example.com
frt02.example.com
dbms01.example.com
dbms02.example.com
- 使用
:&
来指定两个组之间的逻辑AND
,例如,如果我们想要所有在centos
组和apps
组中的主机(同样,您必须在 shell 中使用单引号):
$ ansible -i hostgroups-children-ini 'centos:&apps' --list-hosts
hosts (2):
app01.example.com
app02.example.com
- 使用
*
通配符的方式类似于在 shell 中使用的方式,如本例所示:
$ ansible -i hostgroups-children-ini 'db*.example.com' --list-hosts
hosts (2):
dbms02.example.com
dbms01.example.com
另一种限制命令在哪些主机上运行的方法是使用带 Ansible 的--limit
开关。这使用了与前面完全相同的语法和模式符号,但优点是您可以将其与ansible-playbook
命令一起使用,其中仅ansible
命令本身支持在命令行上指定主机模式。因此,例如,您可以运行以下命令:
$ ansible-playbook -i hostgroups-children-ini site.yml --limit frontends:apps
PLAY [A simple playbook for demonstrating inventory patterns] ******************
TASK [Gathering Facts] *********************************************************
ok: [frt02.example.com]
ok: [app01.example.com]
ok: [frt01.example.com]
ok: [app02.example.com]
TASK [Ping each host] **********************************************************
ok: [app01.example.com]
ok: [app02.example.com]
ok: [frt02.example.com]
ok: [frt01.example.com]
PLAY RECAP *********************************************************************
app01.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
app02.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
frt01.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
frt02.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
模式是处理库存的一个非常有用和重要的部分,毫无疑问,你会发现这是非常宝贵的。我们关于 Ansible 库存的章节到此结束;然而,希望这已经为您提供了自信地处理 Ansible 库存所需的一切。
创建和管理 Ansible 库存是您使用 Ansible 工作的一个关键部分,因此我们在本书的早期已经介绍了这个基本概念。它们是至关重要的,因为如果没有它们,Ansible 将不知道运行自动化任务的主机是什么,然而它们提供的远不止这些。它们为配置管理系统提供了一个集成点,为要存储的特定于主机(或特定于组)的变量提供了一个合理的来源,并为您提供了一种灵活的方式来运行本行动手册。
在本章中,您学习了如何创建简单的静态清单文件并向其中添加主机。然后,我们通过学习如何添加主机组和为主机分配变量来扩展这一点。我们还研究了当单个平面库存文件变得太难处理时,如何组织库存和变量。然后,我们学习了如何利用动态清单文件,最后看了一些有用的提示和技巧,例如组合清单源和使用模式来指定主机,所有这些都将使您处理清单更加容易,同时也更加强大。
在下一章中,我们将学习如何使用 Ansible 开发行动手册和角色来配置、部署和管理远程机器。
- 如何将
frontends
组变量添加到库存中?
A) [frontends::]
B) [frontends::values]
C) [frontends:host:vars]
D) [frontends::variables]
E) [frontends:vars]
- 是什么让您能够自动执行 Linux 任务,例如配置 DNS、管理 DHCP、更新包和配置管理?
剧本
b)百胜
补鞋匠
D) Bash
角色
- Ansible 允许您使用命令行上的
-i
选项指定库存文件位置。
真的吗
假的
- GitHub 资源库中 Ansible 的所有常见动态清单如下:https://GitHub . com/ansi ble/ansi ble/tree/dev/contrib/inventory。