企业级自动化配置工具之 Ansible 基础
相关介绍
背景描述
当服务器越来越多,Shell 脚本扛不住了!
想象你是公司的运维工程师,最初团队只有 2-3 台服务器,日常运维(比如批量更新软件、修改配置、重启服务)靠几条 Shell 命令或简单的脚本就能轻松搞定。
更新所有服务器的 Nginx:
for ip in 192.168.88.{100..102};
do
ssh root@$ip "yum update nginx -y";
done
检查服务状态:
# 手动登录每台机器敲
systemctl status nginx
但随着业务增长,服务器数量 从几台飙升到几十甚至上百台,问题来了:
- **重复劳动爆炸:**同样的命令要在每台服务器上重复执行(敲到手酸);
- **脚本难维护:**Shell 脚本里硬编码 IP、依赖具体环境(管理混乱);
- **效率瓶颈:**批量操作时,脚本逐台 SSH 连接速度慢,出错后排查困难(比如 SSH 登录失败,脚本卡住);
- **人为失误风险:**手动操作容易漏掉某台服务器或者输错命令(比如把 yum update 写成 yum remove);
必须引入自动化工具!
基本概念


Ansible是一种由Python开发的自动化运维工具,集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点,实现了批量系统配置、批量程序部署、批量运行命令等功能。
特点:
- 部署简单
- 只需要在服务端安装Ansible,被管理的客户端不需要安装任何Ansible相关的组件
- 默认使用ssh进行管理,基于Python里的paramiko模块开发
- 用密码或者公私钥连接
- 管理端和被管理端不需要启动服务
- 配置简单,功能强大,扩展性强
- 能通过Playbook(剧本)进行多个任务的编排
常见运维工具
- **Puppet:**基于Ruby语言编写,成熟稳定。适合于大型架构,相对于Ansible和Saltstack会复杂些。
- **Saltstack:**基于Python语言编写,简单、并发能力比Ansible要好, 需要维护被管理端的服务。如果服务断开,连接就会出问题。
- **Ansible:**基于Python语言编写,简单快捷,被管理端不需要启服务。直接走ssh协议,需要验证,但是机器多的话速度会较慢。
Puppet 音 帕皮特,中文木偶的意思,也可以指被控制的人。
| 工具 | 是否需要客户端 | 配置语言 | 学习曲线 | 适用场景 |
|---|---|---|---|---|
| Ansible | 不需要(基于 SSH) | YAML | 平缓(新手友好) | 批量配置、部署、日常运维 |
| Puppet | ✅ 需要 | 自有 DSL | 较陡 | 大规模配置管理 |
| SaltStack | ✅ 需要 | Python/YAML | 中等 | 实时性要求高的场景 |
Ansible 三大核心组件(重点)
Inventory(清单)- 管理谁
定义要管理的服务器列表(IP 或域名),类似“通讯录”。
默认是 /etc/ansible/hosts 文件,支持分组(比如 [web]、[db])
# /etc/ansible/hosts
[web_servers]
192.168.88.101
192.168.88.102
[db_servers]
192.168.88.103
Playbook(剧本)- 做什么
用 YAML 格式编写一系列任务(比如安装软件、改配置、重启服务),类似“操作手册”。
一个 Playbook 可以管理多个服务器,支持条件判断、循环等逻辑。
Module(模块)- 怎么做
Ansible 的“工具箱”,每个模块负责一个具体功能(比如 yum 安装软件、copy 拷贝文件、service 管理服务)。
官方提供 上千个内置模块(无需自己写),覆盖系统管理、云服务、网络等场景。
总结
Inventory = 通讯录(管哪些人)
Playbook = 操作指南(让这些人做什么事)
Module = 工具(比如配置IP、Hostname、域名的具体方式)
Ansible 安装与配置(重点)
环境搭建
准备四台机器
四台机器,一台管理,三台被管理
| IP | 角色 |
|---|---|
| 192.168.88.100 | Ansible管理机 |
| 192.168.88.101 | 被管理机1 |
| 192.168.88.102 | 被管理机2 |
| 192.168.88.103 | 被管理机3 |
配置说明:
- 静态IP、主机名、域名解析
- 关闭防火墙、SELinux
- 时间同步
- 确认和配置yum源
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0
systemctl stop firewalld &> /dev/null
systemctl disable firewalld &> /dev/null
iptables -F
# 改主机名
hostnamectl set-hostname ansible && bash
hostnamectl set-hostname node1 && bash
hostnamectl set-hostname node2 && bash
hostnamectl set-hostname node3 && bash
# 优化DNS解析(可选)
cat >/etc/resolv.conf<<EOF
nameserver 192.168.88.2 # 以实际网关为准
nameserver 114.114.114.114
EOF
# 配置/etc/hosts解析(推荐)
cat >/etc/hosts<<EOF
192.168.88.100 ansible
192.168.88.101 node1
192.168.88.102 node2
192.168.88.103 node3
EOF
# 安装 EPEL(Extra Packages for Enterprise Linux)软件源
dnf install epel-release -y
# 安装 NTPsec(Network Time Protocol Secure)服务(可选)
dnf install ntpsec -y
# 立即同步系统时间到中国 NTP 服务器(cn.ntp.org.cn),连不上的话就使用阿里云
ntpdate cn.ntp.org.cn
ntpdate ntp.aliyun.com
配置免密登录(推荐)
管理机上安装Ansible,被管理节点必须打开ssh服务。
实现ansible对agent的免密登录,只在ansible上做。(如果这一步不做,则在后面操作agent时都要加-k参数传密码或者在主机清单里传密码)
ssh-keygen
ssh-copy-id root@192.168.88.101
ssh-copy-id root@192.168.88.102
ssh-copy-id root@192.168.88.103
在管理节点安装 Ansible
阿里源(推荐)
cat >/etc/yum.repos.d/aliyun.repo<<EOF
[baseos]
name=CentOS Stream \$releasever - BaseOS
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/BaseOS/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1
[baseos-debug]
name=CentOS Stream \$releasever - BaseOS - Debug
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/BaseOS/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[baseos-source]
name=CentOS Stream \$releasever - BaseOS - Source
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/BaseOS/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[appstream]
name=CentOS Stream \$releasever - AppStream
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/AppStream/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1
[appstream-debug]
name=CentOS Stream \$releasever - AppStream - Debug
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/AppStream/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[appstream-source]
name=CentOS Stream \$releasever - AppStream - Source
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/AppStream/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[crb]
name=CentOS Stream \$releasever - CRB
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/CRB/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0
[crb-debug]
name=CentOS Stream \$releasever - CRB - Debug
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/CRB/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[crb-source]
name=CentOS Stream \$releasever - CRB - Source
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/CRB/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[highavailability]
name=CentOS Stream \$releasever - HighAvailability
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/HighAvailability/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0
[highavailability-debug]
name=CentOS Stream \$releasever - HighAvailability - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/HighAvailability/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[highavailability-source]
name=CentOS Stream \$releasever - HighAvailability - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/HighAvailability/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[nfv]
name=CentOS Stream \$releasever - NFV
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/NFV/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0
[nfv-debug]
name=CentOS Stream \$releasever - NFV - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/NFV/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[nfv-source]
name=CentOS Stream \$releasever - NFV - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/NFV/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[rt]
name=CentOS Stream \$releasever - RT
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/RT/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0
[rt-debug]
name=CentOS Stream \$releasever - RT - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/RT/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[rt-source]
name=CentOS Stream \$releasever - RT - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/RT/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[resilientstorage]
name=CentOS Stream \$releasever - ResilientStorage
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/ResilientStorage/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0
[resilientstorage-debug]
name=CentOS Stream \$releasever - ResilientStorage - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/ResilientStorage/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[resilientstorage-source]
name=CentOS Stream \$releasever - ResilientStorage - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/ResilientStorage/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
[extras-common]
name=CentOS Stream \$releasever - Extras packages
baseurl=http://mirrors.aliyun.com/centos-stream/SIGs/\$stream/extras/\$basearch/extras-common/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Extras-SHA512
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1
[extras-common-source]
name=CentOS Stream \$releasever - Extras packages - Source
baseurl=http://mirrors.aliyun.com/centos-stream/SIGs/\$stream/extras/source/extras-common/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Extras-SHA512
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
EOF
注意:Ansible不需要在每一台机器上安装,只需要在Ansible控制节点安装即可。
yum install epel-release -y
yum install ansible -y
ansible --version
输出类似
[root@ansible ~]# ansible --version
ansible [core 2.14.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.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible
python version = 3.9.19 (main, Aug 23 2024, 00:00:00) [GCC 11.5.0 20240719 (Red Hat 11.5.0-2)] (/usr/bin/python3)
jinja version = 3.1.2
libyaml = True

注意:如果下载ansible包很慢,那么可以按照以下方法执行
wget https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/Packages/ansible-core-2.14.18-2.el9.x86_64.rpm # 如果本地有 ansible-core-2.14.18-2.el9.x86_64.rpm 包,建议直接从本地上传到 Linux 系统
yum localinstall -y ansible-core-2.14.18-2.el9.x86_64.rpm
第3步: 在ansible上定义主机组,并测试连接性
[root@ansible ~]# vim /etc/ansible/hosts
[web_servers]
192.168.88.101
192.168.88.102
[db_servers]
192.168.88.103
-m 代表模块 -> module
经典案例 ping 模块
[root@ansible ~]# tail -n 6 /etc/ansible/hosts
[web_servers]
192.168.88.101
192.168.88.102
[db_servers]
192.168.88.103
[root@ansible ~]# ansible -m ping all
192.168.88.102 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
192.168.88.101 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
192.168.88.103 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
[root@ansible ~]# ansible -m ping web_servers
192.168.88.102 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
192.168.88.101 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
[root@ansible ~]# ansible -m ping db_servers
192.168.88.103 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"ping": "pong"
}
[root@ansible ~]#


