我使用 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 的优秀文章和书籍,但从未在“我必须立即让它工作!”的情况下。而现在就是——好吧,就是现在!

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

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

所以,不再描述我的磨难,以下是我发现并让我开始运行的事情。

配置

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

ansible.cfg

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

deprecation_warnings = False

这些警告很重要,所以我稍后会重新审视它们,并弄清楚我需要做什么。但现在,它们不再使屏幕混乱,并掩盖了我实际上需要关注的错误。

hosts 文件

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

主机可以列在任何组之外,但组可以帮助识别具有一个或多个共同特征的主机。组使用 INI 格式,因此服务器组看起来像这样

[servers]
server1
server2
...etc.

主机名必须存在于此文件中,Ansible 才能对其进行操作。即使某些子命令允许您指定主机名,除非主机名在 hosts 文件中,否则命令将失败。一个主机也可以列在多个组中。因此,server1 除了 [servers] 组之外,也可能是 [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。还有许多可下载的模块可以安装,但内置模块已经满足了我当前项目的所有需求。

Playbook

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。我见过 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 行以彩色显示。在我的主机上,使用我的琥珀色底黑色的终端颜色配置,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 环境并不容易。
我开始将内容拆分到环境(清单和 playbook)中,这些环境位于单独的 Git 存储库中。我也将我的角色放入单独的存储库中。很快我就必须研究 AWX/Tower,以允许其他人运行我的 playbook,而无需在其机器上设置 Ansible 或强迫他们进入“Ansible 服务器”上的命令行。

怀疑您已将配置文件置于版本控制之下。将存储库拉取到主机上的暂存位置,并在后续任务中硬链接和/或复制文件到最终位置怎么样。

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

顺便说一句,这也是我写少量评论的原因之一。在大多数情况下,我可以在任务名称中说明我的意图和原因。

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

Blocks 也非常适合错误处理。不要留下半损坏的配置。

如果一个 block 变得非常大和/或在其他 playbook 中有用,我会将其拆分到一个角色中。

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

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

谢谢!

回复 作者 Dominik Riva

© . All rights reserved.