Skip to main content

第16章 使用Ansible服务实现自动化运维

章节简述

Ansible是近年来备受瞩目的一款开源运维自动化工具,堪称运维领域的翘楚。它极大地提升了运维人员的工作效率,同时减少了人为错误的发生,可谓事半功倍。Ansible包含数千个功能丰富且实用的模块,并附有详尽的帮助信息,即使是初学者也能迅速上手,如鱼得水。

本章首先介绍了Ansible的起源、关键术语以及主机清单的配置,带领大家深入学习ping、yum、firewalld、service、template、setup、lvol、lvg、copy、file、debug等十多个常用的Ansible模块,以满足日常运维的需求。接着,本章通过动手实操,详解如何从系统中加载角色、从外部获取角色以及自行创建角色的方法,旨在让读者在生产环境中能够得心应手地管理和控制任务流程。此外,本章还通过精心编写的playbook文件,演示了如何创建逻辑卷设备、依据主机改写文件以及管理文件属性的方法。最后,章节以使用Ansible的vault功能对变量和playbook文件进行加密的内容收尾。

本章全面涵盖了Ansible的使用细节,内容环环相扣,相信读者在学完本章内容之后会有酣畅淋漓之感。

16.1 Ansible介绍与安装

Ansible是当前运维自动化工具中简便易用的典范,堪称运维人员的得力助手。它能够高效地管理各种资源,并通过自动部署应用程序,实现IT基础架构的全面自动化。借助Ansible,我们可以轻松完成服务器的初始化配置、安全基线配置,以及系统更新和补丁管理等操作。与Chef、Puppet、SaltStack等采用C/S(客户端/服务器)架构的自动化工具相比,尽管Ansible在性能上并非最佳,但由于其基于SSH远程会话协议,不需要安装客户端程序,只需掌握受控主机的账号密码,便可直接通过SSH协议进行远程控制。因此,Ansible在使用便捷性上占据了显著优势,称得上是运维领域的“利器”。

2012年2月,程序员Michael DeHaan发布了Ansible的第一个版本。Michael DeHaan在配置管理和架构设计领域拥有丰富的经验,他在红帽公司任职期间,曾研发了Cobbler自动化系统安装工具。然而,面对现有自动化工具的复杂性和局限性,他深感不满,遂决心打造一款集众家所长的自动化工具。Ansible因此应运而生。凭借其卓越的易用性和强大功能,Ansible迅速获得了运维界的广泛认可,其在GitHub上的star和fork数量远超SaltStack,足以见其受欢迎程度。2015年,Ansible正式被红帽公司收购,为其未来发展增添了无限潜力。

使用自动化运维工具,能够肉眼可见地提高运维人员的工作效率,并减少人为错误。Ansible服务本身并没有批量部署的功能,它仅仅是一个框架,真正具有批量部署能力的是其所运行的模块。Ansible内置了上千个模块,会在安装Ansible时一并安装,通过调用指定的模块,就能实现特定的功能。Ansible内置的模块非常丰富,几乎满足了一切需求,使用起来也非常简单,一条命令甚至影响上千台主机。如果需要更高级的功能,也可以运用Python语言对Ansible进行二次开发。

如今,Ansible已被Amazon、Google、Microsoft、Cisco、HP、VMware、Twitter等大科技公司接纳并投入使用,并在实践中展现出非凡的性能和灵活性。红帽公司更是对自家产品进行了不遗余力的支持。从2020年8月1日起,RHCE考试的内容由配置多款服务转变成Ansible专项考题内容。现在,要想顺利拿到RHCE认证,真的有必要好好学习一下本章了。

在正式介绍Ansible之前,我们先来熟悉一下相关的专用术语,好让大家对术语有统一的理解,以便在后续实验时能直奔主题。这里整理的与Ansible相关的专用术语如表16-1所示。

表16-1 Ansible服务专用术语对照表

术语中文叫法含义
Control node控制节点指安装了Ansible服务的主机,用于发布运行任务、调用模块,对其他主机进行批量控制。
Managed nodes受控节点被Ansible服务管理的主机,是模块命令的执行对象,也称为受控主机或客户端。
Inventory主机清单受控节点的列表,可以是IP地址、主机名称或域名。
Modules模块指特定功能代码,Ansible默认自带上千款功能模块,也可以在Ansible Galaxy中选择其他模块。
Task任务Ansible客户端上要执行的操作。
Playbook剧本用YAML语言编写的可重复执行的任务列表,将常用操作写入剧本文件,下次可直接执行。
Roles角色用于结构化组织Playbook,通过调用角色实现一连串功能。

由于受控节点不需要安装客户端,加之SSH协议是Linux系统的标配,因此可以直接通过SSH协议进行远程控制。在控制节点上,也无需每次都重复启动服务程序,只需使用ansible命令直接调用模块即可进行控制。

RHEL 10系统的镜像文件默认不包含Ansible服务程序,用户需要从Extra Packages for Enterprise Linux(EPEL)扩展软件包仓库获取。EPEL软件包仓库由红帽公司提供,是一个用于创建、维护和管理企业版Linux的高质量软件扩展仓库,通用于RHEL、CentOS、Oracle Linux等多种红帽系企业版系统,目的是对于默认系统仓库软件包进行扩展。

下面准备在系统上部署Ansible服务程序。

第1步:在“虚拟机设置”界面中,将“网络适配器”的“网络连接”选项调整为“桥接模式”,并将系统的网卡设置成“Automatic(DHCP)”模式,如图16-1及图16-2所示。

图16-1 将“网络连接”设置为“桥接模式”

图16-2 将网卡设置为“Automatic(DHCP)”模式

在大多数情况下,只要把虚拟机设置成桥接模式,且Linux系统的网卡信息与物理机相同,然后再重启网络服务,就可以连接外部网络了。如果不放心,就通过ping命令测试下吧。

root@linuxprobe:~# nmcli connection up ens160 
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/3)
root@linuxprobe:~# ping -c 4 www.linuxprobe.com
PING www.linuxprobe.com.w.kunlunno.com (124.95.157.160) 56(84) bytes of data.
64 bytes from www.linuxprobe.com (124.95.157.160): icmp_seq=1 ttl=53 time=17.1 ms
64 bytes from www.linuxprobe.com (124.95.157.160): icmp_seq=2 ttl=53 time=15.6 ms
64 bytes from www.linuxprobe.com (124.95.157.160): icmp_seq=3 ttl=53 time=16.8 ms
64 bytes from www.linuxprobe.com (124.95.157.160): icmp_seq=4 ttl=53 time=17.5 ms

--- www.linuxprobe.com.w.kunlunno.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 10ms
rtt min/avg/max/mdev = 15.598/16.732/17.452/0.708 ms

第2步:在原有软件仓库配置的下方,追加EPEL扩展软件包安装源的信息。

root@linuxprobe:~# vim /etc/yum.repos.d/rhel10.repo
[BaseOS]
name=BaseOS
baseurl=file:///media/cdrom/BaseOS
enabled=1
gpgcheck=0

[AppStream]
name=AppStream
baseurl=file:///media/cdrom/AppStream
enabled=1
gpgcheck=0

[EPEL]
name=EPEL
baseurl=https://dl.fedoraproject.org/pub/epel/10/Everything/x86_64/
enabled=1
gpgcheck=0

第3步:安装!

ansible-core是Ansible服务程序的软件包名,而sshpass软件包的功能是让服务器可以远程管理受控节点:

root@linuxprobe:~# dnf install ansible-core sshpass
Updating Subscription Management repositories.
BaseOS 2.7 MB/s | 2.7 kB 00:00
AppStream 2.7 MB/s | 2.8 kB 00:00
EPEL 1.3 MB/s | 3.8 MB 00:03
Dependencies resolved.
========================================================================================================
Package Architecture Version Repository Size
========================================================================================================
Installing:
ansible-core noarch 1:2.16.3-3.el10 AppStream 3.9 M
sshpass x86_64 1.09-10.el10_1 EPEL 27 k
Installing dependencies:
python3-argcomplete noarch 3.2.2-3.el10 AppStream 90 k
python3-cffi x86_64 1.16.0-5.el10 BaseOS 312 k
python3-cryptography x86_64 43.0.0-2.el10 BaseOS 1.4 M
python3-jinja2 noarch 3.1.4-2.el10 AppStream 330 k
python3-markupsafe x86_64 2.1.3-5.el10 AppStream 36 k
python3-ply noarch 3.11-24.el10 BaseOS 139 k
python3-pycparser noarch 2.20-15.el10 BaseOS 162 k
python3-resolvelib noarch 1.0.1-5.el10 AppStream 49 k
………………省略部分输出信息………………
Installed:
ansible-core-1:2.16.3-3.el10.noarch python3-argcomplete-3.2.2-3.el10.noarch
python3-cffi-1.16.0-5.el10.x86_64 python3-cryptography-43.0.0-2.el10.x86_64
python3-jinja2-3.1.4-2.el10.noarch python3-markupsafe-2.1.3-5.el10.x86_64
python3-ply-3.11-24.el10.noarch python3-pycparser-2.20-15.el10.noarch
python3-resolvelib-1.0.1-5.el10.noarch sshpass-1.09-10.el10_1.x86_64

Complete!

由于ansible-core默认只安装了核心模块,像是lvg、parted、lvol、mount这样的模块属于community.general集合(collection),需要手动安装:

root@linuxprobe:~# ansible-galaxy collection install community.general
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/community-general-10.5.0.tar.gz to /root/.ansible/tmp/ansible-local-3064xaqti5li/tmpt0je0dr4/community-general-10.5.0-z14ni88o
Installing 'community.general:10.5.0' to '/root/.ansible/collections/ansible_collections/community/general'
community.general:10.5.0 was installed successfully

安装完毕后,Ansible服务便默认已经启动。使用--version参数可以看到Ansible服务的版本及配置信息。