服务器分组(重点理解)
https://docs.ansible.com/ansible/latest/inventory*guide/intro*inventory.html
Ansible通过一个"主机清单"功能来实现服务器分组。
Ansible的默认主机清单配置文件为/etc/ansible/hosts
案例1:
[起始值:结束值],包含起始值也包含结束值
[nginx] 组名
apache[1:10].aaa.com 表示apache1.aaa.com到apache10.aaa.com这10台机器
nginx[a:z].aaa.com 表示nginxa.aaa.com到nginxz.aaa.com共26台机器
192.168.88.[161:165] 表示192.168.88.161到192.168.88.165这5台机器
案例2:
[nginx]
10.1.1.13:2222 表示10.1.1.13这台,但ssh端口为2222
案例3:定义10.1.1.13:2222这台服务器的别名为nginx1
nginx1 ansible_ssh_host=10.1.1.13 ansible_ssh_port=2222
案例4:没有做免密登录的服务器可以指定用户名与密码 => paramiko模块
nginx1 ansible_ssh_host=10.1.1.13 ansible_ssh_port=2222 ansible_ssh_user=root ansible_ssh_pass="123456"
注意:
nginx1别名
ansible_ssh_host:要连接的主机的IP地址
ansible_ssh_port:ssh对应的端口号
ansible_ssh_user:用户名
ansible_ssh_pass:密码
补充:
临时:ANSIBLE_HOST_KEY_CHECKING=False ansible -m ping nginx1
永久:ssh-keyscan -H 192.168.88.104 >> ~/.ssh/known_hosts
案例5:
nginx1 ansible_ssh_host=10.1.1.13 ansible_ssh_port=2222 ansible_ssh_user=root ansible_ssh_pass="123456"
nginx2 ansible_ssh_host=10.1.1.12 ansible_ssh_port=3333 ansible_ssh_user=root ansible_ssh_pass="123456"
[nginx]
nginx1
nginx2
小结:
/etc/ansible/hosts => 主机清单 => Ansible是通过主机清单实现服务器分组
主机清单的作用: 服务器分组
主机清单的常见功能:
① 可以通过IP范围来分, 主机名的范围来分
② 如果ssh端口不是22的,可以传入新的端口
③ 没有做免密登录,可以传密码
Ansible 模块(重点)
查看官方文档和手册
[root@ansible ~]# ansible-doc -l
amazon.aws.autoscaling_group >
amazon.aws.autoscaling_group_info >
amazon.aws.aws_az_info >
amazon.aws.aws_caller_info >
amazon.aws.cloudformation >
amazon.aws.cloudformation_info >
amazon.aws.cloudtrail >
amazon.aws.cloudtrail_info >
amazon.aws.cloudwatch_metric_alarm >
amazon.aws.cloudwatch_metric_alarm_info >
amazon.aws.cloudwatchevent_rule >
amazon.aws.cloudwatchlogs_log_group >
amazon.aws.cloudwatchlogs_log_group_info >
amazon.aws.cloudwatchlogs_log_group_metric_filter >
amazon.aws.ec2_ami >
amazon.aws.ec2_ami_info >
amazon.aws.ec2_eip >
amazon.aws.ec2_eip_info >
amazon.aws.ec2_eni >
amazon.aws.ec2_eni_info >
amazon.aws.ec2_instance >
amazon.aws.ec2_instance_info >
amazon.aws.ec2_key >
amazon.aws.ec2_metadata_facts >
...
...
...
ansible.builtin.add_host >
ansible.builtin.apt >
ansible.builtin.apt_key >
ansible.builtin.apt_repository >
ansible.builtin.assemble >
ansible.builtin.assert >
ansible.builtin.async_status >
ansible.builtin.blockinfile >
ansible.builtin.command >
ansible.builtin.copy >
ansible.builtin.cron >
ansible.builtin.debconf >
ansible.builtin.debug >
ansible.builtin.dnf >
ansible.builtin.dpkg_selections >
ansible.builtin.expect >
ansible.builtin.fail >
ansible.builtin.fetch >
ansible.builtin.file >
ansible.builtin.find >
ansible.builtin.gather_facts
...
...
...

