RHCSE--ansible1-入门和模块

快速入门使用

功能
  • 批量执行远程命令,可以对远程的多台主机同时进行命令的执行

  • 批量安装和配置软件服务,可以对远程的多台主机进行自动化的方式配置和管理各种服务

  • 编排高级的企业级复杂的IT架构任务, Ansible的Playbook和role可以轻松实现大型的IT复杂架构

  • 提供自动化运维工具的开发API, 有很多运维工具,如jumpserver就是基于 ansible 实现自动化管理功能

ansible特性:
  • 模块化:调用特定的模块完成特定任务,支持自定义模块,可使用任何编程语言写模块

  • 基于Python语言实现

  • 部署简单,基于python和SSH(默认已安装),agentless,无需代理不依赖PKI(无需ssl)安全,基于OpenSSH

  • 幂等性:一个任务执行1遍和执行n遍效果一样,不因重复执行带来意外情况,此特性非绝对支持playbook编排任务,YAML格式,编排任务,支持丰富的数据结构

  • 较强大的多层解决方案 role

Ansible 架构

架构拓扑

复制代码
控制节点 (Ansible控制机)  ←──→ 受控节点 (目标服务器)
     │                              │
     └─ 执行Ansible命令 ────────────┘
     
控制节点命令 → SSH连接 → 受控节点执行 → 返回结果
    ↓               ↓           ↓
ansible命令    网络通信    实际安装配置
​
​
1. 环境准备(前置步骤)
   → 控制节点安装 Ansible
   → 控制节点配置 SSH 免密登录到受控节点(就是这一步)
   → 控制节点编写 inventory 文件(/etc/ansible/hosts)定义受控节点
​
2. 执行部署(核心步骤)
   → 选择纯剧本或剧本+角色方式
   → 执行 ansible-playbook 命令部署 LNMP+Discuz
1. 左侧:输入 / 数据源
  • USERS(用户):Ansible 的操作发起者,可从「公有 / 私有云」「CMDB(配置管理数据库)」获取资产信息,也可直接操作 Ansible。

  • CMDB:配置管理数据库,存储企业所有 IT 资产(服务器、网络设备等)的信息,会将资产数据同步到 Ansible 的「Inventory(主机清单)」。

  • ANSIBLE PLAYBOOK:用户编写的自动化任务编排文件(YAML 格式),是 Ansible 执行复杂任务的核心输入。

2. 中间:Ansible 自动化引擎(核心)

这是 Ansible 的 "大脑",包含 4 个核心组件:

  • INVENTORY(主机清单):存储受控节点(服务器、网络设备)的信息(IP、分组、变量等),数据来源可以是用户手动维护、CMDB 同步、云平台自动拉取。

  • API:Ansible 提供的编程接口,用于和其他系统(如运维平台、CI/CD 工具)集成,实现自动化流程的对接。

  • MODULES(模块) :Ansible 的 "功能单元",是执行具体任务的最小工具(比如yum模块装软件、service模块启停服务),Ansible 通过调用模块完成对受控节点的操作。

  • PLUGINS(插件):扩展 Ansible 功能的组件(比如连接插件控制 SSH 连接、回调插件自定义输出格式),是 Ansible 灵活性的关键。

3. 右侧:管理对象
  • HOSTS(受控主机):服务器、虚拟机等计算节点,是 Ansible 的核心管理目标。

  • NETWORKING(网络设备):交换机、路由器等网络硬件,Ansible 也支持通过专用模块管理这类设备。

实验: rpm包安装ansible
复制代码
[root@rhce ~]# wget  https://mirrors.aliyun.com/epel/epel-release-latest-9.noarch.rpm
[root@rhce ~]# rpm -ivh epel-release-latest-9.noarch.rpm 
[root@rhce ~]# yum install  ansible -y
​
实验:主控节点与受控节点建立连接(密码登录)

写法1:

复制代码
[root@rhce ~]# vi /etc/ansible/hosts 
192.168.223.152  ansible_ssh_user=root  ansible_ssh_port=22  ansible_ssh_pass=redhat
[root@rhce /]# vim /etc/ansible/ansible.cfg 
[defaults]
host_key_checking=False
[root@rhce /]# ansible  192.168.223.152 -m ping 
192.168.223.152 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
[root@rhce /]# ansible  192.168.223.152  -m shell -a "id -u"
​

Ansible 的输出颜色是对执行结果的直观区分,和 "成功 / 失败" 的对应关系很简单:

颜色 核心状态 含义
绿色 SUCCESS 操作成功,且没有产生任何状态变化(比如目标已经是挂载状态,无需再执行);
黄色 CHANGED 操作成功,且产生了实际状态变化(比如之前未挂载,现在成功挂载了);
红色 FAILED 操作失败(会明确显示错误信息,比如权限不足、设备不存在等)。

写法2:

复制代码
[root@rhce /]# vim  /etc/ansible/hosts 
192.168.223.152   ansible_ssh_pass=redhat 

[server]
192.168.223.152  ansible_ssh_pass=redhat
192.168.223.151  ansible_ssh_pass=redhat
[root@rhce /]# vim /etc/ansible/ansible.cfg 
[defaults]
host_key_checking=False

