我使用 Ansible 的第一天

一位系统管理员分享了他将 Ansible 应用于实际,配置其网络中的计算机的信息和建议。
114 位读者喜欢这篇文章。
Avoiding data disasters with Sanoid

Opensource.com

启动并运行一台新的计算机(无论是物理的还是虚拟的)既耗时又需要大量工作——无论是第一次还是第 50 次。 多年来,我一直使用我创建的一系列脚本和 RPM 来安装我需要的软件包,并为我喜欢的工具执行许多配置。 这种方法效果很好,简化了我的工作,并减少了我键入命令的时间。

我一直在寻找更好的做事方式,并且多年来,我一直在听说和阅读 Ansible,这是一种用于自动化系统配置和管理的强大工具。 Ansible 允许系统管理员为一个或多个 playbook 中的每个主机定义一个特定状态,然后执行必要的任务以将主机带到该状态。 这包括安装或删除各种资源,例如 RPM 或 Apt 软件包、配置文件和其他文件、用户、组等等。

我推迟了学习如何使用它很长时间,因为——事情太多了。 直到最近,当我遇到一个我认为 Ansible 可以轻松解决的问题时。

本文不是关于开始使用 Ansible 的完整操作指南; 相反,它旨在提供对我遇到的一些问题以及我只在一些非常模糊的地方找到的一些信息的见解。 我在各种在线讨论和关于 Ansible 的问答组中发现的许多信息是不正确的。 错误范围从没有注明日期或来源的非常旧的信息到完全错误的信息。

本文中的信息已知有效——尽管可能还有其他方法可以完成相同的事情——它适用于 Ansible 2.9.13 和 Python 3.8.5。

我的问题

我所有最好的学习经历都始于我需要解决的问题,这次也不例外。

我一直在做一个小项目,修改 Midnight Commander (mc) 文件管理器的配置文件,并将它们推送到我网络上的各种系统进行测试。 尽管我有一个脚本来自动化这个过程,但它仍然需要一些命令行循环来提供我想要推送新代码的系统的名称。 我对配置文件进行了大量更改,这使得我必须经常推送新的配置文件。 但是,就在我以为我的新配置恰到好处时,我发现了一个问题,需要在修复后再次推送。

这种环境使得很难跟踪哪些系统有新文件,哪些系统没有。 我还有几个主机需要区别对待。 我对 Ansible 的一点了解表明它可能能够完成我需要的所有或大部分工作。

开始入门

我阅读了许多关于 Ansible 的好文章和书籍,但从未处于“我现在必须让它工作!”这样的情况。 而现在就是——好吧,就是现在!

在重新阅读这些文档时,我发现它们大多讨论如何使用——等等——Ansible 从 GitHub 安装 Ansible。 这很酷,但我真的只是想开始,所以我使用 DNF 和 Fedora 存储库中的版本在我的 Fedora 工作站上安装了它。 简单。

但是,我随后开始寻找文件位置,并尝试确定需要修改哪些配置文件、在哪里保存我的 playbook、playbook 甚至是什么样子以及它的作用。 我脑子里有很多(到目前为止)未解答的问题。

所以,无需进一步描述我的苦难,以下是我发现并让我开始的事情。

配置

Ansible 的配置文件保存在 /etc/ansible 中。 这很有道理,因为 /etc 是系统程序应该保存其配置文件的地方。 我需要处理的两个文件是 ansible.cfghosts

ansible.cfg

在使用我在文档和在线上找到的一些练习开始之后,我开始收到关于弃用某些旧 Python 文件的警告消息。 所以,我在 ansible.cfg 中将 deprecation_warnings 设置为 false,那些愤怒的红色警告消息就停止了。

deprecation_warnings = False

这些警告很重要,所以稍后我将重新审视它们,并弄清楚我需要做什么。 但就目前而言,它们不再使屏幕变得混乱并模糊了我实际需要关注的错误。

hosts 文件

/etc/hosts 文件不同,hosts 文件也称为清单文件,它列出了网络上的主机。 此文件允许将主机分组到相关的集合中,例如服务器、工作站和几乎任何您需要的指定。 此文件包含自己的帮助和大量示例,因此我不会在此处赘述。 但是,有一些事情需要知道。

主机可以列在任何组之外,但组有助于识别具有一个或多个共同特征的主机。 组使用 INI 格式,因此服务器组如下所示

[servers]
server1
server2
...etc.

