1. Ansible 介绍与安装
Ansible 目前是运维自动化工具中最简单、容易上手的一款优秀软件,能够用来管理各种资源。用户可以使用 Ansible 自动部署应用程序,以此实现 IT 基础架构的全面部署。例如,借助于 Ansible,我们可以轻松地对服务器进行初始化配置、安全基线配置,以及进行更新和打补丁操作。相较于 Chef、Puppet、SaltStack 等 C/S(客户端/服务器)架构的自动化工具来讲,尽管 Ansible 的性能并不是最好的,但由于它基于 SSH 远程会话协议,不需要客户端程序,只要知道受管主机的账号密码,就能直接用 SSH 协议进行远程控制,因此使用起来优势明显。
使用自动化运维工具,可以肉眼可见地提高运维人员的工作效率,并减少人为错误。Ansible 服务本身并没有批量部署的功能,它仅仅是一个框架,真正具有批量部署能力的是其所运行的模块。Ansible 内置了上千个模块,会在安装 Ansible 时一并安装,通过调用指定的模块,就能实现特定的功能。Ansible 内置的模块非常丰富,几乎可以满足一切需求,使用起来也非常简单,一条命令甚至影响上千台主机。如果需要更高级的功能,也可以运用 Python语言对 Ansible 进行二次开发。
当前,Ansible 已经被 Amazon、Google、Microsoft、Cisco、HP、VMware、Twitter 等大科技公司接纳并投入使用。红帽公司更是对自家产品进行了不遗余力的支持。从 2020 年 8 月 1 日起,RHCE 考试的内容由配置多款服务转变成 Ansible 专项考题内容。现在,要想顺利拿到 RHCE 认证,真的有必要好好学习一下本章了。
在正式介绍 Ansible 之前,我们先普及一下相关的专用术语,好让大家对术语有统一的理解,以便在后续实验时能直奔主题。这里整理的与 Ansible 相关的专用术语如表所示。
由于受控节点不需要安装客户端,外加 SSH 协议是 Linux 系统的标配,因此可以直接通过 SSH 协议进行远程控制。在控制节点上,也不用每次都重复开启服务程序,使用 ansible命令直接调用模块进行控制即可。
RHEL 8 系统的镜像文件默认不带有 Ansible 服务程序,需要从 Extra Packages for Enterprise Linux(EPEL)扩展软件包仓库获取。EPEL 软件包仓库由红帽公司提供,是一个用于创建、维护和管理企业版 Linux 的高质量软件扩展仓库,通用于 RHEL、CentOS、Oracle Linux 等多种红帽系企业版系统,目的是对于默认系统仓库软件包进行扩展。
下面准备在系统上部署 Ansible 服务程序。
第一步: 在"虚拟机设置"界面中,将"网络适配器"的"网络连接"选项调整为"桥接模式",并将系统的网卡设置成"Automatic(DHCP)"模式,如图 16-1 及图 16-2 所示。
在大多数情况下,只要把虚拟机设置成桥接模式,且 Linux 系统的网卡信息与物理机相同,然后再重启网络服务,就可以连接外部网络了。如果不放心,可以通过 ping 命令进行测试。
bash
[root@linuxprobe~]# nmcli connection up ens160
Connection successfully activated (D-Bus active path: /org/freedesktop/
NetworkManager/ActiveConnection/4)
[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
**第二步:**在原有软件仓库配置的下方,追加 EPEL 扩展软件包安装源的信息。
bash
[root@linuxprobe~]# vim /etc/yum.repos.d/rhel.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/8/Everything/x86_
64/
enabled=1
gpgcheck=0
**第三步:**安装!
bash
[root@linuxprobe~]# dnf install -y ansible
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered to Red Hat Subscription Management. You can use
subscription-manager to register.
Last metadata expiration check: 0:01:31 ago on Sun 04 Apr 2021 02:23:32 AM CST.
Dependencies resolved.
===============================================================================
Package Arch Version Repository Size
===============================================================================
Installing:
ansible noarch 2.9.18-2.el8 EPEL 17 M
Installing dependencies:
python3-babel noarch 2.5.1-3.el8 AppStream 4.8 M
python3-jinja2 noarch 2.10-9.el8 AppStream 537 k
python3-jmespath noarch 0.9.0-11.el8 AppStream 45 k
python3-markupsafe x86_64 0.23-19.el8 AppStream 39 k
python3-pyasn1 noarch 0.3.7-6.el8 AppStream 126 k
libsodium x86_64 1.0.18-2.el8 EPEL 162 k
python3-bcrypt x86_64 3.1.6-2.el8.1 EPEL 44 k
python3-pynacl x86_64 1.3.0-5.el8 EPEL 100 k
sshpass x86_64 1.06-9.el8 EPEL 27 k
Installing weak dependencies:
python3-paramiko noarch 2.4.3-1.el8 EPEL 289 k
Transaction Summary
===============================================================================
Install 11 Packages
..................省略部分输出信息............
Installed:
ansible-2.9.18-2.el8.noarch python3-paramiko-2.4.3-1.el8.noarch
python3-babel-2.5.1-3.el8.noarch python3-jinja2-2.10-9.el8.noarch
python3-jmespath-0.9-11.el8.noarch python3-markupsafe-0.23-19.el8.x86_64
python3-pyasn1-0.3.7-6.el8.noarch libsodium-1.0.18-2.el8.x86_64
python3-bcrypt-3.1.6-2.el8.1.x86_64 python3-pynacl-1.3.0-5.el8.x86_64
sshpass-1.06-9.el8.x86_64
Complete!
安装完毕后,Ansible 服务便默认已经启动。使用--version 参数可以看到 Ansible 服务的版本及配置信息。
bash
[root@linuxprobe~]# ansible --version
ansible 2.9.18
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.6/site-packages/ansible
executable location = /usr/bin/ansible
python version = 3.6.8 (default, Jan 11 2019, 02:17:16) [GCC 8.2.1 20180905
(Red Hat 8.2.1-3)]
2. 设置主机清单
在初次使用 Ansible 服务时,大家可能会遇到这种情况:参数明明已经修改了,但却不生效。这是因为 Ansible 服务的主配置文件存在优先级的顺序关系,默认存放在/etc/ansible 目录中的主配置文件优先级最低。如果在当前目录或用户家目录中也存放着一份主配置文件,则以当前目录或用户家目录中的主配置文件为主。同时存在多个 Ansible 服务主配置文件时,具体优先级顺序如表。
既然 Ansible 服务是用于实现主机批量自动化控制的管理工具,受管的主机一定不是一两台,而是数十台甚至成百上千台,那么主机清单(inventory)在生产环境中就可以帮上大忙了。用户可以把要管理的主机 IP 地址预先写入/etc/ansible/hosts 文件,这样后续再通过执行ansible 命令来执行任务时就自动包含这些主机了,也就不需要每次都重复输入受管主机的地址了。例如,要管理 5 台主机,对应的 IP 地址如表 16-3 所示。
首先需要说明的是,受管主机的系统默认使用 RHEL 8,这是为了避免大家在准备实验机阶段产生歧义而给出的建议值,也可以用其他 Linux 系统。主机清单文件/etc/ansible/hosts 中默认存在大量的注释信息,建议全部删除,然后替换成实验信息。
bash
[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)。在对主机进行分组标注后,后期在管理时就方便多了。
bash
[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"命令以结构化的方式显示出受管主机的信息。因为我们对受管主机进行了分组,因此这种方式非常便于我们的阅读。
bash
[root@linuxprobe~]# ansible-inventory --graph
@all:
|--@balancers:
| |--192.168.10.24
|--@dev:
| |--192.168.10.20
|--@prod:
| |--192.168.10.22
| |--192.168.10.23
|--@test:
| |--192.168.10.21
|--@ungrouped:
先不要着急开始后面的实验。前文讲过,Ansible 服务是基于 SSH 协议进行自动化控制的,这是开展后面实验的前提条件。前面章节讲到,sshd 服务在初次连接时会要求用户接受一次对方主机的指纹信息。准备输入受管主机的账号和密码。例如,正常的第一次SSH 远程连接过程是这样的:
bash
[root@linuxprobe~]# ssh 192.168.10.10
The authenticity of host '192.168.10.10 (192.168.10.10)' can't be established.
ECDSA key fingerprint is SHA256:QRW1wrqdwN0PI2bsUvBlW5XOIpBjE+ujCB8yiCqjMQQ.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.10.10' (ECDSA) to the list of known hosts.
root@192.168.10.10's password: (此处输入管理员密码后回车确认)
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Mon Mar 29 06:30:15 2021
[root@linuxprobe~]#
众所周知,自动化运维的一个好处就是能提高工作效率。但是,如果每次执行操作都要输入受管主机的密码,也是比较麻烦的事情。好在 Ansible 服务已经对此有了解决办法,那就是使用如表 16-4 所示的变量。
用户只需要将对应的变量及信息填写到主机清单文件中,在执行任务时便会自动对账号和密码进行匹配,而不用每次重复输入它们。继续修改主机清单文件:
bash
[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 主配置文件中的第 71 行设置成默认不需要 SSH 协议的指纹验证,以及将第 107 行设置成默认执行剧本时所使用的管理员名称为 root:
bash
[root@linuxprobe~]# vim /etc/ansible/ansible.cfg
69
70 # uncomment this to disable SSH key host checking
71 host_key_checking = False
72
..................省略部分输出信息..................
104
105 # default user to use for playbooks if user is not specified
106 # (/usr/bin/ansible will use current user as default)
107 remote_user = root
108
不需要重启服务,在以上操作完全搞定后就可以开始后面的实验了。由于刚才是将Ansible 服务器设置成了桥接及 DHCP 模式,现在请同学们自行将网络适配器修改回"仅主机模式"(见图 16-3)以及 192.168.10.10/24 的 IP 地址。在修改完成后重启网卡,然后自行在主机之间执行 ping 操作。保证主机之间的网络能够互通是后续实验的基石。
bash
[root@linuxprobe~]# 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::d0bb:17c8:880d:e719 prefixlen 64 scopeid 0x20
ether 00:0c:29:7d:27:bf txqueuelen 1000 (Ethernet)
RX packets 32 bytes 5134 (5.0 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 43 bytes 4845 (4.7 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
..................省略部分输出信息..................
3. 运行临时命令
Ansible 服务的强大之处在于只需要一条命令,便可以操控成千上万台的主机节点,而ansible 命令便是最得力的工具之一。前文提到,Ansible 服务实际上只是一个框架,能够完成工作的是模块化功能代码。Ansible 的常用模块大致有 20 多个(见表 16-5),会在后面的实验中逐一详解。
偶尔遇到没有提及的模块,大家可以使用"ansible-doc 模块名称"的命令格式自行查询,或是使用 ansibe-doc -l 命令列出所有的模块信息以供选择。
在 Ansible 服务中,ansible 是用于执行临时任务的命令,也就在是执行后即结束(与剧本文件的可重复执行不同)。在使用 ansible 命令时,必须指明受管主机的信息,如果已经设置过主机清单文件(/etc/ansible/hosts),则可以使用 all 参数来指代全体受管主机,或是用 dev、test 等主机组名称来指代某一组的主机。
ansible 命令常用的语法格式为"ansible 受管主机节点 -m 模块名称[-a 模块参数]",常见的参数如表 16-6 所示。其中,-a 是要传递给模块的参数,只有功能极其简单的模块才不需要额外参数,所以大多情况下-m 与-a 参数都会同时出现。
如果想实现某个功能,但是却不知道用什么模块,又或者是知道了模块名称,但不清楚模块具体的作用,则建议使用 ansible-doc 命令进行查找。例如,列举出当前 Ansible 服务所支持的所有模块信息:
bash
[root@linuxprobe~]# ansible-doc -l
a10_server Manage A10 Networks AX/SoftAX/Thunder/v...
a10_server_axapi3 Manage A10 Networks AX/SoftAX/Thunder/v...
a10_service_group Manage A10 Networks AX/SoftAX/Thunder/v...
a10_virtual_server Manage A10 Networks AX/SoftAX/Thunder/v...
aci_aaa_user Manage AAA users (aaa:User)
aci_aaa_user_certificate Manage AAA user certificates (aaa:User...
aci_access_port_block_to_access_port Manage port blocks of Fabric interface ...
aci_access_port_to_interface_policy_leaf_profile Manage Fabric interface
policy leaf pro...
aci_access_sub_port_block_to_access_port Manage sub port blocks of Fabric
interf...
aci_aep Manage attachable Access Entity Profile...
aci_aep_to_domain Bind AEPs to Physical or Virtual Domain...
aci_bd_subnet Manage Subnets (fv:Subnet)
..................省略部分输出信息..................
一般情况下,很难通过名称来判别一个模块的作用,要么是参考模块后面的介绍信息,要么是平时多学多练,进行积累。例如,接下来随机查看一个模块的详细信息。ansible-doc命令会在屏幕上显示出这个模块的作用、可用参数及实例等信息:
bash
[root@linuxprobe~]# ansible-doc a10_server
> A10_SERVER (/usr/lib/python3.6/site-packages/ansible/modules/network/a10/
a10_server.py)
Manage SLB (Server Load Balancer) server objects on A10 Networks devices
via aXAPIv2.
* This module is maintained by The Ansible Community
..................省略部分输出信息..................
在上一小节中,已经成功地将受管主机的 IP 地址填写到主机清单文件中,接下来小试牛刀,检查一下这些主机的网络连通性。ping 模块用于进行简单的网络测试(类似于常用的 ping 命令)。可以使用 ansible 命令直接针对所有主机调用 ping 模块,不需要增加额外的参数,返回值若为 SUCCESS,则表示主机当前在线。
bash
[root@linuxprobe~]# ansible all -m ping
192.168.10.20 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
192.168.10.21 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
192.168.10.22 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
192.168.10.23 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}192.168.10.24 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
注:由于 5 台受控主机的输出信息大致相同,因此,本章后续的输出结果默认仅保留192.168.10.20 主机的输出值,其余相同的输出信息将会被省略。
是不是感觉很方便呢?!一次就能知道所有主机的在线情况。除了使用-m 参数直接指定模块名称之外,还可以用-a 参数将参数传递给模块,让模块的功能更高级,更好地满足当前生产的需求。例如,yum_repository 模块的作用是管理主机的软件仓库,能够添加、修改及删除软件仓库的配置信息,参数相对比较复杂。遇到这种情况时,建议先用 ansible-doc 命令对其进行了解。尤其是下面的 EXAMPLES 结构段会有该模块的实例,对用户来说有非常高的参考价值。
bash
[root@linuxprobe~]# ansible-doc yum_repository
> YUM_REPOSITORY (/usr/lib/python3.6/site-packages/ansible/modules/packaging>
Add or remove YUM repositories in RPM-based Linux
distributions. If you wish to update an existing repository
definition use [ini_file] instead.
* This module is maintained by The Ansible Core Team
........................省略部分输出信息..................
EXAMPLES:
- name: Add repository
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)
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)
yum_repository:
name: rpmforge
description: RPMforge YUM repo
file: external_repos
baseurl: http://apt.sw.be/redhat/el7/en/$basearch/rpmforge
还好,参数并不是很多,而且与此前学过的/etc/yum.repos.d/目录中的配置文件基本相似。现在,想为主机清单中的所有服务器新增一个如表 16-7 所示的软件仓库,该怎么操作呢?
我们可以对照着 EXAMPLE 实例段,逐一对应填写需求值和参数,其标准格式是在-a 参数后接整体参数(用单引号圈起),而各个参数字段的值则用双引号圈起。这是最严谨的写法。在执行下述命令后如果出现 CHANGED 字样,则表示修改已经成功:
bash
[root@linuxprobe~]# ansible all -m yum_repository -a 'name="EX294_BASE" descript
ion
="EX294 base software" baseurl="file:///media/cdrom/BaseOS" gpgcheck=yes en
abled=1
gpgkey="file:///media/cdrom/RPM-GPG-KEY-redhat-release"'
192.168.10.20 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": true,
"repo": "EX294_BASE",
"state": "present"
}
在命令执行成功后,可以到主机清单中的任意机器上查看新建成功的软件仓库配置文件。尽管这个实验的参数很多,但是并不难。
bash
[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
4. 剧本文件实战
在很多情况下,仅仅执行单个命令或调用某一个模块,根本无法满足复杂工作的需要。Ansible 服务允许用户根据需求,在类似于 Shell 脚本的模式下编写自动化运维脚本,然后由程序自动、重复地执行,从而大大提高了工作效率。
Ansible 服务的剧本(playbook)文件采用 YAML 语言编写,具有强制性的格式规范,它通过空格将不同信息分组,因此有时会因一两个空格错位而导致报错。大家在使用时要万分小心。YAML 文件的开头需要先写 3 个减号(---),多个分组的信息需要间隔一致才能执行,而且上下也要对齐,后缀名一般为.yml。剧本文件在执行后,会在屏幕上输出运行界面,内容会根据工作的不同而变化。在运行界面中,绿色表示成功,黄色表示执行成功并进行了修改,而红色则表示执行失败。
剧本文件的结构由 4 部分组成,分别是 target、variable、task、handler,其各自的作用如下。
➢ target: 用于定义要执行剧本的主机范围。
➢ variable: 用于定义剧本执行时要用到的变量。
➢ task: 用于定义将在远程主机上执行的任务列表。
➢ handler: 用于定义执行完成后需要调用的后续任务。
YAML 语言编写的 Ansible 剧本文件会按照从上到下的顺序自动运行,其形式类似于 Shell 脚本,但格式有严格的要求。例如,创建一个名为 packages.yml 的剧本,让 dev、test 和 prod 组的主机可以自动安装数据库软件,并且将 dev 组主机的软件更新至最新。
安装和更新软件需要使用 yum 模块。先看一下帮助信息中的示例吧:
bash
[root@linuxprobe~]# ansible-doc yum
> YUM (/usr/lib/python3.6/site-packages/ansible/modules/packaging/os/yum.py)
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 [dnf]
module.
* This module is maintained by The Ansible Core Team
* note: This module has a corresponding action plugin.
..................省略部分输出信息..................
EXAMPLES:
- name: install the latest version of Apache
yum:
name: httpd
state: latest
在配置 Ansible 剧本文件时,ansible-doc 命令提供的帮助信息真是好用。在知道 yum模块的使用方法和格式后,就可以开始编写剧本了。初次编写剧本文件时,请务必看准格式,模块及 play(动作)格式也要上下对齐,否则会出现"参数一模一样,但不能执行"的情况。
综上,一个剧本正确的写法应该是:
bash
[root@linuxprobe~]# vim packages.yml
---
- name: 安装软件包
hosts: dev,test,prod
tasks:
- name: one
yum:
name: mariadb
state: latest
[root@linuxprobe~]#
其中,name 字段表示此项 play(动作)的名字,用于在执行过程中提示用户执行到了哪一步,以及帮助管理员在日后阅读时能想起这段代码的作用。大家可以在 name 字段自行命名,没有任何限制。hosts 字段表示要在哪些主机上执行该剧本,多个主机组之间用逗号间隔;如果需要对全部主机进行操作,则使用 all 参数。tasks 字段用于定义要执行的任务,每个任务都要有一个独立的 name 字段进行命名,并且每个任务的 name 字段和模块名称都要严格上下对齐,参数要单独缩进。
而错误的剧本文件是下面这样的:
bash
[root@linuxprobe~]# vim packages.yml
---
- name: 安装软件包
hosts: dev,test,prod
tasks:
- name: one
yum:
name: mariadb
state: latest
大家可以感受到 YAML 语言对格式要求有多严格吧。
在编写 Ansible 剧本文件时,RHEL 8 系统自带的 Vim 编辑器具有自动缩进功能,这可以给我们提供很多帮助。在确认无误后就可以用 ansible-playbook 命令运行这个剧本文件了。
bash
[root@linuxprobe~]# 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 软件包,此时会提示该服务已经存在。这说明刚才的操作一切顺利!
bash
[root@linuxprobe~]# dnf install mariadb
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered to Red Hat Subscription Management. You can use
subscription-manager to register.
Last metadata expiration check: 1:05:53 ago on Thu 15 Apr 2021 08:29:11 AM CST.
Package mariadb-3:10.3.11-1.module+el8+2765+cfa4f87b.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!
5. 创建及使用角色
在日常编写剧本时,会存在剧本越来越长的情况,这不利于进行阅读和维护,而且还无法让其他剧本灵活地调用其中的功能代码。角色(role)这一功能则是自 Ansible 1.2 版本开始引入的新特性,用于层次性、结构化地组织剧本。角色功能分别把变量、文件、任务、模块及处理器配置放在各个独立的目录中,然后对其进行便捷加载。简单来说,角色功能是把常用的一些功能"类模块化",然后在用的时候加载即可。
Ansible 服务的角色功能类似于编程中的封装技术---将具体的功能封装起来,用户不仅可以方便地调用它,而且甚至可以不用完全理解其中的原理。就像普通消费者不需要深入理解汽车刹车是如何实现的,制动总泵、刹车分泵、真空助力器、刹车盘、刹车鼓、刹车片或ABS 泵都藏于底层结构中,用户只需要用脚轻踩刹车踏板就能制动汽车。这便是技术封装的好处。
角色的好处就在于将剧本组织成了一个简洁的、可重复调用的抽象对象,使得用户把注意力放到剧本的宏观大局上,统筹各个关键性任务,只有在需要时才去深入了解细节。角色的获取有 3 种方法,分别是加载系统内置角色、从外部环境获取角色以及自行创建角色。
5.1 加载系统内置角色
在使用 RHEL 系统的内置角色时,我们不需要联网就能实现。用户只需要配置好软件仓库的配置文件,然后安装包含系统角色的软件包 rhel-system-roles,随后便可以在系统中找到它们了,然后就能够使用剧本文件调用角色了。
bash
[root@linuxprobe~]# dnf install -y rhel-system-roles
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered to Red Hat Subscription Management. You can use
subscription-manager to register.
Last metadata expiration check: 1:06:26 ago on Tue 13 Apr 2021 07:22:03 AM CST.
Dependencies resolved.
===============================================================================
Package Arch Version Repository Size
===============================================================================
Installing:
rhel-system-roles noarch 1.0-5.el8 AppStream 127 k
Transaction Summary
===============================================================================
Install 1 Package
..................省略部分输出信息..................
Installed:
rhel-system-roles-1.0-5.el8.noarch
Complete!
安装完毕后,使用 ansible-galaxy list 命令查看 RHEL 8 系统中有哪些自带的角色可用:
bash
[root@linuxprobe~]# ansible-galaxy list
# /usr/share/ansible/roles
- linux-system-roles.kdump, (unknown version)
- linux-system-roles.network, (unknown version)
- linux-system-roles.postfix, (unknown version)
- linux-system-roles.selinux, (unknown version)
- linux-system-roles.timesync, (unknown version)
- rhel-system-roles.kdump, (unknown version)
- rhel-system-roles.network, (unknown version)
- rhel-system-roles.postfix, (unknown version)
- rhel-system-roles.selinux, (unknown version)
- rhel-system-roles.timesync, (unknown version)
# /etc/ansible/roles
[WARNING]: - the configured path /root/.ansible/roles does not exist.
大家千万不要低估这些由系统镜像自带的角色,它们在日常的工作中能派上大用场。这
些角色的主要功能如表所示。
以 rhel-system-roles.timesync 角色为例,它用于设置系统的时间和 NTP 服务,让主机能够同步准确的时间信息。剧本模板文件存放在/usr/share/doc/rhel-system-roles/目录中,可以复制过来修改使用:
bash
[root@linuxprobe~]# cp /usr/share/doc/rhel-system-roles/timesync/example-
timesync-playbook.yml timesync.yml
NTP 服务器主要用于同步计算机的时间,可以提供高精度的时间校准服务,帮助计算机校对系统时钟。在复制来的剧本模板文件中,删除掉多余的代码,将 NTP 服务器的地址填写到 timesync_ntp_servers 变量的 hostname 字段中即可。该变量的参数含义如表所示。稍后timesync 角色就会自动为用户配置参数信息了。
bash
[root@linuxprobe~]# vim timesync.yml
---
- hosts: all
vars:
timesync_ntp_servers:
- hostname: pool.ntp.org
iburst: yes
roles:
- rhel-system-roles.timesync
5.2 从外部环境获取角色
Ansible Galaxy 是 Ansible 的一个官方社区,用于共享角色和功能代码,用户可以在网站自由地共享和下载 Ansible 角色。该社区是管理和使用角色的不二之选。
在图 16-4 所示的 Ansible Galaxy 官网中,左侧有 3 个功能选项,分别是首页(Home)、搜索(Search)以及社区(Community)。单击 Search 按钮进入到搜索界面,这里以 nginx 服务为例进行搜索,即可找到 Nginx 官方发布的角色信息,如图 16-5 所示。
当单击 nginx 角色进入到详情页面后,会显示这个项目的软件版本、评分、下载次数等信息。在 Installation 字段可以看到相应的安装方式,如图 16-6 所示。在保持虚拟机能够连接外网的前提下,可以按这个页面提示的命令进行安装。
这时,如果需要使用这个角色,可以在虚拟机联网的状态下直接按照"ansible-galaxy install角色名称"的命令格式自动获取:
bash
[root@linuxprobe~]# ansible-galaxy install nginxinc.nginx
- downloading role 'nginx', owned by nginxinc
- downloading role from https://github.com/nginxinc/ansible-role-nginx/archive/
0.19.1.tar.gz
- extracting nginxinc.nginx to /etc/ansible/roles/nginxinc.nginx
- nginxinc.nginx (0.19.1) was installed successfully
执行完毕后,再次查看系统中已有的角色,便可找到 nginx 角色信息了:
bash
[root@linuxprobe~]# ansible-galaxy list
# /etc/ansible/roles
- nginxinc.nginx, 0.19.1
# /usr/share/ansible/roles
- linux-system-roles.kdump, (unknown version)
- linux-system-roles.network, (unknown version)
- linux-system-roles.postfix, (unknown version)
- linux-system-roles.selinux, (unknown version)
- linux-system-roles.timesync, (unknown version)
- rhel-system-roles.kdump, (unknown version)
- rhel-system-roles.network, (unknown version)
- rhel-system-roles.postfix, (unknown version)
- rhel-system-roles.selinux, (unknown version)
- rhel-system-roles.timesync, (unknown version)
这里还存在两种特殊情况:
➢ 在国内访问 Ansible Galaxy 官网时可能存在不稳定的情况,导致访问不了或者网速较慢。
➢ 某位作者是将作品上传到了自己的网站,或者除 Ansible Galaxy 官网以外的其他平台。
在这两种情况下,就不能再用"ansible-galaxy install 角色名称"的命令直接加载了,而是需要手动先编写一个 YAML 语言格式的文件,指明网址链接和角色名称,然后再用-r 参数进行加载。
bash
[root@linuxprobe~]# cat nginx.yml
---
- src: https://www.linuxprobe.com/Software/nginxinc-nginx_core-0.3.0.tar.gz
name: nginx-core
随后使用 ansible-galaxy 命令的-r 参数加载这个文件,即可查看到新角色信息了:
bash
[root@linuxprobe~]# ansible-galaxy install -r nginx.yml
- downloading role from https://www.linuxprobe.com/nginxinc-nginx_core-0.3.0.tar.gz
- extracting nginx to /etc/ansible/roles/nginx
- nginx was installed successfully
[root@linuxprobe~]# ansible-galaxy list
# /etc/ansible/roles
- nginx-core, (unknown version)
- nginxinc.nginx, 0.19.1
# /usr/share/ansible/roles
- linux-system-roles.kdump, (unknown version)
- linux-system-roles.network, (unknown version)
- linux-system-roles.postfix, (unknown version)
- linux-system-roles.selinux, (unknown version)
- linux-system-roles.timesync, (unknown version)
- rhel-system-roles.kdump, (unknown version)
- rhel-system-roles.network, (unknown version)
- rhel-system-roles.postfix, (unknown version)
- rhel-system-roles.selinux, (unknown version)
- rhel-system-roles.timesync, (unknown version)
5.3 自行创建角色
除了能够使用系统自带的角色和从 Ansible Galaxy 中获取的角色之外,也可以自行创建符合工作需求的角色。这种定制化的编写工作能够更好地贴合生产环境的实际情况,但难度也会稍高些。
接下来将会创建一个名为 apache 的新角色,它能够帮助我们自动安装、运行 httpd 网站服务,设置防火墙的允许规则,以及根据每个主机生成独立的 index.html 首页文件。用户在调用这个角色后能享受到"一条龙"的网站部署服务。
在 Ansible 的主配置文件中,第 68 行定义的是角色保存路径。如果用户新建的角色信息不在规定的目录内,则无法使用 ansible-galaxy list 命令找到。因此需要手动填写新角色的目录路径,或是进入/etc/ansible/roles 目录内再进行创建。为了避免后期角色信息过于分散导致不好管理,我们还是决定在默认目录下进行创建,不再修改。
bash
[root@linuxprobe roles]# vim /etc/ansible/ansible.cfg
66
67 # additional paths to search for roles in, colon separated
68 #roles_path = /etc/ansible/roles
69
在 ansible-galaxy 命令后面跟一个 init 参数,创建一个新的角色信息,且建立成功后便会在当前目录下生成出一个新的目录:
bash
[root@linuxprobe~]# cd /etc/ansible/roles
[root@linuxprobe roles]# ansible-galaxy init apache
- Role apache was created successfully
[root@linuxprobe roles]# ls
apache nginx nginxinc.nginx
此时的 apache 即是角色名称,也是用于存在角色信息的目录名称。切换到该目录下,查看它的结构:
bash
[root@linuxprobe roles]# cd apache
[root@linuxprobe apache]# ls
defaults files handlers meta README.md tasks templates tests vars
在创建新角色时,最关键的便是能够正确理解目录结构。通俗来说,就是要把正确的信息放入正确的目录中,这样在调用角色时才能有正确的效果。角色信息对应的目录结构及含义如表所示。
下面准备创建新角色。
**第一步:**打开用于定义角色任务的 tasks/main.yml 文件。在该文件中不需要定义要执行的主机组列表,因为后面会单独编写剧本进行调用,此时应先对 apache 角色能做的事情(任务)有一个明确的思路,在调用角色后 yml 文件会按照从上到下的顺序自动执行。
➢ 任务 1 : 安装 httpd 网站服务。
➢ 任务 2 : 运行 httpd 网站服务,并加入到开机启动项中。
➢ 任务 3 : 配置防火墙,使其放行 HTTP 协议。
**➢ 任务 4 :**根据每台主机的变量值,生成不同的主页文件。
先写出第一个任务。使用 yum 模块安装 httpd 网站服务程序(注意格式):
bash
[root@linuxprobe apache]# vim tasks/main.yml
---
- name: one
yum:
name: httpd
state: latest
**第二步:**使用 service 模块启动 httpd 网站服务程序,并加入到启动项中,保证能够一直为用户提供服务。在初次使用模块前,先用 ansible-doc 命令查看一下帮助和实例信息。由于篇幅的限制,这里对信息进行了删减,仅保留了有用的内容。
bash
[root@linuxprobe apache]# ansible-doc service
> SERVICE (/usr/lib/python3.6/site-packages/ansible/modules/system/service.py)
Controls services on remote hosts. Supported init systems
include BSD init, OpenRC, SysV, Solaris SMF, systemd, upstart.
For Windows targets, use the [win_service] module instead.
* This module is maintained by The Ansible Core Team
* note: This module has a corresponding action plugin.
..................省略部分输出信息..................
EXAMPLES:
- name: Start service httpd, if not started
service:
name: httpd
state: started
- name: Enable service httpd, and not touch the state
service:
name: httpd
enabled: yes
真幸运,默认的 EXAMPLES 示例使用的就是 httpd 网站服务。通过输出信息可得知,启动服务为"state: started"参数,而加入到开机启动项则是"enabled: yes"参数。继续编写:
bash
[root@linuxprobe apache]# vim tasks/main.yml
---
- name: one
yum:
name: httpd
state: latest
- name: two
service:
name: httpd
state: started
enabled: yes
**第三步:**配置防火墙的允许策略,让其他主机可以正常访问。在配置防火墙时,需要使用 firewalld 模块。同样也是先看一下帮助示例:
bash
[root@linuxprobe defaults]# ansible-doc firewalld
> FIREWALLD (/usr/lib/python3.6/site-packages/ansible/modules/system/firewalld.py)
This module allows for addition or deletion of services and
ports (either TCP or UDP) in either running or permanent
firewalld rules.
* This module is maintained by The Ansible Community
OPTIONS (= is mandatory):
EXAMPLES:
- firewalld:
service: https
permanent: yes
state: enabled
- firewalld:
port: 8081/tcp
permanent: yes
state: disabled
immediate: yes
依据输出信息可得知,在 firewalld 模块设置防火墙策略时,指定协议名称为"service: http"参数,放行该协议为"state: enabled"参数,设置为永久生效为"permanent: yes"参数,当前立即生效为"immediate: yes"参数。参数虽然多了一些,但是基本与前面所学习的一致,并不需要担心。继续编写:
bash
[root@linuxprobe 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
**第四步:**让每台主机显示的主页文件均不相同。在使用 Ansible 的常规模块时,都是采用"查询版主示例并模仿"的方式搞定的,这里为了增加难度,我们再提出个新需求,即能否让每台主机上运行的 httpd 网站服务都能显示不同的内容呢?例如显示当前服务器的主机名及 IP 地址。这就要用到 template 模块及 Jinja2 技术了。
我们依然使用 ansible-doc 命令来查询 template 模块的使用方法。示例部分依然大有帮助:
bash
[root@linuxprobe apache]# ansible-doc template
> TEMPLATE (/usr/lib/python3.6/site-packages/ansible/modules/files/template.>
Templates are processed by the L(Jinja2 templating
language,http://jinja.pocoo.org/docs/). Documentation on the
template formatting can be found in the L(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.
* This module is maintained by The Ansible Core Team
* note: This module has a corresponding action plugin.
..................省略部分输出信息..................
EXAMPLES:
- name: Template a file to /etc/files.conf
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。继续编写:
bash
[root@linuxprobe 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
Jinja2 是 Python 语言中一个被广泛使用的模板引擎,最初的设计思想源自 Django 的模块引擎。Jinja2 基于此发展了其语法和一系列强大的功能,能够让受管主机根据自身变量产生出不同的文件内容。换句话说,正常情况下的复制操作会让新旧文件一模一样,但在使用 Jinja2 技术时,不是在原始文件中直接写入文件内容,而是写入一系列的变量名称。在使用 template 模块进行复制的过程中,由 Ansible 服务负责在受管主机上收集这些变量名称所对应的值,然后再逐一填写到目标文件中,从而让每台主机的文件都根据自身系统的情况独立生成。
例如,想要让每个网站的输出信息值为"Welcome to 主机名 on 主机地址",也就是用每个主机自己独有的名称和 IP 地址来替换文本中的内容,这样就有趣太多了。这个实验的难点在于查询到对应的变量名称、主机名及地址所对应的值保存在哪里?可以用 setup 模块进行查询。
bash
[root@linuxprobe apache]# ansible-doc setup
> SETUP (/usr/lib/python3.6/site-packages/ansible/modules/system/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 变量保存有主机名称。随后进行下一步操作:
bash
[root@linuxprobe~]# ansible all -m setup -a 'filter="*fqdn*"'
192.168.10.20 | SUCCESS => {
"ansible_facts": {
"ansible_fqdn": "linuxprobe.com",
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
..................省略部分输出信息..................
用于指定主机地址的变量可以用 ip 作为关键词进行检索。可以看到,ansible_all_ipv4_addresses变量中的值是我们想要的信息。如果想输出 IPv6 形式的地址,则可用 ansible_all_ipv6_addresses
变量。
bash
[root@linuxprobe~]# ansible all -m setup -a 'filter="*ip*"'
192.168.10.20 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.10.20",
"192.168.122.1"
],
"ansible_all_ipv6_addresses": [
"fe80::d0bb:17c8:880d:e719"
],
"ansible_default_ipv4": {},
"ansible_default_ipv6": {},
"ansible_fips": false,
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
..................省略部分输出信息..................
在确认了主机名与 IP 地址所对应的具体变量名称后,在角色所对应的 templates 目录内新建一个与上面的 template 模块参数相同的文件名称(index.html.j2)。Jinja2 在调用变量值时,格式为在变量名称的两侧格加两个大括号:
bash
[root@linuxprobe apache]# vim templates/index.html.j2
Welcome to {{ ansible_fqdn }} on {{ ansible_all_ipv4_addresses }}
进行到这里,任务基本就算完成了。最后要做的就是编写一个用于调用 apache 角色的 yml文件,以及执行这个文件。
bash
[root@linuxprobe apache]# cd~
[root@linuxprobe~]# 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 所示。
实验成功!
6. 创建和使用逻辑卷
创建一个能批量、自动管理逻辑卷设备的剧本,不但能大大提高硬盘设备的管理效率,而且还能避免手动创建带来的错误。例如,我们想在每台受管主机上都创建出一个名为 data的逻辑卷设备,大小为 150MB,归属于 research 卷组。如果创建成功,则进一步用 Ext4 文件系统进行格式化操作;如果创建失败,则给用户输出一条报错提醒,以便排查原因。
在这种情况下,使用 Ansible 剧本要比使用 Shell 脚本的优势大,原因主要有下面两点。
➢ Ansible 模块化的功能让操作更标准: 只要在执行过程中无报错,那么便会依据远程主机的系统版本及配置自动做出判断和操作,不用担心因系统变化而导致命令失效的问题。
➢ Ansible 服务在执行剧本文件时会进行判断: 如果该文件或该设备已经被创建过,或是某个动作(play)已经被执行过,则绝对不会再重复执行;而使用 Shell 脚本有可能导致设备被重复格式化,导致数据丢失。
首先在 prod 组的两台主机上分别添加一块硬盘设备,大小为 20GB,类型为 SCSI,其余选项选择默认值,如图 16-10~图 16-12 所示。
通过学习过的逻辑卷的知识,我们应该让剧本文件依次创建物理卷(PV)、卷组(VG)及逻辑卷(LV)。需要先使用 lvg 模块让设备支持逻辑卷技术,然后创建一个名为 research 的卷组。lvg 模块的帮助信息如下:
bash
[root@linuxprobe~]# ansible-doc lvg
> LVG (/usr/lib/python3.6/site-packages/ansible/modules/system/lvg.py)
This module creates, removes or resizes volume groups.
* This module is maintained by The Ansible Community
..................省略部分输出信息..................
EXAMPLES:
- name: Create a volume group on top of /dev/sda1 with physical extent size = 3>
lvg:
vg: vg.services
pvs: /dev/sda1
pesize: 32
- name: Create a volume group on top of /dev/sdb with physical extent size = 12>
lvg:
vg: vg.services
pvs: /dev/sdb
pesize: 128K
通过输出信息可得知,创建 PV 和 VG 的 lvg 模块总共有 3 个必备参数。其中,vg 参数用于定义卷组的名称,pvs 参数用于指定硬盘设备的名称,pesize 参数用于确定最终卷组的容量大小(可以用 PE 个数或容量值进行指定)。这样一来,我们先创建出一个由/dev/sdb 设备组成的名称为 research、大小为 150MB 的卷组设备。
bash
[root@linuxprobe~]# vim lv.yml
---
- name: 创建和使用逻辑卷
hosts: all
tasks:
- name: one
lvg:
vg: research
pvs: /dev/sdb
pesize: 150M
由于刚才只在 prod 组的两台主机上添加了新硬盘设备文件,因此在执行上述操作时其余3 台主机会提示未创建成功,这属于正常情况。接下来使用 lvol 模块创建出逻辑卷设备。还是按照惯例,先查看模块的帮助信息:
bash
[root@linuxprobe~]# ansible-doc lvol
> LVOL (/usr/lib/python3.6/site-packages/ansible/modules/system/lvol.py)
This module creates, removes or resizes logical volumes.
* This module is maintained by The Ansible Community
..................省略部分输出信息..................
EXAMPLES:
- name: Create a logical volume of 512m
lvol:
vg: firefly
lv: test
size: 512
- name: Create a logical volume of 512m with disks /dev/sda and /dev/sdb
lvol:
vg: firefly
lv: test
size: 512
pvs: /dev/sda,/dev/sdb
通过输出信息可得知,lvol 是用于创建逻辑卷设备的模块。其中,vg 参数用于指定卷组名称,lv 参数用于指定逻辑卷名称,size 参数则用于指定最终逻辑卷设备的容量大小(不用加单位,默认为 MB)。填写好参数,创建出一个大小为 150MB、归属于 research 卷组且名称为 data 的逻辑卷设备:
bash
[root@linuxprobe~]# 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 模块来完成设备的文件系统格式化操作。该模块的帮助信息如下:
bash
[root@linuxprobe~]# ansible-doc filesystem
> FILESYSTEM (/usr/lib/python3.6/site-packages/ansible/modules/system/filesy>
This module creates a filesystem.
* This module is maintained by The Ansible Community
..................省略部分输出信息..................
EXAMPLES:
- name: Create a ext2 filesystem on /dev/sdb1
filesystem:
fstype: ext2
dev: /dev/sdb1
filesystem 模块的参数真是简练,fstype 参数用于指定文件系统的格式化类型,dev 参数用于指定要格式化的设备文件路径。继续编写:
bash
[root@linuxprobe~]# 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 组的主机上添加了新的硬盘设备文件,其余主机是无法按照既定模块顺利完成操作的。这时就要使用类似于 if 条件语句的方式进行判断---如果失败......,则......。
首先用 block 操作符将上述的 3 个模块命令作为一个整体(相当于对这 3 个模块的执行结果作为一个整体进行判断),然后使用 rescue 操作符进行救援,且只有 block 块中的模块执行失败后才会调用 rescue 中的救援模块。其中,debug 模块的 msg 参数的作用是,如果 block中的模块执行失败,则输出一条信息到屏幕,用于提醒用户。完成编写后的剧本是下面这个样子:
bash
[root@linuxprobe~]# 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 剧本文件检阅一下效果:
bash
[root@linuxprobe~]# 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 组的主机上,找到新建的逻辑卷设备信息:
bash
[root@linuxprobe~]# lvdisplay
--- Logical volume ---
LV Path /dev/research/data
LV Name data
VG Name research
LV UUID EOUliC-tbkk-kOJR-8NaH-O9XQ-ijrK-TgEYGj
LV Write Access read/write
LV Creation host, time linuxprobe.com, 2021-04-23 11:00:21 +0800
LV Status available
# open 0
LV Size 5.00 GiB
Current LE 1
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 8192
Block device 253:2
..................省略部分输出信息..................
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 模块的帮助信息:
bash
[root@linuxprobe~]# ansible-doc copy
> COPY (/usr/lib/python3.6/site-packages/ansible/modules/files/copy.py)
The `copy' module copies a file from the local or remote
machine to a location on the remote machine. Use the [fetch]
module to copy files from remote locations to the local box.
If you need variable interpolation in copied files, use the
[template] module. Using a variable in the `content' field
will result in unpredictable output. For Windows targets, use
the [win_copy] module instead.
* This module is maintained by The Ansible Core Team
* note: This module has a corresponding action plugin.
..................省略部分输出信息..................
EXAMPLES:
- name: Copy file with owner and permissions
copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: '0644'
- name: Copy using inline content
copy:
content: '# This file was moved to /etc/other.conf'
dest: /etc/mine.conf
在输出信息中列举了两种管理文件内容的示例。第一种用于文件的复制行为,第二种是通过 content 参数定义内容,通过 dest 参数指定新建文件的名称。显然,第二种更加符合当前的实验场景。编写剧本文件如下:
bash
[root@linuxprobe~]# 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 是用于判断的语法,我们将其用在每个动作的下方进行判断,使得只有在满足条件才会执行:
bash
[root@linuxprobe~]# 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):
bash
[root@linuxprobe~]# 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 主机上,查看文件内容:
bash
[root@linuxprobe~]# cat /etc/issue
Development
登录到 test 组的 192.168.10.21 主机上,查看文件内容:
bash
[root@linuxprobe~]# cat /etc/issue
Test
登录到 prod 组的 192.168.10.22/23 主机上,查看文件内容:
bash
[root@linuxprobe~]# cat /etc/issue
Production
8. 管理文件属性
我们学习剧本的目的是为了满足日常的工作需求,把重复的事情写入到脚本中,然后再批量执行下去,从而提高运维工作的效率。其中,创建文件、管理权限以及设置快捷方式几乎是每天都用到的技能。尤其是在学习文件的一般权限、特殊权限、隐藏权限时,往往还会因命令的格式问题而导致出错。这么多命令该怎么记呢?
Ansible 服务将常用的文件管理功能都合并到了 file 模块中,大家不用再为了寻找模块而"东奔西跑"了。先来看一下 file 模块的帮助信息:
bash
[root@linuxprobe~]# ansible-doc file
> FILE (/usr/lib/python3.6/site-packages/ansible/modules/files/file.py)
Set attributes of files, symlinks or directories.
Alternatively, remove files, symlinks or directories. Many
other modules support the same options as the `file' module -
including [copy], [template], and [assemble]. For Windows
targets, use the [win_file] module instead.
* This module is maintained by The Ansible Core Team
..................省略部分输出信息..................
EXAMPLES:
- name: Change file ownership, group and permissions
file:
path: /etc/foo.conf
owner: foo
group: foo
mode: '0644'
- name: Create a symbolic link
file:
src: /file/to/link/to
dest: /path/to/symlink
owner: foo
group: foo
state: link
- name: Create a directory if it does not exist
file:
path: /etc/some_directory
state: directory
mode: '0755'
- name: Remove file (delete file)
file:
path: /etc/foo.txt
state: absent
通过上面的输出示例,大家已经能够了解 file 模块的基本参数了。其中,path 参数定义了文件的路径,owner 参数定义了文件所有者,group 参数定义了文件所属组,mode 参数定义了文件权限,src 参数定义了源文件的路径,dest 参数定义了目标文件的路径,state 参数则定义了文件类型。
可见,file 模块基本上把学习过的管理文件权限的功能都包含在内了。我们来就来挑战下面的实验吧:
请创建出一个名为/linuxprobe 的新目录,所有者及所属组均为 root 管理员身份;
设置所有者和所属于组拥有对文件的完全控制权,而其他人则只有阅读和执行权限;
给予 SGID 特殊权限;
仅在 dev 主机组的主机上实施。
第二条要求是算术题,即将权限描述转换为数字表示法,即可读为 4、可写为 2、可执行为 1。大家可以先自行默默计算一下答案。此前在编写剧本文件时,hosts 参数对应的一直是 all,即全体主机,这次需要修改为仅对 dev 主机组成员生效,请小心谨慎。编写模块代码如下:
bash
[root@linuxprobe~]# 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 服务会帮我们完成:
bash
[root@linuxprobe~]# 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
剧本文件的执行过程如下所示:
bash
[root@linuxprobe~]# 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 的快捷方式均已经被顺利创建:
bash
[root@linuxprobe~]# ls -ld /linuxprobe
drwxrwsr-x. 2 root root 6 Apr 20 09:52 /linuxprobe
[root@linuxprobe~]# ls -ld /linuxcool
lrwxrwxrwx. 1 root root 11 Apr 20 09:52 /linuxcool -> /linuxprobe
实验顺利完成。
9. 管理密码库文件
自 Ansible 1.5 版本发布后,vault 作为一项新功能进入到了运维人员的视野。它不仅能对密码、剧本等敏感信息进行加密,而且还可以加密变量名称和变量值,从而确保数据不会被他人轻易阅读。使用 ansible-vault 命令可以实现内容的新建(create)、加密(encrypt)、解密(decrypt)、修改密码(rekey)及查看(view)等功能。
下面通过示例来学习 vault 的具体用法。
**第一步:**创建出一个名为 locker.yml 的配置文件,其中保存了两个变量值:
bash
[root@linuxprobe~]# vim locker.yml
---
pw_developer: Imadev
pw_manager: Imamgr
第二步: 使用 ansible-vault 命令对文件进行加密。由于需要每次输入密码比较麻烦,因此还应新建一个用于保存密码值的文本文件,以便让 ansible-vault 命令自动调用。为了保证数据的安全性,在新建密码文件后将该文件的权限设置为 600,确保仅管理员可读可写:
bash
[root@linuxprobe~]# vim /root/secret.txt
whenyouwishuponastar
[root@linuxprobe~]# chmod 600 /root/secret.txt
在 Ansible 服务的主配置文件中,在第 140 行的 vault_password_file 参数后指定密码值保存的文件路径,准备进行调用:
bash
[root@linuxprobe~]# 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
第三步: 在设置好密码文件的路径后,Ansible 服务便会自动进行加载。用户也就不用在每次加密或解密时都重复输入密码了。例如,在加密刚刚创建的 locker.yml 文件时,只需要使用 encrypt 参数即可:
bash
[root@linuxprobe~]# ansible-vault encrypt locker.yml
Encryption successful
文件将使用 AES 256 加密方式进行加密,也就是意味着密钥有 2 256 种可能。查看到加密后的内容为:
bash
[root@linuxprobe~]# cat locker.yml
$ANSIBLE_VAULT;1.1;AES256
38653234313839336138383931663837333533396161343730353530313038313631653439366335
3432346333346239386334663836643432353434373733310a306662303565633762313232663763
38366334316239376262656230643531656665376166663635656436363338626464333430343162
6664643035316133650a333331393538616130656136653630303239663561663237373733373638
62383234303061623865633466336636363961623039343236356336356361613736333739623961
6334303865663838623363333339396637363061626363383266
如果不想使用原始密码了呢?也可以使用 rekey 参数手动对文件进行改密操作,同时应结合--ask-vault-pass 参数进行修改,否则 Ansible 服务会因接收不到用户输入的旧密码值而拒绝新的密码变更请求:
bash
[root@linuxprobe~]# ansible-vault rekey --ask-vault-pass locker.yml
Vault password: (输入旧的密码)
New Vault password: (输入新的密码)
Confirm New Vault password: (再输入新的密码)
Rekey successful
第四步: 如果想查看和修改加密文件中的内容,该怎么操作呢?对于已经加密过的文件,需要使用 ansible-vault 命令的 edit 参数进行修改,随后用 view 参数即可查看到修改后的内容。ansible-vault 命令对加密文件的编辑操作默认使用的是 Vim 编辑器,在修改完毕后请记得执行wq 操作保存后退出:
bash
[root@linuxprobe~]# ansible-vault edit locker.yml
---
pw_developer: Imadev
pw_manager: Imamgr
pw_production: Imaprod
最后,再用 view 参数进行查看,便是最新的内容了:
bash
[root@linuxprobe~]# ansible-vault view locker.yml
Vault password: (输入密码后敲击回车确认)
---
pw_developer: Imadev
pw_manager: Imamgr
pw_production: Imaprod