[root@rhce /]# ansible server -m ping 
192.168.223.151 | SUCCESS => {
    "ansible_facts": {   
 #ansible_facts是 Ansible 中的 "事实集合" 字段 ------ 它专门用来存储 Ansible 从受控节点收集到的系统级基础信息(比如操作系统版本、IP 地址、Python 解释器路径、内存大小等),这些信息被称为「Ansible Facts」("事实")。
        "discovered_interpreter_python": "/usr/bin/python3"  #discovered_interpreter_python是 Ansible 自动探测到的 "可用 Python 解释器路径",确保后续推送的模块能正常被解释执行。
    },
    "changed": false,
    "ping": "pong"
}
192.168.223.152 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

写法3:

复制代码
[root@master ~]# ansible-config   init --disabled  > /ansible.cfg
#把/etc/ansible.cfg里的配置删除,然后配置/ansible.cfg,切换到当前目录下,ansible就不读取/etc/ansible.cfg,只读取/ansible.cfg并使生效
[root@master ~]# vim /ansible.cfg 
[defaults]
host_key_checking=False
[root@master ~]# cd  /
[root@rhce /]# vim  /etc/ansible/hosts 
[server]
192.168.223.15[1:2]  ansible_ssh_pass=redhat
[root@rhce /]# ansible server -m ping 
192.168.223.152 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
192.168.223.151 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}

ansible.cfg的加载顺序:

1.环境变量 ANSIBLE_CONFIG 指定的路径:

#示例:若项目下的ansible.cfg在/root/my-ansib1e/目录下,执行:export ANSIBLE_CONFIG=/root/my-ansible/ansible.cfg

2.当前工作目录下的 ansible.cfg(即你项目目录下的配置);只需进入项目目录(ansib1e.cfg 所在的目录),再执行Ansib!e命令(如 ansible、ansib1e-p1aybook),Ansib!e会自动优先读取项目下的ansib1e.cfg,无需额外操作。

3.用户家目录的.ansible.cfg;

4.系统全局配置 /etc/ansible/ansible.cfg。

写法4:

复制代码
[root@rhce /]# vim /hosts
[newserver]
192.168.223.15[1:2]   ansible_ssh_user=redhat ansible_ssh_pass=redhat
[root@rhce /]# vim /ansible.cfg 
[defaults]
host_key_checking=False
[root@rhce /]# cd  /
[root@rhce /]# ansible newserver  -i /hosts   -m ping
192.168.223.151 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
192.168.223.152 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"

清单来源的加载优先级(从高到低):

  1. 若你在执行命令时用了 `-i /tmp/test-hosts`,无论环境变量 /ansible.cfg/ 默认路径有没有 hosts,Ansible 只会读取 `/tmp/test-hosts`;

  2. 若没指定命令行 `-i`,但环境变量 `ANSIBLE_INVENTORY` 配置了路径,就用环境变量指定的 hosts;

  3. 若以上都没配置,才会读 `ansible.cfg` 中 `inventory` 项指定的 hosts;

  4. 只有前 3 项都没配置时,才会用系统默认的 `/etc/ansible/hosts`。

`/etc/ansible/hosts` 是 Ansible 管理受控节点的**主机清单文件**,支持多种写法以适配不同场景,以下是常用格式:

实验:主控节点与受控节点建立连接(密钥登录)

复制代码
[root@rhce /]# cat /hosts 
[newserver]
192.168.223.15[1:2]  ansible_ssh_private_key_file=/root/.ssh/id_rsa 
[root@rhce /]# cat   /ansible.cfg 
[defaults]
remote_user = root
timeout = 3           
host_key_checking=False
[root@rhce /]# ssh-keygen -t rsa 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:V2LfaWLetM+MIES+iKOJWFXhOG7DtKFbidPgc/zA/hk root@rhce
The key's randomart image is:
+---[RSA 3072]----+
|      .          |
|     o .         |
|  . = o   + .    |
| . @ *   + + . . |
|  * /   S + + =  |
|   X + . + + = . |
|  o . E . o o o  |
| o . + +   . . = |
|. . o o       . +|
+----[SHA256]-----+


