7.Ansible自动化之-实施任务控制

发布于:2025-08-18 ⋅ 阅读:(9) ⋅ 点赞:(0)

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 权限管理)。

实验流程

  1. 先编写不含循环的 Playbook(直观感受重复任务的冗余);
  2. loop 改写 Playbook(简化任务,减少重复代码);
  3. 执行 Playbook 并验证用户是否成功创建。

详细步骤

  1. 不含循环的 Playbook(对比用)
    每次创建用户都需要写一个独立任务,用户数量越多,代码越冗余。

    ---
    - 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"
    ...
    
  2. loop 改写的 Playbook
    用一个任务配合 loop 列表,一次性处理多个用户。

    - name: 使用loop循环创建用户
      hosts: node1
      gather_facts: no
      tasks:
        - name: 批量创建用户
          user:
            name: "{{ item }}"  # item自动引用loop列表中的当前元素(第一次是jane,第二次是joe)
            groups: "wheel"
            state: present
          loop:  # 循环列表:需要创建的用户名
             - jane
             - joe
    
  3. 通过变量定义循环列表
    把循环列表存到变量中,方便后续修改(比如新增用户只需改变量)。

    - name: 用变量存储循环列表
      hosts: node1
      gather_facts: no
      tasks:
        - name: 批量创建用户
          user:
            name: "{{ item }}"
            groups: "wheel"
            state: present
          loop: "{{ users }}"  # 引用变量users作为循环列表
          vars:  # 定义变量users,值为需要创建的用户名列表
            users:
              - jane
              - joe
    
  4. 执行与验证

    # 执行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 取所属组)。

实验流程

  1. 定义包含用户信息的字典列表(每个字典存一个用户的名称和所属组);
  2. 编写循环任务,通过 item.键名 引用字典中的具体信息;
  3. 执行 Playbook 并验证用户组配置是否正确。

详细步骤

---
- 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(字典列表)
...

验证

# 在node1上检查用户所属组(groups命令可查看用户的所有组)
ssh node1 'groups jane; groups joe'
旧版循环关键字(仅供了解)

Ansible 2.5 之前常用 with_* 格式的循环(如 with_items),现在官方推荐用 loop(功能更统一)。以下为常见旧语法示例(不建议新环境使用):

  1. with_items(类似 loop,但会自动展开嵌套列表)

    ---
    - name: 旧语法with_items
      hosts: node1
      tasks:
        - name: 创建用户
          user:
            name: "{{ item }}"
            groups: wheel
          with_items:  # 等价于loop,但会展开嵌套列表(例如[[1,2],3]会变成1,2,3)
            - jane
            - joe
    ... 
    
  2. with_together(并行遍历多个列表)

    ---
    - 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"
    ...
    
  3. with_sequence(生成数字序列)

    ---
    - 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 定义每次重试的间隔时间(秒)。

- 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

场景:循环执行命令并收集每个迭代的结果(例如:循环输出多条信息,然后分别查看每条的执行结果)。

---
- 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 变量控制任务是否执行。

---
- 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 检查路径是否为普通文件。

---
- 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。

- 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。

---
- 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,最终只执行一次(避免重复操作,例如多次修改配置文件,只重启一次服务)。
基础用法

实验流程

  1. 安装 httpd 和 httpd-manual 软件;
  2. notify 关键字在软件安装(发生变更)时,触发 Handler 重启 httpd;
  3. 执行 Playbook 验证 Handler 是否生效(首次安装会重启,已安装则不重启)。

详细步骤

---
- 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。

---
- 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

场景:任务可能失败,但不影响后续流程(例如:检测一个可能不存在的文件,即使不存在也继续执行)。

---
- 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” 表示逻辑失败)。

---
- name: 自定义失败条件
  hosts: node1
  tasks:
    - name: 执行自定义脚本
      shell: /root/adduser  # 假设脚本执行成功(返回码0),但输出可能是"success"或"failed"
      register: result
      failed_when: "'failed' in result.stdout"  # 若输出含"failed",则强制判定为任务失败
...
块任务(block)与异常处理

block 用于将多个任务分组,结合 rescue(异常处理,任务失败时执行)和 always(始终执行,无论成功失败)实现复杂逻辑(例如:升级失败则回滚,无论成败都重启服务)。

场景:更新数据库,失败则回滚,无论成功与否都重启服务。

---
- 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 包含 “安装软件”“配置文件”“启动服务” 多个任务,可通过标签只执行 “安装软件”)。

基础用法

实验流程

  1. 给不同任务打标签(如 webserver 对应 web 服务相关任务,mailserver 对应邮件服务任务);
  2. 执行 Playbook 时,通过 --tags 指定只运行的标签,或 --skip-tags 指定排除的标签。

详细步骤

---
- 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: "这是通用任务"  # 无标签,默认会执行
...

执行命令

# 只执行带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: node1
  tasks:
    - name: 总是执行的任务
      debug:
        msg: "我一定会执行"
      tags: always  # 无论是否指定其他标签,该任务都会执行

    - name: 默认不执行的任务
      debug:
        msg: "除非指定--tags never,否则我不执行"
      tags: never  # 不指定--tags never时,该任务不会执行
...

如涉及版权问题请联系作者处理!!!!!


网站公告

今日签到

点亮在社区的每一天
去签到