Ansible模块速查指南:高效定位与实战技巧

Ansible 自带了模块搜索、列表、详情查询的命令,靠关键词就能快速定位

场景 命令示例 优点
记得关键词(key/ssh) `ansible-doc -l grep -i key`
完全忘关键词 `ansible-doc -l less`
找到后验证用法 ansible-doc authorized_key 看参数 / 示例,避免用错

查看指定模块的完整帮助(比如file模块) ansible-doc file ​ # 只看模块的参数说明(精简版) ansible-doc -s file

帮助手册的组成

Ansible 所有模块的帮助手册(ansible-doc 模块名 查看)都遵循统一结构,按优先级拆解如下:

1. 基础信息区(手册顶部)
  • 模块名称 + 简介 :比如 file -- Manage files and file properties(模块名 + 核心用途);

  • 官方分类 :比如 Category: files(属于文件管理类模块);

  • 版本信息 :比如 Version added: historical(模块首次加入的版本)。

2. 参数说明区(核心部分,占比 90%)

这是最需要重点看的部分,每个参数的说明都包含以下核心字段(按需出现):

字段名 作用 常见值 / 示例
参数行(=/-) 标识参数是否必选:= 参数名 → 必选;- 参数名 → 可选 = path(必选)、- recurse(可选)
描述文本 一句话说明参数的核心作用 Path to the file being managed.
aliases 参数别名(简化记忆,多个别名等效) [dest, name]
type 参数值的类型(传值必须匹配,否则报错) path/bool/str/list/int
default 可选参数的默认值(省略该参数时的默认行为) false/""/[]
required 显式标识是否必选(和 =/− 等效,部分手册会补充) true/false
choices 参数的可选值(只能传这些值,否则报错) [file, directory, link, absent](file 模块 state 参数)
added in 参数新增的版本(低版本 Ansible 不支持该参数) version 1.1 of ansible-core
适用条件文本 参数生效的前置条件(比如依赖其他参数的取值) This applies only whenstate' is set to directory'.
notes 参数的注意事项(比如 "该参数仅对 Linux 生效,Windows 不支持") -
3. 示例区(EXAMPLES)

手册末尾会有 EXAMPLES 板块,提供该模块的高频实操示例(比如 file 模块的创建目录、修改权限、创建软链等),是新手最易上手的部分。

4. 返回值区(RETURN)

说明模块执行后会返回哪些数据(比如 file 模块会返回 path(路径)、state(状态)、mode(权限)等),适合编写 Playbook 时获取模块执行结果。

怎么看帮助手册

查看 ansible-doc 输出的核心逻辑是:先明确模块能做什么 → 再看必选参数(=)→ 再看可选参数的默认值/用法约束 → 最后参考示例验证。

核心查看逻辑(按优先级)

先看 "必选参数" :所有 = 开头的参数必须传,先确认这些参数的作用和类型(比如 file 模块的path是必选,必须指定要管理的路径);

再看 "可选参数" 的默认值 :比如recurse默认false,如果需要递归管理目录,必须手动设为true

看参数的适用条件 :比如recurse仅对state=directory生效,若管理的是文件(state=file),传recurse=true也无效;

看参数别名 :比如 file 模块的path可以用dest替代,Playbook 里写dest=/tmp/test更符合直觉;

参考示例区 :如果不知道参数怎么组合使用,直接抄示例改路径 / 值即可(比如创建递归目录的示例:- name: Create directory Recursively file: path=/tmp/testdir state=directory recurse=yes)。

打开ansible-doc file后,进入交互模式,可通过以下快捷键高效浏览:

  • /参数名:搜索指定参数(比如输入/recurse快速定位到该参数);

  • 上下箭头:逐行滚动;

  • q:退出帮助手册。

命令行调用模块

**ansible -a MODULE_ARGS -m MODULE_NAME -i INVENTORY
ansible <主机组/单个IP> -m <模块> -a <模块参数>

Ansible 常用命令行选项说明

--version # 显示版本
-m module # 指定模块,默认为command
-v # 显示详细过程,-vv/-vvv可显示更详细内容
--list-hosts # 显示主机列表,可简写为--list

-k, --ask-pass # 提示输入SSH连接密码(默认使用密钥验证)
-u, --user=REMOTE_USER # 指定远程执行任务的用户,默认用户为root
-b, --become # 替代旧版的sudo切换(提权执行)
--become-user=USERNAME # 指定sudo的runas用户,默认用户为root
-K, --ask-become-pass # 提示输入sudo操作的口令
​**

#先在受控节点sudo授权

复制代码
[root@node1 ~]# vim /etc/sudoers
hehe    ALL=(ALL)   NOPASSWD: ALL
[root@node1 ~]# echo redhat | passwd  hehe  --stdin

更改用户 hehe 的密码 。

passwd:所有的身份验证令牌已经成功更新。

#以hehe用户连接,并利用sudo使用hehe用户提权,执行cat /etc/shadow

复制代码
[root@rhce ~]# ansible 192.168.223.151  -m shell -a 'cat /etc/shadow'  -u hehe  -k -b
SSH password:
192.168.223.151 | CHANGED | rc=0 >>
root:$6$M0deCMG.nnloaq5p$4K6bB7qtDosTRKwKwP.uC5wFq5TAqmsEajYN6pE2s06X11zhPJTlwmSi0.fQPXdfei6YvcVNTWYjgTAFe6HDj.::0:99999:7:::
bin:*:19347:0:99999:7:::
daemon:*:19347:0:99999:7:::

复制代码
[root@node1 ~]# vim /etc/sudoers
hehe    ALL=(ALL)   ALL
[root@node1 ~]# echo redhat | passwd  hehe  --stdin

更改用户 hehe 的密码 。

passwd:所有的身份验证令牌已经成功更新。

复制代码
[root@rhce ~]# ansible 192.168.223.151  -m shell -a 'cat /etc/shadow'  -u hehe  -k -b  -K
SSH password:
BECOME password[defaults to SSH password]:
192.168.223.151 | CHANGED | rc=0 >>
root:$6$M0deCMG.nnloaq5p$4K6bB7qtDosTRKwKwP.uC5wFq5TAqmsEajYN6pE2s06X11zhPJTlwmSi0.fQPXdfei6YvcVNTWYjgTAFe6HDj.::0:99999:7:::

ansible指令执行过程

控制节点解析命令 → 读主机清单 → 建并发进程 → SSH 连目标主机 → 传模块脚本 → 提权 / 检查后执行 → 收集结果 + 清理临时文件 → 输出最终结果

