7.Ansible.实施任务控制
一、环境搭建
详细步骤:
bash
# 创建名为web的工作目录(用于存放后续的Playbook、配置等文件),并进入该目录
[bq@controller ~]$ mkdir web && cd web
# 创建Ansible配置文件ansible.cfg,用于设置默认运行参数
[bq@controller web]$ cat > ansible.cfg <<'EOF'
[defaults]
remote_user = bq # 默认使用bq用户登录远程节点(无需每次指定用户名)
inventory = ./inventory # 指定节点清单文件路径为当前目录的inventory
[privilege_escalation]
become = True # 允许执行提权操作(需要时切换到root用户)
become_user = root # 提权的目标用户为root(获取最高权限)
become_method = sudo # 提权方式使用sudo(Linux常用的权限提升工具)
become_ask_pass = False # 提权时不询问密码(前提:已在远程节点配置bq用户的sudo免密)
EOF
# 创建节点清单文件inventory,记录需要管理的所有节点名称或IP
[bq@controller web]$ cat > inventory <<'EOF'
controller # 控制节点自身(也可作为被管理节点)
node1 # 受管节点1(需提前配置与控制节点的免密登录)
node2 # 受管节点2
node3 # 受管节点3
node4 # 受管节点4
EOF
编写循环任务
循环的核心作用:用一个任务处理多个相似操作 (比如一次性创建 10 个用户,或安装 5 个软件),避免重复编写几乎一样的任务。Ansible 通过 loop
关键字实现循环,每次循环时用 item
变量表示当前处理的元素。
简单循环
场景:在 node1 节点上创建 jane 和 joe 两个用户,并将他们加入 wheel 组(wheel 组通常用于 sudo 权限管理)。
实验流程:
- 先编写不含循环的 Playbook(直观感受重复任务的冗余);
- 用
loop
改写 Playbook(简化任务,减少重复代码); - 执行 Playbook 并验证用户是否成功创建。
详细步骤:
-
不含循环的 Playbook(对比用) :
每次创建用户都需要写一个独立任务,用户数量越多,代码越冗余。
yaml--- - name: 不使用循环创建用户 hosts: node1 # 目标节点为node1 gather_facts: no # 不收集主机信息(加快任务执行速度) tasks: - name: 创建用户jane user: name: "jane" # 用户名 groups: "wheel" # 加入wheel组 state: present # 确保用户存在(如果不存在则创建) - name: 创建用户joe user: name: "joe" state: present groups: "wheel" ...
-
用
loop
改写的 Playbook :用一个任务配合
loop
列表,一次性处理多个用户。yaml- name: 使用loop循环创建用户 hosts: node1 gather_facts: no tasks: - name: 批量创建用户 user: name: "{{ item }}" # item自动引用loop列表中的当前元素(第一次是jane,第二次是joe) groups: "wheel" state: present loop: # 循环列表:需要创建的用户名 - jane - joe
-
通过变量定义循环列表 :
把循环列表存到变量中,方便后续修改(比如新增用户只需改变量)。
yaml- name: 用变量存储循环列表 hosts: node1 gather_facts: no tasks: - name: 批量创建用户 user: name: "{{ item }}" groups: "wheel" state: present loop: "{{ users }}" # 引用变量users作为循环列表 vars: # 定义变量users,值为需要创建的用户名列表 users: - jane - joe
-
执行与验证:
bash# 执行Playbook(假设文件名为user_loop.yml) ansible-playbook user_loop.yml # 登录node1节点,验证jane和joe是否存在(id命令可查看用户信息) ssh node1 'id jane; id joe'
循环字典列表
场景:创建用户时,每个用户需要单独指定所属组(例如:jane 加入 wheel 组,joe 加入 root 组)。
原理 :循环列表中的元素可以是 "字典"(键值对形式,类似 "姓名:张三,年龄:20"),通过 item.键名
即可引用对应的值(如 item.name
取用户名,item.groups
取所属组)。
实验流程:
- 定义包含用户信息的字典列表(每个字典存一个用户的名称和所属组);
- 编写循环任务,通过
item.键名
引用字典中的具体信息; - 执行 Playbook 并验证用户组配置是否正确。
详细步骤:
yaml
---
- name: 循环字典列表创建用户
hosts: node1
gather_facts: no
vars: # 定义用户信息字典列表:每个元素是一个用户的详细配置
users:
- name: jane # 第一个用户:名称为jane
groups: wheel # 所属组为wheel
- name: joe # 第二个用户:名称为joe
groups: root # 所属组为root
tasks:
- name: 批量创建用户(带组配置)
user:
name: "{{ item.name }}" # 引用字典中的name键(即用户名)
state: present
groups: "{{ item.groups }}" # 引用字典中的groups键(即所属组)
loop: "{{ users }}" # 循环变量users(字典列表)
...
验证:
bash
# 在node1上检查用户所属组(groups命令可查看用户的所有组)
ssh node1 'groups jane; groups joe'
旧版循环关键字(仅供了解)
Ansible 2.5 之前常用 with_*
格式的循环(如 with_items
),现在官方推荐用 loop
(功能更统一)。以下为常见旧语法示例(不建议新环境使用):
-
with_items
(类似loop
,但会自动展开嵌套列表):yaml--- - name: 旧语法with_items hosts: node1 tasks: - name: 创建用户 user: name: "{{ item }}" groups: wheel with_items: # 等价于loop,但会展开嵌套列表(例如[[1,2],3]会变成1,2,3) - jane - joe ...
-
with_together
(并行遍历多个列表):yaml--- - name: 并行遍历列表 hosts: node1 tasks: - name: 输出配对结果 debug: msg: "数字{{ item.0 }}对应字母{{ item.1 }}" with_together: # 按索引位置配对两个列表(索引0配索引0,索引1配索引1) - [1, 2, 3] # 第一个列表:数字 - [a, b, c] # 第二个列表:字母 # 输出结果: # "msg": "数字1对应字母a" # "msg": "数字2对应字母b" # "msg": "数字3对应字母c" ...
-
with_sequence
(生成数字序列):yaml--- - name: 生成数字序列 hosts: node1 tasks: - name: 输出1-5的数字 debug: msg: "{{ item }}" with_sequence: start=1 # 起始值 end=5 # 结束值 stride=1 # 步长(每次增加1) # 输出结果:1,2,3,4,5 ...
Do-Until 循环(轮询等待)
场景:等待 node2 节点恢复网络连接(每隔 1 秒检测一次,最多尝试 20 次,直到 ping 通为止)。
原理 :until
定义 "终止条件"(满足条件则停止循环),retries
定义最大重试次数,delay
定义每次重试的间隔时间(秒)。
yaml
- name: 轮询检测node2是否可达
hosts: node1 # 在node1节点上执行ping命令(检测node2)
gather_facts: no
tasks:
- shell: ping -c1 -w 2 node2 # ping node2:发送1个包(-c1),超时2秒(-w2)
register: result # 将命令执行结果保存到变量result中
until: result.rc == 0 # 终止条件:命令返回码为0(rc=0表示成功,即ping通)
retries: 20 # 最多重试20次
delay: 1 # 每次重试间隔1秒
...
循环与注册变量(register
)
场景:循环执行命令并收集每个迭代的结果(例如:循环输出多条信息,然后分别查看每条的执行结果)。
yaml
---
- name: 循环执行命令并收集结果
hosts: node1
gather_facts: no
tasks:
- name: 循环输出信息
shell: "echo 这是第{{ item }}个元素" # 执行echo命令,输出当前元素
loop:
- one
- two
register: result # 将所有迭代的结果保存到result变量(result.results是所有结果的列表)
- name: 打印所有结果
debug:
var: result # 查看result的完整结构(包含每个迭代的stdout、rc等信息)
- name: 提取每个迭代的输出
debug:
msg: "命令输出:{{ item.stdout }}" # 从result.results中取每个迭代的stdout(命令输出)
loop: "{{ result.results }}" # 循环所有迭代的结果列表
...
编写条件任务
条件任务用于满足特定条件时才执行任务 (例如:只在 CentOS 系统上安装 httpd,在 Ubuntu 上不执行;或内存大于 2G 时才部署应用)。通过 when
关键字定义条件。
基础条件判断
常见判断场景:
条件类型 | 示例 | 说明 |
---|---|---|
变量是否定义 | username is defined |
若变量 username 已定义(无论值是什么),则条件为真 |
等于(字符串) | ansible_machine == "x86_64" |
若主机架构是 x86_64(64 位),则条件为真 |
数值比较 | min_memory > 256 |
若内存大于 256MB,则条件为真 |
包含关系 | "wheel" in result.stdout |
若命令输出中包含 "wheel" 字符串,则条件为真 |
任务执行结果 | result is succeeded |
若前序任务执行成功,则条件为真 |
实验 1:根据布尔变量执行任务
通过 true
/false
变量控制任务是否执行。
yaml
---
- name: 布尔变量控制任务执行
hosts: node1
gather_facts: no
vars:
run_task: true # 布尔变量(true:执行任务;false:不执行)
tasks:
- name: 条件执行的任务
debug:
msg: "任务执行了!"
when: run_task # 当run_task为true时,才执行该任务
...
实验 2:判断文件是否存在
通过 is file
检查路径是否为普通文件。
yaml
---
- name: 检查文件是否存在
hosts: node1
gather_facts: no
vars:
file_path: /etc/hosts # 要检查的文件路径(/etc/hosts是系统默认文件,通常存在)
tasks:
- name: 输出文件状态
debug:
msg: "{{ file_path }}是普通文件"
when: file_path is file # 当路径是普通文件时,执行该任务
...
多条件组合
通过 and
/or
和括号组合多个条件,推荐用 "列表格式"(每个条件占一行,可读性更高)。
示例 :
只在 "系统是 CentOS" 且 "内存大于 1024MB" 时,才安装 httpd。
yaml
- name: 多条件判断
hosts: node1
tasks:
- name: 特定系统且内存足够时安装软件
yum:
name: httpd
state: present
when:
- ansible_distribution == "CentOS" # 条件1:系统为CentOS
- ansible_memtotal_mb > 1024 # 条件2:总内存大于1024MB
# 只有两个条件同时满足,才会执行安装任务
...
循环与条件结合
场景:只对 "根目录(/)可用空间大于 300MB" 的主机安装 mariadb-server。
yaml
---
- name: 结合循环和条件安装软件
hosts: node1
tasks:
- name: 根目录空间足够时安装mariadb
yum:
name: mariadb-server
state: latest
loop: "{{ ansible_mounts }}" # 循环所有挂载点信息(从facts中获取,包含每个挂载点的路径、可用空间等)
when:
- item.mount == "/" # 只处理根目录(/)的挂载点
- item.size_available > 300000000 # 可用空间>300MB(单位:字节,300*1024*1024≈300000000)
...
Ansible Handlers(事件触发任务)
Handlers 用于当任务发生变更时执行后续操作(例如:修改 httpd 配置文件后,自动重启 httpd 服务使其生效)。
- 特点 1:默认在所有任务执行完后运行(而非触发后立即运行)。
- 特点 2:多次触发同一 Handler,最终只执行一次(避免重复操作,例如多次修改配置文件,只重启一次服务)。
基础用法
实验流程:
- 安装 httpd 和 httpd-manual 软件;
- 用
notify
关键字在软件安装(发生变更)时,触发 Handler 重启 httpd; - 执行 Playbook 验证 Handler 是否生效(首次安装会重启,已安装则不重启)。
详细步骤:
yaml
---
- name: 部署web服务并触发Handler
hosts: node1
tasks:
- name: 安装httpd
yum:
name: httpd
state: present
notify: # 当该任务发生变更(如httpd从无到有安装),则通知Handler
- 重启并启用apache
- name: 安装httpd手册
yum:
name: httpd-manual
state: present
notify: # 再次通知同一个Handler(若该任务也变更,仍只触发一次)
- 重启并启用apache
- debug:
msg: 所有任务执行完毕(Handler还未运行) # 验证Handler默认在任务后执行
handlers: # 定义Handler(名称需与notify中的名称一致)
- name: 重启并启用apache
service:
name: httpd
state: restarted # 重启服务(使配置生效)
enabled: yes # 设置开机自启
...
验证:
- 第一次执行:httpd 未安装,两个安装任务都会发生变更,Handler 被触发,最终执行一次(重启 httpd)。
- 第二次执行:httpd 已安装,任务无变更,Handler 不会执行(避免无效重启)。
强制立即执行 Handler(meta
模块)
默认 Handler 在所有任务结束后运行,若后续任务依赖 Handler 的结果(例如:安装数据库后需立即启动,才能创建数据库用户),可用 meta: flush_handlers
强制立即执行已触发的 Handler。
yaml
---
- name: 立即执行Handler
hosts: node1
tasks:
- name: 安装mariadb
yum:
name: mariadb-server
state: present
notify:
- 启动mariadb # 安装完成后通知启动数据库
- meta: flush_handlers # 强制立即执行已触发的Handler(此时启动mariadb)
- name: 创建数据库用户(依赖mariadb已启动)
mysql_user: # 该模块需要数据库服务已运行,否则会失败
name: bq
password: 123
handlers:
- name: 启动mariadb
service:
name: mariadb
state: started
...
错误处理
Ansible 默认会在任务失败时终止整个 Playbook 的执行。通过以下方式可自定义错误处理逻辑(例如:忽略不重要的错误,或失败后执行回滚)。
忽略错误(ignore_errors
)
场景:任务可能失败,但不影响后续流程(例如:检测一个可能不存在的文件,即使不存在也继续执行)。
yaml
---
- name: 忽略任务错误
hosts: node1
tasks:
- name: 尝试读取不存在的文件
shell: cat /etc/not_exist # /etc/not_exist通常不存在,该命令会失败(返回非0退出码)
ignore_errors: yes # 忽略该任务的错误,继续执行后续任务
register: result # 保存命令结果(包含失败信息)
- name: 输出错误信息(如果失败)
debug:
msg: "文件不存在"
when: result is failed # 若前序任务失败,则执行该提示任务
...
自定义失败条件(failed_when
)
场景:命令执行成功(返回码为 0),但输出包含 "失败" 标识时,需判定为任务失败(例如:自定义脚本执行成功,但输出 "failed" 表示逻辑失败)。
yaml
---
- name: 自定义失败条件
hosts: node1
tasks:
- name: 执行自定义脚本
shell: /root/adduser # 假设脚本执行成功(返回码0),但输出可能是"success"或"failed"
register: result
failed_when: "'failed' in result.stdout" # 若输出含"failed",则强制判定为任务失败
...
块任务(block
)与异常处理
block
用于将多个任务分组,结合 rescue
(异常处理,任务失败时执行)和 always
(始终执行,无论成功失败)实现复杂逻辑(例如:升级失败则回滚,无论成败都重启服务)。
场景:更新数据库,失败则回滚,无论成功与否都重启服务。
yaml
---
- name: 数据库更新与异常处理
hosts: node1
tasks:
- block: # 主任务块(正常执行的逻辑)
- name: 升级数据库
shell: /usr/local/bin/upgrade-db # 执行升级脚本
rescue: # 主任务块失败时执行(异常处理/回滚)
- name: 回滚数据库
shell: /usr/local/bin/rollback-db # 执行回滚脚本
always: # 无论主任务块成功或失败,都执行(收尾操作)
- name: 重启数据库服务
service:
name: mariadb
state: restarted
...
实施 Tags(任务标签)
Tags 用于只执行 Playbook 中的部分任务(例如:Playbook 包含 "安装软件""配置文件""启动服务" 多个任务,可通过标签只执行 "安装软件")。
基础用法
实验流程:
- 给不同任务打标签(如
webserver
对应 web 服务相关任务,mailserver
对应邮件服务任务); - 执行 Playbook 时,通过
--tags
指定只运行的标签,或--skip-tags
指定排除的标签。
详细步骤:
yaml
---
- name: 带标签的任务示例
hosts: node1
gather_facts: no
tasks:
- name: 安装httpd
yum:
name: httpd
state: latest
tags: webserver # 打标签webserver(表示该任务属于web服务)
- name: 安装postfix
yum:
name: postfix
state: latest
tags: mailserver # 打标签mailserver(表示该任务属于邮件服务)
- name: 输出调试信息
debug:
msg: "这是通用任务" # 无标签,默认会执行
...
执行命令:
bash
# 只执行带webserver标签的任务(即只安装httpd)
ansible-playbook tags.yml --tags webserver
# 执行除mailserver标签外的所有任务(安装httpd + 输出调试信息)
ansible-playbook tags.yml --skip-tags mailserver
...
特殊标签
always
:总是执行(除非用--skip-tags always
明确跳过)。never
:默认不执行(除非用--tags never
明确指定才执行)。
yaml
- name: 特殊标签示例
hosts: node1
tasks:
- name: 总是执行的任务
debug:
msg: "我一定会执行"
tags: always # 无论是否指定其他标签,该任务都会执行
- name: 默认不执行的任务
debug:
msg: "除非指定--tags never,否则我不执行"
tags: never # 不指定--tags never时,该任务不会执行
...
如涉及版权问题请联系作者处理!!!!!