Ansible开篇

Ansible开篇
[TOC]
web服务回顾

架构图

-
🧱 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 安装全局包
root@m01 ~# pip install ansible× This environment is externally managed# 外部环境托管❌️ 在 Ubuntu 24.04 上直接 pip install 会报错📌 为什么会这样?
Ubuntu 系统自身很多==核心功能==(软件包管理器、系统工具)依赖 Python
- 如果你用
pip全局乱装包,版本冲突可能直接把系统干崩✅️ 所以 Ubuntu 24.04 一刀切:想装东西?先进虚拟环境,或者用 pipx(不需要进虚拟环境)
💡 所以就有了今天要讲的四个工具,它们分两派:
| 派别 | 成员 | 干什么的 |
|---|---|---|
| 环境隔离派 | venv 、uv | 创建独立的 Python 运行空间,装了啥都不影响系统 |
| 包安装派 | pip 、pipx | 往某个环境里装包,区别是一个装库、一个装 CLI 工具 |
这两派是配合关系,不是竞争关系: 先用
venv(或uv)开个房间 → 再用pip(或uv pip)往房间里搬东西 或者直接用pipx——它自己把”开房间 + 搬东西”一步搞定,==专给 CLI 工具用==
四大工具通俗讲解
pip — Python 的”万能安装器”
# 没进 venv → 装到系统全局(Ubuntu 24.04 会拦截)pip install requests====================================# 先进 venv → 装到虚拟环境里,不影响系统source .venv/bin/activate(.venv) pip install requests # 只装在这个 venv 里(.venv) pip install ansible # 也只在这个 venv 里,退出就找不到了====================================📌 pip 现在主要在'虚拟环境里面'用,装到 venv 里而不是系统里🔹 pip 只管”送到当前环境”,不管隔离——当前环境是哪个就装到哪个
🔹 如果所有包都往系统塞 → 版本一冲突就炸锅
🔹 Ubuntu 24.04 禁止往系统塞:externally-managed-environment
💡 pip 装的东西分两种——搞清楚这个,后面 pipx 才好理解:
requests(库) | ansible(CLI 工具) | |
|---|---|---|
| 本质 | 给 Python 代码 import 用的 | 在终端直接敲命令用的 |
| 怎么用 | import requests | ansible --version |
| 终端能敲吗 | ❌ requests → command not found | ✅ 能敲,有可执行入口 |
| 谁装 | pip / uv pip | pip / uv pip / ==pipx== |
# requests 是库——终端敲不了root@m01 ~# requestsbash: requests: command not found====================================# 只能在 Python 代码里 importimport requestsresp = requests.get("https://api.github.com")print(resp.status_code) # 200📌 这就是 pip 和 pipx 的分界线:pipx 不装库,因为库装了也没命令可敲 pipx 只装 ansible、black、httpie 这种能直接在终端敲的 CLI 工具
venv — Python 的”房间隔离术”
[root@Master ~]# python3 -VPython 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 -lhtotal 0drwxr-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 --versionansible [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 工具的”单身公寓”
1)安装 pipx[root@Master kpyun]# yum -y install pipx[root@Master kpyun]# pipx --version1.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 - uvxdone! ✨ 🌟 ✨✅️ '配置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 --versionuv 0.11.19 (x86_64-unknown-linux-gnu)# ✅ 不需要 activate!
4)即使在虚拟环境中也是可以找到的[root@Master kpyun]# source ./myven/bin/activate((myven) ) [root@Master kpyun]# uv --versionuv 0.11.19 (x86_64-unknown-linux-gnu)📌 pipx 的核心设计理念:
| 特性 | 说明 |
|---|---|
| 安装对象 | 只装 CLI 工具(ansible、black、httpie…),不装库 |
| 隔离方式 | 每个工具一个独立虚拟环境,藏在 ~/.local/share/pipx/venvs/ |
| 暴露方式 | 只在 ~/.local/bin/ 放一个链接,全局可直接敲命令 |
| 退出环境还能用吗 | ✅ 能!装完就是全局的,跟虚拟环境无关 |
[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 ✅️ 全局可直接敲命令| 对比维度 | pip | pipx |
|---|---|---|
| 主要用途 | 装库(可被 import)和 CLI 工具 | 只装 CLI 工具(ansible、black 等) |
| 环境隔离 | ❌ 不隔离,全塞一起 | ✅ 每个工具独立环境 |
| 依赖冲突 | 高 — A 要 v1,B 要 v2,炸了 | 低 — 各住各的 |
| 全局可用 | 装在哪就在哪用 | ✅ 装完即全局可用 |
| 适用场景 | 在 venv 里给项目装依赖 | 全局装 Python 小工具 |
| 装库可以吗 | ✅ pip install requests | ❌ pipx 不装库 |
📌 选哪个?
- 你在项目 venv 里装依赖 →
pip(或uv pip) - 你想全局随时用 ansible / black / httpie →
pipx
💡 pipx 解决了什么问题?
你想要全局敲 ansible 命令,但 Ubuntu 又禁止你全局 pip install
✅️ pipx 说:“我帮你装一个独立的 ansible 环境,再给你一把全局的钥匙,完美!”
⚠️ pipx 只能装 CLI 工具,不能装库(比如 requests 你还是要用 pip 在 venv 里装)
🔹 pipx 在虚拟环境内 ==依然== 是全局安装 — 它的设计就是全局可用,跟你当前在不在 venv 没关系
uv — 极速版的 venv + pip 合体
一句话:uv 就是把
venv和pip用 Rust 重写了一遍,速度直接起飞创建虚拟环境 + 装包,这两件事它干得比原版快 10-100 倍
顺便还能指定 Python 版本——创建环境时自动下载,不用你手动装
📌 uv 到底快在哪里?
| 对比维度 | venv + pip | uv |
|---|---|---|
| 底层语言 | Python | Rust |
| 创建环境 | 复制整个 Python 解释器(3-5 秒) | 符号链接/硬链接(毫秒级) |
| 装包速度 | 逐个下载解压安装 | 并行处理,全局缓存复用 |
| 包占用磁盘 | 每个环境复制一份 | 所有环境共享一份缓存,==硬链接==指向 |
| Python 版本管理 | ❌ 需额外装 pyenv | ✅ 内置 uv python install 3.14 |
| 依赖锁定 | ❌ 需配合 pip-tools | ✅ 内置 uv.lock 精确锁定 |
📌 uv 的”零成本复用”是怎么做到的?
10 个虚拟环境都要用 requests 这个库:
venv + pip:下载 1 次,复制 10 次 → 10 份磁盘空间uv:下载 1 次,10 个环境全部用硬链接指向同一份文件 → 1 份磁盘空间 + 10 个指针- 省磁盘、省时间,而且每个环境看起来都”有自己的那份”
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 - uvxdone! ✨ 🌟 ✨✅️ '配置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.14cpython-3.14.5-linux-x86_64-gnu (download)'自动下载 Python 3.14'Using CPython 3.14.5Creating virtual environment at: hahaActivate 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 --versionansible [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 -VPython 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 --versionpip 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 --versionansible [core 2.21.0]✅️ pip可以直接安装在系统中推荐组合:Ubuntu 24.04 最佳实践
📌 等等——Ansible 这类工具,真的需要进 venv 吗?
上面演示的是”在虚拟环境里装 Ansible”,但其实对于 Ansible 这类 CLI 工具,更推荐直接用 pipx 全局安装:
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)一键全局安装,无需 venvroot@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-vaultdone! ✨ 🌟 ✨root@m01 ~# ansible --versionansible [core 2.21.0]'核心引擎,只有最基本的模块'root@m01 ~# ansible-community --versionAnsible community version 14.0.0'核心引擎 + 一堆 Collections(社区模块包)'# 14.0.0 不是低版本,它俩的"14"和"2.21"是完全独立的编号体系
3)更改hosts文件root@m01 ~# cat >> /etc/hosts <<EOF10.0.0.111 node110.0.0.112 node210.0.0.113 node3EOF'测试了联通性'root@m01 ~# ping -W2 -c3 node2PING 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 ms64 bytes from node2 (10.0.0.112): icmp_seq=2 ttl=64 time=0.254 ms64 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 2027msrtt min/avg/max/mdev = 0.204/0.340/0.564/0.159 ms- 关机拍快照
📌 Ubuntu 24.04 完整工具链:
apt install pipx → pipx install uv → 日常用 pipx 装 CLI 工具,用 uv venv 管项目环境 & uv pip 安装软件
💡 为什么 Ansible 适合 pipx 全局装?
Ansible 是你天天要在终端敲的命令,不是某个项目的依赖库
- pipx 给它一个独立的”单身公寓”,依赖隔离,但命令全局通——完美匹配
实战(旧版):源码编译安装 Python + 全局 pip 安装 Ansible
⚠️ 以下为旧版 kylin/CentOS 环境下的操作,Ubuntu 24.04 不适用,仅作参考
1)准备一台服务器kylin# IP 10.0.0.81 主机名称 m01'1核2G内存'[root@m01 ~]# mkdir /server/tmp[root@m01 ~]# cd /server/tmp/[root@m01 tmp]# python3 -VPython 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]# lsPython-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 altinstallmake -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 -VPython 3.8.16[root@m01 ~]# python3 -VPython 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 -VPython 3.8.16'这次就找到了!'
5)用 pip 安装 Ansible[root@m01 ~]# pip3.8 install ansible -i https://mirrors.aliyun.com/pypi/simple/[root@m01 ~]# ansible --versionansible [core 2.13.13]'两个-'# 查看版本号,验证是否安装!
6)创建 Ansible 配置文件[root@m01 ~]# mkdir /etc/ansible[root@m01 ~]# vim /etc/ansible/ansible.cfg'配置文件'# 默认没有、手动创建的[defaults]host_key_checking = Falsedeprecation_warnings = Falseinterpreter_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]# 它会先显示文件修改前和修改后的内容差异主机清单
清单是 Ansible 管理节点的”通讯录”——告诉 Ansible 要操控哪些主机
📌 常见格式:==INI==(推荐,简洁,有高亮显示)和 ==YAML==
默认路径 /etc/ansible/hosts,pip 安装后需手动创建
默认配置文件: /etc/ansible/hosts'需要手动创建!'[root@m01 ~]# ll /etc/ansible/hostsls: cannot access '/etc/ansible/hosts': No such file or directory==================================定义主机清单的方式:(1)单台定义[root@m01 ~]# vim /etc/ansible/hosts10.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.xxxweb01 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 pingweb01 | 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/hostsweb01 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 successweb01 | SUCCESS => {web02 | SUCCESS => {web03 | SUCCESS => {
(4)通过主机名定义'新开一个机器lb01'[root@m01 ~]# cat /etc/hosts | grep lb01172.16.1.5 lb01# 这里是172.xxx[root@m01 ~]# grep 172.16.1.5 /etc/ansible/hosts172.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/hostslb01 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'# 但如果我们这样配置!依旧没有ansible_ssh_host[root@m01 ~]# ansible lb01 -m pinglb01 | SUCCESS => { "changed": false, "ping": "pong"}'但这个主机名可以从/etc/hosts中读取--》172.16.1.5'# 域名解析
(5)支持序列的定义方式[root@m01 ~]# grep 'web0[123]' /etc/hosts172.16.1.7 web01172.16.1.8 web02172.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/hosts172.16.1.5 lb01172.16.1.7 web01172.16.1.8 web02172.16.1.9 web03172.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]websback# 我只想webs和backup执行,lb组不执行!lnmp:组名(随便起)children:⚠️固定写法,'子组'[root@m01 ~]# ansible lnmp -m ping | grep -i successweb02 | SUCCESS => {backup | SUCCESS => {web01 | SUCCESS => {web03 | SUCCESS => {# 刚好是4台机器
(7)设置变量'vars’'1)all给全部设置相同的变量[root@m01 ~]# vim /etc/ansible/hosts[webs]web01web02web03
[lb]lb01
[back]backup
[all:vars]ansible_ssh_user=rootansible_ssh_pass='oldboy123.com''⚠️这两个不同的变量也得换行!'[root@m01 ~]# ansible all -m ping| grep -i successweb02 | SUCCESS => {lb01 | SUCCESS => {web03 | SUCCESS => {web01 | SUCCESS => {backup | SUCCESS => {
2)给某几个组设置变量# 前提是得有children(子组)# 再给子组设置变量[root@m01 ~]# cat /etc/ansible/hosts[webs]web01web02web03
[lb]lb01 ansible_ssh_user=root ansible_ssh_pass='oldboy123.com'
[back]backup
[lnmp:children]websback
[lnmp:vars]ansible_ssh_user=rootansible_ssh_pass='oldboy123.com'# 给组设置变量# 在lnmp组中的所有主机共同使用定义好的vars变量[root@m01 ~]# ansible lnmp -m ping| grep -i successweb02 | SUCCESS => {web01 | SUCCESS => {web03 | SUCCESS => {backup | SUCCESS => {免密登录
'后期学完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_keyYour public key has been saved in /root/.ssh/my_key.pubThe key fingerprint is:SHA256:Jgs3AXXpk4jIDguRhkDaeAc8AR1bAUzqzPR4nhiT8fo root@m01The 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.comlb01:172.16.1.5web01:172.16.1.7web02:172.16.1.8web03:172.16.1.9backup:172.16.1.41
(3)修改默认连接的私钥'客户端配置信息'[root@m01 ~]# vim /etc/ssh/ssh_configIdentityFile ~/.ssh/my_key# 默认就用这个私钥连接!
(4)测试验证'/etc/hosts主机有对应的解析!'[root@m01 ~]# cat /etc/hosts127.0.0.1 localhost::1 localhost172.16.1.5 lb01172.16.1.6 lb02172.16.1.7 web01172.16.1.8 web02172.16.1.9 web03172.16.1.10 web04172.16.1.31 nfs01172.16.1.41 backup172.16.1.51 db01172.16.1.52 db02172.16.1.53 db03172.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/hostsweb0[1:3]lb01backup[root@m01 ~]# ansible all -m ping | grep -i successlb01 | SUCCESS => {backup | SUCCESS => {web02 | SUCCESS => {web03 | SUCCESS => {web01 | SUCCESS => {实战:Ansible 基础操作
======'Ubuntu 24.04'======1)创建全局配置文件root@m01 ~# mkdir /etc/ansibleroot@m01 ~# vim /etc/ansible/ansible.cfg[defaults]host_key_checking = Falsedeprecation_warnings = Falseinterpreter_python = /usr/bin/python3[inventory][privilege_escalation][paramiko_connection][ssh_connection][persistent_connection][accelerate][selinux][colors][diff]
2)全局的主机清单⚠️ '需要手动创建!'root@m01 ~# cat /etc/ansible/hosts10.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 ping10.0.0.113 | SUCCESS => { "changed": false, "ping": "pong"}
3)只有主机名这次'无非是IP换为了主机名' --> 需配合/etc/hosts使用root@m01 ~# vim /etc/ansible/hostsnode1 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 pingnode3 | SUCCESS => { "changed": false, "ping": "pong"}root@m01 ~# ansible node3 -a idnode3 | CHANGED | rc=0 >>uid=0(root) gid=0(root) groups=0(root)
4)创建sudo权限文件root@m01 ~# tail -1 /etc/sudoers@includedir /etc/sudoers.droot@m01 ~# echo 'test ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/testroot@m01 ~# cat /etc/sudoers.d/testtest 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 - testLast failed login: Wed Jun 10 06:43:36 CST 2026 from 10.0.0.210 on ssh:nottyThere was 1 failed login attempt since the last successful login.[test@node1 ~]$ sudo -l...................... secure_path=/sbin\:/bin\:/usr/sbin\:/usr/binUser test may run the following commands on node1: (ALL) NOPASSWD: ALL ✅️[test@node1 ~]$ ls /rootls: cannot open directory '/root': Permission denied[test@node1 ~]$ ls /hometest[test@node1 ~]$ sudo ls /root | wc -l0 '使用sudo后可以正常访问'
6)免密登录# master节点分发到node节点的test用户root@m01 ~# ssh-keygenroot@m01 ~# ssh-copy-id test@node1root@m01 ~# ssh-copy-id test@node2root@m01 ~# ssh-copy-id test@node3root@m01 ~# ssh test@node1 'ls /home/'testroot@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.droot@node2's password:' ⚠️ 为什么还需要密码❓️'我们只是对node节点的test用户实现免密登录, root用户依旧要使用密码'test 100% 30 55.6KB/s 00:00root@m01 ~# scp /etc/sudoers.d/test node3:/etc/sudoers.droot@node3's password:'test 100% 30 57.1KB/s 00:00
8)指定远程用户root@m01 ~# grep node1 /etc/ansible/hostsnode1 ansible_ssh_user=root ansible_ssh_port=22 ansible_ssh_pass='1''我已经把root用户的密码输入在这里面了' --> 相当于对root免密登录# 这是远程登录的是root用户root@m01 ~# ansible node1 -a idnode1 | CHANGED | rc=0 >>uid=0(root) gid=0(root) groups=0(root)# 手动指定用户为testroot@m01 ~# cat > /etc/ansible/hosts <<EOFnode1 ansible_ssh_user=testnode2 ansible_ssh_user=testnode3 ansible_ssh_user=testEOF'做完免密可以不需要远程登录的密码了'# 更换了远程登录用户为testroot@m01 ~# ansible node1 -a idnode1 | 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 >>0root@m01 ~# ansible node1 -m shell -a 'ls /root'node1 | FAILED | rc=2 >>ls: cannot open directory '/root': Permission deniedroot@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(全局默认)
📌 规律:谁离项目近谁先用 —> 当前目录 > 家目录 > 全局默认,找到即停
项目级配置实战: 在当前目录放 ansible.cfg + inventory,覆盖全局配置
1)创建项目级 ansible.cfgroot@m01 ~# ansible --version | grep 'config file' config file = /etc/ansible/ansible.cfg'当前走的是全局配置'root@m01 ~# vim ansible.cfg# /root/ansible.cfg — 项目级配置[defaults]inventory = ./inventoryinterpreter_python = auto_silentremote_user = test
[privilege_escalation]become = truebecome_method = sudobecome_user = rootbecome_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→ 不用输密码
root@m01 ~# ansible --version | grep 'config file' config file = /root/ansible.cfg'当前目录的配置文件优先生效!全局的 /etc/ansible/ansible.cfg 被覆盖'
2)创建项目级 inventoryroot@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 ./inventoryroot@m01 ~# echo $(pwd)/rootroot@m01 ~# lsansible.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]' > ./inventoryroot@m01 ~# cat ./inventorynode[1:3]'node[1:3] 展开为 node1 node2 node3'root@m01 ~# ansible node1 -a idnode1 | 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 目录可访问-
命令行
-i指定的清单优先级 >ansible.cfg中定义的清单,多个-i可合并多个清单来源 -
ansible-inventory —list 查看清单结构
1)排查思路root@m01 ~# lsansible.cfg inventoryroot@m01 ~# cat inventorynode[1:3]root@m01 ~# ansible --version | grep 'config file' config file = /root/ansible.cfgroot@m01 ~# cat ./ansible.cfg | grep inventoryinventory = ./inventory✅️ 配置文件中的主机清单就是这个✅ '先确定配置文件 --> 再找对应的主机清单'
2)查看清单结构root@m01 ~# ansible-inventory --list# 查看的就是上面的主机清单{ "_meta": { 📌 内部的元数据 ✅️ # 记录每台主机绑了什么变量、用的什么解析方式 "hostvars": {}, "profile": "inventory_legacy" }, "all": { "children": [ "ungrouped" ] }, "ungrouped": { "hosts": [ "node1", "node2", "node3" ] }}Ansible 有两个内置组,不用定义就存在:
| 组名 | 包含 |
|---|---|
all | 清单中所有主机 |
ungrouped | 没有分到任何组的主机 |
3)改名字# 清单以.ini结尾(有语法高亮)'我们就是以ini格式书写的主机清单'root@m01 ~# mv ./inventory ./inventory.iniroot@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模块
[root@m01 ~]# cat /etc/ansible/hostsweb0[1:3]lb01backup==================================[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 00 -rw------- 1 www www 0 Mar 28 09:52 1.txt0 -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 00 -rwxr-xr-x 1 root root 0 Mar 28 09:52 1.txt0 -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/tmpgroup & user模块
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 useryum & systemd模块
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"⚠️是stoppedweb01 | 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"enabledcommand模块
Ansible 的 command 模块不支持 Shell 的管道符(|)
-
shell模块会通过系统的/bin/sh来执行命令,因此它完全支持管道、重定向等 Shell 特性 -
|、>、<等操作不能直接依赖command模块完成 -
你只需要将
command替换为shell即可
❌️不建议使用command和shell违背了 Ansible 的设计哲学、只有在万不得已的情况下使用✅️💡-m command -a "只读、无副作用的命令"hostname -Ifree -hdf -huptime.......[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模块
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 -l14# 本来就有,共14条记录![root@m01 ~]# echo 192.168.219.128 Centos-7 > ./hosts[root@m01 ~]# cat ./hosts192.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# 虽然没有指定属主和属组# 但从下面的显示我们能看出来是rootweb01 | 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 Worldcron模块
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"0setup模块
ansible web01(主机) -m setup# 它的主要作用是收集 目标主机 的各种系统信息'收集包括操作系统、硬件配置、网络设置、已安装的软件包等详细信息'# ✅️并以 JSON 格式返回==================================😭默认会返回海量的信息[root@m01 ~]# ansible web01 -m setupweb01 | 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模块
'只修改特定行,而不会破坏文件的其他内容'✅ 核心逻辑是:在目标文件中查找匹配的行,如果存在则替换,如果不存在则添加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.8installhttps# 往里面写点东西!!
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.8installhttpsHello 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.8install✅️jiuhttpsHello 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.8installshihttpsHello 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.8installshiHello Worldblockinfile模块
'专门用来管理一大段配置块的'path: 目标文件路径# 目标文件得存在!!touchblock: 你要写入的那一大段内容 在 Playbook 中通常用 | (管道符) 来定义多行文本 在 命令行 中,通常需要用引号包裹,并用 \n 表示换行marker: 标记行(可自定义) 默认是 # {mark} ANSIBLE MANAGED BLOCK ⚠️这个标记行必须有! '自定义标记,参考下一篇笔记!📚'state: present (创建块) 或 absent (删除块)insertafter='^pip' 先正则匹配在插入block'默认在文件末尾创建!'✅️用单引号把正则表达式括起来!✅️指定位置:'仅对“新块”有效'第二次及以后,都是直接替换marker标记中的块==================================[root@m01 ~]# ssh web01 "cat /home/test.txt"pip3.8installshiHello 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.8installshiHello World# BEGIN ANSIBLE MANAGED BLOCK01 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.8installshiHello World# BEGIN ANSIBLE MANAGED BLOCKnew_01new_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.8installshiHello 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 BLOCKnew_01new_02# END ANSIBLE MANAGED BLOCKinstallshiHello World文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!



