Ansible开篇

11786 字
59 分钟
Ansible开篇

Ansible开篇#

[TOC]


web服务回顾#

image-20260327081836438
image-20260327081836438

架构图#

图片变清晰
图片变清晰

  • 🧱 Ansible 架构图

  • Ansible = 你写“剧本” + 它当“导演” + 服务器当“演员” → 全自动完成任务!

💡 关键优势(大白话):

  • 不用装客户端:省心省力
  • 用 SSH 就行:大多数 Linux 都支持
  • 写一次,到处用:标准化、可重复
  • 安全又可控:你说了算,它只听你的

1️⃣ Users(用户)

  • 就是你——运维、开发或管理员
  • 你在电脑上写命令或剧本,告诉 Ansible 要干啥

2️⃣ Host Inventory(主机清单)

  • 这是个“通讯录”:列出了你要管的所有服务器 IP 或名字
  • 告诉 Ansible:“这些机器是我要操作的目标”

3️⃣ Playbooks(任务剧本)

  • 就是“操作说明书”:你写的 YAML 文件,详细说明每一步该做什么
  • 类似于“菜谱”:照着做就行,不会出错

4️⃣ Core Modules & Custom Modules(核心模块 + 自定义模块)

  • 这些是 Ansible 的“工具箱”:
    • core modules:自带的常用工具
    • custom modules:你自己写的特殊功能,比如调某个 API 接口
  • 简单说: Ansible 用这些“小工具”去执行具体任务

5️⃣ Connection Plugins(连接插件)

  • 就是“通信方式”:Ansible 怎么和服务器说话?
  • 默认用 SSH(最常见),也可以用 Kubernetes 等
  • 它负责建立连接,把命令发出去

6️⃣ Plugins(其他插件)

  • 比如发邮件、记录日志、输出格式化结果等
  • 可以让你在执行完任务后自动通知你:“部署成功啦!”或者“出错了!”

7️⃣ Hosts(目标服务器)

  • 就是你管理的那些机器,可能是物理机、虚拟机
    • 可能是云上的服务器(Public/Private Cloud)
  • 它们不需要装任何东西(无代理),只要能连上 SSH 就行!

🔁 整体流程一句话总结:

你写个剧本 → Ansible 根据主机清单找到目标 → 用 SSH 连上去 → 执行内置或自定义工具 → 完成任务 → 把结果告诉你

Python环境管理#

🌏 故事从 PEP 668 讲起

Ubuntu 24.04 严格执行 ==PEP 668==,禁止直接使用 pip install 安装全局包

Terminal window
root@m01 ~# pip install ansible
× This environment is externally managed
# 外部环境托管
❌️ Ubuntu 24.04 上直接 pip install 会报错

📌 为什么会这样?

Ubuntu 系统自身很多==核心功能==(软件包管理器、系统工具)依赖 Python

  • 如果你用 pip 全局乱装包,版本冲突可能直接把系统干崩

✅️ 所以 Ubuntu 24.04 一刀切:想装东西?先进虚拟环境,或者用 pipx(不需要进虚拟环境)

💡 所以就有了今天要讲的四个工具,它们分两派:

派别成员干什么的
环境隔离派venvuv创建独立的 Python 运行空间,装了啥都不影响系统
包安装派pippipx往某个环境里装包,区别是一个装库、一个装 CLI 工具

这两派是配合关系,不是竞争关系: 先用 venv(或 uv)开个房间 → 再用 pip(或 uv pip)往房间里搬东西 或者直接用 pipx ——它自己把”开房间 + 搬东西”一步搞定,==专给 CLI 工具用==


四大工具通俗讲解#

pip — Python 的”万能安装器”#

Terminal window
# 没进 venv → 装到系统全局(Ubuntu 24.04 会拦截)
pip install requests
====================================
# 先进 venv → 装到虚拟环境里,不影响系统
source .venv/bin/activate
(.venv) pip install requests # 只装在这个 venv 里
(.venv) pip install ansible # 也只在这个 venv 里,退出就找不到了
====================================
📌 pip 现在主要在'虚拟环境里面'用,装到 venv 里而不是系统里
Note

🔹 pip 只管”送到当前环境”,不管隔离——当前环境是哪个就装到哪个 🔹 如果所有包都往系统塞 → 版本一冲突就炸锅 🔹 Ubuntu 24.04 禁止往系统塞:externally-managed-environment

💡 pip 装的东西分两种——搞清楚这个,后面 pipx 才好理解:

requests(库)ansible(CLI 工具)
本质给 Python 代码 import 用的在终端直接敲命令用的
怎么用import requestsansible --version
终端能敲吗requestscommand not found✅ 能敲,有可执行入口
谁装pip / uv pippip / uv pip / ==pipx==
Terminal window
# requests 是库——终端敲不了
root@m01 ~# requests
bash: requests: command not found
====================================
# 只能在 Python 代码里 import
import requests
resp = requests.get("https://api.github.com")
print(resp.status_code) # 200
Tip

📌 这就是 pip 和 pipx 的分界线:pipx 不装库,因为库装了也没命令可敲 pipx 只装 ansible、black、httpie 这种能直接在终端敲的 CLI 工具


venv — Python 的”房间隔离术”#

Terminal window
[root@Master ~]# python3 -V
Python 3.12.13
✅️ Rocky升级完软件包后(dnf update)
[root@Master ~]# mkdir /kpyun
[root@Master ~]# cd /kpyun
1)创建一个叫 myenv 的虚拟环境
[root@Master kpyun]# python3 -m venv myven
⚠️ 生成的时候,会明显觉得卡顿!比较慢
[root@Master kpyun]# ls -lh
total 0
drwxr-xr-x 5 root root 74 Jun 9 22:13 myven
[root@Master kpyun]# ls ./myven/
bin include lib lib64 pyvenv.cfg
2)激活这个环境(走进这个房间)
[root@Master kpyun]# source ./myven/bin/activate
((myven) ) [root@Master kpyun]#
# 现在你在这个"房间"里,随便 pip install 都不会影响系统
((myven) ) [root@Master kpyun]# pip install ansible -i https://mirrors.aliyun.com/pypi/simple/
安全!
((myven) ) [root@Master kpyun]# ansible --version
ansible [core 2.21.0]
版本号 👆
3)退出房间
((myven) ) [root@Master kpyun]# deactivate
[root@Master kpyun]# ansible --version
-bash: ansible: command not found
📌 '只在虚拟环境生效'

🔹 venv 是 Python 3.3+ 自带的,不需要额外安装 🔹 每个项目一个 venv,各自独立,互不干扰 🔹 缺点:每创建一个环境都要完整复制一遍 Python 和包文件,占磁盘

⚠️ 那个 (myenv) 前缀,就是告诉你”你现在在虚拟环境里”


pipx — CLI 工具的”单身公寓”#

Terminal window
1)安装 pipx
[root@Master kpyun]# yum -y install pipx
[root@Master kpyun]# pipx --version
1.12.0
2)用 pipx 装一个 CLI 工具(比如 uv)
[root@Master kpyun]# pipx install uv -i https://mirrors.aliyun.com/pypi/simple/
installed package uv 0.11.19, installed using Python 3.12.13
These apps are now available
- uv
- uvx
done! 🌟
✅️ '配置uv的shell命令补齐'
# tab键可以补全命令
[root@Master kpyun]# echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bashrc
[root@Master kpyun]# echo 'eval "$(uvx --generate-shell-completion bash)"' >> ~/.bashrc
[root@Master kpyun]# source ~/.bashrc
3)现在 uv 命令在任何地方都能用,不需要进虚拟环境!
[root@Master kpyun]# uv --version
uv 0.11.19 (x86_64-unknown-linux-gnu)
# ✅ 不需要 activate!
4)即使在虚拟环境中也是可以找到的
[root@Master kpyun]# source ./myven/bin/activate
((myven) ) [root@Master kpyun]# uv --version
uv 0.11.19 (x86_64-unknown-linux-gnu)

📌 pipx 的核心设计理念:

特性说明
安装对象只装 CLI 工具(ansible、black、httpie…),不装库
隔离方式每个工具一个独立虚拟环境,藏在 ~/.local/share/pipx/venvs/
暴露方式只在 ~/.local/bin/ 放一个链接,全局可直接敲命令
退出环境还能用吗✅ 能!装完就是全局的,跟虚拟环境无关
Terminal window
[root@Master kpyun]# ls ~/.local/
bin share state
'虚拟环境'
[root@Master kpyun]# ls ~/.local/share/pipx/venvs/
uv
'链接位置📍'
[root@Master kpyun]# ls ~/.local/bin/
uv uvx ✅️ 全局可直接敲命令
对比维度pippipx
主要用途(可被 import)和 CLI 工具只装 CLI 工具(ansible、black 等)
环境隔离❌ 不隔离,全塞一起✅ 每个工具独立环境
依赖冲突高 — A 要 v1,B 要 v2,炸了低 — 各住各的
全局可用装在哪就在哪用✅ 装完即全局可用
适用场景在 venv 里给项目装依赖全局装 Python 小工具
装库可以吗pip install requests❌ pipx 不装库