主机名必须存在于此文件中,Ansible 才能在其上工作。 即使某些子命令允许您指定主机名,但除非主机名位于 hosts 文件中,否则该命令将失败。 主机也可以列在多个组中。 因此,除了 [servers] 组之外,server1 也可以是 [webservers] 组的成员,并且是 [ubuntu] 组的成员,以将其与 Fedora 服务器区分开来。

Ansible 很聪明。 如果 all 参数用于主机名,Ansible 将扫描该文件并在该文件中列出的所有主机上执行定义的任务。 Ansible 将只尝试在每个主机上工作一次,无论它出现在多少个组中。 这也意味着不需要定义“all”组,因为 Ansible 可以确定文件中所有主机名并创建自己的唯一主机名列表。

另一个需要注意的小事情是单个主机的多个条目。 我在我的 DNS 区域文件中使用 CNAME 记录来创建指向我的某些主机的 A 记录 的别名。 这样,我可以将主机称为 host1h1myhost。 如果您在 hosts 文件中为同一主机使用多个主机名,Ansible 将尝试在所有这些主机名上执行其任务; 它无法知道它们指的是同一主机。 好消息是,这不会影响总体结果; 只是 Ansible 在辅助主机名上工作并确定所有操作都已执行时,需要花费更多时间。

Ansible facts

我读过的关于 Ansible 的大多数材料都谈到了 Ansible facts,它“是与您的远程系统相关的数据,包括操作系统、IP 地址、连接的文件系统等等。” 这些信息可以通过其他方式获得,例如 lshwdmidecode/proc 文件系统等等,但 Ansible 会生成一个包含此信息的 JSON 文件。 每次 Ansible 运行时,它都会生成此 facts 数据。 此数据流中包含大量信息,所有信息都在 <"variable-name": "value"> 对中。 所有这些变量都可以在 Ansible playbook 中使用。 了解可用的大量信息的最佳方法是自己显示它

# ansible -m setup <hostname> | less

明白我的意思了吗? 您想了解的关于主机硬件和 Linux 发行版的一切都在那里,并且可以在 playbook 中使用。 我还没有到需要使用这些变量的地步,但我确信我会在接下来的几天内使用。

模块

上面的 ansible 命令使用 -m 选项来指定“setup”模块。 Ansible 已经内置了许多模块,所以你不需要为这些模块使用 -m。 还有许多可下载的模块可以安装,但到目前为止,内置模块可以满足我当前项目所需的一切。

Playbooks

Playbook 可以位于几乎任何地方。 由于我需要以 root 身份运行我的 playbook,所以我将它们放在 /root/ansible 中。 只要这个目录是我运行 Ansible 时的当前工作目录 (PWD),它就可以找到我的 playbook。 Ansible 还有一个运行时选项可以指定不同的 playbook 和位置。

Playbook 可以包含注释,尽管我看到的很少有文章或书籍提到这一点。 作为一名相信记录一切的系统管理员,我发现使用注释非常有用。 这与其说是在注释中说与我在任务名称中相同的事情,不如说是识别任务组的目的,并确保我记录了我以某种方式或顺序做某些事情的原因。 这可以在以后调试问题时有所帮助,因为我可能已经忘记了我最初的想法。

Playbook 只是定义主机所需状态的任务集合。 主机名或清单组在 playbook 的开头指定,并定义 Ansible 将在其上运行 playbook 的主机。

这是一个我的 playbook 的示例

################################################################################
# This Ansible playbook updates Midnight commander configuration files.        #
################################################################################
- name: Update midnight commander configuration files
  hosts: all
  
  tasks:
  - name: ensure midnight commander is the latest version
    dnf:
      name: mc
      state: present

  - name: create ~/.config/mc directory for root
    file:
      path: /root/.config/mc
      state: directory
      mode: 0755
      owner: root
      group: root

  - name: create ~/.config/mc directory for dboth
    file:
      path: /home/dboth/.config/mc
      state: directory
      mode: 0755
      owner: dboth
      group: dboth


  - name: copy latest personal skin
    copy:
      src: /root/ansible/UpdateMC/files/MidnightCommander/DavidsGoTar.ini
      dest: /usr/share/mc/skins/DavidsGoTar.ini
      mode: 0644
      owner: root
      group: root

  - name: copy latest mc ini file
    copy:
      src: /root/ansible/UpdateMC/files/MidnightCommander/ini
      dest: /root/.config/mc/ini
      mode: 0644
      owner: root
      group: root

  - name: copy latest mc panels.ini file
    copy:
      src: /root/ansible/UpdateMC/files/MidnightCommander/panels.ini
      dest: /root/.config/mc/panels.ini
      mode: 0644
      owner: root
      group: root
<SNIP>

