RCHE-学习指南-全-

RCHE 学习指南(全)

原文:Red Hat Certified Engineer (RHCE) Study Guide

协议:CC BY-NC-SA 4.0

一、理解 Ansible 和红帽 RHCE

Red Hat 的企业 Linux 解决方案和随后的认证计划一直处于领先地位。对于 Linux 管理员来说,认证的顶峰一直是 RHCE,即红帽认证工程师。随着 Red Hat 收购 Ansible,他们再次引领潮流,将配置管理作为管理工作和新 RHCE 的重点。企业要赚钱,变得更有效率;通过使用 Ansible 这样的配置管理系统,一个管理员现在可以做十个管理员一样的工作。重要的是,你要成为那十分之一的人,你要在 Ansible 中学习和认证。

Note

“ansible”这个词是由作家厄休拉·K·勒·古恩在她 1966 年的小说《罗卡农的世界》中首次使用的。作为“answerable”一词的缩写,它指的是可以通过星际距离向管理系统发送消息的虚拟设备。Red Hat 的 Ansible 可能无法跨越星际距离工作,但它确实可以管理通常位于地球上的设备。

在第一章中,我将带您踏上 Ansible 的 RHCE 之旅,并向您介绍红帽认证和 Ansible 产品。如果你想跟随你自己的实验室系统,让你获得所有重要的实践,这对我来说将是令人惊奇和荣幸的。理解了这种需求,我将解释我在整本书中使用的系统,以及你完成自己的练习至少需要什么。你当然可以用这本书作为学习指南,但更重要的是,你可以用这本书来学习 Ansible。我在这里给你的信息不仅适用于考试,也适用于现实生活。可以理解的是,考试将只关注适用于您的托管设备的 Red Hat Enterprise Linux。利用其他 Linux 发行版,我也使用 Ubuntu 系统,即使我们没有到达星际距离,也允许你利用 Ansible 的真正力量。

红帽和 Ansible

位于北卡罗来纳州罗利的红帽公司在 2015 年收购了 Ansible。Ansible 最初由 Michael DeHaan 编写,是一个无代理配置管理系统,可用于管理 Linux、Unix、Microsoft Windows 和托管网络系统。从安装了 Ansible 的系统 Ansible controller 管理您的房地产,意味着您可以更轻松地管理更多系统。Ansible 记录并强制执行配置,是确保您满足企业和法规遵从性要求的完美之选。在一个充满不确定性的世界里,拥有每次都能以指定的配置可靠地快速部署系统的敏捷性绝对是一种天赋。Ansible 是免费开源的;部署 Ansible 来管理您的系统并提高您的效率是没有成本的。

基于 Linux 的其他配置管理系统包括:

  • PuppetLabs 的木偶

  • 来自 chef.io 的厨师

  • 盐堆中的盐

红帽认证

正如我前面提到的,Red Hat 处于 Linux 认证的最前沿,拥有最令人满意的可信度和认可度。参加考试将以实际的方式测试您的知识,为您提供实时系统来配置到所需的状态。认证从 RHCSA 开始;通过之后,你就可以坐 RHCE 了。

瑞沙

红帽认证之旅的起点是红帽认证系统管理员,通常称为 RHCSA。测试您的 Linux 管理技能,目前在 Red Hat Enterprise Linux 8 中,您可以向自己和世界证明您是最优秀的。您需要演示文件系统、用户、权限、网络等的管理。您可以通过为期九天的课堂培训获得这些技能,这些培训基于为期五天的课程 RH124,以及随后为期四天的课程 RH134。有了这些知识,你将会在考试中证明你的技能。

断续器

最新版本的红帽认证工程师于 2019 年推出,已经获得 RHCSA 认证的潜在候选人在与 Ansible 和红帽企业 Linux 8 的比赛中发挥他们的技能。他们通过将目标系统配置为考试开发者所设计的理想状态来实现这一点。你的任务变得更简单,因为你将从本书或为期四天的 RH294 课程课堂培训中获得必要的技能。使用 Ansible,您可以快速地将受管设备配置到所需的状态,通常是通过称为剧本的 YAML 文件,或者偶尔是在 Ansible 控制器的命令行执行的特别命令。

实验室系统

在本书中,我们将使用 CentOS 8,它是 Red Hat Enterprise Linux 的免费重建版。在考试中,你会被要求使用红帽,但 CentOS 和红帽是直接可比的。除了这个基于 Red Hat 的发行版,我们还将使用一个基于 Debian 的发行版,Ubuntu 18.04。这可以让你更多地了解 Ansible,以及我们如何轻松地将多个发行版集成到我们的 Ansible 管理中。Ansible 和大多数配置管理系统与底层操作系统无关。我们要求在不考虑如何实现的情况下进行配置。利用系统变量或事实,我们可以从 os_family 事实中确定 os,并修改任何操作以满足目标 OS 的需求,例如不同的包或服务名。如果您可以使用多个发行版,您对 Ansible 的学习将会得到加强,但是如果您受到可用系统的限制,您必须至少拥有一个 CentOS 8 系统。

Note

我们用的是两个 CentOS 8 系统和一个 Ubuntu 18.04 系统。

这些系统可以采取任何形式;您只需要拥有对它们的完全管理权限。这些可以是物理系统、您托管的虚拟系统,或者在云中托管的虚拟系统。由于这本书将在几个月内完成,我将使用托管在 MacOS 的 VMware Fusion 中的内部虚拟机,而不是云。如果你能在几周内完成你的学业,使用基于云的系统是一个好主意。事实是,所使用的虚拟化引擎与 Ansible 无关。你需要使用 CentOS 8 作为你的 Ansible 控制器;这是唯一一个需要添加软件的系统,因为 Ansible 不需要被管理设备上的代理。您将从控制器管理的每个设备都需要能够通过 TCP 端口 22(SSH)上的网络被控制器访问。理想情况下,您的实验室环境应该将受管设备托管在同一网络上,但这不是必需的。

在我的每个实验室系统上,我将总是创建一个名为 tux 的帐户;该帐户应添加到发行版的管理员组中。这是 CentOS 的组和 Ubuntu 的须藤组。

在 CentOS 8 上安装 Ansible

你将被期望在考试中使用红帽企业版 Linux 8;在这里,我们使用的是它的重建版本(例如,8 度音程)。我们只需要在 CentOS 8 系统上安装 Ansible。Ubuntu 系统不需要安装额外的软件。这个系统成为我们的可靠控制器,很有可能成为您在企业中的 Linux 工作站。我们将使用未安装 GUI 的 CentOS 8 服务器。如果您要开始实验室的全新安装,对于课程的大部分时间来说,最低限度地安装带有 1GB RAM 和 20GB 磁盘的 CentOS 8 服务器就足够了。

首先,在我们将用作 Ansible 控制器的 CentOS 8 系统上安装 EPEL(Enterprise Linux 的额外产品)存储库。我们就是在这个系统上安装 Ansible 的,它是唯一一个需要明确安装 Ansible 软件的系统。

$ sudo yum install -y epel-release
$ sudo yum update -y epel-release

Listing 1-1Adding the EPEL Repository on the CentOS 8 Controller System

安装然后更新将确保我们拥有最新版本的 EPEL 库。较新的版本通常存在于存储库中。对于 CentOS,我们可以从这个存储库安装 Ansible。如果使用 Red Hat,必须通过 subscription manager 启用存储库。这不是你在考试中可能会涉及到的任何事情。我敢肯定,不得不处理候选人订阅不是红帽会想参与只是为了考试。

接下来我们可以安装 Ansible。这不是一项困难的任务;让我展示给你看。

$ yum install -y ansible

Listing 1-2Install Ansible on CentOS

安装 Ansible 后,我们可以花点时间检查一下我们安装的版本。

$ ansible --version
ansible 2.9.15
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/tux/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.6/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.6.8 (default, Apr 16 2020, 01:36:27) [GCC 8.3.1 20191121 (Red Hat 8.3.1-5)]

Listing 1-3Printing the Version of Ansible

查看输出,我们可以看到 EPEL 存储库的版本是 2.9.15。在考试中,你最有可能使用 2.8.x,但应该没有什么不同。

在 Ubuntu 18.04 上安装 Ansible

重要的是,或者至少我觉得是重要的是,我们也要学习如何在另一个发行版上安装 Ansible。尽管 Ubuntu 不会在考试中使用,但在现实世界中,你可能想在一个发行版而不是基于 Red Hat 的发行版上使用 Ansible。我将指导您在 Ubuntu 18.04 上安装 Ansible,但在本课程中,我们将只使用 CentOS 8 系统作为控制器。在余下的课程中,Ubuntu 系统将仍然是一个被管理的节点,不会在这些节点上安装 Ansible 包。

首先,Ansible 在标准的 Ubuntu 库内;但是,这是一个较旧的版本,显示为版本 2.5。虽然这样可以,但并不是真的可取。我们可以直接从 Ansible 添加软件库,以便稍后安装一些东西。在基于 Debian 的系统中,这些额外的库是 PPAs 或个人包档案

$ sudo apt install ansible sshpass

Listing 1-5Install Ansible Where We Add sshpass Also

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

Listing 1-4Adding the PPA to Ubuntu

您也可以将此信息复制到可回复的文档中: https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installing-ansible-on-ubuntu

这将安装 Ansible 自己的最新版本。检查版本,我们看到这是目前在 EPEL 相同的版本。

$ ansible --version
ansible 2.9.15
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/tux/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.17 (default, Sep 30 2020, 13:38:04) [GCC 7.5.0]

Listing 1-6Checking the Version Install on Ubuntu

记住,我们将使用 CentOS 8 系统作为控制器;这台 Ubuntu 主机和另一台 CentOS 8 系统将是被管理的设备。在这个阶段,我提醒您,如果您缺少对系统的访问,那么只使用控制器来运行大多数任务是可能的。但是,如果我们可以用一个命令配置多个系统,总体效果会更令人印象深刻。如果你能运行两三个系统,那就更好了。

摘要

你知道吗?你太棒了。你现在知道 Ansible 是什么了,你明白你成为一个 RHCE,一个认证的行政神的道路了!最重要的是,你知道你会通过大量的练习达到目的。我猜你是如此热情,以至于你已经在建立你的三个实验室系统了。是的,没错:两个 CentOS 8 系统和一个 Ubuntu 18.04 系统。没有一个系统需要 GUI 桌面,这也意味着每个系统上的资源非常少。老实说,1GB 的 RAM 和 20GB 的磁盘对于每个系统来说,在控制台模式下运行是绰绰有余的。对于一个章节,我们将需要控制器上的 2GB 内存。

您现在应该已经在 CentOS 8 系统上安装了 Ansible。这将作为您的责任控制器。Ansible 是一个无代理的配置管理系统,其优势在于无需向您管理的设备添加支持软件或客户端。您还了解了如果没有 CentOS 8,可以在其他系统上安装 Ansible。无论哪个系统是控制器,Ansible 的行为都是一样的。在考试中,你应该熟悉使用 CentOS 8 或 RHEL 8。

二、将 Ansible 用于配置

Ansible 的配置文件控制系统配置实用程序 Ansible 如何在被管理设备上运行。这可能包括权限提升的方式以及连接到受管理设备时使用的用户帐户。这些需要在你参与的每个项目中都是一样的吗?个人管理员或开发人员应该控制他们自己的配置吗?这些都是很好的问题,也是我们将在这里尝试回答的问题。让我们看看配置的内容,什么可以进入一个 Ansible 配置文件,以及可以进行的配置的层次结构和它们的搜索顺序。

Note

Ansible 的有效配置可以从命令 ansible - version 中确定。从您将为项目执行其他 Ansible 命令的目录中运行此命令。

Ansible 配置层次

与 Ansible 一起安装的默认 Ansible 配置文件的完整路径是: /etc/ansible/ansible.cfg 。Ansible 只需要安装在 Ansible 控制器主机系统,我们的主 CentOS 8 盒子上。通过阅读 version 选项的完整输出,我们可以看到基于当前工作目录和 shell 变量的有效 Ansible 配置。

$ pwd
/home/tux
$ ansible --version
ansible 2.9.15
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/tux/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.6/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.6.8 (default, Apr 16 2020, 01:36:27) [GCC 8.3.1 20191121 (Red Hat 8.3.1-5)]

Listing 2-1Listing the Current and Default Location for the Ansible Configuration

紧接着 Ansible 版本号,我们看到了配置路径指令。这个显示为 /etc/ansible/ansible.cfg 。如前所述,这是文件的默认位置。它也是后备位置和配置层次结构的最底层。在 /etc 结构中拥有一个中心位置将会是一个非常规范 Ansible 配置。需要拥有控制器的管理权限来修改 /etc 中的文件,您不会期望每个人都编辑这个文件。如果没有其他文件可以使用,那么这些设置对于控制器上的每个 Ansible 项目都是相同的。现在,我无法反驳这一点,因为我不知道你正在进行的 Ansible 项目;然而,通常需要更大的灵活性,并且通常每个项目的配置比集中配置更可取。就责任而言,权力下放是王道。

最先发现 最先应用的基础上应用有效可行配置。请务必注意,只能有一个配置处于活动状态并被应用,并且这些配置不会累积。搜索顺序显示在下面的项目符号列表中,从列表的顶部到底部进行搜索。效率最低的配置是列表底部的 /etc/ansible/ansible.cfg

  • ANSIBLE_CONFIG :如果设置了环境变量 ANSIBLE_CONFIG ,则使用该配置。默认选项用于任何未设置的配置选项。这种默认行为在所有配置中都很常见。

  • ansible.cfg :如果当前工作目录(CWD)中有一个 ansible.cfg 文件,并且 ANSIBLE_CONFIG 环境变量没有设置,那么使用的就是这个文件。

  • ~/.ansible.cfg :如果没有检测到之前列出的配置,ansible 将在当前用户的主目录中检查一个名为 .ansible.cfg 的隐藏文件。如果该文件存在,那么它将成为层次结构中的第三个选择。对于用户来说,这是一个很好的选择,可以作为所有用户项目的默认选项,除了那些需要稍微调整的项目。那些需要调整的可以将配置文件添加到项目目录中;或者,正如您将了解到的,变量可以设置为覆盖某些选项,就像 Ansible 剧本中的设置一样。因此,有很多选项可以根据需要调整配置。

  • /etc/ansi ble/ansi ble . CFG:没有其他配置或没有检测到其他配置的默认文件。文件本身只包含注释,这意味着文件中没有有效的设置。但是不要绝望;这将导致默认设置应用于所有设置。文件本身并没有被浪费,它为您可能想要实现的配置文件提供了很好的文档。

作为如何构建层次结构的简单演示,我们可以从配置树的底部开始向上将文件添加到它们的位置。显然,树的底部已经就位,正如我们在最初的ansible --version输出中已经看到的。默认的 ansible.cfg 和 ansible 一起安装。

只有这个默认文件存在,您可以肯定它将被使用。使用 awesome 命令grep,我们可以过滤结果,只看到我们感兴趣的行。

$ ansible --version | grep 'config file'
  config file = /etc/ansible/ansible.cfg

Listing 2-2Listing the Default Configuration Location

当用户的主目录中存在隐藏的 Ansible 文件时,如果后面的层次结构中不存在其他文件,它将是有效文件。通过添加 $HOME/.ansible.cfg 文件,我们可以看到我们如何开始提升层次结构。

$ touch ~/.ansible.cfg
$ ansible --version | grep 'config file'
  config file = /home/tux/.ansible.cfg

Listing 2-3Adding a Configuration to the Home Directory

通过将一个 ansible.cfg 文件添加到当前工作目录,我们可以看到它接管了有效的设置。在没有 ANSIBLE_CONFIG 环境变量的情况下,CWD 中的 ansible.cfg 是 ansible 命令的有效配置。在下面的代码清单中,您将看到在我们的主目录中创建了一个新目录。我们进入新创建的目录,并创建新的空文件 ansible.cfg 。虽然这个目录是任何 Ansible 命令的工作目录,但是在没有变量的情况下,这个文件用于配置。

$ mkdir $HOME/ansible
$ cd $HOME/ansible
$ touch ansible.cfg
$ ansible --version | grep 'config file'
  config file = /home/tux/ansible/ansible.cfg

Listing 2-4Adding a Configuration to the Current Directory

Important

对于 Ansible 安全性来说,绝不从全局可写的目录中加载配置文件是非常重要的。如果一个目录是全局可写的,(其中 others 拥有写权限),另一个用户可能会有意或无意地将一个恶意的 ansible.cfg 文件添加到您的工作目录中。

为了演示我们可能面临的安全问题,我们现在将更改刚刚创建的 $HOME/ansible 目录的权限,添加全局可写权限。一旦我们证明了理论是正确的,我们就恢复目录上的权限,使 Ansible 能够从目录中读取配置。

$ cd $HOME/ansible
$ chmod -v 777 $HOME/ansible.
mode of '/home/tux/ansible' changed from 0775 (rwxrwxr-x) to 0777 (rwxrwxrwx)
$ ansible --version | grep 'config file'
[WARNING]: Ansible is being run in a world writable directory (/home/tux/ansible), ignoring it as an ansible.cfg source. For more
information see https://docs.ansible.com/ansible/devel/reference_appendices/config.html#cfg-in-world-writable-dir
  config file = /home/tux/.ansible.cfg
$ chmod -v 775 $HOME/ansible
mode of '/home/tux/ansible' changed from 0777 (rwxrwxrwx) to 0775 (rwxrwxr-x)
$ ansible --version | grep 'config file'
  config file = /home/tux/ansible/ansible.cfg

Listing 2-5Test Ansible Security

在配置层次结构的最顶层,我们有一个环境变量, ANSIBLE_CONFIG 。这是配置界的大老板,她说什么很重要;她说话,Ansible 听!

该变量可以在用户的登录脚本中设置,也可以在命令行中动态配置。如果它是由管理员在登录脚本中设置的,那么值得将该变量设置为只读,从而消除用户更改该变量的任何机会。例如,如果我们想强制配置用户家中的 ansible.cfg 文件,我们可以实现变量。

$ touch $HOME/ansible.cfg
$ declare -xr ANSIBLE_CONFIG=$HOME/ansible.cfg
$ ansible --version | grep 'config file'
  config file = /home/tux/ansible.cfg
$ declare -xr ANSIBLE_CONFIG=$HOME/my.cfg
-bash: declare: ANSIBLE_CONFIG: readonly variable
$ unset ANSIBLE_CONFIG
-bash: unset: ANSIBLE_CONFIG: cannot unset: readonly variable

Listing 2-6Using the Variable to Set the Configuration Location and Viewing Read-Only Variables

Note

选项 -x声明设置一个环境变量,(对所有命令可用),选项-r 将变量设置为只读。作为只读变量,不能取消设置或更改。现在,我知道你会习惯于出口的命令;我们可以使用 export 使变量对环境可用,并使用 readonly 命令使变量只读。然而,使用 declare 命令让我们能够在一个命令执行中设置两个选项。

从示例中我们可以看到,当我们遍历层次结构时,我们使用新的配置并忽略以前使用的配置,例如默认的 ansible.cfg 。我们还可以看到,作为管理员,我们可以通过理解在 bash shell 中使用declare命令来强制使用变量的位置。

目前,我们不想使用这个变量;正如我们已经看到的,我们不能取消设置,但是我们可以注销并重新登录到系统中。我们没有在登录脚本中设置它,所以这将有效地清除变量。我们还将从主目录中删除 ansible.cfg 文件,而不是隐藏文件,只是删除 $HOME/ansible.cfg

$ exit
Log back in as tux
$ rm $HOME/ansible.cfg
$ cd $HOME; ansible --version | grep 'config file'
  config file = /home/tux/.ansible.cfg

Listing 2-7Cleaning the Environment, We Should See the Hidden File as the Effective Configuration When Executed with Home as the Working Directory

打印 Ansible 配置

即使我们还没有进行任何配置设置,我们仍然能够打印有效文件的内容,该文件将是空的。我们还可以打印有效设置,即默认设置。为此,我们可以使用命令ansible-config,它有令人惊叹的三个子命令:

  • ansible-config view :打印当前有效可行配置的内容。

  • ansible-config dump :打印生效设置,由生效文件中的显式设置和未设置选项的默认设置组成。

  • ansible-config list :这充分详细地描述了可以通过变量或通过配置文件或剧本中的指令进行的设置。

很容易就能看到这一点,我将向你们展示。不要忘记在您自己的实验室系统上努力工作,您对这些命令的实践越多,它们在您的脑海中就越清晰。考试也是基于实践的,这意味着实践经验对考试和你自己的成功都很重要。

$ cd
$ ansible-config view

Listing 2-8Printing the Current Configuration File Content; This Should Be Empty

视图子命令仅打印当前配置的有效设置;任何注释行都不会被打印。我们可以通过重命名 $HOME/.ansible.cfg 来测试这一点。这将使文件 /etc/ansible/ansible.cfg 再次成为有效的配置,因为我们回到了默认文件。即使文件不是空的,每一行都有注释,所以不会打印任何内容。

$ mv $HOME/.ansible.cfg $HOME/.ansible.old
$ ansible --version | grep 'config file'
  config file = /etc/ansible/ansible.cfg
$ ansible-config view
$ head -n 15 /etc/ansible/ansible.cfg
# config file for ansible -- https://ansible.com/
# ===============================================

# nearly all parameters can be overridden in ansible-playbook
# or with command line flags. ansible will read ANSIBLE_CONFIG,
# ansible.cfg in the current working directory, .ansible.cfg in
# the home directory or /etc/ansible/ansible.cfg, whichever it
# finds first

[defaults]

# some basic default values...

#inventory      = /etc/ansible/hosts
#library        = /usr/share/my_modules/

Listing 2-9Printing the Default Configuration

配置采用 INI 文件的形式,这意味着配置选项被组合在方括号中的节头中。默认文件中的节头没有注释,所以很容易单独打印。这也是使用正则表达式的很好的练习,我们可以和我们的好朋友grep一起使用。我们在查询中使用的正则表达式元字符在下面的列表中列出并解释:

  • ^:行开始于

  • 我们实际上是在寻找以左括号开始的行。我们需要去掉括号,因为它会被解释为正则表达式中一个范围的开始。

  • .*:正则表达式中的句号代表任意字符,星号代表前面字符的任意数量。这样,我们可以说括号可以包含任何数量的任何字符。

  • 再次强调,我们必须避开右括号,因为我们希望它被当作文字而不是正则表达式元字符来读。

  • 使用grep,我们希望打印以方括号内的标题名称指示的部分标题开始的行。

$ grep -E '^\[.*\]' /etc/ansible/ansible.cfg
[defaults]
[inventory]
[privilege_escalation]
[paramiko_connection]
[ssh_connection]
[persistent_connection]
[accelerate]
[selinux]
[colors]
[diff]

Listing 2-10Cataloging the Headers

因此,我们现在已经对配置文件和ansible-config命令有了一些了解。到目前为止,我们只看到了视图子命令。我们现在必须继续,看看转储子命令。这向我们显示了基于显式设置的当前配置,以及那些未设置和使用默认选项的配置。

$ ansible-config dump | head
ACTION_WARNINGS(default) = True
AGNOSTIC_BECOME_PROMPT(default) = True
ALLOW_WORLD_READABLE_TMPFILES(default) = False
ANSIBLE_CONNECTION_PATH(default) = None
ANSIBLE_COW_PATH(default) = None
ANSIBLE_COW_SELECTION(default) = default
ANSIBLE_COW_WHITELIST(default) = ['bud-frogs', 'bunny', 'cheese', 'daemon', 'default', 'dragon', 'elephant-in-snake', 'elephant', 'eyes', 'hellokitty', 'kitty', 'luke-koala', 'meow', 'milk', 'moofasa', 'moose', 'ren', 'sheep', 'small', 'stegosaurus', 'stimpy', 'supermilker', 'three-eyes', 'turkey', 'turtle', 'tux', 'udder', 'vader-koala', 'vader', 'www']
ANSIBLE_FORCE_COLOR(default) = False
ANSIBLE_NOCOLOR(default) = False
ANSIBLE_NOCOWS(default) = False

Listing 2-11Listing the Current Effective Settings

有效配置文件中当前没有设置选项。我们看到的用 dump 子命令打印的每个选项将显示相应的默认配置值。这通过在配置名称后使用(默认)来显示。我们只列出了前十行,但是在我们创建自己的定制配置之前,每个设置都将是自己的默认设置。

尽管 dump 子命令在查看当前有效设置的能力方面非常出色,但是它并没有为配置设置提供任何帮助或解释。为此,我们需要使用列表子命令。来,我们来看看;但是我们将过滤输出,只查看一个设置。输出是冗长的,非常冗长,因此过滤单个设置将更容易看到和理解。

$ ansible-config list | grep -A8 DEFAULT_REMOTE_USER
DEFAULT_REMOTE_USER:
  default: null
  description: [Sets the login user for the target machines, 'When blank it uses the connection plugin''s default, normally the user currently executing Ansible.']
  env:
  - {name: ANSIBLE_REMOTE_USER}
  ini:
  - {key: remote_user, section: defaults}
  name: Login/Remote User

Listing 2-12List All Configuration Settings with Documentation

选项名为DEFAULT _ REMOTE _ USER;这没有默认值,但是当前用户将与 Linux 插件一起使用。可以使用环境变量 ANSIBLE_REMOTE_USER 设置该值,或者使用 defaults 部分标题中的键 remote_user 从配置值设置该值。如果设置了变量,它将优先于配置文件。

Note

这些命令对你很有帮助,因为它们可以在考试中使用。所以,在需要的时候利用这一点,并确保你练习了这些命令,这样你在考试时就会很流利。

创建基本的 Ansible 配置文件

现在,到了这个阶段,您一定渴望开始自己的配置。你的声音已经被听到,这就是你现在要开始学习的。通过在我们的主目录和. ansible.cfg 文件中进行设置,如果没有在工作目录中进行设置,这些可以作为我们自己的默认设置。首先,我们恢复之前重命名的 .ansible.cfg 文件。

$ cd ; mv .ansible.old .ansible.cfg
$ ls -la .ansible.cfg
-rw-rw-r--. 1 tux tux 0 Nov 16 14:42 .ansible.cfg

Listing 2-13Restoring the .ansible.cfg file in Our Home Directory

我们现在可以设置一些配置选项,我们可能希望在多个 Ansible 项目之间共享这些选项。这些设置可能最适合在主目录的 .ansible.cfg 中进行配置。我们首先通过从默认文件中复制它们来确保我们有正确的节标题。

Note

以这种方式添加节头消除了可能出现的打字错误,并且我们有未使用的节也没有关系。

$ grep -E '^\[.*\]' /etc/ansible/ansible.cfg > $HOME/.ansible.cfg
$ vim $HOME/.ansible.cfg
[defaults]
remote_user = ansible ; we will later create this account
inventory = $HOME/inventory ; list of remote hosts
[inventory]
[privilege_escalation]
become = true ; user rights will be elevated
become_method = sudo ; by using sudo
[paramiko_connection]
[ssh_connection]
[persistent_connection]
[accelerate]
[selinux]
[colors]
[diff]

Listing 2-14Creating an Ansible Configuration

Note

