Ansible
Ansible
- Ansible是批量管理服务器的工具。是Michael DeHaan (也是Cobbler的作者)使用Python语言编写的,2015年被红帽公司收购。
- Ansible的软件:
ansible
,没有服务。
Ansible的命令:ansible
- Ansible基于ssh 进行管理(需要免密ssh),所以不需要额外在被控端安装软件(需要开启ssh服务)。
- Ansible是用Python编写的,所以需要安装Python来运行Ansible的核心引擎。
- Ansible的软件:
- Ansible管理远程主机,需要在
工作目录
下进行,管理工作目录
下的配置文件ansible.cfg
中指定的主机清单文件
中记录的主机,主要通过各种模块来实现管理。
环境准备
java
// Ansible的环境准备
/* 准备四台主机pubserver(192.168.88.240)、web1(192.168.88.11)、web2(192.168.88.12)、db1(192.168.88.13)。
* 四台主机配置主机名、IP地址,关闭SELinux和防火墙。
* 主机pubserver拷贝真机/linux-soft/s2/zzg/ansible_soft目录下的rpm包,并配置为第三个YUM仓库。四台主机配置网络YUM仓库。
* 主机pubserver配置名称解析、SSH免密登陆到另外三台主机。
*/
达内的真机(有脚本vm,可用 man vm 查看具体用法。创建的虚拟主机的用户root默认密码a):
[student@server1 ~]$ sudo -i
[root@server1 ~]# vm clone web{1,2} db1 // vm脚本创建虚拟主机
[root@server1 ~]# vm setip web1 192.168.88.1 // vm脚本配置IP地址
[root@server1 ~]# vm setip web2 192.168.88.12
[root@server1 ~]# vm setip db1 192.168.88.13
[root@server1 ~]# scp /linux-soft/s2/zzg/ansible_soft/* 192.168.88.240: /* 将rpm包拷贝到主机pubserver */
控制端主机pubserver(192.168.88.240):
// 关闭SElinux和防火墙
]# vim /etc/selinux/config
SELINUX=disabled
]# setenforce 0
]# systemctl stop firewalld.service
]# systemctl disable firewalld.service
// 先配置本地YUM仓库,安装vsftpd、createrepo后再配置为网络仓库
]# yum install -y vsftpd createrepo
]# vim /etc/vsftpd/vsftpd.conf
12 anonymous_enable=YES // NO访问时会报错503
]# systemctl restart vsftpd.service
]# systemctl enable vsftpd.service
]# mkdir /var/ftp/{dvd,rpms}
]# mv ~/*.rpm /var/ftp/rpms /* 将rpm包移动到ftp共享目录下的rpms目录 */
]# createrepo /var/ftp/rpms // 创建rpms目录的仓库清单
]# cat /etc/yum.repos.d/local.repo // 创建网络YUM仓库配置文件
[app]
name=appstream
baseurl=ftp://192.168.88.240/dvd/AppStream
gpgcheck=0
[base]
name=baseos
baseurl=ftp://192.168.88.240/dvd/BaseOS
gpgcheck=0
[rpms]
name=rpms
baseurl=ftp://192.168.88.240/rpms
gpgcheck=0
]# echo "/dev/sr0 /var/ftp/dvd iso9660 defaults 0 0" >> /etc/fstab // 开机自动挂载
]# mount -a
]# yum repoinfo
// 配置名称解析
]# for i in 1 2
> do
> echo -e "192.168.88.1$i\tweb$i" >> /etc/hosts
> done
]# echo -e "192.168.88.13\tdb1" >> /etc/hosts
// SSH免密登陆
]# ssh-keygen // 三个问题默认回车
]# for i in 1 2 3
> do
> ssh-copy-id 192.168.88.1$i
> done
// 传递YUM仓库配置文件
]# for i in 1 2 3
> do
> scp /etc/yum.repos.d/local.repo 192.168.88.1${i}:/etc/yum.repos.d/local.repo
> done
真机管理虚拟机的命令
virsh list
:真机查看运行中的KVM虚拟机。virsh list --all
:真机查看所有的KVM虚拟机。virt-manager
:打开KVM虚拟系统管理器。virsh console 虚拟机名称
:连接该台虚拟机的控制台。- 使用virsh console管理虚拟机,相比ssh命令,当IP地址变换时不会断开连接,重启虚拟机不会断开连接。
- 但virsh console命令只能用于通过终端控制本地运行的虚拟机。
virsh net-list
:查看虚拟机的所有网络。
java
// virsh list
真机查看虚拟机的名称和状态
[student@server1 ~]$ sudo -i
[root@server1 ~]# virsh list // 查看运行中的虚拟机
Id 名称 状态
-----------------------
4 166-2 运行中
11 88-240 运行中
12 db1 运行中
13 web1 运行中
14 web2 运行中
[root@server1 ~]# virsh list --all // 查看所有虚拟机
Id 名称 状态
-----------------------
4 166-2 运行中
11 88-240 运行中
12 db1 运行中
13 web1 运行中
14 web2 运行中
- proxy 关闭
- win10 关闭
[root@server1 ~]# virsh console db1 // 连接虚拟机db1的控制台
连接到域 db1
Escape character is ^] (Ctrl + ]) // 按回车
Rocky Linux 8.6 (Green Obsidian)
Kernel 4.18.0-372.9.1.el8.x86_64 on an x86_64
db1 login: root
Password:
Last login: Thu Jul 20 17:20:11 from 192.168.88.240
[root@db1 ~]# // 成功登陆虚拟机db1
[root@server1 ~]# virsh net-list // 查看虚拟机的所有网络
名称 状态 自动开始 持久
--------------------------------------
ckavbr 活动 是 是
default 活动 是 是
network166 活动 是 是
private1 活动 是 是
private2 活动 是 是
rhce 活动 是 是
Ansible远程管理
工作目录、配置文件、主机清单文件
- Ansible管理远程主机,需要在
工作目录
下进行,管理工作目录
下的配置文件ansible.cfg
中指定的主机清单文件
中记录的主机,主要通过各种模块来实现管理。 - 配置Ansible管理环境步骤:
- 下载Ansible软件ansible(ansible是工具,下载后不用启动服务)
- 创建Ansible的工作目录(任意取名1)并进入该工作目录(Ansible管理需要在该目录下进行)
- 编写Ansible的配置文件
工作目录/ansible.cfg
:指定主机清单文件 。- 第一行:
[defaults]
第二行:inventory=主机清单文件路径
。相对路径从本配置文件父目录为起始。
第三行:host_key_checking=false
。第一次连接known_hosts没有记录过的主机时是否询问检查主机密钥(默认为是),如果连接没有记录过的主机必须写该项,如果连接有记录的可以不写该项。 - 可参考默认配置文件是
/etc/ansible/ansible.cfg
。 - Ansible配置文件的使用顺序:
ANSIBLE_CONFIG
变量定义的配置文件。- 当前目录下的
./ansible.cfg
文件。(一般使用该文件) - 当前用户家目录下的
~/ansible.cfg
文件。 - 默认配置文件
/etc/ansible/ansible.cfg
。
- 第一行:
- 编写Ansible的主机清单文件(任意取名2):指定被管理的所有主机 。
- 第一行:
[组名]
。组名可任意取名。
第二行:组内的主机名或IP地址
- 第一行:
[组名:children]
。组名可任意取名。
第二行:组内的子组组名
- 可参考
/etc/ansible/hosts
。 - 组名或主机名或IP地址必须写在主机清单文件才能被Ansible管理 。
- 注意:如果这里写主机名,则必须在
/etc/hosts
内增加名称解析IP地址 主机名
,否则ansible命令使用主机名会识别不了主机名的IP地址。 - 注意:如果这里写IP地址,则ansible命令需要使用IP地址,因为必须写在主机清单文件中才能被ansible命令使用。
- 注意:如果这里写主机名,则必须在
- 第一行:
- 查看被Ansible管理的主机(在Ansible工作目录下操作) :
ansible --version
:查看ansible相关信息。ansible all --list-hosts
:查看被管理的所有主机。ansible 组名 --list-hosts
:查看该组的所有主机。ansible-inventory --graph
:以图形化方式查看主机清单文件。
java
// 配置Ansible管理环境
真机:
]# scp /linux-soft/s2/zzg/ansible_soft/ansible-6.3.0-1.el8.noarch.rpm 192.168.88.240: // ansible软件
]# scp /linux-soft/s2/zzg/ansible_soft/ansible-core-2.13.3-1.el8.x86_64.rpm 192.168.88.240: // ansible的依赖
控制主机pubserver(192.168.88.240):
]# yum install -y ansible-core-2.13.3-1.el8.x86_64.rpm
]# yum install -y ansible-6.3.0-1.el8.noarch.rpm // ansible是工具,下载后不用启动服务
]# mkdir ansibletest // 创建Ansible的工作目录,任意取名
]# cd ansibletest/
ansibletest]# touch ansible.cfg inventorytest
ansibletest]# cat ansible.cfg // Ansible配置文件,固定名称ansible.cfg。
[defaults]
inventory=inventorytest // 指定主机清单文件位置,相对路径从本配置文件父目录为起始
host_key_checking=false // 非必需项。是否检查主机密钥
ansibletest]# cat inventorytest // 主机清单文件,任意取名
[webs] // [组名],任意取名
web[1:2] // 组内的主机名或IP地址。[1:2]表示从1到2。
[dbs] // [组名],任意取名
db1 // 组内的主机名或IP地址。
[cluster:children] // [组名:children],任意取名
webs // 组内的子组
dbs // 组内的子组
// 注意,一定在工作目录下执行命令。
ansibletest]# ansible --version
ansible [core 2.13.3]
config file = /root/ansibletest/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.9.7 (default, May 10 2022, 23:45:56) [GCC 8.5.0 20210514 (Red Hat 8.5.0-10)]
jinja version = 3.1.2
libyaml = True
ansibletest]# ansible-inventory --graph // 以图形化方式查看主机清单文件
@all:
|--@cluster:
| |--@dbs:
| | |--db1
| |--@webs:
| | |--web1
| | |--web2
|--@ungrouped:
ansibletest]# ansible all --list-hosts // 查看被管理的所有的主机
hosts (3):
web1
web2
db1
ansibletest]# ansible webs --list-hosts // 查看webs组中所有的主机
hosts (2):
web1
web2
// 报错
// 情况1:
ansibletest]# ansible db1 -a "ls -l /opt"
db1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: Could not resolve hostname db1: Name or service not known",
"unreachable": true
}
// 原因:名称解析文件/etc/hosts中没有"192.168.88.13 db1",系统无法识别db1的IP地址。
// 情况2:
ansibletest]# ansible 192.168.88.13 -a "ls -l /opt"
[WARNING]: Could not match supplied host pattern, ignoring: 192.168.88.13
[WARNING]: No hosts matched, nothing to do
// 原因:主机清单文件中没有记录"192.168.88.13",因此ansible命令无法管理该主机。
// 情况3:
ansibletest]# ansible 192.168.88.13 -a "ls -l /opt"
192.168.88.13 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: root@192.168.88.13: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
"unreachable": true
}
// 原因:没有传递公钥"ssh-copy-id 192.168.88.13",无法免密ssh该主机,ansible命令基于ssh,因此报错。
Ansible远程管理方法
-
Ansible进行远程管理的两个方法(需要在Ansible工作目录下操作,都是通过模块和参数操作):
- ad-hoc临时命令:在命令行上执行管理命令。
- playbook剧本:把管理任务用特定格式写到剧本文件
.yml
中。
ad-hoc临时命令
- 语法规范:
ansible all或组名或主机 -m 模块 -a "参数键值对" [-i 主机清单文件路径]
(module-name,args):远程管理所有主机,或该组所有主机,或该主机。省略主机清单文件路径默认为ansible.cfg
设置的主机清单文件。 - 改变目标主机数据(CHANGED)则返回黄色,没有改变目标主机数据(SUCCESS)则返回绿色,拼写错误返回红色。
ansible-doc说明文档
ansible-doc -l
(list):查看全部模块。ansible-doc -l | wc -l
:查看模块数量(7214)。ansible-doc -l | grep 字符串
:查看包含字符串的模块名称。ansible-doc 模块名称
:查看该模块的说明文档。(/EXAMPLE
查看例子,按空格翻页,按q退出)ansible-doc -s 模块名称
(snippet ):查看该模块的所有参数及用法。
ping模块
ansible all或组名或主机 -m ping
:检查配置的管理环境是否可用,是否可以管理 远程主机。(绿色SUCCESS表示成功)- 注意:ping模块通过ssh协议检查能否远程管理目标主机,而不同于ping命令使用ICMP协议。
command模块
ansible all或组名或主机 -m command -a "命令 [参数]"
可简写为ansible all或组名或主机 -a "命令 [参数]"
- command模块是ansible的默认模块,用于在远程主机上执行任意命令。
- command模块不支持shell特性,如管道、重定向。
- command模块和shell模块需要被控端安装python(许多Linux发行版会默认安装python)。
- 常用参数:
creates
:如果该参数指定的文件存在,就不执行命令。removes
:如果该参数指定的文件不存在,就不执行命令。(只有该参数指定的文件存在,才执行命令)
java
// command模块
ansibletest]# ansible all -a "mkdir /tmp/demo"
web2 | CHANGED | rc=0 >>
db1 | CHANGED | rc=0 >>
web1 | CHANGED | rc=0 >>
ansibletest]# ansible webs -m command -a "mkdir /tmp/demo2"
web2 | CHANGED | rc=0 >>
web1 | CHANGED | rc=0 >>
// 其值路径存在不执行命令。参数removes,其值路径不存在就不执行命令
ansibletest]# ansible web1 -a "mkdir /cr"
ansibletest]# ansible web1 -a "touch /cr/a.txt"
ansibletest]# ansible web1 -a "touch /cr/b.txt creates=/cr/a.txt" // 参数creates,其值路径存在不执行命令
web1 | SUCCESS | rc=0 >>
skipped, since /cr/a.txt existsDid not run command since '/cr/a.txt' exists
ansibletest]# ansible web1 -a "ls /cr"
web1 | CHANGED | rc=0 >>
a.txt
ansibletest]# ansible web1 -a "touch /cr/c.txt removes=/cr/b.txt" // 参数removes,其值路径不存在就不执行命令
web1 | SUCCESS | rc=0 >>
skipped, since /cr/b.txt does not existDid not run command since '/cr/b.txt' does not exist
ansibletest]# ansible web1 -a "ls /cr"
web1 | CHANGED | rc=0 >>
a.txt
shell模块
ansible all或组名或主机 -m shell -a "命令"
- shell模块与command模块类似,但是支持shell特性,如管道、重定向。
- 常用参数:
creates
:如果该参数指定的文件存在,就不执行命令。removes
:如果该参数指定的文件不存在,就不执行命令。(只有该参数指定的文件存在,才执行命令)
java
// shell模块
ansibletest]# ansible all -m command -a "ip a s eth0 | head -3" // 报错,command模块不支持管道
// 可简写为:ansible all -a "ip a s | head"
web1 | FAILED | rc=255 >>
Error: either "dev" is duplicate, or "|" is a garbage.non-zero return code
web2 | FAILED | rc=255 >>
Error: either "dev" is duplicate, or "|" is a garbage.non-zero return code
db1 | FAILED | rc=255 >>
Error: either "dev" is duplicate, or "|" is a garbage.non-zero return code
ansibletest]# ansible webs -m shell -a "ip a s eth0 | head -3"
web1 | CHANGED | rc=0 >>
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:0b:d0:d8 brd ff:ff:ff:ff:ff:ff
inet 192.168.88.11/24 brd 192.168.88.255 scope global noprefixroute eth0
web2 | CHANGED | rc=0 >>
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:55:75:64 brd ff:ff:ff:ff:ff:ff
inet 192.168.88.12/24 brd 192.168.88.255 scope global noprefixroute eth0
script模块
ansible all或组名或主机 -m script -a "脚本路径"
- script模块用于在远程主机上执行脚本:将脚本上传到主机后,执行该脚本,然后删除该脚本。
- 常用参数:
creates
:如果该参数指定的文件存在,就不执行命令。removes
:如果该参数指定的文件不存在,就不执行命令。(只有该参数指定的文件存在,才执行命令)
java
// script模块
ansibletest]# cat test.sh
#!/bin/bash
for user in user{1..5}
do
useradd $user
echo '123' | passwd --stdin $user
done
ansibletest]# ansible webs -m script -a "test.sh"
ansibletest]# ansible webs -a "tail -5 /etc/passwd"
web2 | CHANGED | rc=0 >>
user1:x:1002:1002::/home/user1:/bin/bash
user2:x:1003:1003::/home/user2:/bin/bash
user3:x:1004:1004::/home/user3:/bin/bash
user4:x:1005:1005::/home/user4:/bin/bash
user5:x:1006:1006::/home/user5:/bin/bash
web1 | CHANGED | rc=0 >>
user1:x:1002:1002::/home/user1:/bin/bash
user2:x:1003:1003::/home/user2:/bin/bash
user3:x:1004:1004::/home/user3:/bin/bash
user4:x:1005:1005::/home/user4:/bin/bash
user5:x:1006:1006::/home/user5:/bin/bash
幂等性
- 幂等性:执行任意次所产生的影响均与执行一次的影响相同。例如:不会因为重复创建同一个数据而报错。
- command模块、shell模块、script模块的参数是命令模式,不具备幂等性,因此不建议使用。
- ansible工具其他模块是声明模式,具备幂等性。
file模块
ansible all或组名或主机 -m file -a "参数1=值1 参数n=值n"
- file模块可以创建删除文件、目录、链接等,还可以修改权限、归属关系等。
- 常用的参数:
path
或name
:指定被操作数据的路径。state
:touch
表示创建文件(已存在则改变修改时间),directory
表示创建目录,link
表示创建软链接,hard
表示创建硬链接,absent
表示删除。owner
:设置被操作数据的所有者。该所有者在被控端必须存在否则报错。group
:设置被操作数据的所属组。该所属组在被控端必须存在否则报错。mode
:设置被操作数据的权限。src
(source):指定链接源数据路径。dest
(destination):指定链接目标数据路径。recurse
:是否递推修改属性。force
:是否强制覆盖同名数据。省略默认为no
。
java
// file模块
ansibletest]# ansible-doc file // 查看file密块说明文档
// state=touch 创建文件。文件不存在则创建(返回黄色);文件存在则只改变它的修改时间(返回黄色)
ansibletest]# ansible webs -m file -a "path=/tmp/file.txt state=touch"
// state=directory 创建目录。目录不存在则创建(返回黄色);目录存在则不改变(返回绿色)
ansibletest]# ansible webs -m file -a "path=/tmp/demo state=directory"
// owner=所有者、group=所属组、mode=权限。配置不同则修改(返回黄色),配置相同则不修改(返回绿色)
ansibletest]# ansible webs -m file -a "path=/tmp/file.txt owner=sshd group=adm mode=777"
ansibletest]# ansible webs -a "ls -l /tmp/file.txt"
web1 | CHANGED | rc=0 >>
-rwxrwxrwx 1 sshd adm 0 Jul 17 15:17 /tmp/file.txt
web2 | CHANGED | rc=0 >>
-rwxrwxrwx 1 sshd adm 0 Jul 17 15:17 /tmp/file.txt
// state=absent 删除数据。数据存在则删除(返回黄色);数据不存在则无改变(返回绿色)
ansibletest]# ansible webs -m file -a "path=/tmp/file.txt state=absent"
// state=link 创建软链接。src=源数据路径,dest=目标数据路径。
ansibletest]# ansible webs -m file -a "src=/etc/hosts dest=/tmp/hosts.txt state=link"
ansibletest]# ansible webs -a "ls -l /etc/hosts" // 查看软链接源文件
web2 | CHANGED | rc=0 >>
-rw-r--r--. 1 root root 158 Sep 10 2018 /etc/hosts
web1 | CHANGED | rc=0 >>
-rw-r--r--. 1 root root 158 Sep 10 2018 /etc/hosts
ansibletest]# ansible webs -a "ls -l /tmp/hosts.txt" // 查看软链接
web2 | CHANGED | rc=0 >>
lrwxrwxrwx 1 root root 10 Jul 17 15:39 /tmp/hosts.txt -> /etc/hosts
web1 | CHANGED | rc=0 >>
lrwxrwxrwx 1 root root 10 Jul 17 15:39 /tmp/hosts.txt -> /etc/hosts
copy模块
ansible all或组名或主机 -m copy -a "参数1=值1 参数n=值n"
- copy模块可以用于将数据(文件或目录)从控制端拷贝(上传)到被控端。可以拷贝,也可以在被控端直接凭空创建数据。
- 常用参数:
src
(source):指定控制端的源数据路径。只拷贝源数据,不拷贝路径。dest
(destination):指定被控端的目标数据路径(相对路径是登陆用户的家目录)。(需存在父目录)content
:覆盖重定向文件内容。(结尾\n
换行符)force
:是否强制覆盖同名文件。省略默认为no
。backup
:是否重命名同名文件(带时间戳)以备份。yes
则将被控端的同名文件重命名备份并上传控制端的文件,省略默认为no
。owner
:设置目标数据所有者。group
:设置目标数据所属组。mode
:设置目标数据权限。
- [template模块](# template模块)和copy模块的区别:
- copy模块可以上传文件,但是文件内容固定。
- template模块可以上传包含变量的文件。当远程主机接收到文件之后,文件中的变量将会变成具体的值。
java
// copy模块
// src=控制端源数据路径,dest=被控端目标数据路径
ansibletest]# echo '123456' > a3.txt
ansibletest]# ansible webs -m copy -a "src=a3.txt dest=/root" // 控制端相对路径
ansibletest]# ansible webs -a "cat /root/a3.txt"
web1 | CHANGED | rc=0 >>
123456
web2 | CHANGED | rc=0 >>
123456
ansibletest]# echo '789' > a4.txt
ansibletest]# ansible webs -m copy -a "src=/root/ansibletest/a4.txt dest=/root" // 控制端绝对路径
ansibletest]# ansible webs -a "cat /root/a4.txt"
web1 | CHANGED | rc=0 >>
789
web2 | CHANGED | rc=0 >>
789
ansibletest]# ls -ld /etc/security/
drwxr-xr-x. 7 root root 4096 7月 17 12:53 /etc/security/
ansibletest]# ansible webs -m copy -a "src=/etc/security dest=/opt" // 控制端目录
ansibletest]# ansible webs -a "ls -ld /opt/security"
web1 | CHANGED | rc=0 >>
drwxr-xr-x 7 root root 4096 Jul 17 15:50 /opt/security
web2 | CHANGED | rc=0 >>
drwxr-xr-x 7 root root 4096 Jul 17 15:50 /opt/security
// dest=被控端目标数据路径,content='文件内容'
ansibletest]# ansible webs -m copy -a "dest=/root/a3.txt content='Hello world\n'" // 凭空创建,同名覆盖
ansibletest]# ansible webs -a "cat /root/a3.txt"
web2 | CHANGED | rc=0 >>
Hello world
web1 | CHANGED | rc=0 >>
Hello world
// dest需存在父目录
ansibletest]# ansible webs -a "ls -l /mnt"
web2 | CHANGED | rc=0 >>
total 4
drwxr-xr-x 7 sshd adm 4096 Jul 17 20:34 security
web1 | CHANGED | rc=0 >>
total 4
drwxr-xr-x 7 sshd adm 4096 Jul 17 20:34 security
ansibletest]# ansible webs -m copy -a "src=/etc/passwd dest=/mnt/b/c"
// 报错:"msg": "Destination directory /mnt/b does not exist"
ansibletest]# ansible webs -m copy -a "src=/etc/passwd dest=/mnt/b"
ansibletest]# ansible webs -a "ls -l /mnt"
web2 | CHANGED | rc=0 >>
total 8
-rw-r--r-- 1 root root 2507 Jul 17 21:56 b // 文件b
drwxr-xr-x 7 sshd adm 4096 Jul 17 20:34 security
web1 | CHANGED | rc=0 >>
total 8
-rw-r--r-- 1 root root 2507 Jul 17 21:56 b
drwxr-xr-x 7 sshd adm 4096 Jul 17 20:34 security
// backup=yes 重命名同名文件(带时间戳)以备份
ansibletest]# ansible web1 -a "mkdir /cpbk"
ansibletest]# ansible web1 -m shell -a "echo 'hello' > /cpbk/a.txt"
ansibletest]# echo 'bye' > a.txt
ansibletest]# ansible web1 -m copy -a "src=a.txt dest=/cpbk/a.txt backup=yes"
ansibletest]# ansible web1 -a "ls /cpbk"
web1 | CHANGED | rc=0 >>
a.txt // 上传的新文件
a.txt.4285.2023-08-17@20:24:38~ // 重命名同名文件(带时间戳)以备份
ansibletest]# ansible web1 -a "cat /cpbk/a.txt"
web1 | CHANGED | rc=0 >>
bye
ansibletest]# ansible web1 -a "cat /cpbk/a.txt.4285.2023-08-17@20:24:38~"
web1 | CHANGED | rc=0 >>
hello
fetch模块
ansible all或组名或主机 -m fetch -a "参数1=值1 参数n=值n"
- fetch模块与copy模块相反,copy模块是将数据(文件或目录)上传到被控端,fetch模块是将文件下载到控制端。
- 常用参数:
src
(source):指定被控端的源文件路径(相对路径是登陆用户的家目录)。必须是文件 。默认会下载路径 ,但去掉开头的根目录/
。dest
(destination):指定控制端的目标文件路径。(可以直接创建不存在的多级目录)flat
:是否不下载路径。只对单个主机生效,省略默认为否。
java
// fetch模块
// src=被控端源文件路径,dest=控制端目标文件路径
ansibletest]# ansible webs -m fetch -a "src=/etc/hostname dest=/opt"
ansibletest]# tree /opt
/opt
├── web1
│ └── etc
│ └── hostname // 会下载路径 etc/hostname
└── web2
└── etc
└── hostname
4 directories, 2 files
// 只能操作文件
ansibletest]# ansible webs -m fetch -a "src=/etc/security dest=/opt" // 报错,只能下载文件
web1 | FAILED! => {
"changed": false,
"msg": "remote file is a directory, fetch cannot work on directories" // 不可以下载目录
}
web2 | FAILED! => {
"changed": false,
"msg": "remote file is a directory, fetch cannot work on directories"
}
// dest可以不存在父目录,直接创建不存在的多级目录
ansibletest]# ls /mnt
a
ansibletest]# ansible webs -m fetch -a "src=/etc/passwd dest=/mnt/b/c"
ansibletest]# tree /mnt
/mnt
├── a
└── b
└── c // 可以直接创建不存在的b目录和c目录
├── web1
│ └── etc
│ └── passwd
└── web2
└── etc
└── passwd
7 directories, 2 files
// flat=yes 不下载路径,只下载文件本身。只能用于单台主机
ansibletest]# mkdir /flat
ansibletest]# ansible web1 -m file -a "path=/fetchflat state=directory"
ansibletest]# ansible web1 -m copy -a "dest=/fetchflat/flat.txt content='testflat'"
ansibletest]# ansible web1 -m fetch -a "src=/fetchflat/flat.txt dest=/flat/mome/"
ansibletest]# ansible web1 -m fetch -a "src=/fetchflat/flat.txt dest=/flat/none/ flat=yes"
// 注意dest的目录可以不存在,但必须以`/`结尾,否则为文件重命名
ansibletest]# tree /flat
/flat
├── mome
│ └── web1
│ └── fetchflat
│ └── flat.txt // 默认flat=no 下载路径/fetchflat/flat.txt
└── none
└── flat.txt // flat=yes 不下载路径,只下载文件flat.txt
4 directories, 2 files
lineinfile模块??
ansible all或组名或主机 -m lineinfile -a "参数1=值1 参数n=值n"
- lineinfile模块用于确保存目标文件中有某一行内容。
- 常用参数:
path
:指定目标文件路径。line
:设置写入该文件的一行内容(严格匹配)。如果该行不存在,则默认添加到文件结尾。regexp
:指定被替换的行(正则表达式,包含匹配)。多行满足匹配则从后往前依次替换。- state:默认为present??
java
// lineinfile模块
ansibletest]# ansible webs -a "cat /etc/issue"
web2 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
web1 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
// path=目标文件路径,line='严格匹配某一行内容'
ansibletest]# ansible webs -m lineinfile -a "path=/etc/issue line='Hello World'" // webs组的主机的/etc/issue中严格匹配某一行内容为Hello World。如果该行不存在,则默认添加到文件结尾
ansibletest]# ansible webs -m lineinfile -a "path=/etc/issue line=' Hello World'" // 严格匹配,多个空格也匹配失败
ansibletest]# ansible webs -a "cat /etc/issue"
web2 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
Hello World
Hello World
web1 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
Hello World
Hello World
// regexp=包含匹配被替换的行
ansibletest]# ansible webs -m lineinfile -a "path=/etc/issue line='goodbye' regexp='Hello'" // 将webs组的主机的/etc/issue中包含匹配字符串Hello的行,替换成goodbye
ansibletest]# ansible webs -a "cat /etc/issue"
web1 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
Hello World
goodbye // 多行满足匹配则从后往前依次替换
web2 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
Hello World
goodbye
ansibletest]# ansible webs -m lineinfile -a "path=/etc/issue line='goodbye' regexp='Hello'"
ansibletest]# ansible webs -a "cat /etc/issue"
web2 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
goodbye // 多行满足匹配则从后往前依次替换
goodbye
web1 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
goodbye
goodbye
replace模块
ansible all或组名或主机 -m replace -a "参数1=值1 参数n=值n"
- lineinfile模块替换一行,replace模块可以替换部分。
- 常用参数:
path
:指定目标文件路径。regexp
:指定被替换的内容(正则表达式,严格匹配)。多个满足匹配则全部被替换。replace
:设置替换后的内容。
java
// replace模块
// path=目标文件路径,replace=替换后的内容,regexp=严格匹配被替换的内容
ansibletest]# ansible webs -a "cat /etc/issue"
web2 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
goodbye
goodbye
web1 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
goodbye
goodbye
ansibletest]# ansible webs -m replace -a "path=/etc/issue replace=' work' regexp='bye'" // 将webs组的主机的/etc/issue的所有字符串bye替换为 work
ansibletest]# ansible webs -a "cat /etc/issue"
web2 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
good work // 多个满足匹配则全部被替换
good work // 多个满足匹配则全部被替换
web1 | CHANGED | rc=0 >>
\S
Kernel \r on an \m
good work
good work
user模块
ansible all或组名或主机 -m user -a "参数1=值1 参数n=值n"
-
user模块可以实现用户管理
-
常用参数:
name
:待创建的用户名(已创建则指定用户)。state
:present
表示创建,absent
表示删除,省略默认为present
。remove
:是否删除家目录、邮箱等。配合state=absent
使用,是为yes或true或1,省略默认为否。uid
:设置用户的UID。group
:设置基本组。groups
:设置附加组。home
:设置家目录。password
:重置用户密码。(返回黄色)password={``{'密码' | password_hash('sha512')}}
:{``{}}
是固定格式,表示执行命令。password_hash
是函数,sha512
是加密算法。
java
// user模块
// name=用户名,state=present创建(省略默认为present),uid=UID,group=基本组,groups=附加组1,附加组n
ansibletest]# ansible webs -m user -a "name=zhangsan uid=1010 group=adm groups=daemon,root"
ansibletest]# ansible webs -a "id zhangsan"
web2 | CHANGED | rc=0 >>
uid=1010(zhangsan) gid=4(adm) groups=4(adm),0(root),2(daemon)
web1 | CHANGED | rc=0 >>
uid=1010(zhangsan) gid=4(adm) groups=4(adm),0(root),2(daemon)
// home=家目录,password={{'密码'|password_hash('sha512')}}
ansibletest]# ansible webs -m user -a "name=lisi home=/home/lisitest password={{'123'|password_hash('sha512')}}"
ansibletest]# ansible webs -a "tail -1 /etc/passwd"
web2 | CHANGED | rc=0 >>
lisi:x:1011:1011::/home/lisitest:/bin/bash
web1 | CHANGED | rc=0 >>
lisi:x:1011:1011::/home/lisitest:/bin/bash
// state=absent删除,remove=yes或true连同家目录和邮箱删除(省略默认否)
ansibletest]# ansible webs -m user -a "name=zhangsan state=absent"
ansibletest]# ansible webs -m user -a "name=lisi state=absent remove=yes"
ansibletest]# ansible webs -a "ls -l /home"
web1 | CHANGED | rc=0 >>
total 0
drwx------ 2 user1 user1 83 Jul 17 14:36 user1
drwx------ 2 user2 user2 83 Jul 17 14:36 user2
drwx------ 2 user3 user3 62 Jul 17 14:35 user3
drwx------ 2 user4 user4 62 Jul 17 14:35 user4
drwx------ 2 user5 user5 62 Jul 17 14:35 user5
drwx------ 2 1007 1007 83 Jul 17 17:48 zhangsan // /home/zhangsan保留
web2 | CHANGED | rc=0 >>
total 0
drwx------ 2 user1 user1 62 Jul 17 14:35 user1
drwx------ 2 user2 user2 62 Jul 17 14:35 user2
drwx------ 2 user3 user3 62 Jul 17 14:35 user3
drwx------ 2 user4 user4 62 Jul 17 14:35 user4
drwx------ 2 user5 user5 62 Jul 17 14:35 user5
drwx------ 2 1007 1007 62 Jul 17 17:34 zhangsan
group模块
ansible all或组名或主机 -m group -a "参数1=值1 参数n=值n"
- group模块用于创建、删除组
- 常用参数:
name
:待创建的组名(已创建则指定组)。gid
:设置组的GID。state
:present
表示创建,absent
表示删除,省略默认present
。
java
// group模块
// name=组名,gid=GID,state=present创建(省略默认为absent)
ansibletest]# ansible webs -m group -a "name=test gid=1020"
ansibletest]# ansible webs -a "tail -2 /etc/group"
web1 | CHANGED | rc=0 >>
user5:x:1006:
test:x:1020:
web2 | CHANGED | rc=0 >>
user5:x:1006:
test:x:1020:
// state=absent删除
ansibletest]# ansible webs -m group -a "name=test state=absent"
ansibletest]# ansible webs -a "tail -1 /etc/group"
web2 | CHANGED | rc=0 >>
user5:x:1006:
web1 | CHANGED | rc=0 >>
user5:x:1006:
yum_repository模块
ansible all或组名或主机 -m yum_repository -a "参数1=值1 参数n=值n"
- yum_repository模块用于编写YUM仓库配置文件。
- 常用参数:
file
:设置配置文件名(不含扩展名.repo
),在目标主机的/etc/yum.repos.d
下创建该文件。name
:设置仓库唯一标识ID。description
:设置仓库name,创建仓库时不可省略。baseurl
:指定仓库位置(本地或网络)。enabled
:是否启用本仓库,省略默认为是。(1或true或yes,0或false或no)gpgcheck
:是否检查红帽签名。(1或true或yes,0或false或no)gpgkey
:红帽签名信息/etc/pki/rpm-gpp/RPM-GPG-KEY-rockyofficial
state
:present
表示创建该仓库,absent
表示删除该仓库,省略默认为present
。- 一次创建一个仓库:file(配置文件名)不存在则新建文件,file相同且name(仓库ID)相同则重置该仓库,file相同但name不同则追加仓库。
- 一次删一个仓库,删除最后的仓库会删除文件。
java
// yum_repository模块
// 查找包含yum字符串的ansible模块
ansibletest]# ansible-doc -l | grep yum
[WARNING]: dellemc.openmanage.ome_active_directory has a documentation
formatting error
community.general.packaging.os.yum_versionlock Locks / unlocks a installed package(s) from being updated by yum p...
yum Manages packages with the `yum' p...
yum_repository Add or remove YU...
// state=present创建仓库,省略默认为present。file=配置文件名,name=仓库ID,description=仓库name
// 编写YUM仓库配置文件。file不存在则新建文件,file(文件名)相同且name(仓库ID)相同则重置该仓库,file相同但name不同则追加仓库。
ansibletest]# ansible db1 -m yum_repository -a "file=myrepo1 name=ida \ // file不存在
> description=test gpgcheck=0 enabled=1
> baseurl=ftp://192.168.88.240/dvd/AppStream"
/* 在主机db1的/etc/yum.repos.d下创建一个"myrepo1.repo"文件,仓库ida
[root@db1 ~]# ls /etc/yum.repos.d/
local.repo myrepo1.repo
[root@db1 ~]# cat /etc/yum.repos.d/myrepo1.repo
[ida]
async = 1
baseurl = ftp://192.168.88.240/dvd/AppStream
enabled = 1
gpgcheck = 0
name = test
*/
ansibletest]# ansible db1 -m yum_repository -a "file=myrepo1 name=ida \ // file相同且name相同
> description=test gpgcheck=0 enabled=1
> baseurl=ftp://192.168.88.240/dvd/BaesOS"
/* 在主机db1的/etc/yum.repos.d下的"myrepo1.repo"文件,重置仓库ida
[root@db1 ~]# ls /etc/yum.repos.d/
local.repo myrepo1.repo
[root@db1 ~]# cat /etc/yum.repos.d/myrepo1.repo
[ida]
async = 1
baseurl = ftp://192.168.88.240/dvd/BaesOS
enabled = 1
gpgcheck = 0
name = test
*/
ansibletest]# ansible db1 -m yum_repository -a "file=myrepo1 name=idb \ // file相同但name不同
> description=test gpgcheck=0
> baseurl=ftp://192.168.88.240/dvd/AppStream"
/* 在主机db1的/etc/yum.repos.d下的"myrepo1.repo"文件,追加仓库idb
[root@db1 ~]# ls /etc/yum.repos.d/
local.repo myrepo1.repo
[root@db1 ~]# cat /etc/yum.repos.d/myrepo1.repo
[ida]
async = 1
baseurl = ftp://192.168.88.240/dvd/BaesOS
enabled = 1
gpgcheck = 0
name = test
[idb]
async = 1
baseurl = ftp://192.168.88.240/dvd/AppStream
gpgcheck = 0
name = test
*/
ansibletest]# ansible db1 -m yum_repository -a "file=myrepo2 name=ida \ // file不存在
> description=test gpgcheck=0
> baseurl=ftp://192.168.88.240/dvd/AppStream"
/* 在主机db1的/etc/yum.repos.d下创建一个"myrepo2.repo"文件,仓库ida
[root@db1 ~]# ls /etc/yum.repos.d/
local.repo myrepo1.repo myrepo2.repo
*/
// state=absent删除仓库,一次删除一个仓库,删除最后的仓库会删除文件
ansibletest]# ansible db1 -m yum_repository -a "file=myrepo1 name=idb state=absent"
/* 在主机db1的/etc/yum.repos.d下的"myrepo1.repo"文件,删除仓库idb
[root@db1 ~]# cat /etc/yum.repos.d/myrepo1.repo
[ida]
async = 1
baseurl = ftp://192.168.88.240/dvd/BaesOS
enabled = 1
gpgcheck = 0
name = test
*/
ansibletest]# ansible db1 -m yum_repository -a "file=myrepo1 name=ida state=absent"
/* 在主机db1的/etc/yum.repos.d下的"myrepo1.repo"文件,删除最后的仓库ida,也会删除"myrepo1.repo"文件
[root@db1 ~]# cat /etc/yum.repos.d/myrepo1.repo
cat: /etc/yum.repos.d/myrepo1.repo: No such file or directory
[root@db1 ~]# ls /etc/yum.repos.d/
local.repo myrepo2.repo
*/
// 也可以通过file模块直接删除文件
ansibletest]# ansible db1 -m file -a "path=/etc/yum.repos.d/myrepo2.repo state=absent"
/*[root@db1 ~]# ls /etc/yum.repos.d/
local.repo
*/
yum模块
ansible all或组名或主机 -m yum -a "参数1=值1 参数n=值n"
- yum模块用于rpm软件包管理,如安装、升级、卸载。
- 常用参数:
name
:指定rpm包的软件名
(支持通配符)或@rpm包组名
,多个软件名用,
隔开。state
:present
表示安装,latest
表示安装或升级到最新版本,absent
表示卸载,省略默认为present
。
java
// yum模块
// name=软件名。state=present安装,省略默认为present,已安装返回绿色,未安装则安装并返回黄色
ansibletest]# ansible db1 -m yum -a "name=net-tools"
db1 | SUCCESS => { // 已安装返回绿色
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"msg": "Nothing to do",
"rc": 0,
"results": []
}
ansibletest]# ansible db1 -m yum -a "name=wget,net-tools "
db1 | CHANGED => { // 未安装则安装并返回黄色
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": true,
"msg": "",
"rc": 0,
"results": [
"Installed: wget-1.19.5-10.el8.x86_64",
"Installed: libmetalink-0.1.3-7.el8.x86_64"
]
}
// state=latest,如果仓库内有多个版本,安装最新版本
ansibletest]# ansible db1 -m yum -a "name=nginx state=latest"
// state=absent卸载
ansibletest]# ansible db1 -m yum -a "name=wget state=latest"
service模块
ansible all或组名或主机 -m service -a "参数1=值1 参数n=值n"
- service模块用于systemctl命令控制服务。启动、关闭、重启、开机自启。
- 常用参数:
name
:指定rpm包的服务名,一次一个。state
:started
表示启动,stopped
表示关闭,restarted
表示重启,reloaded
表示重载配置文件。不能省略。enabled
:1或yes或ture表示设置开机自启,0或no或false表示设置开机不要自启。
java
// service模块
// name=服务名。state=started启动,enabled=1或yes或true开机自启
ansibletest]# ansible db1 -m service -a "name=nginx state=started enabled=1" // 启动并开机自启
ansibletest]# ansible db1 -a "systemctl status nginx"
db1 | CHANGED | rc=0 >>
● nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2023-07-18 10:18:30 CST; 39s ago
......省略一万字
// enabled=0或no或false开机不自启
ansibletest]# ansible db1 -m service -a "name=nginx enabled=0" // 开启不自启
ansibletest]# ansible db1 -a "systemctl status nginx"
db1 | CHANGED | rc=0 >>
● nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; vendor preset: disabled)
Active: active (running) since Tue 2023-07-18 10:18:30 CST; 1min 42s ago
......省略一万字
lvg模块
ansible all或组名或主机 -m lvg -a "参数1=值1 参数n=值n"
- lvg模块(Logical Volume Group)用于创建、删除卷组,修改卷组大小。
- 常用参数:
vg
:设置卷组名(已存在则重置该卷组的大小,即物理卷组成)。pvs
:指定组成卷组的逻辑卷,多个逻辑卷用,
隔开。(直接写硬盘分区会自动创建物理卷,但删除卷组后需要手动删除物理卷)state
:present
表示创建,absent
表示删除,省略默认为present
。如果卷组还有逻辑卷则无法删除。
java
// lvg模块
/* 关闭虚拟机db1,为其添加2块20GB的硬盘,给新添的第一块硬盘分区为5G和15G
* LINUX下KVM虚拟机新加的硬盘,名称是/dev/vdb和/dev/vdc
* Windows下vmware虚拟机新加的硬盘,名称是/dev/sdb和/dev/sdc
* 如果选nvme硬盘,名称可能是/dev/nvme0n1和/dev/nvme0n2
*/
ansibletest]# ansible dbs -a "lsblk"
db1 | CHANGED | rc=0 >>
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vda 253:0 0 20G 0 disk
└─vda1 253:1 0 20G 0 part /
vdb 253:16 0 20G 0 disk
├─vdb1 253:17 0 5G 0 part // 分区1为5G
└─vdb2 253:18 0 15G 0 part // 分区2为15G
vdc 253:32 0 20G 0 disk
// vg=卷组名,pvs=物理卷1,物理卷n(直接写硬盘分区会自动创建物理卷)
ansibletest]# ansible db1 -m lvg -a "vg=myvg pvs=/dev/vdb1" // 创建卷组myvg
ansibletest]# ansible db1 -a "vgs"
db1 | CHANGED | rc=0 >>
VG #PV #LV #SN Attr VSize VFree
myvg 1 0 0 wz--n- <5.00g <5.00g
// 卷组vg同名会重置原卷组
ansibletest]# ansible db1 -m lvg -a "vg=myvg pvs=/dev/vdb2" // 重置卷组myvg
ansibletest]# ansible db1 -a "vgs"
db1 | CHANGED | rc=0 >>
VG #PV #LV #SN Attr VSize VFree
myvg 1 0 0 wz--n- <15.00g <15.00g // 重置卷组myvg为15G(/dev/vdb2)
// 扩展卷组
ansibletest]# ansible db1 -m lvg -a "vg=myvg pvs=/dev/vdb1,/dev/vdb2" // 重置卷组myvg
ansibletest]# ansible db1 -a "vgs"
db1 | CHANGED | rc=0 >>
VG #PV #LV #SN Attr VSize VFree
myvg 2 0 0 wz--n- 19.99g 19.99g // 重置卷组myvg为20G(/dev/vdb1,/dev/vdb2)
// 删除卷组
ansibletest]# ansible db1 -m lvg -a "vg=myvg state=absent" // 删除卷组myvg
ansibletest]# ansible db1 -a "vgs"
db1 | CHANGED | rc=0 >> // 已删除卷组
ansibletest]# ansible db1 -a "pvs"
db1 | CHANGED | rc=0 >>
PV VG Fmt Attr PSize PFree // 自动生成的物理卷也要手动删除
/dev/vdb1 lvm2 --- 5.00g 5.00g
/dev/vdb2 lvm2 --- <15.00g <15.00g
ansibletest]# ansible db1 -a "pvremove /dev/vdb1 /dev/vdb2" // 手动删除逻辑卷,释放硬盘
ansibletest]# ansible db1 -a "pvs"
db1 | CHANGED | rc=0 >> // 已删除逻辑卷
lvol模块
ansible all或组名或主机 -m lvol -a "参数1=值1 参数n=值n"
- lvol模块(Logical Volume)用于创建、删除逻辑卷,修改逻辑卷大小。
- 常用参数:
lv
:设置逻辑卷名。vg
:指定用于创建逻辑卷的卷组。size
:设置逻辑卷的大小,单位为M、G等。省略单位默认为M。force
:是否强制执行。(1或true或yes,0或false或no)state
:present
表示创建,absent
表示删除,省略默认为present
。如果逻辑卷被挂载使用无法删除。resizefs
:是否更新文件系统(已经格式化的逻辑卷扩展时给扩展的部分创建文件系统)。
java
// lvol模块
ansibletest]# ansible db1 -m lvg -a "vg=myvg pvs=/dev/vdb1,/dev/vdb2"
ansibletest]# ansible db1 -a "vgs"
db1 | CHANGED | rc=0 >>
VG #PV #LV #SN Attr VSize VFree
myvg 2 0 0 wz--n- 19.99g 19.99g
// vg=卷组名,lv=逻辑卷名,size=逻辑卷大小。state=present创建逻辑卷,省略默认为present。
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg size=2G" // 创建逻辑卷mylv
ansibletest]# ansible db1 -a "lvs"
db1 | CHANGED | rc=0 >>
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
mylv myvg -wi-a----- 2.00g
// size=逻辑卷大小,如果重置后的逻辑卷更大则为扩展
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg size=4G" // 扩展逻辑卷mylv
ansibletest]# ansible db1 -a "lvs"
db1 | CHANGED | rc=0 >>
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
mylv myvg -wi-a----- 4.00g
// size=逻辑卷大小,如果重置后的逻辑卷更小则为缩减。默认不能缩减逻辑卷,使用force=yes强制执行
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg size=1G" // 缩减逻辑卷mylv报错
db1 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"msg": "Sorry, no shrinking of mylv without force=yes."
}
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg size=1G force=yes" // 强制缩减逻辑卷mylv
ansibletest]# ansible db1 -a "lvs"
db1 | CHANGED | rc=0 >>
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
mylv myvg -wi-a----- 1.00g // 逻辑卷mylv大小1g
ansibletest]# ansible db1 -a "vgs"
db1 | CHANGED | rc=0 >>
VG #PV #LV #SN Attr VSize VFree
myvg 2 1 0 wz--n- 19.99g 18.99g // 卷组myvg大小19.99g,可用大小18.99g
// state=absent删除逻辑卷。默认不能缩减逻辑卷,使用force=yes强制执行
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg state=absent" // 删除逻辑卷mylv报错
db1 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"msg": "Sorry, no removal of logical volume mylv without force=yes."
}
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg state=absent force=yes" // 强制删除逻辑卷mylv
ansibletest]# ansible db1 -a "lvs"
db1 | CHANGED | rc=0 >> // 已删除逻辑卷mylv
ansibletest]# ansible db1 -a "vgs"
db1 | CHANGED | rc=0 >>
VG #PV #LV #SN Attr VSize VFree
myvg 2 1 0 wz--n- 19.99g 19.99g // 卷组myvg大小19.99g,可用大小19.99g
filesystem模块
ansible all或组名或主机 -m filesystem -a "参数1=值1 参数n=值n"
- filesystem模块用于格式化,也就是创建文件系统。可以作用于硬盘分区,也可以作用于逻辑卷。
- 常用参数:
dev
:指定要格式化的设备路径,可以是硬盘分区,也可以是逻辑卷。fstype
:指定文件系统类型(xfs、ext4)。state
:present
表示创建文件系统,absent
表示删除文件系统,省略默认为present
。
java
// filesystem模块
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg size=6G"
ansibletest]# ansible db1 -a "lvs"
db1 | CHANGED | rc=0 >>
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
mylv myvg -wi-a----- 6.00g
// dev=格式化的设备路径(分区或逻辑卷),fstype=文件系统类型。state=present创建文件系统,省略默认为present
ansibletest]# ansible db1 -m filesystem -a "dev=/dev/myvg/mylv fstype=xfs"
/*
[root@db1 ~]# blkid /dev/myvg/mylv
/dev/myvg/mylv: UUID="fefa93d0-5d2e-4409-acd6-2d7d76d31860" BLOCK_SIZE="512" TYPE="xfs"
*/
// state=absent删除文件系统
ansibletest]# ansible db1 -m filesystem -a "dev=/dev/myvg/mylv fstype=xfs"
/*
[root@db1 ~]# blkid /dev/myvg/mylv // 没有返回内容
*/
mount模块
ansible all或组名或主机 -m mount -a "参数1=值1 参数n=值n"
- mount模块用于挂载已创建文件系统 的设备。可以挂载硬盘分区,也可以挂载逻辑卷。设备未创建文件系统的部分不会被df命令识别。
- 常用参数:
src
:指定要挂载的设备路径,可以是硬盘分区,也可以是逻辑卷。(需要先格式化)path
:指定挂载点路径。fstype
:指定文件系统类型(xfs、ext4)。state
:不能省略 。mounted
表示永久挂载(增加开机自动挂载信息并立即挂载),如果挂载点不存在会自动创建。unmounted
表示卸载(不会删除开机自动挂载信息),不会删除挂载点present
表示增加开机自动挂载信息(不立即挂载)。absent
表示永久卸载 (立即卸载并删除开机自动挂载信息和挂载点)。
java
// mount模块
ansibletest]# ansible db1 -m filesystem -a "dev=/dev/myvg/mylv fstype=xfs"
/*
[root@db1 ~]# blkid /dev/myvg/mylv
/dev/myvg/mylv: UUID="fefa93d0-5d2e-4409-acd6-2d7d76d31860" BLOCK_SIZE="512" TYPE="xfs"
[root@db1 ~]# lsblk /dev/myvg/mylv
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
myvg-mylv 252:0 0 6G 0 lvm
*/
// src=要挂载的设备路径(分区或逻辑卷),path=挂载点路径,fstype=文件系统类型。state=mounted永久挂载
ansibletest]# ansible db1 -m mount -a "src=/dev/myvg/mylv path=/data fstype=xfs state=mounted"
/* 在/etc/fstab中增加设备/dev/myvg/mylv的自动挂载
[root@db1 ~]# df -Th /data
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/myvg-mylv xfs 6.0G 76M 6.0G 2% /data
*/
// state=absent永久卸载并删除挂载点
ansibletest]# ansible db1 -m mount -a "src=/dev/myvg/mylv path=/data fstype=xfs state=absent"
/* /etc/fstab中设备/dev/myvg/mylv的自动挂载被删除
[root@db1 ~]# ls /data
ls: cannot access '/data': No such file or directory
*/
ansibletest]# ansible db1 -a "df -Th /dev/myvg/mylv"
db1 | CHANGED | rc=0 >>
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/myvg-mylv xfs 6.0G 77M 6.0G 2% /data
逻辑卷的扩展
- 格式化后的逻辑卷如果扩展,需要使用lvol模块的参数
resizefs
来更新文件系统。如果是扩展未格式化的逻辑卷,扩展后直接格式化。
java
// 格式化后的逻辑卷如果扩展,需要使用参数resizefs=yes来更新文件系统
ansibletest]# ansible db1 -m mount -a "src=/dev/myvg/mylv path=/data fstype=xfs state=mounted"
/*
[root@db1 ~]# lsblk /dev/myvg/mylv
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
myvg-mylv 252:0 0 6G 0 lvm
[root@db1 ~]# df -Th /data
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/myvg-mylv xfs 6.0G 76M 6.0G 2% /data
*/
// 如果没有更新文件系统:设备未创建文件系统的部分不会被df命令识别
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg size=7G"
/*
[root@db1 ~]# lsblk /dev/myvg/mylv // 逻辑卷已扩展至7G
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
myvg-mylv 252:0 0 7G 0 lvm
[root@db1 ~]# df -Th /data // 扩展部分未创建文件系统
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/myvg-mylv xfs 6.0G 76M 6.0G 2% /data
*/
// resizefs=true更新文件系统
ansibletest]# ansible db1 -m lvol -a "lv=mylv vg=myvg size=8G resizefs=true" // 如果size=7G,ansible不会changed
/*
[root@db1 ~]# lsblk /dev/myvg/mylv // 逻辑卷已扩展至8G
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
myvg-mylv 252:0 0 8G 0 lvm /data
[root@db1 ~]# df -Th /data // 扩展部分已创建文件系统
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/myvg-mylv xfs 8.0G 90M 8.0G 2% /data
*/
// 快捷操作:
// 按Ctrl+R:
(reverse-i-search)`':
// 按7:
(reverse-i-search)`7': ansible db1 -m lvol -a "vg=myvg lv=mylv size=7G"
// 按Esc,修改为8G,并更新文件系统
ansibletest]# ansible db1 -m lvol -a "vg=myvg lv=mylv size=8G resizefs=true"
parted模块
-
ansible all或组名或主机 -m parted -a "参数1=值1 参数n=值n"
-
parted模块使用parted命令进行硬盘分区。
-
常用参数:
-
device
:指定用于分区的硬盘路径。 -
label
:指定分区模式(分区规划、分区表)。msdos
或gpt
,省略默认为msdos
。 -
state
:present
表示创建分区,absent
表示删除分区,info
表示查看硬盘分区详情。省略默认为info
。 -
number
:分区编号。只能写一个数字。 -
part_type
:指定新分区的类型。primary
表示主分区,extended
表示扩展分区,logical
表示逻辑分区,省略默认为primary
。 -
part_start
:指定新分区的起始位置。创建第一个分区时要省略不能为0。单位可以是MB、MiB、GB、GiB等,也可以写为百分比。 -
part_end
:指定新分区的结束位置。创建第一个分区时省略则为整个硬盘。上一个分区的结束位置是下一个分区的起始位置。单位可以是MB、MiB、GB、GiB等,也可以写为百分比。- 电脑存储最小单位:位(比特bit)。单位以iB显示。
- 1Byte=8bit
1KB=1000B(1KiB=1024B)
1MB=1000KM(1MiB=1024KB)
- 1Byte=8bit
- 电脑存储最小单位:位(比特bit)。单位以iB显示。
-
如果编写剧本,可以使用参数
part
来同时创建或删除多个分区 :每个分区指定参数number
、part_type
、part_start
、part_end
。
-
java
// parted模块
/* device=硬盘路径。state=present创建分区,省略默认为info。
* label=msdos或gpt,省略默认为msdos。
* number=分区编号
* part_type=primary或extended或logical,省略默认为primary。
* part_start=新分区起始位置,第一个分区要省略。
* part_end=新分区结束位置,第一个分区省略则为整个硬盘。
*/
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=present number=1 part_end=1GiB"
// 起始位置和结束位置,单位GB和GiB大小不同
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc number=2 part_start=1GiB part_end=5GiB state=present"
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc number=3 part_start=5GiB part_end=9GB state=present"
/*
[root@db1 ~]# lsblk /dev/vdc
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vdc 253:32 0 20G 0 disk
├─vdc1 253:33 0 1023M 0 part // 由于硬盘开头会保留一部分用于存储硬盘信息,所以不足1GiB
├─vdc2 253:34 0 4G 0 part // 4GiB
└─vdc3 253:35 0 3.4G 0 part // 9GB-5GiB=8.38GiB-5GiB=3.34GiB
*/
// part_type=extended扩展分区
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=present number=4 part_type=extended part_start=9GB part_end=100%"
// part_type=logical逻辑分区
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=present number=5 part_type=logical part_start=9GB part_end=75%"
// 起始位置和结束位置,可以写为百分比
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=present number=6 part_type=logical part_start=75% part_end=100%"
/*
[root@db1 ~]# lsblk /dev/vdc
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vdc 253:32 0 20G 0 disk // 20GiB
├─vdc1 253:33 0 1023M 0 part
├─vdc2 253:34 0 4G 0 part
├─vdc3 253:35 0 3.4G 0 part
├─vdc4 253:36 0 1K 0 part // 扩展分区,SIZE不准
├─vdc5 253:37 0 6.6G 0 part // 逻辑分区
└─vdc6 253:38 0 5G 0 part // 逻辑分区
*/
// state=absent删除分区
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=absent number=6"
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=absent number=5"
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=absent number=4"
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=absent number=3"
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=absent number=2"
ansibletest]# ansible db1 -m parted -a "device=/dev/vdc state=absent number=1"
/*
[root@db1 ~]# lsblk /dev/vdc
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vdc 253:32 0 20G 0 disk
*/
firewalld模块
ansible all或组名或主机 -m firewalld -a "参数1=变量1 参数n=变量n"
- firewalld模块用于配置防火墙。
- 常用参数:
port
:指定端口/udp
或端口号/tcp
。服务名和端口号对应关系的说明文件:/etc/services
source
:指定源IP地址/子网掩码
zone
permanent
:是否永久生效,但不会立即生效(重启服务生效)immediate
:是否立即生效,临时生效(重启服务失效)state
:present
表示,absent
表示,enabled
表示放行访问,disabled
表示拒绝访问,不能省略。
- 配置服务器的防火墙,只需要配置允许开放哪些服务或端口。没有明确允许的,都是默认拒绝。
java
// firewalld模块
// db1主机安装并开启防火墙
db1主机(192.168.88.13):
[root@db1 ~]# yum install -y nginx
[root@db1 ~]# echo "nginx-db1~~" > /usr/share/nginx/html/index.html
/*
[root@pubserver ansibletest]# curl 192.168.88.13
nginx-db1~~
*/
[root@db1 ~]# yum install -y firewalld
[root@db1 ~]# systemctl start firewalld
[root@db1 ~]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports:
protocols:
forward: no
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
/* 防火墙拒绝了其他设备对nginx服务(端口80)的访问
[root@pubserver ansibletest]# curl 192.168.88.13
curl: (7) Failed to connect to 192.168.88.13 port 80: 没有到主机的路由
*/
// Ansible管理db1主机的防火墙
控制端(192.168.88.240):
// port=端口号/传输层协议。immediate=是否立即生效(重启服务失效)。state=enabled放行访问
ansibletest]# ansible db1 -m firewalld -a "port=80/tcp immediate=yes state=enabled"
ansibletest]# curl 192.168.88.13
nginx-db1~~
/*
[root@db1 ~]# firewall-cmd --list-ports
80/tcp
[root@db1 ~]# systemctl restart firewalld.service // 重启服务放行端口80失效
[root@db1 ~]# firewall-cmd --list-ports
*/
ansibletest]# curl 192.168.88.13
curl: (7) Failed to connect to 192.168.88.13 port 80: 没有到主机的路由
// immediate=是否立即生效(重启服务失效)。state=disabled拒绝访问
ansibletest]# ansible db1 -m firewalld -a "port=80/tcp immediate=yes state=enabled"
ansibletest]# curl 192.168.88.13
nginx-db1~~
ansibletest]# ansible db1 -m firewalld -a "port=80/tcp immediate=yes state=disabled"
ansibletest]# curl 192.168.88.13
curl: (7) Failed to connect to 192.168.88.13 port 80: 没有到主机的路由
// permanent=是否永久生效(不会立即生效,重启服务生效)。state=enabled放行访问
ansibletest]# ansible db1 -m firewalld -a "port=80/tcp permanent=yes state=enabled"
ansibletest]# curl 192.168.88.13
curl: (7) Failed to connect to 192.168.88.13 port 80: 没有到主机的路由
/*
[root@db1 ~]# firewall-cmd --list-ports
[root@db1 ~]# systemctl restart firewalld.service // 重启服务放行端口80生效
[root@db1 ~]# firewall-cmd --list-ports
80/tcp
*/
ansibletest]# curl 192.168.88.13
nginx-db1~~
// permanent=是否永久生效(不会立即生效,重启服务生效)。state=disabled拒绝访问
ansibletest]# ansible db1 -m firewalld -a "port=80/tcp permanent=yes state=disabled"
ansibletest]# curl 192.168.88.13
nginx-db1~~
/*
[root@db1 ~]# firewall-cmd --list-ports
80/tcp
[root@db1 ~]# systemctl restart firewalld.service // 重启服务放行端口80失效
[root@db1 ~]# firewall-cmd --list-ports
*/
ansibletest]# curl 192.168.88.13
curl: (7) Failed to connect to 192.168.88.13 port 80: 没有到主机的路由
// 一般permanet和immediate同时使用,即使重启服务也不影响
ansibletest]# ansible db1 -m firewalld -a "port=80/tcp permanent=yes immediate=yes state=enabled"
ansibletest]# curl 192.168.88.13
nginx-db1~~
ansibletest]# ansible db1 -m service -a "name=firewalld state=restarted"
ansibletest]# curl 192.168.88.13
nginx-db1~~
playbook剧本
剧本的编写和运行
- 剧本(playbook)是一个文件,该文件中通过yaml语法格式来书写。
-
yaml语法规范(YAML Ain't a Markup Language):
- 使用yaml语法书写的文件,扩展名一般为
.yml
或.yaml
。 - 文件内容一般以
---
作为第一行,非必需但常用。用#
注释。 - 键值对使用冒号
:
表示,冒号后面必须有空格。例如:key: value
。 - 数组使用减号
-
表示,减号后面必须有空格。例如:多个play、多个任务、多个值等。(多个参数不用-
) - 相同的层级必须有相同的缩进。如果缩进不对,则有语法错误。建议每一级缩进2个空格。
- 全文不能使用制表符,必须使用空格。
- 包含数字 的字符串必须使用引号。值以
{``{}}
开头必须使用引号。
- 使用yaml语法书写的文件,扩展名一般为
-
编写剧本:使用yaml语法书写,包含要素
hosts
、tasks
(包含要素模块
、参数
)等。- 一个剧本可以包含多个play(根据所作用的主机
hosts
来划分play)。play名省略则默认同hosts
的值。- 每个play的任务可以有多个(写在
tasks
中,每个任务使用一个模块)。- 每个任务包含
模块名
和模块的参数
。任务名省略则默认同模块名
。 - 每个play都有默认任务
Gathering Facts
。可以使用gather_facts: no
来跳过该任务。
- 每个任务包含
- 每个play的任务可以有多个(写在
- 每个play前使用
-空格
,每个任务前使用-空格
。
- 一个剧本可以包含多个play(根据所作用的主机
-
运行剧本:
ansible-playbook 剧本文件名
java
// 剧本的编写和运行(组webs包含主机web1、web2,组dbs包含主机db1)
// 修改家目录/.vimrc,以方便书写
ansibletest]# cat ~/.vimrc
set ai // autoindent,设置换行自动对齐上一行缩进
set ts=2 // tabstop,设置一个制表符缩进2个空格
set et // expandtab,将制表符转换成相应个数的空格,这样YAML可以使用Tab键。
// 编写剧本
ansibletest]# cat test01.yml
---
- name: create dir and copy file // 剧本下的"-",第一个play。值为play名,省略同主机名
hosts: webs,dbs // 值为组名或主机名,必须是出现在主机清单文件中的。该play对webs组和dbs组生效
# gather_facts: no
tasks:
- name: create dir // tasks下的"-",第一个任务。值为任务名,省略同模块名
file: // 键为模块名。使用file模块
path: /tmp/demo // 键为参数名
state: directory
mode: '0755'
- name: copy file // tasks下的"-",第二个任务
copy:
src: /etc/hosts
dest: /tmp/demo/hosts
// 运行剧本
ansibletest]# ansible-playbook test01.yml
PLAY [create dir and copy file] *********************************** // 第一个play
TASK [Gathering Facts] ******************************************** // 每个play都有默认任务Gathering Facts
ok: [web1]
ok: [web2]
ok: [db1]
TASK [create dir] ************************************************** // 第一个自定义任务
ok: [web1]
ok: [db1]
ok: [web2]
TASK [copy file] *************************************************** // 第二个自定义任务
changed: [db1]
changed: [web1]
changed: [web2]
PLAY RECAP ********************************************************* // 执行结果
db1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
// 编写剧本
ansibletest]# cat test02.yml
---
- name: useradd bob // 第一个play
hosts: webs
tasks:
- name: useradd // 第一个play的第一个任务
user:
name: bob
groups: adm
password: "{{'123' | password_hash('sha512')}}" // 注意:{{}}写在值的开头时需要用引号
- hosts: dbs // 第二个play
tasks:
- copy: // 第二个play的第一个任务
dest: /tmp/hi.txt
content: Hello World\n
// 运行剧本
ansibletest]# ansible-playbook test02.yml
PLAY [useradd bob] **************************************************** // 第一个play
TASK [Gathering Facts] ************************************************ // 默认任务Gathering Facts
ok: [web1]
ok: [web2]
TASK [useradd] ******************************************************** // 第一个自定义任务
ok: [web1]
ok: [web2]
PLAY [dbs] ************************************************************ // 第二个play
TASK [Gathering Facts] ************************************************ // 默认任务Gathering Facts
ok: [db1]
TASK [copy] *********************************************************** // 第一个自定任务
changed: [db1]
PLAY RECAP ************************************************************ // 执行结果
db1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
测试剧本
- 有两种方式可以测试一个playbook剧本而不真正对目标主机进行实际操作:
- Dry Run模式:使用
--check
或-C
参数来启用Dry Run模式,模拟执行playbook。 - Debug模式:使用
-vvv
参数来启用详细的debug模式,显示出Ansible执行过程中的详细信息,包括每个任务的计划和结果。
- Dry Run模式:使用
java
// 测试剧本
// Dry Run
ansible-playbook --check playbook.yml
ansible-playbook -C playbook.yml
// Debug
ansible-playbook -vvv playbook.yml
折行语法 |
、>
|
:以换行符折行。每行内容单独成行。>
:以空格折行。将多行内容合并为一行。
java
// |和>
// |:每行内容单独成行
ansibletest]# cat f1.yml
---
- name: play 1
hosts: db1
tasks:
- name: mkfile 1.txt
copy:
dest: /tmp/1.txt
content: |
Hello World
ni hao
ansibletest]# ansible-playbook f1.yml
/*
[root@db1 ~]# cat /tmp/1.txt
Hello world
ni hao
*/
// >:将多行内容合并为一行
ansibletest]# cat f2.yml
---
- name: play 2
hosts: db1
tasks:
- name: mkfile 2.txt
copy:
dest: /tmp/2.txt
content: >
Hello World
ni hao
ansibletest]# ansible-playbook f2.yml
/*
[root@db1 ~]# cat /tmp/2.txt
Hello world ni hao
*/
剧本进行硬盘管理
java
// 分区与逻辑卷
// 创建分区
ansibletest]# cat disk.yml
---
- name: disk manage
hosts: db1
tasks:
- name: create partition
parted: // parted模块
device: /dev/vdc // 要分区的硬盘
state: present // 省略默认为info(查看硬盘分区详情)
number: 1 // 分区编号
part_end: 1GiB // 结束位置。这里写1G会识别为1GB=1000MB=1000*1000B
// 没有使用part_start参数:由于硬盘开头会保留一部分用于存储硬盘信息,因此不要从0开始。
ansibletest]# ansible-playbook disk.yml
/*
[root@db1 ~]# lsblk /dev/vdc
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vdc 253:32 0 20G 0 disk
└─vdc1 253:33 0 1023M 0 part // 硬盘开头会保留一部分用于存储硬盘信息,因此不足1GiB
*/
// 创建多个分区,parted模块的参数part
ansibletest]# cat disk.yml
---
- name: disk manage
hosts: db1
tasks:
- name: create partition
parted:
device: /dev/vdc
state: present
part: // 参数part同时创建或删除多个分区
- number: 1 // 第一个分区
part_end: 1GiB
- number: 2 // 第二个分区
part_start: 1GiB
part_end: 6GiB
ansibletest]# ansible-playbook disk.yml
/*
[root@db1 ~]# lsblk /dev/vdc
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
vdc 253:32 0 20G 0 disk
├─vdc1 253:33 0 1023M 0 part
└─vdc2 253:34 0 5G 0 part
*/
// 创建逻辑卷,格式化并挂载
ansibletest]# cat disk.yml
---
- name: disk manage
hosts: db1
tasks:
- name: create partition
parted: // 分区
device: /dev/vdc
state: present
part:
- number: 1
part_end: 1GiB
- number: 2
part_start: 1GiB
part_end: 6GiB
- name: create my_vg
lvg: // 卷组
vg: my_vg
pvs: /dev/vdc1,/dev/vdc2
- name: create my_lv
lvol: // 逻辑卷
vg: my_vg
lv: my_lv
size: 1G
- name: mkfs my_lv
filesystem: // 格式化
dev: /dev/my_vg/my_lv
fstype: ext4
- name: mount my_lv
mount: // 挂载
src: /dev/my_vg/my_lv
path: /data2
fstype: ext4
state: mounted // mounted永久挂载,挂载点不存在会自动创建
ansibletest]# ansible-playbook disk.yml
/*
[root@db1 ~]# vgs my_vg
VG #PV #LV #SN Attr VSize VFree
my_vg 2 1 0 wz--n- 5.99g 4.99g
[root@db1 ~]# lvs /dev/my_vg/my_lv
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
my_lv my_vg -wi-a----- 1.00g
[root@db1 ~]# df -Th /data2
Filesystem Type Size Used Avail Use% Mounted on
/dev/mapper/my_vg-my_lv ext4 976M 2.6M 907M 1% /data2
*/
剧本安装软件包、软件包组、升级已安装软件
- 如果参数支持同时赋多个值,有以下三种写法:(以yum模块的参数name为例)
- yum命令的写法:
name: 软件名1,软件名2,软件名n
- python列表的标准写法:
name: [软件名1,软件名2,软件名n]
- yaml的标准写法:
name:
空格空格- 软件名1
空格空格- 软件名2
空格空格- 软件名n
- yum命令的写法:
java
// 安装多个软件包httpd、php、php-mysqlnd
ansibletest]# cat pkg.yml
// 写法1:
---
- name: install pkgs
hosts: webs
tasks:
- name: install web pkgs
yum:
name: httpd,php,php-mysqlnd // yum命令的写法
state: present
// 写法2:
---
- name: install pkgs
hosts: webs
tasks:
- name: install web pkgs
yum:
name: [httpd,php,php-mysqlnd] // python列表的标准写法
state: present
// 写法3:
---
- name: install pkgs
hosts: webs
tasks:
- name: install web pkgs
yum:
name:
- httpd // yaml的标准写法
- php
- php-mysqlnd
state: present
// 安装软件包组
/* 根据功能等,可以将一系列软件放到一个组中,安装软件包组,将会把很多软件一起安装上。
[root@web1 ~]# yum grouplist // 列出所有的软件包组
[root@web1 ~]# yum groupinstall "Development Tools" // 安装该软件包组
[root@web1 ~]# LANG=C yum grouplist // 使用中文列出组名
*/
ansibletest]# cat pkg.yml
---
- name: install pkgs
hosts: webs
tasks:
- name: install web pkgs
yum:
name:
- httpd
- php
- php-mysqlnd
state: present
- name: install gtoup
yum:
name: "@Development Tools" // 使用@表示软件包组
state: present
// 升级所有已安装的软件包到最新版本
/* 更新已安装的软件包:卸载原来的Rocky8.6镜像,挂载新的Rocky8.7镜像(网络下载),然后使用升级命令yum update
[root@web1 ~]# yum update
*/
ansibletest]# cat pkg.yml
---
- name: install pkgs
hosts: webs
tasks:
- name: install web pkgs
yum:
name:
- httpd
- php
- php-mysqlnd
state: present
- name: install gtoup
yum:
name: "@Development Tools" // 使用@表示软件包组
state: present
- name: update system
yum:
name: "*" // 表示系统已经安装的所有包
state: latest
ansibletest]# ansible-playbook pkg.yml
ansible变量
- 在ansible中,调用变量使用
{``{变量名}}
facts变量
- facts变量是ansible自带的预定义变量,用于描述被控端软硬件信息。
- 常用的facts变量:
ansible_all_ipv4_addresses
:所有的IPV4地址ansible_eth0.ipv4.address
:eth0的IPV4地址ansible_bios_version
:BIOS版本信息ansible_memtotal_mb
:总内存大小ansible_memfree_mb
:可用内存大小ansible_hostname
:主机名
setup模块
ansible all或组名或主机 -m setup
:查看所有facts变量。
ansible all或组名或主机 -m setup -a "filter=facts变量"
:查看该facts变量的值。
- setup模块可以获取facts变量。
- 常用参数:
filter
:指定具体的facts变量。
- 找到facts变量完整路径的操作:
ansible 组名或主机名 -m setup | cat -n | grep "目标关键字"
:找到目标关键字的行号。ansible 组名或主机名 -m setup | grep "目标关键字"
:找到目标关键字的行。- 鼠标放在关键字的开头不动,滑动鼠标滚轮找到上一级,每一级用
.
连接。
java
// setup模块
// 查看所有facts变量。可以Shift+PageUp/PageDown在终端翻页
ansibletest]# ansible db1 -m setup
// 查看所有的IPv4地址,filter=facts变量
ansibletest]# ansible db1 -m setup -a "filter=ansible_all_ipv4_addresses"
db1 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.88.13"
],
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
// 查看所有内存
ansibletest]# ansible db1 -m setup -a "filter=ansible_memtotal_mb"
db1 | SUCCESS => {
"ansible_facts": {
"ansible_memtotal_mb": 1924,
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
// 查看可用内存
ansibletest]# ansible db1 -m setup -a "filter=ansible_memfree_mb"
db1 | SUCCESS => {
"ansible_facts": {
"ansible_memfree_mb": 1633,
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
// 查看主板版本
ansibletest]# ansible db1 -m setup -a "filter=ansible_bios_version"
db1 | SUCCESS => {
"ansible_facts": {
"ansible_bios_version": "rel-1.15.0-0-g2dd4b9b3f840-prebuilt.qemu.org",
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
// 查看主机名
ansibletest]# ansible db1 -m setup -a "filter=ansible_hostname"
db1 | SUCCESS => {
"ansible_facts": {
"ansible_hostname": "db1",
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false
}
debug模块
- debug模块用于输出信息。
- 常用的参数:
msg
:指定输出内容。
- 在ansible剧本中,调用变量使用
{``{变量名}}
java
// debug模块,参数msg。调用变量:{{变量名}}
/* 找到facts变量完整路径的操作:
* ansible db1 -m setup | cat -n | grep "目标关键字"。找到目标关键字的行号。
* ansible db1 -m setup | cat -n。找到目标关键字的行。
* 鼠标放在关键字的开头不动,滑动鼠标滚轮找到上一级,每一级用"."连接。
*/
// 输出facts变量值的剧本
ansibletest]# cat debug.yml
---
- name:
hosts: db1
tasks:
- name: print hostname and memory
debug:
msg: "hostname: {{ansible_hostname}}, memory: {{ansible_memtotal_mb}}MB"
ansibletest]# ansible-playbook debug.yml
PLAY [db1] **************************************
TASK [Gathering Facts] ************************** // 收集facts变量任务。没有这一步,则无法识别facts变量
ok: [db1]
TASK [print hostname and memory] ****************
ok: [db1] => {
"msg": "hostname: db1, memory: 1924MB"
}
PLAY RECAP **************************************
db1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
// 输出facts变量值不能使用adhoc临时命令,因为没有"收集facts变量任务",无法识别facts变量,会报错
ansibletest]# ansible db1 -m debug -a "msg='hostname: {{ansible_hostname}}, memory: {{ansible_memtotal_mb}}MB'"
db1 | FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: 'ansible_hostname' is undefined. 'ansible_hostname' is undefined"
}
ansibletest]# cat ip.yml
---
- name:
hosts: db1
tasks:
- name: display ipv4 and /vdb2
debug:
msg: "eth0 ip: {{ansible_eth0.ipv4.address}} , /vdb2 size: {{ansible_devices.vdb.partitions.vdb2.size}}"
自定义变量
-
自定义变量的值为字符串时可以不加引号,为数字时必须加引号。
-
ansible支持10种以上的变量定义方式。常用的变量有:
- facts变量。
- 主机清单变量,在主机清单文件中的自定义变量。
- 组变量 :针对组内所有主机的变量:
[组名:vars]
变量名=变量值
- 主机变量 :针对单个主机的变量(写在组内):
主机名或主机IP 变量名=变量值
- 组变量 :针对组内所有主机的变量:
- 剧本文件变量。在剧本文件中自定义的变量。
vars:
空格空格- 变量名: 变量值
空格空格- 变量名: 变量值
- 剧本级变量 :针对play中的所有任务的变量(写在play内)
任务级变量:针对某个任务的变量(写在任务内)
- 变量文件的变量 。在变量文件中自定义的变量。
- 剧本文件使用
vars_files: 变量文件名.yml
调用该变量文件中的变量(只能写在play内)。
- 剧本文件使用
-
同名变量:任务级变量 > 变量文件变量 > 剧本级变量 > 主机变量 > 组变量
java
// 自定义变量??
// 给webs组创建用户wangwu,给db1主机创建用户zhaoliu
// 普通的写法需要写两个play
ansibletest]# cat u1.yml
---
- name: create a user
hosts: webs
tasks:
- name: useradd wangwu
user:
name: wangwu
- name: create a user
hosts: db1
tasks:
- name: useradd zhaoliu
user:
name: zhaoliu
ansibletest]# ansible-playbook u1.yml
// 主机清单文件变量
ansibletest]# cat inventorytest
[webs]
web[1:2]
[webs:vars] // 组变量:针对组内所有主机生效:[组名:vars]
username=wangwu // 值为字符串可以不加引号,为数字需要加引号
[dbs:vars] // 组变量
username=wangwu
[dbs]
db1 username=zhaoliu // 主机变量:针对单个主机生效:与主机写在同一行
[cluster:children]
webs
dbs
ansibletest]# cat u2.yml
---
- name: create a user
hosts: webs,db1
tasks:
- name: useradd
user:
name: "{{username}}"
ansibletest]# ansible_playbook u2.yml
/* 同名变量优先级:主机变量 > 组变量。db1主机的变量username=zhaoliu,优先于wangwu
[root@db1 ~]# id wangwu
id: 'wangwu': no such user
[root@db1 ~]# id zhaoliu
uid=1008(zhaoliu) gid=1008(zhaoliu) groups=1008(zhaoliu)
*/
// playbook变量
ansibletest]# cat u3.yml
---
- name: create a user
hosts: db1
vars:
username: jack // 剧本级变量:针对本play下的所有任务"useradd 1"和"useradd 2"生效
mima: "123"
tasks:
- name: "useradd 1"
user:
name: "{{username}}"
password: "{{mima | password_hash('sha512')}}"
- name: "useradd 2"
vars:
yonghu: rose // 任务级变量:针对本任务"useradd 2"生效
mima: "456"
user:
name: "{{username}}"
password: "{{mima | password_hash('sha512')}}"
ansibletest]# ansible-playbook u3.yml
/* 同名变量:任务级变量 > 剧本级变量 > 主机变量 > 组变量
[root@db1 ~]# id jack // db1主机的任务"useradd 1"的变量username=jack,优先于zhaoliu
uid=1009(jack) gid=1009(jack) groups=1009(jack)
[root@db1 ~]# id rose // db1主机的任务"useradd 2"的变量username=rose,优先于jack
uid=1010(rose) gid=1010(rose) groups=1010(rose)
*/
// 变量文件变量
ansibletest]# cat var.yml
---
username: fileuser
mima: "789"
ansibletest]# cat u4.yml
---
- name: create a user
hosts: db1
vars_files: var.yml // 同级无顺序,vars_files也可以写在vars下,结果一样
vars:
- username: playuser
- mima: "123"
tasks:
- name: "useradd 1"
user:
name: "{{username}}"
password: "{{mima | password_hash('sha512')}}"
- name: "useradd 2"
vars:
username: taskuser
mima: "456"
user:
name: "{{username}}"
password: "{{mima | password_hash('sha512')}}"
ansibletest]# ansible-playbook u4.yml
/* 同名变量:任务级变量 > 变量文件变量 > 剧本级变量 > 主机变量 > 组变量
[root@db1 ~]# id fileuser
uid=1012(fileuser) gid=1012(fileuser) groups=1012(fileuser)
[root@db1 ~]# id playuser // 任务"useradd 1"的变量username=fileuser,优先于playuser
id: 'playuser': no such user
[root@db1 ~]# id taskuser // 任务"useradd 2"的变量username=taskuser,优先于fileuser
uid=1013(taskuser) gid=1013(taskuser) groups=1013(taskuser)
*/
同名变量优先级
- 在Ansible中,同名变量的优先级从高到低依次如下:越特例优先级越高
- 命令行参数:通过选项
--extra-vars
(简写-e
)传递的变量具有最高优先级 。ansible-playbook 剧本文件 -e "变量名=变量值"
ansible-playbook 剧本文件 -e "@变量文件"
(变量文件支持 YAML 或 JSON)
- 任务级变量:在剧本文件为单个任务定义的变量。
- 变量文件变量:
vars_files
调用变量文件的变量。 - 剧本级变量:在剧本文件为单个play定义的变量。
- 主机变量:在主机清单文件中为单个主机定义的变量。
- 组变量:在主机清单文件中为单个组定义的变量。
- 角色级变量:在角色的
defaults
目录下定义的变量将作为角色的默认值。?? - 包含角色:如果使用了角色,那么角色中定义的变量将覆盖其他位置定义的变量。??
- 包含关系:使用
include
或import
导入了其他文件中的变量。?? - 环境变量:环境变量中定义的变量。
- Ansible配置文件:在
ansible.cfg
中定义的变量。
template模块
-
ansible all或组名或主机 -m template -a "参数1=变量1 参数n=变量n"
-
template模块和[copy模块](# copy模块)的区别:
- copy模块可以上传文件,但是文件内容固定。
- template模块可以上传包含变量的文件。当远程主机接收到文件之后,文件中的变量将会变成具体的值。
-
template模块上传的文件,使用的语法叫Jinja2。
-
常用参数:
src
(source):指定控制端的源数据路径。只拷贝源数据,不拷贝路径。dest
(destination):指定被控端的目标数据路径(相对路径是/root
)。(只需存在父目录)
java
// template模块
ansibletest]# vim index.html // 编写包含facts变量的网页文件
Welcome to {{ansible_hostname}} on {{ansible_eth0.ipv4.address}}
// ansible_hostname变量是主机名,ansible_eth0.ipv4.address变量是eth0网卡的IPv4地址。
ansibletest]# vim templ.yml // 使用template模块替换webs组的主机的默认网页文件
---
- name: upload index
hosts: webs
tasks:
- name: upload web index
template:
src: index.html
dest: /usr/share/nginx/html/index.html // rpm包安装的nginx的默认网页文件
ansibletest]# ansible-playbook templ.yml
ansibletest]# curl http://192.168.88.11
Welcome to web1 on 192.168.88.11
ansibletest]# curl http://192.168.88.12
Welcome to web2 on 192.168.88.12
/* template模块上传的文件到远程主机后,变量会变成具体的值
[root@web1 ~]# cat /usr/share/nginx/html/index.html
Welcome to web1 on 192.168.88.11
[root@web2 ~]# cat /usr/share/nginx/html/index.html
Welcome to web2 on 192.168.88.12
*/
错误处理 ignore_errors
-
如果剧本文件中包含多个任务,当某一个任务遇到错误,剧本文件将崩溃,终止执行后面的任务。
-
可以通过
ignore_errors: yes
忽略错误,继续执行后面的任务。- 写在任务中:对本任务生效。如果该任务出现错误,忽略它。
- 写在play中:对本play中所有任务生效。无论该play中的哪个任务出现问题,都忽略它。
java
// 错误处理 ignore_errors
// web1和web2没有mysql服务
ansibletest]# cat myerr.yml
---
- name:
hosts: web1
tasks:
- name: start mysql service
ignore_errors: yes // 写在任务中:忽略本任务的错误继续执行其它任务。任务内同级无先后顺序
service:
name: mysql
state: started
enabled: yes
- name: touch a file
file:
name: /tmp/service.txt
state: touch
- name:
hosts: web2
ignore_errors: yes // 写在play中:对play中所有任务生效
tasks:
- name: start mysql service
service:
name: mysql
state: started
enabled: yes
- name: touch a file
file:
name: /tmp/service.txt
state: touch
ansibletest]# ansible-playbook myerr.yml
PLAY [web1] **************************************************
TASK [Gathering Facts] ***************************************
ok: [web1]
TASK [start mysql service] ***********************************
fatal: [web1]: FAILED! => {"changed": false, "msg": "Could not find the requested service mysql: host"}
...ignoring
TASK [touch a file] ******************************************
changed: [web1]
PLAY [web2] **************************************************
TASK [Gathering Facts] ***************************************
ok: [web2]
TASK [start mysql service] ***********************************
fatal: [web2]: FAILED! => {"changed": false, "msg": "Could not find the requested service mysql: host"}
...ignoring
TASK [touch a file] ******************************************
changed: [web2]
PLAY RECAP ***************************************************
web1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
web2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
触发执行任务 notify、handlers
- 通过handlers定义触发执行的任务
- handlers中定义的任务,不是一定会执行的。tasks中定义的任务是一定会执行的。
- 在tasks中定义的任务,通过
notify: 任务名
通知handlers中的哪个任务要执行。只有tasks中的任务状态是changed才会进行通知。
java
// 触发执行任务 notify、handlers??
ansibletest]# cat get_conf.yml
---
- name: download nginx.conf
hosts: web1
tasks:
- name: nginx.conf
fetch:
src: /etc/nginx/nginx.conf
dest: ./
flat: 1 // 不下载路径,只能对一个主机生效
ansibletest]# vim nginx.conf
39 listen {{http_port}} default_server;
40 listen [::]:{{http_port}} default_server;
ansibletest]# cat trigger.yml
---
- name:
hosts: webs
vars:
http_port: "8010"
tasks:
- name: upload nginx.conf
template:
src: nginx.conf
dest: /etc/nginx/nginx.conf
notify: reload nginx // 当本任务changed时才通知handlers执行任务reload nginx
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
when条件
- 只有满足when为true时,才执行任务。作用类似于shell中的if结构
- 常用的比较符:
==
:相等!=
:不等>
:大于<
:小于<=
:小于等于>=
:大于等于
- 多个条件使用
and
或or
进行连接。 not
结果取反- when表达式中的变量,可以不使用
{``{}}
。
java
# 当dbs组中的主机内存大于2G的时候,才安装mysql-server
[root@pubserver ansible]# vim when1.yml
---
- name: install mysql-server
hosts: dbs
tasks:
- name: install mysql-server pkg
yum:
name: mysql-server
state: present
when: ansible_memtotal_mb>2048
# 如果目标主机没有2GB内存,则不会安装mysqld-server
[root@pubserver ansible]# ansible-playbook when1.yml
# 多条件。系统发行版是Rocky8才执行任务
# /etc/motd中的内容,将会在用户登陆时显示在屏幕上
[root@pubserver ansible]# vim motd
_____________
< hello world >
-------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
[root@pubserver ansible]# vim when2.yml
---
- name: when condition
hosts: webservers
tasks:
- name: modify /etc/motd
copy:
dest: /etc/motd
src: motd
when: >
ansible_distribution == "Rocky"
and
ansible_distribution_major_version == "8"
[root@pubserver ansible]# ansible-playbook when2.yml
regitster注册变量
Ansible的"register"模块是用于捕获和保存任务执行结果的,它允许将其他任务的输出作为变量使用。register是一个关键字,可以将任务执行的结果赋值给指定的变量名称。这个变量可以在后续任务中使用。 register模块可以捕获各种类型的输出,包括stdout、stderr、rc、changed等。它可以与其他模块一起使用,例如"when"条件、"loop"循环等。
java
# 在web1组的主机上执行任务,创建/tmp/regfile1.txt,并打印创建结果
[root@pubserver ansible]# vim reg1.yml
---
- name: create file /tmp/regfile1.txt
hosts: web1
tasks:
- name: create file
file:
path: /tmp/rgefile1.txt
state: touch
register: result
- name: display output
debug:
msg: "{{result}}"
# 在web1主机上执行任务,创建文件/tmp/ademo/abc。如果创建不成功,则通过debug输出create failed
[root@pubserver ansible]# vim reg2.yml
---
- name: create file /tmp/ademo/abc
hosts: web1
ignore_errors: yes
tasks:
- name: create file
file:
path: /tmp/ademo/abc
state: touch
register: result
notify: print mode
- name: debug output
debug:
msg: "create failed"
when: result.failed
handlers:
- name: print mode
debug:
msg: "{{result.mode}}"
java
// =两侧要有空格
"msg" = {
"changed": true,
"dest": "/tmp/filereg1.txt",
"diff": {
"after": {
"atime": 1689759842.9187007,
"mtime": 1689759842.9187007,
"path": "/tmp/filereg1.txt",
"state": "touch"
},
"before": {
"atime": 1689759423.64123,
"mtime": 1689759423.64123,
"path": "/tmp/filereg1.txt",
"state": "file"
}
},
"failed": false,
"gid": 0,
"group": "root",
"mode": "0644",
"owner": "root",
"size": 0,
"state": "file",
"uid": 0
}
msg['mode']
任务块
- 可以通过block关键字,将多个任务组合到一起
- 可以将整个block任务组,一起控制是否要执行
java
# 如果webservers组中的主机系统发行版是Rocky,则安装并启动nginx
[root@pubserver ansible]# vim block1.yml
---
- name: block tasks
hosts: webservers
tasks:
- name: define a group of tasks
block:
- name: install nginx # 通过yum安装nginx
yum:
name: nginx
state: present
- name: start nginx # 通过service启动nginx服务
service:
name: nginx
state: started
enabled: yes
when: ansible_distribution=="Rocky" # 条件为真才会执行上面的任务
[root@pubserver ansible]# ansible-playbook block1.yml
快捷操作
java
// 多行开头增加空格,例如第7行至最后一行
光标在要操作的第一行,Ctrl+v,下箭头至最后一行,大写I,键入空格,Esc退出
末行模式:7,$s/^/ /
block、rescue和always
- block和rescue、always联合使用:
- block中的任务都成功,rescue中的任务不执行
- block中的任务出现失败(failed),rescue中的任务执行
- block中的任务不管怎么样,always中的任务总是执行
java
[root@pubserver ansible]# vim block2.yml
---
- name: block test
hosts: webservers
tasks:
- name: block / rescue / always test1
block:
- name: touch a file
file:
path: /tmp/test1.txt
state: touch
rescue:
- name: touch file test2.txt
file:
path: /tmp/test2.txt
state: touch
always:
- name: touch file test3.txt
file:
path: /tmp/test3.txt
state: touch
# 执行playbook web1上将会出现/tmp/test1.txt和/tmp/test3.txt
[root@pubserver ansible]# ansible-playbook block2.yml
[root@web1 ~]# ls /tmp/test*.txt
/tmp/test1.txt /tmp/test3.txt
# 修改上面的playbook,使block中的任务出错
[root@web1 ~]# rm -f /tmp/test*.txt
[root@pubserver ansible]# vim block2.yml
---
- name: block test
hosts: webservers
tasks:
- name: block / rescue / always test1
block:
- name: touch a file
file:
path: /tmp/abcd/test11.txt
state: touch
rescue:
- name: touch file test22.txt
file:
path: /tmp/test22.txt
state: touch
always:
- name: touch file test33.txt
file:
path: /tmp/test33.txt
state: touch
# 因为web1上没有/tmp/abcd目录,所以block中的任务失败。但是playbook不再崩溃,而是执行rescue中的任务。always中的任务总是执行
[root@pubserver ansible]# ansible-playbook block2.yml
[root@web1 ~]# ls /tmp/test*.txt
/tmp/test22.txt /tmp/test33.txt
loop循环
-
相当于shell中for循环。旧版本是
with_item
。 -
ansible中循环用到的变量名是固定的,叫
item
在test组中的主机上创建5个目录/tmp/{aaa,bbb,ccc,ddd,eee}
[root@pubserver ansible]# vim loop1.yml
- name: use loop
hosts: webservers
tasks:- name: create directory
file:
path: /tmp/{{item}}
state: directory
loop: [aaa,bbb,ccc,ddd,eee] // python的写法,列表[]
- name: create directory
上面写法,也可以改为:
- name: use loop
hosts: webservers
tasks:- name: create directory
file:
path: /tmp/{{item}}
state: directory
loop:- aaa // yaml的写法
- bbb
- ccc
- ddd
- eee
- name: create directory
[root@pubserver ansible]# ansible-playbook loop1.yml
使用复杂变量。创建zhangsan用户,密码是123;创建lisi用户,密码是456
item是固定的,用于表示循环中的变量
循环时,loop中每个-后面的内容作为一个整体赋值给item。
loop中{}中的内容是自己定义的,写法为key:val
取值时使用句点表示。如下例中取出用户名就是{{item.uname}}
[root@pubserver ansible]# vim loop_user.yml
- name: create users
hosts: webservers
tasks:- name: create multiple users
user:
name: "{{item.uname}}"
password: "{{item.upass | password_hash('sha512')}}"
loop:- {"uname": "zhangsan", "upass": "123"}
- {"uname": "lisi", "upass": "456"}
[root@pubserver ansible]# ansible-playbook loop_user.yml
- name: create multiple users
- name: use loop
role角色
- 为了实现playbook重用,可以使用role角色。作用是代码重用
- 角色role相当于把任务打散,放到不同的目录中
- 再把一些固定的值,如用户名、软件包、服务等,用变量来表示
- role角色定义好之后,可以在其他playbook中直接调用
shell
# 使用常规playbook,修改/etc/motd的内容
# 1. 创建motd模板文件
[root@pubserver ansible]# vim motd
Hostname: {{ansible_hostname}} # facts变量,主机名
Date: {{ansible_date_time.date}} # facts变量,日期
Contact to: {{admin}} # 自定义变量
# 2. 编写playbook
[root@pubserver ansible]# vim motd.yml
---
- name: modifty /tmp/motd
hosts: webservers
vars:
admin: root@123.com # 自定义名为admin的变量
tasks:
- name: modify motd
template:
src: motd
dest: /tmp/motd
[root@pubserver ansible]# ansible-playbook motd.yml
[root@web1 ~]# cat /tmp/motd
Hostname: web1
Date: 2023-07-20
Contact to: root@123.com
# 创建角色
# 1. 声明角色存放的位置
[root@pubserver ansible]# vim ansible.cfg
[defaults]
inventory = hosts
roles_path = roles # 定义角色存在当前目录的roles子目录中
# 2. 创建角色目录
[root@pubserver ansible]# mkdir roles
# 3. 创建名为motd的角色
[root@pubserver ansible]# ansible-galaxy init roles/motd
[root@pubserver ansible]# ls roles/
motd # 生成了motd角色目录
[root@pubserver ansible]# yum install -y tree
[root@pubserver ansible]# tree roles/motd/
roles/motd/
├── defaults # 定义变量的目录,优先级最低
│ └── main.yml
├── files # 保存上传的文件(如copy模块用到的文件)
├── handlers # handlers任务写到这个目录的main.yml中
│ └── main.yml
├── meta # 保存说明数据,如角色作者、版本等
│ └── main.yml
├── README.md # 保存角色如何使用之类的说明
├── tasks # 保存任务
│ └── main.yml
├── templates # 保存template模块上传的模板文件
├── tests # 保存测试用的playbook。可选
│ ├── inventory
│ └── test.yml
└── vars # 定义变量的位置,推荐使用的位置
└── main.yml
# 4. 将不同的内容分别写到对应目录的main.yml中
# 4.1 创建motd模板文件
ansibletest]# mv motd roles/motd/templates/
[root@pubserver ansible]# vim roles/motd/templates/motd
Hostname: {{ansible_hostname}}
Date: {{ansible_date_time.date}}
Contact to: {{admin}}
# 4.2 创建变量
[root@pubserver ansible]# echo "admin: test@456.com" >> roles/motd/vars/main.yml # 追加一行
# 4.3 创建任务
[root@pubserver ansible]# vim roles/motd/tasks/main.yml # 追加
- name: modify motd
template:
src: motd # 这里的文件,自动到templates目录下查找
dest: /tmp/motd
# 5. 创建playbook,调用motd角色
[root@pubserver ansible]# vim role_motd.yml
---
- name: modify motd with role
hosts: webs
roles:
- motd
# 6. 执行playbook
[root@pubserver ansible]# ansible-playbook role_motd.yml
[root@db1 ~]# cat /tmp/motd
Hostname: db1
Date: 2023-07-20
Contact to: test@456.com
role练习
- 创建名为pkgs的角色。用于装包。包名使用变量pkg代表
- 创建inst_nginx.yml,调用pkgs角色,安装nginx
- 创建inst_mysql.yml,调用pkgs角色,安装mysql
shell
# 1. 创建名为pkgs的角色。
# 1.1 创建角色目录
[root@pubserver ansible]# ansible-galaxy init roles/pkgs
# 1.2 创建装包的任务,包名使用变量pkg代表
[root@pubserver ansible]# vim roles/pkgs/tasks/main.yml
---
# tasks file for roles/pkgs
- name: install rpm pkg
yum:
name: "{{pkg}}"
state: present
# 1.3 定义变量
[root@pubserver ansible]# vim roles/pkgs/defaults/main.yml
---
# defaults file for roles/pkgs
pkg: nginx
ansibletest]# cat inst_nginx.yml
---
- name: install nginx
hosts: db1
roles:
- pkgs
ansibletest]# ansible-playbook inst_nginx.yml
ansibletest]# cat inst_mysql.yml
---
- name: install mysql
hosts: db1
vars:
pkg: mysql
roles:
- pkgs
ansibletest]# ansible-playbook inst_mysql.yml
java
ansibletest]# mkdir -p roles/svrs/{tasks,defaults}
ansibletest]# cat roles/svrs/tasks/main.yml
---
- name: start and enable
service:
name: "{{svr}}"
state: started
enabled: yes
ansibletest]# cat roles/svrs/defaults/main.yml
---
svr: nginx
ansibletest]# cat inst_nginx.yml
---
- name: install nginx
hosts: db1
roles:
- pkgs
- svrs
ansibletest]# ansible-playbook inst_nginx.yml
ansibletest]# cat inst_mysql.yml
---
- name: install mysql
hosts: db1
vars:
pkg: mysql-server
svr: mysqld
roles:
- pkgs
- svrs
ansible加解密文件 ansible-vault
直接加解密
ansible-vault 动作参数 文件路径
:加解密该文件。- 常用动作参数:
encrypt
:加密。decrypt
:解密(需知密码)。rekey
:重设密码(需知密码)。view
:不解密查看(需知密码)。
- 常用动作参数:
java
// ansible-vault
ansibletest]# echo "for dinner" > /tmp/vault.txt // 创建一个需要加密的文件
ansibletest]# cat /tmp/vault.txt
for dinner
// 加密文件,encrypt
ansibletest]# ansible-vault encrypt /tmp/vault.txt
New Vault password: // 新设密码
Confirm New Vault password: // 确认密码
Encryption successful
ansibletest]# cat /tmp/vault.txt
$ANSIBLE_VAULT;1.1;AES256
66643337396139336230303461313066326664646664666636636331663731323863336535356333
3733643164363338396436666662353532623964323830360a336331313262306330653336323532
62383332333735623662636562653466353637646333323032333130383137656465633265656231
3239623536633534630a623638653039316534323235346637623632656562366137386133333839
3361
// 知道旧密码重设密码,rekey
ansibletest]# ansible-vault rekey /tmp/vault.txt
Vault password: // 旧密码
New Vault password: // 新设密码
Confirm New Vault password: // 确认密码
Rekey successful
ansibletest]# cat /tmp/vault.txt
$ANSIBLE_VAULT;1.1;AES256
31653239643133313637633030336463383035313433666331363764643838326165633563353636
3835666561393566616530633135653631343031656537620a613539373933363830343637356331
30616661353461396234313834386536613362333165613233383564653032656431626538663962
3737323537343063640a393233373538633966373363656339386535313066626239633835353432
6366
// 知道密码查看文件,view
ansibletest]# ansible-vault view /tmp/vault.txt
Vault password:
for dinner
ansibletest]# ansible-vault view /tmp/vault.txt > /tmp/vault2.txt // 不会解密加密文件
Vault password:
ansibletest]# cat /tmp/vault2.txt
for dinner
ansibletest]# cat /tmp/vault.txt
$ANSIBLE_VAULT;1.1;AES256
31653239643133313637633030336463383035313433666331363764643838326165633563353636
3835666561393566616530633135653631343031656537620a613539373933363830343637356331
30616661353461396234313834386536613362333165613233383564653032656431626538663962
3737323537343063640a393233373538633966373363656339386535313066626239633835353432
6366
// 解密文件,decrypt
ansibletest]# ansible-vault decrypt /tmp/vault.txt
Vault password:
Decryption successful
ansibletest]# cat /tmp/vault.txt
for dinner
使用密码文件加解密 --vault-id
ansible-vault 动作参数 --vault-id=密码文件A路径 文件B路径
:使用密码文件A来加解密文件B。- 常用动作参数:
encrypt
:加密。decrypt
:解密。view
:不解密查看。
- 常用动作参数:
java
// 使用密码文件进行加解密,--vault-id=密码文件
ansibletest]# echo "for dinner" > /tmp/vault.txt // 创建一个需要加密的文件
ansibletest]# cat /tmp/vault.txt
for dinner
ansibletest]# echo "asddgfqf.adsbfaaaagaa.afsdf" > /tmp/pass.txt // 创建密码文件
// 将文件/tmp/pass.txt中的内容作为密码加密/tmp/vault.txt,encrypt
ansibletest]# ansible-vault encrypt --vault-id=/tmp/pass.txt /tmp/vault.txt
Encryption successful
ansibletest]# cat /tmp/vault.txt
$ANSIBLE_VAULT;1.1;AES256
63333935636364306263656332396234636661616334633163303561626331316339643666363330
6635636134646534343232623135653632646330623936630a656334396137343062373137333137
35316134306232363364663333353566373038626461653230633139616237356165313233326466
6533363330306464380a373362396534373636623036303639363830373339663937633665313262
3030
// 将文件/tmp/pass.txt中的内容作为密码查看/tmp/vault.txt,view
ansibletest]# ansible-vault view --vault-id=/tmp/pass.txt /tmp/vault.txt
for dinner
// 将文件/tmp/pass.txt中的内容作为密码解密/tmp/vault.txt,decrypt
ansibletest]# ansible-vault decrypt --vault-id=/tmp/pass.txt /tmp/vault.txt
Decryption successful
ansibletest]# cat /tmp/vault.txt
for dinner
执行加密的剧本文件 --ask-vault-password
- 执行加密的剧本文件:需要使用选项
--ask-vault-password
来输入密码。
ansible-playbook --ask-vault-password 剧本文件路径
:执行该剧本文件时询问密码。
java
// 执行加密的剧本文件
// 创建一个剧本文件,加密该剧本文件
ansibletest]# cat user_zhangsan.yml
---
- name: create a user
hosts: db1
tasks:
- name: create user zhangsan
user:
name: zhangsan
password: "{{'123'| password_hash('sha512')}}"
ansibletest]# ansible-vault encrypt user_zhangsan.yml
New Vault password:
Confirm New Vault password:
Encryption successful
// 直接执行剧本文件,报错
ansibletest]# ansible-playbook user_zhangsan.yml
ERROR! Attempting to decrypt but no vault secrets found // 报错
// 使用选项--ask-vault-password执行剧本文件
ansibletest]# ansible-playbook --ask-vault-password user_zhangsan.yml
Vault password:
......省略一万字(执行任务)
ansibletest]# cat user_zhangsan.yml // 不会解密文件
$ANSIBLE_VAULT;1.1;AES256
62363439326161623261643961666533376361363062636566353232353638643832623163613066
6161323564383136636530343638393730633130356437390a653030666535313863646666316232
32633337356462396131383031616466383730353939616564383865613865386138623065653731
6465613066653866380a316461313333343135646161386336303236656363623066323963613437
39306538356537353863313566613663303439393037393034383537323263343165376438313962
32656564636232326430323435373431633337666638623030653737383062653239643962333437
36626134396265326533333936376561373264353835323138343763303938386332303961323437
34313362343166386664356231353862386631396638396231613936643734386362386365663466
31666634363839653531333139313537316335393536353535646330373639346237326331373866
38343534393634386564313432623230336538346531663764396530323230653765666264303064
63386662643965323035643238303263383838356135616561346235356239343565343537356633
63653761333564666131633533333533643530343461356436323933356364633262356237303163
6130
sudo命令
- 一般用于普通用户执行需要root权限的命令
- 在web1上配置zhangsan拥有sudo权限
java
// sudo
// 赋权给zhangsan
[root@db1 ~]# visudo // 使用vi方式打开/etc/sudoers,在最后追加一行信息
zhangsan ALL=(ALL) ALL
/* zhangsan位置是用户名,最后的ALL位置是命令。ALL表示zhangsan可以执行所有命令
* 第一个ALL是主机名。ALL表示所有主机集中认证的域环境中才有效,单机忽略即可
* 第二个ALL是sudo后的用户身份。
* 第三个ALL是赋权的命令
*/
// 切换成zhangsan用户,执行命令
[root@db1 ~]# su - zhangsan
[zhangsan@db1 ~]$ useradd wangwu // 失败,无权限
useradd: Permission denied.
useradd: cannot lock /etc/passwd; try again later.
[zhangsan@db1 ~]$ sudo useradd wangwu // 以管理员身份执行
......省略一万字
[sudo] password for zhangsan: // 输入zhangsan的密码,不是root
// 输入zhangsan密码后有5分钟缓冲时间不用再输密码。
// 赋权给lisi,并免密sudo,NOPASSWD
[root@db1 ~]# visudo // 在最后追加一行信息
lisi ALL=(ALL) NOPASSWD: ALL
// 切换成zhangsan用户,执行命令
[root@db1 ~]# su - lisi
[lisi@db1 ~]$ ls /root/ // 失败,无权限
ls: cannot open directory '/root/': Permission denied
[lisi@db1 ~]$ sudo ls /root/ // 以管理员身份执行,无需输入密码
a3.txt anaconda-ks.cfg
特殊的主机清单变量
- 如果远程主机没有使用免密登陆,如果远程主机ssh服务不是默认的22端口,
可以在控制端设置特殊的Ansible主机清单变量 (与该主机写在同一行):ansible_ssh_user
:指定登陆远程主机的用户名。ansible_ssh_pass
:指定登陆远程主机的密码。ansible_ssh_port
:指定登陆远程主机的端口号。
- 如果没有在
/etc/hosts
中配置主机的名称解析,可以使用主机清单变量ansible_host
:主机名 ansible_host=IP地址
java
// ssh,密码、端口号
// 控制端upserver(192.168.88.240)、被控端web1(88.11)、被控端web2(88.12)、被控端db1(88.13)
控制端upserver(192.168.88.240):
// 删除被控远程主机接收的公钥(/root/.ssh/authorized_keys),恢复通过密码登陆
[root@pubserver ansible]# ansible all -m file -a "path=/root/.ssh/authorized_keys state=absent"
// 创建新的Ansible工作目录myansible
[root@pubserver ~]# mkdir myansible
[root@pubserver ~]# cd myansible
[root@pubserver myansible]# vim ansible.cfg
[defaults]
inventory=inventorytest
host_key_checking=no
[root@pubserver myansible]# cat inventorytest
[group1]
web1
web2
db1
[root@pubserver myansible]# vim /etc/hosts // 新增三行名称解析
192.168.88.11 web1
192.168.88.12 web2
192.168.88.13 db1
[root@pubserver myansible]# ansible all -m ping // 报错,Permission denied。无法免密远程管理
// 注意:ping模块检查是否可以远程管理目标主机,不同于ping命令
主机db1(192.168.88.13):
// 修改ssh服务的端口为220
[root@db1 ~]# systemctl stop firewalld
[root@db1 ~]# echo 'Port 220' >> /etc/ssh/sshd_config
[root@db1 ~]# systemctl restart sshd.service
/* 如果是其它终端远程登陆db1主机,需要指定端口号:-p220
* [root@myhost ~]# ssh -p220 192.168.88.13
*/
控制端upserver(192.168.88.240):
// 主机db1修改ssh服务端口号后,控制端无法连接该主机
[root@pubserver myansible]# ansible all -m ping
db1 | UNREACHABLE! => { // 无法连接ssh默认端口22
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host db1 port 22: Connection refused",
"unreachable": true
}
web2 | UNREACHABLE! => { // 可连接但无法免密远程管理
"changed": false,
"msg": "Failed to connect to the host via ssh: root@web2: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
"unreachable": true
}
web1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: root@web1: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
"unreachable": true
}
// 配置ssh通过用户名、密码管理远程主机,通过220端口连接db1
[root@pubserver myansible]# cat inventorytest
[group1]
web1 ansible_ssh_user=root ansible_ssh_pass=123
web2 ansible_ssh_user=root ansible_ssh_pass=123
db1 ansible_ssh_user=root ansible_ssh_pass=123 ansible_ssh_port=220
[root@pubserver myansible]# ansible all -m ping // 全部SUCCESS
// 删除/etc/hosts中的"192.168.88.13 db1"
[root@pubserver myansible]# ansible all -m ping // 无法识别db1
[root@pubserver myansible]# cat inventorytest
[group1]
web1 ansible_ssh_user=root ansible_ssh_pass=123
web2 ansible_ssh_user=root ansible_ssh_pass=123
db1 ansible_ssh_user=root ansible_ssh_pass=123 ansible_ssh_port=220 ansible_host=192.168.88.13
[root@pubserver myansible]# ansible all -m ping // 全部SUCCESS
blockinfile模块??
- blockinfile模块用于对目标文件写入内容块。
- 常用参数:
path
:指定被操作的文件路径。block
:设置写入该文件的内容块(严格匹配)。state
:present
表示创建,absent
表示删除。省略默认为present
。insertbefore
:指定在哪一行之前插入内容。支持正则表达式。BOF
表示文档开头。insertafter
:指定在哪一行之后插入内容。支持正则表达式。EOF
表示文档结尾。marker
:开始与结束标记语句(标记语句也算插入内容,对state
有影响)。#{mark}字符串
- backup参数 :是否在修改文件之前对文件进行备份。
- create参数 :当要操作的文件并不存在时,是否创建对应的文件。
archive模块
- archive模块用于在远程主机上创建和提取压缩包。
- 常用参数:
path
:指定要压缩的源数据路径。exclude_path
:指定排除的数据路径。src
:指定要提取的压缩包路径。dest
:指定要创建的目标压缩包路径。format
:指定要使用的压缩包格式,如tar、tar.gz、tar.bz2、zip等。mode
:指定要创建的目标压缩包的权限。force
:强制创建或提取压缩包,即使目标压缩包已存在。remove
:是否删除源数据。
unarchive模块
-
unarchive模块用于。
-
常用参数:
src
:指定要解压的压缩包路径。本地路径或远程路径。dest
:指定解压后的目标路径。可以是本地路径或者远程主机上的路径。remote_src
:压缩包是否在远程主机,省略默认为否no
。mode
:指定解压后数据的权限(8进制或字符串表示),省略默认保持与原文件一致"preserve"
。owner
:指定解压后数据的所有者(用户名或GID)。默认为文件或目录的当前所有者。group
:指定解压后文件或目录的所属组。可以使用组名或组ID。默认为文件或目录的当前所属组。extra_opts
:额外的解压缩选项,作为字符串传递。creates
:指定一个路径,如果该路径已经存在,表示文件已经被提取,将不会再次执行解压缩。
synchronize模块
- synchronize模块基于rsync命令批量同步文件。使用这个模块须保证被控端存在rsync命令。
- 常用参数:
src
:指定源数据路径。dest
:指定目标数据路径。mode
:push
表示从控制端同步到被控端,pull
表示从被控端同步到控制端。省略默认为push
。archive
:是否采用归档模式同步,保证源数据和目标数据属性一致。delete
:是否删除不同的文件。省略默认为no
。rsync_opts
:使用rsync参数。rsync_opts=--exclude=*.log
:忽略.log
结尾的数据。
java
// synchronize模块??
]# ansible node1 -m synchronize -a 'src=/n1/ dest=/n1 mode=pull'
]# ansible node1 -m synchronize -a 'src=/n1/ dest=/n1 mode=push'
]# ansible node1 -m synchronize -a 'src=/n1/ dest=/n1 mode=push delete=yes archive=yes'
async和poll
shell
+ 在Ansible的剧本(playbook)中,要实现跳过一个任务的进度条或将一个任务放入后台执行并继续执行下一个任务,可以采用以下方法:使用 async 和 poll
+ Ansible 允许通过 async 参数将任务放入后台执行。async 参数定义了任务在后台运行的最长时间(以秒为单位)。同时,你可以使用 poll 参数来控制 Ansible 对异步任务的检查频率。将 poll 设置为 0 会让 Ansible 启动任务后立即继续执行下一个任务,而不等待该任务完成。
示例:
- name: Run a long running task in background
command: /path/to/long/running/command
async: 3600 # 运行时间最长为1小时
poll: 0 # 立即继续到下一个任务
- name: Continue with the next task
debug:
msg: "这个任务会在后台任务开始后立即执行"
+ 处理异步任务的结果
+ 当你在后台执行一个任务时,可能还需要在某个时间点检查该任务的完成情况。为此,你可以使用 async_status 模块来轮询任务的状态。
示例:
- name: Run a long running task in background
command: /path/to/long/running/command
async: 3600
poll: 0
register: my_background_job
- name: Other tasks can be done here
debug:
msg: "执行其他任务"
- name: Check the status of the background job
async_status:
jid: "{{ my_background_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 30
delay: 10
+ 在这个示例中,async_status 用于检查后台任务的完成情况,通过循环和延迟来实现。
+ 注意事项
使用 async 时,确保定义的最长运行时间适用于你的任务。
当使用 poll: 0 时,你不会得到任务执行的即时反馈。如果任务失败,你需要通过日志或其他方式来诊断问题。
确保在 playbook 的后面部分或在适当的时机检查异步任务的结果。
这些方法可以帮助你在 Ansible playbook 中有效地管理和控制异步任务的执行。
delegate_to
shell
delegate_to是Ansible中的一个关键字,用于指定任务将在哪台主机上运行。通过使用delegate_to,您可以指定一个任务在目标主机以外的其他主机上执行,这对于执行一些特定操作或检查来说可能非常有用。
以下是一个示例,演示了如何在Ansible中使用delegate_to:
---
- name: Copy file to a different host
hosts: host1
tasks:
- name: Copy file to host2
copy:
src: /path/to/source/file
dest: /path/to/destination/file
delegate_to: host2
在这个示例中,任务Copy file to host2指定了delegate_to: host2,表示这个任务将在host2上执行。因此,文件将从控制端拷贝到host2。
使用delegate_to关键字有几个常见的用途,比如:
在目标主机之外执行一些额外的操作,比如在跳板机上执行一些操作。
在特定的主机上执行一些与目标主机无关的任务,比如监控、日志收集等。
在需要在不同主机上执行的情况下,可以通过delegate_to指定任务的执行主机。