如果要查看ping模块的用法,使用下面命令(其它模块以此类推)
[root@ansible ~]# ansible-doc ping
> ANSIBLE.BUILTIN.PING (/usr/lib/python3.9/site-packages/ansible/modules/pi>
A trivial test module, this module always returns `pong' on
successful contact. It does not make sense in playbooks, but
it is useful from `/usr/bin/ansible' to verify the ability to
login and that a usable Python is configured. This is NOT ICMP
ping, this is just a trivial test module that requires Python
on the remote-node. For Windows targets, use the
[ansible.windows.win_ping] module instead. For Network
targets, use the [ansible.netcommon.net_ping] module instead.
ADDED IN: historical
OPTIONS (= is mandatory):
- data
Data to return for the `ping' return value.
If this parameter is set to `crash', the module will cause an
exception.
default: pong
type: str
ATTRIBUTES:
:

官网模块文档:https://docs.ansible.com/ansible/2.9/modules/list*of*all_modules.html
中文文档:https://ansible-tran.readthedocs.io/en/latest/docs/modules.html
hostname 模块(掌握)
hostname 模块用于修改主机名(注意: 它不能修改/etc/hosts文件)
https://docs.ansible.com/ansible/latest/modules/hostname_module.html#hostname-module
将其中一远程机器主机名修改为 agent1.cluster.com
基本格式为:
ansible 操作的机器名或组名 -m 模块名 -a "参数1=值1 参数2=值2"
[root@ansible ~]# ansible -m hostname -a 'name=agent1.cluster.com' 192.168.88.101 # 根据实际情况来写 IP
-m:模块名称
-a:具体参数和参数值

[root@node1 ~]# hostname
node1
[root@node1 ~]# ifconfig
ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.88.101 netmask 255.255.255.0 broadcast 192.168.88.255
ether 00:50:56:37:56:ff txqueuelen 1000 (Ethernet)
RX packets 110712 bytes 147893696 (141.0 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 58777 bytes 3677032 (3.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@node1 ~]#
[root@ansible ~]# ansible -m hostname -a 'name=agent1.cluster.com' 192.168.88.101
192.168.88.101 | CHANGED => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "node1",
"ansible_hostname": "agent1",
"ansible_nodename": "agent1.cluster.com",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"name": "agent1.cluster.com"
}
[root@ansible ~]#
[root@node1 ~]# hostname
node1
[root@node1 ~]# ifconfig
ens160: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.88.101 netmask 255.255.255.0 broadcast 192.168.88.255
ether 00:50:56:37:56:ff txqueuelen 1000 (Ethernet)
RX packets 110712 bytes 147893696 (141.0 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 58777 bytes 3677032 (3.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@node1 ~]# hostname
agent1.cluster.com
[root@ansible ~]# ansible -m hostname -a 'name=agent1.cluster.com' 192.168.88.101
192.168.88.101 | SUCCESS => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "node1",
"ansible_hostname": "agent1",
"ansible_nodename": "agent1.cluster.com",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"name": "agent1.cluster.com"
}
[root@ansible ~]#
[root@node1 ~]# hostname
agent1.cluster.com
[root@node1 ~]# bash
[root@agent1 ~]# hostname
agent1.cluster.com
[root@agent1 ~]#
[root@ansible ~]# ansible -m hostname -a 'name=agent1.cluster.com' 192.168.88.102
192.168.88.102 | CHANGED => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "node2",
"ansible_hostname": "agent1",
"ansible_nodename": "agent1.cluster.com",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"name": "agent1.cluster.com"
}
[root@ansible ~]# ansible -m hostname -a 'name=agent1.cluster.com' 192.168.88.102
192.168.88.102 | SUCCESS => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "node2",
"ansible_hostname": "agent1",
"ansible_nodename": "agent1.cluster.com",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"name": "agent1.cluster.com"
}
[root@ansible ~]#