开始一个新行的注释可以是分号 (#) 或分号(;)。放置在配置行末尾的行内注释和对该行其余部分的注释必须使用分号,就像我们在本例中使用的那样。

随着我们全新的配置就位并等待使用,我们将能够使用之前的ansible-config命令进行更多的演示。同样重要的是,永远不要假设我们输入到文件中的内容是正确的;一点点测试不会伤害任何人。

$ ansible-config view
[defaults]
remote_user = ansible
inventory = $HOME/inventory
[inventory]
[privilege_escalation]
become = true
become_method = sudo
[paramiko_connection]
[ssh_connection]
[persistent_connection]
[accelerate]
[selinux]
[colors]
[diff]

Listing 2-15Viewing the Configuration

我们必须小心使用这个命令,因为绝对没有检查我们使用的节头或提供的键或值。只要文件与 INI 文件格式匹配,就会打印出来。用dump子命令检查有效设置非常有用,尤其是当我们用--only-changed选项过滤时。加油;我带你去看。

$ ansible-config dump --only-changed
DEFAULT_BECOME(/home/tux/.ansible.cfg) = True
DEFAULT_BECOME_METHOD(/home/tux/.ansible.cfg) = sudo
DEFAULT_HOST_LIST(/home/tux/.ansible.cfg) = ['/home/tux/inventory']
DEFAULT_REMOTE_USER(/home/tux/.ansible.cfg) = ansible

Listing 2-16Viewing Settings Changed from the Default

输出现在也确认了 Ansible 的设置是有效的和可用的。如果键或标题未被识别,则它不会改变任何内容,并且节标题或设置无效。如果我们发现我们在输出中没有看到我们正在寻找的一个或多个选项,很可能我们的胖手指在某种程度上妨碍了完美。

摘要

我认为目前的情况是,你正在成为一个可靠的超级英雄。是的,你——正式成为了一个可靠的超级英雄。但也许我们需要更多地关注考试;毕竟,这是赚大钱的地方。你很快就能通过考试;只需看一看事实,并开始理解您现在可以配置 Ansible 了。您知道应用的配置层次。从上往下开始,我们首先搜索:

  • ANSIBLE_CONFIG

  • ansible.cfg 在 CWD,只要该目录不是全局可写的

  • $HOME/.ansible.cfg

  • /etc/ansible/ansible.cfg

不仅如此,在本章中,您还学习了如何查看和打印配置。首先,您学会了使用ansible --version命令打印配置路径,使用专用命令ansible-config打印设置。我们有三个子命令:查看转储列表,其中ansible-config dump --only-changed可能是最有用的,也是我个人最喜欢的。是的,我的确有胖手指!

当创建我们自己的定制配置时,我们可能会使用注释。在一个新行的开始,我们可以用#或#来注释整个行;。然而,如果我们需要注释一行的其余部分,我们只能使用分号。我们使用这些知识在我们的主目录中创建了一个简单的配置。因此,当然,文件名被隐藏并创建为 .ansible.cfg ,并添加我们可以跨项目使用的设置,用特定项目自己的基于项目的配置或环境变量覆盖它们。现在,我们可以继续深入了解如何创建我们在配置中已经引用过的主机清单。

三、创建一个 Ansible 库存

在我们的 CentOS 8 控制器上使用 Ansible 时,我们可以通过主机列表来定位我们想要直接管理的主机。该列表可作为选项提供给ansible命令。当然,我们可以做得更好。我们希望创建一个基于文件的持久主机列表,而不是临时列表。该列表是一个 Ansible 清单,在该文件中,我们还可以根据地理位置、功能或操作系统来定义组,这样就可以轻松地确定特定的目标主机。无论我们是直接在命令行中工作还是从剧本中工作,清单文件都提供了我们定位主机时所需的一致性。

Note

我知道我们还没有推出 Playbook,但别担心,我们很快就会推出。同时,剧本是以 YAML 格式编写的文本文件,它描述了应该在受管设备上执行的任务:如果您愿意,可以说是要完成的工作的清单。

创建库存

在前一章中已经为 Ansible 创建了一个配置,我们几乎可以肯定地继续使用 Ansible 管理主机列表和创建清单中的下一个任务。在 Ansible 配置中,库存文件位置的项目名称是 DEFAULT_HOST_LIST 。使用grep,我们可以使用ansible-config list的输出显示该设置的文档帮助。

$ ansible-config list | grep -A10 DEFAULT_HOST_LIST
DEFAULT_HOST_LIST:
  default: /etc/ansible/hosts
  description: Comma separated list of Ansible inventory sources
  env:
  - {name: ANSIBLE_INVENTORY}
  expand_relative_paths: true
  ini:
  - {key: inventory, section: defaults}
  name: Inventory Source
  type: pathlist
  yaml: {key: defaults.inventory}

Listing 3-1Gaining Help in the Host List or Inventory

我们应该从输出中注意到的第一件事是这个键的默认值;就是文件 /etc/ansible/hosts 。虽然这个文件没有包含任何有效的条目,但是每一行都被注释了,它确实提供了很好的有用的例子。当您刚接触库存和库存组时,这是一个很好的起点。默认文件是 INI 格式,但是,正如我们将在后面看到的,如果我们愿意,我们也可以使用 YAML 格式的库存文件。为了显示这个文件中的例子而不显示其他注释行,我们可以查找以双注释开头的行;除此之外,我们可以通过将输出传送到tr命令来删除显示的注释。当我们在命令行中变得有创造力的欲望压倒一切时,我们可以额外使用命令tee将输出显示到屏幕上,并填充我们自己的库存文件。我会给你看,但前提是你要保证在你自己的系统上练习。

$ grep '^##' /etc/ansible/hosts | tr -d '##' | tee ~/inventory
 green.example.com
 blue.example.com
 192.168.100.1
 192.168.100.10
 [webservers]
 alpha.example.org
 beta.example.org
 192.168.1.100
 192.168.1.110
 www[001:006].example.com
 [dbservers]
 db01.intranet.mydomain.net
 db02.intranet.mydomain.net
 10.25.1.56
 10.25.1.57
 db-[99:101]-node.example.com

Listing 3-2Listing the Default Inventory File to Populate Our Own Inventory

Note

如果您想更好地理解前面管道中的命令,那么就构建这些命令。首先列出不带过滤器的文件:

$ cat /etc/ansible/hosts

$ grep '^##' /etc/ansible/hosts

$ grep '^##' /etc/ansible/hosts | tr -d '##'

$ grep '^##' /etc/ansible/hosts | tr -d '##' | tee ~/inventory

$ cat ~/inventory

没有任何工作,(这总是一个很好的开始),我们现在有了一个可以练习的小组清单。对我们来说,这是理解库存文件和我们可以用来查询库存的相关工具的良好开端。我猜所使用的 IP 地址不符合您的网络,它们肯定也不符合我的主机,所以在我们查询清单的初始练习之后,我们将替换这个文件,或者至少是它的内容。

查询库存条目

我们有两个命令可以用来打印库存文件中的条目。这些命令包括ansible命令以及特定的ansible-inventory命令。除了我们在文件中明确定义的任何组之外,我们还有两个内置组:

  • all :是的,你猜对了,组 all 是指清单文件中包含的所有主机。

  • :未分组组是指不包含在文件中任何特定清单组的主机。

**在 Ansible 的大部分时间里,你可能都会用到这个组。我们经常希望针对所有主机;毕竟,这就是为什么你把它们添加到清单中。到目前为止,我从未有过针对未分组组的需求,但是还有时间!首先,让我们确保我们在用户的主目录中工作,我们将检查正在使用的 Ansible 配置。我们希望确保我们使用的库存文件被设置为 $HOME/inventory

$ ansible --version | grep 'config file'
  config file = /home/tux/.ansible.cfg
$ ansible-config dump --only-changed
DEFAULT_BECOME(/home/tux/.ansible.cfg) = True
DEFAULT_BECOME_METHOD(/home/tux/.ansible.cfg) = sudo
DEFAULT_HOST_LIST(/home/tux/.ansible.cfg) = ['/home/tux/inventory']
DEFAULT_REMOTE_USER(/home/tux/.ansible.cfg) = ansible

Listing 3-3Verify the Ansible Configuration

Note

如果您没有看到相同的配置,那么您可以花点时间回顾一下上一章,在那里我们创建了配置文件 ~/.ansible.cfg 。我向你保证,这本书不会消失;我们会在这里等你回来。

使用 Ansible 列出清单主机

准备好 Ansible 配置文件,并确保使用的清单文件是我们创建的文件,我们就可以开始了。列出清单中所有主机的最简单方法是使用ansible命令。使用内置组 all ,每个主机都会被列出。

$ ansible all --list-hosts
  hosts (21):
    green.example.com
    blue.example.com
    192.168.100.1
    192.168.100.10
    alpha.example.org
    beta.example.org
    www001.example.com
    www002.example.com
    www003.example.com
    db-99-node.example.com
    db-100-node.example.com
    db-101-node.example.com

Listing 3-4Listing Hosts with the Ansible Command, Some Output Is Trimmed to Reduce Space Used in This Book, Thereby Not Just Saving Trees but Saving Your Eyes!

我们还可以列出组及其成员;使用 webservers 组而不是 all 组演示了这一点。

$ ansible webservers --list-hosts
  hosts (10):
    alpha.example.org
    beta.example.org
    192.168.1.100
    192.168.1.110
    www001.example.com
    www002.example.com
    www003.example.com
    www004.example.com
    www005.example.com
    www006.example.com

Listing 3-5List Specific Groups with Ansible

如果您还记得,我们有两个内置组。我们已经看到了所有清单主机的列表,现在我们可以看到组未分组,即未包含在命名组中的主机。

$ ansible ungrouped --list-hosts
  hosts (4):
    green.example.com
    blue.example.com
    192.168.100.1
    192.168.100.10

Listing 3-6Listing Hosts That Do Not Exist in Any Named Group

使用 Ansible-Inventory 列出主机

尽管ansible命令非常简单,但是随着课程的进行,我们将开始意识到仅列出主机是有局限性的。通常我们还需要查看库存变量。正是在这样的时候,当我们的需求变得更加复杂时,我们可以依靠ansible-inventory命令。同样,和以前一样,我们可以从列出清单中的所有主机开始。默认的输出是 JSON 格式的,但是我包含了用 YAML 打印的选项,因为它不太冗长。

$ ansible-inventory --list --yaml
all:
  children:
    dbservers:
      hosts:
        10.25.1.56: {}
        10.25.1.57: {}
        db-100-node.example.com: {}
        db-101-node.example.com: {}
    ungrouped:
      hosts:
        blue.example.com: {}
        green.example.com: {}
    webservers:
      hosts:
        www001.example.com: {}
        www002.example.com: {}
        www003.example.com: {}

Listing 3-7Listing All Hosts with the ansible-inventory Command, Some Output Is Trimmed to Reduce Space Used

我们可以看到每台主机的大括号;这是可以显示库存变量的地方,如果设置了库存变量的话。我们目前没有使用任何变量,但是我可以向您展示ansible-inventory命令在列出这些变量时是多么有用。以我自己系统上的一个工作配置为例,我可以首先列出所有带有ansible的主机,然后列出ansible-inventory

Note

以下命令在我的内部 Ansible 控制器上运行,该控制器用于部署 AWS 系统。这些条目目前不在您自己的清单中,但我们将很快在实验室清单中使用变量。

$ ansible all --list-hosts
  hosts (1):
    3.8.123.144
$ ansible-inventory --list --yaml
all:
  children:
    redhat: {}
    suse:
      hosts:
        3.8.123.144:
          admin_group: sudo
          ansible_user: ec2-user
    ubuntu: {}
    ungrouped: {}

Listing 3-8Listing Inventory with Variables, First with ansible and Then ansible-inventory

我们可以看到主机已经配置了两个变量: admin_groupansible_user 。在创建需要管理系统的用户时,将使用 admin_group 变量;该组可能因 Linux 发行版的不同而不同。有些分配使用组,有些使用组。在 AWS 中,您应该连接的默认用户帐户根据图像创建者的不同而不同;在 openSUSE 中,它是 ec2 用户,在 CentOS 中,它是 centos 用户帐户。通过实现一个变量,我们能够满足不同的账户。Ansible 中的变量帮助我们处理这些不同的需求,作为管理员,能够看到变量将帮助我们调试 Ansible 命令和剧本执行的问题。

回到 CentOS 8 控制器作为我们的实验室系统,我们可以使用ansible-inventory列出一个组中的主机,就像我们使用ansible一样。

$ ansible-inventory --graph --yaml dbservers
@dbservers:
  |--10.25.1.56
  |--10.25.1.57
  |--db-100-node.example.com
  |--db-101-node.example.com
  |--db-99-node.example.com
  |--db01.intranet.mydomain.net
  |--db02.intranet.mydomain.net

Listing 3-9Listing Group Membership with ansible-inventory

添加主机和组条目

当我们将主机添加到清单文件时,我们可以使用可解析的主机名或 IP 地址。我们还可以为主机名或 IP 地址添加范围。

# To add www1.example.com, www2.example.com, www3.example.com
www[1:3].example.com
# To add a range of IP addresses
192.168.1.[1:5]

Listing 3-10Adding Ranges to the Ansible Inventory

加入群组时,群组名称将会加入区段标题。例如,要为伦敦添加一个组,我们可以将下面一行添加到清单文件中。组的成员应该列在组部分标题的下面。

[London]

Listing 3-11Adding a Group to the Ansible Inventory

我们还可以充分利用库存文件中的嵌套组。嵌套组是在其他组中列出的组。例如,如果我们在清单中定义了一个伦敦组和布里斯托尔组,我们可以将这些组嵌套在英国组中。关键字 children 用于表示成员是嵌套组。

[London]
server1
server3
[Bristol]
server2
server4
[UK:children]
Bristol
London

Listing 3-12Using Nested Groups in the Ansible Inventory

发现网络上的主机

如果您正在为 VMWare 使用内部 NAT 网络,那么您将知道在该网络上运行的主机数量有限。如果像我一样,您在 NAT 网络上运行的唯一虚拟机是您在本课程中想要的三台主机,那么我们可以创造一些奇迹。我们可以通过端口扫描来做到这一点,首先我们需要安装端口扫描器nmap

$ sudo yum install -y nmap

Listing 3-13Installing the Port Scanner nmap on the Controller

使用 NAT 网络上的端口扫描器,我们可以检测既在网络上运行又在侦听 TCP 端口 22(SSH 端口)的主机。我们将需要 SSH 端口从 Ansible 连接。扫描网络时,请确保您输入了网络的网络地址,但是如果您没有得到授权,请不要扫描网络!

Important

在一些公司中,网络扫描可能会触发警报,因为网络扫描可能是对网络和服务器资源进行网络攻击的前兆。如果这不是您自己的个人网络,您必须事先获得运行扫描的书面协议。我们在示例中运行的命令没有任何危险,但是我们当然会在网络上发现可以被视为识别的服务。

$ sudo nmap  -Pn -p 22 -n 172.16.120.0/24 --open -oG -
Nov 18 16:51:36 2020 as: nmap -Pn -p 22 -n --open -oG - 172.16.120.0/24
Host: 172.16.120.185 ()  Status: Up
Host: 172.16.120.185 ()  Ports: 22/open/tcp//ssh///
Host: 172.16.120.188 ()  Status: Up
Host: 172.16.120.188 ()  Ports: 22/open/tcp//ssh///
Host: 172.16.120.161 ()  Status: Up
Host: 172.16.120.161 ()  Ports: 22/open/tcp//ssh///
# Nmap done at Wed Nov 18 16:51:41 2020 -- 256 IP addresses (6 hosts up) scanned in 5.57 seconds

Listing 3-14Scanning the Network for SSH Servers

我们启动的端口扫描有几个选项,旨在根据我们的需求提供最佳输出。选项如下所示:

  • -Pn:不要一开始就探测主机看它是否启动。由于我们发现的是单个端口,这不会降低扫描速度,而且可能会更准确。

  • -p 22:仅扫描端口 22;我们默认为 TCP。

  • 在我的例子中,我们正在扫描 NAT 网络。

  • --open:仅列出端口打开时的结果,而不是过滤或关闭。

  • -oG -:我们让输出更容易被grep等命令过滤;最后一个破折号表示我们将输出发送到屏幕 STDOUT。

我们看到的结果是可以的,但是,如果您还记得,我们希望从这个输出中创建一个库存文件。这意味着我们需要排除输出中显示的其余数据。我们可以选择命令awk来过滤我们想要的行和我们想要的确切字段。我们希望查找包含 22/open 的行,并且我们希望只返回第二个字段,即网络上主机的 IP 地址。

$ sudo nmap -Pn -p22 -n 172.16.120.0/24 --open -oG - | awk '/22\/open/{ print $2 }'
172.16.120.185
172.16.120.188
172.16.120.161

Listing 3-15Extracting IP Addresses

最后一步是,一旦我们在屏幕上验证了输出,就将它发送到库存文件;如果看起来没问题,就把它发送到 $HOME/inventory 文件。为了确保世界和库存文件一切正常,我们用ansible-inventory列出了文件的内容。

 $ sudo nmap -Pn -p22 -n 172.16.120.0/24 --open -oG - | awk '/22\/open/{ print $2 }' | tee $HOME/inventory
$ ansible-inventory --list --yaml
all:
  children:
    ungrouped:
      hosts:
        172.16.120.161: {}
        172.16.120.185: {}
        172.16.120.188: {}

Listing 3-16Dynamically Creating Our Own Inventory

库存变量

在本章的最后,我们将设置可以与清单一起使用的主机和组变量。变量可以直接添加到标准 INI 风格清单中;然而,当从清单中抽象出来并存储在单独的文件中时,这些变量变得更加清晰。这使得库存文件不那么密集,变量更加模块化。

默认情况下,Ansible 使用本机 OpenSSH 连接到受管设备。OpenSSH 在基于 Linux 和 Unix 的系统上更受欢迎,因为它支持 ControlPersist、Kerberos 身份验证和存储在 ~/中的选项。ssh/config 等跳转主机设置。如果您的控制器系统使用不支持 ControlPersist 的旧版本 OpenSSH,Ansible 将回退到名为 paramiko 的 OpenSSH Python 实现。其他连接方法也是可用的,例如用于 Microsoft Windows 系统的 WinRM。在管理控制器本身时,我们可能还想跳过 SSH 的使用;我们可以使用本地连接。为此,我们可以使用一个分配给控制器主机的变量。正如我们前面提到的,这可以在 INI 文件清单上设置。您需要确定控制器的 IP 地址,以便能够将变量添加到主机。

$ cd
$ ip -4 addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 172.16.120.161/24 brd 172.16.120.255 scope global dynamic noprefixroute ens33
       valid_lft 1425sec preferred_lft 1425sec
$ sed -Ei 's/(172.16.120.161)/\1 ansible_connection=local/' inventory
$ ansible-inventory  --yaml --host 172.16.120.161
ansible_connection: local

Listing 3-17Determine the Controller IP and Configure Variable for Local Connection

虽然这确实有效,而且我们可以给这个主机添加更多的变量,但是你会发现你的库存变得更加密集,不那么容易阅读。将库存与变量分开是一种更整洁的工作方式。让我们使用sed来恢复我们刚刚添加到清单中的设置。

$ cd
$ sed -Ei 's/\<ansible_connection=local\>//' inventory
$ ansible-inventory --yaml --host 172.16.120.161
{}

Listing 3-18Reverting the Inventory

所选主机的大括号现在是空的,表示没有主机变量。我们现在将创建两个子目录;这些需要在与清单文件相同的目录中创建—在本例中,是我们的用户帐户的主目录。创建目录后,一个用于主机,一个用于组,我们可以为需要配置变量的主机或组添加 YAML 文件。

$ cd
$ mkdir {host,group}_vars
$ echo "ansible_connection: local" > host_vars/172.16.120.161
$ ansible-inventory  --yaml --host 172.16.120.161
ansible_connection: local

Listing 3-19Separate Inventory and Variables

Note

分离的变量文件为 YAML 格式;键用一个:和一个<space>与它们的值分隔开。我们可以在这个新创建的文件中看到它:ansible_connection: local,而在 INI 清单文件中,键/值对使用了=符号,所以是ansible_connection=local

摘要

我被你的进步惊呆了。您现在能够有效地配置 Ansible 清单,即我们可以管理的主机列表。不仅如此,您还能够创建用于主机或组的变量。这真的很神奇,你会发现 Ansible 的这个强大基础真的很有用。

在学习本章的过程中,我们还学习了更多的命令行技巧,这些技巧可以帮助我们加快 bash shell 的使用。首先,我们添加了一些sed例子来动态编辑文件。流编辑器sed非常有用,工作方式类似于grep;使用sed,我们可以编辑文件,而不仅仅是过滤输出。除了在本章中使用sedgrep之外,我们还看了这两个命令的老大哥awk。其次,在为变量文件创建目录时,我们通过使用大括号用一个命令创建了两个目录。命令mkdir {host,group}_vars将扩展为mkdir host_vars; mkdir group_vars。这些快捷方式可以让你在命令行上更快,时间在考试中至关重要。

您还可以使用ansible --list-hosts命令和ansible-inventory命令来查询库存。如果您只是需要列出组或所有主机,那么使用前一个命令。后一个命令非常适合列出与主机或组相关的变量。**

四、使用临时命令和 Ansible 准备

我开始感到你越来越不耐烦了;是的,它甚至超越了我们之间的时间和距离。您希望学习 Ansible 并获得该产品的大量实践经验。好吧,我有好消息告诉你;你不必再等了。我们即将释放迄今为止对你们隐藏的灼热力量。您将学习如何通过在 Ansible 控制器上执行单个命令来并行配置三个实验系统。临时命令允许我们直接进入 Ansible,而不需要剧本文件。矛盾的是,这使得它们既好又坏。临时命令很好,因为它们可以在需要时快速执行。它们之所以不好,是因为我们执行的命令缺乏与 YAML 剧本相关的可重复的正确属性。有了剧本,文件就是需要执行的任务的持久清单,既记录了配置又实现了任何配置管理系统的涅槃:重复正确。使用特别的命令,我们可以很容易地省略一个必需的配置参数,但是使用剧本不会发生这种情况。每次执行剧本时,我们都会得到相同的结果。我猜你现在已经理解了与 Ansible 相关的术语“ad hoc”。这些命令将根据需要运行,不一定需要重复。

测试 Ansible

尽管我们一直像谚语中的蜜蜂一样忙于创建配置和库存,但我们还没有看到 Ansible 在工作。简单的配置更改是特别命令的核心,它们是通过我们的好朋友ansible命令来执行的。这些命令中最简单的是 Ansible ping 模块。它不是网络 ICMP ping,而是使用 ansible_connect 方法进行连接,以发现受管设备上是否存在 Python 解释器。连接通常是 SSH,但是正如我们所看到的,我们已经将控制器设置为使用本地连接。通过检查我们是否可以在托管主机上针对我们的库存主机运行 ping 模块,我们将能够检查一切是否正常工作,并纠正可能出现的问题。然后,我们可以在本课程的剩余时间里继续将系统配置为所需的状态。

Note

我们假设你作为 tux 用户帐户登录到控制器,他能够使用sudo作为根用户运行所有命令。 tux 账户也应该存在于被管理设备上,并且能够使用sudo运行命令。

$ ANSIBLE_REMOTE_USER=tux ansible all -k -K -m ping
SSH password:
BECOME password[defaults to SSH password]:
         172.16.120.188 | FAILED! => {
    "msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this.  Please add this host's fingerprint to your known_hosts file to manage this host."
    }
         172.16.120.185 | FAILED! => {
    "msg": "Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this.  Please add this host's fingerprint to your known_hosts file to manage this host."
}
         172.16.120.161 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}

Listing 4-1Testing Ansible with the Python Ping Module

绿色好,红色不太好。我们还需要添加一堆交换机,在托管主机的配置完成后,我们可以忽略这些交换机。我们将很快介绍开关,但首先让我们纠正我们看到的错误。对我来说,控制器 IP 地址显示连接成功;失败的是我们使用 SSH 的远程系统。如果我们读了这条信息,我们就能看到原因。是的,不要像被车头灯照着的兔子一样呆立不动——阅读错误信息!我们之前没有使用 SSH 连接到远程系统,也没有将它们的公钥存储为 SSH known_hosts 。这里我们可以采取两种方法:或者使用ssh-keyscan来收集远程密钥,或者,正如我们将要做的,我们可以选择禁用主机密钥检查。在我们的实验室环境中,这是明智的选择。让我们将配置复制到我们的 CWD 中,并根据我们的需要进行调整。

$ mkdir -p $HOME/ansible/setup
$ cd !$
cd  $HOME/ansible/setup
$ cp ~/.ansible.cfg .
$ ansible --version | grep 'config file'
  config file = /home/tux/ansible/setup/ansible.cfg

Listing 4-2Overwriting

the Effective Ansible Configuration

Note

我们使用了!$变量来表示在命令行中使用的最后一个参数,以使更改目录变得更加容易和快速。

编辑这个文件既可以纠正我们看到的错误,又可以提高命令执行的效率。在原始文件中,我们设置了提升特权的选项;对于 ping 模块,我们不需要以 root 身份运行,这意味着我们不需要输入 sudo 密码(-K)。我们还在原始文件中使用远程帐户 ansible ,这个文件我们还没有创建。选择在配置中修改此设置意味着我们不需要用变量覆盖设置。我们不能忘记,我们也应该纠正错误;添加值为 false 的密钥 host_key_checking 将解决这个问题。我们应该编辑文件,使它看起来像这样。对文件所做的更改会突出显示。

[defaults]
remote_user = tux
inventory = $HOME/inventory
host_key_checking = false
[inventory]
[privilege_escalation]
become = false
become_method = sudo

Listing 4-3Modified

$HOME/ansible/setup/ansible.cfg

使用设置become = false也有优势;我们可以使用选项–b来提升特殊命令的权限,但不能反过来。不进行不必要的升级有利于使系统更加安全。用我们喜欢的文本编辑器编辑完文件后,我们就可以开始了。现在,让我们在重新运行 Ansible ping 之前测试一下设置是否有效。

$ ansible-config dump --only-changed
DEFAULT_BECOME(/home/tux/ansible/setup/ansible.cfg) = False
DEFAULT_BECOME_METHOD(/home/tux/ansible/setup/ansible.cfg) = sudo
DEFAULT_HOST_LIST(/home/tux/ansible/setup/ansible.cfg) = ['/home/tux/inventory']
DEFAULT_REMOTE_USER(/home/tux/ansible/setup/ansible.cfg) = tux
HOST_KEY_CHECKING(/home/tux/ansible/setup/ansible.cfg) = False
$ ansible all -k -m ping
...

Listing 4-4Testing the Configuration Is Effective and Running the Ping

我们现在应该有三个成功的绿色输出,每个主机一个。如果你和我一样使用的是 Ubuntu 18.04 系统,你会看到一个警告,说 Python 解释器被检测为 /usr/bin/python ,而不是 /usr/bin/python3 。我们可以通过添加一个清单变量来设置 Ubuntu 18.04 主机使用 Python 3 来解决这个问题,但我们将回到这个问题。让我们首先确保我们理解在特别命令的命令行执行中使用的选项。以下列表显示了一些命令选项:

  • all:该参数指定我们要定位的清单中的组。我们在这里使用的是 all 组。

  • -k:提示输入 SSH 密码。我们稍后将使用基于密钥的身份验证,因此可以省略这个选项。

  • -K:我们已经忽略了这一点,因为我们不需要升级 ping 模块就能成功。如果我们确实需要升级,理想情况下,用户帐户可以无密码访问 sudo。我们将用一个特别的命令对此进行配置。

  • -b:使用权限提升,即使在配置中没有设置。

  • -m:要执行的 Ansible Python 模块的名称。我们使用 ping 模块。

  • 这里不使用或不需要。我们可以并且经常需要向模块提供参数,这是通过-a 选项提供的。

实施可变库存组

从之前 Ubuntu 18.04 系统生成的关于 Python 解释器的警告中,我们可以开始看到对组的需求。尽管我们可以为该主机设置一个主机变量,但将来我们可能会招募更多的 18.04 主机,现在添加组将节省将来的工作。根据组设置所需的变量将更容易理解,并且可能更准确,因为不会忘记主机。在此阶段,我们将为 CentOS 主机和一个 Ubuntu 组创建组。我们还将为 18.04 主机创建一个组,因为 20.04 版本的 Ubuntu 可能不需要相同的变量设置。我们将使用仿生的 18.04 主机的代号作为组名。组名不应以数字开头。我们可以将仿生组嵌套在 Ubuntu 组中。正如我们现在看到的,这很容易实现。确保您可以确定 Ubuntu 系统的 IP 地址,以便将其添加到正确的组中。

