原文:Red Hat Certified Engineer (RHCE) Study Guide
协议:CC BY-NC-SA 4.0
八、使用文件和模板
我们已经能够为 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 下载并安装角色
*** :从系统中删除角色**
***可以在 H O M E / 的目录中为每个用户创建角色。或者可以在控制器上的用户之间在 ∗ / e t c / a n s i b l e / r o l e s ∗ 或 ∗ / u s r / s h a r e / a n s i b l e / r o l e s ∗ 目录中共享。首先,我们在系统中没有任何角色;默认情况下, A n s i b l e 不安装角色。 ∗ HOME/的目录中为每个用户创建角色。或者可以在控制器上的用户之间在 */etc/ansible/roles* 或 */usr/share/ansible/roles* 目录中共享。首先,我们在系统中没有任何角色;默认情况下,Ansible 不安装角色。 * 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 子命令,我们可以创建角色所需的文件系统结构。这是可选的,因为我们可以自己创建目录和文件,但是坦率地说,我喜欢不用自己创建它们的便利。向
H
O
M
E
/
添加角色。
a
n
s
i
b
l
e
/
r
o
l
e
s
∗
目录允许我们创建的角色可以在我们的任何剧本中使用,就像我们使用
∗
HOME/添加角色。ansible/roles* 目录允许我们创建的角色可以在我们的任何剧本中使用,就像我们使用 *
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
创建环路设备的第一步是创建用作存储的后端文件。这是通过命令dd
或fallocate
创建的。我们用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 lvg 和 lvol 模块结合使用时,管理逻辑卷就像管理物理设备一样简单。不要忘记我们引入的这些新模块,使用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 个节点,或者像我们所做的那样,通过减少序列化,以便每次在一个节点上执行完整的操作,以满足可用性需求。**