拓展:
[root@ansible ~]# ansible -m hostname -a 'name=node1' 192.168.88.101
192.168.88.101 | CHANGED => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "node1",
"ansible_hostname": "node1",
"ansible_nodename": "node1",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"name": "node1"
}
[root@ansible ~]# ansible -m hostname -a 'name=node2' 192.168.88.102
192.168.88.102 | CHANGED => {
"ansible_facts": {
"ansible_domain": "",
"ansible_fqdn": "node2",
"ansible_hostname": "node2",
"ansible_nodename": "node2",
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"name": "node2"
}
[root@ansible ~]# tail -n 6 /etc/ansible/hosts
[web_servers]
192.168.88.101
192.168.88.102
[db_servers]
192.168.88.103
[root@ansible ~]# ansible all -m shell -a 'hostname'
192.168.88.103 | CHANGED | rc=0 >>
node3
192.168.88.101 | CHANGED | rc=0 >>
node1
192.168.88.102 | CHANGED | rc=0 >>
node2
[root@ansible ~]# ansible web_servers -m shell -a 'hostname'
192.168.88.101 | CHANGED | rc=0 >>
node1
192.168.88.102 | CHANGED | rc=0 >>
node2
[root@ansible ~]#
扩展:
在实际工作中,主机名称必须采用FQDN格式
功能名称.公司域名
web01.itcast.cn
web02.itcast.cn
mysql.itcast.cn
小结:
hostname在Linux操作系统中主要用于(获取或修改主机名称)
在Ansible里面,hostname专门用于主机名称,注意:Linux主机名必须要满足(FQDN)格式
file 模块(重点)
作用:file 模块用于对文件相关的操作(创建, 删除, 软链接等)
https://docs.ansible.com/ansible/latest/modules/file_module.html#file-module
① path=文件或文件夹路径
② state=状态(touch文件、directory文件夹、absent删除、recurse递归)
案例1:创建一个目录
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test state=directory'
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test state=directory'
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"gid": 0,
"group": "root",
"mode": "0755",
"owner": "root",
"path": "/data/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 6,
"state": "directory",
"uid": 0
}
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"gid": 0,
"group": "root",
"mode": "0755",
"owner": "root",
"path": "/data/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 6,
"state": "directory",
"uid": 0
}
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test state=directory'
192.168.88.102 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"gid": 0,
"group": "root",
"mode": "0755",
"owner": "root",
"path": "/data/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 6,
"state": "directory",
"uid": 0
}
192.168.88.101 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"gid": 0,
"group": "root",
"mode": "0755",
"owner": "root",
"path": "/data/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 6,
"state": "directory",
"uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /data/test'
192.168.88.102 | CHANGED | rc=0 >>
192.168.88.101 | CHANGED | rc=0 >>
[root@ansible ~]# ansible web_servers -m shell -a 'ls -dl /data/test'
192.168.88.102 | CHANGED | rc=0 >>
drwxr-xr-x. 2 root root 6 Dec 16 17:32 /data/test
192.168.88.101 | CHANGED | rc=0 >>
drwxr-xr-x. 2 root root 6 Dec 16 17:32 /data/test
[root@ansible ~]#
案例2:创建一个文件
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test/test.txt state=touch'
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test/test.txt state=touch'
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"dest": "/data/test/test.txt",
"gid": 0,
"group": "root",
"mode": "0644",
"owner": "root",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 0,
"state": "file",
"uid": 0
}
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"dest": "/data/test/test.txt",
"gid": 0,
"group": "root",
"mode": "0644",
"owner": "root",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 0,
"state": "file",
"uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /data/test/test.txt' 192.168.88.102 | CHANGED | rc=0 >>
/data/test/test.txt
192.168.88.101 | CHANGED | rc=0 >>
/data/test/test.txt
[root@ansible ~]# ansible web_servers -m shell -a 'ls /data/test/'
192.168.88.102 | CHANGED | rc=0 >>
test.txt
192.168.88.101 | CHANGED | rc=0 >>
test.txt
[root@ansible ~]#
案例3:递归修改目录owner,group,mode(如果目录不存在则自动创建)
[root@ansible ~]# ansible web_servers -m file -a 'path=/test recurse=yes owner=bin group=daemon mode=0777'
[root@ansible ~]# ansible web_servers -m file -a 'path=/test recurse=yes owner=bin group=daemon mode=0777'
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"gid": 2,
"group": "daemon",
"mode": "0777",
"owner": "bin",
"path": "/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 6,
"state": "directory",
"uid": 1
}
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"gid": 2,
"group": "daemon",
"mode": "0777",
"owner": "bin",
"path": "/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 6,
"state": "directory",
"uid": 1
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls -dl /test'
192.168.88.102 | CHANGED | rc=0 >>
drwxrwxrwx. 2 bin daemon 6 Dec 16 17:44 /test
192.168.88.101 | CHANGED | rc=0 >>
drwxrwxrwx. 2 bin daemon 6 Dec 16 17:44 /test
[root@ansible ~]# ansible web_servers -m shell -a 'ls -dl /data/test/'
192.168.88.102 | CHANGED | rc=0 >>
drwxr-xr-x. 2 root root 22 Dec 16 17:36 /data/test/
192.168.88.101 | CHANGED | rc=0 >>
drwxr-xr-x. 2 root root 22 Dec 16 17:36 /data/test/
[root@ansible ~]# ansible web_servers -m shell -a 'stat /data/test/test.txt'
192.168.88.101 | CHANGED | rc=0 >>
File: /data/test/test.txt
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd00h/64768d Inode: 34109660 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:default_t:s0
Access: 2025-12-16 17:36:46.673872657 +0800
Modify: 2025-12-16 17:36:46.673872657 +0800
Change: 2025-12-16 17:36:46.673872657 +0800
Birth: 2025-12-16 17:36:46.668872731 +0800
192.168.88.102 | CHANGED | rc=0 >>
File: /data/test/test.txt
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd00h/64768d Inode: 50954259 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:default_t:s0
Access: 2025-12-16 17:36:46.698977326 +0800
Modify: 2025-12-16 17:36:46.698977326 +0800
Change: 2025-12-16 17:36:46.698977326 +0800
Birth: 2025-12-16 17:36:46.690977447 +0800
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test recurse=yes owner=bin group=daemon mode=0777'
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"gid": 2,
"group": "daemon",
"mode": "0777",
"owner": "bin",
"path": "/data/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 22,
"state": "directory",
"uid": 1
}
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"gid": 2,
"group": "daemon",
"mode": "0777",
"owner": "bin",
"path": "/data/test",
"secontext": "unconfined_u:object_r:default_t:s0",
"size": 22,
"state": "directory",
"uid": 1
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls -dl /data/test/'
192.168.88.102 | CHANGED | rc=0 >>
drwxrwxrwx. 2 bin daemon 22 Dec 16 17:36 /data/test/
192.168.88.101 | CHANGED | rc=0 >>
drwxrwxrwx. 2 bin daemon 22 Dec 16 17:36 /data/test/
[root@ansible ~]# ansible web_servers -m shell -a 'stat /data/test/test.txt'
192.168.88.102 | CHANGED | rc=0 >>
File: /data/test/test.txt
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd00h/64768d Inode: 50954259 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1/ bin) Gid: ( 2/ daemon)
Context: unconfined_u:object_r:default_t:s0
Access: 2025-12-16 17:36:46.698977326 +0800
Modify: 2025-12-16 17:36:46.698977326 +0800
Change: 2025-12-16 17:47:21.335372728 +0800
Birth: 2025-12-16 17:36:46.690977447 +0800
192.168.88.101 | CHANGED | rc=0 >>
File: /data/test/test.txt
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd00h/64768d Inode: 34109660 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1/ bin) Gid: ( 2/ daemon)
Context: unconfined_u:object_r:default_t:s0
Access: 2025-12-16 17:36:46.673872657 +0800
Modify: 2025-12-16 17:36:46.673872657 +0800
Change: 2025-12-16 17:47:21.335744877 +0800
Birth: 2025-12-16 17:36:46.668872731 +0800
[root@ansible ~]#
案例4:删除目录(连同目录里的所有文件)
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test state=absent'
[root@ansible ~]# ansible web_servers -m shell -a 'ls -dl /data/test/'
192.168.88.101 | CHANGED | rc=0 >>
drwxrwxrwx. 2 bin daemon 22 Dec 16 17:36 /data/test/
192.168.88.102 | CHANGED | rc=0 >>
drwxrwxrwx. 2 bin daemon 22 Dec 16 17:36 /data/test/
[root@ansible ~]# ansible web_servers -m shell -a 'ls /data/test/'
192.168.88.102 | CHANGED | rc=0 >>
test.txt
192.168.88.101 | CHANGED | rc=0 >>
test.txt
[root@ansible ~]# ansible web_servers -m file -a 'path=/data/test state=absent'
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"path": "/data/test",
"state": "absent"
}
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"path": "/data/test",
"state": "absent"
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls -dl /data/test/'
192.168.88.101 | FAILED | rc=2 >>
ls: cannot access '/data/test/': No such file or directorynon-zero return code
192.168.88.102 | FAILED | rc=2 >>
ls: cannot access '/data/test/': No such file or directorynon-zero return code
[root@ansible ~]#
案例5:创建文件并指定owner,group,mode等
[root@ansible ~]# ansible web_servers -m file -a 'path=/tmp/file.txt state=touch owner=bin group=daemon mode=0777'
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/file.txt'
192.168.88.101 | FAILED | rc=2 >>
ls: cannot access '/tmp/file.txt': No such file or directorynon-zero return code
192.168.88.102 | FAILED | rc=2 >>
ls: cannot access '/tmp/file.txt': No such file or directorynon-zero return code
[root@ansible ~]# ansible web_servers -m file -a 'path=/tmp/file.txt state=touch owner=bin group=daemon mode=0777'
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"dest": "/tmp/file.txt",
"gid": 2,
"group": "daemon",
"mode": "0777",
"owner": "bin",
"secontext": "unconfined_u:object_r:user_tmp_t:s0",
"size": 0,
"state": "file",
"uid": 1
}
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"dest": "/tmp/file.txt",
"gid": 2,
"group": "daemon",
"mode": "0777",
"owner": "bin",
"secontext": "unconfined_u:object_r:user_tmp_t:s0",
"size": 0,
"state": "file",
"uid": 1
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/file.txt' 192.168.88.101 | CHANGED | rc=0 >>
/tmp/file.txt
192.168.88.102 | CHANGED | rc=0 >>
/tmp/file.txt
[root@ansible ~]# ansible web_servers -m shell -a 'stat /tmp/file.txt'
192.168.88.101 | CHANGED | rc=0 >>
File: /tmp/file.txt
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd00h/64768d Inode: 17485866 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1/ bin) Gid: ( 2/ daemon)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2025-12-16 17:52:43.200130528 +0800
Modify: 2025-12-16 17:52:43.200130528 +0800
Change: 2025-12-16 17:52:43.200130528 +0800
Birth: 2025-12-16 17:52:43.194130614 +0800
192.168.88.102 | CHANGED | rc=0 >>
File: /tmp/file.txt
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: fd00h/64768d Inode: 16785805 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 1/ bin) Gid: ( 2/ daemon)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2025-12-16 17:52:43.193501722 +0800
Modify: 2025-12-16 17:52:43.193501722 +0800
Change: 2025-12-16 17:52:43.193501722 +0800
Birth: 2025-12-16 17:52:43.187501812 +0800
[root@ansible ~]# ansible web_servers -m shell -a 'ls -l /tmp' 192.168.88.101 | CHANGED | rc=0 >>
total 0
drwx------. 2 root root 56 Mar 27 18:04 ansible_ansible.legacy.command_payload_qfkjgoq_
-rwxrwxrwx. 1 bin daemon 0 Mar 27 18:03 file.txt
192.168.88.102 | CHANGED | rc=0 >>
total 0
drwx------. 2 root root 56 Mar 27 18:04 ansible_ansible.legacy.command_payload_w01airm7
-rwxrwxrwx. 1 bin daemon 0 Mar 27 18:03 file.txt
[root@ansible ~]#
案例6:删除文件
[root@ansible ~]# ansible web_servers -m file -a 'path=/tmp/file.txt state=absent'
[root@ansible ~]# ansible web_servers -m file -a 'path=/tmp/file.txt state=absent'
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"path": "/tmp/file.txt",
"state": "absent"
}
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"path": "/tmp/file.txt",
"state": "absent"
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/file.txt'
192.168.88.102 | FAILED | rc=2 >>
ls: cannot access '/tmp/file.txt': No such file or directorynon-zero return code
192.168.88.101 | FAILED | rc=2 >>
ls: cannot access '/tmp/file.txt': No such file or directorynon-zero return code
[root@ansible ~]# ansible web_servers -m shell -a 'ls -l /tmp'
192.168.88.101 | CHANGED | rc=0 >>
total 0
drwx------. 2 root root 56 Mar 27 18:05 ansible_ansible.legacy.command_payload_zoyqdt8j
192.168.88.102 | CHANGED | rc=0 >>
total 0
drwx------. 2 root root 56 Mar 27 18:05 ansible_ansible.legacy.command_payload_om1ezza3
[root@ansible ~]#
案例7:创建软链接文件
[root@ansible ~]# ansible web_servers -m file -a 'src=/etc/fstab path=/tmp/fstab state=link'
参数说明
src:源文件
path:快捷方式路径
state=link:代表创建软链接
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/fstab'
192.168.88.102 | CHANGED | rc=0 >>
#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
192.168.88.101 | CHANGED | rc=0 >>
#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/fstab'
192.168.88.101 | FAILED | rc=1 >>
cat: /tmp/fstab: No such file or directorynon-zero return code
192.168.88.102 | FAILED | rc=1 >>
cat: /tmp/fstab: No such file or directorynon-zero return code
[root@ansible ~]# ansible web_servers -m file -a 'src=/etc/fstab path=/tmp/fstab state=link'
192.168.88.101 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"dest": "/tmp/fstab",
"gid": 0,
"group": "root",
"mode": "0777",
"owner": "root",
"secontext": "unconfined_u:object_r:user_tmp_t:s0",
"size": 10,
"src": "/etc/fstab",
"state": "link",
"uid": 0
}
192.168.88.102 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": true,
"dest": "/tmp/fstab",
"gid": 0,
"group": "root",
"mode": "0777",
"owner": "root",
"secontext": "unconfined_u:object_r:user_tmp_t:s0",
"size": 10,
"src": "/etc/fstab",
"state": "link",
"uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/fstab'
192.168.88.102 | CHANGED | rc=0 >>
#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
192.168.88.101 | CHANGED | rc=0 >>
#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
[root@ansible ~]# ansible web_servers -m shell -a 'ls -l /tmp' 192.168.88.102 | CHANGED | rc=0 >>
total 0
drwx------. 2 root root 56 Mar 27 18:07 ansible_ansible.legacy.command_payload_idlb9_ez
lrwxrwxrwx. 1 root root 10 Mar 27 18:06 fstab -> /etc/fstab
192.168.88.101 | CHANGED | rc=0 >>
total 0
drwx------. 2 root root 56 Mar 27 18:07 ansible_ansible.legacy.command_payload_tahiise6
lrwxrwxrwx. 1 root root 10 Mar 27 18:06 fstab -> /etc/fstab
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/fstab'
192.168.88.101 | CHANGED | rc=0 >>
#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
192.168.88.102 | CHANGED | rc=0 >>
#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
[root@ansible ~]#
小结:
Ansible中与文件管理相关的模块为(-m file)
基于file模块,我们可以创建文件、目录、软连接,还可以删除文件
Ansible 批量操作(拓展)
为目标主机批量创建文件
ansible web_servers -b -m shell -a 'echo "Web Server" > /tmp/web.txt && chmod 777 /tmp/web.txt'
参数含义(对应你要求的格式)
-
web_servers→ inventory 里定义的组,只对这些主机操作 -
-b→ 提升到 root(sudo) -
-m shell→ 使用 shell 模块 -
-a '...'→ 模块参数,这里直接写 Bash 命令 -
echo "Web Server" > /tmp/web.txt创建/覆盖文件并写入内容chmod 777 /tmp/web.txt设权限 0777
[root@ansible ~]# ansible web_servers -m shell -a 'stat /tmp/web.txt'
192.168.88.101 | FAILED | rc=1 >>
stat: cannot statx '/tmp/web.txt': No such file or directorynon-zero return code
192.168.88.102 | FAILED | rc=1 >>
stat: cannot statx '/tmp/web.txt': No such file or directorynon-zero return code
[root@ansible ~]# ansible web_servers -b -m shell -a 'echo "Web Server" > /tmp/web.txt && chmod 777 /tmp/web.txt'
192.168.88.102 | CHANGED | rc=0 >>
192.168.88.101 | CHANGED | rc=0 >>
[root@ansible ~]# ansible web_servers -m shell -a 'stat /tmp/web.txt'
192.168.88.101 | CHANGED | rc=0 >>
File: /tmp/web.txt
Size: 11 Blocks: 8 IO Block: 4096 regular file
Device: fd00h/64768d Inode: 17485883 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2025-12-16 17:59:56.722901811 +0800
Modify: 2025-12-16 17:59:56.722901811 +0800
Change: 2025-12-16 17:59:56.724901782 +0800
Birth: 2025-12-16 17:59:56.722901811 +0800
192.168.88.102 | CHANGED | rc=0 >>
File: /tmp/web.txt
Size: 11 Blocks: 8 IO Block: 4096 regular file
Device: fd00h/64768d Inode: 16785806 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Context: unconfined_u:object_r:user_tmp_t:s0
Access: 2025-12-16 17:59:56.695090443 +0800
Modify: 2025-12-16 17:59:56.695090443 +0800
Change: 2025-12-16 17:59:56.697090414 +0800
Birth: 2025-12-16 17:59:56.695090443 +0800
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/web.txt'
192.168.88.101 | CHANGED | rc=0 >>
Web Server
192.168.88.102 | CHANGED | rc=0 >>
Web Server
[root@ansible ~]#