$ cat $HOME/inventory
[bionic]
172.16.120.188
[centos]
172.16.120.161
172.16.120.185
[ubuntu:children]
bionic
$ ansible bionic --list-hosts
  hosts (1):
    172.16.120.188
$ ansible ubuntu --list-hosts
  hosts (1):
    172.16.120.188
$ ansible centos --list-hosts
  hosts (2):
    172.16.120.161
    172.16.120.185

Listing 4-5Modify Your $HOME/inventory File to Include Groups

设置组启用了在即席命令中独立定位这些组的机制。我们不必只使用组 all 。我们还可以使用组来设置所需的变量。在下面,我们首先列出没有配置变量的 bionic 组,然后设置变量,并重新列出该组,然后只针对 Ubuntu 组重新运行 ping 命令。

Note

我们仍然可以用命令ansible-inventory使用--host选项来引用一个组名。

$ ansible-inventory --host bionic --yaml
{}
$ echo "ansible_python_interpreter: /usr/bin/python3" > $HOME/group_vars/bionic
$ ansible-inventory --host bionic --yaml
ansible_python_interpreter: /usr/bin/python3
$ ansible ubuntu -k -m ping
SSH password:
172.16.120.188 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Listing 4-6Implementing Group Variables to Resolve Ad Hoc Issues

使用 CentOS 和 Ubuntu 可以让我们发现在单一发行版中看不到的问题。这一点在实验室系统介绍中提到过,但值得重复一遍,因为我们已经研究了在 Ansible 部署中常见的不同环境中有用的其他选项。这也让我们在课程的早期巩固了对组和嵌套组的理解。

为 Ansible 准备用户帐户

实际情况是,在受管系统上使用一个专用帐户来负责操作,可以提高配置更改的透明度和安全性。到目前为止,我们已经使用了 tux 帐户,我们将需要继续使用这个帐户,直到我们为 Ansible 创建专用帐户。

创建用户

我们将使用用户模块直接创建账户。我们将需要参数来创建用户,使我们能够演示-a选项。我们将用户密码设置为一个参数,它需要是一个加密的散列。我们在创建帐户之前生成这个散列。将加密的密码存储在一个变量中后,我们可以运行这个特别的命令,需要通过使用选项-b来提升特权。我们还需要分别使用选项-k-K提示 SSH 密码和 sudo 密码。创建帐户后,我们可以确认条目存在于 passwdshadow 数据库中。

$ user_password=$(openssl passwd -6 Password1)
$ ansible all -kKbm user -a "name=ansible password=$user_password"
...
$ getent passwd ansible
ansible:x:1001:1001::/home/ansible:/bin/bash
$ sudo getent shadow ansible
ansible:$6$li9wmHhZW/TUHYeX$WzH596QutESoI5j3GYqoqnkSLlN.9VxdnMt5aix7SX18AE.1.3rH25quQU1wLrtg3zwXCNNdlQ8Bm6CenJenL/:18586:0:99999:7:::

Listing 4-7Using an Ad Hoc Command to Create the Dedicated Ansible User Account

允许无密码的 Sudo 访问

使用新创建的帐户时,我们需要提升权限,而不需要添加密码。这有助于简化操作,尤其是当我们希望安排 Ansible 命令在无人值守的情况下运行时。在我们的 Linux 系统上,将一个文件添加到/ etc/sudoes.d/ 目录中,将允许在没有密码的情况下访问sudo。我们创建一个本地文件,然后使用 Ansible copy 模块分发它。我们可以在分发文件之前验证它,以保持 sudoers 子系统的完整性。

$ cd ~/ansible/setup
$ echo "ansible ALL=(root) NOPASSWD: ALL" > ansible
$ sudo visudo -cf ansible
ansible: parsed OK 

$ ansible all -bkK -m copy -a "src=ansible dest=/etc/sudoers.d/ansible"

Listing 4-8Allowing Access Without a Password to sudo

SSH 密钥认证

我们也倾向于使用基于密钥的身份验证,使帐户更加健壮,并减少执行命令时的交互需求。我们需要为我们的用户帐户 tux 生成一个 SSH 密钥对。公钥将需要被分发到远程系统和责任账户。从 tux 用户到 ansible 用户帐户的认证不需要 SSH 的密码。我们将用来分发密钥的 Ansible 模块是 authorized_key 模块,但是首先我们需要为 tux 生成密钥对。

$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/tux/.ssh/id_rsa):
Enter passphrase (empty for no passphrase): (leave blank)
Enter same passphrase again: (leave blank)
Your identification has been saved in /home/tux/.ssh/id_rsa.
Your public key has been saved in /home/tux/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:BdooIqc1yJbyIlEC20U/Xqxvx4k7AGqWasvWeTj64wo tux@controller
The key's randomart image is:
+---[RSA 3072]----+
|o=*o+   ..       |
+----[SHA256]-----+
$ ansible all -bkKm authorized_key -a "user='ansible' state="present" \
 key='{{ lookup('file','/home/tux/.ssh/id_rsa.pub')}}'"
...

Listing 4-9Establishing Key-Based Authentication

三个系统的输出应该是黄色的,表示已经发生了变化。我们几乎完成了我们的配置。不过,我想知道你是否能够想到一个我们仍然可能会遇到的问题。

嗯,这很简单,需要做两个改变。我们需要告诉 Ansible 使用我们已经创建的专用 ansible 账户,我们需要确保控制器允许无密码sudo访问,在那里我们使用来自 tux 用户的本地连接。

配置最终更改

$ cd ~/ansible/setup ; cp ansible tux
$ sed -i s/ansible/tux/ tux
$ sudo visudo -cf tux
tux: parsed OK
$ sudo cp tux /etc/sudoers.d/tux
$ sed -i s/tux/ansible/ ansible.cfg

Listing 4-10Configuring

the tux Account on the Controller System and Reverting the ansible.cfg

我们现在可以通过重新执行之前分发 SSH 密钥的ansible命令来测试这一点。我们现在应该能够排除所有的密码提示。重新执行该命令不会导致任何问题,Ansible 是幂等的,这意味着我们可以多次运行相同的特别命令,并且只有在我们不满足所需的配置状态时才会进行更改。我们应该看到每台主机的绿色输出,表明我们符合配置。

$ ansible all -bm authorized_key -a "user='ansible' state="present" \
 key='{{ lookup('file','/home/tux/.ssh/id_rsa.pub')}}'"
...

Listing 4-11Testing Access Without Interaction

获取模块帮助

我们在这一章做了很大的改进,使用了 pingcopyuserauthorized_key 模块。问题是:你怎么知道存在什么模块,它们接受什么参数?这是另一个简单但很棒的问题,我们可以用ansible-doc命令来回答。在下面的代码示例中,我们首先看到如何打印所有模块;我们还对它们进行了计数,以查看我们正在使用的版本中有超过 3000 个。之后,我们将获得本章介绍的用户模块的帮助。

$ ansible-doc user

Listing 4-13Gain Help on a Specific Ansible Module

$ ansible-doc --list
...
$ ansible-doc --list | wc -l
3387

Listing 4-12List All Ansible Modules

当获得关于某个模块的帮助时,您可以搜索示例以获得如何使用该模块的实用指南。它们包括 YAML 剧本的例子,但是这些很容易适应命令行。我们可以通过添加和删除另一个用户来扩展我们在命令行上使用特殊命令的实践。

$ ansible all -bm user -a "name=fred"

Listing 4-14Adding a Supplementary Test User to Our Systems

当使用用户模块时,如果我们没有指定键,状态被假定为存在。下面的命令与前面的清单相同,我们没有定义状态。那么输出将是绿色的,表明我们符合配置;用户存在。

$ ansible all -bm user -a "name=fred state=present"

Listing 4-15Explicitly Setting the State in the User Module

当删除用户帐户时,我们将状态设置为不存在。我们还可以用 remove=true 键/值对设置删除他们的主目录和邮件假脱机文件。

$ ansible all -bm user -a "name=fred state=absent remove=true"

Listing 4-16Removing a User Account and Their Home Directory

摘要

我们现在真的能使用 Ansible 了,而且用得很好。你已经能够看到你的系统管理有多有效。我们轻松地在三个系统上创建了新帐户,并添加了 sudo 文件和我们的身份验证密钥。我们执行的每个命令都是在三台受管主机上执行的。即使使用我们需要实现的配置,使用 Ansible 执行这些任务也会比单独配置每台主机更快。这仅仅是开始;从这一点开始,收益将呈指数级增长。

在本章中,我们将组添加到我们的清单中,并为 Ubuntu 18.04 的仿生组添加了变量ansi ble _ python _ interpreter。我们还使用带有 false 值的密钥 host_key_checking 在 Ansible 配置中轻松地自动接受 SSH 主机密钥。在我们经常连接到新主机的情况下,例如在敏捷开发运维环境中,该设置是必不可少的。配置和库存就绪后,我们很快就能够扩展 Ansible 的知识,在使用用户模块创建 Ansible 使用的专用帐户之前,我们可以使用 ping 模块检查受管设备上的 Python 解释器。这个用户需要在没有密码的情况下访问 sudo,在使用 authorized_key 模块允许对我们添加的用户帐户进行基于密钥的认证之前,我们使用 copy 模块将 sudoers 文件传送到每个主机。

在本章的最后,我真的希望你正在用 Ansible 建立你对配置管理的信心。不要忘记你的自信是通过实践建立起来的。因此,请在您自己的实验室中练习这些命令,并研究使用ansible-doc命令可以获得的帮助。

五、编写 YAML 和基本剧本

Ansible 中的即席命令棒极了;看看我们是如何轻松地跨所有三个系统创建用户帐户的。然而,我们不能让自己过于沉浸在我们所取得的成就中;临时命令是我们旅程的一部分,但不是目的地。因此,让我们庆祝我们已经取得的成就,但不要休息太久;我们将继续前进,开始理解剧本和 YAML 的基础知识。首先要了解的是缩略词本身:YAMLAin tMarkupLlanguage。它是用于数据处理的,而不是一种标记语言。要理解和掌握的下一个也是最重要的特性是使用重要的前导空格。一个元素的缩进级别决定了它与文件中其他元素的关系。

在本章中,您将学习编写剧本,并受益于每次执行都正确的可重复命令。我们将学习调整 YAML 文件的文本编辑器,帮助我们创建正确的 YAML 语法。我们还将先睹为快,看看 Linux 上的图形编辑器和 Microsoft Visual Studio 代码。记住,Ansible 控制器很可能是一个 Linux 工作站,这使得使用 GUI 编写 YAML 非常合理。请系好安全带,因为我们即将开始你的下一阶段学习。

编写简单的 YAML 剧本

在创建剧本时,我们需要意识到,与 Python 文件一样,我们处理的文件格式中,前导空格非常重要,并且有一定的意义。为了保持文件格式的整洁,我们不使用任何形式的括号来分组相关的代码元素;我们使用缩进级别。你可以查看一个文件,每个元素看起来都与前面的元素对齐。如果一行使用制表符作为缩进,而另一行使用八个空格,则它们不在同一缩进级别。最好使用明确的空格,而不是制表符;使用 tab 键仍然很方便,所以能够配置您的文本编辑器将制表符视为空格是一个很好的学习设置。首先,我们必须了解剧本是由什么组成的。

剧本的要素

我们知道剧本是 YAML 文件,但是它们到底包含什么,尤其是它们必须包含什么?我亲爱的朋友和读者,这很简单:一个剧本将包含至少一个剧本,一个剧本将包含至少一个任务。一个 Ansible 任务与我们可以从命令行执行的每个单独的特别命令相关:

  • 剧本 :包含一个或多个剧本的 YAML 格式文本文件

  • Play :该游戏将包含一个或多个任务,稍后我们将看到其他元素,如处理程序。

  • 任务 :一个任务将代表一个带有参数的模块的执行,参数可以是可选的,也可以是强制的,就像我们使用特殊命令一样。我们在剧本任务中使用的模块与在特殊命令中使用的 Python 模块完全相同。这是优于竞争系统(如 SaltStack 的盐)的一个优势。与状态模块相比,Salt 使用不同的模块进行远程执行(特别命令),在状态模块中,Salt 状态文件与剧本相当。

我们的第一本剧本

学习剧本结构的最好方法是实际上弄脏我们的键盘并开始写一个。这比仅仅讨论剧本应该是什么样子要好得多。因此,让我们利用第一本剧本,看看如何安装软件,这是我们以前没有使用过的特殊命令。我们将继续为项目使用 $HOME/ansible/setup 目录。请使用您最喜欢使用的命令行文本编辑器。我的是vim,但是我们稍后会考虑定制vimnano来很好地配合 YAML。

$ cd $HOME/ansible/setup
$ ansible-config dump --only-changed
DEFAULT_BECOME(/home/tux/ansible/setup/ansible.cfg) = False
DEFAULT_BECOME_METHOD(/home/tux/ansible/setup/ansible.cfg) = sudo
DEFAULT_HOST_LIST(/home/tux/ansible/setup/ansible.cfg) = ['/home/tux/inventory']
DEFAULT_REMOTE_USER(/home/tux/ansible/setup/ansible.cfg) = ansible
HOST_KEY_CHECKING(/home/tux/ansible/setup/ansible.cfg) = False
$ vim software.yml
---
- name: My first play
  hosts: all
  become: true
  tasks:
          - name: Install software
            package:
                    name: bash-completion
                    state: present
...

Listing 5-1Creating Our First Playbook

在我们创建的文本文件中,我们有一个包含一个行动的剧本。反过来,这个剧本只包含了一个任务。

  • 剧本 :剧本就是文档本身。YAML 文档可以选择以三个破折号---开始,相应的文档结束标记是三个点...。遗憾的是,我们没有提到三个朋友,这可能会更有趣一点。一个剧本将包含至少一个剧本。这些表示为一个列表。YAML 的清单项目用单个破折号-表示。

  • 播放 :在每个播放中我们设置可选名称。虽然是可选的,但我强烈建议添加一个名称,既有助于记录文件,也有助于诊断。游戏名作为控制台输出的一部分打印出来。在与播放name键相同的级别,我们为个人播放设置了其他键。这些键必须与name键处于相同的缩进级别。也就是说,用来自name的字母n而不是破折号排队。我们可以看到namehostsbecometasks都是这部剧被纵向排列的元素。在该行动中,hosts键用于将库存主机指向目标。become键用于强制提升特权,类似于将-b选项用于特殊命令。最后,我们有了tasks字典。与存储单个值的标准 YAML 键不同,YAML 字典包含多个键和值,或者像在本例中一样,以单个任务的形式存储键/值对的列表。

  • 任务 :生活在tasks字典下,每个任务需要缩进到同一字典内其他任务的同一级别。我们使用 vim 默认的八个空格作为缩进。每个任务都是任务字典中的一个列表项,因此可选的任务名称以破折号和空格开头。在这个任务中,我们使用了package Ansible Python 模块,类似于我们在一个特别命令中用-m选项指定的模块名称。该模块是一个 YAML 字典,包含一组键/值对。这些键需要缩进以显示它们与包字典的关系。我们与用于缩进这些键的八个空格保持一致。对于模块,我们引用要处理的包的名称和状态。这里我们规定它应该以状态present安装。我们可以使用absentpresentlatest来确保包是最新的。

Note

虽然我们有一个yum模块和一个apt模块,但是使用package模块来管理软件允许 Ansible 不知道操作系统选择最合适的打包程序。尽管我们努力使可翻译剧本不可知,但我们确实需要注意包名,它可能因操作系统而异。群体变量可以帮助我们克服这些差异。在这种情况下,该包在 CentOS 和 Ubuntu 上具有相同的名称。

现在,在这个阶段,您应该在 $HOME/ansible/setup 目录下创建剧本文件 software.yml 。仔细观察我们提到的缩进层次。一旦创建了文件,就可以执行它。您可以选择在执行之前检查语法。我们现在使用的是ansible-playbook命令,而不是我们在临时执行中使用的ansible命令。

$ $ ansible-playbook software.yml --syntax-check

playbook: software.yml
$ ansible-playbook software.yml
PLAY [My first play]
TASK [Gathering Facts]
ok: [172.16.120.185]
ok: [172.16.120.188]
ok: [172.16.120.161]
TASK [Install software]
ok: [172.16.120.188]
ok: [172.16.120.161]
changed: [172.16.120.185]

Listing 5-2Checking Playbook Syntax and Executing Playbooks, Executed from $HOME/ansible/setup

从剧本执行的输出中,我们可以看到,在两个 CentOS 主机上,没有必要进行任何更改。然而,该软件包并不存在,因此安装在 Ubuntu 系统上。所有主机现在都处于所需状态。我们可以对这些主机再次执行相同的剧本,没有一个系统需要安装软件。仔细观察,我们现在可以开始理解为什么我们不应该把游戏或任务的名字看作是可选的。它有助于记录文件和可解析的输出。但是,但是,挺住,还有两个任务;我们只创建了一个任务,却执行了两个任务。Ansible 在玩什么呢!啊,好问题,我很高兴你注意到了。收集事实任务将收集我们可以在游戏中使用的关于被管理设备的事实或信息。这可能是为了决定应该执行哪些任务的逻辑,或者是为了在任务中包含一些事实,例如系统的主机名。如果我们在游戏中不使用事实,我们可以使用游戏键gather_facts: false禁用该任务。

Tip

禁用事实收集将会加快剧本的执行速度,当单个剧本不需要事实时,这是值得的。在一个剧本中有多个剧本将意味着多次执行 gather_facts 任务,每个剧本一个,如果没有禁用的话。当使用多个剧本时,尝试将需要事实的任务组合到一个剧本中。

使用事实扩展剧本

我不知道你的感受,但是我对 Ansible 能为我节省多少时间感到兴奋不已。安装软件对我来说至关重要。我在 AWS 上运行许多在线培训课程。能够部署全新的、干净的 AWS 系统,并为它们配置特定课程所需的包,这是非常宝贵的。我经常为一门课程配置十个或更多的系统,我使用 Ansible 是因为它是无代理的,可以毫不费力地在新系统上工作。顺便提一下,我个人的控制器是一个树莓 Pi,在我英国彼得伯勒的家庭办公室里,它总是开着。

我们可以开始扩展剧本,添加第二个任务来简单地显示托管系统的主机名。为此,我们可以使用debug模块。此外,不要对此过于沮丧;虽然本身没什么用,但是我们可以习惯用事实。

---
- name: My first play
  hosts: all
  become: true
  tasks:
          - name: Install software
            package:
                    name: bash-completion
                    state: present
          - name: Show hostname
            debug:
                    msg: "This host is {{ ansible_hostname }}"
...

Listing 5-3Using Facts in the Playbook

使用debug模块和msg键,我们能够将文本打印到控制台上显示的输出中。变量(包括事实)必须包含在双引号文本字符串中,并用双括号括起来,如代码所示。对于执行此操作的每台主机,我们将看到静态文本和被管理设备的主机名都打印在控制器上。这两个任务都将运行,但只有显示主机名任务会导致动作,因为第一个任务中的软件已经安装。

$ ansible-playbook software.yml
TASK [Show hostname]
ok: [172.16.120.161] => {
    "msg": "This host is controller"
}
ok: [172.16.120.185] => {
    "msg": "This host is client"
}
ok: [172.16.120.188] => {
    "msg": "This host is ubuntu"
}

Listing 5-4Viewing Abbreviated Output from the Debug Module

要列出主机上的所有事实,我们可以使用一个特别的命令和setup模块。这也可以从剧本中运行,但最适合提供快速、一次性参考的临时命令。由于以下示例过于冗长,我们已经将其输出排除在外,但是请务必在您自己的系统上运行该命令并查看输出。

$ ansible ubuntu -m setup

Listing 5-5Listing All Facts from the Targeted Group or Host

安装多个软件包

如果像我一样,您有每个系统都需要的自己喜欢的软件,您会希望确保它们无处不在。我们可以通过软件包模块的单个任务执行来安装许多软件包,创建一个软件包名称列表。我们现在将编辑该文件,以便在初始任务中包含更多的包。

$ cat $HOME/inventory
$ vim software.yml
---
- name: My first play
  hosts: all
  become: true
  tasks:
          - name: Install software
            package:
                    name:
                            - bash-completion
                            - vim
                            - tree
                            - nano
                    state: present
          - name: Show hostname
            debug:
                    msg: "This host is {{ ansible_hostname }}"
...
$ ansible-playbook software.yml
TASK [Install software]
changed: [172.16.120.161]
changed: [172.16.120.188]
changed: [172.16.120.185]

Listing 5-6Installing More Than One Package

Note

以这种方式使用多个包名,我们只使用底层包一次,它类似于yum install curl vim tree,等等。如果我们使用多个任务,额外的时间和资源被使用,因为它变得类似于运行yum install curlyum install tree,等等。我们能够让剧本及其执行更加高效。

改进文本编辑器

当我们看着我们一直在处理的发展中的 YAML 文件时,屏幕和书本上显示它所需的空间都在增加。这很大程度上是因为默认的缩进级别为八个空格。如果我们把这个值设置得小一点,YAML 文件会更容易处理,同时我们还可以使用其他选项来加快编辑速度。首先,我们将看看如何定制nano文本编辑器,因为 CentOS 中的默认设置不允许对 YAML 文件提供任何帮助。对软件. yml 文件的最终编辑应该已经在所有需要的系统上安装了nano。我们将创造一个。nanorc 文件放在控制器上我们自己的主目录中。

$ nano $HOME/.nanorc
set autoindent
set tabsize 2
set tabstospaces

Listing 5-7Creating the $HOME/.nanorc Control File for the Nano Text Editor

通过配置这个控制文件,我们可以使用 return 键返回到之前的缩进层次,从而有效地编辑 YAML 文件。我们将 tab 键设置为使用两个空格,并将制表符转换为空格保存。我们可以通过在我们之前使用的安装目录中创建一个简单的测试手册来测试它。使用列表时,autoindent选项会将光标返回到列表项目破折号使用的级别。我们将需要使用 tab 键缩进两个空格,与列表项对齐,而不是与破折号对齐。这将更改缩进级别,以便下次使用 return 键时将光标定位在正确的级别。这是在 YAML 文件中使用两个空格的制表位的一个原因。

$ cd $HOME/ansible/setup; vim nano.yml
---
- name: Ping
  hosts: all
  gather_facts: false
  tasks:
    - name: Ping hosts
      ping:
...

Listing 5-8Sample Playbook File to Test .nanorc

使用vim,你可能会发现你在编辑时得到了一些帮助。默认情况下,结果可能不是最好的,但我们可以做出巨大的改进。回到我们的主目录,我们可以编辑。vimrc 文件,它看起来类似于所示的例子:

$ vim $HOME/.vimrc
set tabstop=2 shiftwidth=2 expandtab autoindent
set cursorcolumn cursorline

Listing 5-9Sample $HOME/.vimrc File for Editing YAML Files

第一行提供了与前面非常相似的效果。我们之前创建的 nanorc 文件。新的第二行。vimrc 文件在缩进量很大的任何格式中都非常有用,比如我们将要使用的 YAML 剧本。我们突出显示光标所在的垂直列,当前水平线带有下划线。您可能会发现,根据所实现的终端仿真,列突出显示可能在您的终端上不起作用。默认 PuTTY 终端不支持该设置,但可以调整。如果你知道用这些新设置编辑文件时应该看到什么,那就太好了。因此,不考虑费用,the great folk at Apress 包含了以下屏幕截图。这张截图是在编辑之前的 nano.yml 文件时拍摄的。突出显示的列当前位于任务列表中,即破折号。我们可以使用这个可视化工具来确保在整个 YAML 文件中正确使用一致的缩进。随着 YAML 变长,这一功能变得越来越有用。

img/507585_1_En_5_Fig1_HTML.jpg

图 5-1

用 vim 和 new 编辑 YAML 文件。vimrc 文件

全图形用户界面

如果我们为控制器使用图形桌面,我们可以利用非常强大的 ide,集成开发环境来编辑剧本。使用在标准存储库中包含微软的 Visual Studio 代码的 Ubuntu 桌面,并编辑之前展示的剧本,我们可以看到 IDE 的一些好处。

img/507585_1_En_5_Fig2_HTML.jpg

图 5-2

在 Visual Studio 代码中编辑 YAML 文件

摘要

当我们将这一章带入我们专业知识的红色天空时,我们应该回忆起这一章将如何塑造我们的成功和职业生涯。通过 Ansible 剧本,我们可以为新部署和持续合规部署创建可重复的配置。它们以 YAML 格式编写,既记录了配置,又强制符合您描述的需求。

YAML 文档的特征包括用三个破折号显示的文档页眉和用三个句点显示的文档页脚。

  • ---:用三条虚线显示的 YAML 文件标题

  • 类似地,在 YAML,三个句点表示文档页脚。

YAML 的列表用单个破折号显示。我们已经看到了与剧本中的剧本列表和剧本中的任务列表一起使用的列表。我们还看到,我们可以安装多个带有包名列表的包。

package:
  name:
     - bash-completion
     - vim
     - tree
     - nano
  state: present

Listing 5-10List of Package Names Used with the Ansible Package Module

通过使用 YAML 文件,我们已经了解了理解和格式化缩进层次以将相关项目组合在一起的必要性。将我们的文本编辑器配置为将制表符存储为空格有助于确保我们能够更容易、更准确地进行这些配置。

我们使用带有语法检查选项的ansible-playbook命令,而不是使用ansible命令来执行剧本。这两个命令使用相同的清单和可解析配置。考虑将您配置文件设置为不提升权限,因为从一个 Ansible play 中提升权限很容易。通常最重要的提醒是查看 ansible-doc 命令模块的文档,并找出它们的示例部分。

六、使用 Ansible 剧本管理用户

虽然我们已经用一个特别的命令创建了一个新用户,但是我们还没有用剧本做同样的事情。在剧本定义中创建用户意味着那些特别的步骤变得更加规范,并且将在每次执行时以相同的方式发生,重要的是,没有遗漏。即使我们的三台主机拥有专用的 Ansible 用户帐户,我们也可以在新系统上线时使用剧本以一致的方式供应它们。

在本章中,您将学习编写剧本来添加和删除用户,甚至了解我们如何使用一个剧本来创建和删除用户,并使用逻辑来控制执行哪个任务。我们将重新讨论组变量,以迎合 Ubuntu 和 CentOS 之间的差异,并花时间研究用户密码如何工作,以及单向密码哈希加密和可以解密的加密机制之间的差异。作为一门 RHCE 课程,我不希望你错过重要的安全知识,你会从中受益。

管理用户的剧本

我们将从 CWD 开始,保留在 $HOME/ansible/setup 目录中,并开始开发一个通用的剧本来管理一般用户。稍后,我们将开始剧本的工作,以创建专用的 Ansible 用户帐户,从 CLI 以特别的方式复制我们之前所做的工作。与往常一样,文档对于您在考试环境中的学习和快速参考变得至关重要。如果你只是在考试的时候使用文档,那么不要期望很快或熟练地获得帮助。相信我,现在积累丰富的经验将在考试中获得巨大的回报。花点时间阅读由user模块提供的选项的完整列表,您将对如何使用该模块来满足自己的需求有一个大致的了解。

用户模块帮助

首先,你能不能迁就我一下,为了你自己的利益,研究一下在微软视窗操作系统中创建用户时应该使用的模块;这在user模块帮助中列出。一旦你做到了这一点,我希望你能进一步研究帮助,以确定如何为用户禁用基于密码的身份验证。

$ ansible-doc user

Listing 6-1Researching User Module Documentation

Note

阅读完整的用户模块帮助不会花费太多时间。投资于你自己,阅读可用的选项。不过,作为一个提示,用于 Windows 的模块显示在第一段中。要禁用密码,请阅读 password_lock 键上的帮助。你可以用这个和其他选项来练习,观察它们的行为。

创建一致的用户帐户