简化流程步骤 具体操作 对应完整执行流程的环节 关键实操补充(避坑 / 调试)
步骤 1:加载配置文件 读取 Ansible 全局配置(如/etc/ansible/ansible.cfg)、模块默认参数、用户自定义配置 完整流程「命令解析与参数校验」→ 加载 Ansible 全局配置子环节 ① 配置包含 SSH 端口、临时目录、默认模块等核心参数;② 若自定义ansible.cfg,优先级高于全局配置;③ 校验配置合法性(如临时目录是否可写)。
步骤 2:加载模块文件 从 Ansible 内置模块目录(如/usr/lib/python3.9/site-packages/ansible/modules/)读取指定模块(如ping/file)的核心逻辑 完整流程「模块传输与临时文件生成」→ 模块准备子环节 ① 模块多为 Python 脚本(command/shell为文本逻辑);② 校验模块参数(如file模块必传path,缺省则报错);③ 自定义模块需放在~/.ansible/modules/,优先加载。
步骤 3:生成临时 py 文件并传输 控制节点将模块脚本打包为临时 Python 文件(命名规则:ansible-tmp-xxxx/xxx.py),通过 SSH 传输到远程主机临时目录(默认~/.ansible/tmp/ 完整流程「模块传输与临时文件生成」核心环节 ① 临时文件命名含随机字符串,避免冲突;② 可通过ansible.cfgremote_tmp自定义远程临时目录;③ command/shell模块无需生成 py 文件,直接传输命令文本。
步骤 4:给文件添加执行权限 远程主机对临时文件执行chmod +x,确保脚本可运行 完整流程「模块执行」→ 执行前准备子环节 ① 仅给临时文件加执行权限,不修改系统文件;② 若远程主机无 Python(极少),自动适配为 Shell 脚本执行;③ 权限仅在执行阶段有效,执行后立即清理。
步骤 5:执行并返回结果 远程主机运行临时脚本,执行模块核心逻辑(如ping测连通、file创建目录),将结果(状态 / 返回码 / 输出)通过 SSH 传回控制节点 完整流程「模块执行」核心环节 ① 结果状态分 3 类:changed(有变更)/ok(无变更)/failed(执行失败);② 提权(-b)、检查模式(-C)在此环节生效;③ -v/-vv可查看执行的具体命令和返回日志。
步骤 6:删除临时文件 控制节点确认结果接收后,触发远程主机删除~/.ansible/tmp/下的临时脚本 完整流程「结果收集与清理」核心环节 ① 执行失败(如超时)可能残留临时文件,需手动清理:ansible 主机组 -m file -a 'path=~/.ansible/tmp/ state=absent' -b;② 调试模块时可禁用清理:ansible.cfg中设置keep_remote_files=yes;③ 清理操作不影响远程主机系统文件,仅删 Ansible 临时文件。
模块的历史发展

版本划分依据:

  • 早期版本:通常指 Ansible 1.x ~ 2.0 版本(发布时间 2012-2016 年),功能基础、模块数量少,还未引入 Collection 体系。

  • 新版本 :Ansible 2.10 + 版本(2020 年后)开始引入 Collection(集合)体系,后续版本号规则调整为 "Ansible 包版本"(比如你的 7.7.0),对应的核心引擎是ansible-core 2.14(2023 年左右发布的版本),属于近年的主流新版本,支持 Collection、更多模块和企业级功能。、

    说明:之前用到的 ansible.posix.authorized_key 模块,其实就是调用了 ansible.posix 这个 Collection 里的 authorized_key 模块 ------ 这就是 Collection 体系下的模块调用方式(命名空间.集合名.模块名)。

    Collection 出现前的问题(为什么需要它):

    早期 Ansible(2.10 之前)的模块是 "零散存放" 的:

    • 官方模块直接放在核心代码里,第三方模块(比如某厂商的设备管理模块)只能手动下载、放在指定目录,容易丢、难更新;

    • 不同模块的版本无法单独管理(比如想升级 "Docker 相关模块",只能升级整个 Ansible);

    • 模块命名容易冲突(比如不同厂商都有叫server的模块)。

    Collection 到底是什么?

    Collection 是一个 "资源包",每个 Collection 里会打包对应场景下的所有相关资源

    每个 Collection 都有唯一的 "身份标识":命名空间.集合名(比如 ansible.posixcommunity.docker):

    • 命名空间 :区分 "归属方"(比如 ansible是官方、community是社区、cisco是厂商);

    • 集合名 :区分 "功能场景"(比如 posix是 POSIX 系统工具、docker是 Docker 管理工具)

    比如:

    • ansible.posix:官方维护的 "POSIX 系统(Linux/Unix)工具集合",包含你用过的authorized_key模块;

    • community.docker:社区维护的 "Docker 管理工具集合",包含docker_container(管理 Docker 容器)等模块;

    • cisco.aci:Cisco 厂商维护的 "ACI 网络设备管理集合",包含 Cisco 设备的配置模块。

    历史扩展:

    UNIX 是 1969 年由贝尔实验室的肯・汤普森、丹尼斯・里奇发的一个多用户、多任务分时操作系统(不是'一台',是软件系统)------ 最初是为小型机开发的,后来因为它能同时支持多个用户、高效处理多任务,在学术界和企业界迅速流行(比如大学、科研机构用它做开发 / 计算)。

    但后来 UNIX 的版权被 AT&T(贝尔实验室的母公司)收回并闭源,同时开始对商用授权收费。此时有两类人群分别推动了'类 UNIX 系统'的诞生:

    1. 学术界(比如加州大学伯克利分校):基于早期 UNIX 的开源源码分支,开发出了BSD 系统(伯克利软件分发),这是最早的类 UNIX 系统之一,后来逐渐脱离 UNIX 版权约束,成为独立的开源类 UNIX 系统;

    2. 普通用户 / 开发者:因为 UNIX 闭源收费,同时当时个人电脑没有好用的多任务系统,1991 年林纳斯・托瓦兹(大学生)基于'教学用类 UNIX 系统 Minix'的启发,开发出了Linux 内核------ 后来社区把 Linux 内核和各种开源工具(比如 GNU 的命令行工具)组合起来,形成了各种 Linux 发行版(比如 Ubuntu、SUSE、RHEL、CentOS)。

    这些'功能、用法和 UNIX 高度相似,但不是官方 UNIX'的系统,就被统称为类 UNIX 系统------ 它不止包含 Linux 系(Ubuntu、SUSE 等都是 Linux 的发行版),还包括 BSD 系(比如 FreeBSD)、商业类 UNIX(比如 Solaris)等。

    为了解决这个问题,IEEE(电气和电子工程师协会)制定了POSIX 标准(全称'可移植操作系统接口')------ 它是一套'操作系统接口的统一标准',规定了类 UNIX 系统必须遵守的接口规则(比如系统调用、命令格式、权限逻辑)。

    只要类 UNIX 系统符合 POSIX 标准,开发者写的软件(比如 Ansible),不用修改就能直接在所有符合 POSIX 的类 UNIX 系统上运行 ------ 这也是 Ansible 能'用同一个模块管理 Ubuntu 和 RHEL'的核心原因(这两个系统都符合 POSIX)。"

2015年底270多个模块,2016年达到540个,2018年01月12日有1378个模块,2018年07月15日1852个模块,2019年05月25日(ansible 2.7.10)时2080个模块,2020年03月02日有3387个模块

早期模块以基础系统运维功能为主,分类简洁:

  1. 文件与权限管理

    • 模块:file(创建文件 / 目录、修改权限 / 属主)、copy(复制文件)、unarchive(解压)
  2. 用户与组管理

    • 模块:user(创建 / 修改用户)、group(创建 / 修改用户组)、authorized_key(配置 SSH 密钥)
  3. 软件包管理

    • 模块:yum(RHEL/CentOS)、apt(Debian/Ubuntu)、pip(Python 包)
  4. 系统服务管理

    • 模块:service(管理 sysvinit/systemd 服务)
  5. 命令与脚本执行

    • 模块:command(简单命令)、shell(支持 Shell 语法)、script(执行本地脚本)
  6. 网络与安全管理

    • 模块:iptables(传统防火墙)、nmcli(网络接口配置)
  7. 磁盘与存储管理

    • 模块:mount(挂载文件系统)、parted(磁盘分区)
  8. 配置与定时任务管理

    • 模块:lineinfile(修改单行配置)、cron(系统定时任务)

当前 Ansible 模块覆盖全运维场景,包含核心内置模块 + 第三方 Collections 模块,分类如下:

  1. 文件与权限管理

    • 核心模块:file(文件 / 目录、权限 / 属主)、copy(复制文件 / 内容)、template(模板渲染)、unarchive(解压)、get_url(下载文件)、synchronize(rsync 同步)
  2. 用户与身份管理

    • 核心模块:user(用户创建 / 修改)、group(用户组)、authorized_key(SSH 密钥)、seuser(SELinux 用户)
  3. 软件包管理

    • 系统包:yum/dnf(RHEL/CentOS 8+)、apt(Debian/Ubuntu)、package(自动适配系统包管理器)

    • 语言包:pip(Python)、gem(Ruby)、npm(Node.js)、go(Go)

  4. 系统服务与进程管理

    • 服务管理:systemd(systemd 服务)、service(兼容 sysvinit)、supervisorctl(Supervisor 进程)

    • 进程操作:ps(查询进程)、kill(终止进程)

  5. 命令与脚本执行

    • 模块:command(简单命令)、shell(支持 Shell 语法)、script(执行本地脚本)、raw(无 Python 依赖的命令)
  6. 网络与安全管理

    • 网络配置:nmcli(网络接口)、route(路由)、dnsmasq(DNS 服务)

    • 安全加固:firewalld(动态防火墙)、ufw(Ubuntu 防火墙)、selinux(SELinux 状态)、auditd(审计规则)

  7. 磁盘与存储管理

    • 模块:mount(挂载)、parted(磁盘分区)、lvg(逻辑卷组)、lvol(逻辑卷)、filesystem(创建文件系统)
  8. 配置文件管理

    • 模块:lineinfile(修改单行配置)、replace(文本替换)、ini_file(INI 文件)、xml(XML 文件)、json_file(JSON 文件)
  9. 数据存储管理

    • 数据库:mysql_db(MySQL/MariaDB)、postgresql_db(PostgreSQL)、mongodb(MongoDB)、redis(Redis)

    • 存储服务:nfs_server(NFS 服务)、gluster_volume(GlusterFS)

  10. 定时任务管理

    • 模块:cron(系统定时任务)、at(一次性任务)
  11. 云资源管理(Collections)

    • 公有云:amazon.aws.ec2_instance(AWS EC2)、azure.azcollection.azure_vm(Azure VM)、aliyun.alicloud.aliyun_ecs(阿里云 ECS)

    • 私有云:openstack.cloud.server(OpenStack)、vmware.vmware_rest.vcenter_vm(VMware)

  12. 容器与虚拟化管理

    • 容器:community.docker.docker_container(Docker)、containers.podman.podman_container(Podman)、kubernetes.core.k8s(Kubernetes)

    • 虚拟化:libvirt.libvirt.vm(KVM)、vmware.vmware_rest.vcenter_vm(VMware)

  13. 源码与版本控制

    • 模块:git(Git 仓库)、svn(SVN)、hg(Mercurial)
  14. 工具与辅助模块

    • 模块:wait_for(等待端口 / 文件)、debug(调试输出)、pause(暂停任务)、assert(断言检查)、set_fact(设置变量)
  15. 消息与通知

    • 模块:slack(Slack 通知)、email(邮件)、telegram(Telegram)、wechat_work(企业微信)

command 模块

  1. 参数格式与执行范围command 模块接收「命令名 + 空格分隔的参数列表」作为输入,指定的命令会在所有选中的远程节点上执行。

  2. 核心限制(无 Shell 解析):该模块执行的命令

    不会经过远程节点的 Shell 解释器处理,因此以下内容均无法生效:

    • 环境变量(如 $HOSTNAME);

    • Shell 特殊操作符(如 "*" 通配符、</> 重定向、| 管道、;/& 多命令拼接)。

  3. 功能替代方案 :若需使用上述 Shell 特性,应替换为 [ansible.builtin.shell] 模块。

  4. 优化任务可读性:对于使用空格分隔参数导致可读性差的command任务,可通过两种方式传参:

    • 使用 args 任务关键字;

    • 使用 cmd 参数(推荐)。

  5. 必选参数要求 :必须指定「自由格式命令」或 cmd 参数二者其一(具体用法见示例)。

说明:如果能用对应模块解决问题的,尽量就不要用command或者shell,因为对应的模块会更安全,且模块会具有幂等性特点

========== 示例:command模块用法 ==========

  • name: 将motd文件内容保存到已注册的变量中

ansible.builtin.command: cat /etc/motd

register: mymotd # 注册任务结果到mymotd变量

自由格式(字符串)参数,所有参数写在同一行

  • name: 仅当/path/to/database文件不存在时执行命令(不使用'args'参数)

ansible.builtin.command: /usr/bin/make_database.sh db_user db_name creates=/path/to/database

自由格式(字符串)参数,部分参数通过'args'关键字分行书写

'args'是任务关键字,需与模块写在同一层级

  • name: 仅当/path/to/database不存在时执行命令(使用'args'关键字)

ansible.builtin.command: /usr/bin/make_database.sh db_user db_name

args:

creates: /path/to/database # 幂等参数:文件不存在则执行命令

'cmd'是模块参数(推荐的规范写法)

  • name: 仅当/path/to/database不存在时执行命令(使用'cmd'参数)

ansible.builtin.command:

cmd: /usr/bin/make_database.sh db_user db_name

creates: /path/to/database

  • name: 切换工作目录到somedir/并以db_owner用户身份执行命令

ansible.builtin.command: /usr/bin/make_database.sh db_user db_name

become: yes # 开启提权

become_user: db_owner # 切换到db_owner用户执行

args:

chdir: somedir/ # 执行命令前切换工作目录

creates: /path/to/database

argv(列表)参数,每个参数分行书写,无需使用'args'关键字

'argv'是模块参数,需相对于模块名缩进一层

  • name: 使用'argv'以列表形式传递命令------将'command'字段留空

ansible.builtin.command:

argv:

  • /usr/bin/make_database.sh

  • Username with whitespace # 含空格的用户名(argv自动处理空格问题)

  • dbname with whitespace # 含空格的数据库名

creates: /path/to/database

实例:

复制代码
[root@ansible ~]#ansible websrvs -m command -a 'chdir=/etc cat centos-release'
10.0.0.7 | CHANGED | rc=0 >>
CentOS Linux release 7.7.1908 (Core)
10.0.0.8 | CHANGED | rc=0 >>
CentOS Linux release 8.1.1911 (Core)
[root@ansible ~]#ansible websrvs -m command -a 'chdir=/etc creates=/data/f1.txt 
cat centos-release'
10.0.0.7 | CHANGED | rc=0 >>
CentOS Linux release 7.7.1908 (Core)
10.0.0.8 | SUCCESS | rc=0 >>
skipped, since /data/f1.txt exists
[root@rhce ~]# ansible  newserver  -m command -a 'creates=/data/hehe mkdir /data  ' -i /hosts
192.168.223.152 | CHANGED | rc=0 >>
​
192.168.223.151 | CHANGED | rc=0 >>
​
[root@rhce ~]# ansible  newserver  -m command -a 'creates=/data/hehe mkdir /data  ' -i /hosts 
192.168.223.151 | FAILED | rc=1 >>
mkdir: 无法创建目录 "/data": 文件已存在non-zero return code
192.168.223.152 | FAILED | rc=1 >>
mkdir: 无法创建目录 "/data": 文件已存在non-zero return code
[root@rhce ~]# 
[root@rhce ~]# ansible  newserver  -m command -a 'creates=/data/ mkdir /data  ' -i /hosts
192.168.223.152 | SUCCESS | rc=0 >>
skipped, since /data/ existsDid not run command since '/data/' exists
192.168.223.151 | SUCCESS | rc=0 >>
skipped, since /data/ existsDid not run command since '/data/' exists
​
[root@ansible ~]#ansible websrvs -m command -a 'chdir=/etc removes=/data/f1.txt 
cat centos-release'
10.0.0.7 | SUCCESS | rc=0 >>
skipped, since /data/f1.txt does not exist
10.0.0.8 | CHANGED | rc=0 >>
CentOS Linux release 8.1.1911 (Core)

习题 1:基础用法 + chdir 辅助参数(切换目录执行命令)

  • 要求 :在 newserver 主机上,切换到 /etc 目录后读取 redhat-release 文件内容(指定 inventory 文件 /hosts,提权执行)。

  • 正确命令

    ansible newserver -m command -a 'chdir=/etc cat redhat-release' -i /hosts -b

  • 核心知识点

chdircommand模块的辅助参数,作用是「执行命令前切换到指定目录」,属于高频运维场景(避免写绝对路径)。

在 Ansible 中,模块参数和 指令(命令内容)是完全不同的东西:

  • 模块参数:是 Ansible 模块(比如这里的command模块)的 "配置项",由 Ansible 本身解析、处理,作用是控制模块的执行逻辑(比如执行指令前切换目录、判断文件是否存在等)。

    比如chdir=/etccommand模块的专属参数,功能是 "在执行指令前,先切换到/etc目录"。

  • 指令内容 :是模块要在目标主机上执行的 "实际命令",由目标主机的系统(比如 Linux)解析、执行,作用是完成具体操作(比如这里的cat redhat-release是读取文件内容)。

  • 注意点:chdir仅改变命令执行的目录,不能单独使用(必须搭配具体命令,否则报no command given错误)。

扩展1:Ansible 会先解析这模块参数

提问:为什么'cat redhat-release chdir=/etc'这个反这些也能够看到/etc目录下的redhat-realse

因为 chdir=/etccommand 模块的参数,而非命令的一部分 ------Ansible 会先解析这个参数,在执行 cat redhat-release 命令前,自动切换到 /etc 目录 ,所以此时 cat 读取的是 /etc 目录下的 redhat-release 文件

扩展2:执行多个指令,用&&分隔

如果指令连着写,会被当做前一个指令的参数

如果两个指令由chdir参数分隔开,仅会执行前面的指令,也没有完成目的:

如果要执行多个指令就用&&分隔:

复制代码
[root@rhce ~]# ansible newserver -m shell -a 'chdir=/etc touch /root/hehel ; ls /root ; cat redhat-release' -i /hosts
​
[root@rhce ~]# ansible newserver -m shell -a 'chdir=/etc touch /root/hehel && ls /root && cat redhat-release' -i /hosts
192.168.223.151 | CHANGED | rc=0 >>
anaconda-ks.cfg
epel-release-latest-9.noarch.rpm
hehe
hehel
Red Hat Enterprise Linux release 9.3 (Plow)
192.168.223.152 | CHANGED | rc=0 >>
anaconda-ks.cfg
epel-release-latest-9.noarch.rpm
hehe
hehel
Red Hat Enterprise Linux release 9.3 (Plow)
[root@rhce ~]# cat banben.yml
---
- hosts: newserver
  tasks:
    - name: 执行touch/ls/cat命令(chdir=/etc)
      ansible.builtin.shell:
        cmd: touch /root/hehel && ls /root && cat redhat-release
        chdir: /etc
​

习题 2:验证「不支持 Shell 特殊语法」(管道 /&&/ 重定向)

  • 要求 :尝试用 command 模块执行 cat /etc/passwd | grep root(过滤 root 用户),观察结果并分析。

  • 错误命令

    #command 不支持管道 `、重定向`、`$VARNAME`、`;`、`&` 等 Shell 语法

    ansible websrvs -m command -a 'service vsftpd start'

    ansible websrvs -m command -a 'echo magedu |passwd --stdin wang'

    ansible websrvs -m command -a 'rm -rf /data/'

    ansible websrvs -m command -a 'echo hello > /data/hello.log'

    ansible websrvs -m command -a "echo $HOSTNAME"

  • 结果 :执行失败,提示 No such file or directory(系统把 |grep root 当成文件名)。

  • 核心知识点command模块直接调用系统命令,不经过 Shell 解析,因此不支持管道、&&、重定向(>/>>)等 Shell 特殊语法。

  • 注意点 需用shell模块替代command实现此类需求。

习题 3:creates 参数实现幂等性(文件存在则跳过执行)

  • 要求 :仅当 /tmp/test.txt 不存在时,在 newserver 主机上执行 touch /tmp/test.txt(避免重复创建文件)。

  • 正确命令

ansible newserver -m command -a 'creates=/test.txt touch /test.txt' -i /hosts

核心知识点

  • creates是高频幂等性参数:指定文件若存在,则跳过命令执行(Ansible 核心特性,避免重复操作)。

  • 注意点

    对应还有removes参数(指定文件若存在则执行命令,常用于清理文件)。

shell 模块

  1. 参数格式要求shell 模块接收 "命令名 + 空格分隔的参数列表" 作为输入,必须指定「自由格式命令」或 cmd 参数(示例见前文),二者任选其一。

  2. command 模块的核心差异 :该模块与 [ansible.builtin.command] 模块功能几乎完全一致,唯一区别是 shell 模块会通过远程节点的 /bin/sh shell 解释器执行命令(支持管道、变量替换等 shell 语法)。

========== 示例:shell模块用法 ==========

  • name: 在远程shell中执行命令;将标准输出重定向到指定文件

ansible.builtin.shell: somescript.sh >> somelog.txt

  • name: 执行命令前将工作目录切换到somedir/

ansible.builtin.shell: somescript.sh >> somelog.txt

args:

chdir: somedir/

也可以使用'args'格式来传递这些选项。

  • name: 该命令会先切换工作目录到somedir/,且仅在somelog.txt不存在时执行

ansible.builtin.shell: somescript.sh >> somelog.txt

args:

chdir: somedir/

creates: somelog.txt

也可以使用'cmd'参数替代自由格式写法。

  • name: 该命令会将工作目录切换到somedir/

ansible.builtin.shell:

cmd: ls -l | grep log

chdir: somedir/

习题 1:执行管道 && 组合命令
  • 要求 :在 newserver 主机上,过滤 /etc/passwd 中 root 用户,并将结果写入 /tmp/root_user.txt

  • 正确命令

    ansible newserver -m shell -a 'cat /etc/passwd | grep root > /tmp/root_user.txt' -i /hosts

  • 核心知识点

    shell模块通过/bin/sh(可指定executable=/bin/bash)解析命令,支持管道、重定向、&&等所有 Shell 语法。

  • 注意点

    • 注意:调用bash执行命令 类似 cat /tmp/test.md | awk -F'|' '{print 1,2}' &> /tmp/example.txt 这些复杂命令,即使使用shell也可能会失败,解决办法:写到脚本时,copy到远程,执行,再把需要的结果

      • 重定向写文件时,需确保目标目录有写入权限(必要时加-b提权)。

      • 拉回执行命令的机器

范例:将shell模块代替command,设为模块

复制代码
[root@ansible ~]#vim /etc/ansible/ansible.cfg
#修改下面一行
module_name = shell
习题2:环境变量
复制代码
[root@rhce ~]# ansible newserver -m command -a 'echo $HOSTNAME' -i /hosts -b
192.168.223.151 | CHANGED | rc=0 >>
$HOSTNAME
192.168.223.152 | CHANGED | rc=0 >>
$HOSTNAME
[root@rhce ~]# ansible newserver -m shell -a 'echo $HOSTNAME' -i /hosts -b
192.168.223.152 | CHANGED | rc=0 >>
node2
192.168.223.151 | CHANGED | rc=0 >>
node1
维度 command 模块 shell 模块
执行机制 直接调用远程主机的系统命令(如 echo),不经过 Shell(bash/sh)解析 先调用远程主机的 Shell(默认 /bin/sh/bash),由 Shell 预处理命令后,再执行系统命令
环境变量解析 不解析任何 Shell 环境变量(如 $HOSTNAME),$HOSTNAME 被当作普通字符串传给 echo,因此原样输出 $HOSTNAME Shell 先将 $HOSTNAME 解析为远程主机的实际主机名(node1/node2),再传给 echo,因此输出真实主机名
Shell 特性支持 不支持任何 Shell 解析特性(管道、重定向、&&/; 分隔符、流程控制等) 支持所有 Shell 解析特性(环境变量、管道、重定向、循环 / 条件判断等)
适用场景 仅执行 "纯系统命令 + 命令自身参数"(无 Shell 特性),更安全、轻量 执行需要 Shell 预处理的复杂命令(含变量、管道、逻辑分隔符)

可能出现的 "特殊情况"(command 看似解析了变量)

正常情况下 command 不解析变量,但以下场景会让 command 模块 "看似解析了环境变量",容易混淆:

1. 远程主机的 "父 Shell 进程" 提前解析了变量

Ansible 通过 SSH 连接远程主机时,SSH 会启动一个 "父 Shell 进程";若远程主机的系统级配置(如 /etc/bashrc//etc/profile)加载了环境变量,且父 Shell 进程强制解析了命令中的变量,command 模块的命令会被父 Shell 提前替换变量。

  • 示例:若把 export HOSTNAME=test 写入远程主机 /etc/bashrc,再执行 command -a 'echo $HOSTNAME',可能输出 test(而非 $HOSTNAME);

  • 原因:父 Shell 进程在传递命令给 command 模块前,已解析了 $HOSTNAME,掩盖了 command 模块 "不解析变量" 的本质。

2. 远程 SSH 配置强制所有命令通过 Shell 执行

若远程主机的 SSH 配置(/etc/ssh/sshd_config)设置了 ForceCommand /bin/bash,所有通过 SSH 执行的命令都会被 bash 解析 ------ 此时 command 模块的命令也会被 Shell 预处理,变量会被解析。

  • 解决:恢复 SSH 默认配置(注释 ForceCommand),重启 sshd 服务即可还原 command 模块的原生逻辑。
3. 老旧 Ansible 版本的兼容性问题

Ansible 2.0 之前的版本对 command 模块的处理不严格,部分场景下会间接调用 Shell 解析命令;而你使用的 Ansible 7.7.0(新版)严格区分 command/shell,因此能看到纯原生的执行结果(command 不解析变量)。

4. 命令是 "Shell 内置命令" 而非系统二进制命令

比如 cd/alias 是 Shell 内置命令(无对应的系统二进制文件),用 command 模块执行 cd /tmp 会失败(提示 "找不到命令"),而 shell 模块能正常执行 ------ 因为 command 只能调用系统二进制命令,Shell 内置命令必须依赖 Shell 解析。

script 模块

  1. 参数格式要求script 模块接收「脚本名称 + 空格分隔的参数列表」作为输入,必须指定「自由格式命令」或 cmd 参数(具体用法可参考示例)。

  2. 脚本执行流程:位于本地路径的脚本会先被传输到远程节点(受控主机),随后在远程节点上执行。

  3. 执行环境特性:指定的脚本会通过远程节点的 Shell 环境进行处理(支持 Shell 语法,如变量、管道、重定向等)。

  4. script优势

    • command/shell 执行脚本的前提:你必须先通过copy/synchronize等模块,把脚本传到远程节点,才能执行;

    • script 模块无需提前传脚本:直接指定主控节点的脚本路径,Ansible 会自动完成「传输→执行」全流程,少一步操作。

  5. script脚本执行流程

    • 主控节点 :读取 /root/init.sh 脚本内容;

    • 受控节点 :Ansible 自动创建临时目录(格式如 /tmp/ansible-tmp-随机字符串/,比如 /tmp/ansible-tmp-1752890123.456789/);

    • 传输 :把主控的 init.sh 传输到这个临时目录;

    • 执行:在受控节点的临时目录中执行该脚本(执行时会通过远程 Shell 解析);

    • 后续:执行完成后,临时目录和脚本默认不会自动删除(避免排查问题时无日志)。

    简单说:脚本的执行是 "临时目录一次性执行",不会自动放到受控节点的 /root/usr/bin 等常规目录。

  • name: 直接执行本地脚本(自动传输+执行)

ansible.builtin.script: /root/init.sh

  • name: 运行带参数的脚本(自由格式)

ansible.builtin.script: /some/local/script.sh --some-argument 1234

  • name: 运行带参数的脚本(使用'cmd'参数)

ansible.builtin.script:

cmd: /some/local/script.sh --some-argument 1234

  • name: 仅当远程节点上不存在file.txt时运行脚本

ansible.builtin.script: /some/local/create_file.sh --some-argument 1234

args:

creates: /the/created/file.txt # 受控节点上的文件,不存在则执行脚本

  • name: 仅当远程节点上存在file.txt时运行脚本

ansible.builtin.script: /some/local/remove_file.sh --some-argument 1234

args:

removes: /the/removed/file.txt # 受控节点上的文件,存在则执行脚本

  • name: script传含空格的参数(安全)

ansible.builtin.script:

argv:

  • /root/create_user.sh

  • Zhang San # 直接写空格,无需转义

习题 1:基础用法(本地简单脚本远程执行)

  • 要求 :将本地 /root/test.sh 脚本上传到 newserver 主机执行。

  • 本地脚本(/root/test.sh)

    复制代码
    [root@rhce ~]# vim test.sh 
    #不给test.sh加x权限,ansible照样可以执行
    [root@rhce ~]# cat test.sh 
    #!/bin/bash
    hostname -I
  • 正确命令

    复制代码
    [root@rhce ~]# ansible  newserver  -m script -a '/root/test.sh'   -i  /hosts 
    192.168.223.151 | CHANGED => {
        "changed": true,
        "rc": 0,
        "stderr": "Shared connection to 192.168.223.151 closed.\r\n",
        "stderr_lines": [
            "Shared connection to 192.168.223.151 closed."
        ],
        "stdout": "192.168.223.151 \r\n",
        "stdout_lines": [
            "192.168.223.151 "
        ]
    }
    .....

    #注意我用redhat用户执行服务器的脚本/root/test.sh

    #注意:script 模块的核心是 "传本地脚本到远程临时目录执行",全程不涉及远程 /root 目录的访问,因此 redhat 用户即使无权限访问远程 /root,也能成功执行;只有当你试图直接访问 / 执行远程 /root 下的文件时,才会触发权限失败。

    复制代码
    [root@rhce ~]# ansible  192.168.223.151   -m script -a '/root/test.sh' -u redhat -k   -i  /hosts 
    SSH password: 
    192.168.223.151 | CHANGED => {
        "changed": true,
        "rc": 0,
        "stderr": "Shared connection to 192.168.223.151 closed.\r\n",
        "stderr_lines": [
            "Shared connection to 192.168.223.151 closed."
        ],
        "stdout": "192.168.223.151 \r\n",
        "stdout_lines": [
            "192.168.223.151 "
        ]
    }
    复制代码
  • 核心知识点 模块自动将本地脚本上传到远程主机的临时目录,赋予执行权限后运行,无需手动传文件。

  • 注意点 本地脚本需有可读权限,远程主机需有执行脚本的权限(必要时提权)。

    复制代码
    [root@rhce ~]# chmod -r   /root/test.sh 
    [root@rhce ~]# ansible  192.168.223.151   -m script -a '/root/test.sh' -u redhat -k   -i  /hosts 
    SSH password: 
    An exception occurred during task execution. To see the full traceback, use -vvv. The error was: NoneType: None
    192.168.223.151 | FAILED! => {
        "changed": true,
        "msg": "non-zero return code",
        "rc": 126,   # rc=127:rc=127 是「文件不存在」,而 rc=126 是「文件存在但无法执行」;
        "stderr": "Shared connection to 192.168.223.151 closed.\r\n",
        "stderr_lines": [
            "Shared connection to 192.168.223.151 closed."
        ],
        "stdout": "/bin/bash: /home/redhat/.ansible/tmp/ansible-tmp-1765802630.2622228-2074-39475357481292/test.sh: 权限不够\r\n",
        "stdout_lines": [
            "/bin/bash: /home/redhat/.ansible/tmp/ansible-tmp-1765802630.2622228-2074-39475357481292/test.sh: 权限不够"
        ]

习题 2:脚本带参数执行

  • 要求 :本地 /root/create_file.sh 脚本接收一个参数(文件名),在远程主机 /tmp 目录创建该文件,执行时传入参数 test_param.txt

  • 本地脚本(/root/create_file.sh)

    复制代码
    #!/bin/bash
    touch /root/$1
  • 正确命令

    ansible newserver -m script -a '/root/create_file.sh test_param.txt' -i /hosts

  • 注意点 参数若包含空格 / 特殊字符,需用引号包裹(如'/root/create_file.sh "test file.txt"')。

习题 3:creates 参数避免重复执行脚本

  • 要求 :仅当远程主机 /tmp/init_done 文件不存在时,执行本地 /root/init.sh 脚本(避免重复初始化)。

  • 正确命令

    复制代码
    ansible newserver -m script -a '/root/init.sh creates=/tmp/init_done' -i /hosts 
  • 核心知识点 script模块也支持creates /removes参数,实现幂等性(脚本执行后可在末尾添加touch /tmp/init_done标记)。

  • 注意点 creates检测的是远程主机的文件,而非本地文件。

习题 4:chdir 参数切换远程执行目录

  • 要求 :执行本地 /root/write_file.sh 脚本时,先切换到远程主机 /etc 目录,脚本内容为写入当前目录的 test.conf 文件。

  • 本地脚本(/root/write_file.sh)

    #!/bin/bash

    echo "test config" > test.conf

  • 正确命令

    ansible newserver -m script -a '/root/write_file.sh chdir=/etc' -i /hosts -b

  • 核心知识点

chdir 参数让脚本在远程主机的指定目录执行,脚本中相对路径会基于该目录(最终/etc/test.conf会被创建)。

  • 注意点

    脚本中若用绝对路径,chdir不会影响;仅相对路径会生效。

总结:

command/shell/script 模块的幂等性分两层:

  1. 「指令层」:直接执行无判断的原生系统指令 / 脚本 → 无幂等性;

  2. 「模块层」:利用模块自带的 creates/removes 等参数约束执行时机 → 具备幂等性。

常用模块:

copy
  1. 核心功能copy 模块用于将文件从「本地机器」或「远程机器」复制到远程机器的指定位置。

  2. 反向文件复制的替代方案 :若需将文件从远程位置复制到本地机器,应使用 ansible.builtin.fetch 模块(copy 模块不支持该反向操作)。

  • name: 复制文件并设置所有者及权限

ansible.builtin.copy:

src: /srv/myfiles/foo.conf

dest: /etc/foo.conf ##如目标存在,默认覆盖,此处指定先备份

owner: foo

group: foo

mode: '0644'

  • name: 复制文件并设置所有者及权限(使用符号权限表示法)

ansible.builtin.copy:

src: /srv/myfiles/foo.conf

dest: /etc/foo.conf

owner: foo

group: foo

mode: u=rw,g=r,o=r

  • name: 将内联内容写入文件

ansible.builtin.copy:

content: '# This file was moved to /etc/other.conf'

dest: /etc/mine.conf

实例:

复制代码
[root@rhce ~]# ansible newserver -i /hosts   -m copy  -a "src=/etc/yum.repos.d/xixi.repo dest=/etc/yum.repos.d/xixi.repo "
192.168.223.151 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "checksum": "3f776e50a14ec2d5dc90ea945aa00ce5c344f941",
    "dest": "/etc/yum.repos.d/xixi.repo",
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "path": "/etc/yum.repos.d/xixi.repo",
    "secontext": "system_u:object_r:system_conf_t:s0",
    "size": 112,
    "state": "file",
    "uid": 0
}
​
yum_repository

核心功能与适用场景:在基于 RPM 的 Linux 发行版中,添加或删除 YUM 仓库。

  • name: 添加YUM仓库

ansible.builtin.yum_repository:

name: epel

description: EPEL YUM repo

baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/

  • name: 向同一个配置文件中添加多个仓库(1/2)

ansible.builtin.yum_repository:

name: epel

description: EPEL YUM repo

file: external_repos

baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/

gpgcheck: no

  • name: 向同一个配置文件中添加多个仓库(2/2)

ansible.builtin.yum_repository:

name: rpmforge

description: RPMforge YUM repo

file: external_repos

baseurl: http://apt.sw.be/redhat/el7/en/$basearch/rpmforge

mirrorlist: http://mirrorlist.repoforge.org/el7/mirrors-rpmforge

enabled: no

  • name: 从指定的仓库配置文件中移除仓库

ansible.builtin.yum_repository:

name: epel

file: external_repos

state: absent

实例:

复制代码
[root@rhce ~]# ansible newserver -i /hosts   -m yum_repository  -a 'name=epel description="EPEL YUM repo" file=xixi baseurl=https://mirrors.aliyun.com/epel/9/Everything/x86_64/ gpgcheck=no'  
192.168.223.152 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "repo": "epel",
    "state": "present"
}
​
mount

该模块管理 /etc/fstab 中已配置的挂载点及当前活跃的挂载点,隐含两层操作:

  • 管理「已配置的挂载点」:修改 /etc/fstab 中的挂载配置(如添加、删除、修改挂载项);

  • 管理「活跃的挂载点」:执行实际的挂载(mount)或卸载(umount)操作,让配置生效。

  • name: Mount DVD read-only

ansible.posix.mount:

path: /mnt/dvd

src: /dev/sr0

fstype: iso9660

opts: ro,noauto

state: present

实例:

复制代码
#写入文件/etc/fstab并且挂载
复制代码
[root@rhce ~]# ansible newserver -m mount -a 'path=/mnt   src=/dev/sr0  state=mounted fstype=iso9660 ' -i /hosts 
​
​
[root@rhce ~]# ansible newserver -m mount -a 'path=/mnt   src=/dev/sr0  state=present fstype=iso9660 ' -i /hosts 
​
​
[root@rhce ~]# ansible newserver -m mount -a 'path=/mnt   src=/dev/sr0  state=unmounted fstype=iso9660 ' -i /hosts 
​
​
[root@rhce ~]# ansible newserver -m mount -a 'path=/mnt   src=/dev/sr0  state=absent fstype=iso9660 ' -i /hosts 
​
​
[root@rhce ~]# ansible newserver -m mount -a 'path=/mnt   src=/dev/sr0  state=ephemeral fstype=iso9660 ' -i /hosts 
复制代码

补充:state参数

state 参数说明

state 是 Ansible 挂载管理模块的核心参数,用于定义存储设备的挂载状态,各取值的具体行为如下:

  1. mounted

    • 设备会被主动挂载,且在 /etc/fstab(文件系统表)中完成对应的持久化配置;

    • 若指定的挂载点不存在,会自动创建该挂载点。

  2. unmounted

    • 仅卸载设备,不会修改 /etc/fstab 中的挂载配置。
  3. present

    • 仅将设备的挂载配置写入 /etc/fstab,不会触发实际挂载操作,也不要求设备处于已挂载状态。
  4. ephemeral(该选项新增于 1.5.0 版本)

    • 仅临时挂载设备,不修改 /etc/fstab

    • 若设备已挂载,会触发重新挂载操作,该取值始终返回 changed=True

    • 若挂载点 path 已挂载其他设备(且源设备与 src 指定的不一致),模块会执行失败,避免意外卸载现有设备或覆盖挂载点;

    • 若挂载点不存在,会自动创建挂载点;

    • 完全忽略 /etc/fstab 的配置。

  5. absent

    • 将设备的挂载条目从 /etc/fstab 中移除;

    • 同时卸载该设备,并删除对应的挂载点。

  6. remounted(该选项新增于 2.9 版本)

    • 用于强制刷新设备的挂载状态,触发设备重新挂载,该取值始终返回 changed=True

    • 若设置了 opts(挂载选项),这些选项会应用于重新挂载操作,但不会修改 /etc/fstab

    • 若设置了 opts 且重新挂载命令执行失败,模块会抛出错误,防止挂载配置发生意外变更(建议使用 mounted 取值规避此问题);

    • 该取值要求挂载点已存在于 /etc/fstab 中;

    • 若需重新挂载未在 /etc/fstab 中注册的挂载点(尤其针对 BSD 系统节点),请改用 ephemeral 取值。

  7. absent_from_fstab

    • 仅将设备的挂载条目从 /etc/fstab 中移除;

    • 不会卸载设备,也不会删除对应的挂载点。

参数基础属性补充

  • 参数类型:字符串(str);

  • 可选值:absentabsent_from_fstabmountedpresentunmountedremounted

dnf

通过 dnf 包管理器执行软件包和软件组的安装、升级、移除及列出操作。

  • name: Install the latest version of Apache and MariaDB

ansible.builtin.dnf:

name:

  • httpd

  • mariadb-server

state: latest

  • name: Remove the Apache package

ansible.builtin.dnf:

name: httpd

state: absent

补充:

state 参数说明(分点翻译)

1. 参数核心作用

用于指定软件包的操作行为:安装(present/latest)或移除(absent)。

2. 可选值及含义
  • present:确保软件包已安装(默认行为,若已安装则不重复操作,不强制更新到最新版本);

  • latest:确保软件包已安装且为当前可用的最新版本;

  • absent:确保软件包已从系统中移除;

  • installed:与 present 功能完全等效(兼容写法);

  • removed:与 absent 功能完全等效(兼容写法)。

3. 默认值说明
  • 配置默认值为 null(无显式默认);

  • 实际生效逻辑:

    • 未启用模块的 autoremove 选项时,默认行为等同于 present

    • 启用 autoremove 选项时,默认行为等同于 absent

实例:

复制代码
[root@rhce ~]# ansible  newserver  -i /hosts   -m dnf  -a 'name=nginx  state=absent'
复制代码
systemd
  1. service 模块

    • 适合init 系统混合的环境(如同时管理 CentOS 6 和 CentOS 7 主机),兼容性强但功能有限;

    • 不推荐在纯 systemd 系统中使用(会浪费 systemd 的专属特性)。

  2. systemd 模块

    • 适合systemd 系统的环境(现在绝大多数生产环境都是),功能更全、状态更精准;

    • 不兼容传统 init 系统(如 CentOS 6),用在非 systemd 系统会直接报错。

  • name: 确保服务单元处于运行状态

ansible.builtin.systemd:

state: started

name: httpd

  • name: 若Debian系统上的cron服务正在运行,则停止该服务

ansible.builtin.systemd:

name: cron

state: stopped

  • name: 无论何种情况,重启CentOS系统上的crond服务,并执行daemon-reload以加载配置变更

ansible.builtin.systemd:

state: restarted

daemon_reload: true

name: crond

  • name: 无论何种情况,重新加载httpd服务配置

ansible.builtin.systemd:

name: httpd.service

state: reloaded

  • name: 启用httpd服务并确保其未被屏蔽

ansible.builtin.systemd:

name: httpd

enabled: true

masked: no

lineinfile
  1. 核心功能 该模块用于确保文件中存在某一特定行,或通过带反向引用的正则表达式替换文件中已存在的行。

  2. 核心适用场景此模块主要适用于「仅需修改文件中某一行内容」的场景。

  3. 其他场景的替代模块建议

    • 若需修改文件中多行相似内容 ,请使用 ansible.builtin.replace 模块;

    • 若需在文件中「插入 / 更新 / 删除一整块多行内容」,请查看 ansible.builtin.blockinfile 模块;

    • 对于上述之外的其他场景(如完整替换文件、基于模板生成文件),请使用 ansible.builtin.copyansible.builtin.template 模块。

ansible在使用sed进行替换时,经常会遇到需要转义的问题,而且ansible在遇到特殊符号进行替换时,存在问题,无法正常进行替换 。其实在ansible自身提供了两个模块:lineinfile模块和replace模块,可以方便的进行替换

一般在ansible当中去修改某个文件的单行进行替换的时候需要使用lineinfile模块regexp 参数 :使用正则表达式匹配对应的行,当替换文本时,如果有多行文本都能被匹配,则只有最后面被匹配到的那行文本才会被替换,当删除文本时,如果有多行文本都能被匹配,这么这些行都会被删除

如果想进行多行匹配进行替换需要使用 replace模块功能:相当于sed,可以修改文件内容

示例:

注:在2.3版本之前,使用选项'dest'、'destfile'或'name'替代'path'

  • name: 确保SELinux设置为强制模式

ansible.builtin.lineinfile:

path: /etc/selinux/config

regexp: '^SELINUX='

line: SELINUX=enforcing

  • name: 确保wheel用户组不在sudoers配置中

ansible.builtin.lineinfile:

path: /etc/sudoers

state: absent

regexp: '^%wheel'

  • name: 搜索字面字符串替换localhost条目(避免转义操作)

ansible.builtin.lineinfile:

path: /etc/hosts

search_string: '127.0.0.1'

line: 127.0.0.1 localhost

owner: root

group: root

mode: '0644'

  • name: 确保Apache默认端口为8080

ansible.builtin.lineinfile:

path: /etc/httpd/conf/httpd.conf

regexp: '^Listen '

insertafter: '^#Listen '

line: Listen 8080

  • name: 若文件不存在则创建并添加指定行(无需传递regexp参数)

ansible.builtin.lineinfile:

path: /tmp/testfile

line: 192.168.1.99 foo.lab.net foo

create: yes

注:因行中包含': ',需使用全引号包裹。详见YAML文档中的注意事项。

  • name: 保存前验证sudoers文件的合法性

ansible.builtin.lineinfile:

path: /etc/sudoers

state: present

regexp: '^%ADMIN ALL='

line: '%ADMIN ALL=(ALL) NOPASSWD: ALL'

validate: /usr/sbin/visudo -cf %s

Shell 命令行 中,反斜杠 \ 是「Shell 转义符」------ Shell 会先解析一次命令,若想让 Ansible / 正则引擎最终拿到 \s,必须写两个 \\(Shell 解析后会把 \\ 变成单个 \ 传给正则引擎)。

简单说:命令行里的 \\s = 正则引擎实际处理的 \s

符号 含义 区别(* vs +)
\s 空白字符(空格 / Tab) -
\d 数字(0-9) -
* 匹配 0 个或多个前置字符 比如 \s*:允许行首无缩进
+ 匹配 1 个或多个前置字符 比如 \s+:必须有至少 1 个空格
. 任意字符(除换行) 比如 .*:匹配所有附加参数
() 分组,可通过 \1 引用 用于保留原格式(如缩进)

实例:

复制代码
[root@rhce ~]# ansible newserver -i /hosts -m lineinfile -a "path=/etc/nginx/nginx.conf regexp='^(\s*)listen.*' line='\1listen 100;'"
192.168.223.152 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup": "",
    "changed": true,
    "msg": "line replaced"
}
[root@rhce ~]# ansible newserver -i /hosts  -m shell  -a 'grep "listen"  /etc/nginx/nginx.conf'
192.168.223.152 | CHANGED | rc=0 >>
        listen 99;