批量配置 DNS 解析
一条命令,直接用 copy 模块把内容写过去即可(/etc/resolv.conf直接覆盖):

ansible all -b -m copy -a 'dest=/etc/resolv.conf content="nameserver 192.168.88.2\nnameserver 114.114.114.114\n" mode=0644 owner=root group=root'

跑完验证:
ansible all -m shell -a 'cat /etc/resolv.conf'
ansible all -m shell -a 'ping -c 2 www.163.com'

另一种方式
ansible web_servers -m copy -a 'src=/etc/resolv.conf dest=/etc/resolv.conf force=yes'
ansible web_servers -m shell -a 'ping -c 2 www.163.com'
或者
ansible all -m copy -a 'src=/etc/resolv.conf dest=/etc/resolv.conf force=yes'
ansible all -m shell -a 'ping -c 2 www.163.com'
[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/resolv.conf dest=/etc/resolv.conf force=yes'
192.168.88.101 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"checksum": "cf0ae0fe855d205828d80a5125fd97b1f2391903",
"dest": "/etc/resolv.conf",
"gid": 0,
"group": "root",
"mode": "0644",
"owner": "root",
"path": "/etc/resolv.conf",
"size": 51,
"state": "file",
"uid": 0
}
192.168.88.102 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"checksum": "cf0ae0fe855d205828d80a5125fd97b1f2391903",
"dest": "/etc/resolv.conf",
"gid": 0,
"group": "root",
"mode": "0644",
"owner": "root",
"path": "/etc/resolv.conf",
"size": 51,
"state": "file",
"uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'ping -c 2 www.163.com'
192.168.88.102 | CHANGED | rc=0 >>
PING www.163.com.w.kunluncan.com (111.7.88.243) 56(84) bytes of data.
64 bytes from 111.7.88.243 (111.7.88.243): icmp_seq=1 ttl=128 time=5.96 ms
64 bytes from 111.7.88.243 (111.7.88.243): icmp_seq=2 ttl=128 time=6.83 ms
--- www.163.com.w.kunluncan.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 5.959/6.393/6.828/0.434 ms
192.168.88.101 | CHANGED | rc=0 >>
PING www.163.com.w.kunluncan.com (111.7.88.237) 56(84) bytes of data.
64 bytes from 111.7.88.237 (111.7.88.237): icmp_seq=1 ttl=128 time=5.98 ms
64 bytes from 111.7.88.237 (111.7.88.237): icmp_seq=2 ttl=128 time=5.99 ms
--- www.163.com.w.kunluncan.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 5.981/5.987/5.993/0.006 ms
[root@ansible ~]#