[root@rhce /]# ansible newserver  -m  authorized_key -a "user=root state=present key='{{ lookup('file', '/root/.ssh/id_rsa.pub') }}'" -e "ansible_ssh_pass=redhat" -i /hosts
192.168.223.151 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": null,
    "exclusive": false,
    "follow": false,
    "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCMlu3p/HeB0XB1nMkKDT+chy53xpj72T55yambR5X/GhrXe1nKc2oqjhto/oS4zuGic/aAMw6ODRaPmFMCdSsf+ZwuOY3AgftCjb4nYdHXFItLJ8qAKtN/oQBvU57byBsH4aeLs0MITJppnzKBmwb963imgfVAN+vEgXUYMcWBWjHo3GuylYjpi0E1xyMuoXu56Fe3BOkWtHLfgC/LXZOcUJwCj69aom54e8QozUbFQWlechZPG1GqkPnBx8dHR1Sx1TRmccEW9wwhrfWSNXponHnuISQrFV2cu+kPaHSvpwTVerEZFppLjtsWkVWi50VJ+ng8mjafU7po1+sx4C7HOwl/FS7SSVmqN1MrZVZcL02OalXOO0P3egM77ToggOXtMe3WpOEGywEKG22JmlcK6HUV6E/AABZ1u/3ENKSKvcR4Rhg3mp3noxjKIyesQQbpmxt4f6bOkeElZv4/tdKb4PcPZEwyavGjLAl5BxdyiQfRC0pGBqHvBkI3wDC5UlE= root@rhce",
    "key_options": null,
    "keyfile": "/root/.ssh/authorized_keys",
    "manage_dir": true,
    "path": null,
    "state": "present",
    "user": "root",
    "validate_certs": true
}
192.168.223.152 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": null,
    "exclusive": false,
    "follow": false,
    "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCMlu3p/HeB0XB1nMkKDT+chy53xpj72T55yambR5X/GhrXe1nKc2oqjhto/oS4zuGic/aAMw6ODRaPmFMCdSsf+ZwuOY3AgftCjb4nYdHXFItLJ8qAKtN/oQBvU57byBsH4aeLs0MITJppnzKBmwb963imgfVAN+vEgXUYMcWBWjHo3GuylYjpi0E1xyMuoXu56Fe3BOkWtHLfgC/LXZOcUJwCj69aom54e8QozUbFQWlechZPG1GqkPnBx8dHR1Sx1TRmccEW9wwhrfWSNXponHnuISQrFV2cu+kPaHSvpwTVerEZFppLjtsWkVWi50VJ+ng8mjafU7po1+sx4C7HOwl/FS7SSVmqN1MrZVZcL02OalXOO0P3egM77ToggOXtMe3WpOEGywEKG22JmlcK6HUV6E/AABZ1u/3ENKSKvcR4Rhg3mp3noxjKIyesQQbpmxt4f6bOkeElZv4/tdKb4PcPZEwyavGjLAl5BxdyiQfRC0pGBqHvBkI3wDC5UlE= root@rhce",
    "key_options": null,
    "keyfile": "/root/.ssh/authorized_keys",
    "manage_dir": true,
    "path": null,
    "state": "present",
    "user": "root",
    "validate_certs": true
}

说明:

1.命令行中 key=后的公钥内容:

o若用 1ookup 函数,需保证引号嵌套正确(外层双引号,内层单引号);

。若直接粘贴公钥字符串,需去掉 {{ 1ookup(...)}},直接写公钥(如 key='ssh-ed25519AAAxxx... root@rhce')

2.权限问题: authorized_key 模块会自动将 authorized_keys 文件权限设为 600(符合 SSH 要求)无需手动修改。

3.免密验证:推送完成后,执行 ansible servers -m ping 验证免密是否生效(无需再传 -eansible_ssh pass)。

yam!格式重写写authorized_key模块:

复制代码
[root@rhce /]# ansible newserver  -m  authorized_key -a "user=root state=absent key='{{ lookup('file', '/root/.ssh/id_rsa.pub') }}'" -i /hosts

[root@rhce /]# cat  /hosts 
[newserver]
192.168.223.15[1:2]  ansible_ssh_private_key_file=/root/.ssh/id_rsa 

[root@rhce /]# cat /auth.yaml 
---
- hosts: newserver          # 目标主机组(对应你清单里的[newserver])
  gather_facts: no          # 关闭事实收集(首次推送公钥时,受控节点可能未配置Python依赖)
  vars:
    ansible_ssh_pass: "redhat"
    ansible_host_key_checking: False
  tasks:
    - name: 给root用户添加SSH公钥(Ansible管理)
      authorized_key:
        user: root                    # 指定目标用户(必填)
        state: present                # 确保公钥存在(添加)
        key: "{{ lookup('file', '/root/.ssh/id_rsa.pub') }}"  # 读取本地公钥
[root@rhce /]# ansible-playbook   /auth.yaml   -i /hosts 

模块

帮助

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%)

标识参数是否必选:= 参数名 → 必选;- 参数名 → 可选

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       # 提取执行,默认提权到root用户,是否能提权到root,还要看受控节点的/etc/sudoers文件有没有该用户
--become-user=USERNAME  # 指定sudo的runas用户,默认用户为root,指定用户提权到哪个用户
-K, --ask-become-pass   # 提示输入sudo操作的口令,默认就是sudoers文件里面的用户的登录密码

范例:将普通用户提升权限

复制代码
#先在受控节点sudo授权
[root@node1 ~]# vim /etc/sudoers
hehe	ALL=(ALL) 	NOPASSWD: ALL
[root@node1 ~]# echo redhat | passwd  hehe  --stdin 
更改用户 hehe 的密码 。
passwd:所有的身份验证令牌已经成功更新。



注意:-u hehe是受控节点的用户,并且该用户需要写到/etc/sudoers
#ansible要成功执行模块需要::确保ansible能够连接到受控节点,然后再做下面的指令
#以hehe用户连接,并利用sudo使用hehe用户提权,执行cat /etc/shadow
#如果远程登录受控节点的hehe用户,并且以密码的方式登录:-u hehe  -k,由于ansible已经把主控节点的公钥保存到受控节点的hehe用户的家目录,所以不要-k也可远程能连接
#-b  默认将hehe提权到root用户,需要输入hehe用户
[root@rhce ~]# ansible 192.168.223.151  -m shell -a 'cat /etc/shadow'  -u hehe  -k -b 
#如果是创建/hosts、/ansible.cfg文件,则需要加-i /hosts :ansible 192.168.60.133 -i /hosts -m shell -a 'cat /etc/shadow'  -u xixi  -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(企业微信)