\1listen 100;
​
​
[root@rhce ~]# ansible newserver -i /hosts -u root -m lineinfile -a "path=/etc/nginx/nginx.conf regexp='^\s*listen 99;$' state=absent"
[root@rhce ~]# ansible newserver -i /hosts -u root -m replace -a "path=/etc/nginx/nginx.conf regexp='(\\\\\\1listen 100;\\s*)' replace='listen 90;\n'"
​
​
​
​
[root@rhce ~]# ansible newserver -i /hosts -u root -m lineinfile -a "path=/etc/nginx/nginx.conf regexp='^(\\s*)listen\\s.*;' line='\\\\1listen 89;'   backrefs=yes  backup=yes"
#backrefs=yes 是控制「正则分组反向引用生效」的核心参数

search_string参数:

search_string 是 lineinfile 模块中基于 "字面量字符串" 匹配文件内容 的参数(区别于 regexp 的正则匹配),用于精准查找固定字符串(特殊字符无需转义),并配合 state 参数实现 "替换 / 删除 / 添加" 行的操作。

核心特性:字面量匹配(关键区别于 regexp):

  • 匹配规则:严格按 "字符原样" 匹配,不会将 .*| 等视为正则符号;

  • 示例:若 search_string: "*.conf",只会匹配包含 *.conf 的行,而非正则中的 "任意.conf 文件"。

