ansible管理变量和事实与实施任务控制
在 Ansible 中,变量和事实(Facts)就像给剧本(Playbook)配备的 “信息工具箱”,让你的自动化配置管理更灵活、更智能。
变量:提前准备的 “预设信息”
变量就像你出门前准备的清单,提前可以提前预先定义好各种信息,用的时候直接拿出来用。
-
怎么定义?
-
在 Playbook 里直接写
:就像把清单贴在剧本首页
- hosts: webserversvars:app_port: 8080 # 定义应用端口变量app_name: "myapp" # 定义应用名称变量tasks:- name: 启动应用command: "/opt/{{ app_name }}/start --port {{ app_port }}"
-
单独放变量文件
:就像把清单放进文件夹,更整洁
# vars/app_vars.yml app_port: 8080 app_name: "myapp"
然后在 Playbook 里引用:
- hosts: webserversvars_files:- vars/app_vars.yml
-
命令行传变量
:临时改清单,比如临时换个端口
ansible-playbook deploy.yml -e "app_port=9090"
-
-
变量的小技巧:
- 可以用
{{ 变量名 }}
在任务中调用,像插卡一样灵活 - 支持条件判断,比如 “如果是生产环境,端口用 443;测试环境用 8080”
- 可以用
事实(Facts):自动收集的 “系统情报”
事实就像 Ansible 派出去的 “侦察兵”,会自动收集目标主机的信息(比如 IP 地址、操作系统、内存大小等),不用你手动问。
-
怎么看收集到的情报?
跑个命令让侦察兵汇报:ansible webservers -m setup
会得到一堆信息,比如:
ansible_facts['os_family']
:操作系统类型(比如 RedHat、Debian)ansible_facts['default_ipv4']['address']
:主机 IP 地址ansible_facts['memory_mb']['total']
:总内存(MB)
-
在 Playbook 里用事实:
比如根据操作系统选不同的安装命令:- hosts: alltasks:- name: 安装nginx(Debian系统)apt: name=nginx state=presentwhen: ansible_facts['os_family'] == "Debian"- name: 安装nginx(RedHat系统)yum: name=nginx state=presentwhen: ansible_facts['os_family'] == "RedHat"
-
小提示:
- 事实默认会自动收集,如果想关掉(比如加快执行速度),可以在 Playbook 里加
gather_facts: no
- 可以自定义事实(local facts),把自己关心的信息存到目标主机的
/etc/ansible/facts.d/
目录,Ansible 会自动读取
- 事实默认会自动收集,如果想关掉(比如加快执行速度),可以在 Playbook 里加
总结:变量 vs 事实
- 变量:你主动告诉 Ansible 的信息(提前设定)
- 事实:Ansible 主动从目标主机收集的信息(动态获取)
两者结合,就像给 Ansible 装上了 “大脑”—— 既知道你提前安排的计划,又能根据实际情况灵活调整,让配置管理既聪明又高效~
管理 VARIABLES
变量简介
ansible 利用变量来存储数据,以便在Ansible项目文件中重复引用,有利于简化项目的创建和维护,降低出错率。我们在playbook中可以针对如用户、软件包、服务、文件等进行变量定义。
变量命名规则
- 只能包含字母、数字和下划线(如包含空格、点、$符号都为非法变量名)
- 只能以字母开头
变量范围和优先级
ansible项目文件中多个位置支持定义变量,主要包含三个基本范围:
- Global scope:从命令行或 Ansible 配置设置的变量。
- Play scope:在play和相关结构中设置的变量。
- Host scope:由清单、事实(fact)收集或注册的任务,在主机组和个别主机上设置的变量。
优先级从高到低顺序:Global -> Play -> Host。
在多个级别上定义了相同名称的变量,则采用优先级别最高的变量。
Play scope
在 Ansible 里,“Play scope”(剧本作用域)可以理解为一个 Play(剧本)的 “管辖范围” 和 “生效边界”。它就像给这个 Play 划了个圈,圈里的规则、变量、设置只在这个范围内起作用,不会跑到圈外去影响其他 Play。
举个生活例子:你家里有客厅和卧室两个区域(相当于两个 Play)。在客厅里你规定 “看电视音量不能超过 30”(这是客厅 Play 的变量 / 设置),这个规则只在客厅生效,到了卧室就不算数了 —— 这就是每个 Play 有自己独立的 scope。
具体来说,Play scope 包含这些 “圈内要素”:
- 目标主机:通过
hosts
指定的主机 / 主机组,只有这些主机受这个 Play 管理 - 变量:在 Play 的
vars
或vars_files
里定义的变量,只在当前 Play 的任务中可用 - 提权设置:当前 Play 里的
become
相关配置,不会影响其他 Play - 事实收集:
gather_facts
的开关状态,只控制当前 Play 是否收集主机信息
比如一个 Playbook 里有两个 Play:
- name: 管理web服务器 # Play 1hosts: webserversvars:app: "nginx" # 这个变量只在Play 1里有效tasks:- name: 安装nginxyum: name={{ app }} state=present- name: 管理数据库服务器 # Play 2hosts: dbserversvars:app: "mysql" # 这个变量只在Play 2里有效tasks:- name: 安装mysqlyum: name={{ app }} state=present
这里两个 Play 的app
变量互不干扰,因为它们各自有独立的 scope。
简单说,Play scope 就是 Ansible 里的 “楚河汉界”,让每个 Play 在自己的地盘里按自己的规则干活,互不打扰~
vars 声明
在 Ansible 中,vars
就像给 Playbook 准备的 “变量口袋”,用来提前存放各种需要反复使用的信息。声明变量的方式灵活多样,就像你可以把东西放在口袋、抽屉或专门的收纳盒里一样。
1. 直接在 Play 里声明(最直观)
就像把常用物品直接揣在口袋里,随用随拿。在 Play 的vars
块里定义变量,只在当前 Play 中生效。
- name: 部署web应用hosts: webserversvars:app_name: "blog" # 应用名称app_port: 8080 # 运行端口max_connections: 100 # 最大连接数tasks:- name: 创建应用目录file:path: "/opt/{{ app_name }}" # 引用变量state: directory
2. 单独放变量文件(更整洁)
如果变量太多,就像东西太多需要用抽屉分类收纳。把变量写在单独的 YAML 文件里,再在 Playbook 中引用。
# 变量文件:vars/app_settings.yml
app_name: "blog"
app_port: 8080
db_host: "db.example.com"
在 Playbook 中调用这个文件:
- name: 部署web应用hosts: webserversvars_files:- vars/app_settings.yml # 引入外部变量文件tasks:- name: 配置数据库连接lineinfile:path: "/opt/{{ app_name }}/config.ini"line: "db_host = {{ db_host }}"
3. 命令行临时声明(应急用)
就像临时从口袋里掏出个备用物品,适合临时修改变量值,不用改 Playbook 本身。用-e
参数传递:
# 临时把端口改成9090运行
ansible-playbook deploy.yml -e "app_port=9090"
4. 主机 / 组变量(按目标分类)
如果不同主机需要不同变量(比如 web 服务器和数据库服务器配置不同),可以在inventory
目录下创建专门的变量文件,Ansible 会自动对应。
目录结构通常是这样:
inventory/├── hosts # 主机清单├── group_vars/ # 组变量(对整个组生效)│ ├── webservers.yml # web服务器组的变量│ └── dbservers.yml # 数据库服务器组的变量└── host_vars/ # 主机变量(对单台主机生效)└── web01.yml # 给web01主机单独的变量
比如group_vars/webservers.yml
里写:
app_type: "nginx"
log_path: "/var/log/nginx"
变量使用小技巧
-
引用变量时用双大括号
{{ 变量名 }}
,比如{{ app_name }}
-
变量名可以包含字母、数字和下划线(不能以数字开头)
-
支持嵌套,比如:
app:name: "blog"port: 8080
引用时用
{{ app.name }}
{{ app.port }}
简单说,vars
声明就是给 Ansible 提前 “备课”—— 把需要用的信息整理好,用的时候直接喊名字就能调出来,不用重复写死,既灵活又好维护~
Host scope
主机变量应用于主机和主机组。主机变量优先级高于主机组变量。
主机清单中定义
较旧的做法是直接在清单文件中定义。不建议采用,但仍可能会遇到。
[servers]
node1 user=laoma
node2[servers:vars]
user=laowang
目录分层结构定义
在项目目录中创建如下目录:
-
group_vars,定义主机组变量。目录中文件名可以直接使用
主机组名
或者主机组名.yaml
。 -
host_vars,定义主机变量。目录中文件名可以直接使用
主机名
或者主机名.yaml
。
主机连接特殊变量
详情参考:主机连接特殊变量。
-
ansible_connection,与主机的连接类型,可以是 smart、ssh 或 paramiko。默认为smart。
-
ansible_host,要连接的主机的名称,默认值就是主机清单名称。
-
ansible_port,ssh 端口号,如果不是 22。
-
ansible_user,ssh 用户名。
-
ansible_ssh_pass,要使用的 ssh 密码。切勿以纯文本形式存储此变量,始终使用保管库。
-
ansible_ssh_private_key_file,ssh 使用的私钥文件。如果使用多个密钥并且您不想使用 SSH 代理,这很有用。
-
ansible_ssh_common_args,此设置始终附加到 sftp、scp 和 ssh 的默认命令行。
-
ansible_sftp_extra_args,此设置始终附加到默认的 sftp 命令行。
-
ansible_scp_extra_args,此设置始终附加到默认的 scp 命令行。
-
ansible_ssh_extra_args,此设置始终附加到默认的 ssh 命令行。
-
ansible_become,等效于 ansible_sudo 或 ansible_su,允许强制提权。
-
ansible_become_method,允许设置权限提升方法。
-
ansible_become_user,等效于 ansible_sudo_user 或 ansible_su_user,允许设置您通过权限升级成为的用户。
-
ansible_become_pass,等效于 ansible_sudo_pass 或 ansible_su_pass,允许您设置权限提升密码(切勿以纯文本形式存储此变量;始终使用保管库。请参阅变量和保管库)。
数组变量
除了将与同一元素相关的配置数据(软件包列表、服务列表和用户列表等)分配到多个变量外,管理员也可以使用数组变量,将多个值存储在同一变量中。
示例:
user1_first_name: Bob
user1_last_name: Jones
user1_home_dir: /users/bjones
user2_first_name: Anne
user2_last_name: Cook
user2_home_dir: /users/acook
改写如下:
users:bjones:first_name: Boblast_name: Joneshome_dir: /users/bjonesacook:first_name: Annelast_name: Cookhome_dir: /users/acook
数组变量引用方式一:
# Returns 'Bob'
users.bjones.first_name
# Returns '/users/acook'
users.acook.home_dir
数组变量引用方式二:
# Returns 'Bob'
users['bjones']['first_name']
# Returns '/users/acook'
users['acook']['home_dir']
引用方式总结:
- 如果使用方法一**.分隔符**引用的关键字与python的功能函数同名,例如discard、copy、add,那么就会出现问题。方法二[‘’]引用方式可以避免这种错误。
- 尽管两种方法都可以使用,为了减少排故难度,Ansible中统一使用其中一种方法。
示例1:
---
- name: test vars statement in playhosts: node1vars: users:laoma:user_name: laomahome_path: /home/laomalaowang:user_name: laowanghome_path: /home/laowangtasks:- name: add user {{ users.laoma.user_name }}user:name: '{{ users.laoma.user_name }}'home: "{{ users.laoma.home_path }}"- name: debug laowangdebug: msg: >username is {{ users['laowang']['user_name'] }}home_path is {{ users['laowang']['home_path'] }}
示例2:
---
- name: test vars statement in playhosts: node1vars: users:- user_name: laoma1home_path: /home/laoma1- user_name: laoma2home_path: /home/laoma2tasks:- name: add user {{ users.0.user_name }}user:name: "{{ users.0.user_name }}"home: "{{ users.0.home_path }}"- name: debug {{ users[1].user_name }}debug: msg: "{{ users[1].user_name }}"
register 语句
**register 语句捕获任务输出。**输出保存在一个临时变量中,稍后在playbook中可用于调试用途或者达成其他目的。
示例:
---
- name: Installs a package and prints the resulthosts: node1tasks:- name: Install the packageyum:name: httpdstate: installedregister: install_result- debug: var: install_result
在 Ansible 里,register
就像给任务装了个 “记录仪”,能把任务执行的结果(比如命令输出、状态信息)存起来,方便后面的任务 “回看” 或 “利用” 这些结果。
打个比方:就像你让同事去查一个文件的大小,他回来告诉你 “文件有 100MB”——register
就相当于把这句话记在笔记本上,你后面可以根据这个结果决定 “要不要备份”(如果大于 50MB 就备份)。
基本用法:记录任务结果
在任务里加register: 变量名
,就会把结果存到这个变量里。比如记录ls
命令的输出:
- name: 查看/tmp目录内容command: ls /tmpregister: tmp_files # 把结果存到tmp_files变量里- name: 打印刚才的结果debug:var: tmp_files # 显示变量内容
运行后会看到tmp_files
里包含很多信息:命令是否成功(success
)、输出内容(stdout
)、错误信息(stderr
)等。
实用场景:根据结果做判断
最常用的是结合when
条件,根据记录的结果决定下一步操作。
比如:检查某个进程是否存在,存在就重启,不存在就启动:
- name: 检查nginx进程command: pgrep nginxregister: nginx_statusignore_errors: yes # 即使命令失败(进程不存在)也不终止Playbook- name: 如果进程存在,就重启nginxservice:name: nginxstate: restartedwhen: nginx_status.rc == 0 # rc=0表示命令成功(进程存在)- name: 如果进程不存在,就启动nginxservice:name: nginxstate: startedwhen: nginx_status.rc != 0 # rc≠0表示命令失败(进程不存在)
这里nginx_status.rc
是命令的返回码(rc
即 return code),0 代表成功,非 0 代表失败。
常用的结果字段
register
变量里有很多有用的 “子信息”,常用的有:
stdout
:命令的标准输出(比如ls
列出的文件)stderr
:命令的错误输出(如果命令失败)rc
:返回码(0 = 成功,非 0 = 失败)changed
:任务是否改变了系统状态(布尔值)failed
:任务是否失败(布尔值)
比如只想看命令输出的内容:
- name: 查看系统版本command: cat /etc/os-releaseregister: os_info- name: 打印系统版本信息debug:msg: "系统版本:{{ os_info.stdout }}" # 只取stdout部分
简单说,register
就是 Ansible 里的 “记事贴”—— 让任务之间能 “传递消息”,根据前面的结果动态决定后面的操作,让 Playbook 变得更智能、更灵活~
MAGIC 变量
magic 变量由 Ansible 自动设置,可用于获取与特定受管主机相关的信息。
假设当前清单内容为:
controller[webs]
node1
node2[dbs]
node3
node4
最常用四个 Magic 变量:
-
inventory_hostname,包含清单中配置的当前受管主机的主机名称。这可能因为各种原因而与FACTS报告的主机名称不同。
[laoma@controller web]$ ansible node1 -m debug -a 'var=inventory_hostname' node1 | SUCCESS => {"inventory_hostname": "node1" }
-
group_names,列出当前受管主机所属的所有主机组。
[laoma@controller web]$ ansible node1 -m debug -a 'var=group_names' node1 | SUCCESS => {"group_names": ["webs"] }
-
groups,列出清单中的所有组,以及组中含有的主机。
[laoma@controller web]$ ansible node1 -m debug -a 'var=groups' node1 | SUCCESS => {"groups": {"all": ["workstation","node1","node2","node3","node4"],"dbs": ["node3","node4"],"ungrouped": ["controller"],"webs": ["node1","node2"]} }
-
hostvars,包含所有受管主机的变量,可用于获取另一台受管主机的变量的值。如果还没有为受管主机收集FACTS,则它不会包含该主机的 FACTS。
例如:
hostvars.controller.group_names
在 Ansible 里,“MAGIC 变量”(魔法变量)就像自带的 “万能钥匙”,不需要你手动定义,Ansible 会自动生成并提供这些变量,帮你快速获取 inventory(主机清单)里的各种信息,让 Playbook 更灵活地处理主机间的关系。
它们之所以叫 “魔法变量”,是因为你不用声明就能直接用,就像凭空出现的工具,专门解决和主机清单相关的问题。
最常用的几个 “魔法变量”:
1. inventory_hostname
:当前主机的 “身份证”
返回当前正在处理的主机在 inventory 里的名字(不是主机的 hostname,而是你在清单里写的名字)。
比如 inventory 里写着 web01.example.com
,那这个变量就返回它。
- name: 显示当前主机名debug:msg: "正在处理的主机:{{ inventory_hostname }}"
2. groups
:主机组的 “花名册”
返回所有主机组的列表,以及每个组里的主机。比如想知道 webservers
组有哪些主机:
- name: 显示web服务器组的所有主机debug:msg: "web组主机:{{ groups['webservers'] }}"
如果想获取所有主机组的名字,用 groups.keys()
。
3. group_names
:当前主机的 “所属群组”
返回当前主机所在的所有组(列表形式)。比如一台主机既在 webservers
组又在 prod
组,这个变量就会返回这两个组名。
- name: 显示当前主机所属的组debug:msg: "我属于这些组:{{ group_names }}"
4. hostvars
:其他主机的 “信息库”
可以获取其他主机的变量或 facts 信息,相当于 “跨主机查资料”。
比如想获取 db01
主机的 IP 地址(需要先收集过 facts):
- name: 显示db01的IPdebug:msg: "数据库IP:{{ hostvars['db01']['ansible_default_ipv4']['address'] }}"
5. play_hosts
:当前 Play 的 “任务清单”
返回当前 Play 中正在处理的所有主机(受 hosts
字段限制的主机列表)。
- name: 显示当前Play要处理的所有主机debug:msg: "本次任务涉及主机:{{ play_hosts }}"
为什么需要魔法变量?
它们就像 Ansible 内置的 “导航系统”,帮你在复杂的主机清单中定位信息:
- 比如跨主机通信(web 服务器需要知道数据库服务器的 IP)
- 比如根据主机所在组执行不同任务
- 比如动态获取当前处理的主机信息
管理 SECRETS
Ansible Vault 简介
Ansible可能需要访问密码或API密钥等敏感数据,此信息可能以纯文本形式存储在清单变量或其他Ansible文件中。任何有权访问Ansible文件的用户或存储这些Ansible文件的版本控制系统都能够访问此敏感数据。
这显然存在安全风险。Ansible随附的 Ansible Vault 可以加密任何由Ansible使用的结构化数据文件,包括清单变量、playbook中含有的变量文件、在执行playbook时作为参数传递的变量文件,以及Ansible角色中定义的变量。
变量管理推荐做法
- 包含敏感变量的文件可通过 ansible-vault 命令进行保护。
- 敏感变量和所有其他变量保存在相互独立的文件中。
- 管理组变量和主机变量的首选方式是在项目目录中创建子目录。
可为每个主机组或受管主机使用独立的目录。这些目录可包含多个变量文件,它们都由该主机组或受管主机使用。
管理 FACTS
FACTS 介绍
FACTS 是 Ansible 在受管主机上自动检测到的变量,默认保存在内容中,只存在于本次playbook执行期间。
FACTS含有主机相关的信息,可以像play中的常规变量一样使用。
受管主机的 facts 包括:
• 主机名称 • 内核版本 • 网络接口 • IP地址 • 操作系统版本 • 各种环境变量
• CPU数量 • 提供的或可用的内存 • 可用磁盘空间
借助 facts,可以方便地检索受管主机的状态,并根据该状态确定要执行的操作。
例如:
- 可以根据当前内核版本的FACTS运行条件任务,以此来重新启动服务器。
- 可以根据通过FACTS报告的可用内存来自定义 MySQL 配置文件。
- 可以根据FACTS的值设置配置文件中使用的 IPv4 地址。
通常,每个play在执行第一个任务之前会先自动收集FACTS。
查看 FACTS 内容
示例1:查看所有变量
---
- name: Dump factshosts: node1tasks:- name: Print all factsdebug:var: ansible_facts
示例2:查看单个变量
---
- hosts: node1tasks:- name: Print Ansible factsdebug: msg: >The default IPv4 address of {{ ansible_fqdn }}is {{ ansible_default_ipv4.address }}
部分 FACTS
FACT | VARIABLE |
---|---|
短主机名 | ansible_facts[‘hostname’] |
完全限定的域名 | ansible_facts[‘fqdn’] |
主要IPv4地址(基于路由) | ansible_facts[‘default_ipv4’][‘address’] |
所有网络接口的名称列表 | ansible_facts[‘interfaces’] |
/dev/vdal磁盘分区的大小 | ansible_facts[‘devices’][‘vda’][‘partitions’]['vda1][‘size’] |
DNS服务器列表 | ansible_facts[‘dns’][‘nameservers’] |
当前运行的内核的版本 | ansible_facts[‘kernel’] |
setup 和 gather_facts 模块
setup 和 gather_facts 模块都可以用来收集facts:
-
gather_facts 模块,只能用来收集facts。
-
setup 模块,除了用来收集facts,还提供额外选项:
-
filter 选项,用于查看特定facts值。
-
gather_subset 选项,用于控制收集facts范围
-
关闭 FACTS 收集
关闭 FACTS 收集部分原因:
- 不使用任何FACTS
- 希望加快play速度或减小play在受管主机上造成的负载
- 受管主机因为某种原因而无法运行setup模块
- 需要安装一些必备软件后再收集FACTS
Ansible配置文件设置
[defaults]
gathering = explicit
play中设置
---
- name: Fact dumphosts: node1gather_facts: no
即使关闭以后,也可以随时使用setup模块收集facts。
实施任务控制
编写循环任务
利用循环,管理员无需编写多个使用同一模块的任务。例如,确保存在五个用户,不需要编写五个任务,而是只需编写一个任务来对含有五个用户的列表迭代。
Ansible支持使用 loop 关键字对一组项目迭代任务。您可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。
简单循环
简单循环对一组项目迭代任务。loop关键字添加到任务中, 将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。
示例:
---
- name: add several usershosts: node1gather_facts: notasks:- name: add user janeuser:name: "jane"groups: "wheel"state: present- name: add user joeuser:name: "joe"state: presentgroups: "wheel"
使用loop循环改写:
- name: test loophosts: node1gather_facts: notasks:- name: add usersuser:name: "{{ item }}"groups: "wheel"state: presentloop:- jane- joe
这个 Playbook 的作用是在node1
主机上批量创建两个用户(jane 和 joe),并将他们加入wheel
组。
name: test loop
:这是 Play 的名称,用于标识这个 Play 的用途hosts: node1
:指定在node1
这台主机上执行gather_facts: no
:关闭 facts 收集,加快执行速度(因为这个任务不需要系统信息)- 任务部分使用了
loop
关键字,后面跟着一个用户列表[jane, joe]
在循环过程中,Ansible 会自动将列表中的每个元素依次赋值给item
变量,然后执行user
模块:
- 第一次循环:
item = jane
,创建用户 jane 并加入 wheel 组 - 第二次循环:
item = joe
,创建用户 joe 并加入 wheel 组
user
模块的参数说明:
name: "{{ item }}"
:用户名,这里引用循环变量groups: "wheel"
:指定用户所属的附加组(wheel 组通常用于 sudo 权限)state: present
:确保用户存在(如果不存在则创建)
执行这个 Playbook 后,目标主机上会新增 jane 和 joe 两个用户,且都属于 wheel 组。这种循环方式非常适合需要批量执行相同操作的场景,避免了重复编写任务代码。
循环散列或字典列表
在以下示例中,列表中的每个项实际上是散列或字典。
示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。
示例:
- name: test loophosts: node1gather_facts: notasks:- name: add usersuser:name: "{{ item.name }}"groups: "{{ item.groups }}"state: presentloop: - name: janegroups: wheel- name: joegroups: root
改写为:
---
- name: add several usershosts: node1gather_facts: novars:users:- name: janegroups: wheel- name: joegroups: roottasks:- name: add users user:name: "{{ item.name }}"state: presentgroups: "{{ item.groups }}" loop: "{{ users }}"
Register 与 Loop
示例:
---
- name: Loop Register Testhosts: node1gather_facts: notasks:- name: Looping Echo Taskshell: "echo This is my item: {{ item }}"loop:- one- tworegister: result- name: Show result variabledebug:var: result- name: Show result variable stdoutdebug:msg: "STDOUT from previous task: {{ item.stdout }}"loop: "{{ result.results }}"
编写条件任务
Ansible可使用conditionals在符合特定条件时执行任务或play。
例如,管理员可利用条件来区分不同的受管节点,并根据它们所符合的条件来分配功能角色。 Playbook变量、注册的变量和ANSIBLE FACTS都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。
用例:
- 定义变量min_memory,判断被管理节点可用内存是否满足该值。
- 捕获命令输出,判定task是否执行完成,以便决定是否进行下一步操作。
- 被管理节点上收集到的网络facts,判定是否适合哪种绑定(bonding或者trunking)。
- 根据CPU的数量决定如何调优web服务器。
- Registered变量与预定义的变量对比,判断是否有变化。例如文件的MD5值。
when 语句
ansible playbook 中使用 when 来运行条件任务。
when 用于有条件地运行任务,取要测试的条件作为值。如果条件满足,则运行任务。若条件不满足,则跳过任务。
注意:通常的惯例是将可能存在的任何when关键字放在任务名称和模块(及模块参数)的后面。原因是任务是YAML散列/字典,when 语句只是任务中的一个键,就如任务的名称以及它所使用的模块一样。
常见判断
操作 | 示例 |
---|---|
等于(值为字符串) | ansible_machine == “x86_64” |
等于(值为数字) | max_memory == 512 |
小于 | min_memory < 128 |
大于 | min_memory > 256 |
小于等于 | min_memory <= 256 |
大于等于 | min_memory >= 512 |
不等于 | min_memory != 512 |
变量存在 | min_memory is defined |
变量不存在 | min_memory is not defined |
布尔变量值是1、True或yes的求值为真。 | memory_available |
布尔变量值是0、False或no的求值为假。 | memory_available |
memory_available变量值为真,最终结果为假。 | not memory_available |
第一个变量的值存在,作为第二个变量的列表中的值 | ansible_distribution in supported_distros |
布尔值变量判断
示例:
---
- name: test hosts: node1gather_facts: novars:run_my_task: truetasks:- name: test whendebug:msg: "Hello run my task"when: run_my_task
示例中的when语句导致:任务仅在run_my_task为 true时运行。
变量是否定义判断
变量判断:
- defined == not undefined 变量定义返回真
- undefined == not defined 变量未定义返回真
- none 变量定义了,但是值是空值,返回真
示例1:
---
- hosts: node1gather_facts: novars:username: laomatasks:- debug:msg: "var: username is defined"when: username is defined
示例2:判断受管主机是否具有相应设备。
---
- name: create and use lvhosts: node1tasks:- name: Create a logical volume of 4000mlvol:vg: researchlv: datasize: 4000when: ansible_lvm.vgs.research is defined- debug:msg: Volume group does not existwhen: ansible_lvm.vgs.research is not defined
文件属性判断
- file:如果路径是一个普通文件,返回真
- directory:如果路径是一个目录,返回真
- link:如果路径是一个软连接,返回真
- mount:如果路径是一个挂载点,返回真
- exist:如果路径是存在,返回真
示例:
---
- hosts: node1gather_facts: novars:file_name: /etc/hoststasks:- debug:msg: "{{ file_name }} is regular file"when: file_name is file
任务执行结果判断
- succeeded,通过任务的返回信息判断,任务执行成功返回真。
- failed,通过任务的返回信息判断,任务执行失败返回真。
- changed,通过任务的返回信息判断,任务执行状态为changed返回真。
- skipped,通过任务的返回信息判断,任务没有满足条件跳过执行,返回真。
示例:
---
- hosts: node1gather_facts: novars:doshell: "yes"tasks:- shell: cat /etc/hostsregister: resultignore_errors: truewhen: doshell == "yes"- name: successdebug:msg: successwhen: result is succeeded- name: faileddebug:msg: failedwhen: result is failed- name: changeddebug:msg: changedwhen: result is changed- name: skippeddebug:msg: skipwhen: result is skipped
其他测试:
- 设置doshell: “no”
- 设置shell: cat /etc/hosts-no-exist
in 和 not in 判断
示例1:给用户添加组
---
- name: test hosts: node1gather_facts: novars:username: devopssupergroup: wheeltasks:- name: gather user informationshell: id {{ username }}register: result- name: Task run if user is in supergroupsuser:name: "{{ username }}"groups: "{{ supergroup }}"append: yeswhen: supergroup not in result.stdout
示例2:给用户添加多个组
---
- name: test hosts: node1gather_facts: novars:username: devopssupergroups: - wheel- roottasks:- name: gather user informationshell: id {{ username }}register: result- name: Task run username is in supergroupsuser:name: "{{ username }}"groups: "{{ item }}"append: yeswhen: item not in result.stdoutloop: "{{ supergroups }}"
Ansible Handlers
Ansible Handlers 功能
Ansible的模块的设计是可以多次执行的,当被管理节点是预期状态时,是不会做任何更改的。然而,有时候执行了一个任务,还需要进一步执行下一个任务。
例如,更改了服务配置文件之后,需要重新加载配置文件才能生效。Handlers是由其他任务通知执行的任务,可看做inactive任务,通过notify调用。
示例:
---
- name: deploy web serverhosts: node1tasks:- name: install packagesyum:name: httpdstate: presentnotify:- enable and restart apache- name: install httpd-manualyum:name: httpd-manualstate: presentnotify:- enable and restart apache- debug: msg: last task in taskshandlers:- name: enable and restart apacheservice:name: httpdstate: restartedenabled: yes
在 Ansible 中,Handlers(处理器) 就像一个 “待命的助手”,专门用来处理那些 “只有在系统状态发生变化时才需要执行” 的操作。它有点像 “触发器”—— 只有当某个任务真正改变了系统状态(比如修改了配置文件),Handlers 才会被触发执行。
为什么需要 Handlers?
举个例子:当你修改了 Nginx 的配置文件(nginx.conf
),只有在配置文件真的被改动时,才需要重启 Nginx 服务。如果配置文件没变化,重启操作就是多余的。
Handlers 正是为这种场景设计的:它能 “监听” 任务是否导致了系统状态变更(通过changed: true
标识),只有变更发生时才执行指定操作,避免无效重复执行。
基本用法:定义和触发 Handlers
Handlers 的使用分为两步:定义 Handlers 和 在任务中通知(notify)Handlers。
- name: 配置Nginx并按需重启hosts: webserverstasks:- name: 修改Nginx配置文件copy:src: ./nginx.confdest: /etc/nginx/nginx.confnotify: # 当这个任务导致状态变更时,通知Handlers- 重启Nginx服务 # 这里的名称要和Handlers中定义的一致handlers: # 定义Handlers(放在Play的handlers块中)- name: 重启Nginx服务 # 名称要和notify中的完全匹配service:name: nginxstate: restarted
执行逻辑:
- 执行 “修改 Nginx 配置文件” 任务:
- 如果配置文件有变化(
changed: true
),则标记 “重启 Nginx 服务” 这个 Handler 为 “待执行” - 如果配置文件无变化(
changed: false
),则不通知 Handler
- 如果配置文件有变化(
- 当 Play 中所有任务执行完毕后,Ansible 会统一执行所有被标记为 “待执行” 的 Handlers。
Handlers 的特点
-
延迟执行:Handlers 不会在被通知后立即执行,而是等到当前 Play 中所有任务都执行完才统一运行。
-
幂等性保障:即使多个任务通知同一个 Handler,它也只会执行一次。例如:
tasks:- name: 修改配置文件Acopy: src=a.conf dest=/etc/nginx/notify: 重启Nginx服务- name: 修改配置文件Bcopy: src=b.conf dest=/etc/nginx/notify: 重启Nginx服务
无论两个任务是否都触发了变更,“重启 Nginx 服务” 只会执行一次,避免多次重启。
-
名称唯一:Handlers 通过
name
标识,notify
必须严格匹配名称(包括大小写),否则无法触发。
常见使用场景
Handlers 最适合处理 “配置变更后需要生效的操作”,例如:
- 服务重启(Nginx、MySQL、Apache 等)
- 重新加载配置(
systemctl reload
) - 重建缓存(如
systemctl daemon-reload
) - 重新编译(当源码或 Makefile 被修改时)
简单说,Handlers 就像 “按需执行的收尾工作”—— 平时待命,只有当系统真的发生了需要它处理的变化时,才会出手完成必要的后续操作,让 Playbook 更高效、更符合实际运维逻辑。
处理 Errors
Errors 介绍
Ansible评估各任务的返回代码,从而确定任务是成功还是失败。通常而言, 当某个主机执行任务失败时,Ansible将立即终止该主机继续执行play,其他主机可以继续执行play。
---
- name: testhosts: node1,node2tasks:- name: show /etc/myhostsshell: cat /etc/myhosts- name: echo enddebug:msg: echo end
ignore_errors
您可能希望即使在任务失败时也继续执行play。例如,您或许预期特定任务有可能会失败,并且希望通过有条件地运行某项其他任务来恢复。
ignore_errors可以定义在以下位置:
- 定义在 play 中,则play中所有任务忽略错误。
- 定义在 task 中,则特定task忽略错误。
示例:
---
- name: testhosts: node1tasks:- name: install a not exist packageyum:name: notexitpackagestate: presentignore_errors: yesregister: result- name: debug install resultdebug:msg: notexitpackage is not exitwhen: result is failed
fail 模块
fail 模块,执行该任务,任务必定 failed。
示例:
- name: test fail modulehosts: node1gather_facts: notasks:- debug:msg: task1- fail:- debug:msg: task3
提示:fail模块本身也可以配置when判断,实现说明情况下任务是失败的。
failed_when
指明什么条件下,判定任务执行失败。
示例:
- name: test failed_whenhosts: node1tasks:- shell: /root/adduserregister: command_resultfailed_when: "'failed' in command_result.stdout"
环境准备:
[root@node1 ~]# cat /root/adduser
#!/bin/bash
useradd devops &> /dev/null
if [ $? -eq 0 ];thenecho add user devops success
elseecho add user devops failed
fi
[root@node1 ~]# chmod +x /root/adduser
以上示例:
- 当devops用户不存在时,shell模块跳过执行。
- 当devops用户存在时,shell模块执行失败。
以上示例可改写为fail模块和when语句联合使用:
- name: test fail modulehosts: node1tasks:- shell: /root/adduserregister: command_result- fail:msg: "add user devops failed"when: "'failed' in command_result.stdout"
changed_when
指明什么条件下,判定任务执行结果为changed。
示例1:
- name: changed_whenhosts: node1tasks:- name: upgrade-databaseshell: /usr/local/bin/upgrade-databaseregister: resultchanged_when: "'Success' in result.stdout"notify:- restart_databasehandlers:- name: restart_databaseservice:name: mariadbstate: restarted