幂等性

定义:多次执行同一个 Ansible 操作,最终结果和执行一次完全一致,不会产生冗余变更、不会重复创建资源、不会报错,就像 "开关灯"------ 按一次开灯,再按多次还是开灯状态,不会有额外变化。

先明确两个关键概念

1. 声明式模块(原生支持幂等)

这类模块的核心是「声明 "要达到什么状态",而非 "要执行什么动作"」,模块会自动对比 "当前实际状态" 和 "目标状态",仅当二者不一致时才执行操作,这是幂等性的本质。

  • 典型模块:filepackageservicemountfirewalld等。

  • 状态类参数:这类模块中用于声明目标状态的参数(如stateenabled等),是实现幂等的关键载体。

2. 命令式模块(无原生幂等)

这类模块的核心是「单纯执行指定动作」,不会判断目标状态,也不会对比当前状态,仅机械执行命令,因此无原生幂等性。

  • 典型模块:commandshellraw等。

  • 无状态类参数:这类模块仅接收 "要执行的命令 / 动作" 作为参数,不支持声明目标状态。

注意:并不是模块的参数都单独具有幂等性

file模块创建目录为例:

复制代码
- name: 确保目录存在
  file:
    path: /tmp/test
    state: directory  # 状态类参数:声明"目标状态是目录存在"
  1. 参数的作用state: directory只是告诉模块 "我的目标是让/tmp/test成为目录并存在",它本身不具备幂等能力;

  2. 模块内置逻辑的作用:file模块会先检查/tmp/test的当前状态:

    • 若目录不存在:执行创建动作(changed: true);

    • 若目录已存在:不执行任何操作(ok: true);

  3. 幂等性的实现 :是 "state参数声明目标状态" + "模块内置的状态对比逻辑",共同让多次执行结果一致,而非state参数单独实现了幂等。

如果只保留参数,去掉模块内置逻辑(比如手动用command模块执行mkdir),即使有类似 "创建目录" 的参数,也无法实现幂等(多次执行会报错)。

command 模块本身不原生支持幂等性 (它是 "单纯执行命令动作",而非 "确保某种状态"),比如直接用 command: mkdir /tmp/test,第一次执行成功,第二次会报错 "目录已存在"。要实现幂等性,需通过「先检查状态,再条件执行」的逻辑补充

复制代码
[root@rhce ~]# ansible 192.168.223.151  -m command  -a 'mkdir /data'
192.168.223.151 | CHANGED | rc=0 >>

[root@rhce ~]# ansible 192.168.223.151  -m command  -a 'mkdir /data '
192.168.223.151 | FAILED | rc=1 >>
mkdir: 无法创建目录 "/data": 文件已存在non-zero return code
#creates先判断/data文件是否存在,存在就不执行指令,不存在就执行指令
[root@rhce ~]# ansible 192.168.223.151  -m command  -a 'mkdir /data  creates=/data '
192.168.223.151 | SUCCESS | rc=0 >>
skipped, since /data existsDid not run command since '/data' exists

command 模块

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

  2. command 模块通过 cmd 参数或 args 关键字,无法实现同时执行多个指令

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

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

    • 环境变量(如 $HOSTNAME);

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

  4. 功能替代方案 :若需使用shell特殊操作符,应替换为 [ansible.builtin.shell] 模块。

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

语法:

复制代码
# '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
#args 是 Ansible 用于优化「命令执行类任务」的通用关键字,核心作用是传递命令的附加执行选项,而非命令自身参数,它能让命令执行更灵活(如切换工作目录)、更安全(如条件跳过实现幂等),写法也比直接写复合 Shell 命令更规范、更易维护。
#若需要控制命令的执行规则(如切换目录、条件跳过),用 args 关键字;

#若需要安全传递命令参数(尤其是带空格、特殊字符的参数),用 argv 参数
# 'argv'是模块参数,需相对于模块名缩进一层
- name: 使用'argv'以列表形式传递命令------将'command'字段留空
  ansible.builtin.command:
    argv:
      - /usr/bin/make_database.sh
      - Username with whitespace  # 含空格的用户名(argv自动处理空格问题)
      - dbname with whitespace   # 含空格的数据库名
    creates: /path/to/database
  
#argv 是 command模块(优先)/shell模块的专属参数,属于模块内部参数,作用是「以列表形式传递命令及其参数」,替代传统的字符串命令写法,避免特殊字符(空格、引号)导致的解析错误。

实例:

复制代码
 注意:区分指令和参数
 指令:touch、ls。。。
 参数:模块command的参数 chdir  removes creates
#creates先判断如果/datafile是否存在,如果存在,就不执行指令;如果不存在,就执行指令
[root@rhce ~]# ansible 192.168.223.151 -m command -a 'touch /datafile  creates=/datafile' 
192.168.223.151 | CHANGED | rc=0 >>