我们已经为 Ansible 帐户创建了一个专用的用户帐户,但是它是否在每个系统中以相同的方式创建?我猜你的答案要么是“我不知道”,要么是“我想是的”。 " 嗯,那还不够好是吧;是还是不是?这些账户应该是相同的,并且它们很可能在相同的操作系统中是相同的。我们使用了两个发行版,在我们同时使用 CentOS 和 Ubuntu 的地方,一致性不太可能相同,除非为用户设置了每个选项。为了演示用户缺省值的变化,让我们运行一个新的模块来访问 shell,运行一个带有参数的命令。我们可以通过为用户列出 /etc/passwd 文件的第七个字段来列出与每个帐户相关联的 shell。

$ cd $HOME/ansible/setup
$ ansible all -m shell -a "getent passwd ansible | cut -f7 -d:"
172.16.120.161 | CHANGED | rc=0 >>
/bin/bash
172.16.120.188 | CHANGED | rc=0 >>
/bin/sh
172.16.120.185 | CHANGED | rc=0 >>
/bin/bash

Listing 6-2Listing the Default Shell for the Ansible User

这里我们必须做一点检测工作,将 IP 地址引用到操作系统,但是两个 CentOS 主机使用 /bin/bash ,Ubuntu 使用 /bin/sh 。创建一个 Ansible 剧本来配置用户的 shell 将会修改现有的用户,只替换需要更新的字段。

$ vim user.yml
---
- name: Manage User Account
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Create User
      user:
        name: ansible
        shell: /bin/bash
        state: present
...

Listing 6-3Ensuring a Consistent Shell Within a New Playbook

目前在这个剧本中,我们只设置了名称,默认Shell,以及状态键。我们可以省略 state 键,因为 present 是这个模块的默认值,但是我们为什么要这样做呢?包括这个,虽然不是必需的,但是提供了更好的文档,并且使用了 14 次额外的击键(如果我可以计算的话)。执行这个剧本将修改 Ubuntu 中的用户帐户,其中 bash 目前不是用户的默认 shell。

$ ansible-playbook user.yml
TASK [Create User]
ok: [172.16.120.161]
changed: [172.16.120.188]
ok: [172.16.120.185]
$ ansible all -m shell -a "getent passwd ansible | cut -f7 -d:"
172.16.120.161 | CHANGED | rc=0 >>
/bin/bash
172.16.120.188 | CHANGED | rc=0 >>
/bin/bash
172.16.120.185 | CHANGED | rc=0 >>
/bin/bash

Listing 6-4Setting the Default Shell, Ensuring Consistency Across Distributions

我们可以很快理解如何使用剧本可以给我们更准确和一致的结果。即使我们可以使用特殊命令设置完全相同的选项,但是当需要更多选项时,它们就变得不那么方便了。

使用可变循环控件

我们之前也看到了如何在一个任务中指定多个包名。但是,该选项在用户模块中不可用。考虑为什么会这样,我们必须理解底层命令:yum允许多个包,但是useradd不允许多个用户。我们可以使用 Ansible 中的loop控件来克服这个限制。loop任务的一部分,而不是模块的一部分,与其他任务项对齐。特殊变量用作用户模块名称键的值;通过迭代loop控件来填充变量。

$ cd $HOME/ansible/setup
$ vim user.yml
---
- name: Manage User Account
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Create User
      user:
        name: "{{ item }}"
        shell: /bin/bash
        state: present
      loop:
        - user1
        - user2
        - user3
...
$ ansible-playbook user.yml
TASK [Create User]
changed: [172.16.120.188] => (item=user1)
changed: [172.16.120.188] => (item=user2)
changed: [172.16.120.161] => (item=user1)
changed: [172.16.120.185] => (item=user1)
changed: [172.16.120.188] => (item=user3)
changed: [172.16.120.161] => (item=user2)
changed: [172.16.120.185] => (item=user2)
changed: [172.16.120.161] => (item=user3)
changed: [172.16.120.185] => (item=user3)

Listing 6-5Creating Many Users, Edit the Existing Playbook to Support Three New Users

从剧本执行的输出中,我们可以清楚地看到每个系统上三个帐户的创建,为了更加清晰起见,这个输出被稍微简化了。Ansible loop控件可以用于任何模块,是你自己军械库中的一个真正的工具。

删除用户

只需点击几个键,我们就可以轻松地修改剧本,删除那些相同的用户。我们现在将快速删除这些用户帐户,然后继续研究如何更有创造性地使用变量。

$ vim user.yml
---
- name: Manage User Account
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Delete User
      user:
        name: "{{ item }}"
        state: absent
        remove: true
      loop:
        - user1
        - user2
        - user3
...
$ ansible-playbook user.yml

Listing 6-6Deleting Users Using Playbooks

Note

如果您还记得第四章中的,remove: true用于确保用户的主目录以及相关的邮件假脱机和 cron 文件被删除。

在剧本中使用变量和逻辑

回到管理单个用户,我们可以学习如何在创建和删除用户方面变得更有创造性。通过不将用户名硬编码到剧本中,我们可以允许更大的灵活性。我们可能还需要选择创建或删除帐户。运行时传递给剧本的变量可以很容易地允许这种情况发生,您很快就会了解到这一点。

在下面编辑过的剧本中,你会注意到我在单个剧本中添加了两个任务。作为每个任务的一部分,我已经添加了一个读取user_create变量的when子句。请注意,我们没有将变量括在大括号中,因为变量是需要变量的子句的参数。每个任务的用户名来自另一个变量。使用选项-e将这两个变量传递给ansible-playbook命令。

$ vim user.yml
---
- name: Manage User Account
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Create User
      user:
        name: "{{ user_name }}"
        shell: /bin/bash
        state: present
      when: user_create == 'yes'
    - name: Delete User
      user:
        name: "{{ user_name }}"
        state: absent
        remove:  true
      when: user_create == 'no'
...
$ ansible-playbook -e user_create=yes -e user_name=mary user.yml
$ ansible-playbook -e user_create=no -e user_name=mary user.yml

Listing 6-7Building Logic and Choice into the Playbook

使用正确的变量执行剧本使我们拥有了在敏捷开发运维工作环境中经常需要的选择和灵活性。

管理用户密码

为用户设置密码时,我们需要提供密码的加密散列,就像我们需要使用底层的useradd命令一样。密码散列是加密的密码,但是散列是不能被解密的单向加密。我觉得了解这些散列的身份验证是如何工作的以及我们在/etc/shadow 文件中看到的加密密码的元素对您是有用的。

密码元素

存储在/etc/shadow 文件中的密码包含三个元素,允许根据密码哈希进行身份验证。这些元素用美元符号分隔。我们可以使用getent命令为用户提取阴影信息。

  • 加密算法 :密码的第一个元素紧跟在第一个 $ 之后,第二个之前。这里我们有值6,表示我们使用 SHA512 加密来创建散列。值5将使用 SHA256,1 表示较弱的 MD5。

  • :这是一个加盐的密码,意思是给密码增加了一个随机性。SALT 是一个 16 字节的文本字符串,应该随机生成。这里使用的盐紧接在第二个\(之后,第三个\)之前。值为:li9wmHhZW/TUHYeX。SALT 与输入的密码和加密算法相结合来创建密码哈希。如果盐不随机化,密码系统就被削弱了。可能会看到具有相同密码值的用户,可能是默认密码未被更改的帐户。

  • 哈希 :最后的密码跟在第三个 $ 符号后面。这里显示的哈希是:WzH596QutESoI5j3GYqoqnkSLlN.9VxdnMt5aix7SX18AE.1.3rH25quQU1wLrtg3zwXCNNdlQ8Bm6CenJenL/。使用相同的明文密码、SALT 和算法将创建完全相同的哈希,这是密码的加密形式。

$ sudo getent shadow ansible | cut -f2 -d:
$6$li9wmHhZW/TUHYeX$WzH596QutESoI5j3GYqoqnkSLlN.9VxdnMt5aix7SX18AE.1.3rH25quQU1wLrtg3zwXCNNdlQ8Bm6CenJenL/

Listing 6-8Listing a User’s Password

认证用户

密码散列是安全的,因为它使用不可逆转的加密机制。为了对用户进行身份验证,我们必须将根据输入并使用的密码创建的哈希与来自存储的密码和相同算法的 SALT 进行比较。盐和算法都没有加密。我们可以在下面的例子中看到这一点;首先,我们展示了当相同的密码与默认随机生成的 SALT 组合时,每次都会创建一个唯一的散列。然后,我们使用相同的 SALT 值,呈现给我们的是一致的散列。这就是身份验证的工作方式:通过检查是否创建了相同的哈希。

Note

为了简化输出,我们使用 MD5 提供的 128 位加密,而不是更安全的 SHA512 的 512 位。这纯粹是为了减少较小按键所需的显示空间,实际上不会使用。

$ openssl passwd -1 Password1
$1$/EX4F4Hi$YxXViUagixN9DYZ2LvtBM/
$ openssl passwd -1 Password1
$1$7y2QB7Xk$aBdYTlO5vHFY0T61luJeU0
$ openssl passwd -salt 7y2QB7Xk -1 Password1
$1$7y2QB7Xk$aBdYTlO5vHFY0T61luJeU0

Listing 6-9Using OpenSSL to Demonstrate Authentication

通过使用存储的密码中的相同 SALT,如果我们输入相同的密码,产生的散列将是相同的。

在剧本中生成密码

在 Ansible Playbook 中生成密码利用了一个 Python 函数password_hash。这很简单,通过user模块的帮助文档中的 URL 链接来演示。这里的大问题是在他们的例子中使用了静态文本 SALT。这不是您想要做的,因为它将为相同的给定密码创建相同的散列。他们的例子也使用了一个特别的命令,但是这可以很容易地调整到剧本风格。使用debug模块打印到屏幕上,我们可以看到生成的 hash。在下面的例子中,我们展示了一个 Ansible 例子,然后对它进行了调整以使用一个随机 SALT。和以前一样,出于输出简洁的原因,我们将使用 MD5 而不是更安全的 SHA512:

  • 例 1 :使用 mysecret 的静盐

  • 例 2 :使用相同的静态盐,我们可以看到产生了相同的 hash

  • 例 3 :使用随机盐实际上更简单,通过排除password_hash函数的第二个参数。这将为输入的密码生成一个唯一的哈希。

$ ansible ubuntu  -m debug -a "msg={{ 'mypassword' | password_hash('md5', 'mysecret') }}"
172.16.120.188 | SUCCESS => {
    "msg": "$1$mysecret$E0Xe5aWuqhm5pgpi4Epcy/"
}
$ ansible ubuntu  -m debug -a "msg={{ 'mypassword' | password_hash('md5', 'mysecret') }}"
172.16.120.188 | SUCCESS => {
    "msg": "$1$mysecret$E0Xe5aWuqhm5pgpi4Epcy/"
}
$ ansible ubuntu  -m debug -a "msg={{ 'mypassword' | password_hash('md5') }}"
172.16.120.188 | SUCCESS => {
    "msg": "$1$.GAXnycZ$CZGGRTWc..KKqFijwWJpW1"
}

Listing 6-10Using Python to Generate Password Hashes, First with Static SALT and then Random SALT Values

在这个阶段,将这一点添加到剧本中对我们来说是小菜一碟。在本书的后面,您将看到如何保护明文密码值,该值将存储在 YAML 文件中。除了将password键添加到剧本之外,我们还将添加键update_password,这样我们就可以避免重置已更改密码的用户的密码。我们只想设置新用户密码的默认值。

$ vim user.yml
---
- name: Manage User Account
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Create User
      user:
        name: "{{ user_name }}"
        shell: /bin/bash
        state: present
        password: "{{ 'Password1' | password_hash('sha512') }}"
        update_password: on_create
      when: user_create == 'yes'
    - name: Delete User
      user:
        name: "{{ user_name }}"
        state: absent
        remove:  true
      when: user_create == 'no'
...

Listing 6-11Setting Passwords with Playbooks for New User Accounts

现在,我们可以通过使用单一剧本来创建或删除帐户,以及了解管理用户密码的最佳方式,来管理我们的用户帐户。我认为我们已经准备好用剧本创建 Ansible 托管主机的初始设置了。

使用剧本创建托管主机设置

也许你想知道为什么我们在项目中使用目录名 setup 。当我们可以创建一个单一的剧本来运行 Ansible 控制器和受管主机的初始配置时,我们已经发展到了这一步。在新剧本$ HOME/ansi ble/setup/setup . yml中,我们将分阶段构建它,代表我们之前作为特别命令运行过的构建块。

第一个任务是在控制器上为我们自己的用户帐户生成一个 SSH 密钥对。我一直在用用户账号 tux 。这个键只在控制器上需要,我们在游戏的主机键中指定这个键,以前我们使用组 allhosts 键的值应该是一个字符串,因为我们使用的是 IP 地址,所以需要用引号括起来以避免误解。可以使用表示登录用户帐户的 shell 变量 $USER 将用户名自动传递给剧本。假设我们还没有设置无密码sudo访问,我们恢复到提示输入sudo密码。

$ vim setup.yml
---
- name: Manage User Account
  hosts: "172.16.120.161"
  become: true
  gather_facts: false
  tasks:
    - name: Update User
      user:
        name: "{{ user_name }}"
        state: present
        generate_ssh_key: true
...
$ ansible-playbook -K -e user_name=$USER setup.yml
BECOME password:

Listing 6-12Ensuring an SSH Key Pair Exists for the Operator User Account

我想你会同意,这开始看起来非常好;当然,密钥对将会就位,因此不需要做任何更改。接下来,我们要确保我们拥有无密码的访问权限。首先确保你有文件 $HOME/ansible/setup/tux ,作为允许 tux 访问sudo而不需要密码的sudo文件。确保文件中使用的名称代表您在控制器上使用的用户帐户。

$ cat $HOME/ansible/setup/tux
tux ALL=(root) NOPASSWD: ALL

Listing 6-13The tux sudo File

准备好文件后,我们可以将任务添加到 setup.yml 中的现有游戏中。

$ vim setup.yml
---
- name: Manage User Account
  hosts: "172.16.120.161"
  become: true
  gather_facts: false
  tasks:
    - name: Update User
      user:
        name: "{{ user_name }}"
        state: present
        generate_ssh_key: true
    - name: Password-less access for operator
      copy:
        src: tux
        dest: /etc/sudoers.d/tux...
$ ansible-playbook -K -e user_name=$USER setup.yml

Listing 6-14Adding the Task to Allow sudo Access Without Password on the Controller

在我们的控制器上,这个文件已经存在,所以我们应该满足当前的配置。

下一步是将 Ansible 的专用帐户部署到受管设备。我们将需要一个新的剧本,允许我们指定主机组 all 。该剧还将允许我们将 remote_user 键设置为 tux 键,而不是参照 ansible 用户帐户对 Ansible 配置进行更改。此外,我们将为新用户配置组成员身份,使其成为正确管理组的成员。这将需要对库存变量进行调整,并允许对库存命令进行大量检查。

$ echo "admin_group: sudo" >> ~/group_vars/ubuntu
$ echo "admin_group: wheel" >> ~/group_vars/centos
$ ansible-inventory --yaml --list
all:
  children:
    centos:
      hosts:
        172.16.120.161:
          admin_group: wheel
          ansible_connection: local
        172.16.120.185:
          admin_group: wheel
    ubuntu:
      children:
        bionic:
          hosts:
            172.16.120.188:
              admin_group: sudo
              ansible_python_interpreter: /usr/bin/python3
    ungrouped: {}
$ vim setup.yml
---
- name: Manage User Account
  hosts: "172.16.120.161"
  become: true
  gather_facts: false
  tasks:
    - name: Update User
      user:
        name: "{{ user_name }}"
        state: present
        generate_ssh_key: true
    - name: Password-less access for operator
      copy:
        src: tux
        dest: /etc/sudoers.d/tux
- name: Manage Dedicated Ansible Account
  hosts: all
  become: true
  gather_facts: false
  remote_user: tux
  tasks:

    - name: Create Ansible Account
      user:
        name: ansible
        state: present
        groups: "{{ admin_group }}"
        password: "{{ 'Password1' | password_hash('sha512') }}"
        update_password: on_create
        comment: Dedicated Ansible Devops Account
        shell: bin/bash
...
$ ansible-playbook -Kk -e user_name=$USER setup.yml
SSH password:
BECOME password[defaults to SSH password]:

Listing 6-15Creating the Inventory Variables and the new Dedicated Account

虽然我们确实满足了配置需求,但剧本现在真的出现了。有了剧本,我们可以以完全一致的方式配置新主机,而无需额外的工作。接下来,我们现在可以使用authorized_key模块对专用帐户启用 SSH 验证。

$ vim setup.yml
---
- name: Manage User Account
  hosts: "172.16.120.161"
  become: true
  gather_facts: false
  tasks:
    - name: Password-less access for operator
      copy:
        src: tux
        dest: /etc/sudoers.d/tux
- name: Manage Dedicated Ansible Account
  hosts: all
  become: true
  gather_facts: false
  remote_user: tux
  tasks:
    - name: Create Ansible Account
      user:
        name: ansible
        state: present
        groups: "{{ admin_group }}"
        password: "{{ 'Password1' | password_hash('sha512') }}"
        update_password: on_create
        comment: Dedicated Ansible Devops Account
        shell: /bin/bash
    - name: Install Local User Key
      authorized_key:
        user: ansible
        state: present
        manage_dir: true
        key: "{{ lookup('file', '/home/tux/.ssh/id_rsa.pub') }}"
...
$ ansible-playbook -Kk -e user_name=$USER setup.yml
SSH password:
BECOME password[defaults to SSH password]:

Listing 6-16Enabling Key-Based Authentication, Assuming We Are Using the tux Account on the Controller

最后一步是为专用的 Ansible 帐户添加对sudo的无密码访问。对我们来说,我们已经有了账户的文件。我们只需要把最后一个任务加到第二个剧本里。完整的剧本显示在下面的代码块中。

$ vim setup.yml
---
- name: Manage User Account
  hosts: "172.16.120.161"
  become: true
  gather_facts: false
  tasks:
    - name: Update User
      user:
        name: "{{ user_name }}"
        state: present
        generate_ssh_key: true
    - name: Password-less access for operator
      copy:
        src: tux
        dest: /etc/sudoers.d/tux
- name: Manage Dedicated Ansible Account
  hosts: all
  become: true
  gather_facts: false
  remote_user: tux
  tasks:
    - name: Create Ansible Account
      user:
        name: ansible
        state: present
        groups: "{{ admin_group }}"
        password: "{{ 'Password1' | password_hash('sha512') }}"
        update_password: on_create
        comment: Dedicated Ansible Devops Account
        shell: /bin/bash
    - name: Install Local User Key
      authorized_key:
        user: ansible
        state: present
        manage_dir: true
        key: "{{ lookup('file', '/home/tux/.ssh/id_rsa.pub') }}"
    - name: Password-less access for ansible account
      copy:
        src: ansible
        dest: /etc/sudoers.d/ansible
...

$ ansible-playbook -Kk -e user_name=$USER setup.yml

Listing 6-17The Final setup.yml

现在,我们已经完整地记录了使用特殊命令运行的步骤。不仅如此;这些命令是可重复的和正确的,因为它们现在被记录在剧本中。从现在开始,我们可以放弃密码提示,因为我们已经确保了正确的 SSH 密钥认证和对权限提升的无密码访问。

摘要

哇,这是我所有的话。现在已经创建了 setup.yml 剧本,我们可以轻松地添加新的受管主机,而不用担心它们是否准确包含在受管主机中。一切都将像在现有主机上一样进行配置。我们在这一章集中讨论了用户管理,到最后,除了创建了一个真正令人敬畏的 YAML 剧本,你还学到了一大堆。

从 Ansible 中的循环控件开始,我们看到了如何用一个任务管理多个帐户,这导致了 when 子句,我们可以用它来检查一个变量,以确定该任务是否应该运行。这些变量可以是系统中的事实,也可以是传递给 Ansible 的事实。在创建用户时,我们不得不讨论密码和密码散列。这些是无法解密的单向加密文件。我们演示了在密码无法解密的情况下身份验证是如何工作的,这里使用的openssl命令是一个有用的工具。

然后,我们将之前用于配置受管设备的临时命令转移到剧本中,以记录设置,并允许我们轻松引入新的受管主机,而无需记住所需的每个临时命令。这对您来说确实是一个里程碑,您创建的剧本将在您自己的项目中证明对您有用。

七、使用变量和事实

我们已经通过之前使用和学习的例子触及了变量和事实。在本章中,我们可以通过调查可在受管设备上收集的事实来巩固这些知识。这包括检索 IP 地址、主机名和完全限定的域名等项目。以上都是可以在剧本中作为变量使用的事实,要么在子句中控制执行,要么作为键的值。我们将扩展到目前为止所使用的库存变量,以考虑到产品(如 Apache HTTPD 服务器)中出现的包和服务名差异。到本章结束时,您将使用一个任务在 CentOS 和 Ubuntu 上安装 Apache。

收集事实

事实由剧本自动收集,除非通过 gather_facts 键禁用。执行 Ansible Python 模块设置来收集这些事实。从命令中,我们可以看到使用特别命令和设置模块的系统事实。在第一个示例中,我们在过滤第二个示例中的结果之前显示所有事实。

$ ansible all -m setup
$ ansible all -m setup -a "filter='*_distribution_*'"

Listing 7-1Displaying Facts

过滤器通过使用通配符来表示字符范围,就像在命令行 shell 中使用文件通配符一样。当不需要完整正则表达式的能力时,这可能是管道输出到grep的有用替代方法。

打印操作系统信息

在剧本中工作,我们可以显式地运行设置模块,但是只要我们没有用gather_facts: false禁用事实收集,我们将能够使用每个事实作为变量。现在,我们将创建一个新的项目目录,开始查看 CentOS 和 Ubuntu 系统之间的软件升级。

$ mkdir $HOME/ansible/upgrade ; cd $HOME/ansible/upgrade
$ ansible --version | grep 'config file'
  config file = /home/tux/.ansible.cfg
$ ansible-config dump --only-changed
DEFAULT_BECOME(/home/tux/.ansible.cfg) = True
DEFAULT_BECOME_METHOD(/home/tux/.ansible.cfg) = sudo
DEFAULT_HOST_LIST(/home/tux/.ansible.cfg) = ['/home/tux/inventory']
DEFAULT_REMOTE_USER(/home/tux/.ansible.cfg) = ansible
$ vim upgrade.yml
---
- name: Upgrade Systems
  hosts: all
  become: true
  gather_facts: true
  tasks:
    - name: Print Host Details
      debug:
        msg: "{{ item }}"
      loop:
        - "{{ ansible_hostname }}"
        - "{{ ansible_distribution }}"
        - "{{ ansible_distribution_version }}"
...
$ ansible-playbook upgrade.yml

Listing 7-2Creating New Ansible Project to Print OS Details

Note

我们在这里使用了循环操作符,但是您也可以打印一条包含所有变量的消息。对我们来说,我们可以检查一下循环操作符,减少超长行使用的页面宽度。在这一章的后面,我们将看看如何在键中折叠需要的长行。

升级系统

恰好我的 CentOS 8 客户端系统使用的是 8.0,而不是目前可用的 8.2。我确信这只是因为安装客户端操作系统时使用了 ISO 文件,但这也表明在您的环境中使用过时的系统是多么容易。我们可以也将会充分利用这些事实来控制哪些系统需要更新。在以下任务中,我们仅在不等于 8.2 的 CentOS 主机上执行。变量ansi ble _ distribution _ version将它存储为一个文本值,我们将在比较中使用它。回到 upgrade.yml 剧本,我们可以先删除打印变量的原始任务,然后再添加新任务来运行包更新。如果你愿意,欢迎你保留第一个任务;我们这里开门营业。不再需要第一个任务;但是,如果您希望添加新任务并保留原来的任务,剧本仍然有效。

Tip

一个子句可以变得相当长,我们可以理解。通过使用折叠操作符>,我们能够在不影响子句本身的情况下跨越多行。不要忘记将折叠线缩进到从句内部的两个空格处。

$ vim upgrade.yml
---
- name: Upgrade Systems
  hosts: all
  become: true
  gather_facts: true
  tasks:
    - name: Upgrade CentOS
      package:
        name: "*"
        state: latest
      when: >
        ansible_distribution == "CentOS" and
        ansible_distribution_version != "8.2"
...
$ ansible-playbook upgrade.yml

Listing 7-3Updating CentOS Hosts

首次运行本剧本将更新 CentOS 8 客户端系统。第二次运行它,不需要更新,因为两个系统都是正确的最新版本。

更新 Ubuntu 系统,可以研究最新的 18.04 版本,目前是 18.04.5 。我们需要深入研究 ansible_lsb.descripton 变量来了解这一点。以下特别命令演示了 ansible_lsb 数组,该数组在 Ubuntu 系统上默认可用。

$ ansible ubuntu -m setup -a "filter=ansible_lsb*"
172.16.120.188 | SUCCESS => {
    "ansible_facts": {
        "ansible_lsb": {
            "codename": "bionic",
            "description": "Ubuntu 18.04.5 LTS",
            "id": "Ubuntu",
            "major_release": "18",
            "release": "18.04"
        }
    },
    "changed": false
}

Listing 7-4Decting the Full Ubuntu Version

Note

如果我们需要 CentOS 主机上的 ansible_lsb 数组,我们将安装包 redhat-lsb-core 。我们不需要这个包,所以没有安装。

将 CentOS 和 Ubuntu 条件组添加到剧本中现有的 when 子句中,我们将有一个可以更新两组主机的任务。为了控制每组条件的处理,我们用括号将相关元素分组,并用逻辑 OR 操作符将两组带括号的条件组组合起来。编辑后的剧本可供您创建和练习。

$ vim upgrade.yml
---
- name: Upgrade Systems
  hosts: all
  become: true
  gather_facts: true
  tasks:
    - name: Upgrade Older Systems
      package:
        name: "*"
        state: latest
      when: >
        (ansible_distribution == "CentOS" and
        ansible_distribution_version != "8.2") or
        (ansible_distribution == "Ubuntu" and
        ansible_lsb.description != "Ubuntu 18.04.5 LTS")
...

Listing 7-5Upgrading Both Ubuntu and CentOS in a Single Task

我们已经看到,变量可以从库存、从-e选项中读取,事实也可以从系统中读取;然而,我们也可以在剧本本身中定义变量。在这样的例子中,这些变量对我们特别有用。在剧本中尽早定义版本号,可以在发布新版本时根据需要轻松查看和编辑。看看下面例子中更新的剧本,我相信你会明白的。

$ vim ugrade.yml
---
- name: Upgrade Systems
  hosts: all
  become: true
  gather_facts: true
  vars:
    - ubuntu_version: "Ubuntu 18.04.5 LTS"
    - centos_version: "8.2"
  tasks:
    - name: Upgrade Older Systems
      package:
        name: "*"
        state: latest
      when: >
        (ansible_distribution == "CentOS" and
        ansible_distribution_version != centos_version) or
        (ansible_distribution == "Ubuntu" and
        ansible_lsb.description != ubuntu_version)
...

Listing 7-6Setting Variables Inside the Playbook

能够检查剧本中的当前版本集,并在文件顶部轻松更新它是非常方便的,有助于记录强制版本,并且非常容易编辑。我强烈推荐执行剧本来检查你自己的打字已经是典范了!4 个小时的考试飞逝而过,你越快写出准确的 YAML,你为考试做的准备就越充分。

安装 Apache

正如我们所看到的,使用模块,而不是 yumapt ,有助于 Ansible 和我们的剧本保持所有重要的不可知的对操作系统的态度,在所有支持的平台上工作。然而,我们不能迎合包名的差异,这就是库存变量可以帮助我们的地方。

Important

虽然模块非常有用,但是它的简单性是有代价的。对于打包模块,只有很少的选项,因为它必须跨许多不同的打包程序工作。使用底层的 aptyum 模块将为您提供更多的功能,同时失去通用模块的不可知论性质。理解通用模块和特定模块之间的差异非常重要。针对yum 模块的快速ansible-doc可以帮助你理解。