用 shell 模块一条命令搞定批量操作(最原始、最通用)
所有主机:先装 httpd(没有就装,有就跳过),再用 systemctl 启动并设开机自启
ansible all -b -m shell -a 'yum install httpd -y && systemctl enable httpd --now'
解释:
shell模块直接调/bin/sh -c '...'&&保证“装完再启”;如果 httpd 已装,yum 会返回 0,仍然继续启服务。




web_servers:写一个/tmp/web.txt测试文件
ansible web_servers -b -m shell -a 'cat > /tmp/web.txt <<EOF
Ansible test
2026/3/28
EOF'

ansible web_servers -b -m shell -a 'cat > /tmp/web.txt <<EOF
Ansible test
2026/5/27
EOF'

db_servers:写一个 MySQL 配置文件
ansible db_servers -b -m shell -a 'cat > /tmp/db.cnf <<EOF
# Generated by Ansible
[mysqld]
max_connections = 500
innodb_buffer_pool_size = 256M
EOF'

注意:有时候使用 shell 模块对接的命令要用双引号包裹起来!
正确操作:
ansible all -b -m shell -a "cat > /tmp/fstab <<EOF
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
EOF"
错误操作:
ansible all -b -m shell -a 'cat > /tmp/fstab <<EOF
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root / xfs defaults 0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot xfs defaults 0 0
/dev/mapper/cs-swap none swap defaults 0 0
EOF'
验证
ansible web_servers -m shell -a 'cat /tmp/web.txt'
ansible db_servers -m shell -a 'cat /tmp/db.cnf'
ansible all -m shell -a 'systemctl is-active httpd'
ansible all -m shell -a 'systemctl status httpd'






2026/3/27 作业题
- 实现Nginx服务HA高可用,对接后端业务
- 后端MySQL的主从架构升级
- Ansible安装与配置
2026/3/28 面试题
- 说说Keepalived实现VIP漂移的原理
- 商城后端MySQL的主从架构升级是怎样实现的?
- Ansible有哪些优势?它的三大核心组件是哪些?