一、实验环境
沿用前两天的环境
使用 template 模板机 克隆下方 4 台虚拟机,配置好 IP 地址后,通过 WindTerm 远程连接虚拟机,作为 Ansible 控制节点与被管理节点。

二、Ansible 模块进阶
2.1 setup 模块
- setup 模块 用于采集被管理主机的系统事实信息,这些信息被称为 Facts。
ansible_facts变量用于存储采集到的系统信息,供后续任务调用。- 每次执行 Playbook 时,默认第一个任务就是 Gathering Facts (收集事实信息)。

- 使用
setup模块可以手动查看或过滤这些 Facts 信息。
常用命令示例:
bash
# 查看所有 Facts 信息
ansible test -m setup
192.168.8.11 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.1.11"
... 省略部分内容...
- 找出下列facts信息(有父子关系时使用.分隔)
- ansible_bios_version(版本号)
- ansible_memtotal_mb(内存)
- ansible_hostname(主机名)
bash
# 过滤指定 Facts(使用 filter 参数)
ansible web1 -m setup -a "filter=ansible_bios_version" #过滤版本号
ansible web1 -m setup -a "filter=ansible_memtotal_mb" #过滤内存
ansible web1 -m setup -a "filter=ansible_hostname" #过滤主机名
常用 Facts 变量 (有父子关系时使用 . 分隔调用):
ansible_bios_versionansible_memtotal_mbansible_hostnameansible_all_ipv4_addressesansible_ens160.ipv4.address
2.2 debug 模块
debug 模块 可以显示变量的值。debug取的值源自于与setup模块获取的Facts。主要用于辅助排错和验证变量内容。
常用参数:
msg:要输出的信息(引用变量时需要使用{``{ }})
示例 Playbook:
yaml
---
- hosts: web1
tasks:
- debug:
msg: "主机名是:{{ ansible_hostname }}"
- debug:
msg: "总内存大小:{{ ansible_memtotal_mb }}"
补充:var模块也可以直接调用变量且不需要加双花括号,但是不能和字符串一起使用,只能单独调用字符串
三、Ansible 变量
Ansible 支持22种定义变量的方式,本节介绍其中最常用的四种,并按优先级从高到低排序:
- 变量文件(vars_files:) :通过
vars_files:引入外部YAML文件加载的变量 - Playbook 变量(vars:) :在 Play 中使用
vars:定义, Play 级别的变量,仅在单个play生效 - Host Facts 变量(系统常数) :由
setup模块收集的系统信息 - Inventory 变量(主机变量和组变量):静态定义在主机清单(Inventory)文件,或者 host_vars/、group_vars/ 目录中的变量。
补充:官方文档的详细优先级顺序(了解):