root@linuxprobe:~# ansible --version
ansible [core 2.16.3]
config file = /etc/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3.12/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.12.5 (main, Aug 23 2024, 00:00:00) [GCC 14.2.1 20240801 (Red Hat 14.2.1-1)] (/usr/bin/python3)
jinja version = 3.1.4
libyaml = True

16.2 设置主机清单

在初次使用Ansible时,可能会遇到这样的问题:参数明明已经修改,但却未生效。这通常是由于Ansible主配置文件的优先级顺序所导致的。Ansible的主配置文件通常存放在/etc/ansible目录中,但其优先级最低。如果在当前目录或用户家目录中存在另一份主配置文件,则Ansible会优先使用这些文件。当多个主配置文件同时存在时,具体优先级顺序如表16-2所示。

表16-2 Ansible服务主配置文件优先级顺序

优先级文件位置
./ansible.cfg
~/.ansible.cfg
/etc/ansible/ansible.cfg

既然Ansible服务是用于实现主机批量自动化控制的管理工具,受管的主机一定不是一两台台,而是数十台甚至成百上千台,那么主机清单(inventory)在生产环境中就帮上大忙了。用户可以把要管理的主机IP地址预先写入/etc/ansible/hosts文件,这样后续再通过执行ansible命令来执行任务时就自动包含这些主机了,也就不需要每次都重复输入受管主机的地址了。举例来说,假设需要管理5台主机,它们的IP地址和用途如表16-3所示。

表16-3 受管主机信息

操作系统IP地址功能用途
RHEL 10192.168.10.20dev
RHEL 10192.168.10.21test
RHEL 10192.168.10.22prod
RHEL 10192.168.10.23prod
RHEL 10192.168.10.24balancers

首先需要说明的是,受管主机的系统默认使用RHEL 10,这是为了避免大家在准备实验机阶段产生歧义而给出的建议值,也可以用其他Linux系统。主机清单文件/etc/ansible/hosts中默认存在大量的注释信息,建议全部删除,然后替换成实验信息。

root@linuxprobe:~# vim /etc/ansible/hosts
192.168.10.20
192.168.10.21
192.168.10.22
192.168.10.23
192.168.10.24

为了增加实验难度,“通吃”生产环境中的常见需求,我们又为这5台主机分别规划了功能用途,有开发机(dev)、测试机(test)、产品机(prod)(两台)和负载均衡机(balancers)。在对主机进行分组标注后,后期在管理时就方便多了。

root@linuxprobe:~# vim /etc/ansible/hosts
[dev]
192.168.10.20
[test]
192.168.10.21
[prod]
192.168.10.22
192.168.10.23
[balancers]
192.168.10.24

主机清单文件在修改后会立即生效,一般使用“ansible-inventory --graph”命令以结构化的方式显示出受管主机的信息。因为对受管主机进行了分组,因此这种方式非常便于我们阅读。

root@linuxprobe:~# ansible-inventory --graph
@all:
|--@ungrouped:
|--@dev:
| |--192.168.10.20
|--@test:
| |--192.168.10.21
|--@prod:
| |--192.168.10.22
| |--192.168.10.23
|--@balancers:
| |--192.168.10.24

等等!先不要着急开始后面的实验。前文讲过,Ansible服务是基于SSH协议进行自动化控制的,这是开展后面实验的前提条件。第9章曾经讲到,sshd服务在初次连接时会要求用户接受一次对方主机的指纹信息。准备输入受管主机的账号和密码。例如,正常的第一次SSH远程连接过程是这样的:

root@linuxprobe:~# ssh 192.168.10.10
The authenticity of host '192.168.10.10 (192.168.10.10)' can't be established.
ED25519 key fingerprint is SHA256:0R7Kuk/yCTlJ+E4G9y9iX/A/hAklHkALm5ZUgnJ01cc.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.10.10' (ED25519) to the list of known hosts.
root@192.168.10.10's password:此处应输入管理员密码后回车确认
………………省略部分输出信息………………
Last login: Mon Mar 31 13:20:15 2025
root@linuxprobe:~#

众所周知,自动化运维的一个好处就是能提高工作效率。但是,如果每次执行操作都要输入受管主机的密码,也是比较麻烦的事情。好在Ansible服务已经对此有了解决办法,那就是使用如表16-4所示的变量。

表16-4 Ansible常用变量汇总

参数作用
ansible_ssh_host受管主机名
ansible_ssh_port端口号
ansible_ssh_user默认账号
ansible_password默认密码
ansible_shell_typeShell终端类型

用户只需要将对应的变量及信息填写到主机清单文件中,在执行任务时便会自动对账号和密码进行匹配,而不用每次重复输入它们。继续修改主机清单文件:

root@linuxprobe:~# vim /etc/ansible/hosts
[dev]
192.168.10.20
[test]
192.168.10.21
[prod]
192.168.10.22
192.168.10.23
[balancers]
192.168.10.24
[all:vars]
ansible_user=root
ansible_password=redhat

还剩最后一步。生成Ansible的主配置文件,默认会注释一切的参数,根据需要再逐个开启。我们分别将第221行设置成默认执行剧本时所使用的管理员名称为root,以及将第320行设置成默认不需要SSH协议的指纹验证,方便直接管理受控节点:

root@linuxprobe:~# cd /etc/ansible/
root@linuxprobe:/etc/ansible# ansible-config init --disabled > ansible.cfg
root@linuxprobe:/etc/ansible# vim /etc/ansible/ansible.cfg
219 # (string) Sets the login user for the target machines
220 # When blank it uses the connection plugin's default, normally the user curr ently executing Ansible.
221 remote_user=root
………………省略部分输出信息………………
319 # (boolean) Set this to "False" if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host
320 host_key_checking=False

不需要重启服务,在以上操作完全搞定后就可以开始后面的实验了。由于刚才是将Ansible服务器设置成了桥接及DHCP模式,现在请同学们自行将网络适配器修改回“仅主机模式”(见图16-3)以及192.168.10.10/24的IP地址。在修改完成后重启网卡,然后自行在主机之间执行ping操作。保证主机之间的网络能够互通是后续实验的基石。