我们现在将创建一个新项目来部署 Apache web 服务器。CentOS 上的包名是 httpd,Ubuntu 上的包名是 apache2。首先,让我们更新库存变量。

$ echo "apache_pkg: httpd" >> ~/group_vars/centos
$ echo "apache_pkg: apache2" >> ~/group_vars/ubuntu
$ ansible-inventory --yaml --list
all:
  children:
    centos:
      hosts:
        172.16.120.161:
          admin_group: wheel
          ansible_connection: local
          apache_pkg: httpd
        172.16.120.185:
          admin_group: wheel
          apache_pkg: httpd
    ubuntu:
      children:
        bionic:
          hosts:
            172.16.120.188:
              admin_group: sudo
              ansible_python_interpreter: /usr/bin/python3
              apache_pkg: apache2
    ungrouped: {}

Listing 7-7Updating the Ansible Inventory Variables to Support Apache Installation

我们也可以选择打印特定主机的变量;这将包括在主机和组级别定义的变量。如果我们选择控制器,我们将能够看到这种行为,因为它是目前唯一具有主机特定变量集和组变量的系统。

$ ansible-inventory --yaml --host 172.16.120.161
admin_group: wheel
ansible_connection: local
apache_pkg: httpd

Listing 7-8Listing Variables Associated with a Specific Host

设置并确认了库存变量之后,我们现在可以继续安装 Apache 的新项目了。

$ mkdir $HOME/ansible/apache
$ cd $HOME/ansible/apache
$ vim simple_apache.yml
---
- name: Install Apache
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name:  Install Apache Package
      package:
        name: "{{ apache_pkg }}"
        state: present
...
$ ansible-playbook simple_apache.yml
TASK [Install Apache Package]
changed: [172.16.120.185]
changed: [172.16.120.161]
changed: [172.16.120.188]

Listing 7-9Creating the New Apache Project

通过这个简单的任务,我们能够在三个系统上安装 Apache。有了好的计划,再加上好的软件和好的管理员,我们就能够战胜摆在我们面前的最艰巨的挑战。我们还需要提醒自己,我们实际上只是安装了软件,并没有配置服务。当我们开始学习剩余的章节时,这就会到来。

摘要

我们现在是可变因素和事实的主人。读到这一章的结尾,你应该为自己的进步感到高兴和自豪。设置模块可用于显示来自我们管理的设备的事实。使用模块的过滤器参数,我们能够钻取我们需要研究的特定项目。如果在游戏中启用了gather_facts,设置模块将自动运行,使变量可供您使用。

当子句需要用双引号括起来并放在双括号内时,在之外使用的变量是很好的度量:

name: "{{ ansible_package }}"

与 when 子句一起使用的变量不需要以同样的方式加引号,但文本字符串需要加引号:

when: ansible_distribution == "CentOS"

这些变量可能来自许多地方。本章使用了库存变量、播放变量以及事实。发现这些变量后,我们能够看到它们在允许灵活执行方面变得多么有用。利用 when 子句允许条件求值来决定一个任务是否执行。我们使用逻辑操作符OR和逻辑操作符AND构建了一个复杂的子句。当子句变长时,我们使用折叠操作>,允许子句中有多个缩进行。

我们还使用本模块复习了我们以前使用过的不想忘记的命令。

$ ansible --version | grep "config file"
$ ansible-config dump --only-changed
$ ansible-inventory --list --yaml
$ ansible-inventory --host 172.16.120.161 --yaml
$ ansible ubuntu -m setup -a "filter=*lsb*"

Listing 7-10Commands Reviewed in This Chapter

八、使用文件和模板

我们已经能够为 tux 和 ansible 用户帐户提供 sudoers 文件,我们很清楚这些文件可以通过 Ansible 分发。虽然这对于某些文件来说很好,但对于许多其他文件来说可能不够。当文件包含许多行和选项时,我们可能更喜欢只修改我们需要的行,而不是整个文件。交付一个完整的文件将提供一个单一的整体解决方案,而我们可以通过配置每个给定场景所需的选项来满足各种需求。在这一章中,我们将探讨如何复制完整的文件,动态创建包含新内容的文件,使用 lineinfile 模块就地编辑文件,以及使用 Jinja 2 模板创建满足更复杂需求的文件。

复制模块

我们已经使用了这个模块,它已经向被管理的设备发送了简单的小文件。这可以是使用模块的 src 参数的完整文件,或者我们可以使用内容参数创建动态内容。

使用 SRC

已经看到了使用 sudoers 文件的 src 参数的使用,我们将对它充满信心。让我们通过向新部署的 web 服务器交付 web 内容来稍微扩展一下。由于 Apache web 服务是在 Ubuntu 上安装之后启动的,所以我们可以使用curl很容易地在该主机上测试部署。我们将向所有主机提供他们需要的网络内容,以向世界推广我们的公司!在复制模块中使用directory_mode: true,我们允许复制完整的目录。