replace
  1. 核心功能

    该模块会替换文件中所有匹配指定模式(pattern)的内容实例。

  2. 幂等性要求

    操作的幂等性需由用户自行保障,核心原则是:确保所使用的匹配模式不会匹配到任何通过该模块完成的替换结果(避免因重复匹配导致多次替换,破坏幂等性)

复制代码
示例:
- name: replace模块实现字面量匹配(等价于search_string)
  ansible.builtin.replace:
    path: /etc/nginx/conf.d/test.conf
    # 用regex_escape转义后,*.conf会被当作字面量,而非正则通配符
    regexp: "{{ '*.conf' | regex_escape }}"
    replace: "all.conf"  # 替换后的内容
    backup: yes          # 可选:备份原文件
    
    
    
​
- name: 将旧主机名替换为新主机名(要求Ansible版本≥2.4)
  ansible.builtin.replace:
    path: /etc/hosts
    regexp: '(\s+)old\.host\.name(\s+.*)?$'
    replace: '\1new.host.name\2'
​
- name: 替换表达式之后直至文件末尾的内容(要求Ansible版本≥2.4)
  ansible.builtin.replace:
    path: /etc/apache2/sites-available/default.conf
    after: 'NameVirtualHost [*]'
    regexp: '^(.+)$'
    replace: '# \1'
