ithuang
ithuang
发布于 2026-03-08 / 3 阅读
0

企业级自动化配置工具之 Ansible 基础

企业级自动化配置工具之 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 基础1.jpeg

企业级自动化配置工具之 Ansible 基础2.png

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.100Ansible管理机
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 基础3.png

注意:如果下载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 ~]#

企业级自动化配置工具之 Ansible 基础4.png

企业级自动化配置工具之 Ansible 基础5.png

服务器分组(重点理解)

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 模块(重点)

查看官方文档和手册

https://www.ansible.com/

[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    
...
...
...

企业级自动化配置工具之 Ansible 基础6.png

如果要查看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:
:

企业级自动化配置工具之 Ansible 基础7.png

官网模块文档: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:具体参数和参数值

企业级自动化配置工具之 Ansible 基础8.png

[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 ~]#

企业级自动化配置工具之 Ansible 基础9.png

企业级自动化配置工具之 Ansible 基础10.png

拓展:

[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 ~]#

企业级自动化配置工具之 Ansible 基础11.png

企业级自动化配置工具之 Ansible 基础12.png

企业级自动化配置工具之 Ansible 基础13.png

批量配置 DNS 解析

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

企业级自动化配置工具之 Ansible 基础14.png

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 基础15.png

跑完验证:

ansible all -m shell -a 'cat /etc/resolv.conf'
ansible all -m shell -a 'ping -c 2 www.163.com'

企业级自动化配置工具之 Ansible 基础16.png

另一种方式

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 ~]#

企业级自动化配置工具之 Ansible 基础17.png

企业级自动化配置工具之 Ansible 基础18.png

用 shell 模块一条命令搞定批量操作(最原始、最通用)

所有主机:先装 httpd(没有就装,有就跳过),再用 systemctl 启动并设开机自启

ansible all -b -m shell -a 'yum install httpd -y && systemctl enable httpd --now'

解释:

  • shell 模块直接调 /bin/sh -c '...'
  • && 保证“装完再启”;如果 httpd 已装,yum 会返回 0,仍然继续启服务。

企业级自动化配置工具之 Ansible 基础19.png

企业级自动化配置工具之 Ansible 基础20.png

企业级自动化配置工具之 Ansible 基础21.png

企业级自动化配置工具之 Ansible 基础22.png

web_servers:写一个/tmp/web.txt测试文件

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

企业级自动化配置工具之 Ansible 基础23.png

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

企业级自动化配置工具之 Ansible 基础24.png

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'

企业级自动化配置工具之 Ansible 基础25.png

注意:有时候使用 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'

企业级自动化配置工具之 Ansible 基础26.png

企业级自动化配置工具之 Ansible 基础27.png

企业级自动化配置工具之 Ansible 基础28.png

企业级自动化配置工具之 Ansible 基础29.png

企业级自动化配置工具之 Ansible 基础30.png

企业级自动化配置工具之 Ansible 基础31.png

2026/3/27 作业题

  1. 实现Nginx服务HA高可用,对接后端业务
  2. 后端MySQL的主从架构升级
  3. Ansible安装与配置

2026/3/28 面试题

  1. 说说Keepalived实现VIP漂移的原理
  2. 商城后端MySQL的主从架构升级是怎样实现的?
  3. Ansible有哪些优势?它的三大核心组件是哪些?