playbook 以其自身的名称以及它将作用于的主机开始——在本例中,是我的 hosts 文件中列出的所有主机。 tasks 部分列出了使主机符合所需状态所需的特定任务。 此 playbook 首先执行一项任务,如果 Midnight Commander 不是最新版本,则 Ansible 的内置 DNF 会更新它。 下一个任务确保如果所需的目录不存在,则会创建它们,其余任务将文件复制到正确的位置。 这些 filecopy 任务还可以设置目录和文件的所有权和文件模式。

我的 Playbook 的详细信息超出了本文的范围,但我对这个问题使用了一种有些粗暴的解决方式。还有其他方法可以确定哪些用户需要更新文件,而不是为每个用户的每个文件都使用一个任务。我的下一个目标是简化这个 Playbook,使用一些更高级的技术。

运行 Playbook 很简单,只需使用 ansible-playbook 命令即可。.yml 扩展名代表 YAML。我见过几种不同的解释,但我认为它是 "Yet Another Markup Language"(另一种标记语言),尽管有些人认为 YAML 不是一种标记语言。

此命令运行我创建的用于更新 Midnight Commander 文件的 Playbook

# ansible-playbook -f 10 UpdateMC.yml

-f 选项指定 Ansible 应该 fork 最多 10 个线程,以便并行执行操作。这可以大大加快整体任务完成速度,尤其是在处理多个主机时。

输出

运行中的 Playbook 的输出会列出每个任务及其结果。ok 表示任务管理的主机状态已在任务节中定义。因为任务中定义的状态已经为真,所以 Ansible 不需要执行任务节中定义的操作。

响应 changed 表示 Ansible 执行了节中指定的任务,以使其达到期望的状态。在本例中,节中定义的主机状态不为真,因此执行了定义的操作使其为真。在支持颜色的终端上,TASK 行以彩色显示。在我的主机上,使用 amber-on-black 终端颜色配置,TASK 行以琥珀色显示,changed 行以棕色显示,ok 行以绿色显示。错误行以红色显示。

以下输出来自我最终将用于在新主机上执行安装后配置的 Playbook

PLAY [Post-installation updates, package installation, and configuration] 

TASK [Gathering Facts] 
ok: [testvm2]

TASK [Ensure we have connectivity] 
ok: [testvm2]

TASK [Install all current updates] 
changed: [testvm2]

TASK [Install a few command line tools] 
changed: [testvm2]

TASK [copy latest personal Midnight Commander skin to /usr/share] 
changed: [testvm2]

TASK [create ~/.config/mc directory for root] 
changed: [testvm2]

TASK [Copy the most current Midnight Commander configuration files to /root/.config/mc] 
changed: [testvm2] => (item=/root/ansible/PostInstallMain/files/MidnightCommander/DavidsGoTar.ini)
changed: [testvm2] => (item=/root/ansible/PostInstallMain/files/MidnightCommander/ini)
changed: [testvm2] => (item=/root/ansible/PostInstallMain/files/MidnightCommander/panels.ini)

TASK [create ~/.config/mc directory in /etc/skel] 
changed: [testvm2]

<SNIP>

奶牛

如果您的计算机上安装了 cowsay 程序,您会注意到 TASK 名称出现在奶牛的演讲泡泡中。

 ____________________________________
< TASK [Ensure we have connectivity] >
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

如果您没有此有趣的功能并且想要它,请使用您的发行版的软件包管理器安装 cowsay 软件包。如果您拥有此功能并且不想要它,请在 /etc/ansible/ansible.cfg 文件中设置 nocows = 1 以禁用它。

我喜欢这只奶牛,觉得它很有趣,但它减少了可用于显示消息的屏幕空间。所以我在它开始碍事后禁用了它。

文件

与我的 Midnight Commander 任务一样,通常需要安装和维护各种类型的文件。对于创建用于存储 Playbook 中使用的文件的目录树,存在与系统管理员一样多的 "最佳实践" - 或者至少与编写关于 Ansible 的书籍和文章的作者一样多。

我选择了一个对我来说有意义的简单结构

/root/ansible
└── UpdateMC
    ├── files
    │   └── MidnightCommander
    │       ├── DavidsGoTar.ini
    │       ├── ini
    │       └── panels.ini
    └── UpdateMC.yml

您应该使用对您有效的任何结构。只是要注意,其他系统管理员可能需要处理您设置的内容,所以应该有一定的逻辑性。当我使用 RPM 和 Bash 脚本来执行我的安装后任务时,我的文件存储库有点分散,而且肯定没有使用任何逻辑来构建。当我创建用于许多管理任务的 Playbook 时,我将引入一个更合乎逻辑的结构来管理我的文件。