📌 选哪个?

  • 你在项目 venv 里装依赖 → pip(或 uv pip
  • 你想全局随时用 ansible / black / httpie → pipx
Tip

💡 pipx 解决了什么问题?

你想要全局敲 ansible 命令,但 Ubuntu 又禁止你全局 pip install

✅️ pipx 说:“我帮你装一个独立的 ansible 环境,再给你一把全局的钥匙,完美!”

Note

⚠️ pipx 只能装 CLI 工具,不能装库(比如 requests 你还是要用 pip 在 venv 里装)

🔹 pipx 在虚拟环境内 ==依然== 是全局安装它的设计就是全局可用,跟你当前在不在 venv 没关系


uv — 极速版的 venv + pip 合体#

一句话:uv 就是把 venvpip 用 Rust 重写了一遍,速度直接起飞

创建虚拟环境 + 装包,这两件事它干得比原版快 10-100 倍

顺便还能指定 Python 版本——创建环境时自动下载,不用你手动装

📌 uv 到底快在哪里?

对比维度venv + pipuv
底层语言PythonRust
创建环境复制整个 Python 解释器(3-5 秒)符号链接/硬链接(毫秒级)
装包速度逐个下载解压安装并行处理,全局缓存复用
包占用磁盘每个环境复制一份所有环境共享一份缓存,==硬链接==指向
Python 版本管理❌ 需额外装 pyenv✅ 内置 uv python install 3.14
依赖锁定❌ 需配合 pip-tools✅ 内置 uv.lock 精确锁定

📌 uv 的”零成本复用”是怎么做到的?

Tip

10 个虚拟环境都要用 requests 这个库:

  • venv + pip:下载 1 次,复制 10 次 → 10 份磁盘空间
  • uv:下载 1 次,10 个环境全部用硬链接指向同一份文件 → 1 份磁盘空间 + 10 个指针
    • 省磁盘、省时间,而且每个环境看起来都”有自己的那份”
Terminal window
1)用 pipx 装 uv(全局可用)
[root@Master kpyun]# pipx install uv -i https://mirrors.aliyun.com/pypi/simple/
installed package uv 0.11.19, installed using Python 3.12.13
These apps are now available
- uv
- uvx
done! 🌟
✅️ '配置uv的shell命令补齐'
# tab键可以补全命令
[root@Master kpyun]# echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bashrc
[root@Master kpyun]# echo 'eval "$(uvx --generate-shell-completion bash)"' >> ~/.bashrc
[root@Master kpyun]# source ~/.bashrc
2)创建虚拟环境(替代 python -m venv 还能指定 Python 版本)
[root@Master oldboy]# uv venv haha --python 3.14
cpython-3.14.5-linux-x86_64-gnu (download)
'自动下载 Python 3.14'
Using CPython 3.14.5
Creating virtual environment at: haha
Activate with: source haha/bin/activate
====================================
📌 '选哪个?'
✅️ 简单项目,不想多装东西 `venv` 够用
✅️ 追求效率,需要多 Python 版本管理 `uv`
====================================
3)激活环境
[root@Master oldboy]# source ./haha/bin/activate
(haha) [root@Master oldboy]# ansible --version
-bash: ansible: command not found
4)装包(替代 pip install)
(haha) [root@Master oldboy]# uv pip install ansible -i https://mirrors.aliyun.com/pypi/simple/
'比 pip 快 10-100 倍'
(haha) [root@Master oldboy]# ansible --version
ansible [core 2.21.0]
(haha) [root@Master oldboy]# deactivate
[root@Master oldboy]# ansible --version
-bash: ansible: command not found
✅️ 同样也是只能在虚拟环境中生效
'在哪里安装的在哪里生效'
5)直接在系统安装试一试
# Rocky Linux
[root@Master oldboy]# uv pip install ansible -i https://mirrors.aliyun.com/pypi/simple/
error: No virtual environment found; run `uv venv` to create an environment
'连 uv 也拒绝直接往系统里装!'
📌 uv pip 更严格——没进 venv 直接报错,不给你误操作的机会
'pip 只是被 Ubuntu 拦了,在 Rocky 上还能装;uv 不管什么系统,一律要求进 venv'
6)直接在Rocky中pip安装ansible
[root@Master oldboy]# python3 -V
Python 3.12.13
[root@Master oldboy]# dnf install python3-pip python3-devel -y
# 安装Python包管理工具和其他必需组件
====================================
'只是安装Ansible的话上面就够了'
dnf install gcc openssl-devel bzip2-devel libffi-devel zlib-devel -y
# 安装编译工具(用于后续可能需要编译的Python包)
====================================
[root@Master oldboy]# pip3 --version
pip 23.3.2 from /usr/lib/python3.12/site-packages/pip (python 3.12)
[root@Master oldboy]# ansible --version
-bash: ansible: command not found
[root@Master oldboy]# pip3 install ansible -i https://mirrors.aliyun.com/pypi/simple/
[root@Master oldboy]# ansible --version
ansible [core 2.21.0]
✅️ pip可以直接安装在系统中

推荐组合:Ubuntu 24.04 最佳实践#

📌 等等——Ansible 这类工具,真的需要进 venv 吗?

上面演示的是”在虚拟环境里装 Ansible”,但其实对于 Ansible 这类 CLI 工具,更推荐直接用 pipx 全局安装:

Terminal window
1)用 pipx 装 uv(全局可用,一次安装终身受益)
root@m01 ~# apt install pipx -y
====================================
✅️ 下面操作仅供演示在Ubuntu中安装uv
# 因为我们不在虚拟环境中安装, 只需要安装pipx👆即可
root@m01 ~# pipx install uv -i https://mirrors.aliyun.com/pypi/simple/
installed package uv 0.11.19, installed using Python 3.12.3
⚠️ Note: '/root/.local/bin' is not on your PATH environment variable.
root@m01 ~# pipx ensurepath
'pipx ensurepath 把 ~/.local/bin 加入 PATH,不然 uv 命令找不到'
root@m01 ~# source ~/.bashrc
'使环境变量生效'
====================================
2)一键全局安装,无需 venv
root@m01 ~# pipx install --include-deps ansible -i https://mirrors.aliyun.com/pypi/simple/
'短命令 ansible + 完整社区模块 + 全局可用'
# 装完随处可用,不需要 source activate
installed package ansible 14.0.0, installed using Python 3.12.3
These apps are now globally available
- ansible ✅️
- ansible-community ✅️
- ansible-config
- ansible-console
- ansible-doc ✅️
- ansible-galaxy
- ansible-inventory
- ansible-playbook ✅️
- ansible-pull
- ansible-test
- ansible-vault
done! 🌟
root@m01 ~# ansible --version
ansible [core 2.21.0]
'核心引擎,只有最基本的模块'
root@m01 ~# ansible-community --version
Ansible community version 14.0.0
'核心引擎 + 一堆 Collections(社区模块包)'
# 14.0.0 不是低版本,它俩的"14"和"2.21"是完全独立的编号体系
3)更改hosts文件
root@m01 ~# cat >> /etc/hosts <<EOF
10.0.0.111 node1
10.0.0.112 node2
10.0.0.113 node3
EOF
'测试了联通性'
root@m01 ~# ping -W2 -c3 node2
PING node2 (10.0.0.112) 56(84) bytes of data.
64 bytes from node2 (10.0.0.112): icmp_seq=1 ttl=64 time=0.564 ms
64 bytes from node2 (10.0.0.112): icmp_seq=2 ttl=64 time=0.254 ms
64 bytes from node2 (10.0.0.112): icmp_seq=3 ttl=64 time=0.204 ms
--- node2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2027ms
rtt min/avg/max/mdev = 0.204/0.340/0.564/0.159 ms
Note
  • 关机拍快照

📌 Ubuntu 24.04 完整工具链:

apt install pipxpipx install uv → 日常用 pipx 装 CLI 工具,用 uv venv 管项目环境 & uv pip 安装软件

Tip

💡 为什么 Ansible 适合 pipx 全局装?

Ansible 是你天天要在终端敲的命令,不是某个项目的依赖库

  • pipx 给它一个独立的”单身公寓”,依赖隔离,但命令全局通——完美匹配

实战(旧版):源码编译安装 Python + 全局 pip 安装 Ansible#

⚠️ 以下为旧版 kylin/CentOS 环境下的操作,Ubuntu 24.04 不适用,仅作参考