[root@rhce ~]# ansible 192.168.223.151 -m command -a 'ls -l /datafile' 
192.168.223.151 | CHANGED | rc=0 >>
-rw-r--r--. 1 root root 0 12月 19 20:37 /datafile


#removes先判断/datafile是否存在,如果存在,就执行指令;如果不存在,就不执行指令
[root@rhce ~]# ansible 192.168.223.151 -m command -a 'touch /datafile  removes=/datafile' 
192.168.223.151 | CHANGED | rc=0 >>

[root@rhce ~]# ansible 192.168.223.151 -m command -a 'ls -l /datafile' 
192.168.223.151 | CHANGED | rc=0 >>
-rw-r--r--. 1 root root 0 12月 19 20:39 /datafile
[root@rhce ~]# ansible 192.168.223.151 -m command -a 'rm -rf /datafile  removes=/datafile' 
192.168.223.151 | CHANGED | rc=0 >>

[root@rhce ~]# ansible 192.168.223.151 -m command -a 'ls -l /datafile' 
192.168.223.151 | FAILED | rc=2 >>
ls: 无法访问 '/datafile': 没有那个文件或目录non-zero return code

 - name: Run command if /path/to/database does not exist (with 'cmd' parameter)
  ansible.builtin.command:
    cmd: /usr/bin/make_database.sh db_user db_name
    creates: /path/to/database
    chdir: /etc

扩展:playbook调用模块

复制代码
[root@rhce ~]# cat com.yaml 
---
- name: 测试提权
  hosts: 192.168.223.151
  remote_user: redhat
  gather_facts: no 
  tasks:
    - name: 查看shadow文件,以redhat身份执行
      command: cat /etc/shadow
      become: yes
      become_user: root
      args:
        creates: /data2
[root@rhce ~]# ansible-playbook -i /hosts  com.yaml  -K
BECOME password: 

PLAY [192.168.223.151] *********************************************************

==============说明==============

# 整体格式说明:
# 1. 所有缩进必须使用【半角空格】,禁止使用全角空格/制表符(Tab),默认2个空格为1个缩进层级
# 2. 键值对格式为「key: value」,冒号后必须跟1个半角空格,不能直接跟内容
# 3. YAML列表项以「-」开头,「-」后必须跟1个半角空格
# 4. 顶层无缩进,每向下一个层级,增加2个半角空格
扩展1:Ansible 会先解析模块参数
  • 要求 :在 newserver 主机上,切换到 /etc 目录后读取 redhat-release 文件内容(指定 inventory 文件 /hosts,提权执行)。

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

Ansible 会先解析模块参数

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

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

扩展2:验证「不支持 Shell 特殊语法」(管道 /&&/ 重定向)
  • 要求 :尝试用 command 模块执行 cat /etc/passwd | grep root(过滤 root 用户),观察结果并分析。

  • 错误命令

    #command 不支持管道 、重定向$VARNAME;& 等 Shell 语法
    ansible newserver -i /hosts -m command -a 'echo redhat |passwd --stdin wang'
    ansible newserver -i /hosts -m command -a 'rm -rf /data/'
    ansible newserver -i /hosts -m command -a 'echo hello > /data/hello.log'
    ansible newserver -i /hosts -m command -a "echo $HOSTNAME"

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:执行管道 && 组合命令
  • shell模块通过/bin/sh(可指定executable=/bin/bash)解析命令,支持管道、重定向、&&等所有 Shell 语法。

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

  • 问题:调用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:SHELL支持环境变量