root@linuxprobe:/etc/ansible# ifconfig
ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.10.10 netmask 255.255.255.0 broadcast 192.168.10.255
inet6 fe80::20c:29ff:fee5:e733 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:e5:e7:33 txqueuelen 1000 (Ethernet)
RX packets 1132 bytes 107455 (104.9 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 152 bytes 18695 (18.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
………………省略部分输出信息………………

图16-3 将虚拟机网卡改回仅主机模式

16.3 运行临时命令

Ansible服务的强大之处在于只需要一条命令,便能够操控成千上万台的主机节点,而ansible命令便是最得力的工具之一。前文提到,Ansible服务实际上只是一个框架,实际完成工作的是模块化功能代码。Ansible的常用模块大致有20多个(见表16-5),本书将会在后面的实验中逐一详解。

偶尔遇到书中没有提及的模块,大家可以使用“ansible-doc 模块名称”的命令格式自行查询,或是使用ansible-doc -l命令列出所有的模块信息以供选择,浏览完毕后输入q键退出。

表16-5 Ansible服务常用模块名称及作用

模块名称模块作用
ping检查受管节点主机网络是否能够联通。
yum安装、更新及卸载软件包。
yum_repository管理主机的软件仓库配置文件。
template复制模板文件到受管节点主机。
copy新建、修改及复制文件。
user创建、修改及删除用户。
group创建、修改及删除用户组。
service启动、关闭及查看服务状态。
get_url从网络中下载文件。
file设置文件权限及创建快捷方式。
cron添加、修改及删除计划任务。
command直接执行用户指定的命令。
shell直接执行用户指定的命令(支持特殊字符)。
debug输出调试或报错信息。
mount挂载硬盘设备文件。
filesystem格式化硬盘设备文件。
lineinfile通过正则表达式修改文件内容。
setup收集受管节点主机上的系统及变量信息。
firewalld添加、修改及删除防火墙策略。
lvg管理主机的物理卷及卷组设备。
lvol管理主机的逻辑卷设备。

在Ansible服务中,ansible是用于执行临时任务的命令,也就在是执行后即结束(与剧本文件的可重复执行不同)。在使用ansible命令时,必须指明受管主机的信息,如果已经设置过主机清单文件(/etc/ansible/hosts),则可以使用all参数来指代全体受管主机,或是用dev、test等主机组名称来指代某一组的主机。

ansible命令常用的语法格式为“ansible 受管主机节点 -m 模块名称 [-a 模块参数]”,常见的参数如表16-6所示。其中,-a是要传递给模块的参数,只有功能极其简单的模块才不需要额外参数,所以大多情况下-m与-a参数都会同时出现。

表16-6 ansible命令常用参数

参数作用
-k手动输入SSH协议密码
-i指定主机清单文件
-m指定要使用的模块名
-M指定要使用的模块路径
-S使用su命令
-T设置SSH协议连接超时时间
-a设置传递给模块的参数
--version查看版本信息
-h帮助信息

如果想实现某个功能,但是却不知道用什么模块,又或者是知道了模块名称,但不清楚模块具体的作用,则建议使用ansible-doc命令进行查找。例如,列举出当前Ansible服务所支持的所有模块信息:

root@linuxprobe:/etc/ansible# ansible-doc -l 
ansible.builtin.add_host Add a host (and alternatively a grou...
ansible.builtin.apt Manages apt-packages
ansible.builtin.apt_key Add or remove an apt key
ansible.builtin.apt_repository Add and remove APT repositories
ansible.builtin.assemble Assemble configuration files from fr...
ansible.builtin.assert Asserts given expressions are true
ansible.builtin.async_status Obtain status of asynchronous task
………………省略部分输出信息………………

一般情况下,很难通过名称来判别一个模块的作用,要么是参考模块后面的介绍信息,要么是平时多学多练,进行积累。例如,接下来随机查看一个模块的详细信息。ansible-doc命令会在屏幕上显示出这个模块的作用、可用参数及实例等信息:

root@linuxprobe:/etc/ansible# ansible-doc add_host
> ANSIBLE.BUILTIN.ADD_HOST (/usr/lib/python3.12/site-packages/ansible/module>

Use variables to create new hosts and groups in inventory for
use in later plays of the same playbook. Takes variables so
you can define the new hosts more fully. This module is also
supported for Windows targets.
………………省略部分输出信息………………

在16.2节,已经成功地将受管主机的IP地址填写到主机清单文件中,接下来小试牛刀,检查一下这些主机的网络连通性。ping模块用于进行简单的网络测试(类似于常用的ping命令)。可以使用ansible命令直接针对所有主机调用ping模块,不需要增加额外的参数,返回值若为SUCCESS,则表示主机当前在线。

root@linuxprobe:/etc/ansible# ansible all -m ping
192.168.10.20 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
192.168.10.21 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
192.168.10.22 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
192.168.10.23 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
192.168.10.24 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}

Tips :
由于5台受控主机的输出信息大致相同,因此为了提升读者的阅读体验,本章后续的输出结果默认仅保留192.168.10.20主机的输出值,其余相同的输出信息将会被省略。

是不是感觉很方便呢?!一次就能知道所有主机的在线情况。除了使用-m参数直接指定模块名称之外,还可以用-a参数将参数传递给模块,让模块的功能更高级,更好地满足当前生产的需求。例如,yum_repository模块的作用是管理主机的软件仓库,能够添加、修改及删除软件仓库的配置信息,参数相对比较复杂。遇到这种情况时,建议先用ansible-doc命令对其进行了解。尤其是下面的EXAMPLES结构段会有该模块的实例,对用户来说有非常高的参考价值。

root@linuxprobe:/etc/ansible# ansible-doc yum_repository
> ANSIBLE.BUILTIN.YUM_REPOSITORY (/usr/lib/python3.12/site-packages/ansible/>

Add or remove YUM repositories in RPM-based Linux
distributions. If you wish to update an existing repository
definition use [community.general.ini_file] instead.
………………省略部分输出信息………………
EXAMPLES:
- name: Add repository
ansible.builtin.yum_repository:
name: epel
description: EPEL YUM repo
baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/

- name: Add multiple repositories into the same file (1/2)
ansible.builtin.yum_repository:
name: epel
description: EPEL YUM repo
file: external_repos
baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/
gpgcheck: no

- name: Add multiple repositories into the same file (2/2)
ansible.builtin.yum_repository:
name: rpmforge
description: RPMforge YUM repo
file: external_repos
baseurl: http://apt.sw.be/redhat/el7/en/$basearch/rpmforge
mirrorlist: http://mirrorlist.repoforge.org/el7/mirrors-rpmforge
enabled: no

还好,参数并不是很多,而且与此前学过的/etc/yum.repos.d/目录中的配置文件基本相似。现在,想为主机清单中的所有服务器新增一个如表16-7所示的软件仓库,该怎么操作呢?

表16-7 新增软件仓库信息

参数项参数值
仓库名称EX294_BASE
仓库描述EX294 base software
仓库地址file:///media/cdrom/BaseOS
GPG签名启用
GPG密钥文件file:///media/cdrom/RPM-GPG-KEY-redhat-release

我们可以对照着EXAMPLE实例段,逐一对应填写需求值和参数,其标准格式是在-a参数后接整体参数(用单引号圈起),而各个参数字段的值则用双引号圈起。这是最严谨的写法。在执行下述命令后如果出现CHANGED字样,则表示修改已经成功:

root@linuxprobe:/etc/ansible# ansible all -m yum_repository -a 'name="EX294_BASE" description="EX294 base software" baseurl="file:///media/cdrom/BaseOS" gpgcheck=yes enabled=1 gpgkey="file:///media/cdrom/RPM-GPG-KEY-redhat-release"'

192.168.10.20 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"repo": "EX294_BASE",
"state": "present"
}

在命令执行成功后,可以到主机清单中的任意机器上查看新建成功的软件仓库配置文件。尽管这个实验的参数很多,但是并不难。

root@linuxprobe:~# cat /etc/yum.repos.d/EX294_BASE.repo 
[EX294_BASE]
baseurl = file:///media/cdrom/BaseOS
enabled = 1
gpgcheck = 1
gpgkey = file:///media/cdrom/RPM-GPG-KEY-redhat-release
name = EX294 base software

16.4 剧本文件实战

在很多情况下,仅仅执行单个命令或调用某一个模块,根本无法满足复杂工作的需要。Ansible服务允许用户根据需求,在类似于Shell脚本的模式下编写自动化运维脚本,然后由程序自动、重复地执行,从而大大提高了工作效率。

Ansible服务的剧本(playbook)文件采用YAML语言编写,具有强制性的格式规范,它通过空格将不同信息分组,因此有时会因一两个空格错位而导致报错。大家在使用时要万分小心。YAML文件的开头需要先写3个减号(---),多个分组的信息需要间隔一致才能执行,而且上下也要对齐,后缀名一般为.yml。剧本文件在执行后,会在屏幕上输出运行界面,内容会根据工作的不同而变化。在运行界面中,绿色表示成功,黄色表示执行成功并进行了修改,而红色则表示执行失败。

剧本文件的结构由四部分组成,分别是target、variable、task、handler,其各自的作用如下。

target:用于定义要执行剧本的主机范围。
variable:用于定义剧本执行时要用到的变量。
task:用于定义将在远程主机上执行的任务列表。
handler:用于定义执行完成后需要调用的后续任务。

YAML语言编写的Ansible剧本文件会按照从上到下的顺序自动运行,其形式类似于第4章介绍的Shell脚本,但格式有严格的要求。例如,创建一个名为packages.yml的剧本,让dev、test和prod组的主机可以自动安装数据库软件,并且将dev组主机的软件更新至最新。

安装和更新软件需要使用yum模块。先看一下帮助信息中的示例吧:

root@linuxprobe:/etc/ansible# ansible-doc yum
> ANSIBLE.BUILTIN.YUM (/usr/lib/python3.12/site-packages/ansible/modules/yum>

Installs, upgrade, downgrades, removes, and lists packages and
groups with the `yum' package manager. This module only works
on Python 2. If you require Python 3 support see the
[ansible.builtin.dnf] module.
………………省略部分输出信息………………
EXAMPLES:

- name: Install the latest version of Apache
ansible.builtin.yum:
name: httpd
state: latest

在配置Ansible剧本文件时,ansible-doc命令提供的帮助信息真是好用。在知道yum模块的使用方法和格式后,就可以开始编写剧本了。初次编写剧本文件时,请务必看准格式,模块及play(动作)格式也要上下对齐,否则会出现“参数一模一样,但不能执行”的情况。

综上,一个剧本正确的写法应该是:

root@linuxprobe:/etc/ansible# vim packages.yml
---
- name: 安装软件包
hosts: dev,test,prod
tasks:
- name: one
yum:
name: mariadb
state: latest

其中,name字段表示此项play(动作)的名字,用于在执行过程中提示用户执行到了哪一步,以及帮助管理员在日后阅读时能想起这段代码的作用。大家可以在name字段自行命名,没有任何限制。hosts字段表示要在哪些主机上执行该剧本,多个主机组之间用逗号间隔;如果需要对全部主机进行操作,则使用all参数。tasks字段用于定义要执行的任务,每个任务都要有一个独立的name字段进行命名,并且每个任务的name字段和模块名称都要严格上下对齐,参数要单独缩进。

而错误的剧本文件是下面这样的:

root@linuxprobe:/etc/ansible# vim packages.yml
---
- name: 安装软件包
hosts: dev,test,prod
tasks:
- name: one
yum:
name: mariadb
state: latest

大家已经感受到YAML语言对格式要求有多严格吧。

在编写Ansible剧本文件时,RHEL 10系统自带的Vim编辑器具有自动缩进功能,能给我们提供很多帮助。在确认无误后就可以用ansible-playbook命令运行这个剧本文件了。

root@linuxprobe:/etc/ansible# ansible-playbook packages.yml 
PLAY [安装软件包] **************************************************************

TASK [Gathering Facts] ********************************************************
ok: [192.168.10.20]
ok: [192.168.10.21]
ok: [192.168.10.22]
ok: [192.168.10.23]

TASK [one] ********************************************************************
changed: [192.168.10.20]
changed: [192.168.10.21]
changed: [192.168.10.22]
changed: [192.168.10.23]

PLAY RECAP ********************************************************************
192.168.10.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.21 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.22 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.23 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

在执行成功后,我们主要观察最下方的输出信息。其中,ok和changed表示执行及修改成功。如遇到unreachable或failed大于0的情况,建议手动检查剧本是否在所有主机中都正确运行了,以及有无安装失败的情况。在正确执行过packages.yml文件后,随机切换到dev、test、prod组中的任意一台主机上,再次安装mariadb软件包,此时会提示该服务已经存在。这说明刚才的操作一切顺利!

root@linuxprobe:~# dnf install mariadb
Updating Subscription Management repositories.
Last metadata expiration check: 0:00:38 ago on Tue 01 Apr 2025 06:52:29 PM CST.
Package mariadb-3:10.11.9-3.el10.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!

16.5 创建及使用角色

在编写Ansible剧本时,随着内容的不断增加,代码可能变得冗长、复杂,难以维护,且其他剧本也难以灵活调用其中的功能。为了解决这些问题,Ansible自1.2版本起引入了“角色”(Role)功能,用于层次化、结构化地组织剧本。角色功能将变量、文件、任务、模块及处理器分别放置在独立的目录中,能够方便地进行加载和复用。简单来说,角色功能将常用的功能模块化,使其在被需要时方便地调用。

Ansible的角色功能类似于编程中的封装技术——将具体功能封装起来,用户不仅可以方便地调用,还不必深入理解其内部原理。就像普通消费者在驾驶汽车时,不需要理解刹车系统的复杂构造,如制动总泵、刹车分泵、真空助力器、刹车盘、刹车鼓、刹车片或ABS泵的工作原理,只需轻踩刹车踏板即可实现制动。这便是技术封装带来的好处。

角色的优势在于将剧本组织成一个简洁且可重复调用的抽象对象,使用户能够将注意力集中在剧本的宏观设计上,统筹各个关键任务。只有在需要时,才需深入了解细节。这种结构化的组织方式极大地提升了剧本的可维护性和可扩展性。

获取和使用角色有三种主要方法:加载系统内置角色、从外部环境获取角色以及自行创建角色。通过这些方法,用户可以根据需求灵活地使用角色功能,从而实现高效、简洁的自动化运维管理。

16.5.1 加载系统内置角色

在使用RHEL系统的内置角色时,我们不需要联网就能实现。用户只需要配置好软件仓库的配置文件,然后安装包含系统角色的软件包rhel-system-roles,随后便可以在系统中找到它们了,然后就可以使用剧本文件调用角色了。

root@linuxprobe:/etc/ansible# dnf install rhel-system-roles
Updating Subscription Management repositories.
Last metadata expiration check: 0:27:07 ago on Tue 01 Apr 2025 06:27:45 PM CST.
Dependencies resolved.
================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
rhel-system-roles noarch 1.88.9-0.1.el10 AppStream 3.9 M
………………省略部分输出信息………………
Installed:
rhel-system-roles-1.88.9-0.1.el10.noarch

Complete!

安装完毕后,使用ansible-galaxy list命令查看RHEL 10系统中有哪些自带的角色可用:

root@linuxprobe:/etc/ansible# ansible-galaxy list
# /usr/share/ansible/roles
- rhel-system-roles.network, (unknown version)
- rhel-system-roles.podman, (unknown version)
- rhel-system-roles.postfix, (unknown version)
- rhel-system-roles.postgresql, (unknown version)
- rhel-system-roles.rhc, (unknown version)
- rhel-system-roles.selinux, (unknown version)
- rhel-system-roles.snapshot, (unknown version)
- rhel-system-roles.ssh, (unknown version)
- rhel-system-roles.sshd, (unknown version)
- rhel-system-roles.storage, (unknown version)
- rhel-system-roles.sudo, (unknown version)
- rhel-system-roles.systemd, (unknown version)
- rhel-system-roles.timesync, (unknown version)
………………省略部分输出信息………………

大家千万不要低估这些由系统镜像自带的角色,它们在日常的工作中能派上大用场。这些角色的主要功能如表16-8所示。

表16-8 ansible系统角色描述

角色名称作用
rhel-system-roles.kdump配置kdump崩溃恢复服务
rhel-system-roles.network配置网络接口
rhel-system-roles.selinux配置SELinux策略及模式
rhel-system-roles.timesync配置网络时间协议
rhel-system-roles.postfix配置邮件传输服务
rhel-system-roles.firewall配置防火墙服务
rhel-system-roles.tuned配置系统调优选项

以rhel-system-roles.timesync角色为例,它用于设置系统的时间和NTP服务,让主机能够同步准确的时间信息。剧本模板文件存放在/usr/share/doc/rhel-system-roles/目录中,可以复制过来修改使用:

root@linuxprobe:/etc/ansible# cp /usr/share/doc/rhel-system-roles/timesync/example-single-pool-playbook.yml timesync.yml

NTP服务器主要用于同步计算机的时间,提供高精度的时间校准服务,帮助计算机校对系统时钟。在复制来的剧本模板文件中,删除掉多余的代码,将NTP服务器的地址填写到timesync_ntp_servers变量的hostname字段中即可。该变量的参数含义如表16-9所示。稍后timesync角色就会自动为用户配置参数信息了。

表16-9 timesync_ntp_servers变量参数含义

参数作用
hostnameNTP服务器主机名
iburst启用快速同步
root@linuxprobe:/etc/ansible# vim timesync.yml 
---
- name: Example with single pool
hosts: all
vars:
timesync_ntp_servers:
- hostname: 2.pool.ntp.org
pool: true
iburst: true
roles:
- rhel-system-roles.timesync

16.5.2 从外部获取角色

Ansible Galaxy是Ansible的一个官方社区,用于共享角色和功能代码,用户可以在网站自由地共享和下载Ansible角色。该社区是管理和使用角色的不二之选。

在图16-4所示的Ansible Galaxy官网中,左侧有5个功能选项,分别是首页搜索(Search)、集合(Collections)、角色(Roles)、文档(Documentation)和许可信息(Terms of Use)。单击Roles按钮进入到角色搜索界面,这里以nginx服务为例进行搜索,即可找到Nginx官方发布的角色信息,如图16-5所示。

图 16-4 Ansible Galaxy 官网首页

图16-5 搜索界面中找到nginx角色信息

Tips :
Ansible Galaxy 官网首页:https://galaxy.ansible.com

当单击nginxinc.nginx角色进入到详情页面后,会显示这个项目的软件版本、评分、下载次数等信息。在Installation字段能看到相应的安装方式,如图16-6所示。在保持虚拟机正常连接外网的前提下,可以按这个页面提示的命令进行安装。

这时,如果需要使用这个角色,可以在虚拟机联网的状态下直接按照“ansible-galaxy install 角色名称”的命令格式自动获取:

图16-6 nginx角色详情页

root@linuxprobe:/etc/ansible# ansible-galaxy role install nginxinc.nginx
Starting galaxy role install process
- downloading role 'nginx', owned by nginxinc
- downloading role from https://github.com/nginxinc/ansible-role-nginx/archive/0.25.0.tar.gz
- extracting nginxinc.nginx to /etc/ansible/roles/nginxinc.nginx
- nginxinc.nginx (0.25.0) was installed successfully

执行完毕后,再次查看系统中已有的角色,便可找到nginx角色信息了:

root@linuxprobe:/etc/ansible# ansible-galaxy list
# /etc/ansible/roles
- nginx-core, (unknown version)
# /usr/share/ansible/roles
- rhel-system-roles.network, (unknown version)
- rhel-system-roles.podman, (unknown version)
- rhel-system-roles.postfix, (unknown version)
- rhel-system-roles.postgresql, (unknown version)
- rhel-system-roles.rhc, (unknown version)
- rhel-system-roles.selinux, (unknown version)
- rhel-system-roles.snapshot, (unknown version)
- rhel-system-roles.ssh, (unknown version)
- rhel-system-roles.sshd, (unknown version)
- rhel-system-roles.storage, (unknown version)
- rhel-system-roles.sudo, (unknown version)
- rhel-system-roles.systemd, (unknown version)
- rhel-system-roles.timesync, (unknown version)
………………省略部分输出信息………………

这里还存在两种特殊情况。

在国内访问Ansible Galaxy官网时可能存在不稳定的情况,导致访问不了或者网速较慢。
某位作者是将作品上传到了自己的网站,或者除Ansible Galaxy官网以外的其他平台。

在这两种情况下,就不能再用“ansible-galaxy install角色名称”的命令直接加载了,而是需要手动先编写一个YAML语言格式的文件,指明网址链接和角色名称,然后再用-r参数进行加载。

例如,刘遄老师在本书的配套网站( www.linuxprobe.com )上传了一个名为nginx_core的角色软件包(一个用于对nginx网站进行保护的插件)。这时需要编写如下所示的一个yml配置文件:

root@linuxprobe:/etc/ansible# vim nginx.yml
---
- src: https://www.linuxprobe.com/Software/ansible-role-nginx-0.25.0.tar.gz
name: nginx-core

随后使用ansible-galaxy命令的-r参数加载这个文件,即可查看到新角色信息了:

root@linuxprobe:/etc/ansible# ansible-galaxy role install -r nginx.yml
Starting galaxy role install process
- downloading role from https://www.linuxprobe.com/Software/ansible-role-nginx-0.25.0.tar.gz
- extracting nginx-core to /root/.ansible/roles/nginx-core
- nginx-core was installed successfully
root@linuxprobe:/etc/ansible# ansible-galaxy list
# /root/.ansible/roles
- nginx-core, (unknown version)
# /usr/share/ansible/roles
- rhel-system-roles.network, (unknown version)
- rhel-system-roles.podman, (unknown version)
- rhel-system-roles.postfix, (unknown version)
- rhel-system-roles.postgresql, (unknown version)
- rhel-system-roles.rhc, (unknown version)
- rhel-system-roles.selinux, (unknown version)
- rhel-system-roles.snapshot, (unknown version)
- rhel-system-roles.ssh, (unknown version)
- rhel-system-roles.sshd, (unknown version)
- rhel-system-roles.storage, (unknown version)
- rhel-system-roles.sudo, (unknown version)
- rhel-system-roles.systemd, (unknown version)
- rhel-system-roles.timesync, (unknown version)
………………省略部分输出信息………………

16.5.3 创建新的角色

除了使用系统自带的角色和从Ansible Galaxy获取的角色外,用户还可以根据实际工作需求,自行创建符合生产环境的定制化角色。虽然这种定制化编写的工作能够更好地贴合实际需求,但难度也相对较高,需要具备一定的Ansible知识和实践经验。

接下来,我们将创建一个名为apache的新角色,该角色将自动安装和配置httpd网站服务,设置防火墙规则,并根据每台主机生成独立的index.html首页文件。通过调用这个角色,让用户享受到“一条龙”式的网站部署服务,极大地简化了运维工作。

在Ansible的主配置文件中,第224行定义了角色的保存路径。如果用户创建的新角色不在这个指定目录内,则无法通过ansible-galaxy list命令找到该角色。因此,用户需要手动填写新角色的目录路径,或是在进入/etc/ansible/roles目录后再进行角色创建。为了避免角色信息过于分散,导致管理上的困难,建议在默认目录下创建角色,以便后期的统一管理。

root@linuxprobe:/etc/ansible# vim ansible.cfg
223 # (pathspec) Colon separated paths in which Ansible will search for Roles.
224 roles_path=/usr/share/ansible/roles:/etc/ansible/roles

在ansible-galaxy命令后面跟一个init参数,创建一个新的角色信息,且建立成功后便会在当前目录下生成出一个新的目录:

root@linuxprobe:/etc/ansible# cd roles
root@linuxprobe:/etc/ansible/roles# ansible-galaxy init apache
- Role apache was created successfully
root@linuxprobe:/etc/ansible/roles# ls
apache

此时的apache即是角色名称,也是用于存在角色信息的目录名称。切换到该目录下,查看它的结构:

root@linuxprobe:/etc/ansible/roles# cd apache/
root@linuxprobe:/etc/ansible/roles/apache# ls
defaults files handlers meta README.md tasks templates tests vars

在创建新角色时,最关键的便是要正确理解目录结构。通俗来说,就是要把正确的信息放入正确的目录中,这样在调用角色时才能有正确的效果。角色信息对应的目录结构及含义如表16-10所示。

表16-10 Ansible角色目录结构及含义

目录含义
defaults包含角色变量的默认值(优先级低)。
files包含角色执行tasks任务时做引用的静态文件。
handlers包含角色的处理程序定义。
meta包含角色的作者、许可证、频台和依赖关系等信息。
tasks包含角色所执行的任务。
templates包含角色任务所使用的Jinja2模板。
tests包含用于测试角色的剧本文件。
vars包含角色变量的默认值(优先级高)。

下面准备创建新角色。

第1步:打开用于定义角色任务的tasks/main.yml文件。在该文件中不需要定义要执行的主机组列表,因为后面会单独编写剧本进行调用,此时应先对apache角色能做的事情(任务)有一个明确的思路,在调用角色后yml文件会按照从上到下的顺序自动执行。

任务1:安装httpd网站服务。
任务2:运行httpd网站服务,并加入到开机启动项中。
任务3:配置防火墙,使其放行HTTP协议。
任务4:根据每台主机的变量值,生成不同的主页文件。

先写出第一个任务。使用yum模块安装httpd网站服务程序(注意格式):

root@linuxprobe:/etc/ansible/roles/apache# vim tasks/main.yml
---
- name: one
yum:
name: httpd
state: latest

第2步:使用service模块启动httpd网站服务程序,并加入到启动项中,让其能够一直为用户提供服务。在初次使用模块前,先用ansible-doc命令查看一下帮助和实例信息。由于篇幅的限制,这里对信息进行了删减,仅保留了有用的内容。

root@linuxprobe:/etc/ansible/roles/apache# ansible-doc service
> ANSIBLE.BUILTIN.SERVICE (/usr/lib/python3.12/site-packages/ansible/modules/service.py)

Controls services on remote hosts. Supported init systems include BSD init,
OpenRC, SysV, Solaris SMF, systemd, upstart. This module acts as a proxy to
the underlying service manager module. While all arguments will be passed
to the underlying module, not all modules support the same arguments. This
documentation only covers the minimum intersection of module arguments that
all service manager modules support. This module is a proxy for multiple
more specific service manager modules (such as [ansible.builtin.systemd]
and [ansible.builtin.sysvinit]). This allows management of a heterogeneous
environment of machines without creating a specific task for each service
manager. The module to be executed is determined by the `use' option, which
defaults to the service manager discovered by [ansible.builtin.setup]. If
[ansible.builtin.setup] was not yet run, this module may run it. For
Windows targets, use the [ansible.windows.win_service] module instead.
………………省略部分输出信息………………
EXAMPLES:

- name: Start service httpd, if not started
ansible.builtin.service:
name: httpd
state: started

- name: Stop service httpd, if started
ansible.builtin.service:
name: httpd
state: stopped

真幸运,默认的EXAMPLES示例使用的就是httpd网站服务。通过输出信息可得知,启动服务为“state: started”参数,而加入到开机启动项则是“enabled: yes”参数。继续编写:

root@linuxprobe:/etc/ansible/roles/apache# vim tasks/main.yml
---
- name: one
yum:
name: httpd
state: latest
- name: two
service:
name: httpd
state: started
enabled: yes

第3步:配置防火墙的允许策略,让其他主机能正常访问。在配置防火墙时,需要使用firewalld模块。在红帽RHEL 10系统中firewalld模块属于ansible.posix这个集合(Collection),需要先安装:

root@linuxprobe:/etc/ansible/roles/apache# ansible-galaxy collection install ansible.posix
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/ansible-posix-2.0.0.tar.gz to /root/.ansible/tmp/ansible-local-3580_c2zd0z_/tmpfmch8wdd/ansible-posix-2.0.0-vf36mg2m
Installing 'ansible.posix:2.0.0' to '/root/.ansible/collections/ansible_collections/ansible/posix'
ansible.posix:2.0.0 was installed successfully

然后再查看下帮助示例:

root@linuxprobe:/etc/ansible/roles/apache# ansible-doc firewalld
> ANSIBLE.POSIX.FIREWALLD (/root/.ansible/collections/ansible_collections/ansible/posix/plugins/modu>

This module allows for addition or deletion of services and ports (either
TCP or UDP) in either running or permanent firewalld rules.
………………省略部分输出信息………………
EXAMPLES:

- name: Permanently enable https service, also enable it immediately if possible
ansible.posix.firewalld:
service: https
state: enabled
permanent: true
immediate: true
offline: true

- name: Permit traffic in default zone for https service
ansible.posix.firewalld:
service: https
permanent: true
state: enabled

依据输出信息可得知,在firewalld模块设置防火墙策略时,指定协议名称为“service: http”参数,放行该协议为“state: enabled”参数,设置为永久生效为“permanent: yes”参数,当前立即生效为“immediate: yes”参数。参数虽然多了一些,但是基本与在第8章节学习的一致,并不需要担心。继续编写:

root@linuxprobe:/etc/ansible/roles/apache# vim tasks/main.yml
---
- name: one
yum:
name: httpd
state: latest
- name: two
service:
name: httpd
state: started
enabled: yes
- name: three
firewalld:
service: http
permanent: yes
state: enabled
immediate: yes

第4步:让每台主机显示的主页文件均不相同。在使用Ansible的常规模块时,都是采用“查询版主示例并模仿”的方式搞定的,这里为了增加难度,我们再提出个新需求,即能否让每台主机上运行的httpd网站服务都能显示不同的内容呢?例如显示当前服务器的主机名及IP地址。这就要用到template模块及Jinja2技术了。

依旧先使用ansible-doc命令查询template模块的使用方法。示例部分大有帮助:

root@linuxprobe:/etc/ansible/roles/apache# ansible-doc template
> ANSIBLE.BUILTIN.TEMPLATE (/usr/lib/python3.12/site-packages/ansible/modules/template.py)

Templates are processed by the Jinja2 templating language
<http://jinja.pocoo.org/docs/>. Documentation on the template formatting
can be found in the Template Designer Documentation
<http://jinja.pocoo.org/docs/templates/>. Additional variables listed below
can be used in templates. `ansible_managed' (configurable via the
`defaults' section of `ansible.cfg') contains a string which can be used to
describe the template name, host, modification time of the template file
and the owner uid. `template_host' contains the node name of the template's
machine. `template_uid' is the numeric user id of the owner.
`template_path' is the path of the template. `template_fullpath' is the
absolute path of the template. `template_destpath' is the path of the
template on the remote system (added in 2.8). `template_run_date' is the
date that the template was rendered.
………………省略部分输出信息………………
EXAMPLES:

- name: Template a file to /etc/file.conf
ansible.builtin.template:
src: /mytemplates/foo.j2
dest: /etc/file.conf
owner: bin
group: wheel
mode: '0644'

从template模块的输出信息中可得知,这是一个用于复制文件模板的模块,能够把文件从Ansible服务器复制到受管主机上。其中,src参数用于定义本地文件的路径,dest参数用于定义复制到受管主机的文件路径,而owner、group、mode参数可选择性地设置文件归属及权限信息。

正常来说,我们可以直接复制文件的操作,受管主机上会获取到一个与Ansible服务器上的文件一模一样的文件。但有时候想让每台客户端根据自身系统的情况产生不同的文件信息,这就需要用到Jinja2技术了,Jinja2格式的模板文件后缀是.j2。继续编写:

root@linuxprobe:/etc/ansible/roles/apache# vim tasks/main.yml
---
- name: one
yum:
name: httpd
state: latest
- name: two
service:
name: httpd
state: started
enabled: yes
- name: three
firewalld:
service: http
permanent: yes
state: enabled
immediate: yes
- name: four
template:
src: index.html.j2
dest: /var/www/html/index.html
JINJA

Jinja2是Python语言中广泛使用的模板引擎,其设计灵感源自Django的模板引擎。Jinja2在此基础上发展了更为强大的语法和功能,使其能够根据受管主机的变量动态生成不同的文件内容。简而言之,正常情况下的文件复制操作会使新旧文件完全一致,而使用Jinja2技术时,文件中并不会直接写入固定内容,而是写入一系列变量名称。

在使用template模块进行文件复制时,Ansible服务会在受管主机上收集这些变量的对应值,并将其动态地填入到模板文件中。最终生成的文件将根据每台主机的具体情况量身定制。这种方式极大地提升了自动化运维的灵活性和智能化,使得同一模板能适应多种环境和需求。

例如,想要让每个网站的输出信息值为“Welcome to 主机名 on 主机地址”,也就是用每个主机自己独有的名称和IP地址来替换文本中的内容,这样就有趣太多了。这个实验的难点在于查询到对应的变量名称、主机名及地址所对应的值保存在哪里?可以用setup模块进行查询。

root@linuxprobe:/etc/ansible/roles/apache# ansible-doc setup
> ANSIBLE.BUILTIN.SETUP (/usr/lib/python3.12/site-packages/ansible/modules/setup.py)

This module is automatically called by playbooks to gather useful variables
about remote hosts that can be used in playbooks. It can also be executed
directly by `/usr/bin/ansible' to check what variables are available to a
host. Ansible provides many `facts' about the system, automatically. This
module is also supported for Windows targets.
………………省略部分输出信息………………

setup模块的作用是自动收集受管主机上的变量信息,使用-a参数外加filter命令能够对收集来的信息进行二次过滤。相应的语法格式为ansible all -m setup -a 'filter="关键词"',其中*号是第3章节讲到的通配符,用于进行关键词查询。例如,如果想搜索各个主机的名称,则可以使用通配符搜索所有包含fqdn关键词的变量值信息。

FQDN(Fully Qualified Domain Name,完全限定域名)用于在逻辑上准确表示出主机的位置。FQDN常常被作为主机名的完全表达形式,比/etc/hostname文件中定义的主机名更加严谨和准确。通过输出信息可得知,ansible_fqdn变量保存有主机名称。随后进行下一步操作:

root@linuxprobe:/etc/ansible/roles/apache# ansible all -m setup -a 'filter="*fqdn*"'
192.168.10.20 | SUCCESS => {
"ansible_facts": {
"ansible_fqdn": "linuxprobe.com",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false
}
………………省略部分输出信息………………

用于指定主机地址的变量可以用ip作为关键词进行检索。下面输出信息中,ansible_all_ipv4_addresses变量中的值是我们想要的信息。如果想输出IPv6形式的地址,则可用ansible_all_ipv6_addresses变量。

root@linuxprobe:/etc/ansible/roles/apache# ansible all -m setup -a 'filter="*ip*"'
192.168.10.20 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.10.20"
],
"ansible_all_ipv6_addresses": [
"fe80::20c:29ff:fed3:80d8"
],
"ansible_default_ipv4": {},
"ansible_default_ipv6": {},
"ansible_fips": false,
"ansible_locally_reachable_ips": {
"ipv4": [
"127.0.0.0/8",
"127.0.0.1",
"192.168.10.20"
],
"ipv6": [
"::1",
"fe80::20c:29ff:fed3:80d8"
]
},
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false
}
………………省略部分输出信息………………

在确认了主机名与IP地址所对应的具体变量名称后,在角色所对应的templates目录内新建一个与上面的template模块参数相同的文件名称(index.html.j2)。Jinja2在调用变量值时,格式为在变量名称的两侧格加两个大括号:

root@linuxprobe:/etc/ansible/roles/apache# vim templates/index.html.j2
Welcome to {{ ansible_fqdn }} on {{ ansible_all_ipv4_addresses }}

进行到这里,任务基本就算完成了。最后要做的就是编写一个用于调用apache角色的yml文件,以及执行这个文件。

root@linuxprobe:/etc/ansible/roles/apache# cd ../../
root@linuxprobe:/etc/ansible# vim roles.yml
---
- name: 调用自建角色
hosts: all
roles:
- apache
root@linuxprobe:~# ansible-playbook roles.yml
PLAY [调用自建角色] **************************************************************************

TASK [Gathering Facts] **********************************************************************
ok: [192.168.10.20]
ok: [192.168.10.21]
ok: [192.168.10.22]
ok: [192.168.10.23]
ok: [192.168.10.24]

TASK [apache : one] *************************************************************************
changed: [192.168.10.20]
changed: [192.168.10.21]
changed: [192.168.10.22]
changed: [192.168.10.23]
changed: [192.168.10.24]

TASK [apache : two] *************************************************************************
changed: [192.168.10.20]
changed: [192.168.10.21]
changed: [192.168.10.22]
changed: [192.168.10.23]
changed: [192.168.10.24]

TASK [apache : three] ***********************************************************************
changed: [192.168.10.20]
changed: [192.168.10.21]
changed: [192.168.10.22]
changed: [192.168.10.23]
changed: [192.168.10.24]

TASK [apache : four] ***********************************************************************
changed: [192.168.10.20]
changed: [192.168.10.21]
changed: [192.168.10.22]
changed: [192.168.10.23]
changed: [192.168.10.24]

PLAY RECAP **********************************************************************************
192.168.10.20 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.21 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.22 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.23 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.24 : ok=4 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

执行完毕后,在浏览器中随机输入几台主机的IP地址,即可访问到包含主机FQDN和IP地址的网页了,如图16-7~图16-9所示。

图16-7 随机访问一台主机节点的网站首页

图16-8 随机访问一台主机节点的网站首页

图16-9 随机访问一台主机节点的网站首页

实验相当成功!

16.6 创建和使用逻辑卷

创建一个能批量、自动管理逻辑卷设备的剧本,不但能大大提高硬盘设备的管理效率,而且还能避免手动创建带来的错误。例如,我们想在每台受管主机上都创建出一个名为data的逻辑卷设备,大小为150MB,归属于research卷组。如果创建成功,则进一步用Ext4文件系统进行格式化操作;如果创建失败,则给用户输出一条报错提醒,以便排查原因。

在这种情况下,使用Ansible剧本要比使用Shell脚本的优势大,原因主要有下面两点。

Ansible模块化的功能让操作更标准,只要在执行过程中无报错,那么便会依据远程主机的系统版本及配置自动做出判断和操作,不用担心因系统变化而导致命令失效的问题。

Ansible服务在执行剧本文件时会进行判断:如果该文件或该设备已经被创建过,或是某个动作(play)已经被执行过,则绝对不会再重复执行;而使用Shell脚本有可能导致设备被重复格式化,导致数据丢失。

首先在prod组的两台主机上分别添加一块硬盘设备,大小为20GB,类型为SCSI,其余选项选择默认值,如图16-10~图16-12所示。

图16-10 添加一块新硬盘

图16-11 设置硬盘类型

图16-12 新硬盘添加完毕

通过回忆第7章学习过的逻辑卷的知识,我们应该让剧本文件依次创建物理卷(PV)、卷组(VG)及逻辑卷(LV)。需要先使用lvg模块让设备支持逻辑卷技术,然后创建一个名为research的卷组。lvg模块的帮助信息如下:

root@linuxprobe:/etc/ansible# ansible-doc lvg
> COMMUNITY.GENERAL.LVG (/root/.ansible/collections/ansible_collections/community/general/plugins/mo>

This module creates, removes or resizes volume groups.
………………省略部分输出信息………………
EXAMPLES:

- name: Create a volume group on top of /dev/sda1 with physical extent size = 32MB
community.general.lvg:
vg: vg.services
pvs: /dev/sda1
pesize: 32

- name: Create a volume group on top of /dev/sdb with physical extent size = 128KiB
community.general.lvg:
vg: vg.services
pvs: /dev/sdb
pesize: 128K

通过输出信息可得知,创建PV和VG的lvg模块总共有3个必备参数。其中,vg参数用于定义卷组的名称,pvs参数用于指定硬盘设备的名称,pesize参数用于确定最终卷组的容量大小(可以用PE个数或容量值进行指定)。这样一来,我们先创建出一个由/dev/sdb设备组成的名称为research、大小为150MB的卷组设备。

root@linuxprobe:/etc/ansible# vim lv.yml
---
- name: 创建和使用逻辑卷
hosts: all
tasks:
- name: one
lvg:
vg: research
pvs: /dev/sdb
pesize: 150M

由于刚才只在prod组的两台主机上添加了新硬盘设备文件,因此在执行上述操作时其余3台主机会提示未创建成功,这属于正常情况。接下来使用lvol模块创建出逻辑卷设备。还是按照惯例,先查看模块的帮助信息:

root@linuxprobe:/etc/ansible# ansible-doc lvol
> COMMUNITY.GENERAL.LVOL (/root/.ansible/collections/ansible_collections/community/general/plugins/m>

This module creates, removes or resizes logical volumes.
………………省略部分输出信息………………
EXAMPLES:

- name: Create a logical volume of 512m
community.general.lvol:
vg: firefly
lv: test
size: 512

- name: Create a logical volume of 512m with disks /dev/sda and /dev/sdb
community.general.lvol:
vg: firefly
lv: test
size: 512
pvs:
- /dev/sda
- /dev/sdb

通过输出信息可得知,lvol是用于创建逻辑卷设备的模块。其中,vg参数用于指定卷组名称,lv参数用于指定逻辑卷名称,size参数则用于指定最终逻辑卷设备的容量大小(不用加单位,默认为MB)。填写好参数,创建出一个大小为150MB、归属于research卷组且名称为data的逻辑卷设备:

root@linuxprobe:/etc/ansible# vim lv.yml
---
- name: 创建和使用逻辑卷
hosts: all
tasks:
- name: one
lvg:
vg: research
pvs: /dev/sdb
pesize: 150M
- name: two
lvol:
vg: research
lv: data
size: 150M

这样还不够好,如果还能将创建出的/dev/research/data逻辑卷设备自动用Ext4文件系统进行格式化操作,则又能帮助运维管理员减少一些工作量。可使用filesystem模块来完成设备的文件系统格式化操作。该模块的帮助信息如下:

root@linuxprobe:/etc/ansible# ansible-doc filesystem
> COMMUNITY.GENERAL.FILESYSTEM (/root/.ansible/collections/ansible_collections/community/general/plu>

This module creates a filesystem.
………………省略部分输出信息………………
EXAMPLES:

- name: Create a ext2 filesystem on /dev/sdb1
community.general.filesystem:
fstype: ext2
dev: /dev/sdb1

filesystem模块的参数真是简练,fstype参数用于指定文件系统的格式化类型,dev参数用于指定要格式化的设备文件路径。继续编写:

root@linuxprobe:/etc/ansible# vim lv.yml
---
- name: 创建和使用逻辑卷
hosts: all
tasks:
- name: one
lvg:
vg: research
pvs: /dev/sdb
pesize: 150M
- name: two
lvol:
vg: research
lv: data
size: 150M
- name: three
filesystem:
fstype: ext4
dev: /dev/research/data

这样按照顺序执行下来,逻辑卷设备就能自动创建好了。等一下,还有个问题没有解决。现在只有prod组的主机上添加了新的硬盘设备文件,其余主机是无法按照既定模块顺利完成操作的。这时就要使用类似于第4章学习的if条件语句的方式进行判断—如果失败……,则……。

首先用block操作符将上述的3个模块命令作为一个整体(相当于对这3个模块的执行结果作为一个整体进行判断),然后使用rescue操作符进行救援,且只有block块中的模块执行失败后才会调用rescue中的救援模块。其中,debug模块的msg参数的作用是,如果block中的模块执行失败,则输出一条信息到屏幕,用于提醒用户。完成编写后的剧本是下面这个样子:

root@linuxprobe:/etc/ansible# vim lv.yml
---
- name: 创建和使用逻辑卷
hosts: all
tasks:
- block:
- name: one
lvg:
vg: research
pvs: /dev/sdb
pesize: 150M
- name: two
lvol:
vg: research
lv: data
size: 150M
- name: three
filesystem:
fstype: ext4
dev: /dev/research/data
rescue:
- debug:
msg: "Could not create logical volume of that size"

YAML语言对格式有着硬性的要求,既然rescue是对block内的模块进行救援的功能代码,因此recue和block两个操作符必须严格对齐,错开一个空格都会导致剧本执行失败。确认无误后,执行lv.yml剧本文件检阅一下效果:

root@linuxprobe:/etc/ansible# ansible-playbook lv.yml 

PLAY [创建和使用逻辑卷] *********************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.10.20]
ok: [192.168.10.21]
ok: [192.168.10.22]
ok: [192.168.10.23]
ok: [192.168.10.24]

TASK [one] *********************************************************************
fatal: [192.168.10.20]: FAILED! => {"changed": false, "msg": "Device /dev/sdb not found."}
fatal: [192.168.10.21]: FAILED! => {"changed": false, "msg": "Device /dev/sdb not found."}
changed: [192.168.10.22]
changed: [192.168.10.23]
fatal: [192.168.10.24]: FAILED! => {"changed": false, "msg": "Device /dev/sdb not found."}

TASK [two] *********************************************************************
changed: [192.168.10.22]
changed: [192.168.10.23]

TASK [three] *********************************************************************
changed: [192.168.10.22]
changed: [192.168.10.23]

TASK [debug] *******************************************************************
ok: [192.168.10.20] => {
"msg": "Could not create logical volume of that size"
}
ok: [192.168.10.21] => {
"msg": "Could not create logical volume of that size"
}
ok: [192.168.10.24] => {
"msg": "Could not create logical volume of that size"
}

PLAY RECAP *********************************************************************
192.168.10.20 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
192.168.10.21 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
192.168.10.22 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.23 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.24 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0

在剧本运行完毕后的执行记录(PLAY RECAP)中可很清晰地看到只有192.168.10.22及192.168.10.23这两台prod组中的主机执行成功了,其余3台主机均触发了rescue功能。登录到任意一台prod组的主机上,找到新建的逻辑卷设备信息:

root@linuxprobe:/etc/ansible# lvdisplay 
--- Logical volume ---
LV Path /dev/research/data
LV Name data
VG Name research
LV UUID jI3PoD-az0v-oTRZ-w8Ve-FI08-LvDk-2oISvZ
LV Write Access read/write
LV Creation host, time linuxprobe.com, 2025-04-03 20:28:41 +0800
LV Status available
# open 0
LV Size 150.00 MiB
Current LE 1
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 8192
Block device 253:2
………………省略部分输出信息………………

16.7 判断主机组名

在上面的剧本实验中,我们可以让不同的主机根据自身不同的变量信息而生成出独特的网站主页文件,但却无法对某个主机组进行针对性的操作。其实,在每个客户端中都会有一个名为inventory_hostname的变量,用于定义每台主机所对应的Ansible服务的主机组名称,也就是/etc/ansible/hosts文件中所对应的分组信息,例如dev、test、prod、balancers。

inventory_hostname是Ansible服务中的魔法变量,这意味着无法使用setup模块直接进行查询,诸如ansible all -m setup -a 'filter="关键词"'这样的命令将对它失效。魔法变量需要在执行剧本文件时的Gathering Facts阶段进行搜集,直接查询是看不到的,只能在剧本文件中进行调用。

在获得了存储主机组名称的变量名称后,接下来开始实战。这里的需求如下:

若主机在dev分组中,则修改/etc/issue文件内容为Development;
若主机在test分组中,则修改/etc/issue文件内容为Test;
若主机在prod分组中,则修改/etc/issue文件内容为Production。

根据表16-5所提及的Ansible常用模块名称及作用,可以看到copy模块的主要作用是新建、修改及复制文件,更符合当前的需要,此时便派上了用场。先查询copy模块的帮助信息:

root@linuxprobe:/etc/ansible# ansible-doc copy
> ANSIBLE.BUILTIN.COPY (/usr/lib/python3.12/site-packages/ansible/modules/copy.py)

The [ansible.builtin.copy] module copies a file or a directory structure
from the local or remote machine to a location on the remote machine. File
system meta-information (permissions, ownership, etc.) may be set, even
when the file or directory already exists on the target system. Some meta-
information may be copied on request. Get meta-information with the
[ansible.builtin.stat] module. Set meta-information with the
[ansible.builtin.file] module. Use the [ansible.builtin.fetch] module to
copy files from remote locations to the local box. If you need variable
interpolation in copied files, use the [ansible.builtin.template] module.
Using a variable with the `content' parameter produces unpredictable
results. For Windows targets, use the [ansible.windows.win_copy] module
instead.
………………省略部分输出信息………………
EXAMPLES:
- name: Copy file with owner and permissions
ansible.builtin.copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: '0644'

- name: Copy file with owner and permission, using symbolic representation
ansible.builtin.copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: u=rw,g=r,o=r

在输出信息中列举了两种管理文件内容的示例。第一种用于文件的复制行为,第二种是通过content参数定义内容,通过dest参数指定新建文件的名称。显然,第二种更加符合当前的实验场景。编写剧本文件如下:

root@linuxprobe:/etc/ansible# vim issue.yml
---
- name: 修改文件内容
hosts: all
tasks:
- name: one
copy:
content: 'Development'
dest: /etc/issue
- name: two
copy:
content: 'Test'
dest: /etc/issue
- name: three
copy:
content: 'Production'
dest: /etc/issue

但是,如果按照这种顺序执行下去,每一台主机的/etc/issue文件都会被重复修改3次,最终定格在“Production”字样,这显然缺少了一些东西。我们应该依据inventory_hostname变量中的值进行判断。若主机为dev组,则执行第一个动作;若主机为test组,则执行第二个动作;若主机为prod组,则执行第三个动作。因此,要进行3次判断。

when是用于判断的语法,将其用在每个动作的下方进行判断,使得只有在满足条件才会执行:

root@linuxprobe:/etc/ansible# vim issue.yml
---
- name: 修改文件内容
hosts: all
tasks:
- name: one
copy:
content: 'Development'
dest: /etc/issue
when: "inventory_hostname in groups.dev"
- name: two
copy:
content: 'Test'
dest: /etc/issue
when: "inventory_hostname in groups.test"
- name: three
copy:
content: 'Production'
dest: /etc/issue
when: "inventory_hostname in groups.prod"

执行剧本文件,在过程中可清晰地看到由于when语法的作用,未在指定主机组中的主机将被跳过(skipping):

root@linuxprobe:/etc/ansible# ansible-playbook issue.yml 

PLAY [修改文件内容] ************************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [192.168.10.20]
ok: [192.168.10.21]
ok: [192.168.10.22]
ok: [192.168.10.23]
ok: [192.168.10.24]

TASK [one] ********************************************************************************
changed: [192.168.10.20]
skipping: [192.168.10.21]
skipping: [192.168.10.22]
skipping: [192.168.10.23]
skipping: [192.168.10.24]

TASK [two] ********************************************************************************
skipping: [192.168.10.20]
changed: [192.168.10.21]
skipping: [192.168.10.23]
skipping: [192.168.10.24]
skipping: [192.168.10.25]

TASK [three] ******************************************************************************
skipping: [192.168.10.20]
skipping: [192.168.10.21]
changed: [192.168.10.22]
changed: [192.168.10.23]
skipping: [192.168.10.24]

PLAY RECAP ********************************************************************************
192.168.10.20 : ok=2 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
192.168.10.21 : ok=2 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
192.168.10.22 : ok=2 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
192.168.10.23 : ok=2 changed=1 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
192.168.10.24 : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0

登录到dev组的192.168.10.20主机上,查看文件内容:

root@linuxprobe:~# cat /etc/issue 
Development

登录到test组的192.168.10.21主机上,查看文件内容:

root@linuxprobe:~# cat /etc/issue 
Test

登录到prod组的192.168.10.22/23主机上,查看文件内容:

root@linuxprobe:~# cat /etc/issue 
Production

16.8 管理文件属性

我们学习剧本的目的是为了满足日常的工作需求,把重复的事情写入到脚本中,然后再批量执行下去,从而提高运维工作的效率。其中,创建文件、管理权限以及设置快捷方式几乎是每天都用到的技能。尤其是在第5章学习文件的一般权限、特殊权限、隐藏权限时,往往还会因命令的格式问题而导致出错。这么多命令该怎么记呢?

Ansible服务将常用的文件管理功能都合并到了file模块中,大家不用再为了寻找模块而“东奔西跑”了。先来看一下file模块的帮助信息:

root@linuxprobe:/etc/ansible# ansible-doc file
> ANSIBLE.BUILTIN.FILE (/usr/lib/python3.12/site-packages/ansible/modules/file.py)

Set attributes of files, directories, or symlinks and their targets.
Alternatively, remove files, symlinks or directories. Many other modules
support the same options as the [ansible.builtin.file] module - including
[ansible.builtin.copy], [ansible.builtin.template], and
[ansible.builtin.assemble]. For Windows targets, use the
[ansible.windows.win_file] module instead.
………………省略部分输出信息………………
EXAMPLES:

- name: Change file ownership, group and permissions
ansible.builtin.file:
path: /etc/foo.conf
owner: foo
group: foo
mode: '0644'

- name: Give insecure permissions to an existing file
ansible.builtin.file:
path: /work
owner: root
group: root
mode: '1777'

- name: Create a symbolic link
ansible.builtin.file:
src: /file/to/link/to
dest: /path/to/symlink
owner: foo
group: foo
state: link

通过上面的输出示例,大家已经能够了解file模块的基本参数了。其中,path参数定义了文件的路径,owner参数定义了文件所有者,group参数定义了文件所属组,mode参数定义了文件权限,src参数定义了源文件的路径,dest参数定义了目标文件的路径,state参数则定义了文件类型。

可见,file模块基本上把第5章学习过的管理文件权限的功能都包含在内了。我们现在来挑战下面的实验吧:

请创建出一个名为/linuxprobe的新目录,所有者及所属组均为root管理员身份;
设置所有者和所属于组拥有对文件的完全控制权,而其他人则只有阅读和执行权限;
给予SGID特殊权限;
仅在dev主机组的主机上实施。

第二条要求是算术题,即将权限描述转换为数字表示法,即可读为4、可写为2、可执行为1。大家先自行默默计算一下答案。此前在编写剧本文件时,hosts参数对应的一直是all,即全体主机,这次需要修改为仅对dev主机组成员生效,请小心谨慎。编写模块代码如下:

root@linuxprobe:/etc/ansible# vim chmod.yml
---
- name: 管理文件属性
hosts: dev
tasks:
- name: one
file:
path: /linuxprobe
state: directory
owner: root
group: root
mode: '2775'

一不小心把题目出简单了,这里没能完全展示出file模块的强大之处。我们临时添加一个需求:请再创建一个名称为/linuxcool的快捷方式文件,指向刚刚建立的/linuxprobe目录。这样用户在访问两个目录时就能有相同的内容了。在使用file模块设置快捷方式时,不需要再单独创建目标文件,Ansible服务会帮我们完成:

root@linuxprobe:/etc/ansible# vim chmod.yml
---
- name: 管理文件属性
hosts: dev
tasks:
- name: one
file:
path: /linuxprobe
state: directory
owner: root
group: root
mode: '2775'
- name: two
file:
src: /linuxprobe
dest: /linuxcool
state: link

剧本文件的执行过程如下所示:

root@linuxprobe:/etc/ansible# ansible-playbook chmod.yml 

PLAY [管理文件属性] ***************************************************************

TASK [Gathering Facts] ***********************************************************
ok: [192.168.10.20]
ok: [192.168.10.21]
ok: [192.168.10.22]
ok: [192.168.10.23]
ok: [192.168.10.24]

TASK [one] ***********************************************************************
changed: [192.168.10.20]
skipping: [192.168.10.21]
skipping: [192.168.10.22]
skipping: [192.168.10.23]
skipping: [192.168.10.24]

TASK [two] ***********************************************************************
changed: [192.168.10.20]
skipping: [192.168.10.21]
skipping: [192.168.10.22]
skipping: [192.168.10.23]
skipping: [192.168.10.24]

PLAY RECAP ***********************************************************************
192.168.10.20 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.168.10.22 : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
192.168.10.22 : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
192.168.10.22 : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
192.168.10.22 : ok=1 changed=0 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0

进入到dev组的主机中,看到/linuxprobe目录及/linuxcool的快捷方式均已经被顺利创建:

root@linuxprobe:~# ls -ld /linuxprobe
drwxrwsr-x. 2 root root 6 Apr 3 20:35 /linuxprobe
root@linuxprobe:~# ls -ld /linuxcool
lrwxrwxrwx. 1 root root 11 Apr 3 20:35 /linuxcool -> /linuxprobe

16.9 管理密码库文件

自Ansible 1.5版本发布后,vault作为一项新功能进入到了运维人员的视野。它不仅能对密码、剧本等敏感信息进行加密,而且还能加密变量名称和变量值,从而确保数据不会被他人轻易阅读。使用ansible-vault命令可以实现内容的新建(create)、加密(encrypt)、解密(decrypt)、修改密码(rekey)及查看(view)等功能。

下面通过示例来学习vault的具体用法。

第1步:创建出一个名为locker.yml的配置文件,其中保存了两个变量值:

root@linuxprobe:/etc/ansible# vim locker.yml
---
pw_developer: Imadev
pw_manager: Imamgr

第2步:使用ansible-vault命令对文件进行加密。由于需要每次输入密码比较麻烦,因此还应新建一个用于保存密码值的文本文件,以便让ansible-vault命令自动调用。为了保证数据的安全性,在新建密码文件后将该文件的权限设置为600,确保仅管理员可读可写:

root@linuxprobe:/etc/ansible# vim /root/secret.txt
whenyouwishuponastar
root@linuxprobe:/etc/ansible# chmod 600 /root/secret.txt

在Ansible服务的主配置文件中,在第278行的vault_password_file参数后指定密码值保存的文件路径,准备进行调用:

root@linuxprobe:/etc/ansible# vim /etc/ansible/ansible.cfg
137
138 # If set, configures the path to the Vault password file as an alternative to
139 # specifying --vault-password-file on the command line.
140 vault_password_file = /root/secret.txt
141

第3步:在设置好密码文件的路径后,Ansible服务便会自动进行加载。用户也就不用在每次加密或解密时都重复输入密码了。例如,在加密刚刚创建的locker.yml文件时,只需要使用encrypt参数即可:

root@linuxprobe:/etc/ansible# ansible-vault encrypt locker.yml
Encryption successful

文件将使用AES 256加密方式进行加密,也就是意味着密钥有2256种可能。查看到加密后的内容为:

root@linuxprobe:/etc/ansible# cat locker.yml 
$ANSIBLE_VAULT;1.1;AES256
38666539353062343930633135306633613736386366633665353464343964613838356135616137
3538616139303438316636613564313266353833336337640a313963316664303431383132386362
30326430643234653336363130393238633266386636333666633932613937326135373766656539
3637386562646539610a333761333039323565353761353636623762623435616163333031376333
36316334386562326337363730353463323462316131383064333234626561366338356261383333
3235626134393832323436386232646562396133666435386361

如果不想使用原始密码了呢?也可以使用rekey参数手动对文件进行改密操作,同时应结合--ask-vault-pass参数进行修改,否则Ansible服务会因接收不到用户输入的旧密码值而拒绝新的密码变更请求:

root@linuxprobe:/etc/ansible# ansible-vault rekey --ask-vault-pass locker.yml 
Vault password: 输入旧的密码
New Vault password: 输入新的密码
Confirm New Vault password: 再输入新的密码
Rekey successful

第4步:如果想查看和修改加密文件中的内容,该怎么操作呢?对于已经加密过的文件,需要使用ansible-vault命令的edit参数进行修改,随后用view参数即可查看到修改后的内容。ansible-vault命令对加密文件的编辑操作默认使用的是Vim编辑器,在修改完毕后请记得执行wq操作保存后退出:

root@linuxprobe:/etc/ansible# ansible-vault edit locker.yml
---
pw_developer: Imadev
pw_manager: Imamgr
pw_production: Imaprod

最后,再用view参数进行查看,便是最新的内容了:

root@linuxprobe:/etc/ansible# ansible-vault view locker.yml
Vault password: 输入密码后敲击回车确认
---
pw_developer: Imadev
pw_manager: Imamgr
pw_production: Imaprod

本章节的复习作业

1.当前已经搭建好软件仓库,但却不能用dnf命令安装Ansible服务。这有可能是什么原因呢?

答:RHEL 10系统中默认的BaseOS和AppStream软件仓库不包含Ansible服务软件包,需要额外配置EPEL安装源。

2.当/etc/ansible/ansible.cfg与~/.ansible.cfg两个主配置文件都同时存在时,以哪个为准?

答:个人家目录中的主配置文件的优先级更高。

3.使用Ansible的哪个模块能启动服务,使用Ansbile的哪个模块能挂载硬盘设备文件?

答:使用service模块启动服务,使用mount模块挂载设备文件。

4.我们想了解一个模块的作用,可以使用什么命令来查询它的帮助信息?

答:可以使用ansible-doc命令查询模块的帮助信息。

5.Ansible角色有几种获取方法?

答:有3种,分别是加载系统内置角色、从外部环境获取角色以及自行创建角色。

6.在执行剧本文件时,出现了黄色显示的changed字样,这表示什么意思?

答:表示剧本文件执行成功并进行了修改。

7.Jinja2与copy复制模块有什么区别?

答:Copy模块是一比一的复制,源文件与目标文件内容完全相同。而Jinja2则会将源文件中的变量名替换成受控节点上的实际变量值。

8.想要让剧本内容对主机清单中所有受控节点都生效,应如何操作?

答:应使用参数hosts: all。

9.只记得某个变量中的一部分名称,应如何通过关键词搜索?

答:可执行命令:ansible all -m setup -a 'filter="关键词"'。

10.在使用ansible-vault命令进行加密时,默认使用的是哪种加密方式?

答:AES 256。