Terminal window
1)准备一台服务器kylin
# IP 10.0.0.81 主机名称 m01
'1核2G内存'
[root@m01 ~]# mkdir /server/tmp
[root@m01 ~]# cd /server/tmp/
[root@m01 tmp]# python3 -V
Python 3.7.9
# 大写的V
# 是 python3✅️ 命令;而不是python❌️
'不在支持3.8以下版本,需要安装更高的python版本'
2)下载并编译 Python 3.8
[root@m01 tmp]# wget https://www.python.org/ftp/python/3.8.16/Python-3.8.16.tgz
[root@m01 tmp]# tar xf Python-3.8.16.tgz -C .
# 解压为软件包!
[root@m01 tmp]# ls
Python-3.8.16 Python-3.8.16.tgz
[root@m01 tmp]# cd Python-3.8.16
# 进入软件包
[root@m01 Python-3.8.16]# mkdir /soft
[root@m01 Python-3.8.16]# ./configure --prefix=/soft/python3.8 --enable-optimizations
--prefix=/soft/python3.8:自定义安装路径
--enable-optimizations:用编译时间换取运行速度
'编译时间会长一点,但最终的 Python 解释器运行速度显著提升'
3)编译安装
[root@m01 Python-3.8.16]# make -j$(nproc) && make altinstall
make -j$(nproc):多线程并行编译(全速运行)
# 别偷懒,把你所有的 CPU 核心都利用起来,以最快的速度把软件编译出来
make altinstall:安装新版本但不覆盖系统默认 Python
===============================================
# 我们Nginx之前用的都是 make && make install
'直接覆盖安装旧版'
📌自带的python不能覆盖安装
# CentOS, Ubuntu 的核心组件都依赖于系统自带的 Python 版本
# 如果你卸载了系统默认的 Python,极有可能导致系统崩溃
===============================================
# 新装的 Python 会以 python3.8 命令形式存在,pip3.8 也一并装好了
# pip3.8 是 Python 3.8 的标准组件
'看到 WARNING: The script pip3.8 is installed in /soft/python3.8/bin 这条信息不用慌'
# pip3.8 这个工具已经装好了,放在 /soft/python3.8/bin 这个文件夹里
# 但没在 PATH 环境变量里,所以你直接敲 pip3.8 可能会提示‘找不到命令’
===============================================
[root@m01 Python-3.8.16]# cd
[root@m01 ~]# python3.8 -V
-bash: python3.8: command not found
[root@m01 ~]# /soft/python3.8/bin/python3.8 -V
Python 3.8.16
[root@m01 ~]# python3 -V
Python 3.7.9
'新版的python3.8已经安装好了!但不影响自带的python3'
# 现在还是找不到环境变量!我们把它加入到环境变量!
4)加入环境变量
[root@m01 ~]# echo "export PATH=\$PATH:/soft/python3.8/bin/" >> /etc/profile
[root@m01 ~]# source /etc/profile
[root@m01 ~]# python3.8 -V
Python 3.8.16
'这次就找到了!'
5)用 pip 安装 Ansible
[root@m01 ~]# pip3.8 install ansible -i https://mirrors.aliyun.com/pypi/simple/
[root@m01 ~]# ansible --version
ansible [core 2.13.13]
'两个-'
# 查看版本号,验证是否安装!
6)创建 Ansible 配置文件
[root@m01 ~]# mkdir /etc/ansible
[root@m01 ~]# vim /etc/ansible/ansible.cfg
'配置文件'
# 默认没有、手动创建的
[defaults]
host_key_checking = False
deprecation_warnings = False
interpreter_python = /usr/bin/python3
[inventory]
[privilege_escalation]
[paramiko_connection]
[ssh_connection]
[persistent_connection]
[accelerate]
[selinux]
[colors]
[diff]
[defaults]
# “总指挥部”
host_key_checking = False
# SSH 第一次连接一台新服务器时,会弹出来一个提示问你“这个主机的指纹你确认要信任吗?(yes/no)
# “设置为 False 就是告诉 Ansible:“别问我了,直接信任,继续干活!”
'便于自动化,牺牲少量安全性'
==================================
deprecation_warnings = False
# 警告信息太多,会干扰你看真正重要的输出
# “别老提醒我有些工具快过时了,专心干活!”
'让输出更干净'
==================================
interpreter_python = /usr/bin/python3
# 指定使用python3版本
interpreter_python 这个配置,就是用来告诉“总部”(控制节点):“当你派任务到‘分部’(托管节点)去执行时,必须使用‘分部’自己机器上位于 /usr/bin/python3 的这个 Python 解释器来运行
==================================
[inventory]
# 默认位置:/etc/ansible/hosts(需要手动创建!)
“通讯录”
==================================
[privilege_escalation]
# sudo提权选项
# 如果普通权限不够用,就用sudo升级成管理员
==================================
“两种不同的连接插件“
[paramiko_connection]
# Ansible 自带的插件
[ssh_connection]
# ssh插件
[persistent_connection]
# ssh持久连接选项(默认选项)
“保持热线,说不定一会儿还有事“
==================================
[accelerate]
# 比较老的加速模式
“让它跑得更快“
[selinux]
[colors]
# Ansible命令行输出的颜色(默认)
[diff]
# 它会先显示文件修改前和修改后的内容差异

主机清单#

Note

清单是 Ansible 管理节点的”通讯录”——告诉 Ansible 要操控哪些主机

📌 常见格式:==INI==(推荐,简洁,有高亮显示)和 ==YAML==

默认路径 /etc/ansible/hosts,pip 安装后需手动创建