复制代码
[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 预处理的复杂命令(含变量、管道、逻辑分隔符)

注意:shell打印环境变量这功能是不稳定的,了解即可

script 模块

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

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

  3. script优势

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

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

  4. script脚本执行流程

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

    • 受控节点:Ansible 自动创建临时目录;

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

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

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

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

    • name: 直接执行本地脚本(自动传输+执行)
      ansible.builtin.script: /root/init.sh

    • name: 运行带参数的脚本(使用'cmd'参数)
      ansible.builtin.script:
      cmd: /some/local/script.sh 传参1 传参2 传参3

    • 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 # 受控节点上的文件,存在则执行脚本

扩展1:提权执行脚本
  • 要求 :将本地 /root/3.sh 脚本上传到 192.168.223.151 主机执行。

    [root@rhce ~]# vim /3.sh
    #不给test.sh加x权限,ansible照样可以执行
    [root@rhce ~]# cat /3.sh
    #!/bin/bash
    cat /etc/shadow

  • 正确命令

    #成功原因:ansible默认远程连接root用户,以root什么调用模块,权限是足够的
    [root@rhce ~]# ansible 192.168.223.151 -m script -a '/3.sh' -i /hosts
    192.168.223.151 | CHANGED => {

    #失败原因:redhat作为普通用户没有权限cat /etc/shadow
    [root@rhce ~]# ansible 192.168.223.151 -m script -a '/3.sh' -i /hosts -u redhat
    An exception occurred during task execution. To see the full traceback, use -vvv. The error was: NoneType: None
    192.168.223.151 | FAILED! => {

    #成功原因:由于脚本里面的指令需要提高权限才能使用,所以提权
    [root@rhce ~]# ansible 192.168.223.151 -m script -a '/3.sh' -i /hosts -u redhat -b -K
    BECOME password:
    192.168.223.151 | CHANGED => {

习题 2:脚本带参数执行
复制代码
主控节点编辑脚本:
[root@rhce ~]# cat /root/1.sh
#!/bin/bash
mkdir  $1 $2 $3  $4
#注意:第一次执行没报错
[root@rhce ~]# ansible  192.168.223.151  -m script  -a '/root/1.sh he1 he2 he3 he4 chdir=/data'
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": "",
    "stdout_lines": []
}
[root@node ~]# tree /data   #受控节点
/data
├── he1
├── he2
├── he3
└── he4

#第二次执行:报错,原因是目前的参数都没有原生的幂等性判断逻辑
[root@rhce ~]# ansible  192.168.223.151  -m script  -a '/root/1.sh he1_dir he2_dir he3_dir he4_dir chdir=/data'
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": 1,
    "stderr": "Shared connection to 192.168.223.151 closed.\r\n",
    "stderr_lines": [
        "Shared connection to 192.168.223.151 closed."
    ],
    "stdout": "mkdir: 无法创建目录 "he1_dir": 文件已存在\r\nmkdir: 无法创建目录 "he2_dir": 文件已存在\r\nmkdir: 无法创建目录 "he3_dir": 文件已存在\r\nmkdir: 无法创建目录 "he4_dir": 文件已存在\r\n",
    "stdout_lines": [
        "mkdir: 无法创建目录 "he1_dir": 文件已存在",
        "mkdir: 无法创建目录 "he2_dir": 文件已存在",
        "mkdir: 无法创建目录 "he3_dir": 文件已存在",
        "mkdir: 无法创建目录 "he4_dir": 文件已存在"
    ]
}
#第三次执行:改进,加上creates保证幂等
[root@rhce ~]# ansible  192.168.223.151  -m script  -a '/root/1.sh he1_dir he2_dir he3_dir he4_dir chdir=/data creates=/data/he1_dir'
192.168.223.151 | SKIPPED
 
#注意:参数若包含空格 / 特殊字符,需用引号包裹(如`'/root/create_file.sh "test file.txt"'`)。
ansible newserver -m script -a '/root/create_file.sh test_param.txt' -i /hosts

扩展:

复制代码
================扩展:ansible的命令行调用模块--->ansible的playbook调用模块=======================
[root@rhce ~]# cat 1.sh 
#!/bin/bash
mkdir  $1 $2 $3  $4
[root@rhce ~]# cat 1.yaml 
---
- hosts: 192.168.223.151
  tasks:  
    - name: 用argv参数结构化传递多个参数(含特殊字符)
      ansible.builtin.script:
        cmd: /root/1.sh ha1 ha2 ha3 ha4       
      args:
        chdir: /data
        creates: /data/ha1   #重点注意:当/data/ha1存在就不执行cmd模块 
[root@rhce ~]# ansible-playbook -i /hosts  /root/1.yaml

PLAY [192.168.223.151] *********************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.223.151]

TASK [用argv参数结构化传递多个参数(含特殊字符)] ******************************
skipping: [192.168.223.151]

PLAY RECAP *********************************************************************
192.168.223.151            : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
==============说明==============

# 整体格式说明:
# 1. 所有缩进必须使用【半角空格】,禁止使用全角空格/制表符(Tab),默认2个空格为1个缩进层级
# 2. 键值对格式为「key: value」,冒号后必须跟1个半角空格,不能直接跟内容
# 3. YAML列表项以「-」开头,「-」后必须跟1个半角空格
# 4. 顶层无缩进,每向下一个层级,增加2个半角空格

其他常用模块:

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

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

    • name: 复制文件并设置所有者及权限
      ansible.builtin.copy:
      src: /srv/myfiles/foo.conf
      dest: /etc/foo.conf ##如目标存在,默认覆盖,此处指定先备份,如果不存在/etc/foo.conf,则复制过来并创建
      #如果是个dest这里是个目录,则不改变源文件的名字
      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"
}

实例:playbook的方式调用模块

复制代码
---
- name: 给web服务器部署nginx  # Play的名字(方便看日志)
  hosts: webservers  # 这个Play要操作的主机(对应你的Hosts列表)
  tasks:  # 这个Play里的Task列表
    - name: 配置仓库base
      yum_repository:
        file: x
        name: base
        description: base 
        state: present
        enabled: yes
        gpgcheck: no
        baseurl: /mnt/BaseOS
    - name: 配置仓库app
      yum_repository:
        file: x
        name: app
        description: app 
        state: present
        enabled: yes
        gpgcheck: no
        baseurl: /mnt/AppStream
    - name: 挂载光盘
      mount:
        path: /mnt
        src: /dev/sr0
        state: mounted
        fstype: iso9660   # 光盘专属文件系统类型(必须加)
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 是 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在使用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

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;

#删除这行:\1listen 100;
[root@rhce ~]#  ansible 192.168.223.151 -i /hosts -m lineinfile -a "path=/etc/nginx/nginx.conf  search_string='\1listen 100;' state=absent"
192.168.223.151 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup": "",
    "changed": true,
    "found": 1,
    "msg": "1 line(s) removed"
}