多次运行 Playbook

可以根据需要或期望运行 Playbook 多次。只有当状态与任务节中指定的状态不匹配时,才会执行每个任务。这使得可以轻松地从先前 Playbook 运行期间遇到的错误中恢复。当 Playbook 遇到错误时,它会停止运行。

在测试我的第一个 Playbook 时,我犯了很多错误并纠正了它们。Playbook 的每次额外运行 - 假设我的修复是好的 - 都会跳过状态已经与指定状态匹配的任务,并执行那些不匹配的任务。当我的修复有效时,先前失败的任务会成功完成,并且 Playbook 中该任务之后的任何任务也会执行 - 直到它遇到另一个错误。

这也使测试变得容易。我可以添加新任务,并且当我运行 Playbook 时,只会执行这些新任务,因为它们是唯一不匹配测试主机所需状态的任务。

一些想法

某些任务不适合 Ansible,因为有更好的方法可以实现特定的主机状态。我想到的用例是将 VM 返回到初始状态,以便可以根据需要多次使用它,以从该已知状态开始执行测试。将 VM 置于所需状态,然后拍摄当时的主机状态的快照要容易得多。恢复到该快照通常比使用 Ansible 将主机返回到该所需状态更容易和更快。这是我在研究文章或测试新代码时每天都会做几次的事情。

在完成用于更新 Midnight Commander 的 Playbook 后,我启动了一个新的 Playbook,我将使用它在新安装的 Fedora 主机上执行安装后任务。我已经取得了良好的进展,并且该 Playbook 比我的第一个 Playbook 更复杂,也更不粗暴。

在使用 Ansible 的第一天,我就创建了一个解决问题的 Playbook。我还启动了第二个 Playbook,它将解决安装后配置的非常大的问题。而且我学到了很多。

虽然我真的很喜欢使用 Bash 脚本来处理我的许多管理任务,但我已经发现 Ansible 可以完成我想要的一切 - 并且以一种可以使系统保持在我想要的状态的方式。仅使用了一天,我就成为了 Ansible 的粉丝。

资源

我发现最完整和有用的文档是 Ansible 网站上的 用户指南。本文档旨在作为参考,而不是操作指南或入门文档。

Opensource.com 多年来发表了许多关于 Ansible 的文章,我发现其中大多数对我的需求非常有帮助。Enable Sysadmin 网站也有很多 Ansible 文章,我发现这些文章也很有帮助。您可以通过查看本周(2020 年 10 月 13 日至 14 日)举行的 AnsibleFest 了解更多信息。该活动完全是虚拟的,并且可以免费注册。

接下来读什么
David Both
David Both 是一位开源软件和 GNU/Linux 的倡导者、培训师、作家和演讲者。自 1996 年以来,他一直从事 Linux 和开源软件工作,自 1969 年以来一直从事计算机工作。他是“系统管理员的 Linux 哲学”的坚定支持者和传播者。

3 条评论

这是对探索 Ansible 的一个很好的反应。去年我有机会参加了一个 Ansible 课程,真的很喜欢。在从未使用过 YAML 的情况下,我能够很快掌握它,并像您一样投入到 Ansible 中。

写得很好。

以一种实际的方式构建您的 Ansible 环境并不明显。
我开始将内容拆分为环境(inventory 和 playbook),这些环境位于单独的 Git 存储库中。我还将我的 roles 放入单独的存储库中。很快我就不得不研究 AWX/Tower,以允许其他人运行我的 playbook,而无需在他们的机器上设置 Ansible 或强迫他们在“Ansible Server”上使用命令行。

怀疑您是否在版本控制下有配置文件。是否可以将存储库拉到主机上的暂存位置,并在后续任务中将文件硬链接和/或复制到最终位置。

在您的第一个任务状态中:latest 将更符合您的任务名称中的意图/注释。

顺便说一句,这就是我写很少评论的原因之一。大多数时候,我可以在任务名称中说明我的意图和原因。

看看 blocks,我现在开始大量使用它们,并且发现我经常为每个 block 编写注释并添加一个标签。

Blocks 对于错误处理也很重要。不要留下半损坏的配置。

如果一个 block 变得非常大和/或在其他 playbook 中有用途,我会将它分离成一个 role。

有用于管理快照的 Ansible 模块,那么为什么不使用 Ansible 管理您的测试工作流程呢?

这些都是很棒的想法。我现在将探索这些想法和其他想法,因为我已经超越了我的第一天。

谢谢!

回复 Dominik Riva

Creative Commons License 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.