7.Ansible自动化之-实施任务控制
7.Ansible.实施任务控制
一、环境搭建
详细步骤:
# 创建名为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(对比用):
每次创建用户都需要写一个独立任务,用户数量越多,代码越冗余。--- - name: 不使用循环创建用户hosts: node1 # 目标节点为node1gather_facts: no # 不收集主机信息(加快任务执行速度)tasks:- name: 创建用户janeuser:name: "jane" # 用户名groups: "wheel" # 加入wheel组state: present # 确保用户存在(如果不存在则创建)- name: 创建用户joeuser:name: "joe"state: presentgroups: "wheel" ...
-
用
loop
改写的 Playbook:
用一个任务配合loop
列表,一次性处理多个用户。- name: 使用loop循环创建用户hosts: node1gather_facts: notasks:- name: 批量创建用户user:name: "{{ item }}" # item自动引用loop列表中的当前元素(第一次是jane,第二次是joe)groups: "wheel"state: presentloop: # 循环列表:需要创建的用户名- jane- joe
-
通过变量定义循环列表:
把循环列表存到变量中,方便后续修改(比如新增用户只需改变量)。- name: 用变量存储循环列表hosts: node1gather_facts: notasks:- name: 批量创建用户user:name: "{{ item }}"groups: "wheel"state: presentloop: "{{ users }}" # 引用变量users作为循环列表vars: # 定义变量users,值为需要创建的用户名列表users:- jane- joe
-
执行与验证:
# 执行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 并验证用户组配置是否正确。
详细步骤:
---
- name: 循环字典列表创建用户hosts: node1gather_facts: novars: # 定义用户信息字典列表:每个元素是一个用户的详细配置users:- name: jane # 第一个用户:名称为janegroups: wheel # 所属组为wheel- name: joe # 第二个用户:名称为joegroups: root # 所属组为roottasks:- name: 批量创建用户(带组配置)user:name: "{{ item.name }}" # 引用字典中的name键(即用户名)state: presentgroups: "{{ item.groups }}" # 引用字典中的groups键(即所属组)loop: "{{ users }}" # 循环变量users(字典列表)
...
验证:
# 在node1上检查用户所属组(groups命令可查看用户的所有组)
ssh node1 'groups jane; groups joe'
旧版循环关键字(仅供了解)
Ansible 2.5 之前常用 with_*
格式的循环(如 with_items
),现在官方推荐用 loop
(功能更统一)。以下为常见旧语法示例(不建议新环境使用):
-
with_items
(类似loop
,但会自动展开嵌套列表):--- - name: 旧语法with_itemshosts: node1tasks:- name: 创建用户user:name: "{{ item }}"groups: wheelwith_items: # 等价于loop,但会展开嵌套列表(例如[[1,2],3]会变成1,2,3)- jane- joe ...
-
with_together
(并行遍历多个列表):--- - name: 并行遍历列表hosts: node1tasks:- 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
(生成数字序列):--- - name: 生成数字序列hosts: node1tasks:- 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
定义每次重试的间隔时间(秒)。
- name: 轮询检测node2是否可达hosts: node1 # 在node1节点上执行ping命令(检测node2)gather_facts: notasks:- 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
)
场景:循环执行命令并收集每个迭代的结果(例如:循环输出多条信息,然后分别查看每条的执行结果)。
---
- name: 循环执行命令并收集结果hosts: node1gather_facts: notasks:- name: 循环输出信息shell: "echo 这是第{{ item }}个元素" # 执行echo命令,输出当前元素loop:- one- tworegister: 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
变量控制任务是否执行。
---
- name: 布尔变量控制任务执行hosts: node1gather_facts: novars:run_task: true # 布尔变量(true:执行任务;false:不执行)tasks:- name: 条件执行的任务debug:msg: "任务执行了!"when: run_task # 当run_task为true时,才执行该任务
...
实验 2:判断文件是否存在
通过 is file
检查路径是否为普通文件。
---
- name: 检查文件是否存在hosts: node1gather_facts: novars:file_path: /etc/hosts # 要检查的文件路径(/etc/hosts是系统默认文件,通常存在)tasks:- name: 输出文件状态debug:msg: "{{ file_path }}是普通文件"when: file_path is file # 当路径是普通文件时,执行该任务
...
多条件组合
通过 and
/or
和括号组合多个条件,推荐用 “列表格式”(每个条件占一行,可读性更高)。
示例:
只在 “系统是 CentOS” 且 “内存大于 1024MB” 时,才安装 httpd。
- name: 多条件判断hosts: node1tasks:- name: 特定系统且内存足够时安装软件yum:name: httpdstate: presentwhen:- ansible_distribution == "CentOS" # 条件1:系统为CentOS- ansible_memtotal_mb > 1024 # 条件2:总内存大于1024MB# 只有两个条件同时满足,才会执行安装任务
...
循环与条件结合
场景:只对 “根目录(/)可用空间大于 300MB” 的主机安装 mariadb-server。
---
- name: 结合循环和条件安装软件hosts: node1tasks:- name: 根目录空间足够时安装mariadbyum:name: mariadb-serverstate: latestloop: "{{ 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 是否生效(首次安装会重启,已安装则不重启)。
详细步骤:
---
- name: 部署web服务并触发Handlerhosts: node1tasks:- name: 安装httpdyum:name: httpdstate: presentnotify: # 当该任务发生变更(如httpd从无到有安装),则通知Handler- 重启并启用apache- name: 安装httpd手册yum:name: httpd-manualstate: presentnotify: # 再次通知同一个Handler(若该任务也变更,仍只触发一次)- 重启并启用apache- debug: msg: 所有任务执行完毕(Handler还未运行) # 验证Handler默认在任务后执行handlers: # 定义Handler(名称需与notify中的名称一致)- name: 重启并启用apacheservice:name: httpdstate: restarted # 重启服务(使配置生效)enabled: yes # 设置开机自启
...
验证:
- 第一次执行:httpd 未安装,两个安装任务都会发生变更,Handler 被触发,最终执行一次(重启 httpd)。
- 第二次执行:httpd 已安装,任务无变更,Handler 不会执行(避免无效重启)。
强制立即执行 Handler(meta
模块)
默认 Handler 在所有任务结束后运行,若后续任务依赖 Handler 的结果(例如:安装数据库后需立即启动,才能创建数据库用户),可用 meta: flush_handlers
强制立即执行已触发的 Handler。
---
- name: 立即执行Handlerhosts: node1tasks:- name: 安装mariadbyum:name: mariadb-serverstate: presentnotify:- 启动mariadb # 安装完成后通知启动数据库- meta: flush_handlers # 强制立即执行已触发的Handler(此时启动mariadb)- name: 创建数据库用户(依赖mariadb已启动)mysql_user: # 该模块需要数据库服务已运行,否则会失败name: bqpassword: 123handlers:- name: 启动mariadbservice:name: mariadbstate: started
...
错误处理
Ansible 默认会在任务失败时终止整个 Playbook 的执行。通过以下方式可自定义错误处理逻辑(例如:忽略不重要的错误,或失败后执行回滚)。
忽略错误(ignore_errors
)
场景:任务可能失败,但不影响后续流程(例如:检测一个可能不存在的文件,即使不存在也继续执行)。
---
- name: 忽略任务错误hosts: node1tasks:- 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” 表示逻辑失败)。
---
- name: 自定义失败条件hosts: node1tasks:- name: 执行自定义脚本shell: /root/adduser # 假设脚本执行成功(返回码0),但输出可能是"success"或"failed"register: resultfailed_when: "'failed' in result.stdout" # 若输出含"failed",则强制判定为任务失败
...
块任务(block
)与异常处理
block
用于将多个任务分组,结合 rescue
(异常处理,任务失败时执行)和 always
(始终执行,无论成功失败)实现复杂逻辑(例如:升级失败则回滚,无论成败都重启服务)。
场景:更新数据库,失败则回滚,无论成功与否都重启服务。
---
- name: 数据库更新与异常处理hosts: node1tasks:- block: # 主任务块(正常执行的逻辑)- name: 升级数据库shell: /usr/local/bin/upgrade-db # 执行升级脚本rescue: # 主任务块失败时执行(异常处理/回滚)- name: 回滚数据库shell: /usr/local/bin/rollback-db # 执行回滚脚本always: # 无论主任务块成功或失败,都执行(收尾操作)- name: 重启数据库服务service:name: mariadbstate: restarted
...
实施 Tags(任务标签)
Tags 用于只执行 Playbook 中的部分任务(例如:Playbook 包含 “安装软件”“配置文件”“启动服务” 多个任务,可通过标签只执行 “安装软件”)。
基础用法
实验流程:
- 给不同任务打标签(如
webserver
对应 web 服务相关任务,mailserver
对应邮件服务任务); - 执行 Playbook 时,通过
--tags
指定只运行的标签,或--skip-tags
指定排除的标签。
详细步骤:
---
- name: 带标签的任务示例hosts: node1gather_facts: notasks:- name: 安装httpdyum:name: httpdstate: latesttags: webserver # 打标签webserver(表示该任务属于web服务)- name: 安装postfixyum:name: postfixstate: latesttags: mailserver # 打标签mailserver(表示该任务属于邮件服务)- name: 输出调试信息debug:msg: "这是通用任务" # 无标签,默认会执行
...
执行命令:
# 只执行带webserver标签的任务(即只安装httpd)
ansible-playbook tags.yml --tags webserver# 执行除mailserver标签外的所有任务(安装httpd + 输出调试信息)
ansible-playbook tags.yml --skip-tags mailserver
...
特殊标签
always
:总是执行(除非用--skip-tags always
明确跳过)。never
:默认不执行(除非用--tags never
明确指定才执行)。
- name: 特殊标签示例hosts: node1tasks:- name: 总是执行的任务debug:msg: "我一定会执行"tags: always # 无论是否指定其他标签,该任务都会执行- name: 默认不执行的任务debug:msg: "除非指定--tags never,否则我不执行"tags: never # 不指定--tags never时,该任务不会执行
...
如涉及版权问题请联系作者处理!!!!!