$ cd $HOME/ansible/apache ; mkdir web
$ echo "Welcome" > web/index.html
$ echo "Peterborough, UK" > web/contact.html
$ ls web
contact.html  index.html
$ vim simple_apache.yml
---
- name: Install Apache
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name:  Install Apache Package
      package:
        name: "{{ apache_pkg }}"
        state: present
    - name: Copy web content
      copy:
        src: web/*
        directory_mode: true
        dest: /var/www/html
...
$ ansible-playbook simple_apache.yml
TASK [Copy web content]
changed: [172.16.120.161]
changed: [172.16.120.188]
changed: [172.16.120.185]
$ curl 172.16.120.188 #use ip of ubuntu host
Welcome

Listing 8-1Copy Web Content from Controller to Managed Devices

内容为王

如果文件的内容非常简单,很可能很短,我们可以使用复制模块的内容参数动态创建它。为了演示这一点,我们将为 /etc/motd 文件创建一个新项目。这是一个文本文件,在您登录系统时用作当天的消息。没有人会阅读这条消息,但是我们觉得有必要为我们的用户创建一条消息。不知道为什么;这只是系统管理员的事情之一。

$ mkdir $HOME/ansible/motd ; cd $HOME/ansible/motd
$ vim motd.yml
---
- name: Manage the /etc/motd file
  become: true
  hosts: all
  gather_facts: true
  tasks:
    - name: Copy /etc/motd
      copy:
        dest: /etc/motd
        content: |
          This system is managed by Ansible
          The system name is {{ ansible_hostname }}
          The IP address is {{ ansible_default_ipv4.address }}
...
$ ansible-playbook motd.yml
$ ssh ansible@<ubuntu ip> #login via ssh to the ubuntu or client system
This system is managed by Ansible
The system name is ubuntu
The IP address is 172.16.120.188
Last login: Wed Nov 25 14:16:09 2020 from 172.16.120.161
$ exit #to return to controller

Listing 8-2Delivering the MOTD File with Ansible

不同的折叠运算符

motd.yml 中,我们使用折叠操作符作为|,竖线。之前,我们使用折叠操作符作为大于符号>。那么为什么有两个又有什么区别呢?这些都是很好的问题,我将在这里尝试回答。

  • >:我们在when子句中需要一行代码时使用了这个,即使我们已经扩展了许多行。使用>操作符,换行符被空格替换。

  • |:我们刚刚在 motd.yml 的内容参数中使用了这个。我们希望内容在多行上,并且|操作符维护折叠字符串中的换行符。

就地编辑文件

在许多文件中,我们希望实现或替换被管理设备上已经存在的文件的现有设置。我们可以替换整个文件,但这可能不是必需的,也不一定是我们想要的。很容易想象两个 Ansible 项目需要编辑同一个配置文件并导致冲突。仅更改我们想要的行,允许需要在同一个文件中配置它们自己的独立行的项目共存。除了避免这些配置冲突,我们在交付更改时使用了更少的带宽。

在 SSH 服务器的新项目中,我们将确保不允许 root 用户通过服务登录。我们已经知道 SSH 必须在受管设备上配置,因为我们使用 SSH 连接到远程系统。首先,我们将比较 CentOS 和 Ubuntu 之间的 SSHD 配置差异。我们使用一个特别的命令从 sshd_config 中搜索所需的设置。

$ ansible all -m shell -a "grep PermitRootLogin /etc/ssh/sshd_config"
(CentOS)172.16.120.161 | CHANGED | rc=0 >>
PermitRootLogin yes
# the setting of "PermitRootLogin without-password".
(CentOS)172.16.120.185 | CHANGED | rc=0 >>
PermitRootLogin yes
# the setting of "PermitRootLogin without-password".
(Ubuntu)172.16.120.188 | CHANGED | rc=0 >>
#PermitRootLogin prohibit-password
# the setting of "PermitRootLogin without-password".

Listing 8-3Searching Current SSHD Settings, Annotate the Output with the OS of the Given System

查看(posh word for reading)输出,我们可以看到该设置在 CentOS 中处于活动状态,并允许 root 登录。在 Ubuntu 中,该设置是不活动的,但默认设置仅在不使用基于密码的身份验证时允许 root 登录。我们希望在所有系统上保持一致的设置,防止通过 SSH 进行 root 登录。我们不需要通过 SSH 直接访问 root 帐户,这肯定是不安全的,尤其是对于面向公众的系统。 lineinfile 模块将完成我们编辑该文件所需的工作。

$ mkdir $HOME/ansible/ssh ; cd $HOME/ansible/ssh
$ vim sshd.yml
---
- name: Manage SSHD
  hosts: all
  gather_facts: false
  become: true
  tasks:
    - name: Edit SSHD Config
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PermitRootLogin '
        insertafter: '#PermitRootLogin'
        line: 'PermitRootLogin no'
...
$ ansible-playbook sshd.yml
TASK [Edit SSHD Config] changed: [172.16.120.161]
changed: [172.16.120.188]
changed: [172.16.120.185]
$ ansible all -m shell -a "grep PermitRootLogin /etc/ssh/sshd_config"
172.16.120.161 | CHANGED | rc=0 >>
PermitRootLogin no
# the setting of "PermitRootLogin without-password".
172.16.120.188 | CHANGED | rc=0 >>
#PermitRootLogin prohibit-password
PermitRootLogin no
# the setting of "PermitRootLogin without-password".
172.16.120.185 | CHANGED | rc=0 >>
PermitRootLogin no
# the setting of "PermitRootLogin without-password"

Listing 8-4Editing the SSHD Configuration

Important

我们已经编辑了文件,但是我们还没有重新启动服务,这意味着设置还没有生效。我们将在下一章中修改剧本,以便在文件更改时重启服务。

lineinfile 模块非常强大,所以让我带您浏览用于帮助您理解的参数:

  • 路径 :这个比较简单,要编辑的文件在被管理设备上的路径。

  • regexp :如果该行可能存在,我们可以搜索它,允许替换当前行。

  • insertafter :如果该行不存在,将在文件的末尾或我们在此指定的行之后添加新的一行。如果需要,我们将在注释行之后添加一行。

  • :这是我们规定必须在文件中的行,也是我们想要实现的期望设置。

使用模板

我们已经看到,通过我们之前创建的今日消息(MOTD)文件,使用 content 参数到 copy 模块,将事实和变量传递到文件中是最有可能的。也许这对我们有用,因为我认为,我们只用了两个变量。随着文件需求和复杂性的增加,我们会发现 Jinja 2 模板更加方便。我们将返回到 $HOME/ansible/motd 目录来进一步开发,首先创建模板来存放文本和变量。

$ cd $HOME/ansible/motd
$ vim motd.j2
Welcome to {{ ansible_hostname }}
The system uses:
{{ ansible_distribution }} {{ ansible_distribution_version }}
The IP Address is: {{ ansible_default_ipv4.address }}

Listing 8-5Building a Jinja 2 Template

对于较大的文件来说,模板是一种更方便的方法,因为变量可以放在模板内部,以便于布局。这对于保持剧本的整洁和模板成为变量的焦点很有帮助。我们不使用复制模块,而是使用模板模块来确保变量在运行时被正确渲染。

$ vim motd.yml
---
- name: Manage the /etc/motd file
  become: true
  hosts: all
  gather_facts: true
  tasks:
    - name: Copy /etc/motd
      template:
        dest: /etc/motd
        src: motd.j2
...
$ ansible-playbook motd.yml
$ ssh ansible@172.16.120.185
Welcome to client
The system uses:
CentOS 8.2
The IP Address is: 172.16.120.185
Last login: Thu Nov 26 12:10:05 2020 from 172.16.120.161
$ exit

Listing 8-6Using the Template Module in Playbooks

在模板中放置变量和文本,包括可能的配置项,将允许更复杂的项目,其中设置值可以由变量填充。

摘要

在本章中,我们的目标是成为在 Ansible 中分发文件和模板的禅宗大师。你感觉如何,我是否帮助你实现了目标?

让我们花一点时间让我们所有的情绪安定下来,回忆一下我们的旅程。从本章中使用的五个 Ansible 模块开始:

  • 复制

  • 模板

  • 不插电

  • Shell

当然,我们以前见过模块,但是这次我们看到模块的不可知论性质只延伸到这里。我们需要设置库存变量来分配正确的包名。我们之前也使用过复制模块;不过,这一次,我们看的是内容参数,而不是我们之前使用的 src 参数。使用内容允许在剧本本身中动态定义文件内容。这也意味着我们可以像最初在 MOTD 文件中做的那样呈现变量。在这里,我们还学习了两个折叠操作符|>,前者支持换行的保留,后者将它们转换为空格。

也许这一章中模块之王是 lineinfile 模块,它允许我们编辑或添加单独的行到一个文件中,而不是大规模地替换它。不过,有些人无疑会投票支持模板模块,它扩展了复制/内容的功能,但是将变量和文本存储在 Jinja 2 模板文件中。你认为这一章最有用的特点是什么?一定要让我们知道。

九、使用 Ansible 管理服务

作为一名出色的系统管理员,您需要能够面对 Linux 发行版中的差异,并且微笑着面对它们。我们刚刚安装了 Apache web 服务器;在 CentOS 上,相关的服务在安装后没有启动,而在 Ubuntu 上却启动了。当然,最终,我们希望服务运行在所有的 web 服务器上,而不管发行版本如何。我们不仅面临与服务有关的问题;我们已经使用 lineinfile 模块编辑了所有系统上的 SSHD 配置,但是这些更改在服务本身重启之前不会受到影响。所以,我们的系统仍然处于危险之中。在本章中,我们将通过在 Ansible 中实现服务系统模块来解决其中的一些问题,并为将来的解决方案做准备。我们将了解如何根据需要启动和启用服务,以及停止和禁用我们不需要的服务。至关重要的是,对于影响服务的配置文件中的更改,我们可以在文件状态发生更改时重启服务。这是 Ansible 中的一个新元素,称为处理程序

服务模块

模块非常相似,通用服务模块可以帮助您管理服务,而无需关心底层操作系统。以同样的方式,这对我们既有帮助也有阻碍。这是有用的,因为模块的竞争性质;但是,它对底层服务管理器的特定功能没有帮助。文档ansible-doc service将打印模块的帮助,并说明支持的稀疏参数。不过,它有一些基本的东西,大多数时候我们可以用这个模块来凑合。

系统模块

Ubuntu 18.04 和 CentOS 8 都使用了更新的 systemd 服务管理器。如果我们不确定管理器,那么 Ansible 可以帮助我们发现任何给定主机上的底层服务管理器。这是作为一个可回答的事实提供的,等待我们随时询问,软件包管理器也是如此。让我们在我们所有的系统上使用特别的命令来研究这个问题。

$ ansible all -m setup -a "filter=ansible_*_mgr"
172.16.120.188 | SUCCESS => {
    "ansible_facts": {
        "ansible_pkg_mgr": "apt",
        "ansible_service_mgr": "systemd"
    },
    "changed": false
}
172.16.120.161 | SUCCESS => {
    "ansible_facts": {
        "ansible_pkg_mgr": "dnf",
        "ansible_service_mgr": "systemd",
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false
}
172.16.120.185 | SUCCESS => {
    "ansible_facts": {
        "ansible_pkg_mgr": "dnf",
        "ansible_service_mgr": "systemd",
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false
}

Listing 9-1Interrogating Ansible Facts to Determine Managers

我们可以看到,所有的系统都使用 systemd 作为服务的底层管理器,事实上可以这样看待: ansible_service_mgr 。我们看到的差异包含在 ansible_pkg_mgr 中,其中 Ubuntu 使用 apt ,CentOS 使用 dnf 。使用 systemd 模块,我们可以实现更多功能,例如屏蔽和取消屏蔽服务,这些功能是服务模块所不具备的。主要是我们可以坚持使用不可知的通用服务模块;毕竟,除了启用/禁用或启动/停止服务之外,我们还想对服务做什么呢?百分之九十的时间服务模块就足够了。对于我们最好的和偶尔需要屏蔽或取消屏蔽服务的场合,我们保留了 systemd 模块。

使用 Ansible 处理程序

处理程序是一个类似于任务字典的剧本中的字典列表。顾名思义,它们包含一系列处理程序,而不是任务。简单,真的;一切都在名字里。与任务不同,处理程序只有在被其他任务通知时才会被执行。许多任务可以通知完全相同的处理程序,但是处理程序只会执行一次。如果没有任务通知处理程序,那么它不会被执行。回到我们的 SSH 项目,我们可以让 SSHD 服务在配置文件发生更改时重启。我们将通过实现我们的第一个处理程序来做到这一点。虽然我们知道 SSH 服务必须运行才能与 Ansible 进行通信,但是我们也可以实现一个任务来确保服务同时被启用启动。通过启用,我们的意思是该服务应该在系统启动时自动启动。

$ cd $HOME/ansible/ssh
$ vim sshd.yml
PLAY RECAP ********************************************************************************************************************
---
- name: Manage SSHD
  hosts: all
  gather_facts: false
  become: true
  tasks:
    - name: Ensure SSHD Started and Enabled
      service:
        name: sshd
        enabled: true
        state: started
    - name: Edit SSHD Config
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PermitRootLogin '
        insertafter: '#PermitRootLogin'
        line: 'PermitRootLogin no '
      notify: restart_sshd
  handlers:
    - name: restart_sshd
      service:
        name: sshd
        state: restarted
......
$ ansible-playbook sshd.yml
TASK [Edit SSHD Config]
changed: [172.16.120.161]
changed: [172.16.120.188]
changed: [172.16.120.185]

RUNNING HANDLER [restart_sshd]
changed: [172.16.120.161]
changed: [172.16.120.188]
changed: [172.16.120.185]

Listing 9-2Managing Services and Implementing Handlers

Important

请注意,我们在 lineinfile 模块的参数中的单词 no 后添加了一个空格。为了通知处理程序配置文件必须更改,添加一个空格会更改文件,而不会对实际配置产生任何影响。

确保 SSHD 已启动并启用

实现处理程序不需要这第一个任务;请不要认为我们必须有一个任务来启用处理程序操作的服务。但是,如果我们需要通过一个处理程序重启一个服务,我们也将有一个任务来确保服务被启用和启动。服务系统模块的其他可能状态包括重装重启启动停止

编辑固态混合硬盘配置

该任务使用 lineinfile 模块检查所需配置行是否存在。您会注意到我们已经为任务添加了一个参数,这意味着与任务名和模块名的缩进级别相同。通知参数用于将该任务链接到指定的处理程序。我们提供的名称必须与处理程序的名称完全匹配。为了帮助解决这个问题,我总是用小写字母命名我的处理程序,并用下划线代替空格来连接单词。对于任务和处理程序来说,这可能是一个很好的命名标准。

处理程序:restart_sshd

最后,我们将处理程序作为处理程序字典中的一个列表项。我们使用相同的服务模块引用 SSHD 服务。不过,状态设置为重启。使用任何一种状态,重启重载(如果服务支持的话,重新读取配置),在一个可执行的任务中意味着任务将一直执行。将它放在 Ansible 处理程序中允许我们只在需要的时候执行模块。这是纯粹的魔术,也是一个让你享受和进一步发展的功能。

当没有通知处理程序时,它们不会运行

再次运行剧本,第二次,我们将观察到处理程序没有执行。因为不需要更改配置,所以不会调用 notify 选项,处理程序可以安静地工作,进行一些应有的休息和恢复。从剧本的输出来看,没有以任何方式、形状或形式引用处理者。

 $ ansible-playbook sshd.yml
PLAY [Manage SSHD]
TASK [Ensure SSHD Started and Enabled]
ok: [172.16.120.161]
ok: [172.16.120.188]
ok: [172.16.120.185]
TASK [Edit SSHD Config]
ok: [172.16.120.161]
ok: [172.16.120.188]
ok: [172.16.120.185]

Listing 9-3When the Handler Is Not Called, There Is No Reference to It Within the Playbook Output

服务事实

通过收集事实,我们可以获得任何给定系统上存在的服务列表。这将列出系统上的所有服务,并且与它们的当前状态无关。设置模块收集我们的标准事实集合;对于服务列表,我们需要将 service_facts 模块作为独立任务来执行。这可以独立于设置模块进行收集,并且不参考收集事实的状态。这对于我们非常有用,因为它允许根据服务的存在与否来控制后续任务的执行。例如,如果我们想运行 Apache web 服务,我们可能想首先检查 Nginx web 服务是否被屏蔽,从而无法启动。对我们来说,我们已经知道 Apache web 服务启动时没有问题,但是在更大的环境中我们不能确定这一点。我们可以假设主要是 Nginx 服务将不存在,这意味着仅仅有一个任务来屏蔽 Nginx 服务单元是没有意义的。如果我们采用这种方法,在服务不存在的情况下,剧本将在该任务上出错。我们需要 service_facts 模块和构建一点逻辑的技巧来消除任何潜在的问题,使我们成为我们一直想要成为的负责任的专家。

让我们返回到 $HOME/ansible/apache 项目目录,在这里我们可以创建一个新的剧本来显式地检查和屏蔽 Nginx 服务。该任务应该只在 Nginx 服务存在于系统中的情况下运行,并且不依赖于该服务的当前状态。我们是管理我们系统的最终权威,现在就让我们证明这一点吧!

$ cd $HOME/ansible/apache
$ vim nginx.yml
---
- name: Manage masking of NGINX
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Collect service list
      service_facts:
    - name: Mask Nginx
      systemd:
        name: nginx
        masked: true
        state: stopped
      when: "'nginx.service' in ansible_facts.services"
...
$ ansible-playbook nginx.yml

Listing 9-4Masking a Service If It Is Present on the System

when 子句现在要求我们以双引号开始字符串,因为我们没有以变量开始子句。服务名用单引号引起来以示区别。由 service_facts 模块创建的服务数组将包含系统上的服务列表。我们只需要在数组中寻找 nginx.service 来确定它在系统中的存在。目前,在我们所有的三个系统上,我们都没有 Nginx 服务,所以任务不需要运行。

如果我们想测试我们在这里使用的逻辑,我们可以添加另一个任务,简单地将文本打印到控制台,但是只在 Ubuntu 系统上,因为我们现在寻找 Apache2 服务。

$ vim nginx.yml
---
- name: Manage masking of NGINX
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Collect service list
      service_facts:
    - name: Mask Nginx
      systemd:
        name: nginx
        masked: true
        state: stopped
      when: "'nginx.service' in ansible_facts.services"
    - name: Is Apache service
      debug:
        msg: "This must be Ubuntu!"
      when: "'apache2.service' in ansible_facts.services"
...
$ ansible-playbook nginx.yml
TASK [Is Apache service]
skipping: [172.16.120.161]
skipping: [172.16.120.185]
ok: [172.16.120.188] => {
    "msg": "This must be Ubuntu!"
}

Listing 9-5Testing Service Logic

摘要

你会相信吗《现代启示录》中的四骑士向你的优势和对 Ansible 的掌握屈膝。你对控制任务执行的逻辑的使用已经超越了你在开始这本书时所能想象的任何东西。不仅如此,你还可以选择重启服务,更重要的是,只在需要的时候重启。这是你已经添加到你的剧本中的某种形式的 AI 或人工智能,这些剧本已经变得很宏伟。你的名字现在只被悄悄地提起,而且总是带着绝对的崇敬。

在我们的剧本中,我们习惯并熟悉了服务系统和模块。这些可以用来控制大多数现代 Linux 发行版上使用的 systemd 的通用模块和特定模块的服务。我建议使用通用模块,这样我们可以避免非 Linux 操作系统和旧的 Linux 系统(如 CentOS 6:没有实现 systemd 的系统)的错误。我们应该把专门的系统和模块的使用保留到那些我们需要访问它所提供的细节的特殊时候。

在我们精心制作的剧本中,我们学会了掌握处理程序的使用。使用处理程序,我们可以确保它们只在被另一个任务调用时才被执行。通过这种方式,我们能够确保编辑过的 SSHD 配置文件通知用于重启 SSHD 服务的处理程序。我们创建了一个经过高度调整的完美剧本来管理我们托管设备上的 SSH 服务。

我们没有在这里停下来,不,不是很远。我们希望您成为贵组织内 Ansible 的推荐人,能够解决 Ansible 相关问题的关键人物。为此,我们通过使用 service_facts 模块扩展了 Ansible 标准事实。这在系统上创建了一个服务数组或列表,允许我们创建所需的逻辑来运行与服务相关的任务。对我们来说,这意味着我们可以确保在设备上只加载一个 web 服务,确保 Nginx web 服务被屏蔽或阻止在我们需要 Apache 运行的地方启动。纯天才!这就是我所听到的,人们现在虔诚地走过你的门前;纯天才!在使用 Ansible Vault 加密敏感数据之前,先享受一下这种荣耀,我们将在下一篇文章中讨论。

十、使用 Ansible Vault 保护敏感数据

我们需要在剧本中使用的一些数据可能包含敏感数据,需要以某种方式、形状或形式进行保护。我们可以使用ansible-vault创建一个密文文件,与剧本一起使用,而不是将这些敏感数据以明文形式直接存储在剧本中。一个简单的需求是我们之前在 $HOME/ansible/setup 目录中创建的 user.yml 文件;用户的密码以明文形式存储在剧本中,这并不完全理想。通过使用一个变量作为密码,我们可以引用一个加密的变量文件来保证剧本操作的安全,让我们晚上可以更安心地休息。

创建外部变量文件

即使不需要加密,我们仍然可以通过引用存储变量的外部文件来利用这个过程的一部分。在一个 Ansible 剧本中,我们已经知道我们可以引用一个变量列表。我们看到使用变量已经成为一种非常方便的方式,当我们决定是否需要更新发行版时,我们可以用它来设置版本号。如果变量列表很长或者可能会变得很长,我们可能更喜欢引用一个文件。最简单的实现方法是使用专用的任务和模块include_vars,而不是使用字典vars:。现在,如果我们回想一下,在$ HOME/ansi ble/upgrade/upgrade . yml中,我们只引用了两个变量,所以这不是一个很长的列表!但是如果你能满足我的话,我们可以快速地看看如何引用一个外部文件。没有必要加密这些变量,所以我们一开始就将变量存储为明文 YAML。

$ cd $HOME/ansible/upgrade ; cat upgrade.yml
---
- name: Upgrade Systems
  hosts: all
  become: true
  gather_facts: true
  vars:
    - ubuntu_version: "Ubuntu 18.04.5 LTS"
    - centos_version: "8.2"
  tasks:
    - name: Upgrade Older Systems
      package:
        name: "*"
        state: latest
      when: >
        (ansible_distribution == "CentOS" and
        ansible_distribution_version != centos_version) or
        (ansible_distribution == "Ubuntu" and
        ansible_lsb.description != ubuntu_version)
...
$ #we change the embedded variables to external variables
$ echo 'ubuntu_version: "Ubuntu 18.04.5 LTS"' >> version.yml
$ echo 'centos_version: "8.2"' >> version.yml
$ cat version.yml
ubuntu_version: "Ubuntu 18.04.5 LTS"
centos_version: "8.2"
$ vim upgrade .yml
---
- name: Upgrade Systems
  hosts: all
  become: true
  gather_facts: true
  tasks:
    - name: read the variables file
      include_vars:
        file: version.yml
    - name: Upgrade Older Systems
      package:
        name: "*"
        state: latest
      when: >
        (ansible_distribution == "CentOS" and
        ansible_distribution_version != centos_version) or
        (ansible_distribution == "Ubuntu" and
        ansible_lsb.description != ubuntu_version)
...

Listing 10-1Storing Variables in an External YAML File

加密现有的 YAML 文件

当我们需要一个更安全的可变数据存储时,我们可以简单地加密现有的文件。如果文件不存在,我们可以创建一个全新的文件,从一开始就加密。因为我们从现有的 YAML 变量存储开始,所以在创建新的加密文件之前,我们将加密现有的文件。

$ ansible-vault encrypt version.yml
New Vault password:
Confirm New Vault password:
Encryption successful
$ cat version.yml
$ANSIBLE_VAULT;1.1;AES256
37313034613630313338383036303834393261383239623335383730386261333166316163393263
6330356337373032646163626331643530346635663030650a373734326433333566383039366662
61323436383637663139393539646530383964336161613133656635303239663064373166333735
6265613935623733620a656364333134383039353335643562363632333438303663333033643939
65306566313433643061396561613830653137346533646136643832363030343537363038393934
63333135303737613433623439636664323363383765303830623265636565323433363033646335
353733613537363531663130363062333266
$ ansible-vault view version.yml
Vault password:
ubuntu_version: "Ubuntu 18.04.5 LTS"
centos_version: "8.2"

Listing 10-2Encrypting an Existing YAML File

加密文件后,它受到 AES256 位加密的保护,需要输入密码才能访问内容。可以使用 view 子命令来查看明文中的内容,我们可以使用 edit 子命令来允许在输入密码后编辑文件。要执行引用该文件的剧本,我们还需要提供密码。

$ ansible-playbook --ask-vault-pass upgrade.yml
Vault password:

Listing 10-3Executing the Playbook When Variables Are Encrypted

剧本现在可以正确执行了,因为变量可用于ansible-playbook命令。

创建新的加密文件

如果加密变量在 YAML 文件中不存在,我们可以直接用ansible-vault创建一个新文件。在 CentOS 上,默认编辑器是vim。如果我们希望用nano或另一个编辑器打开ansible-vault,我们可以使用编辑器环境变量。回到设置目录,我们可以为用户密码变量创建一个加密文件。

$ cd $HOME/ansible/setup
$ EDITOR=nano ansible-vault create private.yml
New Vault password:
Confirm New Vault password (nano will then open)

user_password: Password1

$ $ ansible-vault view private.yml
Vault password:
user_password: Password1

Listing 10-4Creating a New Encrypted YAML File Bypassing the Default Editor

创建新文件后,我们可以在任何阶段使用ansible-vault edit private.yml编辑它。当然,我们需要输入用于加密文件的密码。我们不需要编辑文件,但在现实世界中,这可能是必需的。

Note

请小心使用密码!忘记使用的密码将意味着无法访问该文件。不!存储在便利贴上不是一个选项!

为了利用这一点,和以前一样,我们需要向剧本中添加额外的任务。这次我们使用的是我们在 $HOME/ansible/setup 目录中创建的 user.yml

$ vim user.yml
---
- name: Manage User Account
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Read password variable
      include_vars:
        file: private.yml
    - name: Create User
      user:
        name: "{{ user_name }}"
        shell: /bin/bash
        state: present
        password: "{{ user_password | password_hash('sha512') }}"
        update_password: on_create
      when: user_create == 'yes'
    - name: Delete User
      user:
        name: "{{ user_name }}"
        state: absent
        remove:  true
      when: user_create == 'no'
...
$ ansible-playbook -e user_name=april -e user_create=yes \
  --ask-vault-pass user.yml
Vault password:

Listing 10-5Editing the user.yml

读取保险库密码

如果无法通过交互方式输入存储库密码,例如在安排剧本执行时,可以从文件中读取密码。不过,我不太喜欢这种方法。对我来说,这有点像儿歌,“我的桶里有个洞”我们又回到了起点,将敏感数据保存在明文文件中,但这次我们将保险库密码保存在明文文件中。如果我们被迫使用这个作为解决方案,那么我们当然应该提高文件安全性。在这个例子中,我们使用模块400将文件设置为对文件所有者是只读的。

$ echo Password1 > passwd.txt
$ chmod 400 passwd.txt
$ ansible-playbook -e user_name=may \
  -e user_create=yes --vault-password-file=passwd.txt user.yml

Listing 10-6Reading the Vault Password from a File

摘要

保护敏感数据,比如用户密码,应该在你的安全世界中每天都存在。你可以变成三头冥府之神,坐镇守卫你自己的冥府之门。幽默放在一边,这是一件严肃的事情。如果获得了对保存敏感数据(或密码等数据密钥)的剧本的任何未经授权的访问,您所在地区的数据保护监管机构可能会像一吨砖头一样扑向您。这不再是内部错误,而是可以报告的。加密密码和用于剧本的默认密码确实可以在自动管理的同时为您提供一定程度的保护。

命令ansible-vault使用 AES256 加密算法管理加密和解密。我们可以创建新的加密文件或加密现有文件。如果有必要,我们可以使用 decrypt 子命令完全删除加密。这将文件返回到明文。如果您觉得加密密码已经泄露,您可以使用 re-key 子命令指定一个新的密钥并重新加密文件。

为了从剧本中访问加密文件,我们需要使用-ask-vault-pass选项。在需要变量的 Ansible play 中,我们必须使用任务模块 include_vars ,这样我们就可以引用变量文件。该变量文件可以加密或不加密。仅仅因为我们想要包含一个变量文件,并不意味着它需要被加密。当需要访问许多变量时,文件可能是最好的解决方案。

十一、实现完整的 Apache 部署

非常感谢神奇的道格拉斯·亚当斯和《银河系漫游指南》,我们都知道 42 是终极答案——是生命、宇宙和一切的意义。我恭敬地建议这现在可能是 Ansible,在这一章中,我希望用一个使用自动化我们能实现多少的演示来说服你。我们已经使用了 Ansible 中许多我们需要知道的工具和元素,这意味着我们可以从一些更强大的东西开始。我们将解决安装 Apache 时需要做的所有事情,并让 Ansible 在 Ubuntu 和 CentOS Linux 发行版上实现自动化。

部署 Apache

正如我刚才提到的,Apache web 服务器的部署不仅仅是安装软件包的单一任务。有许多更小的任务组合在一起,形成一个令人敬畏的配置,不会忘记任何事情。当我们查看部署中最基本的内容时,我们需要包括以下任务:

  • 部署正确的 Apache 包

  • 开始服务,特别是当我们使用 Red Hat 发行版时;基于 Debian 的系统通常在安装时启动它们的服务

  • 在防火墙管理器中打开正确的端口:ufw 用于 Ubuntu,firewalld 用于 CentOS

  • 对特定于发行版的 Apache 配置文件进行配置更改

  • 使用处理程序在配置更改时重新启动服务

  • 部署标准 web 内容

  • 配置文档根文件系统安全性

这些代表了我们可能配置的最少任务;当然,它可能更多,但是请想一想这将会多么有用。一旦您有了所需的任务列表,您就可以在剧本中记录设置,并自动执行相同的配置。

在学习本章的过程中,我们将学习新的 Ansible 模块,并对之前学习过的模块进行总结。一如既往,我们将确保每次都能重复正确地部署 web 服务。我们将使用包含所有任务和处理程序的单一剧本。稍后,我们将看到如何使用角色来简化剧本。角色存储可以在许多剧本中使用的共享代码,而不是在每个剧本中重复需要的任务。

在我们学习本章的过程中,剧本将会增加很多行。我们不会在每次编辑后都显示完整的剧本文件,而是只列出每个部分最近的编辑。完整的剧本将在本章末尾列出。你的学习和理解是我的首要目标,我想让你清楚每一个学习步骤,这就是为什么单独列出任务。为了理解大局,最终完成的剧本可以帮助你看到最终的 YAML 应该是什么样子。

阿帕奇战术手册

我们将进入 $HOME/ansible/apache 目录。在这个目录中,我们已经创建了剧本,它已经用于部署 web 服务器和 web 内容。我们将从这个文件开始,并在本章中不断完善它。在调整新内容之前,我会复制一份现有的 YAML 剧本。

$ cd $HOME/ansible/apache
$ cp simple_apache.yml full_apache.yml
$ vim full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  tasks:
    - name:  Install Apache Package
      package:
        name: "{{ apache_pkg }}"
        state: present
    - name: Copy web content
      copy:
        src: web/
        directory_mode: true
        dest: /var/www/html
    - name: Start and Enable Apache Service
      service:
        name: "{{ apache_pkg }}"
        state: started
        enabled: true
...
$ ansible-playbook full_apache.yml

Listing 11-1Beginning a Full Apache Deployment

在创建了新剧本之后,我们做了一些小的改动。我们已经改变了剧本的标题,以更好地适应我们使用的任务,我们已经开始收集可回答的事实,我们将很快使用。我们添加的新任务确保 Apache 服务被启用和启动。服务名很方便地与包名匹配,所以我们能够利用现有的变量。

专用服务器页面

我们应该花些时间进行模板练习。我们可以在 Apache 项目目录中创建一个新模板,与其他 web 内容一起部署到我们的系统中。模板将驻留在控制器上,但不在我们之前创建的 web 目录中,它是使用复制模块复制的;我们需要模板模块来服务金贾 2 模板。模板模块填充我们添加到模板文件中的变量内容。

$ vim server.j2
This is {{ ansible_hostname }}
We are running {{ ansible_distribution }}
$ vim full_apache.yml
    - name: Custom web content
      template:
        src: server.j2
        dest: /var/www/html/server.html

Listing 11-2Deploying a Jinja 2 Template as the server.html Web Page

Note

我建议在每个阶段测试剧本,这样更容易检测和纠正出现的拼写错误,而不是在添加了所有更改后进行更复杂的调试。

关于防火墙的一切

我们现在可以测试远程系统了。在前一章中,我已经演示了如何访问 Ubuntu 系统。这工作得很好,因为服务已经自动启动,默认情况下防火墙在 Ubuntu 中是不启用的。现在已经添加了自定义页面,并确保服务将在所有系统上运行,我们可以进一步测试了。从控制器上,我们应该能够访问控制器的 web 服务和 Ubuntu 的 web 服务,但很可能防火墙会阻止 CentOS 客户端上的访问。下面列出了我的实验室中每个系统使用的 IP 地址:

  • 172.16.120.161 :我的 CentOS 控制器

  • 172.16.120.185 :我的 CentOS 客户端

  • 172.16.120.188 :我的 Ubuntu 主机

$ curl 172.16.120.161/server.html
This is controller
We are running CentOS
$ curl 172.16.120.188/server.html
This is ubuntu
We are running Ubuntu
$ curl 172.16.120.185/server.html
curl: (7) Failed to connect to 172.16.120.185 port 80: No route to host

Listing 11-3Testing HTTP Access to the Web Servers

看起来我们无法连接到客户端系统,但是稍微了解一下 CentOS,我们应该知道默认情况下 Firewalld 防火墙是活动的。我们确实可以访问控制器,这也是 CentOS,但请记住,我们是从控制器而不是远程访问它。当使用 Ansible 来补救这种情况时,我们可以选择在每个系统上禁用防火墙,或者在每个系统上启用防火墙。主要目标是所有系统的一致性,但在这个网络感知的世界中,安全性也必须突出。考虑到安全性,我们将选择在每个系统上启用防火墙;在 Ubuntu 上,UFW 被使用但被禁用,在 CentOS 上,防火墙被使用并启用。首先,我们将添加变量来标识底层防火墙管理器。

$ echo "firewall_pkg: firewalld" >> $HOME/group_vars/centos
$ echo "firewall_pkg: ufw" >> $HOME/group_vars/ubuntu
$ ansible-inventory --yaml --host 172.16.120.161
admin_group: wheel
ansible_connection: local
apache_pkg: httpd
firewall_pkg: firewalld
$ ansible-inventory --yaml --host 172.16.120.188
admin_group: sudo
ansible_python_interpreter: /usr/bin/python3
apache_pkg: apache2
firewall_pkg: ufw

Listing 11-4Updating Inventory Variables

现在我们已经配置了变量,我们可以在我们的系统上配置防火墙了。我们将确保安装了正确的防火墙包,并且服务正在运行。在这里,我们可以将 firewall_pkg 变量用于包名和服务名。然后,使用正确的模块来管理安装的防火墙,我们启用 SSH 和 HTTP。这是对条款的重大修改。

$ vim full_apache.yml
    - name: Firewall Package
      package:
        name: "{{ firewall_pkg }}"
        state: present
    - name: Firewall Service
      service:
        name: "{{ firewall_pkg }}"
        enabled: true
        state: started
    - name: UFW Ubuntu
      ufw:
          state: enabled
          policy: deny
          rule: allow
          port: "{{ item }}"
          proto: tcp
      loop:
        - "80"
        - "22"
      when: ansible_distribution == "Ubuntu"
    - name: Firewalld CentOS
      firewalld:
        service: "{{ item }}"
        permanent: true
        immediate: true
        state: enabled
      loop:
        - "http"
        - "ssh"
      when: ansible_distribution == "CentOS"

Listing 11-5Enabling the Ubuntu UFW Firewall and Allowing Access to SSH and HTTP

我们已经一次性编写了所有这些与防火墙相关的任务。我们不想做的是启用防火墙服务,并发现防火墙系统的默认设置中没有启用 SSH 或 TCP 端口 22。那会把我们和 Ansible 锁在系统之外。我们通常提倡在创建测试任务时进行测试;但我们也需要意识到我们的方法中可能存在的陷阱。

我们介绍 ufw防火墙和可扩展模块:

  • :在这里,我们使用永久直接自变量。Firewalld 可以通过写入后端配置文件来实现永久设置。这些设置直到服务重新启动后才会被加载,这就是为什么我们还使用了 immediate 参数来将这些设置分配给运行时配置。

*** ufw :可以启动 ufw 防火墙服务,但可以独立禁用配置。这是我的 Ubuntu 系统的默认设置。在该模块中,我们首先启用防火墙。然后我们将默认的策略设置为拒绝。任何与现有规则不匹配的数据包都将应用默认策略。这意味着我们需要显式地允许我们希望成功的传入流量。**

**### Apache 配置文件

其配置中使用的 Apache 指令 ServerName 不是默认设置的,这将导致日志文件中出现警告。我们可以通过设置带有系统主机名的指令来轻松解决这个问题。主机名是通过一个可解析的事实获得的。您可能已经猜到,Ubuntu 和 Centos 中 Apache 配置文件的位置和名称是不同的。因此,我们将从设置所需的库存变量开始。

$ echo "apache_cfg: /etc/httpd/conf/httpd.conf" >> $HOME/group_vars/centos
$ echo "apache_cfg: /etc/apache2/sites-enabled/000-default.conf" >> \
   $HOME/group_vars/ubuntu
$ ansible-inventory --yaml --host 172.16.120.188
admin_group: sudo
ansible_python_interpreter: /usr/bin/python3
apache_cfg: /etc/apache2/sites-enabled/000-default.conf
apache_pkg: apache2
firewall_pkg: ufw
$ ansible-inventory --yaml --host 172.16.120.161
admin_group: wheel
ansible_connection: local
apache_cfg: /etc/httpd/conf/httpd.conf
apache_pkg: httpd
firewall_pkg: firewalld

Listing 11-6Setting Variables for the Apache Configuration files

随着基础工作的完成和变量的耐心等待,我们可以配置 Apache 服务器,并确保我们添加了处理程序,以便在配置发生变化时重启服务。

$ vim full_apache.yml
- name: Configure Apache
      lineinfile:
        path: "{{ apache_cfg }}"
        line: "ServerName {{ ansible_hostname }}"
        insertafter: "#ServerName"
      notify:
        - restart_apache
  handlers:
      - name: restart_apache
        service:
          name: "{{ apache_pkg }}"
          state: restarted

Listing 11-7Configuring Apache ServerName

配置文件系统安全性

CentOS 或 Ubuntu 包提供的 Apache HTTP 服务器的文件系统安全性不是最好的。网络服务器本身将通过授予其他的权限获得访问权。我们最好取消对其他人的访问,而允许通过 Apache 用户或组帐户进行访问。对我来说,这似乎是任何安全系统的基础。将权限授予较小的组,而不是像和其他那样的全局组。我们可以使用 Ansible 中的文件模块来设置标准权限,或者使用 acl 模块通过 POSIX ACLs 来授予权限。我们将使用 ACL,因为它们提供了更大的灵活性。

使用 POSIX ACLs,我们可以实现以下文件系统安全优势:

  • 默认 ACL :在一个目录中添加一个默认 ACL,将允许在该目录中创建的所有新文件应用该默认 ACL。这样,新文件就可以拥有正确的权限,而不考虑是谁创建了该文件或当前的 UMASK 值。

  • :标准文件模式允许为单个用户和单个组分配权限。这就是为什么其他人经常被用作一个实体,因为不止一个用户或组需要访问。使用 ACL,我们可以向其他人分配有限的权限或不分配权限,并单独列出所需的用户或组。

**由于 web 服务将在不同的发行版中使用不同的用户帐户,我们需要再次设置库存变量。在 CentOS 上,用户账号是 apache ,在 Ubuntu 上,账号是 www-data

$ echo "apache_user: apache" >> $HOME/group_vars/centos
$ echo "apache_user: www-data" >> $HOME/group_vars/ubuntu
$ ansible-inventory --yaml --host 172.16.120.161
admin_group: wheel
ansible_connection: local
apache_cfg: /etc/httpd/conf/httpd.conf
apache_pkg: httpd
apache_user: apache
firewall_pkg: firewalld
$ ansible-inventory --yaml --host 172.16.120.188
admin_group: sudo
ansible_python_interpreter: /usr/bin/python3
apache_cfg: /etc/apache2/sites-enabled/000-default.conf
apache_pkg: apache2
apache_user: www-data
firewall_pkg: ufw

Listing 11-8Creating Inventory Variable for the Apache User Account

使用 acl 模块,我们可以学到一些新的东西。我们将使用这个 Ansible 模块来保护 Apache 使用的文件系统。我们在 Apache DocumentRoot 上设置了默认 ACL,并为正确的 Apache 帐户设置了特定的权限。该帐户不需要写权限,也没有被分配。我们从全局组 others 中删除了目录 ACL 和默认 ACL 中的权限,因此新文件在 DocumentRoot 下创建时将无法访问其他文件。

$ vim full_apache.yml
   - name: Secure default ACL for apache user on document root
      acl:
        path: /var/www/html
        entity: "{{ apache_user }}"
        etype: user
        state: present
        permissions: rx
        default: true
    - name: Secure default ACL for others on document root
      acl:
        path: /var/www/html
        entry: default:others::---
        state: present
    - name: Set read and execute permissions on document root for apache user
      acl:
        path: /var/www/html
        entity: "{{ apache_user }}"
        etype: user
        state: present
        permissions: rx
    - name: Set permissions to others to nothing on document root
      acl:
        path: /var/www/html
        entry: others::---
        state: present

Listing 11-9Creating an ACL and Default ACL to Secure Apache

完整的阿帕奇战术手册

我们为剧本写了很多东西。我敢肯定,你的键盘在呼唤休息,但还不是时候。正如承诺的那样,我们通过在每个点只显示添加的元素来保持内容简短。我们现在为您列出完整的剧本。

---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  tasks:
    - name:  Install Apache Package
      package:
        name: "{{ apache_pkg }}"
        state: present
    - name: Copy web content
      copy:
        src: web/
        directory_mode: true
        dest: /var/www/html
    - name: Start and Enable Apache Service
      service:
        name: "{{ apache_pkg }}"
        state: started
        enabled: true
    - name: Custom web content
      template:
        src: server.j2
        dest: /var/www/html/server.html
    - name: Firewall Package
      package:
        name: "{{ firewall_pkg }}"
        state: present
    - name: Firewall Service
      service:
        name: "{{ firewall_pkg }}"
        enabled: true
        state: started
    - name: UFW Ubuntu
      ufw:
          state: enabled
          policy: deny
          rule: allow
          port: "{{ item }}"
          proto: tcp
      loop:
        - "80"
        - "22"
      when: ansible_distribution == "Ubuntu"
    - name: Firewalld CentOS
      firewalld:
        service: "{{ item }}"
        permanent: true
        immediate: true
        state: enabled
      loop:
        - "http"
        - "ssh"
      when: ansible_distribution == "CentOS"
    - name: Configure Apache
      lineinfile:
        path: "{{ apache_cfg }}"
        line: "ServerName {{ ansible_hostname }}"
        insertafter: "#ServerName"
      notify:
        - restart_apache
    - name: Secure default ACL for apache user on document root
      acl:
        path: /var/www/html
        entity: "{{ apache_user }}"
        etype: user
        state: present
        permissions: rx
        default: true
    - name: Secure default ACL for others on document root
      acl:
        path: /var/www/html
        entry: default:others::---
        state: present
    - name: Set read and execute permissions on document root for apache user
      acl:
        path: /var/www/html
        entity: "{{ apache_user }}"
        etype: user
        state: present
        permissions: rx
    - name: Set permissions to others to nothing on document root
      acl:
        path: /var/www/html
        entry: others::---
        state: present
  handlers:
      - name: restart_apache
        service:
          name: "{{ apache_pkg }}"
          state: restarted
...

Listing 11-10Fill Apache Playbook Listing

摘要

将服务部署到混合部署环境中总是比您想象的要复杂一些。正是因为这个原因,我们使用 Ansible,确保每件事总是被完成,没有什么被遗忘。在规划组成部署的所有元素上花费的时间越多,部署就越好。

在本章中,我们通过确保安装了软件并启动了服务,完成了 Apache 的全面部署。随着基于主机的防火墙越来越常见于缓解网络威胁,我们还需要确保服务的正确端口是开放的。对我们来说,这意味着学习 Ubuntu 的 ufw 模块和 CentOS 的防火墙模块。在所有系统上,我们希望 HTTP 端口打开(我们在书中没有使用 HTTPS)。

通过检查 lineinfile 模块的使用,我们可以更改 Apache 配置,将正确的 ServerName 指令配置到每个 web 服务器的主机名。回顾这一点很好,这样我们也可以看到我们可以使用变量来设置所需的行。如果你记得的话,这是我的第一模块之一。

我们通过将 Ansible 控制器上的一个目录的内容复制到每个 web 服务器上,将内容添加到 web 服务器中,复制模块和directory_mode: true参数允许复制完整的内容。我们使用 Jinja 2 模板创建的定制网页允许显示特定的主机信息。这是与模板模块一起交付的。

纠正 Apache 部署中文件系统权限的一些弱点,我们使用 Ansible 中的 acl 模块为正确的 Apache 帐户配置特定的权限。这消除了默认情况下授予其他人对 web 页面位置(在 Apache 中称为 DocumentRoot)的权限。****

十二、使用角色简化剧本

在单个剧本中包含与 Apache 管理相关的所有内容在某种程度上很方便,因为我们只有一个文件要处理。正是这个文件在一个地方为我们提供了大量复杂的数据。通过创建更小的代码元素,我们不仅简化了代码,还允许可能的代码重用。在本章中,我们将花时间研究一个新命令ansible-galaxy,我们用它来管理角色。这样做,我们很可能能够重写一些代码,比如防火墙任务,这样它们变得更加灵活,并允许代码在其他剧本中重用。

了解角色

角色包含剧本的元素,例如任务、变量和在目录中整理的文件。角色可以根据您自己的规范在本地创建,也可以从 Ansible Galaxy 网站下载(我们将在下一章访问该网站)。这些角色包含剧本的必要组成部分,但作为单独的元素。因此,角色是由子目录和文件的集合组成的,而不是一本很长的剧本。这些表示任务、处理程序、文件、模板、变量等等,否则它们会在一个完整的剧本中使用。通过ansible-galaxy命令管理角色。该命令的子命令如下所示:

  • init :为角色创建所需的结构

  • 列表 :列出路径结构内的角色

  • 搜索 :搜索银河储存库中的角色

  • :从 URL 下载并安装角色

*** :从系统中删除角色**

***可以在 \(HOME/的目录中为每个用户创建角色。或者可以在控制器上的用户之间在 */etc/ansible/roles* 或 */usr/share/ansible/roles* 目录中共享。首先,我们在系统中没有任何角色;默认情况下,Ansible 不安装角色。 *\)HOME/。ansible/roles 目录在默认情况下也不存在,所以我们从一个非常暗淡的前景开始。不过不要担心,这很快就会改变。让我们试着列出角色,看看会发生什么:

$ ansible-galaxy list
# /usr/share/ansible/roles
# /etc/ansible/roles
[WARNING]: - the configured path /home/tux/.ansible/roles does not exist.

Listing 12-1Listing Ansible Roles

好吧,没什么。我没有骗你,我们得到警告,该目录不存在。目前我们不需要做任何事情,因为我们可以创建所需的目录以及我们的角色。说到这里,我们先来看看我们的第一个角色,看看ansible-galaxy是怎么回事。

创建防火墙角色

正如我们在 full_ansible.yml 剧本中所看到的,防火墙元素所需的代码行相当多。从剧本中删除与防火墙相关的代码不仅会使剧本更易读、更简洁,而且还允许代码重用。我们将使用一个变量,而不是对我们希望在防火墙中开放的端口或服务进行硬编码。然后,该变量可以被填充到调用剧中,而不与角色一起存储。我们确实从这个角色中直接获得了双重好处:代码的清晰性和在其他剧本和剧本中重用保存的代码的能力。我们将从已经工作了一段时间的 Apache 项目目录开始工作,并开始为防火墙创建一个新的角色。

$ cd $HOME/ansible/apache
$ ansible-galaxy role init $HOME/.ansible/roles/firewall
- Role /home/tux/.ansible/roles/firewall was created successfully
$ $ tree $HOME/.ansible/roles/firewall
/home/tux/.ansible/roles/firewall
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml
        dest: /var/www/html/server.html
$ ansible-galaxy list
# /home/tux/.ansible/roles
- firewall, (unknown version)
# /usr/share/ansible/roles
# /etc/ansible/roles

Listing 12-2Creating the Firewall Role

普及防火墙角色

防火墙角色现已创建,并且在列出我们在系统上安装的角色时会显示出来。角色可以在剧本之间共享,并且不限于任何特定的 YAML 文件。使用tree命令,我们可以看到子目录结构和相关文件。我们可以将角色应该使用的任务添加到角色的任务子目录中的 main.yml 文件中。我们把任务和而不是剧本添加到这个文件中。此外,我们将在任务中使用一个新变量来定义我们需要在防火墙中打开的服务。我们不会在角色中定义变量,因为我们希望角色可以与任何服务一起工作,而不仅仅是 Apache。该变量将从剧本中的调用行动中设置。首先,我们将通过编辑$ HOME/来创建角色内容。ansi ble/roles/firewall/tasks/main . yml文件。我们添加的内容可以先从 full_apache.yml 中删除,然后再复制到 main.yml 中。注意从复制的数据中删除不必要的缩进;现在,每个任务都将成为文件根级别的列表项。

Note

在删除多余的行之前,做一个 full_apache.yml 的备份副本可能是明智的。很容易删除太多的行,能够恢复到保存的版本总是一个令人欣慰的选择。

$ vim $HOME/.ansible/roles/firewall/tasks/main.yml
---
- name: Firewall Package
  package:
    name: "{{ firewall_pkg }}"
    state: present
- name: Firewall Service
  service:
    name: "{{ firewall_pkg }}"
    enabled: true
    state: started