Terminal window
默认配置文件: /etc/ansible/hosts
'需要手动创建!'
[root@m01 ~]# ll /etc/ansible/hosts
ls: cannot access '/etc/ansible/hosts': No such file or directory
==================================
定义主机清单的方式:
(1)单台定义
[root@m01 ~]# vim /etc/ansible/hosts
10.0.0.7 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='oldboy123.com'
# 默认端口就是22,可以不用写
[root@m01 ~]# ansible 10.0.0.7 -m ping
-m:使用ping这个模块
# 单台测试
10.0.0.7 | SUCCESS => {
"changed": false,
"ping": "pong"
}
(2)定义别名
[root@m01 ~]# cat /etc/ansible/hosts
'多出来 ansible_ssh_host'
# 尽量用内网172.xxx
web01 ansible_ssh_host=10.0.0.7 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='oldboy123.com'
# 第一个是别名!
[root@m01 ~]# ansible web01 -m ping
web01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
[root@m01 ~]# ansible 10.0.0.7 -m ping
[WARNING]: Could not match ...ignoring: 10.0.0.7
[WARNING]: No hosts matched, nothing to do
# 当使用了别名,再次用IP就失效了
(3)定义组
1)定义多台:
[root@m01 ~]# cat /etc/ansible/hosts
web01 ansible_ssh_host=10.0.0.7 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='oldboy123.com'
web02 ansible_ssh_host=10.0.0.8 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='oldboy123.com'
web03 ansible_ssh_host=10.0.0.9 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='oldboy123.com'
[root@m01 ~]# ansible all -m ping | grep -i success
'all表示所有主机'
web02 | SUCCESS => {
web01 | SUCCESS => {
web03 | SUCCESS => {
2)将它们划分同一个小组
'当然也可以支持多个组'
[root@m01 ~]# cat /etc/ansible/hosts
[webs]
web01 ansible_ssh_host=10.0.0.7 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
web02 ansible_ssh_host=10.0.0.8 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
web03 ansible_ssh_host=10.0.0.9 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
[root@m01 ~]# ansible webs -m ping | grep -i success
web01 | SUCCESS => {
web02 | SUCCESS => {
web03 | SUCCESS => {
(4)通过主机名定义
'新开一个机器lb01'
[root@m01 ~]# cat /etc/hosts | grep lb01
172.16.1.5 lb01
# 这里是172.xxx
[root@m01 ~]# grep 172.16.1.5 /etc/ansible/hosts
172.16.1.5 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
# 没有ansible_ssh_host
[root@m01 ~]# ansible lb01 -m ping
[WARNING]: Could not match supplied host pattern, ignoring: lb01
[WARNING]: No hosts matched, nothing to do
'不能这样整!!'❌️
# 你ansible配置文件中(/etc/ansible/hosts)压根没有它lb01,自然是找不到的!
# ✅️ansible 172.16.1.5 -m ping
[root@m01 ~]# grep lb01 /etc/ansible/hosts
lb01 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
# 但如果我们这样配置!依旧没有ansible_ssh_host
[root@m01 ~]# ansible lb01 -m ping
lb01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
'但这个主机名可以从/etc/hosts中读取--》172.16.1.5'
# 域名解析
(5)支持序列的定义方式
[root@m01 ~]# grep 'web0[123]' /etc/hosts
172.16.1.7 web01
172.16.1.8 web02
172.16.1.9 web03
[root@m01 ~]# vim /etc/ansible/hosts
[webs]
web0[1:3] ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
':冒号分隔连续的'
# 不支持逗号 web01,web02,web03
# web01,web02,web03 | UNREACHABLE! => {
# ❌️会报错!
✅️不同的组得换行'参考(7)'
(6)多个组+子组'children’'
# 只想某几个组执行,排除其中的一两个组
[root@m01 ~]# egrep 'web0[123]|lb01|backup' /etc/hosts
172.16.1.5 lb01
172.16.1.7 web01
172.16.1.8 web02
172.16.1.9 web03
172.16.1.41 backup
[root@m01 ~]# cat /etc/ansible/hosts
[webs]
web0[1:3] ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
[lb]
lb01 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
[back]
backup ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
[lnmp:children]
webs
back
# 我只想webs和backup执行,lb组不执行!
lnmp:组名(随便起)
children:⚠️固定写法,'子组'
[root@m01 ~]# ansible lnmp -m ping | grep -i success
web02 | SUCCESS => {
backup | SUCCESS => {
web01 | SUCCESS => {
web03 | SUCCESS => {
# 刚好是4台机器
(7)设置变量'vars’'
1)all给全部设置相同的变量
[root@m01 ~]# vim /etc/ansible/hosts
[webs]
web01
web02
web03
[lb]
lb01
[back]
backup
[all:vars]
ansible_ssh_user=root
ansible_ssh_pass='oldboy123.com'
'⚠️这两个不同的变量也得换行!'
[root@m01 ~]# ansible all -m ping| grep -i success
web02 | SUCCESS => {
lb01 | SUCCESS => {
web03 | SUCCESS => {
web01 | SUCCESS => {
backup | SUCCESS => {
2)给某几个组设置变量
# 前提是得有children(子组)
# 再给子组设置变量
[root@m01 ~]# cat /etc/ansible/hosts
[webs]
web01
web02
web03
[lb]
lb01 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
[back]
backup
[lnmp:children]
webs
back
[lnmp:vars]
ansible_ssh_user=root
ansible_ssh_pass='oldboy123.com'
# 给组设置变量
# 在lnmp组中的所有主机共同使用定义好的vars变量
[root@m01 ~]# ansible lnmp -m ping| grep -i success
web02 | SUCCESS => {
web01 | SUCCESS => {
web03 | SUCCESS => {
backup | SUCCESS => {

免密登录#

Terminal window
'后期学完shell脚本,一定要用脚本写免密登录!!'
========通过ssh免秘钥简化配置========
(1)ansible服务端生成密钥对
'非交互式生成'
[root@m01 ~]# ssh-keygen -t ed25519 -f ~/.ssh/my_key -N ''
Generating public/private ed25519 key pair.
Your identification has been saved in /root/.ssh/my_key
Your public key has been saved in /root/.ssh/my_key.pub
The key fingerprint is:
SHA256:Jgs3AXXpk4jIDguRhkDaeAc8AR1bAUzqzPR4nhiT8fo root@m01
The key's randomart image is:'
+--[ED25519 256]--+
|*O*++o. .. |
|===+ . .. |
|==+o...o . |
|B.O.. ..+ |
|.% +. + S. |
|. O .o = |
| o o . |
| . |
| E |
+----[SHA256]-----+
[root@m01 ~]# ll ~/.ssh/
total 12
-rw-r--r-- 1 root root 1562 Mar 27 17:01 known_hosts
-rw------- 1 root root 399 Mar 27 19:59 my_key
-rw-r--r-- 1 root root 90 Mar 27 19:59 my_key.pub
(2)将公钥拷贝所有的客户端
ssh-copy-id -i ~/.ssh/my_key 172.16.1.5
密码:oldboy123.com
lb01:172.16.1.5
web01:172.16.1.7
web02:172.16.1.8
web03:172.16.1.9
backup:172.16.1.41
(3)修改默认连接的私钥
'客户端配置信息'
[root@m01 ~]# vim /etc/ssh/ssh_config
IdentityFile ~/.ssh/my_key
# 默认就用这个私钥连接!
(4)测试验证
'/etc/hosts主机有对应的解析!'
[root@m01 ~]# cat /etc/hosts
127.0.0.1 localhost
::1 localhost
172.16.1.5 lb01
172.16.1.6 lb02
172.16.1.7 web01
172.16.1.8 web02
172.16.1.9 web03
172.16.1.10 web04
172.16.1.31 nfs01
172.16.1.41 backup
172.16.1.51 db01
172.16.1.52 db02
172.16.1.53 db03
172.16.1.81 m01
[root@m01 ~]# ssh lb01
...
Last login: Fri Mar 27 17:33:31 2026 from 172.16.1.81
[root@lb01 ~]# exit
# 成功连接!
(5)重构ansible的主机清单
[root@m01 ~]# vim /etc/ansible/hosts
web0[1:3]
lb01
backup
[root@m01 ~]# ansible all -m ping | grep -i success
lb01 | SUCCESS => {
backup | SUCCESS => {
web02 | SUCCESS => {
web03 | SUCCESS => {
web01 | SUCCESS => {

实战:Ansible 基础操作#

Terminal window
======'Ubuntu 24.04'======
1)创建全局配置文件
root@m01 ~# mkdir /etc/ansible
root@m01 ~# vim /etc/ansible/ansible.cfg
[defaults]
host_key_checking = False
deprecation_warnings = False
interpreter_python = /usr/bin/python3
[inventory]
[privilege_escalation]
[paramiko_connection]
[ssh_connection]
[persistent_connection]
[accelerate]
[selinux]
[colors]
[diff]
2)全局的主机清单
⚠️ '需要手动创建!'
root@m01 ~# cat /etc/ansible/hosts
10.0.0.113 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
✅️ ping IP 是可以直接通的
root@m01 ~# ansible 10.0.0.113 -m ping
10.0.0.113 | SUCCESS => {
"changed": false,
"ping": "pong"
}
3)只有主机名这次
'无非是IP换为了主机名' --> 需配合/etc/hosts使用
root@m01 ~# vim /etc/ansible/hosts
node1 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
node2 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
node3 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
root@m01 ~# ansible node3 -m ping
node3 | SUCCESS => {
"changed": false,
"ping": "pong"
}
root@m01 ~# ansible node3 -a id
node3 | CHANGED | rc=0 >>
uid=0(root) gid=0(root) groups=0(root)
4)创建sudo权限文件
root@m01 ~# tail -1 /etc/sudoers
@includedir /etc/sudoers.d
root@m01 ~# echo 'test ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/test
root@m01 ~# cat /etc/sudoers.d/test
test ALL=(ALL) NOPASSWD:ALL
'不需要在本地创建test用户 --> node节点创建'
# 用来后续的文件分发
5)node节点创建test用户
[root@node1 ~]# useradd test
[root@node1 ~]# echo "oldboy123" | passwd --stdin test
'其他两个节点重复'
✅️ 手动在node节点进行测试
[root@node1 ~]# echo 'test ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/test
[root@node1 ~]# su - test
Last failed login: Wed Jun 10 06:43:36 CST 2026 from 10.0.0.210 on ssh:notty
There was 1 failed login attempt since the last successful login.
[test@node1 ~]$ sudo -l
......................
secure_path=/sbin\:/bin\:/usr/sbin\:/usr/bin
User test may run the following commands on node1:
(ALL) NOPASSWD: ALL ✅️
[test@node1 ~]$ ls /root
ls: cannot open directory '/root': Permission denied
[test@node1 ~]$ ls /home
test
[test@node1 ~]$ sudo ls /root | wc -l
0 '使用sudo后可以正常访问'
6)免密登录
# master节点分发到node节点的test用户
root@m01 ~# ssh-keygen
root@m01 ~# ssh-copy-id test@node1
root@m01 ~# ssh-copy-id test@node2
root@m01 ~# ssh-copy-id test@node3
root@m01 ~# ssh test@node1 'ls /home/'
test
root@m01 ~# ssh test@node1 'ls /root/'
ls: cannot open directory '/root/': Permission denied
'权限不够' --> 拒绝
root@m01 ~# ssh test@node1 'sudo ls /root/ | wc -l'
0 '使用sudo后可以正常访问'
7)拷贝sudo配置文件至其他node节点
'node1我们刚才已经手动添加过了!'
root@m01 ~# scp /etc/sudoers.d/test node2:/etc/sudoers.d
root@node2's password:' ⚠️ 为什么还需要密码❓️
'我们只是对node节点的test用户实现免密登录, root用户依旧要使用密码'
test 100% 30 55.6KB/s 00:00
root@m01 ~# scp /etc/sudoers.d/test node3:/etc/sudoers.d
root@node3's password:'
test 100% 30 57.1KB/s 00:00
8)指定远程用户
root@m01 ~# grep node1 /etc/ansible/hosts
node1 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1'
'我已经把root用户的密码输入在这里面了' --> 相当于对root免密登录
# 这是远程登录的是root用户
root@m01 ~# ansible node1 -a id
node1 | CHANGED | rc=0 >>
uid=0(root) gid=0(root) groups=0(root)
# 手动指定用户为test
root@m01 ~# cat > /etc/ansible/hosts <<EOF
node1 ansible_ssh_user=test
node2 ansible_ssh_user=test
node3 ansible_ssh_user=test
EOF
'做完免密可以不需要远程登录的密码了'
# 更换了远程登录用户为test
root@m01 ~# ansible node1 -a id
node1 | CHANGED | rc=0 >>
uid=1000(test) gid=1000(test) groups=1000(test)
9)远程能够执行的命令有限 --> 用户权限问题
root@m01 ~# ansible node1 -m shell -a 'ls /home/test | wc -l'
node1 | CHANGED | rc=0 >>
0
root@m01 ~# ansible node1 -m shell -a 'ls /root'
node1 | FAILED | rc=2 >>
ls: cannot open directory '/root': Permission denied
root@m01 ~# ansible node1 -m shell -a 'sudo ls /root | wc -l'
node1 | CHANGED | rc=0 >>
0 --> '加上sudo后可以正常访问'

配置文件加载顺序#

Ansible 按以下优先级从高到低查找配置文件,找到第一个就用,后面的全忽略

ANSIBLE_CONFIG 环境变量(如果设置了的话)

./ansible.cfg(当前目录)

~/.ansible.cfg(用户家目录)

/etc/ansible/ansible.cfg(全局默认)

Note

📌 规律:谁离项目近谁先用 —> 当前目录 > 家目录 > 全局默认,找到即停

项目级配置实战: 在当前目录放 ansible.cfg + inventory,覆盖全局配置

Terminal window
1)创建项目级 ansible.cfg
root@m01 ~# ansible --version | grep 'config file'
config file = /etc/ansible/ansible.cfg
'当前走的是全局配置'
root@m01 ~# vim ansible.cfg
# /root/ansible.cfg — 项目级配置
[defaults]
inventory = ./inventory
interpreter_python = auto_silent
remote_user = test
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
配置项所属区块作用
inventory = ./inventory[defaults]指定主机清单路径,./ 表示当前目录
interpreter_python = auto_silent[defaults]自动探测远程 Python 解释器,不打印警告
remote_user = test[defaults]SSH 连接远程主机时使用的默认用户
become = true[privilege_escalation]开启 sudo 提权
become_method = sudo[privilege_escalation]提权方式为 sudo
become_user = root[privilege_escalation]提权目标用户为 root
become_ask_pass = false[privilege_escalation]提权时不询问密码(需提前配置 NOPASSWD)

📌 简单说:用 test 用户连过去 → 自动 sudo 提权到 root → 不用输密码

Terminal window
root@m01 ~# ansible --version | grep 'config file'
config file = /root/ansible.cfg
'当前目录的配置文件优先生效!全局的 /etc/ansible/ansible.cfg 被覆盖'
2)创建项目级 inventory
root@m01 ~# ansible node1 -a id
[WARNING]: provided hosts list is empty, only localhost is available
⚠️ 没有主机清单 --> 只有localhost可达
[WARNING]: Unable to parse /root/inventory as an inventory source
'因为我们指定了 inventory = ./inventory,但文件还不存在'
root@m01 ~# touch ./inventory
root@m01 ~# echo $(pwd)
/root
root@m01 ~# ls
ansible.cfg inventory
✅️ 一个配置文件,一个主机清单
'全局的 /etc/ansible/ansible.cfg 和 /etc/ansible/hosts 不再生效'
# 对test用户做的免密是生效的
3)写入主机并验证
root@m01 ~# ansible node1 -a id
[WARNING]: Could not match supplied host pattern, ignoring: node1
'主机清单是空的,还没写内容'
root@m01 ~# echo 'node[1:3]' > ./inventory
root@m01 ~# cat ./inventory
node[1:3]
'node[1:3] 展开为 node1 node2 node3'
root@m01 ~# ansible node1 -a id
node1 | CHANGED | rc=0 >>
uid=0(root) gid=0(root) groups=0(root)
'remote_user=test 但 become=true 提权为 root,所以 uid=0'
root@m01 ~# ansible node1 -m shell -a 'ls /root | wc -l'
node1 | CHANGED | rc=0 >>
0
✅️ sudo 提权生效,/root 目录可访问
Tip
  • 命令行 -i 指定的清单优先级 > ansible.cfg 中定义的清单,多个 -i 可合并多个清单来源

  • ansible-inventory —list 查看清单结构

Terminal window
1)排查思路
root@m01 ~# ls
ansible.cfg inventory
root@m01 ~# cat inventory
node[1:3]
root@m01 ~# ansible --version | grep 'config file'
config file = /root/ansible.cfg
root@m01 ~# cat ./ansible.cfg | grep inventory
inventory = ./inventory
✅️ 配置文件中的主机清单就是这个
'先确定配置文件 --> 再找对应的主机清单'
2)查看清单结构
root@m01 ~# ansible-inventory --list
# 查看的就是上面的主机清单
{
"_meta": {
📌 内部的元数据 ✅️
# 记录每台主机绑了什么变量、用的什么解析方式
"hostvars": {},
"profile": "inventory_legacy"
},
"all": {
"children": [
"ungrouped"
]
},
"ungrouped": {
"hosts": [
"node1",
"node2",
"node3"
]
}
}
Tip

Ansible 有两个内置组,不用定义就存在:

组名包含
all清单中所有主机
ungrouped没有分到任何组的主机
Terminal window
3)改名字
# 清单以.ini结尾(有语法高亮)
'我们就是以ini格式书写的主机清单'
root@m01 ~# mv ./inventory ./inventory.ini
root@m01 ~# ansible-inventory --list
[WARNING]: Unable to parse /root/inventory as an inventory source
✅️ 因为我们改名字了 --> 但是配置文件中的主机清单依旧是它'原来的'
root@m01 ~# ansible-inventory --list -i inventory.ini
✅️ -i 指定一下就可以了
{ .....xxxx
"ungrouped": {
"hosts": [
"node1",
"node2",
"node3"
]
}
}

命令行实现#

file模块#

Terminal window
[root@m01 ~]# cat /etc/ansible/hosts
web0[1:3]
lb01
backup
==================================
[root@m01 ~]# ssh web01 "> /etc/issue"
'会有一些没用的登录信息!'
Authorized users only. All activities may be monitored and reported.
[root@m01 ~]# ssh web01 "> /etc/issue.net"
Authorized users only. All activities may be monitored and reported.
# 把这两个文件都清空!
[root@m01 ~]# ssh web01 "hostname -I"
10.0.0.7 172.16.1.7
'现在就干干净净的了!'
(1)创建一个空文件
path: /path/to/myfile.txt
state: touch
owner: www
group: www
mode: '0644'
# 类似于 Linux 中的 touch 命令
# 如果文件不存在则创建;若存在,则更新其时间戳
[root@m01 ~]# ssh web01 "id www"
uid=666(www) gid=666(www) groups=666(www)
[root@m01 ~]# ansible web01 -m file -a 'path=/server/jiu.txt state=touch mode=0600 owner=www group=www'
-m:指定模块file
-a:向模块传递参数
'创建文件的同时指定权限、属主、属组'
web01 | CHANGED => {
"changed": true,
"dest": "/server/jiu.txt",
"gid": 666,
"group": "www",
"mode": "0600",
"owner": "www",
"size": 0,
"state": "file",
"uid": 666
}# www用户的uid和gid都是666
'假如发现自己mode权限改错了!'又如何修改呢❓️
[root@m01 ~]# ansible web01 -m file -a 'path=/server/jiu.txt mode=0644 owner=root group=root'
'无非少写一个选项!state'
# 我们不创建它touch,就是单纯修改权限和属主
[root@m01 ~]# ssh web01 "ls -lh /server/jiu.txt"
-rw-r--r-- 1 root root 0 Mar 28 09:10 /server/jiu.txt
==================================
对于Ansible file 模块,只要你不加 state: absent(删除)
✅️'文件里的数据就是安全的'
file模块 执行时,逻辑是这样的:
1)检查目标:
它首先会去服务器上找 /server/jiu.txt 这个文件
2)对比状态:
🈶如果文件存在:它会对比当前的权限/属主和你要求的是否一致。
如果不一致,它只运行 '类似' Linux 下的 chmod chown 命令
这两个命令只修改元数据,不动文件内容
🈚如果文件不存在:
如果你没写 state 选项(默认是 file):Ansible 会报错,❌️提示文件不存在,什么都不会做
如果你写了 state: touch:它会创建一个空文件(这时候内容才会变空)
==================================
(2)创建目录
path: /path/to/mydir
state: directory
owner: www
group: www
mode: '0755'
💡无非是'state'由touch ---》directory
[root@m01 ~]# ansible web01 -m file -a 'path=/server/dire state=directory mode=0755 owner=www group=www'
# 创建目录的同时,指定权限、属主、属组
web01 | CHANGED => {
"changed": true,
"gid": 666,
"group": "www",
"mode": "0755",
"owner": "www",
"path": "/server/dire",
"size": 6,
"state": "directory",
"uid": 666
}
[root@m01 ~]# ssh web01 "ls -ld /server/dire"
drwxr-xr-x 2 www www 6 Mar 28 09:35 /server/dire
# 权限、属主、属组都对得上
✅️'我们ssh免密是用的root用户'
# 如果不写属主、属组、mode是有默认值的
# root:root 0644\0755
1)如何修改目录的属性❓️
'如果目录不存在则创建;若已存在,则只修改其属性(如权限、属主等)'
# 依旧使用state ---》directory
[root@m01 ~]# ansible web01 -m file -a 'path=/server/dire state=directory mode=0511 owner=root group=root'
web01 | CHANGED => {....
[root@m01 ~]# ssh web01 "ls -ld /server/dire"
dr-x--x--x 2 root root 6 Mar 28 09:35 /server/dire
# 依旧是那个目录,只不过属性变了!
2)如何递归修改目录及其下文件的属性❓️
recurse: yes ---》相当于( chmod chown) -R 选项!
# 我们用ansible在/server/dire/这个目录下创建点文件!
ansible web01 -m file -a 'path=/server/dire/1.txt state=touch mode=0600 owner=www group=www'
ansible web01 -m file -a 'path=/server/dire/2.txt state=touch mode=0644 owner=nobody group=nobody'
[root@m01 ~]# ssh web01 "ls -ld /server/dire"
dr-x--x--x 2 root root 32 Mar 28 09:53 /server/dire
# 0511
[root@m01 ~]# ssh web01 "ls -ls /server/dire"
total 0
0 -rw------- 1 www www 0 Mar 28 09:52 1.txt
0 -rw-r--r-- 1 nobody nobody 0 Mar 28 09:53 2.txt
# 0600、0644
[root@m01 ~]# ansible web01 -m file -a 'path=/server/dire state=directory recurse=yes mode=0755 owner=root group=root'
'目录及下面的文件的属性都修改为0755,root:root'
[root@m01 ~]# ssh web01 "ls -ld /server/dire"
drwxr-xr-x 2 root root 32 Mar 28 09:53 /server/dire
[root@m01 ~]# ssh web01 "ls -ls /server/dire"
total 0
0 -rwxr-xr-x 1 root root 0 Mar 28 09:52 1.txt
0 -rwxr-xr-x 1 root root 0 Mar 28 09:53 2.txt
🧱但我们通常递归修改目录及下文件的属主和属组
# chown -R www:www
# mod权限,⚠️目录和文件不会设置一样的!
(3)删除文件或目录
path: /path/to/unwanted_file.txt
state: absent
# path+state,这个简单!
[root@m01 ~]# ansible web01 -m file -a "path=/server/dire/1.txt state=absent"
web01 | CHANGED => {
"changed": true,
"path": "/server/dire/1.txt",
"state": "absent"
}
[root@m01 ~]# ssh web01 "ls -lh /server/dire/1.txt"
ls: cannot access '/server/dire/1.txt': No such file or directory
'目录也是一样的!'
[root@m01 ~]# ansible web01 -m file -a "path=/server/dire state=absent"
[root@m01 ~]# ssh web01 "ls -d /server/dire"
ls: cannot access '/server/dire': No such file or directory
(4)创建软链接
src: /path/to/target
dest: /path/to/symlink
state: link
# 可以是目录也可以是文件!
[root@m01 ~]# ansible web01 -m file -a "src=/server/tmp state=link dest=/home/link_tmp"
web01 | CHANGED => {
"changed": true,
"dest": "/home/link_tmp",
....
# 像属主、属组、mode权限、在链接之前就应该修改好!
"src": "/server/tmp",
"state": "link",
}
[root@m01 ~]# ssh web01 "ls -ld /home/link_tmp"
lrwx... 1 root root.../home/link_tmp -> /server/tmp

group & user模块#

Terminal window
group:
name: test # 定义小组的名称
gid: 777 # 小组的gid号
1)创建test组
# 同时指定组名和组id
[root@m01 ~]# ansible web01 -m group -a "name=test gid=777"
web01 | CHANGED => {
"changed": true,
"gid": 777,
"name": "test",
"state": "present",
"system": false
}
⚠️我们创建组的时候没有用state,因为他默认就是present创建
# 下面创建用户也是,✅️默认state就是创建!
[root@m01 ~]# ssh web01 "tail -1 /etc/group"
test❌777:
==================================
user:
name: test # 定义用户名
uid: 777
group: test # 组名or组ID
state: present # 创建
absent # 删除用户
remove: yes # 删除家目录 类似userdel -r 参数
shell: /sbin/nologin
/bin/bash
create_home: true
false
# Unsupported ... for (user) module: gid
⚠️没有gid这个选项!
'但是可以group=组ID'
✅️写组名也是可以的!
1)创建test虚拟用户
[root@m01 ~]# ansible web01 -m user -a "group=777 uid=777 state=present shell=/sbin/nologin create_home=false name=test"
'✅️这个state可以省略,默认就是创建!'
web01 | CHANGED => {
"changed": true,
"comment": "",
"create_home": false,
"group": 777,
"home": "/home/test",
"name": "test",
"shell": "/sbin/nologin",
"state": "present",
"system": false,
"uid": 777
}
[root@m01 ~]# ssh web01 "id test"
uid=777(test) gid=777(test) groups=777(test)
2)删除test用户
[root@m01 ~]# ansible web01 -m user -a "state=absent name=test remove=yes"
[root@m01 ~]# ssh web01 "id test"
id: ‘test’: no such user
[root@m01 ~]# ssh web01 "tail /etc/group | grep test"
[root@m01 ~]# ssh web01 "tail -1 /etc/group"
mysql❌27:
'我们在删除用户的时候,会把对应的组也给删除了!'
3)创建普通用户jiu
[root@m01 ~]# ansible web01 -m user -a "name=jiu"
web01 | CHANGED => {
"changed": true,
"comment": "",
"create_home": true,
"group": 1001,
"home": "/home/jiu",
"name": "jiu",
"shell": "/bin/bash",
"state": "present",
"system": false,
"uid": 1001
}
# 默认状态就是present创建
# 自动创建组、家目录、可以登录...
[root@m01 ~]# ssh web01 "id jiu"
uid=1001(jiu) gid=1001(jiu) groups=1001(jiu)
[root@m01 ~]# ssh web01 "ls -d /home/jiu"
/home/jiu
[root@m01 ~]# ansible web01 -m user -a "state=absent remove=yes name=jiu"
[root@m01 ~]# ssh web01 "id jiu"
id: ‘jiu’: no such user