​
- name: 替换表达式之前直至文件开头的内容(要求Ansible版本≥2.4)
  ansible.builtin.replace:
    path: /etc/apache2/sites-available/default.conf
    before: '# live site config'
    regexp: '^(.+)$'
    replace: '# \1'
​
# 在Ansible 2.7.10版本之前,同时使用before和after参数会产生与预期相反的效果(内容截断)
# 详情可参考:https://github.com/ansible/ansible/issues/31354。
- name: 替换两个表达式之间的内容(要求Ansible版本≥2.4)
  ansible.builtin.replace:
    path: /etc/hosts
    after: '<VirtualHost [*]>'
    before: '</VirtualHost>'
    regexp: '^(.+)$'
    replace: '# \1'
​
- name: 支持设置文件的通用属性
  ansible.builtin.replace:
    path: /home/jdoe/.ssh/known_hosts
    regexp: '^old\.host\.name[^\n]*\n'
    owner: jdoe
    group: jdoe
    mode: '0644'
​
- name: 支持配置验证命令
  ansible.builtin.replace:
    path: /etc/apache/ports
    regexp: '^(NameVirtualHost|Listen)\s+80\s*$'
    replace: '\1 127.0.0.1:8080'
    validate: '/usr/sbin/apache2ctl -f %s -t'