3.1 Inventory 变量
Inventory 变量是在主机清单配置文件(hosts)中为特定主机(组)定义的变量。
hosts 文件示例:
ini
[webserver]
web[1:2]
[db]
db1 myvar1="hello the world" myvar2="content" #为db1生成两个变量
#写在哪个主机后边,这个变量就属于谁
[cluster:children] #children代表下面是组成员
webserver
db
[webserver:vars] # 为webserver组定义变量,改组成员均可调用。vars 是固定关键字,代表下面的是变量
yourname="tina"
编写调用 Inventory 变量的 Playbook,执行playbook验证:
yaml
---
- hosts: db1
tasks:
- name: use inventory vars
shell: echo {{ myvar1 }} > /tmp/{{ myvar2 }} # 这里 {{}} 不在开头,所以不需要加双引号
- hosts: webserver
tasks:
- name: use inventory vars yourname
user:
name: "{{ yourname }}" # {{}} 开头时需要加双引号
3.2 Host Facts 变量
Host Facts 变量可以直接调用 setup 模块收集到的系统信息。
示例 Playbook:
yaml
---
- hosts: web1
tasks:
- name: Host Facts 变量应用
copy:
content: "{{ ansible_hostname }}:{{ ansible_bios_version }}"
dest: /tmp/facts.txt
3.3 Playbook 变量
在 Playbook 中使用 vars 关键字定义变量。
注意:仅在当前 play 内有效
示例 Playbook:
yaml
---
- hosts: web1
vars:
iname: heal
ipass: '123456' # 密码必须是字符串,需要加引号
tasks:
- name: Use variables create user.
user:
name: "{{ iname }}"
password: "{{ ipass | password_hash('sha512') }}"
3.4 变量文件
将变量单独定义在一个 YAML 文件中,在 Playbook 中通过 vars_files 调用。
变量文件 variables.yml :
注意格式为键值对
yaml
---
iname: cloud
ipass: '123456'
调用变量文件的 Playbook:
yaml
---
- hosts: web1
vars_files: variables.yml #调用YAML变量文件
tasks:
- name: create user.
user:
name: "{{ iname }}" # iname 来自变量文件
password: "{{ ipass | password_hash('sha512') }}" # ipass 来自变量文件
四、Ansible 进阶
4.1 template 模块
-
template 模块作用类似于copy,都可以拷贝文件。
-
copy模块只能复制静态文件。 -
template模块主要用于生成动态文件,并且可以调用文件中的变量如果希望每个主机拷贝的文件内容不一样(例如包含各自的 IP 或主机名),就需要使用
template模块。 -
template模块结合 Jinja2 模板引擎 ,可以实现动态内容生成。- Jinja2 表达式语法:
{``{ 变量 }} - 之前在 Playbook 中调用变量,实际上也是 Jinja2 的功能。
template模块的主要作用是生成动态配置文件。
- Jinja2 表达式语法:
示例 1:为 webserver 组中的主机生成不同内容的首页
模板文件 index.html:
html
Welcome to {{ ansible_hostname }} on {{ ansible_ens160.ipv4.address }}.
# 模板文件中调用变量不需要双引号
Playbook:
yaml
---
- hosts: webserver
tasks:
- name: use template copy index.html to webserver.
template:
src: ~/ansible/template/index.html
dest: /usr/local/nginx/html/index.html
示例 2:使用自定义变量
- template 使用 .j2 后缀模板文件
- 例如:nginx.conf.j2
- 里面可以写 {{ 变量名 }}
模板文件 source.j2:
jinja2
{{ welcome }} {{ iname }} ...
Playbook:
yaml
---
- hosts: webserver
vars:
welcome: 'hello' #为变量赋值
iname: 'jack' #为变量赋值
tasks:
- name: 使用template模块将文件复制到远程主机
template:
src: ~/ansible/template/source.j2 #调用文件,调用文件中的变量
dest: /tmp/
4.2 Ansible 高级语法(重要)
4.2.1 error 处理机制
默认情况下,Ansible 遇到错误会立即停止 Playbook 的执行。
忽略单个任务的错误:
yaml
---
- hosts: web1
tasks:
- name: start a service that does not exist.
service:
name: hehe
state: started
ignore_errors: true # 与模块对齐,则仅针对当前模块任务,忽略错误
- name: touch a file.
file:
path: /tmp/service.txt
state: touch
忽略整个play的错误:
yaml
---
- hosts: web1
ignore_errors: true # 当前 Play 内忽略错误
tasks:
- name: start a service that does not exist.
service:
name: hehe
state: started
- name: touch a file.
file:
path: /tmp/service.txt
state: touch
4.2.2 handlers
handlers是一种特殊的任务,只在被其他任务通过 notify 通知后(任务处于changed 状态时),才在 Playbook 结束时执行一次,通常用于触发服务的重启或重载等收尾操作。
注意:即使被多次 notify,也只执行一次。
handlers 的特点:
- 仅当任务触发(
notify)handlers 时才执行(不触发不执行)。 - 多个任务 notify 同一个 handler 时,只会执行一次。
- 仅当任务的执行状态为
changed时,handlers 才会执行。 - handlers 任务在当前play内的所有其他 tasks 执行完毕后才执行。
注意:一个play只能有一个handlers(但一个剧本可以有多个play)
示例:
yaml
---
- hosts: web1
tasks:
- name: 创建一个目录 #多次执行该任务,playbook状态将不再是changed
file:
path: /tmp/parents/subdir/
state: directory
notify: touch file # notify 后面的名称必须与 handlers 中的任务名称一致
#notify和模块处于同一层级
handlers: #handlers和tasks属于同一层级
- name: touch file
file:
path: /tmp/parents/subdir/new.txt
state: touch
注意:notify 后面的名称必须与 handlers 中的任务名称一致
细节补充:即使使用了ignore_errors: true,也不会影响handlers的触发条件,只有changed状态可以触发
4.2.3 when 条件判断
使用 when 可以为任务添加执行条件,只有条件为真时才执行该任务。
常用比较操作符 :==、!=、>、>=、<、<=
多个条件可以使用 and 或 or 连接。
注意 :
when表达式中引用变量时不需要 使用{``{ }}。
示例 1:内存不足 700MB 时停止 NetworkManager 服务
yaml
---
- hosts: web1
tasks:
- name: 检查内存,内存不足 700MB 时停止 NetworkManager 服务
service:
name: NetworkManager
state: stopped
when: ansible_memfree_mb < 700
示例 2:判断操作系统为 Rocky 8 时创建文件(多行条件)
yaml
---
- hosts: web1
tasks:
- name: 判断操作系统为 Rocky 8 时创建文件
file:
path: /tmp/when.txt
state: touch
when: >
ansible_distribution == "Rocky" #注意:字符串要加双引号
and
ansible_distribution_major_version == "8"
注意!虽然when写在模块尾部,但是优先进行判断
补充:when主流写法写在模块尾部
bash
- name: 安装 Nginx
apt:
name: nginx
state: present
when: ansible_os_family == "Debian" # 习惯写在尾部
非主流也可以写在头部,并且更符合执行逻辑:
bash
- name: 安装 Nginx
when: ansible_os_family == "Debian" # 👈 其实写在头部完全合法
apt:
name: nginx
state: present
4.2.4 block 任务块
使用 block 可以将多个任务组合成一个组,方便统一管理。
block 还支持 rescue 和 always 子句:
rescue:block 中任务执行失败时执行的补救任务。always:无论 block 是否成功,都会执行的任务。
基础 block 示例:
yaml
---
- hosts: db1
tasks:
- name: 定义一组任务
block:
- name: 安装 httpd
yum:
name: httpd
state: present
- name: start httpd
service:
name: httpd
state: started
when: ansible_distribution == "Rocky" #满足此条件时,执行block任务块
rescue + always 示例:
yaml
---
- hosts: web1
tasks:
- block:
- name: 任务块创建 test1.txt
file:
path: /tmp/test1.txt
state: touch
rescue:
- name: 任务块执行失败会创建 test2.txt
file:
path: /tmp/test2.txt
state: touch
always:
- name: 任何情况下都会创建 test3.txt
file:
path: /tmp/test3.txt
state: touch
直接运行剧本,会有test1.txt和test3.txt
将block改成: path: /tmp/abc/test1.txt,会有test2.txt和test3.txt
为什么当block报错整个任务不会停止?因为整个 block: + rescue: + always: 算 Ansible 的「一个任务单元」,单个任务失败不会让剧本停止,且rescue 就是专门用来捕获 block 里的失败,不让任务中断
4.2.5 loop 循环
当多个任务使用相同模块时,可以使用 loop 循环来避免重复编写。
简单列表循环:
yaml
---
- hosts: test
tasks:
- name: 借助loop循环依次创建school,legend,life目录
file:
path: /tmp/{{ item }} # item 是循环中的固定关键字
state: directory
loop: #loop中的所有元素会逐个丢给item
- School
- Legend
- Life
复杂字典列表循环:
yaml
---
- hosts: web1
tasks:
- name: 使用loop循环完成用户的批量创建,并设置密码
user:
name: "{{ item.iname }}" #注意,这里'tiem'属于固定格式
password: "{{ item.ipass | password_hash('sha512') }}"
loop: # 循环成员为字典
- { iname: 'term', ipass: '123456' }
- { iname: 'amy', ipass: '654321' }
这里item是固定格式,必须通过 item.元素 来调用定义的字典元素
五、Ansible Roles
5.1 Roles 概述
在实际生产环境中,随着功能增多,Playbook 文件会越来越多,且可能互相调用变量文件等,管理起来非常困难。
Ansible 从 1.2 版本开始支持 Roles 。
Roles 是管理ansible文件的一种规范(目录结构),用于更好地组织和管理 Playbook、变量、模板、handlers 等文件。
我们需要使用命令创建角色,修改目录下的对应文件内容或者添加内容,最后在剧本中调用它。
ansible的角色(role)像是做软件开发一样。开发是做一堆配置文件,功能文件,然后再main文件中进行集成;role就像是把配置文件,资源文件放到对应目录,然后在剧本中集成/调用
5.2 Roles 规范的目录结构
| 目录 / 文件 | 作用说明 |
|---|---|
defaults/main.yml |
定义变量的缺省值(即变量默认值,优先级较低) |
files/ |
存放静态文件(普通文件) |
handlers/main.yml |
定义 handlers |
meta/main.yml |
角色元数据(作者、版本、依赖等) |
README.md |
整个角色的说明文档 |
tasks/main.yml |
定义任务的核心文件 |
templates/ |
存放 Jinja2 动态模板文件 |
vars/main.yml |
定义变量(优先级较高) |
不同模块会自动到对应目录下查找数据。
标准的目录结构:
bash
role_name/
├── defaults/ # 默认变量(优先级最低)
│ └── main.yml
├── vars/ # 角色变量(优先级高于 defaults)
│ └── main.yml
├── tasks/ # 主要任务列表(必须有)
│ └── main.yml
├── handlers/ # 触发器,用于触发服务重启等
│ └── main.yml
├── templates/ # Jinja2 模板文件(.j2)
│ └── *.j2
├── files/ # 静态文件,直接拷贝
│ └── *
├── meta/ # 角色元数据(依赖、作者、版本等)
│ └── main.yml
└── README.md # 角色说明文档(可选)
5.3 Roles 应用
5.3.1 创建 Role
使用 ansible-galaxy init <rolename> (init初始化) 命令可以创建、管理自己的roles
bash
mkdir ~/ansible/roles
ansible-galaxy init ~/ansible/roles/issue
#创建一个Role,该Role的目的是使用模板修改远程主机的/etc/issue文件
tree ~/ansible/roles/issue/
#查看目录结构,如果没有tree命令则需要使用dnf安装该软件
会发现生成了这样结构的目录:

