编者按:本文节选自节选自《基于Linux的企业自动化》第五章。“第5章,使用Ansible构建用于部署的虚拟机模板,通过构建虚拟机模板来探索部署Linux的最佳实践,虚拟机模板将以实际操作的方式大规模部署在虚拟机管理程序上。”
名词解释:
- cloud-init: 提供云实例初始时自定义配置的能力,支持多个发行版和多个平台;
- docker-compose:业务只需要单个容器场时,可以用docker命令管理。如果业务需要多个容器,可以用docker-compose定义和运行它们;
- Ansible-vault:提供文件和变量的加密能力,可以用于保护密码等敏感数据。
5.3.3 编辑配置文件
到目前为止,我们已经执行的所有的配置工作都非常黑白分明,我们要么安装一些东西(无论是一个文件或一个软件包),或者我们可以同样容易地删除它(关于这一点的更多内容在清理一节叙述)。但是,如果需要更微妙的内容呢?在本章前面的“将文件传输到映像”一节中,我们将用我们自己的版本替换整个chrony.conf文件。然而,这可能有点太暴力了。例如,我们可能只需要更改文件中的一行,而将替换整个文件变成更改一行的工作量有点繁重,特别是当你考虑到配置文件可能会在将来的软件包版本中更新时。
让我们看看另一个常见的操作系统映像配置要求:SSH守护进程安全性。默认情况下,CentOS 7安装(如我们之前创建的安装)允许从root账户进行远程登录。出于安全原因,这是不可取的,所以问题是,我们如何更新SSH守护程序配置而不必替换整个文件呢?幸运的是,Ansible有用于此类任务的模块。
要执行此任务,lineinfile模块将派上用场。考虑以下角色,我们将其称为securesshd:
---
- name: Disable root logins over SSH
lineinfile:
dest: /etc/ssh/sshd_config
regexp: "^PermitRootLogin"
line: "PermitRootLogin no"
state: present
在这里,我们使用lineinfile模块来处理/etc/ssh/sshd_config文件。
我们告诉它寻找以PermitRootLogin开头的行(这可以防止我们意外地编辑已注释掉的行),然后用PermitRootLogin no替换这一行。
让我们在CentOS 7测试系统上尝试一下:
这正是我们想要的。不过,编写正则表达式需要非常小心。例如,SSH守护进程将处理在行首包含空格的配置行。但是,前面代码中的简单正则表达式不考虑空格,因此很容易错过其他有效的SSH配置指令。考虑所有可能的情况和文件的排列来设计正则表达式本身就是一门艺术,因此在创建和使用正则表达式时一定要小心谨慎。
提示
请注意,在正在运行的系统上,你还需要重新启动SSH服务以使此更改生效;但是,由于这是一个映像,我们将对其进行清理,然后关闭以供将来部署,因此无须在此处执行此操作。
在上传一个完整的文件和编辑一个现有的文件之间,使用模板是一个中间选择。Ansible Jinja2模板功能非常强大,非常有用,因为文件的内容可能会随某些变量参数的变化而变化。
再次考虑前面的chrony配置示例,我们传输了一个静态文件,其中包含一个硬编码的NTP服务器地址。如果你的企业依赖于一个静态NTP服务器(或一组静态NTP服务器),那么这是很好的,但是有些服务器依赖于不同的NTP服务器,具体取决于要部署的映像的位置。
让我们用一个名为templatentp的新角色来演示这一点。为了这个,我们将在roles/templatentp/templates中定义一个模板目录,并将一个包含以下内容的名为chrony.conf.j2的文件放在里面:
pool {{ ntpserver }} iburst maxsources 4
keyfile /etc/chrony/chrony.keys
driftfile /var/lib/chrony/chrony.drift
logdir /var/log/chrony
maxupdateskew 100.0
rtcsync
makestep 1 3
注意这个文件与前面的示例几乎相同,只是我们现在在文件的第一行有一个Ansible变量名来代替静态主机名。
让我们创建此角色的main.yml文件如下:
---
- name: Deploy chrony configuration template
template:
src: templates/chrony.conf.j2
dest: /etc/chrony.conf
owner: root
group: root
mode: '0644'
backup: yes
请注意,它与copy示例非常相似。我们的site.yml也只是略有不同,我们将用NTP服务器主机名定义此变量。Ansible中有许多地方都可以定义此类变量,由用户自行确定定义它的最佳位置:
---
- name: Run example roles
hosts: all
become: yes
vars:
ntpserver: time.example.com
roles:
- templatentp
最后,我们可以运行剧本并查看结果:
这样,Ansible为你提供了强大的工具,不仅可以将整个配置复制或下载到位,还可以操纵现有配置以适应你的环境。
假设我们的映像现在已经完成了。我们可以相信这一点,但良好的实践表明,我们应该始终测试任何构建过程的结果,尤其是自动构建过程的结果。幸好,Ansible可以帮助我们验证我们根据需求创建的映像,我们将在下一节中对此进行探讨。
5.3.4 验证映像构建
以及安装和配置映像时,你可能还希望验证某些关键组件以及你假定存在的组件是否确实存在。当你下载由其他人创建的映像时尤其如此。
在Ansible中,有许多方法都可以执行此任务,我们举一个简单的例子。假设你有一个存档脚本,它使用bzip2压缩实用程序来压缩文件。这只是一个很小的工具,但是如果你出于某些目的依赖它,那么如果它不存在,你的脚本就会中断。这也是一个相关的例子,因为CentOS 7的最小安装(正如我们之前执行的)实际上并不包括它!如何解决这个问题呢?我们可以采取两种方法。首先,我们从Ansible的早期背景工作中了解到,大多数模块都是幂等的,也就是说,它们的设计目的是在目标主机上实现所需的状态,而不会重复已经执行的操作。
因此,我们可以很容易地在配置剧本中包含这样一个角色:
---
- name: Ensure bzip2 is installed
yum:
name: bzip2
state: present
当运行此角色而未安装bzip2时,它将执行安装并返回changed的结果。当它检测到安装了bzip2时,它将返回ok并且不执行进一步的操作。然而,如果我们真的想检查一些东西,而不是仅仅执行一个操作,也许作为一个构建后步骤呢?在本书后面,我们将研究更详细的审计系统的方法,但是现在,让我们用Ansible进一步说明这个示例。
如果你使用的是shell命令,那么可以通过以下两种方法之一检查bzip2的存在,即查询RPM数据库以查看是否安装了bzip2包,或者检查文件系统上是否存在/bin/bzip2。
让我们在Ansible中看看后一个示例。Ansible stat模块可用于验证文件是否存在。考虑以下代码,我们将以常规的方式在名为checkbzip2的角色中创建这些代码:
---
- name: Check for the existence of bzip2
stat:
path: /bin/bzip2
register: bzip2result
failed_when: bzip2result.stat.exists == false
- name: Display a message if bzip2 exists
debug:
msg: bzip2 installed.
这里,我们使用stat模块告诉我们关于/bin/bzip2文件的状态(是否存在)。我们在一个名为bzip2result的变量中register(注册)stat模块运行的结果,然后在任务上定义一个自定义故障条件,如果文件不存在,该条件将导致任务失败(从而使整个剧本运行失败)。请注意,当遇到故障情况时,Ansible会停止整个剧本的运行,迫使你在继续之前解决问题。
显然,这可能是你想要的行为,也可能不是,但是很容易相应地改变故障条件。
2.让我们实际看看这个:
如你所见,由于遇到故障,debug语句从未运行过。因此,在运行这个角色时,我们完全可以确定我们的映像将安装bzip2,如果不安装,我们的剧本将失败。
3.一旦安装了bzip2,运行情况看起来就完全不同了:
它的行为非常明确,这正是我们想要的。Ansible不仅仅局限于检查文件,尽管我们还可以检查sshd_config文件是否具有我们之前查看过的Permitrologin no行:
1.我们可以使用如下角色来完成此操作:
---
- name: Check root login setting in sshd_config
command: grep -e "^PermitRootLogin no" /etc/ssh/sshd_config
register: grepresult
failed_when: grepresult.rc != 0
- name: Display a message if root login is disabled
debug:
msg: root login disabled for SSH
2.现在,在设置未就位时再次运行此命令将导致故障:
3.然而,如果我们把这个设置到位,我们会看到以下情况:
同样,这是非常明确的。注意前面输出中的changed状态,这是因为我们使用了command(命令)模块,它成功地运行了命令,因此,它总是返回changed。如果需要的话,我们可以通过对该任务使用changed_when子句来更改此行为。
通过这种方式,多个Ansible 剧本可以放在一起,不仅可以自定义构建,还可以验证最终结果。这对于测试目的,并且安全性是一个考虑因素时尤其有用。
在完成本章之前,让我们在下一节中看一看,我们如何将我们迄今为止讨论过的所有不同角色和代码片段组合在一起,形成一个内聚的自动化解决方案。
5.3.5 综合
在本章的这一节中,你将注意到我们在所有示例中都使用了角色。当然,当谈到建立你的最终映象时,你不想像我们在这里所做的那样单独运行大量的剧本。幸运的是,如果我们要合并所有内容,我们需要做的就是将所有角色全都放在roles/子目录中,然后在site.yml剧本中引用它们。角色目录应该是这样的:
~/hands-on-automation/chapter05/example09/roles> tree -d
.
├── checkbzip2
│ └── tasks
├── checksshdroot
│ └── tasks
├── filecopyexample
│ ├── files
│ └── tasks
├── installbzip2
│ └── tasks
├── packageinstall
│ └── tasks
├── securesshd
│ └── tasks
└── templatentp
├── tasks
└── templates
然后,我们的site.yml文件将如下所示:
---
- name: Run example roles
hosts: all
become: yes
roles:
- filecopyexample
- packageinstall
- templatentp
- installbzip2
- securesshd
- checkbzip2
- checksshdroot
运行此代码留给读者作为练习,因为我们已经在本章前面运行了它的所有组成部分。但是,如果一切顺利,那么当所有角色都完成时,应该没有failed的状态,只有changed和ok的混合状态。
如果你已经完成了构建后定制的过程(如本章所述),那么生成的映像可能需要再次清理。我们可以再次使用virtsysprep命令,不过,Ansible也可以帮助我们。在下一节中,我们将探讨如何使用Ansible清理映像以进行大规模部署。