- name: UFW Ubuntu
  ufw:
      state: enabled
      policy: deny
      rule: allow
      port: "{{ item }}"
  loop:
    - "{{ service_name }}"
    - "ssh"
  when: ansible_distribution == "Ubuntu"
- name: Firewalld CentOS
  firewalld:
    service: "{{ item }}"
    permanent: true
    immediate: true
    state: enabled
  loop:
    - "{{ service_name }}"
    - "ssh"
  when: ansible_distribution == "CentOS"
...

Listing 12-3Populating the Firewall Role tasks/main.yml File

Note

ufw 模块端口参数可以接受服务名或端口号。在这些例子中,为了方便起见,我们对服务名的使用进行了标准化。我们总是希望启用 SSH,我们已经将它硬编码到服务列表中。

这 31 行已经从原始剧本中删除,现在可以独立使用。这个角色可以通过为 MySQL、Redis、SMTP 或任何需要的端口打开正确的端口来工作。

更新 Apache 剧本

即使删除了与防火墙相关的行,原始剧本仍然可以运行;但是,我们仍然希望确保在每台受管设备上正确配置防火墙。在我们的戏剧中,我们可以添加新的角色列表。我们还必须设置角色使用的 service_name 变量。确保我们已经从 full_apache.yml 剧本中删除了与防火墙配置相关的任务,我们可以重新编辑它,设置变量并引用角色。为了减少输出显示,我们只列出游戏细节和角色列表,而不列出任务或处理程序。

$ vim  $HOME/ansible/apache/full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  vars:
    - service_name: http
  roles:
    - firewall

Listing 12-4Editing the full_apache.yml Playbook to Reference the Role

我们有两个新列表:变量列表和角色列表。与防火墙相关的任务已被删除,但不会显示。在此重头戏中配置 service_name 变量,我们将在防火墙角色中启用 HTTP 端口。我们不仅限于开放一个港口;我们可以很容易地在同一个剧本中创建另一个剧本来设置另一个端口,比如 MySQL。每个剧本的变量都是相互独立的。实现这个过程将允许我们部署完整的 LAMP、Linux、Apache、MySQL 和 PHP 服务器,并重用共享代码,防火墙角色同时打开 HTTP 和 MySQL 端口。

为 Web 内容配置角色

很可能不同的网络服务器配置需要不同的网络内容。这是营销 web 服务器还是 IT web 服务器?通过为内容设置不同的角色,我们可以在 Apache 剧本中包含正确的内容角色。这样做,我们也可以学习使用角色的不同元素,我们引入了文件模板目录。为了将正确的文件传送到被管理设备,这些文件应该被添加到角色的文件子目录中。在交付模板时,我们将 server.j2 文件添加到角色的 templates 子目录中。这样,文件和 YAML 代码组织得更好,也更容易识别和定位。我们首先创建一个名为 standard_web 的新角色。

$ ansible-galaxy role init /home/tux/.ansible/roles/standard_web
- Role /home/tux/.ansible/roles/standard_web was created successfully
$ mv $HOME/ansible/apache/web $HOME/.ansible/roles/standard_web/files/
$ mv $HOME/ansible/apache/server.j2 \ $HOME/.ansible/roles/standard_web/templates/
$ tree /home/tux/.ansible/roles/standard_web/
/home/tux/.ansible/roles/standard_web/
├── defaults
│   └── main.yml
├── files
│   └── web
│       ├── contact.html
│       └── index.html
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
│   └── server.j2
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

Listing 12-5Adding Files and Templates to New Web Content Role

浏览tree命令的输出,我们可以看到新的 standard_web 角色应该如何显示新添加的内容。我们再次需要使用带有任务子目录的 main.yml 文件。我们将从 full_apache.yml 剧本中删除与内容相关的任务,将它们添加到 standard_web 角色中。

$ vim $HOME/.ansible/standard_web/tasks/main.yml
---
- name: Copy web content
  copy:
    src: web/
    directory_mode: true
    dest: /var/www/html
- name: Custom web content
  template:
    src: server.j2
    dest: /var/www/html/server.html

Listing 12-6Adding Tasks to the standard_web Role tasks/main.yml

我们小心地从 full_apache.yml 剧本中删除这些台词,并将它们添加到角色中。每个任务都作为列表项添加到文件缩进的根级别。请务必确保模块和模块参数保持正确的缩进级别。该模块应与任务名称和缩进在该模块下的参数处于同一级别。有了这些精心准备,我们现在可以回到 full_apache 剧本并加入新角色。

$ vim  $HOME/ansible/apache/full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  vars:
    - service_name: http
  roles:
    - firewall
    - standard_web

Listing 12-7Referencing the Web Content Role from the full_apache.yml Playbook

我们现在可以开始看到使用角色所带来的好处。我们减少了剧本中的行数,使其更易于阅读和管理。这些台词仍然存在,但现在已经外包给角色,如果需要,可以在许多不同的剧本中使用。

创建 Apache 角色

保留在 full_apache.yml 中的任务和处理程序可能会一起存在于最终角色中。该角色将安装 web 包并启动服务。该服务将需要 ServerName 指令设置,并且我们应该保护 DocumentRoot。虽然它们可能是独立的,但是如果安装了 web 服务器,这些事件应该都会发生,这就是为什么我们将这些任务设置为单个角色。和之前一样,这些被删除的任务会被添加到新角色的任务子目录下的 main.yml 中。我们还可以将处理程序添加到新角色的 handlers 子目录中。

$ ansible-galaxy role init $HOME/.ansible/roles/apache
- Role /home/tux/.ansible/roles/apache was created successfully
$ vim $HOME/.ansible/roles/apache/tasks/main.yml
---
- name:  Install Apache Package
  package:
    name: "{{ apache_pkg }}"
    state: present
- name: Start and Enable Apache Service
  service:
    name: "{{ apache_pkg }}"
    state: started
    enabled: true
- name: Configure Apache
  lineinfile:
    path: "{{ apache_cfg }}"
    line: "ServerName {{ ansible_hostname }}"
    insertafter: "#ServerName"
  notify:
    - restart_apache
- name: Secure default ACL for apache user on document root
  acl:
    path: /var/www/html
    entity: "{{ apache_user }}"
    etype: user
    state: present
    permissions: rx
    default: true
- name: Secure default ACL for others on document root
  acl:
    path: /var/www/html
    entry: default:others::---
    state: present
- name: Set read and execute permissions on document root for apache user
  acl:
    path: /var/www/html
    entity: "{{ apache_user }}"
    etype: user
    state: present
    permissions: rx
- name: Set permissions to others to nothing on document root
  acl:
    path: /var/www/html
    entry: others::---
    state: present
$ vim $HOME/.ansible/roles/apache/handlers/main.yml
---
- name: restart_apache
  service:
    name: "{{ apache_pkg }}"
    state: restarted
$ vim  $HOME/ansible/apache/full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  vars:
    - service_name: http
  roles:
    - apache
    - firewall
    - standard_web

Listing 12-8Creating and Populating the Apache Role

我们现在列出完整的 完整的 _apache 剧本,现在只有 11 行。是的,只有 11 行,而以前是 92 行!剧本的简单现在看起来非常漂亮,每个元素都被移到一个更简洁和具体的文件中。

执行次序

如果您仔细注意我们现在添加的角色,我们会确保首先列出的是 apache 角色。如果我们在剧中列出了任务和角色,他们将按照在剧中列出或写出的顺序来演。如果首先列出任务,它们将首先执行;如果角色首先列出,则角色将首先执行。同样,每个角色或任务都按照列表定义的顺序执行。在我们的角色中,我们需要在添加 web 内容之前安装 Apache web 服务器。虽然这似乎与我们当前的系统无关,因为一切都已经就绪,但对于添加到清单中的新系统,我们确实需要注意并考虑执行顺序。

摘要

角色是 Ansible 中最有用的元素之一。通过实现角色,我们向代码重用迈进了一大步。我们抛弃了过去创造的单一剧本,我们看到了生产力的新曙光。

我们对创建和列出角色的命令ansible-galaxy有点熟悉了。使用 init 子命令,我们可以创建角色所需的文件系统结构。这是可选的,因为我们可以自己创建目录和文件,但是坦率地说,我喜欢不用自己创建它们的便利。向 \(HOME/添加角色。ansible/roles* 目录允许我们创建的角色可以在我们的任何剧本中使用,就像我们使用 *\)HOME/.ansible.cfg 文件在剧本项目中共享一样。

我们首先创建了一个防火墙角色,并通过允许一个变量来控制我们需要打开的端口,使其更加有用。为了便于使用,我们需要列出我们在剧中设置的角色和变量。接下来,我们创建了 web content 角色,我们用它来添加标准 web 页面和来自模板的定制内容。该角色将使用文件和模板,并且该角色将这些文件组织在它们自己的子目录中。代码和文件的组织对于角色来说是至关重要的。最后,当我们创建一个角色来部署 Apache web 服务器时,我们学习了如何在一个角色中使用任务和处理程序——任务用它们自己的目录来组织,处理程序也一样。

剧本最初的行数从 92 行减少到只有 11 行。当然,代码并没有简单地消失;它已被添加到三个新角色中,但是我们可以在剧本之间共享这些角色。在本章中,我们通过创建自己的角色学习了角色的基础知识。在下一章中,我们将学习搜索和下载预先写好的角色来节省我们的努力。还记得那个大反驳吗,为什么要重新发明轮子?****

十三、下载角色

我们不局限于我们自己创造的角色,远非如此。我们可以下载社区创建的角色,并在我们自己的系统上自由使用它们。你会发现这些角色被托管在 https://galaxy.ansible.com 网站上,你可以从命令行浏览或者通过网页浏览器以图形方式浏览。在本章中,我们将继续开发我们的 Apache 剧本,通过添加 PHP 和 MySQL 来创建 LAMP 服务器、Linux Apache、MySQL(MariaDB)和 PHP。

角色和集合

对于 RHCE 考试 EX294,你应该只需要知道角色,而不是集合。目前的考试目标是:“本次考试基于 Red Hat Enterprise Linux 8 和 Red Hat Ansible Engine 2.8 。”我们使用的是 ansi ble 2.9 . x 版本,现在我们有了角色和集合。集合仅仅是角色的集合,技术并没有真正改变。集合可以使组织相关角色变得更容易,并提供简单的单一下载。我们将在本章中使用角色来添加 PHP 和 MySQL。这将是一个肤浅的角色,但集中在搜索和下载所需的角色。我们还将能够看到一个代码块中包含的几个新的独立任务,因此有很多值得期待的内容。

从 CLI 搜索角色

当从 Ansible 控制器的 CLI 或命令行环境中工作时,我们可以使用ansible-galaxy来搜索位于 Galaxy 存储库中的角色。如果我们只是搜索一个模块名,我们可能会发现我们有太多的结果可供选择。请记住,这些是社区创建的。认识一个可靠的作者会有所帮助,我们可以将作者姓名添加到搜索中。找到一个可能的匹配后,我们可以使用 info 子命令来列出更多的细节。一如既往,正如我们在本书中所宣传的那样,我们希望您在自己的实验室环境中进行实践。这将帮助你在工作和考试中变得熟练。

$ ansible-galaxy search php
Found 1075 roles matching your search. Showing first 1000.
...
$ ansible-galaxy search --author geerlingguy php
Found 24 roles matching your search:
...
$ ansible-galaxy info geerlingguy.php
...

Listing 13-1Searching for Roles from the CLI

当查看 info 子命令提供的输出时,下载计数可以帮助您了解角色的受欢迎程度。这里列出的作者在社区中很受尊敬,我自己也使用他的模块。命令行是可以的,但是 Galaxy 的 web 前端提供了更多关于角色的细节。浏览 https://galaxy.ansible.com 网站很简单,并提供对角色自述文件的访问,该文件比我们在命令行看到的简单的 info 输出更详细。尝试访问网站并找到相同的 PHP 模块。

安装 PHP 角色

安装这些现成的角色可以省去我们创建自己角色的麻烦和时间。我们当然可以自己安装 PHP,但是角色让我们不用研究包名。如果需要,我们还可以对 PHP 设置进行更改。

  • PHP:PHP 脚本引擎,用于命令行或 web 服务器
$ ansible-galaxy install geerlingguy.php
- downloading role 'php', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php/archive/4.5.1.tar.gz
- extracting geerlingguy.php to /home/tux/.ansible/roles/geerlingguy.php
- geerlingguy.php (4.5.1) was installed successfully- Role
$ ansible-galaxy list
# /home/tux/.ansible/roles
- firewall, (unknown version)
- standard_web, (unknown version)
- apache, (unknown version)
- geerlingguy.php, 4.5.1
# /usr/share/ansible/roles
# /etc/ansible/roles

Listing 13-2Installing PHP Role on the Ansible Controller Node

调查 PHP 角色并学习更好的编码

安装角色的默认路径是 $HOME/。ansi ble/roles/;如果您需要安装到不同的位置,您将需要使用选项--roles-path。我们可以使用tree命令列出角色的内容。

$ tree /home/tux/.ansible/roles/geerlingguy.php/
/home/tux/.ansible/roles/geerlingguy.php/
├── defaults
│   └── main.yml
├── handlers
│   └── main.yml
├── LICENSE
├── meta
│   └── main.yml
├── molecule
│   └── default
│       ├── converge.yml
│       ├── molecule.yml
│       ├── playbook-source.yml
│       └── requirements.yml
├── README.md
├── tasks
│   ├── configure-apcu.yml
│   ├── configure-fpm.yml
│   ├── configure-opcache.yml
│   ├── configure.yml
│   ├── install-from-source.yml
│   ├── main.yml
│   ├── setup-Debian.yml
│   └── setup-RedHat.yml
├── templates
│   ├── apc.ini.j2
│   ├── fpm-init.j2
│   ├── opcache.ini.j2
│   ├── php-fpm.conf.j2
│   ├── php.ini.j2
│   └── www.conf.j2
└── vars
    ├── Debian-10.yml
    ├── Debian-9.yml
    ├── Debian.yml
    ├── RedHat.yml
    ├── Ubuntu-16.yml
    ├── Ubuntu-18.yml
    └── Ubuntu-20.yml

Listing 13-3Listing the Role

进一步研究和学习,我们可以开始编写更好的代码。任务目录包含许多 YAML 文件,而不仅仅是 ?? 的 main.yml 文件。为了了解这是如何工作的,让我们列出内容 main.yml 。我们花时间查看完整的文件当然是值得的;为了在书中清晰地输出,我们只列出了其中的一部分。

$ grep -A10 Setup $HOME/.ansible/roles/geerlingguy.php/tasks/main.yml
# Setup/install tasks.
- include_tasks: setup-RedHat.yml
  when:
    - not php_install_from_source
    - ansible_os_family == 'RedHat'

- include_tasks: setup-Debian.yml
  when:
    - not php_install_from_source
    - ansible_os_family == 'Debian'

Listing 13-4The Tasks default.yml Includes Other YAML Files

我们可以看到作者 Jeff Geerling 在使用 Red Hat、Ubuntu 和基于 Debian 的系统的专业发行版文件中加入了额外的任务。CentOS 是 Red Hat OS 家族的一部分。我们可以进一步挖掘红帽文件,看看会执行什么。这总是值得研究的;毕竟,代码将在我们的系统中运行。我们希望确定正确的操作将会发生,并且我们可以通过查看他人的代码来学习。这是开源代码的一个基本前提。

$ cat $HOME/.ansible/roles/geerlingguy.php/tasks/setup-RedHat.yml
---
- name: Ensure PHP packages are installed.
  package:
    name: "{{ php_packages + php_packages_extra }}"
    state: "{{ php_packages_state }}"
    enablerepo: "{{ php_enablerepo | default(omit, true) }}"
  notify: restart webserver

Listing 13-5Listing the Red Hat Tasks

由于 web 服务器需要在添加 PHP 后重启,我们可以看到我们通知了一个处理程序来完成这项工作。处理程序与任务分开组织,位于各自的目录中。我们之前在自己的 Apache 角色中看到了这一点。

$ cat $HOME/.ansible/roles/geerlingguy.php/handlers/main.yml
---
- name: restart webserver
  service:
    name: "{{ php_webserver_daemon }}"
    state: restarted
  notify: restart php-fpm
  when: php_enable_webserver

- name: restart php-fpm
  service:
    name: "{{ php_fpm_daemon }}"
    state: "{{ php_fpm_handler_state }}"
  when:
    - php_enable_php_fpm
    - php_fpm_state == 'started'

Listing 13-6Listing Handlers in the PHP Role

在这个角色中使用了许多变量;例如,我们可以看到将要重启的 web 服务器是变量 php_webserver_daemon 。我们可以在角色的变量子目录中进一步搜索。

$ grep php_webserver_daemon \
   $HOME/.ansible/roles/geerlingguy.php/vars/RedHat.yml
__php_webserver_daemon: "httpd"

Listing 13-7Listing Role Variables

我们还可以从剧本或库存中控制这些变量。例如,我们将使用一个变量来确保我们将 PHP 链接到 web 服务器。我们将在下一节看到这一点。

安装 PHP

现在,我们至少对这个角色有点熟悉了,我们可以将它添加到 full_apache.yml 剧本中。我们将设置 PHP 变量来链接到 web 服务器,并创建一个简单的 PHP 页面,这样我们就可以测试 PHP 的操作。

$ vim $HOME/ansible/apache/full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  vars:
    - service_name: http
    - php_enable_webserver: true
  roles:
    - apache
    - firewall
    - standard_web
    - geerlingguy.php
  tasks:
    - name: add php page
      copy:
        dest: /var/www/html/test.php
        content: "<?php phpinfo(); ?>"

Listing 13-8Installing

PHP from the Role

运行剧本后,您将安装 PHP,web 服务器将重新启动。剧本中实现的变量使处理程序能够运行。为了测试这一点,您应该在您的主机系统上使用一个浏览器,并将其指向您的主机的 IP 地址;对我来说,这将是:http://172.16.120.161/test.php。您应该会看到一个彩色的表格,显示了您的 web 服务器和 PHP 的配置。

Note

CentOS 上的test.php文件应该显示正确。在 Ubuntu 上还需要做一些工作,我们将在本章后面研究 Ansible 中的代码块时添加。

添加额外的 PHP 模块

在实验室环境中,我们最终需要从 Apache 上运行的 PHP 代码连接到我们的数据库服务器。Jeff Geerling(Geerling guy)在这方面确实有一个角色,但在 CentOS 8 中没有更新。我们可以修改 geerlingguy.php-msql 角色来满足我们的需求;然而,安装所需的包是很容易的。通过这样做,我们可以证明我们可以利用 PHP 角色中的处理程序在安装所需模块后重启 web 服务器。我们不需要创建自己的处理程序。

我们需要为 CentOS 8 和 Ubuntu 18.04 安装的 PHP 软件包如下所示:

  • CentOS 8 : php-mysqlnd

  • Ubuntu 18:04:PHP 7.2-MySQL

到目前为止,我们已经很清楚我们的库存变量,很容易将这些包名添加到正确的文件组文件中。我们现在证明这一点。但是不要忘记,你应该在你自己的实验室里跟着做,所以不要只是阅读;你需要

$ echo "php_mysql: php7.2-mysql" >> $HOME/group_vars/ubuntu
$ echo "php_mysql: php-mysqlnd" >> $HOME/group_vars/centos
$ ansible-inventory --yaml --host 172.16.120.188
admin_group: sudo
ansible_python_interpreter: /usr/bin/python3
apache_cfg: /etc/apache2/sites-enabled/000-default.conf
apache_pkg: apache2
apache_user: www-data
firewall_pkg: ufw
php_mysql: php7.2-mysql
$ ansible-inventory --yaml --host 172.16.120.161
admin_group: wheel
ansible_connection: local
apache_cfg: /etc/httpd/conf/httpd.conf
apache_pkg: httpd
apache_user: apache
firewall_pkg: firewalld
php_mysql: php-mysqlnd
$ vim full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  vars:
    - service_name: http
    - php_enable_webserver: true
  roles:
    - apache
    - firewall
    - standard_web
    - geerlingguy.php
  tasks:
    - name: add php page
      copy:
        dest: /var/www/html/test.php
        content: "<?php phpinfo(); ?>"
    - name: Install mysql-php
      package:
        name: "{{ php_mysql }}"
      notify: restart webserver

Listing 13-9Adding Correct PHP MySQL Packages to the Systems, Allowing PHP to Talk to the Database Server

这一次,我们在团队库存而不是游戏本身中设置变量。根据主机的分布情况,所需的值会有所不同,因此最适合于清单。该任务将重新启动 web 服务器;该事件的处理程序在geerlingguy.php角色中,不需要重新定义处理程序。

Ubuntu 的代码块和额外配置

在 Ubuntu 18.04 上安装 Apache 默认不安装 PHP Apache 模块。我们需要安装并启用 Apache 模块。理想情况下,我们会专门为 Apache 将此添加到 Apache 角色中,但是有一个论点建议不要在 Apache 中安装您不需要的模块。不是每个 Apache 服务器都需要运行 PHP。目前,我们将在现有的剧本中添加任务,这样我们就可以演示如何在 Ansible 中使用代码块。代码块是一个附加的缩进层次,可以包含一个或多个任务。我们需要添加两个可以添加到代码块中的额外任务,将when子句添加到代码块中,以确保它只能在 Ubuntu 上运行。该限制是在代码块级别定义的,将影响代码块中的所有任务。

Note

可以将when子句添加到代码块中,但是notify运算符与代码块不兼容;我们为每个任务添加了notify操作符。

$ vim full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  vars:
    - service_name: http
    - php_enable_webserver: true
  roles:
    - apache
    - firewall
    - standard_web
    - geerlingguy.php
  tasks:
    - name: add php page
      copy:
        dest: /var/www/html/test.php
        content: "<?php phpinfo(); ?>"
    - name: Install mysql-php
      package:
        name: "{{ php_mysql }}"
      notify: restart webserver
    - name: Add Apache PHP and Enable on Ubuntu
      block:
        - name: Install Apache PHP Module
          apt:
            name: libapache2-mod-php
            state: present
          notify: restart webserver
        - name: Enable PHP Module
          apache2_module:
            state: present
            name: php7.2
          notify: restart web server
      when: ansible_distribution == "Ubuntu"

Listing 13-10Adding Code Blocks to Finalize Ubuntu Apache PHP Installation

Apache 模块与 apt 模块一起安装和启用。我们知道我们只在 Ubuntu 上使用这个,所以使用 apt 而不是。作为双重检查,我们使用 apache2_module 独立启用该模块。这包括手动禁用 Apache 模块的情况;这样,无论发生什么情况,我们都可以确保模块已安装并启用。

我们现在已经在每个系统上安装并运行了 Apache 和 PHP。我们将很快安装数据库服务器,但目前只需确保您可以在每个系统上显示info.php页面。记住,这应该显示一个带有图形和彩色表格列的大页面。

安装数据库角色

您可能会注意到,我没有明确说明我们正在安装的数据库服务器。我不想遮遮掩掩,但我会使用杰夫·格尔林的 mysql 模块。在 CentOS 8 上,它将安装 MariaDB,在 Ubuntu 18.04 上,它将安装 MySQL。两者都适用于我们,但是我们再次强调发行版之间的差异以及在多种 Linux 版本上学习 Ansible 的优势。

创建变量文件

我们将使用一个 vars_file: 参数,就像我们使用ansible-vault一样,而不是像我们到目前为止所做的那样直接将变量添加到剧本中。我们不需要加密文件来使用 vars_file: 参数,这一点已经向您演示过了。我们将 MySQL root 密码存储在变量文件中,所以请考虑加密该文件。你收集的练习越多,你对考试的准备就越充分。到目前为止,您已经完成了一项出色的工作,您不想忘记之前看过的任何内容。

$ vim $HOME/ansible/apache ; mkdir vars
$ vim vars/main.yml
---
mysql_root_password: Password1
mysql_root_password_update: true
mysql_enabled_on_startup: true
mysql_users:
  - name: bob
    host: "%"
    password: Password1
    priv: "*.*:ALL"

Listing 13-11Creating Variables for MySQL

这些变量用于一个新角色,我们将很快下载该角色。大多数变量都是不言自明的,但是我们确实在每个数据库服务器上创建了一个新用户。为了帮助创建用户,我们为每个需要的元素定义了一个字典。我们设置数据库用户名,允许该用户从任何主机访问,设置他们的密码,并允许访问所有数据库。拥有一个额外的帐户对我们的测试很有用,因为出于安全原因,MySQL root 帐户不能从本地主机之外的任何地方登录。

安装 MySQL 角色并实现数据库服务器

我们现在可以下载 mysql 角色,并从剧本中引用角色和变量文件。一如既往,我们应该在每个阶段测试剧本。所以请编辑后运行剧本;当你看到它运行时,你会非常高兴。相信我;你能搞定的!

$ ansible-galaxy install geerlingguy.mysql
$ vim  $HOME/ansible/apache/full_apache.yml
---
- name: Manage Apache Deployment
  hosts: all
  become: true
  gather_facts: true
  vars:
    - service_name: http
    - php_enable_webserver: true
  vars_files:
    - vars/main.yml
  roles:
    - apache
    - firewall
    - standard_web
    - geerlingguy.php
    - geerlingguy.mysql
  tasks:
    - name: add php page
      copy:
        dest: /var/www/html/test.php
        content: "<?php phpinfo(); ?>"
    - name: Install mysql-php
      package:
        name: "{{ php_mysql }}"
      notify: restart webserver
    - name: Add Apache PHP and Enable on Ubuntu
      block:
        - name: Install Apache PHP Module
          apt:
            name: libapache2-mod-php
            state: present
          notify: restart webserver
        - name: Enable PHP Module
          apache2_module:
            state: present
            name: php7.2
          notify: restart web server
      when: ansible_distribution == "Ubuntu"

Listing 13-12Downloading and Using the mysql Role

现在测试剧本应该显示数据库服务器的安装和新数据库用户的创建。

打开 MySQL 防火墙端口

我们将能够以根用户和 ?? 用户的身份进行本地连接,但是我们需要打开每个系统防火墙上的数据库端口,以远程用户身份进行连接。我们可以使用我们之前创建的现有的防火墙角色。我们将创建一个额外的重头戏,以允许我们使用新的服务定义再次执行角色。第二部剧本可以添加到现有剧本中。为了方便起见,我们将它添加为第一次播放,但是它是第一次还是第二次播放并不重要。对我来说,将它作为第一部戏意味着我只需要列出剧本的顶部,供您查看添加了什么。

$ vim full_apache.yml
---
# New Play 1
- name: Enable MySQL Port
  hosts: all
  gather_facts: true
  become: true
  vars:
    - service_name: mysql
  roles:
    - firewall
# Existing Play is now Play 2
- name: Manage Apache Deployment

Listing 13-13Adding a New Play to the Existing Playbook

我们现在已经完成了完整的 LAMP 安装,我们将能够从命令行测试数据库连接。我们应该能够作为 MySQL bob 用户从控制器连接到每台主机。调整以下内容以匹配您自己的实验室 IP 地址。我们作为新用户进行连接,并列出每个系统上托管的标准数据库。

$ mysql  -h 172.16.120.188 -u bob -pPassword1 -e "SHOW DATABASES;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
$ mysql  -h 172.16.120.185 -u bob -pPassword1 -e "SHOW DATABASES;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+
$ mysql  -h 172.16.120.161 -u bob -pPassword1 -e "SHOW DATABASES;"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
+--------------------+

Listing 13-14Testing Database Connectivity

我希望这对你来说是成功的。如果没有,仔细阅读任何错误,并检查剧本和变量文件。坚持这样做是非常值得的。

摘要

哇,看看你做了什么!one Playbook 现在可以在 CentOS 8 和 Ubuntu 18.04 上可靠地安装完整的 LAMP 堆栈。没有什么会被忘记,这是重复正确的。我们遇到了配置管理的涅槃;在你继续之前,停下来好好思考一下。