[root@rhce ~]#  ansible 192.168.223.151 -i /hosts -m lineinfile -a "path=/etc/nginx/nginx.conf  search_string='        listen       80;' line='listen 97;'"
192.168.223.151 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup": "",
    "changed": true,
    "msg": "line replaced"
}
[root@rhce ~]# ansible 192.168.223.151 -i /hosts  -m shell -a 'grep listen /etc/nginx/nginx.conf'   
192.168.223.151 | CHANGED | rc=0 >>
listen 97;
#        listen       443 ssl http2;
#        listen       [::]:443 ssl http2;
[root@rhce ~]#  ansible 192.168.223.151 -i /hosts -m lineinfile -a "path=/etc/nginx/nginx.conf  search_string='listen 97;' line='       listen 97;'"
192.168.223.151 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup": "",
    "changed": true,
    "msg": "line replaced"
}
[root@rhce ~]# ansible 192.168.223.151 -i /hosts  -m shell -a 'grep listen /etc/nginx/nginx.conf'   
192.168.223.151 | CHANGED | rc=0 >>
       listen 97;
#        listen       443 ssl http2;
#        listen       [::]:443 ssl http2;

扩展参数:

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

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

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

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

replace
  1. 核心功能

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

  2. 幂等性要求

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

    示例:

    • 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参数会产生与预期相反的效果(内容截断)

    • 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/hosts
      regexp: '\b(localhost)(\d*)\b'
      replace: '\1\2.localdomain\2 \1\2'

实例:

复制代码
[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(无需从本地复制文件)。

    • name: 下载foo.conf文件
      ansible.builtin.get_url:
      url: http://example.com/path/file.conf # 待下载文件的远程URL
      dest: /etc/foo.conf # 文件下载后在受控节点的保存路径
      mode: '0440' # 下载后文件的权限设置为0440

      • name: 解压本地压缩包(test.tar.gz)到受控节点/tmp/test_unarchive
        ansible.builtin.unarchive:
        src: /root/test.tar.gz # 控制节点本地压缩包的绝对路径(支持.tar/.tar.gz/.zip等)
        dest: /tmp/test_unarchive # 受控节点的解压目标目录(必须存在)
        backup: no # 可选:是否备份已存在的目标文件(默认no)
        mode: 0755 # 可选:解压后文件/目录的权限(仅对新增文件生效)
        # remote_src: no # 默认值,可省略,标识src是控制节点本地路径

    核心任务:解压远程主机上已存在的压缩包(remote_src=yes)

    复制代码
     - name: 解压远程主机本地压缩包到目标目录
       ansible.builtin.unarchive:
         src: /root/remote_archive.tar.gz  # 远程主机上的压缩包绝对路径(必填)
         dest: /tmp/remote_unarchive       # 远程主机的解压目标目录(必填,必须存在)
         remote_src: yes                   # 关键参数:标识src是远程主机本地路径
         mode: 0644                        # 可选:解压后文件的权限(仅新增文件生效)
         owner: root                       # 可选:解压后文件所属用户
         group: root                       # 可选:解压后文件所属组
         backup: no                        # 可选:目标目录有同名文件时是否备份(默认no)

实例:

常见参数:

  1. copy:默认为yes,当copy=yes,拷贝的文件是从ansible主机复制到远程主机上,如果设置为copy=no,会在远程主机上寻找src源文件

  2. remote_src:和copy功能一样且互斥,yes表示在远程主机,不在ansible主机,no表示文件在ansible主机上

  3. src:源路径,可以是ansible主机上的路径,也可以是远程主机(被管理端或者第三方主机)上的路径,如果是远程主机上的路径,则需要设置copy=no

  4. dest:远程主机上的目标路径,该目录要确保存在

  5. mode:设置解压缩后的文件权限

复制代码
ansible all -m unarchive -a 'src=/data/foo.tgz dest=/var/lib/foo owner=redhat group=redhat'
ansible all -m unarchive -a 'src=/tmp/foo.zip dest=/data copy=no mode=0777'
ansible all -m unarchive -a 'src=https://example.com/example.zip dest=/data copy=no'
ansible websrvs -m unarchive -a  'src=https://releases.ansible.com/ansible/ansible-2.1.6.0-0.1.rc1.tar.gz dest=/data/   owner=root remote_src=yes'
ansible websrvs -m unarchive -a 'src=http://nginx.org/download/nginx-1.18.0.tar.gz dest=/usr/local/src/ copy=no
get_url
  1. 核心功能:从 HTTP、HTTPS 或 FTP 协议地址下载文件至远程服务器。

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

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

复制代码
- name: 下载foo.conf文件
  ansible.builtin.get_url:
    url: http://example.com/path/file.conf  # 待下载文件的远程URL
    dest: /etc/foo.conf                     # 文件下载后在受控节点的保存路径
    mode: '0440'                            # 下载后文件的权限设置为0440

实例

复制代码
#若 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  # 配置永久生效(重启防火墙后不失效)
    immediate: true
    state: enabled  # 启用该规则(允许流量)

- name: 禁止默认区域的8081/TCP端口流量
  ansible.posix.firewalld:
    port: 8081/tcp  # 目标端口(8081端口,TCP协议)
    permanent: true  # 配置永久生效
    immediate: true
    state: disabled  # 禁用该规则(禁止流量)

immediate 参数说明

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

实例:

复制代码
[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

  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密码(仅第一次执行)
        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: 创建luntan数据库(多次执行不报错)
        mysql_db:
        name: luntan
        state: present # 幂等:确保库存在,不存在才创建

        动态适配密码:有密码用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

# 查看单个节点的全量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后面的变量有时需要加 " " 引起来

范例1: 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   
范例2: 显示字符串特定字符
复制代码
[root@rhce ~]# vim  debug.yml
[root@rhce ~]# cat debug.yml 
---
- hosts: newserver
  gather_facts: no
  vars:
    a: "12345"  # 仅用半角空格缩进(2/4个,统一即可)
  tasks:
    - debug:     # 缩进对齐,冒号后加空格(可选,但规范)
        msg: "{{a[2]}}"  # 比debug多2个半角空格,无全角字符
[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   
范例3:调试 "仅当命令执行失败时输出信息",可结合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   
范例4:打印注册变量
复制代码
---
- name: 测试提权
  hosts: 192.168.223.151
  remote_user: redhat
  gather_facts: no 
  tasks:
    - name: 打印当前登录的用户名称
      command: whoami
      register: current_user  # 注册第一个任务的结果(成功时获取stdout)

    - name: 输出提权前用户的标准输出
      debug:
        var: current_user.stdout  # 输出标准输出

    - name: 查看shadow文件(提权执行)
      command: cat /etc/shadow
      become: yes
      become_user: root
      args:
        creates: /data2  # 若/data2存在则跳过任务,可临时删除该配置测试失败场景
      register: shadow_result  # 注册该任务的结果(单独注册,避免覆盖前一个变量)

    - name: 输出shadow任务的各类结果(包括标准错误)
      debug:
        msg:
          - "标准输出:{{ shadow_result.stdout }}"
          - "标准错误:{{ shadow_result.stderr }}"
          - "返回码:{{ shadow_result.rc }}"
======================================
场景 1:任务执行成功(提权生效,redhat 用户有 sudo 权限):
shadow_result.stdout:会获取到 /etc/shadow 文件的内容(标准输出);
shadow_result.stderr:为空字符串(无错误输出);
shadow_result.rc:返回 0(执行成功)。
场景 2:任务执行失败(比如未配置免密提权,或取消become: yes):
shadow_result.stdout:为空字符串(无正常输出);
shadow_result.stderr:会获取到错误信息(如 cat: /etc/shadow: 权限不够);
shadow_result.rc:返回非 0 值(如 1,表示执行失败);
扩展5:理解字典变量

字典(也叫键值对)就像一本带标签的信息登记册,每一条信息都有一个唯一的 "标签"(称为「键 /key」),对应一段具体的 "内容"(称为「值 /value」),通过 "标签" 就能快速找到对应的 "内容"。

比如:一个人的信息登记册(字典),结构如下:

标签(键 /key) 内容(值 /value)
姓名 张三
年龄 28
职业 运维工程师

当你用 register: 变量名 注册任务结果时,Ansible 会自动把这个任务的所有执行信息,按照 "标签(键)+ 内容(值)" 的格式整理成一个字典,这个 "变量名" 就是整个字典的名称,通过「变量名.键」就能获取对应的执行信息。

例如:

  • 场景 1:任务执行成功(提权生效,redhat 用户有 sudo 权限):

    • shadow_result.stdout:会获取到 /etc/shadow 文件的内容(标准输出);

    • shadow_result.stderr:为空字符串(无错误输出);

    • shadow_result.rc:返回 0(执行成功)。

  • 场景 2:任务执行失败 (比如未配置免密提权,或取消become: yes):

    • shadow_result.stdout:为空字符串(无正常输出);

    • shadow_result.stderr:会获取到错误信息(如 cat: /etc/shadow: 权限不够);

    • shadow_result.rc:返回非 0 值(如 1,表示执行失败);

    • Ansible 会默认将失败任务标记为 fatal,终止后续任务(若需忽略失败,可添加 ignore_errors: yes)。

范例6:在 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
}

实验:用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='\\listen 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、防火墙放行89号端口
[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

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

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'
相关推荐
奔跑的花短裤2 小时前
ubuntu安装Isaac sim4.5与强化学习使用
linux·ubuntu·机器人·强化学习·isaac sim·isaac lab
陕西小伙伴网络科技有限公司2 小时前
CentOS-7 编译glibc-2.29
linux·运维·centos
清风拂山岗 明月照大江2 小时前
MySQL运维
运维·数据库·mysql
米高梅狮子2 小时前
02. 配置DNS服务器
运维·服务器·centos
宴之敖者、3 小时前
Linux——指令
linux·运维·服务器
Xの哲學4 小时前
Linux设备驱动模型深度解剖: 从设计哲学到实战演练
linux·服务器·网络·算法·边缘计算
企业对冲系统官4 小时前
基差风险管理系统集成说明与接口规范
大数据·运维·python·算法·区块链·github
松涛和鸣4 小时前
DAY55 Getting Started with ARM and IMX6ULL
linux·服务器·网络·arm开发·数据库·html
HIT_Weston5 小时前
101、【Ubuntu】【Hugo】搭建私人博客:元信息&翻译(二)
linux·运维·ubuntu