​
- name: 简短形式的任务(Ansible 2+版本中)需要使用反斜杠转义序列
  ansible.builtin.replace: path=/etc/hosts regexp='\\b(localhost)(\\d*)\\b' replace='\\1\\2.' # 内容截断
​
- name: 长格式任务则无需转义
  ansible.builtin.replace:
    path: /etc/hosts
    regexp: '\b(localhost)(\d*)\b'
    replace: '\1\2.localdomain\2 \1\2'
​
- name: 在替换内容中显式指定位置匹配分组
  ansible.builtin.replace:
    path: /etc/ssh/sshd_config
    regexp: '^(ListenAddress[ ]+)[^\n]+$'
    replace: '\g<1>0.0.0.0'
​
- name: 显式指定命名匹配分组
  ansible.builtin.replace:
    path: /etc/ssh/sshd_config
    regexp: '^(?P<dctv>ListenAddress[ ]+)(?P<host>[^\n]+)$'
    replace: '#\g<dctv>\g<host>\n\g<dctv>0.0.0.0'

参数说明(分点翻译)

1. path
  • 核心作用:指定需要修改的文件路径。

  • 历史版本兼容:在 Ansible 2.3 版本之前,该选项仅能使用 destdestfilename 作为参数名。

  • 别名:destdestfilename(这三个名称与 path 功能等效)。

  • 参数类型:路径类型(path)。