5.3.2 修改 Role
- 定义issue文件的模板文件
模板文件 templates/issue.j2:
bash
[root@control ansible]# vim ~/ansible/roles/issue/templates/issue.j2
This is the system {{ ansible_hostname }}
Today's date is: {{ ansible_date_time.date }}
Contact to {{ admin }}
定义变量文件 vars/main.yml:
yaml
[root@control ansible]# vim ~/ansible/roles/issue/vars/main.yml
#定义变量admin
---
admin: yoyo@test.com
编写任务文件 tasks/main.yml(注意!Role 中的任务文件不需要写 tasks: 关键词):
Role的各个文件之间 相互调用不需要写路径,直接写文件名即可
yaml
[root@control ansible]#vim ~/ansible/roles/issue/tasks/main.yml
---
- name: delever issue file
template:
src: issue.j2
dest: /etc/issue
5.3.3 在 Playbook 中调用 Role
在Playbook中调用角色有两种方法,我们使用第二种方法。
方法一 :在role同级目录创建 Playbook 直接调用。
方法二 :在 ansible.cfg 中指定 roles_path=路径。
bash
[root@control ansible]# vim ~/ansible/ansible.cfg
[defaults]
inventory = ~/ansible/hosts
remote_user = alice
roles_path = ~/ansible/roles #指定Roles读取位置
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False
编写playbook文件,通过roles关键词调用role(调用角色使用角色名即可)
bash
[root@control ansible]# cat ~/ansible/issue.yml
---
- hosts: web2
roles: #调用Role
- issue
# - role2 #支持加载多个Role
[root@control ansible]# ansible-playbook issue.yml
六、总结
本节重点掌握内容:
- 特殊模块 :
setup、debug、template - 变量管理:Inventory 变量、Host Facts 变量、Playbook 变量、变量文件
- 高级语法 :error 处理机制(
ignore_errors)、handlers、when 条件判断、block 任务块、loop 循环 - Roles:角色作用、标准目录结构、角色创建与调用