yum & systemd模块#

Terminal window
yum:
name: wget # 服务、命令名称
xxx.rpm # 也可以是本地的rpm包
state: present # 安装
absent # 卸载
download_only: true # 只下载不安装
download_dir: /opt # 下载到哪个目录
1)卸载wget
[root@m01 ~]# ssh web01 "rpm -qa wget"
wget-1.20.3-6.ky10.x86_64
[root@m01 ~]# ansible web01 -m yum -a "name=wget state=absent"
web01 | CHANGED => {
"ansible_facts": {
"pkg_mgr": "dnf"
},
"changed": true,
"msg": "",
"rc": 0,
"results": [
"Removed: wget-1.20.3-6.ky10.x86_64"
]
}
[root@m01 ~]# ssh web01 "rpm -qa wget"
# 没有这个包了!
2)安装wget
[root@m01 ~]# ansible web01 -m yum -a "name=wget state=present"
"Installed: wget-1.20.3-6.ky10.x86_64"
[root@m01 ~]# ssh web01 "rpm -qa wget"
wget-1.20.3-6.ky10.x86_64
==================================
systemd:
name: nginx # 服务名称
state: started # 启动服务
stopped # 停止服务
restarted # 重启服务
reloaded # 重新加载
enabled: yes # 开机自动启动
no # 开机禁止运行
'这个no就是disabled'🈲禁止开机自启动
[root@m01 ~]# ssh web01 "systemctl is-active nginx"
active
# 查看Nginx的状态!是否活跃
1)关闭服务
[root@m01 ~]# ansible web01 -m systemd -a "name=nginx state=stopped"
⚠️是stopped
web01 | CHANGED => {
"changed": true,
"name": "nginx",
"state": "stopped",
......
# 把它关闭了!
[root@m01 ~]# ssh web01 "systemctl is-active nginx"
inactive
# 就是关闭了的!
2)启动&开机自启
[root@m01 ~]# ansible web01 -m systemd -a "name=nginx state=started enabled=yes"
web01 | CHANGED => {
"changed": true,
"name": "nginx",
"state": "started",
[root@m01 ~]# ssh web01 "systemctl is-active nginx"
active
[root@m01 ~]# ssh web01 "systemctl is-enabled nginx"
enabled

command模块#

Ansible 的 command 模块不支持 Shell 的管道符(|

  • shell 模块会通过系统的 /bin/sh 来执行命令,因此它完全支持管道、重定向等 Shell 特性

  • |>< 等操作不能直接依赖 command 模块完成

  • 你只需要将 command 替换为 shell 即可

Terminal window
❌️不建议使用command和shell
违背了 Ansible 的设计哲学、只有在万不得已的情况下使用✅️
💡-m command -a "只读、无副作用的命令"
hostname -I
free -h
df -h
uptime
.......
[root@m01 ~]# ansible web01 -m command -a 'hostname -I'
web01 | CHANGED | rc=0 >>
10.0.0.7 172.16.1.7
[root@m01 ~]# ansible web01 -m command -a 'uptime'
web01 | CHANGED | rc=0 >>
14:34:14 up 6:12, 2 users, load average: 0.00, 0.00, 0.00
==================================
它只看到命令返回码是0,就认为“成功”.....
# 无法感知状态变化!

copy模块#

Terminal window
copy:
src: a.txt # 源文件
dest: /root/ # 目标路径
owner: www # 属主
group: www # 文件属组
mode: 0644 # 文件权限
backup: yes # 拷贝前是否需要备份
content: 字符串 # 将content后面的内容写入到目标文件
'不仅可以拷贝,还可以现创建文件touch,并写入一段内容!'
==================================
1)拷贝hosts文件
[root@m01 ~]# ansible web01 -m copy -a "src=/etc/hosts dest=/home/the_hosts"
⚠️复制到/home目录并重命名!
# 没有设置权限、属主、属组...
✅️'在下面我们也可以清晰的观察到'
web01 | CHANGED => {
"changed": true,
"checksum": "ecf730686d9142019bdb34d4c8a67ee7e0bcc63d",
"dest": "⚠️/home/the_hosts",
"gid": 0,
"group": "root",
"md5sum": "90802db3d7cda085cf31d7e479886858",
"mode": "0644",
"owner": "root",
"size": 363,
"src": "/root/.ansible/tmp/ansible-tmp-1774682652.0671225-4390-83497777325810/source",
"state": "file",
"uid": 0
}
[root@m01 ~]# ssh web01 "ls /home/the_hosts"
/home/the_hosts
[root@m01 ~]# ssh web01 "tail -1 /home/the_hosts"
172.16.1.81 m01
[root@m01 ~]# ansible web01 -m copy -a "src=/etc/hosts dest=/home/ owner=www group=www mode=600"
⚠️仅复制到/home目录
web01 | CHANGED => {
"changed": true,
"checksum": "ecf730686d9142019bdb34d4c8a67ee7e0bcc63d",
"dest": "⚠️/home/hosts",
"gid": 666,
"✅️group": "www",
"md5sum": "90802db3d7cda085cf31d7e479886858",
"✅️mode": "0600",
"✅️owner": "www",
"size": 363,
"src": "/root/.ansible/tmp/ansible-tmp-1774683020.2063756-4447-276209329430854/source",
"state": "file",
"uid": 666
}
[root@m01 ~]# ssh web01 "ls -lh /home/hosts"
-rw------- 1 www www 363 Mar 28 15:30 /home/hosts
2)如果目标主机有,进行备份!
[root@m01 ~]# ssh web01 "cat /home/hosts" | wc -l
14
# 本来就有,共14条记录!
[root@m01 ~]# echo 192.168.219.128 Centos-7 > ./hosts
[root@m01 ~]# cat ./hosts
192.168.219.128 Centos-7
[root@m01 ~]# ansible web01 -m copy -a "src=./hosts dest=/home backup=yes"
# 还是把hosts文件拷贝到/home目录下
web01 | CHANGED => {
"✅️backup_file": "/home/hosts.18160.2026-03-28@15:39:50~",
"changed": true,
"checksum": "a7522db1bc78f77c4ac136aa064712d8fafd4e82",
"🐷dest": "/home/hosts",
"gid": 666,
"⚠️group": "www",
"md5sum": "463094b6a3f5b92504dcd83e64854ffa",
"⚠️mode": "0600",
"⚠️owner": "www",
[root@m01 ~]# ssh web01 "cat /home/hosts"
192.168.219.128 Centos-7
# 拷贝过去的文件!
# 我拷贝过去的时候并没有指定属主,权限!
'保留了原文件的权限!'
[root@m01 ~]# ssh web01 "ls -lh /home/hosts*"
-rw------- 1 www www 25 Mar 28 15:39 /home/hosts
-rw------- 1 www www 363 Mar 28 15:30 /home/hosts.2026-03-28@15:39:50~
# 第二个是备份文件
[root@m01 ~]# ssh web01 "cat /home/hosts.18160.2026-03-28@15:39:50~ |wc -l"
14
# 备份文件依旧是14条记录📝
3)指定字符串写入到目标主机!
# content=“指定字符串”
⚠️'没有src了 --> 取而代之的是content'
[root@m01 ~]# ansible web01 -m copy -a "content='Hello World' dest=/home/my_passwd mode=600"
# 将字符串写入到目标主机!
'后期可以生成rsync的密码文件'
content=rsync_backup:123
# 虽然没有指定属主和属组
# 但从下面的显示我们能看出来是root
web01 | CHANGED => {
"changed": true,
"checksum": "0a4d55a8d778e5022fab701977c5d840bbc486d0",
"dest": "/home/my_passwd",
"gid": 0,
"group": "root",
"md5sum": "b10a8db164e0754105b7a99be72e3fe5",
"mode": "0600",
"owner": "root"
[root@m01 ~]# ssh web01 "ls -lh /home/my_passwd"
-rw------- 1 root root 11 Mar 28 15:55 /home/my_passwd
[root@m01 ~]# ssh web01 "cat /home/my_passwd"
Hello World

cron模块#

Terminal window
cron:
name: # 定时任务的名字
user:# 该定时任务属于哪个用户
'user可省略,免密登录的就是root'
minute: 1-59 # 分钟
hour: 0-23 # 小时
day\month\weekday:#具体参考定时任务
job: # 具体执行的命令
state: # present(默认就是它)
# absent(删除定时任务)
'cron 模块用于在远程主机上管理用户的定时任务(crontab)'
[root@m01 ~]# ssh web01 "crontab -l"
no crontab for root
# 我们远程登录的是root,查看的自然是root的定时任务
==================================
1)打印当前时间并写入文件
[root@m01 ~]# date +"%F_%H:%M"
2026-03-28_17:06
[root@m01 ~]# ansible web01 -m cron -a "name=w_da user=root minute=*/1 job='date +"\%F_\%H:\%M" >/home/date.txt'"
# root可以省略!
web01 | CHANGED => {
"changed": true,
"envs": [],
"jobs": [
"w_da"
]
}
[root@m01 ~]# ssh web01 "crontab -l"
#Ansible: w_da
*/1 * * * * date +%F_%H:%M >/home/date.txt
'明显是有问题的,定时任务不识别%需要加撬棍!'
# 但我明明加的有,没有生效,那我们再加一个!
[root@m01 ~]# ansible web01 -m cron -a "name=w_da minute=*/1 job='date +"\\%F_\\%H:\\%M" >/home/date.txt'"
web01 | CHANGED => {
"changed": true,
"envs": [],
"jobs": [
"w_da"
]
}
[root@m01 ~]# ssh web01 "crontab -l"
#Ansible: w_da
*/1 * * * * date +\%F_\%H:\%M >/home/date.txt
# 这次加上了!
[root@m01 ~]# ssh web01 "ls /home/date.txt"
ls: cannot access '/home/date.txt': No such file or directory
[root@m01 ~]# ssh web01 "ls /home/date.txt"
/home/date.txt
[root@m01 ~]# ssh web01 "cat /home/date.txt"
2026-03-28_17:21
2)实时同步任务
# 每分钟执行1次
[root@m01 ~]# ansible webs -m cron -a 'name=时间同步 minute=* hour=* job="ntpdate ntp1.aliyun.com &>/dev/null"'
[root@m01 ~]# ssh web01 "crontab -l"
#Ansible: w_da
*/1 * * * * date +\%F_\%H:\%M >/home/date.txt
#Ansible: 时间同步
*/1 * * * * ntpdate ntp1.aliyun.com &>/dev/null
==================================
🌰每间隔2小时执行一次命令
00 */2 * * *
🌰每天凌晨4点执行
00 04 * * *
🌰每月1号凌晨4点执行
00 04 01 * *
'更详细的请参考定时任务'
[root@m01 ~]# ansible webs -m cron -a 'name=时间同步 minute=00 hour=04 day=01 job="date >/dev/null"'
[root@m01 ~]# ssh web01 "crontab -l"
#Ansible: 时间同步
00 04 01 * * date >/dev/null
==================================
3)删除定时任务
[root@m01 ~]# ansible web01 -m cron -a "name=时间同步 state=absent"
web01 | CHANGED => {
"changed": true
[root@m01 ~]# ssh web01 "crontab -l |wc -l"
0

setup模块#

Terminal window
ansible web01(主机) -m setup
# 它的主要作用是收集 目标主机 的各种系统信息
'收集包括操作系统、硬件配置、网络设置、已安装的软件包等详细信息'
# ✅️并以 JSON 格式返回
==================================
😭默认会返回海量的信息
[root@m01 ~]# ansible web01 -m setup
web01 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"10.0.0.7",
"172.16.1.7"
],
"ansible_architecture": "x86_64",
# 为了高效地获取特定信息
# 通常会配合 -a "filter=..." 参数使用
# 通过通配符来筛选出所关心的信息
'通配符 * '
1)列出主机的所有 IPv4 地址
[root@m01 ~]# ansible web01 -m setup -a "filter=ansible_all_ipv4_addresses"
web01 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"10.0.0.7",
"172.16.1.7"
]
},
"changed": false
}
==================================
⚠️ 下面这个也是IP地址 --> '但信息更全'
root@m01 ~# ansible all -m setup -a "filter=ansible_default_ipv4"
node2 | SUCCESS => {
"ansible_facts": {
"ansible_default_ipv4": {
"address": "192.168.122.102",
"alias": "ens1",
"broadcast": "192.168.122.255",
"gateway": "192.168.122.1",
"interface": "ens1",
"macaddress": "52:54:00:0c:8f:a3",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "192.168.122.0",
"prefix": "24",
"type": "ether"
},
"discovered_interpreter_python": "/usr/bin/python3.12"
},
"changed": false
}
2)获取主机名
[root@m01 ~]# ansible web01 -m setup -a "filter=ansible_hostname"
web01 | SUCCESS => {
"ansible_facts": {
"ansible_hostname": "web01"
},
"changed": false
}
3)获取CPU核心数
[root@m01 ~]# ansible web01 -m setup -a "filter=ansible_processor_vcpus"
web01 | SUCCESS => {
"ansible_facts": {
"ansible_processor_vcpus": 1
},
"changed": false
}
4)获取总内存大小
[root@m01 ~]# ansible web01 -m setup -a "filter=ansible_memtotal_mb"
web01 | SUCCESS => {
"ansible_facts": {
"ansible_memtotal_mb": 948
# 返回主机的总内存容量,单位为 MB
},
"changed": false
}
==================================
⚠️ 下面这个是详细内存大小
root@m01 ~# ansible all -m setup -a "filter=ansible_memory_mb"
node2 | SUCCESS => {
"ansible_facts": {
"ansible_memory_mb": {
"nocache": {
"free": 1809,
"used": 151
},
"real": {
"free": 1660,
"total": 1960,
"used": 300
},
"swap": {
"cached": 0,
"free": 2047,
"total": 2047,
"used": 0
}
},
"discovered_interpreter_python": "/usr/bin/python3.12"
},
"changed": false
}
5)获取所有eth0网卡的信息.
[root@m01 ~]# ansible web01 -m setup -a "filter=ansible_eth0"
web01 | SUCCESS => {
"ansible_facts": {
"ansible_eth0": {
"active": true
"ipv4": {
"address": "10.0.0.7",
"broadcast": "10.0.0.255",
"netmask": "255.255.255.0",
"network": "10.0.0.0",
"prefix": "24"
}
.......
6)获取操作系统发行版信息
[root@m01 ~]# ansible web01 -m setup -a "filter=ansible_distribution*"
web01 | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "Kylin Linux Advanced Server",
"ansible_distribution_file_parsed": true,
"ansible_distribution_file_path": "/etc/os-release",
"ansible_distribution_file_variety": "NA",
"ansible_distribution_major_version": "V10",
"ansible_distribution_release": "Lance",
"ansible_distribution_version": "V10"
},
"changed": false
}