我们之前已经看到,我们可以创建自己的角色,这对我们来说非常好,因为我们一直在磨练我们的技能。因此,在开发我们创建的初始角色时,我们利用这些技能确实是有意义的。现在我们有了这些技能,我们可以了解到有许多社区创建的角色来节省我们的努力。然而,如果我们不理解 Ansible,这些角色就没有多大用处,所以你的学习无论如何都没有浪费。

在本章中,我们还学习了新模块。我们使用了专门用于 Ubuntu 的 apt 模块和用于在 Ubuntu 中启用和禁用 apache 模块的 apache_module Ansible 模块。这引导我们学习代码块,允许我们将任务隔离到特定的when子句。

我们创建的最终解决方案是您应该保留和存档的。这是一种宝贵的资源,不应该浪费;我相信你会想留着这个以备将来使用。

十四、使用 Ansible 配置存储

在 Linux 和许多其他系统中,我们可以使用 Ansible 管理许多项目。到目前为止,我们已经集中讨论了 web 服务器的管理,我真心希望这对您学习 Ansible 配置管理有所帮助。现在我们将换一种方式,看看如何管理 Linux 中的存储子系统。我们将学习磁盘分区,并在创建文件系统和挂载这些文件系统之前创建逻辑卷。CentOS 7.5 的新功能是虚拟数据优化器 VDO。使用 VDO,我们可以了解如何创建支持数据压缩和重复数据删除的卷,当然这将通过 Ansible 进行管理。

Note

本章中的演示将仅使用控制器节点进行,因为我们需要向节点添加额外的数据块存储。欢迎您向每个节点添加存储,或者使用额外的未使用存储来部署您的系统。

Block Devices

我们当然可以向控制器中添加额外的虚拟磁盘,以便为我们提供更多的块设备。或者,我们可以在 Linux 中创建环回设备来实现虚拟块设备。这比添加外部磁盘更容易,因为我们都是从 Linux 命令行执行的,那里有可用的空闲磁盘空间。我的控制器默认为 20GB 的驱动器,留给我 15GB 的可用空间,这已经足够了。我们将添加额外的数据块设备来支持练习,为 VDO 使用 5GB 的磁盘,因为要求最小大小为 4GB。

Creating Loopback Devices

环回设备是 Linux 中的内部虚拟块设备。例如,我们可以用它来将 ISO 文件安装到环回设备上。在实验中,我们将创建原始文件,然后将它们作为环回设备进行连接。要列出 Linux 中当前的块设备,我们可以使用命令lsblk

$ lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda            8:0    0   20G  0 disk
├─sda1        8:1    0    1G  0 part /boot
└─sda2        8:2    0   19G  0 part
  ├─cl-root 253:0    0   17G  0 lvm  /
  └─cl-swap 253:1    0    2G  0 lvm  [SWAP]
sr0           11:0    1  6.7G  0 rom

Listing 14-1Listing Block Devices in Linux from the CLI

查看类型列,我们看到磁盘分区逻辑卷,以及一个光盘。目前我们没有回路装置。如果我们列出了回路设备,我们可以用losetup命令单独列出它们。如果没有回路装置,losetup的输出将为空。

$ losetup

Listing 14-2Listing Loop Devices in Linux Using losetup

创建环路设备的第一步是创建用作存储的后端文件。这是通过命令ddfallocate创建的。我们用fallocate因为它更快。

$ cd $HOME/ansible ; mkdir disk ; cd disk
$ fallocate -l 1G disk0 # The option is -l for length
$ ls -lh disk0
-rw-rw-r--. 1 tux tux 1.0G Dec 10 13:59 disk0

Listing 14-3Creating a 1GB Raw Disk File Using fallocate

回顾一下演示的命令,我们首先创建一个新的 Ansible 项目目录,然后在磁盘项目目录中创建一个 1GB 的文件,其中包含fallocate。我们现在可以使用这个原始文件链接到一个回路设备。目前我们没有任何环路设备,这是第一个可用的设备 /dev/loop0

$ sudo losetup /dev/loop0 disk0
$ losetup
NAME       SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE                    DIO LOG-SEC
/dev/loop0         0      0         0  0 /home/tux/ansible/disk/disk0   0     512
$ lsblk
NAME         MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
loop0          7:0    0    1G  0 loop
sda            8:0    0   20G  0 disk
├─sda1        8:1    0    1G  0 part /boot
└─sda2        8:2    0   19G  0 part
  ├─cl-root 253:0    0   17G  0 lvm  /
  └─cl-swap 253:1    0    2G  0 lvm  [SWAP]
sr0           11:0    1  6.7G  0 rom

Listing 14-4Creating Our First Linux Loop Device

losetup列出循环设备现在显示了我们新的块设备,就像lsblk命令一样。我们可以像使用任何读/写块设备一样使用 /dev/loop0 。当我们考虑对这个设备进行分区时,这就是我们开始使用 Ansible 的地方。

Partitioning Disks and Mounting Filesystems

我们将使用 Ansible 添加一个新的分区,然后向其中添加一个 XFS 文件系统,并将该文件系统挂载到一个新创建的目录中。挂载分区还会在 /etc/fstab 文件中添加一个条目,以便在重启时保存文件系统。由于我们现在非常熟悉 Ansible,我们将在磁盘项目目录中创建一个完整的新剧本,其中包含所有必需的任务。

Note

本演示中的主机参数应设置为清单中使用的控制器节点的 IP 地址。

$ vim partition.yml
- name: Partition disk/filesystem/mount
  hosts: 172.16.120.161
  gather_facts: no
  become: true
  tasks:
    - name: Partition loop0 P1
      parted:
        device: /dev/loop0
        part_start: 0%
        part_end: 50%
        number: 1
        state: present
    - name: Create XFS filesystem on P1
      filesystem:
        dev: /dev/loop0p1
        fstype: xfs
    - name: Create mount point
      file:
        path: "{{ item }}"
        state: directory
      loop:
        - /data
        - /data/p1
    - name: Mount P1 to /data/p1
      mount:
        path: /data/p1
        src: /dev/loop0p1
        fstype: xfs
        state: mounted
$ ansible-playbook partition.yml
$ tail -n1 /etc/fstab
/dev/loop0p1 /data/p1 xfs defaults 0 0
$ mount -t xfs
/dev/mapper/cl-root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/loop0p1 on /data/p1 type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

Listing 14-5Partitioning Disk and Mounting filesystem with Ansible

既然我们已经熟练地创建了这些剧本,那么通过剧本运行这些任务可能比在命令行使用原始命令更快。现在为您列出所使用的 Ansible 模块:

  • parted :与命令行中的 parted 命令用法大致相同。我们可以使用这个模块在磁盘上创建和添加分区。

  • 文件系统 :用于格式化块设备

  • 文件 :文件模块,保证文件及其属性的存在与否。在这里,我们确保它们是目录。

  • mount:mount 模块用于挂载或卸载文件系统,在 /etc/fstab 文件中添加或删除文件系统。

Note

向 fstab 文件中添加条目应该可以确保在重新引导后挂载仍然存在。我们的系统不会出现这种情况,因为使用 losetup 创建的环路设备不会持续重新启动。可以用一个新的 systemd 单元文件编写脚本,在重新启动时创建环路设备。

Managing Logical Volumes

不使用完整的磁盘或分区,LVM 或逻辑卷通常被用作替代。这些是动态磁盘,可以以物理磁盘或分区无法实现的方式轻松扩展。我们可以创建一个 partition.yml 剧本的副本,将其命名为 lvm.yml ,让 Ansible 管理逻辑卷。

$ cp partition.yml lvm.yml
$ vim lvm.yml
---
- name: Using LVMs
  hosts: 172.16.120.161
  gather_facts: no
  become: true
  tasks:
    - name: Partition loop0 P2
      parted:
        device: /dev/loop0
        part_start: 50%
        part_end: 100%
        number: 2
        flags: [ lvm ]
        state: present
    - name: Create Volume Group
      lvg:
        vg: vg1
        pvs: /dev/loop0p2
    - name: Create LV
      lvol:
        lv: lv1
        vg: vg1
        size: 100%FREE
        shrink: false
    - name: Create XFS filesystem on lv1
      filesystem:
        dev: /dev/vg1/lv1
        fstype: xfs
    - name: Create mount point
      file:
        path: "{{ item }}"
        state: directory
      loop:
        - /data
        - /data/lv1
    - name: Mount lv1 to /data/lv1
      mount:
        path: /data/lv1
        src: /dev/vg1/lv1
        fstype: xfs
        state: mounted
$ ansible-playbook lvm.yml
...
$ tail -n1 /etc/fstab
/dev/vg1/lv1 /data/lv1 xfs defaults 0 0
$ mount -t xfs
/dev/mapper/cl-root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/loop0p1 on /data/p1 type xfs (rw,relatime,seclabel,attr2,inode64,noquota)
/dev/mapper/vg1-lv1 on /data/lv1 type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

Listing 14-6Managing Logical Volumes with Ansible Playbooks

复制文件的一个副作用是认为我们已经做了所有必要的修改,而实际上并没有。编辑剧本时要小心,以做出所需的更改;这些包括设备名和分区号。已编辑的现有内容会在我的输出中突出显示。不过,我们可以看到,当与 Ansible lvglvol 模块结合使用时,管理逻辑卷就像管理物理设备一样简单。不要忘记我们引入的这些新模块,使用ansible-doc lvol命令或任何你需要帮助的模块来研究可以做什么。

Managing VDO with Ansible

VDO 是 RHEL 8 的新功能之一,但它的实际亮相是在 RHEL 7.5。使用 VDO,我们可以在块设备和文件系统之间创建一个额外的内核层,从而实现重复数据消除和压缩。

Updating a Managed Host

我们需要确保安装了 VDO 工具和内核模块。安装 VDO 内核模块将确保我们也安装了最新的内核。因此,最好检查系统是否更新和重启,以确保我们使用正确的内核启动。这可以用 Ansible 来完成,包括重新启动,但是由于我们是在控制器节点上工作,所以在重新启动时,我们将删除我们自己到 Ansible 引擎的连接。为了演示这一点,我们将首先使用 CentOS 8 客户端进行更新,然后在控制器上手动进行更新。我们不会将此客户端系统用于 VDO,仅用于控制器。我只是想演示更新和重启以及一些额外的功能。

我们只希望在需要更新时重启,因此我们知道使用一个处理程序。默认情况下,处理程序将在所有任务之后运行。在真实的 VDO 部署中,如果添加了新的内核,我们需要重启受管设备,确保运行的内核与内核模块的版本相匹配。重启需要在剧本通过创建 VDO 设备继续之前发生。理想情况下,我们应该有一个单一的剧本来执行更新、重启和 VDO 创建。为了确保重启发生在剩余的 VDO 任务之前,我们使用 Ansible meta 模块在正确的时间强制重启处理程序。

除了学习模块,我们还想看看使用变量的新方法。我们将在客户机系统重新启动后运行一个新任务来收集内核版本。除了将版本直接打印到 Ansible controllers 屏幕上,我们还可以使用寄存器操作符将输出存储在一个数组变量中。这对你学习寻找可变人口的新选择很有帮助,好像我们还没看够似的!

$ vim update.yml
---
- name: Update and reboot
  hosts: 172.16.120.185
  gather_facts: no
  become: true
  tasks:
    - name: Update all packages
      package:
        name: '*'
        state: latest
      notify: reboot
    - name: run handlers now
      meta: flush_handlers
    - name: Collect Kernel
      shell: "uname -r"
      register: kernel_version
    - name: Show Kernel
      debug:
        msg: "The kernel is: {{ kernel_version.stdout }}"
  handlers:

    - name: reboot
      reboot:
$ ansible-playbook update.yml
PLAY [Update and reboot]

TASK [Update all packages]
changed: [172.16.120.185]

RUNNING HANDLER [reboot]
changed: [172.16.120.185]

TASK [Collect Kernel]
changed: [172.16.120.185]

TASK [Show Kernel] ***********************************************************************************************************
ok: [172.16.120.188] => {
    "msg": "The kernel is: 4.18.0-240.1.1.el8_3.x86_64"
}

Listing 14-7Rebooting the Client Device After an Update

Note

变量 kernel_version 是一个存储很多元素的数组,不仅仅是 stdout 。例如,这些包括执行的命令和开始时间。如果您列出完整的变量,您将看到完整的内容。我们只需要从 kernel_version.stdout 获取的输出。

完整的剧本将会运行。重启后,我们应该会看到打印的内核版本。再次运行剧本,我们应该观察到重启没有作为处理程序发生;它仅在更新发生时执行。

Updating the Controller

Note

在 CentOS 8.3 中,VDO 的内存需求增加了。如果您的测试系统使用小于 1GB 的 RAM 运行,我建议将分配给 VM 的 RAM 增加到 2GB。如有必要,您可以减少现在运行的虚拟机,只使用控制器节点。

为了更新控制器,我们将手动运行yum,然后重启。请记住,我们这样做是为了确保我们拥有最新的内核,这样当我们添加内核模块时,版本就会匹配。在我们重启之前,我们可以选择注释添加到 /etc/fstab 文件中的两个新行。我们创建的环路设备将在重启时丢失。作为快速编辑,我们选择使用sed删除文件的最后一行。我们运行该命令两次,确保新添加的两行被删除。这是一个快速编辑,但你需要确定你需要删除最后两行,它们是你所期望的。小心你自己的系统。

$ sudo sed -i '$d' /etc/fstab
$ sudo sed -i '$d' /etc/fstab
$ sudo yum update -y && reboot

Listing 14-8Updating the Controller Node and Rebooting

在控制器的命令行工作,我们可以更新整个系统;只有当yum命令成功时,我们才会重启。

Deploying VDO

我们需要为环路设备创建一个至少 5GB 的新原始磁盘文件,该文件将用作 VDO 的底层存储。VDO 需要至少 4GB 的存储空间,其中大部分空间被用作缓存驱动器,以便在驱动器剩余空间有限的情况下扩展压缩文件。

$ cd $HOME/ansible/disk
$ fallocate -l 5G disk1
$ sudo losetup /dev/loop1 disk1

Listing 14-9Creating Loop Device for VDO

我们现在可以用 Ansible 把注意力转向 VDO 了。VDO 是 the RHCSA 的一个目标,我们在此不做详细介绍。这足以安装 VDO,并创建和安装 VDO 设备。这仅在控制器节点上运行。

$ vim vdo.yml
---
- name: Managing VDO in Ansible
  hosts: 172.16.120.161
  become: true
  gather_facts: false
  tasks:
    - name: Install VDO
      package:
        name:
          - vdo
          - kmod-kvdo
        state: latest
    - name: Start VDO service
      service:
        name: vdo
        enabled: true
        state: started
    - name: Create VDO device
      vdo:
        name: vdo1
        state: present
        device: /dev/loop1
        logicalsize: 10G
    - name: Format VDO device
      filesystem:
        type: xfs
        dev: /dev/mapper/vdo1
    - name: Create Mount Point
      file:
        path: "{{ item }}"
        state: directory
      loop:
        - /data
        - /data/vdo
    - name: Mount VDO filesystem
      mount:
        path: /data/vdo
        fstype: xfs
        state: mounted
        src: /dev/mapper/vdo1
        opts: defaults,x-systemd.requires=vdo.service
$ ansible-playbook vdo.yml

PLAY [Managing VDO in Ansible]

TASK [Install VDO]
ok: [172.16.120.161]

TASK [Start VDO service]
ok: [172.16.120.161]

TASK [Create VDO device]
changed: [172.16.120.161]

TASK [Format VDO device]
changed: [172.16.120.161]

TASK [Create Mount Point]
ok: [172.16.120.161] => (item=/data)
changed: [172.16.120.161] => (item=/data/vdo)

TASK [Mount VDO filesystem]
changed: [172.16.120.161]

PLAY RECAP
172.16.120.161             : ok=6    changed=6    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Listing 14-10Managing VDO with Ansible

现在,您已经创建了一个 VDO 设备,并将其挂载到自己的目录中。如果获得空间,添加到此目录的文件将被自动压缩。例如,添加已经压缩的 JPEG 图像不会从额外的压缩中受益,而在此归档的文本日志文件会从压缩中受益。如果您将虚拟机映像或容器存储在这个目录中,将检查每个块以查看它是否在 VDO 设备的其他地方复制;如果是,则不需要复制该块。这些是目前存储设备的常见功能,很高兴看到它是 CentOS 以上版本的文件系统无关功能。

Archiving Files

当我们看文件系统时,我们也可以看看使用 Ansible archive 模块备份文件和目录。创建的文件的默认格式是使用 gzip 压缩算法,但是也可以使用其他格式。如果你想创建一个 tgz 档案,你必须指定一个目录作为源文件。我们可能希望使用它在每台主机上创建 Apache DocumentRoot 的归档。

$ cd $HOME/ansible/disk
$ vim archive.yml
---
- name: Backup web
  hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Archive DocRoot
      archive:
        path: /var/www/html/
        dest: /root/web.tgz
        format: gz
$ ansible-playbook archive.yml
$ ansible all -b -m command -a "tar -tzf /root/web.tgz warn=false"
  172.16.120.188 | CHANGED | rc=0 >>
    contact.html
    server.html
    test.php
    index.html
  172.16.120.185 | CHANGED | rc=0 >>
    index.html
    contact.html
    server.html
    test.php
  172.16.120.161 | CHANGED | rc=0 >>
    index.html
    contact.html
    server.html
    test.php

Listing 14-11Archiving Directories

剧本在每台主机上创建归档文件,我们可以使用 ad hoc 命令列出归档文件的内容。在这种情况下,我们关闭警告;如果我们不这样做,Ansible 建议我们可以使用的 unarchive 模块来代替的 tar 命令。我们只想列出内容,这就是为什么命令是好的。

Maintenance of Filesystems

本章最后一个目标是 Ansible 中任务的并行性。考虑有多少系统任务应该彼此并行运行。控制器节点上的资源越多,我们可以同时管理的系统就越多。默认分叉数量设置为 5;由于我们只有 3 个受管节点,这对我们来说不成问题。如果我们管理 50 台主机,这个相对较低的值可能会影响剧本的执行速度。

$ ansible-config dump | grep -i fork
DEFAULT_FORKS(default) = 5

Listing 14-12The Default Forks Are Set to 5

在 ansible.cfg 的【默认值】头中设置 forks=20 会增加一次可以管理的节点数量。除了在 ansible.cfg、中全局控制这个设置之外,我们可能希望为一个游戏中的任务控制这个设置,例如,如果我们需要执行一些文件系统的维护。一个原因可能是因为我们想要保护文件系统的挂载点。这将是三项任务:

  • 卸载文件系统

  • 更改装载点上的模式

  • 重新挂载文件系统

在挂载文件系统之前,我们应该确保挂载点目录是安全的。卸载的目录应该只能由 root 用户访问。当目录被挂载时,来自已挂载文件系统的根的权限将替换未挂载目录的权限。通过这种方式,我们可以防止用户在目录未安装时存储文件。

在进入下一个任务之前,我们将观察每个任务在三台主机上运行的默认行为。这意味着三台主机将同时不可用,即使只有几秒钟。我们可以配置这个重头戏,以确保在前进到下一个节点之前,整个重头戏只在一个节点上运行,同时只允许一个节点的文件系统不可用。我们将使用一个简单的调试消息来测试这一点,首先使用默认值,然后调整剧中的 serial 值。

$ cd $HOME/ansible/disk
$ vim serial.yml
---
- name: Serial demo
  hosts: all
  become: false
  gather_facts: false
  tasks:
    - name: task1
      debug:
        msg: "output1"
    - name: task2
      debug:
        msg: "output2"
$ ansible-playbook serial.yml

PLAY [Serial demo]
TASK [task1]
ok: [172.16.120.161] => {
    "msg": "output1"
}
ok: [172.16.120.185] => {
    "msg": "output1"
}
ok: [172.16.120.188] => {
    "msg": "output1"
}

TASK [task2] ok: [172.16.120.185] => {
    "msg": "output2"
}
ok: [172.16.120.161] => {
    "msg": "output2"
}
ok: [172.16.120.188] => {
    "msg": "output2"
}

Listing 14-13By Default, Each Task Is Executed on Each Node Before Moving On

使用默认设置,我们可以看到第一个任务在所有节点上运行,然后第二个任务在所有节点上执行。如果在转移到下一个节点之前,行动中的任务都在每个节点上运行很重要,我们可以修改剧本。

$ cd $HOME/ansible/disk
$ vim serial.yml
---
- name: Serial demo
  hosts: all
  become: false
  gather_facts: false
  serial: 1
  tasks:
    - name: task1
      debug:
        msg: "output1"
    - name: task2
      debug:
        msg: "output2"$ ansible-playbook serial.yml
PLAY [Serial demo]
TASK [task1]
ok: [172.16.120.161] => {
    "msg": "output1"
}

TASK [task2]
ok: [172.16.120.161] => {
    "msg": "output2"
}

PLAY [Serial demo]
TASK [task1]
ok: [172.16.120.185] => {
    "msg": "output1"
}

TASK [task2]
ok: [172.16.120.185] => {
    "msg": "output2"
}

PLAY [Serial demo]

TASK [task1]
ok: [172.16.120.188] => {
    "msg": "output1"
}

TASK [task2]
ok: [172.16.120.188] => {
    "msg": "output2"
}

Listing 14-14Ensuring All Tasks Complete on a Node Before Progressing to the Next Node

我们现在可以看到这出戏排了三次,而不是一次。使用 serial: 1 设置,每次在一个节点上执行播放。如果合适,这可以是更高的值或百分比值。

Summary

Ansible 是一个全功能的配置管理系统。尽管到目前为止,我们在书中主要讨论了 LAMP 部署,但是我们还可以管理更多的 Linux 元素。当然,在 Linux 之外,还有更多领域可以研究。虽然在本课程中只看了 Linux,但我们可以扩展到研究存储,这也是本章的目的。

在使用 Ansible 探索 Linux 存储的过程中,我们发现了许多新的 Ansible 模块。其中包括:

  • 分开 :分区磁盘

  • 文件系统 :在设备上创建文件系统

  • 文件 :我们之前使用过文件模块,但是在这里我们可以看到目录的状态。

  • 挂载 :挂载文件系统并写入 /etc/fstab

  • lvg :管理 LVM 的卷组

  • lvol :管理 LVM 卷

  • vdo :创建 vdo 设备

  • :管理可回复的元信息。我们使用它来强制处理程序在剩余任务之前运行。

  • :备份文件和目录

**我们不仅查看了存储,还可以看到使用 register 操作符将任务的输出存储在一个变量中以备后用。我们还提醒自己在所有任务之后处理程序运行的顺序。当我们需要在其他任务之前重启时,我们可以在重启任务之后直接使用 meta Ansible 模块和 flush_handlers 任务。通过这种方式,完整的剧本可以运行,但是需要重启的任务可以在完成之前等待设备重启。

我们最后看了如何控制在任何时候管理的节点数量。当我们想要更快的性能时,我们可能需要增加这个缺省值 5 个节点,或者像我们所做的那样,通过减少序列化,以便每次在一个节点上执行完整的操作,以满足可用性需求。**

十五、使用 Ansible 管理计划任务

在《RHCE 学习指南》的最后一章,我们将重点放在最后的小考试目标上,你需要知道如何在 Linux 下用 Ansible 创建和管理预定任务。这可能与 atdcrond 有关,其中 atd 非常适合调度 ad hoc Linux 命令,而 crond 适合调度需要定期运行的作业。为简单起见,您可以只针对 Ansible 控制器,但我们将针对所有主机,并在示例中包括 CentOS 和 Ubuntu。

使用 ATD 的特定 Linux 作业

在的守护进程(atd)允许您在 Linux 中调度需要不定期运行的作业,甚至可能只运行一次,比如当您需要在假期期间调度服务器之间的数据迁移时。

如果最低限度安装 CentOS 8 和 Ubuntu 18.04,则不会安装该服务。当然,我们将添加这一功能,并确保它在创建工作之前运行。创建一个安装和配置 atd 的角色是值得的,因此我们可以从任何需要在创建一个预定的作业的剧本中引用这个角色。

创建负责管理 ATD 的角色

我们应该是现在用ansible-galaxy创造角色的专家,这也是一个伟大的记忆慢跑者。

$ ansible-galaxy role init /home/tux/.ansible/roles/atd
- Role $HOME/.ansible/roles/atd was created successfully
$ vim $HOME/.ansible/roles/atd/tasks/main.yml
---
- name: Install AT
  package:
    name: at
    state: present
- name: Manage ATD
  service:
    name: atd
    enabled: true
    state: started

Listing 15-1Creating the ATD Role

在 At 创造就业机会的剧本

atd 的角色很简单,因为包和服务名称在我们的发行版中是一致的。创建这个角色还是很值得的,因为我们可能需要创建几个不同的剧本来安排的工作。这个角色意味着我们可以利用每个必要剧本中的一段代码。RHCSA 更详细地介绍了使用处的创建调度任务,但可以说处的用于调度可能只需要运行一次而不是定期运行的作业。我们可以使用完整的日期和时间或缩写来安排作业,例如星期二代表下星期二。我们在 Ansible 中没有太多的灵活性,而且我们被限制在一定数量的单元内。我们指定作业应该基于指定单元的计数来运行。如果我们想要一个作业明天运行,我们将指定count: 1units: days

$ mkdir $HOME/ansible/at ; cd $HOME/ansible/at
$ vim at.yml
---
- name: Create at job
  hosts: all
  become: true
  gather_facts: false
  roles:
    - atd
  tasks:
    - name: backup users database tomorrow
      at:
        command: 'tar -czf /root/users.tgz /etc/passwd /etc/group /etc/shadow'
        count: 1
        units: days
        unique: true
$ ansible-playbook at.yml
$ sudo atq
Mon Dec 14 11:53:00 2020 a root

Listing 15-2Creating at Jobs with Ansible

启用此工单的唯一性将确保我们在的工单数据库中只有一次此工单的列表。如果未设置此项,则每次执行剧本时都会创建作业。

使用 Cron 创建常规作业

使用 cron 调度常规作业在 Linux 中非常常见,服务和工具是默认安装的。如果我们需要为那些相同的文件创建一个常规备份,而不是一个单独的备份,我们可以使用 cron 。以下剧本将在 /etc/cron.d/ 目录下创建命名文件,并将在周一至周五上午 5:30 运行。

$ mkdir $HOME/ansible/cron ; cd $HOME/ansible/cron
$ vim cron.yml
---
- name: Manage Cron Entries
  hosts: all
  gather_facts: false
  become: true
  tasks:
    - name: Backup user database
      cron:
        name: Backup Users
        hour: 5
        minute: 30
        weekday: 1-5
        user: root
        job: 'tar -czf /root/user.tgz /etc/passwd /etc/shadow'
        cron_file: user_backup
$ ansible-playbook cron.yml
$ cat /etc/cron.d/user_backup
#Ansible: Backup Users
30 5 * * 1-5 root tar -czf /root/user.tgz /etc/passwd /etc/shadow

Listing 15-3Creating cron Entries with Ansible

如您所见,演示结束时在 /etc/cron.d 中列出了新创建的条目。我们分配给 cron 作业的名称在文件中显示为一个注释,使它容易被我们自己识别和回答。

摘要

你才刚刚开始你余下的开发工作或系统管理生涯。从这本书开始,你已经走了很长一段路,现在你已经准备好为自己的成功而奋斗。你掌握着自己未来的钥匙。在这最后一章中,我们能够在查看预定任务时整理一些考试目标的松散部分。您学习了如何使用 Ansible 中的 at 模块来处理不规则作业,以及如何使用 cron Ansible 模块来定期执行这些作业。

你现在应该确保你练习了我们在本章中展示的例子;一如既往,是你付出的努力决定了你的成功。我还建议您查看每一章,看看在不参考完整的分步说明的情况下,您可以完成多少演示。祝你好运,谢谢。

posted @ 2024-08-02 19:35  绝不原创的飞龙  阅读(6)  评论(0编辑  收藏  举报