2. regexp
  • 核心作用:指定用于匹配文件内容的正则表达式。

  • 语法依据:遵循 Python 正则表达式语法,详情可参考:https://docs.python.org/3/library/re.html

  • 匹配模式:启用 MULTILINE(多行)模式,这意味着:

    • 正则符号 ^ 既匹配文件的开头,也匹配文件中 "每一行" 的开头;

    • 正则符号 $ 既匹配文件的结尾,也匹配文件中 "每一行" 的结尾。

  • 未启用模式:不启用 DOTALL 模式,这意味着特殊字符 . 会匹配 "除换行符(\n)之外" 的任意字符。

  • 常见误区:容易误认为像 [^#] 这样的否定字符集也会自动匹配不到换行符(实际不会)。

  • 换行符排除方法:若需在否定字符集中排除换行符,需将换行符显式添加到字符集中,例如 [^#\n]

  • 转义序列注意事项:自 Ansible 2.0 版本起,简短形式的任务中,所有转义序列都需额外添加反斜杠进行转义(即 \ 需写成 \\),以避免这些转义序列被解析为字符串字面量转义(详见示例)。

  • 参数类型:字符串类型(str)。

实例:

复制代码
[root@rhce ~]# ansible newserver -i /hosts -m replace -a "path=/etc/nginx/nginx.conf regexp='^(\s*)listen.*' replace='\1listen 99;'"
192.168.223.151 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "2 replacements made",
    "rc": 0
}
....
[root@rhce ~]# ansible newserver -i /hosts  -m shell  -a 'grep "^ *listen"  /etc/nginx/nginx.conf'
192.168.223.151 | CHANGED | rc=0 >>
        listen 99;
        listen 99;
192.168.223.152 | CHANGED | rc=0 >>
        listen 99;
        listen 99;
[root@rhce ~]# ansible  192.168.223.151  -m  replace  -a "path=/fstab regexp='^(UUID.*)' replace='\1'"
​
[root@rhce ~]# ansible 192.168.223.151   -m  replace  -a "path=/passwd  regexp='^([a-zA-Z].*)$'  replace='# \1'"
[root@rhce ~]# ansible  192.168.223.151  -m  replace  -a "path=/fstab regexp='^#(UUID.*)' replace='\1'"
​
复制代码
unarchive
  1. 核心功能 :该模块的作用是解压归档文件(如 .zip/.tar.gz/.tar.bz2 等);但明确限制 ------ 仅处理 "归档文件",无法解压「仅压缩但不含归档结构的单个文件」(比如纯 .gz 压缩的单个文本文件,而非归档包)。

  2. 默认执行逻辑:模块默认行为是「先将控制节点(本地)的源归档文件复制到目标远程主机,再在目标主机上执行解压操作」。

  3. 远程源文件解压配置 :若需解压「已存在于目标远程主机上」的归档文件,需设置参数 remote_src=yes(无需从本地复制文件)。

  4. 校验和验证的替代方案 :如果需要对归档文件做校验和(checksum)验证,不建议直接用该模块;应先通过 ansible.builtin.get_urlansible.builtin.uri 模块获取文件(自带校验和能力),再配合 remote_src=yesunarchive 解压。

  • name: 下载foo.conf文件

ansible.builtin.get_url:

url: http://example.com/path/file.conf # 待下载文件的远程URL

dest: /etc/foo.conf # 文件下载后在受控节点的保存路径

mode: '0440' # 下载后文件的权限设置为0440

get_url
  1. 核心功能:从 HTTP、HTTPS 或 FTP 协议地址下载文件至远程服务器。

  2. 网络访问要求 :远程服务器必须能够直接访问待下载的远程资源(即目标文件所在的网络地址)。

get_url 模块默认幂等:仅当「远程文件更新」或「本地文件不存在 / 校验和不一致」时才重新下载,避免重复操作。

#若 dest 指定的是目录(而非文件):需确保目录已存在(可先用 file 模块创建),模块会将文件保存到该目录,文件名沿用 URL 中的文件名;

示例:

  • name: 先创建目录

ansible.builtin.file:

path: /etc/foo

state: directory

  • name: 下载文件到目录(文件名沿用file.conf)

ansible.builtin.get_url:

url: http://example.com/path/file.conf

dest: /etc/foo/ # 结尾加/表示目录

file
  1. 核心功能 1:设置属性:设置文件、目录或符号链接(symlinks)及其目标对象的属性(如权限、所有者、所属组等)。

  2. 核心功能 2:删除操作:此外,可执行文件、符号链接或目录的删除操作。

示例:

  • name: 修改文件的所有者、所属组及权限

ansible.builtin.file:

path: /etc/foo.conf

owner: foo

group: foo

mode: '0644'

  • name: 为已有文件设置宽松权限(低安全级别)

ansible.builtin.file:

path: /work

owner: root

group: root

mode: '1777'

  • name: 创建符号链接

ansible.builtin.file:

src: /file/to/link/to

dest: /path/to/symlink

owner: foo

group: foo

state: link

  • name: 创建两个硬链接

ansible.builtin.file:

src: '/tmp/{{ item.src }}'

dest: '{{ item.dest }}'

state: hard

loop:

  • { src: x, dest: y }

  • { src: z, dest: k }

=====================

第一次循环:

模块参数最终为 src: /tmp/x、dest: y → 在远程主机的当前工作目录下,为 /tmp/x 创建名为 y 的硬链接;

第二次循环:

模块参数最终为 src: /tmp/z、dest: k → 在远程主机的当前工作目录下,为 /tmp/z 创建名为 k 的硬链接。

简单来说,loop 定义了两次操作的 "变量值对",src 和 dest 通过 {{ item.xxx }} 读取这些变量,

====================

  • name: 创建目录(若不存在则创建)

ansible.builtin.file:

path: /etc/some_directory

state: directory

mode: '0755'

  • name: 递归修改目录的所有者和所属组

ansible.builtin.file:

path: /etc/foo

state: directory

recurse: yes

owner: foo

group: foo

  • name: 删除文件

ansible.builtin.file:

path: /etc/foo.txt

state: absent

  • name: 递归删除目录

ansible.builtin.file:

path: /etc/foo

state: absent

firewalld
  • name: 允许默认区域的HTTPS服务流量

ansible.posix.firewalld:

service: https # 目标服务(HTTPS,对应默认443端口)

permanent: true # 配置永久生效(重启防火墙后不失效)

state: enabled # 启用该规则(允许流量)

  • name: 禁止默认区域的8081/TCP端口流量

ansible.posix.firewalld:

port: 8081/tcp # 目标端口(8081端口,TCP协议)

permanent: true # 配置永久生效

state: disabled # 禁用该规则(禁止流量)

immediate 参数说明

  • 核心作用 :当配置设为永久生效(permanent: true)时,是否立即将该配置应用到当前运行中的 firewalld 实例。

  • 默认值false(即使设置了 permanent: true,也不会立即生效,需通过 firewall-cmd --reload 或重启服务触发)。

  • 参数类型 :布尔值(bool,仅接受 true/false)。

补充说明

  • permanent: trueimmediate: true 时:配置会同时写入防火墙配置文件(持久化)和立即应用到运行时状态,无需额外执行重载命令;

  • permanent: false 时:该参数无效(临时配置默认立即生效)。

实例:

复制代码
[root@rhce ~]#  ansible newserver -i /hosts  -m firewalld  -a 'port=89/tcp   permanent=true  state=enabled  immediate=true'
​
效果等价于:
[root@rhce ~]# ansible newserver -i /hosts  -m firewalld  -a 'port=89/tcp   permanent=true  state=enabled'
[root@rhce ~]# ansible newserver -m shell -a "firewall-cmd --reload"  -i /hosts
​
selinux
复制代码
- name: Enable SELinux
  ansible.posix.selinux:
    policy: targeted
    state: enforcing
​
- name: Put SELinux in permissive mode, logging actions that would be blocked.
  ansible.posix.selinux:
    policy: targeted
    state: permissive
​
- name: Disable SELinux
  ansible.posix.selinux:
    state: disabled
​
数据库模块

Ansible 的 community.mysql 数据库模块本身设计了完善的幂等性机制,你遇到的 "第一次无密码登录成功、第二次需密码登录失败" 的问题,不是模块没有幂等性,而是未针对 "密码从无到有" 的场景做「动态登录适配」------ 只需在 Playbook 中增加 "判断数据库密码状态" 的逻辑,结合模块的幂等参数,就能实现多次执行不失败。

问题根源分析

  • 第一次执行:MySQL/MariaDB 初始状态 root 无密码,模块不填 login_password 能登录,执行设置密码 / 创建库等操作;

  • 第二次执行:数据库已设置 root 密码,但 Playbook 仍用 "无密码" 方式登录,模块连接失败;

  • 核心矛盾:登录参数未根据 "密码是否存在" 动态调整,而非模块本身的幂等性缺失。

复制代码
#登录受控节点(192.168.223.151),执行以下命令(CentOS/RHEL 系统):
# 安装Python3的MySQL库(现在主流是Python3)
yum install -y python3-PyMySQL
# 或用pip安装(如果没有yum包)
pip3 install PyMySQL

解决方案(完整幂等 Playbook)

以下 Playbook 实现核心逻辑:

  1. 先判断数据库 root 是否已设置密码;

  2. 动态适配登录参数(无密码 / 有密码);

  3. 利用模块幂等性参数,确保 "设置密码、创建库 / 用户" 仅执行一次。

复制代码
---
- name: 数据库密码初始化(幂等版,适配无密码→有密码)
  hosts: newserver  # 替换为自己的数据库服务器IP/组名
  become: yes        # 提权(操作数据库需要root)
  gather_facts: no 
  vars:
    db_root_pwd: "Redhat123!"  # 学生可自行修改目标密码
    db_user: "root"
    db_host: "localhost"
​
  tasks:
    # 步骤1:判断root是否有密码(核心:用简单命令测试)
    - name: 测试root无密码能否登录
      ansible.builtin.shell: 'mysql -u{{ db_user }} -S /var/lib/mysql/mysql.sock -e "show databases;" 2>/dev/null'
      register: no_pwd_login
      ignore_errors: yes  # 有密码时登录失败,忽略错误不中断
      changed_when: no    # 仅判断状态,不标记“变更”
​
    # 步骤2:仅无密码时,设置root密码(幂等:设过就不重复设)
    - name: 初始化root密码(仅第一次执行)
      community.mysql.mysql_user:
        name: "{{ db_user }}"
        host: "localhost"  # 简化:只设localhost的密码,去掉冗余host
        password: "{{ db_root_pwd }}"
        check_implicit_admin: yes  # 无密码时允许登录的关键参数
        login_unix_socket: /var/lib/mysql/mysql.sock
        login_user: "{{ db_user }}"
        login_host: "{{ db_host }}"
        login_password: ""  # 无密码时填空
        state: present      # 幂等核心:密码已设则跳过
      when: no_pwd_login.rc == 0  # 只有无密码登录成功时才执行
​
    # 步骤3:创建测试库(幂等:已存在则跳过,适配有/无密码登录)
    - name: 创建test_db数据库(多次执行不报错)
      community.mysql.mysql_db:
        name: test_db
        state: present  # 幂等:确保库存在,不存在才创建
        encoding: utf8mb4
        # 动态适配密码:有密码用db_root_pwd,无密码填空
        login_unix_socket: /var/lib/mysql/mysql.sock
        login_user: "{{ db_user }}"
        login_host: "{{ db_host }}"
        login_password: "{{ db_root_pwd }}"
​

完整版本:


  • name: 数据库部署(安装+启动+设密码+建luntan库)

hosts: newserver # 替换为服务器IP/组名

become: yes

gather_facts: no

vars:

db_root_pwd: "Redhat123!" # root密码

db_name: "luntan" # 要创建的数据库名

tasks:

1. 安装MariaDB+Python依赖(列表形式更易读)

  • name: 安装mariadb-server和Python依赖

ansible.builtin.dnf:

name:

  • mariadb-server

  • python3-PyMySQL

state: present

2. 启动MariaDB并开机自启

  • name: 启动mariadb服务

ansible.builtin.systemd:

name: mariadb

state: started

enabled: yes

3. 测试root无密码登录(简化命令,仅测连通性)

  • name: 测试root无密码登录

ansible.builtin.shell: 'mysql -uroot -S /var/lib/mysql/mysql.sock -e "SELECT 1" 2>/dev/null'

register: no_pwd_login

ignore_errors: yes # 【核心】登录失败时忽略错误,保证流程不中断

changed_when: no # 【核心】标记无变更,保证幂等性,多次执行仅判断状态不触发变更

4. 仅无密码时设root密码(删掉冲突的login_host)

  • name: 初始化root密码

community.mysql.mysql_user:

name: root

host: localhost

password: "{{ db_root_pwd }}"

check_implicit_admin: yes # 【核心】无密码登录时允许授权操作,是无密码设密码的关键参数

login_unix_socket: /var/lib/mysql/mysql.sock # 【核心】强制走socket认证,避免TCP连接密码校验失败

login_user: root

login_password: ""

state: present # 【核心】幂等核心,密码已设则跳过,保证多次执行不重复操作

when: no_pwd_login.rc == 0 # 【核心】仅无密码登录成功时执行,避免重复设密码

5. 创建luntan数据库(最简参数,无冗余)

  • name: 创建luntan数据库

community.mysql.mysql_db:

name: "{{ db_name }}"

state: present # 【核心】幂等:确保库存在,不存在才创建,多次执行无报错

encoding: utf8mb4

login_unix_socket: /var/lib/mysql/mysql.sock

login_user: root

login_password: "{{ db_root_pwd }}"

扩展:

  • gather_factsbecomePlay 级别通用参数(作用于整个 Play 的所有任务);

  • ignore_errorschanged_when任务级别通用参数(仅作用于单个任务);

check_implicit_admin

在 "初始化 root 密码" 的任务中,因为login_password: ""(无密码登录),如果不配置check_implicit_admin: yes,会触发 "权限不足" 错误,无法完成密码设置 ------ 这是无密码状态下给 root 设密码的必配参数

when: no_pwd_login.rc == 0

  1. when 类型 / 含义:Ansible 的核心关键字,专门用于定义 "任务是否执行" 的条件判断规则。深度拓展:当when后跟随的表达式结果为True时,当前 "初始化 root 密码" 的任务会执行;若表达式结果为False,该任务会被标记为skipped(跳过)。when支持 Python 风格的比较运算符(如==/!=/>/<)和逻辑运算符(如and/or/not),是实现 Ansible 任务条件执行的核心。

  2. no_pwd_login 类型 / 含义:前置 "测试 root 无密码能否登录" 任务的 "结果注册变量名"。深度拓展:这个变量来自前置任务里的register: no_pwd_login配置,Ansible 会把该 shell 任务的所有执行结果(包括返回码、命令输出、执行耗时、错误信息等)都存储在这个变量中,供后续任务调用,是 Ansible 跨任务传递执行结果的核心方式。

  3. .rc 类型 / 含义:no_pwd_login变量的子字段,全称是 return code(返回码 / 退出码),是 Linux 系统中判断命令执行结果的核心指标。深度拓展:Linux 系统通用规则为 ------rc == 0代表命令执行成功(对应场景:root 无密码登录 MySQL/MariaDB 成功);rc != 0(如 1、1045 等)代表命令执行失败(对应场景:root 无密码登录失败,说明已有密码)。相比解析命令输出内容,通过rc判断执行结果更稳定、不易出错。

  4. == 0 类型 / 含义:Python 风格的比较运算符,用于判定no_pwd_login.rc的取值是否等于 0。深度拓展:结合当前场景,该判定逻辑为 ------ 只有当 root 无密码登录成功(rc=0)时,才需要执行 "初始化 root 密码" 的任务;若rc≠0(root 已有密码,无密码登录失败),设密码操作无意义且可能触发 "密码已存在" 报错,因此会跳过该任务,这也是保证 Playbook 幂等性的关键。

ignore_errors: yes

  • 归属:任务级别通用参数(所有类型的 Ansible 任务都可配置);

  • 作用:仅作用于当前 的 shell 任务 ------ 当该任务执行失败(比如 root 已有密码导致登录失败、返回 rc≠0)时,Ansible 不会中断整个 Playbook 的执行流程,而是忽略该错误,继续运行后续的 "初始化 root 密码""创建数据库" 等任务;

  • 层级对比:和任务内的模块同级(属于单个任务的配置)。

changed_when: no

  • 归属:同样是任务级别通用参数;

  • 作用:仅作用于当前 的 shell 任务 ------ 强制将该任务的 "变更状态" 标记为no(即changed=False),无论该 shell 命令是否执行成功,Ansible 都会判定该任务 "无变更";(补充:shell 模块默认会把 "执行了命令" 判定为changed=True,但该任务只是 "只读测试"(没修改任何系统配置 / 数据),标记为no能保证 Playbook 的幂等性统计准确);

encoding: utf8mb4

  • MySQL/MariaDB 中默认的 "utf8" 编码实际是utf8mb3,仅支持部分 UTF-8 字符(无法存储 emoji、部分中文生僻字);

  • utf8mb4 是完整兼容 UTF-8 的编码格式,能覆盖所有 Unicode 字符(包括中文、emoji、特殊符号等),从根本上避免数据库存储中文内容时出现乱码、字符无法插入的问题。

创建luntan(论坛)类数据库时,存储的内容大概率包含中文(如帖子、用户名),配置encoding: utf8mb4避免中文乱码的核心配置,也是学习 / 生产环境中创建中文数据库的标准操作。

setup
  1. 该模块会被 Playbook 自动调用,用于收集远程主机的有用变量,这些变量可在 Playbook 中直接使用。

  2. 也可通过 /usr/bin/ansible 命令直接执行该模块,以查看某台主机可用的变量。

  3. Ansible 会自动提供许多关于系统的「事实信息(facts)」,这些 Facts 包含主机的硬件、系统配置、网络、环境变量等各类基础信息,是编写动态 Playbook(如条件判断、差异化配置)、调试节点信息的关键工具。

功能: setup 模块来收集主机的系统信息,这些 facts 信息可以直接以变量的形式使用,但是如果主机较多,会影响执行速度

可以使用 gather_facts: no 来禁止 Ansible 收集 facts 信息

ansible all -m setup 是基础用法,-a "filter=xxx" 用于过滤指定 Facts(避免输出全量冗余信息)

实战技巧:不用死记所有 Facts,记住「系统 / 硬件 / 网络」三大核心类的常用 Facts 即可,陌生 Facts 按需查询即可。

收集清单中所有节点的全量系统事实(Facts),含硬件、系统、网络等所有信息

ansible newserver -i /hosts -m setup

筛选收集所有节点的完整主机名(FQDN,包含域名)

ansible newserver -i /hosts -m setup -a "filter=ansible_nodename"

筛选收集所有节点的短主机名(不含域名)

ansible newserver -i /hosts -m setup -a "filter=ansible_hostname"

筛选收集所有节点的DNS域名

ansible newserver -i /hosts -m setup -a "filter=ansible_domain"

筛选收集所有节点的物理内存总容量(单位:MB)

ansible newserver -i /hosts -m setup -a "filter=ansible_memtotal_mb"

筛选收集所有节点的内存详细信息(物理内存+交换分区的总/闲/用容量)

ansible newserver -i /hosts -m setup -a "filter=ansible_memory_mb"

筛选收集所有节点的物理内存空闲容量(单位:MB)

ansible newserver -i /hosts -m setup -a "filter=ansible_memfree_mb"

筛选收集所有节点的操作系统家族(如RedHat/Debian/Suse)

ansible newserver -i /hosts -m setup -a "filter=ansible_os_family"

筛选收集所有节点的操作系统发行版主版本号(如7/8/20)

ansible newserver -i /hosts -m setup -a "filter=ansible_distribution_major_version"

筛选收集所有节点的操作系统发行版完整版本号(如7.9/20.04)

ansible newserver -i /hosts l -m setup -a "filter=ansible_distribution_version"

筛选收集所有节点的CPU逻辑核心数

ansible newserver -i /hosts -m setup -a "filter=ansible_processor_vcpus"

筛选收集所有节点的所有IPv4地址列表(不含回环地址)

ansible newserver -i /hosts -m setup -a "filter=ansible_all_ipv4_addresses"

筛选收集所有节点的CPU架构(如x86_64/aarch64)

ansible newserver -i /hosts -m setup -a "filter=ansible_architecture"

筛选收集所有节点的系统运行时长(单位:秒)

ansible newserver -i /hosts -m setup -a "filter=ansible_uptime_seconds"

筛选收集所有节点CPU相关的所有事实(*为通配符,匹配ansible_processor开头的所有项)

ansible newserver -i /hosts -m setup -a "filter=ansible_processor*"

筛选收集所有节点的系统环境变量(如PATH/HOME/USER等)

ansible newserver -i /hosts -m setup -a 'filter=ansible_env'

查看所有节点的全量Facts(输出内容多,建议重定向到文件查看)

ansible newserver -i /hosts -m setup > all_facts.txt

查看单个节点的全量Facts(更高效)

ansible 192.168.223.151 -m setup > single_facts.txt

查看所有CPU相关Facts

ansible newserver -i /hosts -m setup -a "filter=ansible_processor*"

查看所有网络相关Facts

ansible newserver -i /hosts -m setup -a "filter=ansible_*_ipv4*"

查看所有内存相关Facts

ansible newserver -i /hosts -m setup -a "filter=ansible_*mem*"

debug

  1. 该模块在 Playbook 执行过程中输出指定语句,可用于调试变量或表达式,且无需终止 Playbook 的执行。

  2. 该模块与when:指令配合使用时,可有效提升调试效率(是调试场景的常用组合)。

此模块可以用于输出信息,并且通过 msg 定制输出的信息内容

注意: msg后面的变量有时需要加 " " 引起来

范例: debug 模块默认输出Hello world

复制代码
[root@rhce ~]# ansible newserver -i   /hosts  -m debug
192.168.223.151 | SUCCESS => {
    "msg": "Hello world!"
}
192.168.223.152 | SUCCESS => {
    "msg": "Hello world!"
}
​

复制代码
[root@rhce ~]# vim debug.yml
[root@rhce ~]# cat debug.yml
---
- hosts: newserver
  tasks: 
    - name: output hello world
      debug:   
[root@rhce ~]# 
​
[root@rhce ~]# ansible-playbook   -i /hosts   debug.yml
​
PLAY [newserver] **********************************************************************************************************************************************************
​
TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [192.168.223.152]
ok: [192.168.223.151]
​
TASK [output hello world] *************************************************************************************************************************************************
ok: [192.168.223.151] => {
    "msg": "Hello world!"
}
ok: [192.168.223.152] => {
    "msg": "Hello world!"
}
​
PLAY RECAP ****************************************************************************************************************************************************************
192.168.223.151            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.223.152            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
​

范例: 显示字符串特定字符

复制代码
[root@rhce ~]# vim  debug.yml
[root@rhce ~]# ansible-playbook   -i /hosts   debug.yml
​
PLAY [newserver] **********************************************************************************************************************************************************
​
TASK [debug] **************************************************************************************************************************************************************
ok: [192.168.223.151] => {
    "msg": "3"
}
ok: [192.168.223.152] => {
    "msg": "3"
}
​
PLAY RECAP ****************************************************************************************************************************************************************
192.168.223.151            : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.223.152            : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
​
[root@rhce ~]# cat debug.yml 
---
- hosts: newserver
  gather_facts: no
  vars:
    a: "12345"  # 仅用半角空格缩进(2/4个,统一即可)
  tasks:
    - debug:     # 缩进对齐,冒号后加空格(可选,但规范)
        msg: "{{a[2]}}"  # 比debug多2个半角空格,无全角字符
复制代码

范例:调试 "仅当命令执行失败时输出信息",可结合register+when+debug(更贴近真实调试):

复制代码
[root@rhce ~]# vim debug.yml
[root@rhce ~]# cat debug.yml
---
- hosts: newserver
  gather_facts: no
  tasks:
    - name: 模拟执行命令并捕获结果
      shell: "ls /rooti"
      register: cmd_result
      ignore_errors: yes
​
    - name: 仅命令执行失败时输出调试信息
      debug:
        msg: "调试:命令执行失败!返回码={{ cmd_result.rc }},错误信息={{ cmd_result.stderr }}"
      when: cmd_result.rc != 0  # 命令返回码非0(失败)时触发调试输出
​
[root@rhce ~]# ansible-playbook   -i /hosts   debug.yml
​
PLAY [newserver] **********************************************************************************************************************************************************
​
TASK [模拟执行命令并捕获结果] *********************************************************************************************************************************************
fatal: [192.168.223.152]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "ls /rooti", "delta": "0:00:00.006434", "end": "2025-12-19 15:21:55.426362", "msg": "non-zero return code", "rc": 2, "start": "2025-12-19 15:21:55.419928", "stderr": "ls: 无法访问 '/rooti': 没有那个文件或目录", "stderr_lines": ["ls: 无法访问 '/rooti': 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
...ignoring
fatal: [192.168.223.151]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": true, "cmd": "ls /rooti", "delta": "0:00:00.006166", "end": "2025-12-19 15:21:55.429264", "msg": "non-zero return code", "rc": 2, "start": "2025-12-19 15:21:55.423098", "stderr": "ls: 无法访问 '/rooti': 没有那个文件或目录", "stderr_lines": ["ls: 无法访问 '/rooti': 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
...ignoring
​
TASK [仅命令执行失败时输出调试信息] ***************************************************************************************************************************************
ok: [192.168.223.151] => {
    "msg": "调试:命令执行失败!返回码=2,错误信息=ls: 无法访问 '/rooti': 没有那个文件或目录"
}
ok: [192.168.223.152] => {
    "msg": "调试:命令执行失败!返回码=2,错误信息=ls: 无法访问 '/rooti': 没有那个文件或目录"
}
​
PLAY RECAP ****************************************************************************************************************************************************************
192.168.223.151            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1   
192.168.223.152            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1   
​
​

范例:在 Playbook 中用 debug 模块输出 Facts 的值和类型,更贴近实战场景

复制代码
[root@rhce ~]# vim  debug.yml
[root@rhce ~]# cat debug.yml 
---
- hosts: newserver
  gather_facts: yes  # 自动收集Facts(默认开启)
  tasks:
    - name: 查看陌生Facts的具体值
      debug:
        var: ansible_bios_version  # 替换为要查询的Facts名称
​
[root@rhce ~]# ansible newserver -i /hosts  -m setup -a "filter=ansible_bios_version "
192.168.223.152 | SUCCESS => {
    "ansible_facts": {
        "ansible_bios_version": "6.00",
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false
}
192.168.223.151 | SUCCESS => {
    "ansible_facts": {
        "ansible_bios_version": "6.00",
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false
}
​

实验1:用ansible命令行实现nginx部署修改端口号

步骤:

1、写仓库 2、挂载 3、下载nginx 4、修改已存在的配置文件中的部分内容 5、重启配置生效 6、防火墙放行80号端口 7、SELinux设置为宽容模式

复制代码
1、写仓库
[root@rhce ~]# ansible newserver -i /hosts   -m copy  -a "src=/etc/yum.repos.d/xixi.repo dest=/etc/yum.repos.d/xixi.repo "
2、挂载
[root@rhce ~]# ansible newserver -m mount -a 'path=/mnt   src=/dev/sr0  state=mounted fstype=iso9660 ' -i /hosts 
​
3、下载nginx
[root@rhce ~]# ansible   newserver -i /hosts -m dnf  -a 'name=nginx  state=present'  
​
4、修改已存在的配置文件/etc/nginx.conf中的端口号
[root@rhce ~]# ansible newserver -i /hosts -u root -m lineinfile -a "path=/etc/nginx/nginx.conf regexp='^(\\s*)listen\\s.*;' line='\\1listen 89;'   backrefs=yes  backup=yes"
​
​
7、SELinux设置为宽容模式
[root@rhce ~]# ansible  newserver -i /hosts -m selinux  -a 'policy=targeted state=permissive'
​
5、重启配置生效
[root@rhce ~]# ansible newserver -i /hosts   -m  systemd -a 'state=started  name=nginx'
​
​
6、防火墙放行80号端口
[root@rhce ~]# ansible newserver -i /hosts  -m firewalld  -a 'port=89/tcp   permanent=true  state=enabled'
[root@rhce ~]# ansible newserver -m shell -a "firewall-cmd --reload"  -i /hosts
​
​
测试:
http://192.168.223.151:89
http://192.168.223.152:89
复制代码

实验2:创建一个虚拟主机,基于不同端口访问不同页面

1、写虚拟主机文件/etc/nginx/conf.d/nginx.conf

2、添加测试页面

3、重启让配置生效

4、防火墙放行指定端口

5、SELinux设为宽容模式放行指定端口

复制代码
1、写虚拟主机文件/etc/nginx/conf.d/nginx.conf
[root@rhce ~]# cat /nginx.conf 
server {
    listen 80;
    root /web/port80;
    location / {
        index index.html;
    }
}
server {
    listen 666;
    root /web/port666;
    location / {
        index index.html;
    }
}
​
[root@rhce ~]# ansible newserver -i /hosts  -m copy   -a 'src='/nginx.conf'  dest=/etc/nginx/conf.d/ '
[root@rhce ~]# ansible newserver -i /hosts  -m shell  -a 'ls -lZ /etc/nginx/conf.d/nginx.conf'
192.168.223.151 | CHANGED | rc=0 >>
-rw-r--r--. 1 root root system_u:object_r:httpd_config_t:s0 196 12月 17 11:16 /etc/nginx/conf.d/nginx.conf
192.168.223.152 | CHANGED | rc=0 >>
-rw-r--r--. 1 root root system_u:object_r:httpd_config_t:s0 196 12月 17 11:16 /etc/nginx/conf.d/nginx.conf
​
​
2、添加测试页面
[root@rhce ~]# ansible newserver -i /hosts -m file  -a 'path=/web/port666  state=directory'
[root@rhce ~]# ansible newserver -i /hosts -m file  -a 'path=/web/port80  state=directory'
ansible newserver -i /hosts -u root -m copy -a "content='<h1>Welcome to Port 80</h1>' dest=/web/port80/index.html mode=0644"
ansible newserver -i /hosts -u root -m copy -a "content='<h1>Welcome to Port 666</h1>' dest=/web/port666/index.html mode=0644"
​
​
ansible newserver -i /hosts -m shell -a "nginx -t"
​
3、重启让配置生效
ansible newserver -i /hosts  -m service -a "name=nginx state=started enabled=yes"
​
4、防火墙放行指定端口
ansible newserver -i /hosts -m ansible.posix.firewalld -a "port=80/tcp permanent=yes state=enabled  immediate=true"
​
ansible newserver -i /hosts -u root -m ansible.posix.firewalld -a "port=666/tcp permanent=yes state=enabled  immediate=true"
​
5、SELinux只为宽容模式
[root@rhce ~]# ansible  newserver -i /hosts -m selinux  -a 'policy=targeted state=permissive'
复制代码
相关推荐
枕星而眠5 分钟前
Linux守护进程完全指南:从原理到实战
linux·运维·服务器·c++·后端
网络系统管理5 分钟前
第八届江苏技能状元大赛选拔赛信息通信网络运行管理项目模块D网络服务与系统运维-Linux样题解析
linux·运维·网络
da-peng-song37 分钟前
ArcGIS Desktop使用入门(三)图层右键工具——定义查询
数据库·arcgis·拆分数据·定义查询
热爱正能量39 分钟前
数据库死锁排查思路
数据库
骑上单车去旅行1 小时前
openEuler 22.03 离线源码编译 Zabbix 7.0.27 完整最终整合手册
linux·运维·服务器·zabbix
swordbob1 小时前
MySQL和Oracle关于读未提交的区别
数据库·mysql·oracle
抛砖者1 小时前
flink打包方式问题
大数据·flink
林九生1 小时前
【实用技巧】MySQL 绿色版一键路径更新脚本详解 —— update_path.bat 深度解析
android·数据库·mysql
野生技术架构师1 小时前
从 B+ 树到应用层分表:MySQL 海量数据架构解析
数据库·mysql·架构
Amnesia0_01 小时前
MySQL的事务
数据库·mysql