扩展模块#

lineinfile模块#

Terminal window
'只修改特定行,而不会破坏文件的其他内容'
核心逻辑是:在目标文件中查找匹配的行,如果存在则替换,如果不存在则添加
path: 指定要修改的文件路径
regexp: 用于匹配文件中现有行的正则表达式
regexp='.*old_config.*':匹配到包含old_config的行
line: 你希望文件中最终存在的那一行内容
state: present(创建行、默认)或 absent(删除行)
insertafter=EOF:显式指定插入文件末尾
'它后面也可以跟正则表达式!'
insertafter='^Match':在以Match开头的行,下面插入
✅️用单引号把正则表达式括起来!✅️
==================================
# 环境准备
[root@m01 ~]# ansible web01 -m file -a "path=/home/test.txt state=touch"
'在创建的时候规划好,权限、属组'
web01 | CHANGED => {
"changed": true,
"dest": "/home/test.txt",
"gid": 0,
"group": "root",
"mode": "0644",
"owner": "root",
"size": 0,
"state": "file",
"uid": 0
}
[root@m01 ~]# ssh web01 "ls /home/test.txt"
/home/test.txt
# 创建这个文件!
[root@m01 ~]# ssh web01 "echo pip3.8 >/home/test.txt"
[root@m01 ~]# ssh web01 "echo install >>/home/test.txt"
[root@m01 ~]# ssh web01 "echo https >>/home/test.txt"
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
https
# 往里面写点东西!!
1)在文件末尾写入Hello World
'添加指定行'
[root@m01 ~]# ansible web01 -m lineinfile -a "path=/home/test.txt line='Hello World' insertafter=EOF"
# 只需要三个参数:路径、最终那一行的内容、插入的位置
web01 | CHANGED => {
"backup": "",
"changed": true,
"msg": "line added"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
https
Hello World
2)在指定位置插入jiu
'添加指定行'
[root@m01 ~]# ansible web01 -m lineinfile -a "path=/home/test.txt line=jiu insertafter='^install'"
'在以install开头的行,下面插入一行!'
web01 | CHANGED => {
"backup": "",
"changed": true,
"msg": "line added"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
✅️jiu
https
Hello World
3)修改(替换)指定行
[root@m01 ~]# ansible web01 -m lineinfile -a "path=/home/test.txt regexp=^jiu line=shi"
# 先正则找到jiu,最后line换成指定行的内容
web01 | CHANGED => {
"backup": "",
"changed": true,
"msg": "line replaced"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
shi
https
Hello World
4)删除匹配到的行
# 用absent
[root@m01 ~]# ansible web01 -m lineinfile -a "path=/home/test.txt regexp=^https state=absent"
# 也是只需要三个参数:路径、regexp匹配行、absent删除行
web01 | CHANGED => {
"backup": "",
"changed": true,
"found": 1,
"msg": "1 line(s) removed"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
shi
Hello World

blockinfile模块#

Terminal window
'专门用来管理一大段配置块的'
path: 目标文件路径
# 目标文件得存在!!touch
block: 你要写入的那一大段内容
Playbook 中通常用 | (管道符) 来定义多行文本
命令行 中,通常需要用引号包裹,并用 \n 表示换行
marker: 标记行(可自定义)
默认是 # {mark} ANSIBLE MANAGED BLOCK
⚠️这个标记行必须有!
'自定义标记,参考下一篇笔记!📚'
state: present (创建块) 或 absent (删除块)
insertafter='^pip' 先正则匹配在插入block
'默认在文件末尾创建!'
✅️用单引号把正则表达式括起来!✅️
指定位置:'仅对“新块”有效'
第二次及以后,都是直接替换marker标记中的块
==================================
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
shi
Hello World
# 还是它
1)创建block(整段内容)
'默认在文件末尾创建!'
[root@m01 ~]# ansible web01 -m blockinfile -a "path=/home/test.txt block='01\n 02\n'"
# 中间有个空格!
web01 | CHANGED => {
"changed": true,
"msg": "Block inserted"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
shi
Hello World
# BEGIN ANSIBLE MANAGED BLOCK
01
02
# END ANSIBLE MANAGED BLOCK
'它把空格也写入进去了'
2)替换块
# 因为有标记marker(自动创建)
# 所以替换起来非常方便!
[root@m01 ~]# ansible web01 -m blockinfile -a "path=/home/test.txt block='new_01\nnew_02\n'"
'命令行敲起来容易搞混'
# 两个n,哪个用来换行❓️
web01 | CHANGED => {
"changed": true,
"msg": "Block inserted"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
shi
Hello World
# BEGIN ANSIBLE MANAGED BLOCK
new_01
new_02
# END ANSIBLE MANAGED BLOCK
3)删除块
[root@m01 ~]# ansible web01 -m blockinfile -a "path=/home/test.txt state=absent"
# 只需要路径、absent删除;会根据marker标记寻找!
web01 | CHANGED => {
"changed": true,
"msg": "Block removed"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
install
shi
Hello World
4)指定位置插入块
'仅对“新块”有效'
[root@m01 ~]# ansible web01 -m blockinfile -a "path=/home/test.txt insertafter=^pip block='new_01\nnew_02\n'"
'默认是在文件末尾!'
# 指定位置插入
web01 | CHANGED => {
"changed": true,
"msg": "Block inserted"
}
[root@m01 ~]# ssh web01 "cat /home/test.txt"
pip3.8
# BEGIN ANSIBLE MANAGED BLOCK
new_01
new_02
# END ANSIBLE MANAGED BLOCK
install
shi
Hello World

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Ansible开篇
https://www.kpyun.fun/posts/automation/ansible/ansible01/
作者
久棹
发布于
2026-05-20
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
久棹
只要胆子大,天天寒暑假!
公告
欢迎来到久棹的技术小站!本站专注 Linux 运维学习笔记分享,如有问题欢迎交流探讨 🎉
分类
标签
站点统计
文章
98
分类
11
标签
203
总字数
244,453
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.5
文章许可
CC BY-NC-SA 4.0

文章目录