Ansible 学习笔记:变量事实管理、任务控制与文件部署

发布于:2025-08-17 ⋅ 阅读:(14) ⋅ 点赞:(0)

管理变量和事实

管理 FACTS

FACTS 介绍

FACTS 是 “Flexible AC Transmission Systems(柔性交流输电系统)” 的缩写,是指一类用于增强电力系统传输能力、稳定性与控制性的电力电子装置或技术。

它通过快速调节系统中的电压、电流、阻抗或相位角,提高交流电网的传输效率与安全性。

查看 FACTS 内容

示例1:查看所有变量

 [yuxb@controller web 09:49:50]$ cat playbook.yml 
 ---
 # - name: Installs a package and prints the result
 #  hosts: node1
 #  tasks:
  #   - name: Install the package
  #     yum:
  #       name: httpd
  #       state: installed
  #     register: install_result
  #   - debug: 
  #       var: install_result
 - name: Dump facts
   hosts: node1
 ​
   tasks:
     - name: Print all facts
       debug:
         var: ansible_facts
 ...
 ​

示例2:查看单个变量

目的:

通过 setup 模块获取 facts,并在 playbook 中打印指定变量的值。

 ---
 - hosts: node1
   tasks:
     - name: Print Ansible facts
       debug: 
         msg: >
           The default IPv4 address of {{ ansible_fqdn }}
           is {{ ansible_default_ipv4.address }}

使用 setup 模块收集目标主机 facts:

 [yuxb@controller web 09:57:03]$ ansible node1 -m setup > node1.facts
 ^[[A[yuxb@controller web 09:57:1sz node1.factsacts
 ​

说明:

  • -m setup:使用 setup 模块收集系统信息(Facts)

  • > node1.facts:将输出保存到本地文件 node1.facts 中,便于查看和搜索

setup 和 gather_facts 模块

Facts 是 Ansible 自动收集的远程主机信息,比如:

  • 主机名、IP、操作系统、内存、CPU

  • 网络接口、磁盘挂载、Python 版本

  • 默认网关、路由、FQDN 等

setup模块
  • setup 是一个 Ansible 模块,你可以手动调用它来 收集并查看某个主机的所有 facts

  • 适合临时查看、调试或提取特定信息。

 # 收集并打印出 node1 的所有系统信息。
 ansible node1 -m setup
 ​
 # 只查看默认 IPv4 的 facts 信息
 ansible node1 -m setup -a 'filter=ansible_default_ipv4'
 ​
gather_facts 参数
  • gather_factsPlaybook 中的一个参数,用于指定 是否自动在执行任务前收集 facts

  • 默认是 true

示例:

 ---
 - hosts: all
   gather_facts: true  # 默认就是 true
   tasks:
     - name: Print OS type
       debug:
         msg: "This system is {{ ansible_os_family }}"

设置为 false(不收集):

 ---
 - hosts: all
   gather_facts: false
   tasks:
     - name: This will not have facts
       debug:
         msg: "No facts will be available"
总结对比表:
特性 setup 模块 gather_facts 参数
类型 模块(可命令行或 task 中使用) Playbook 级参数
作用时机 手动执行时收集 facts 在每次 Playbook 运行前自动执行
默认行为 不自动运行 默认是自动运行(true)
过滤功能 支持 filter 查询特定 facts 不支持 filter,只能全量收集
使用场景 临时查询、过滤 facts、调试等 用于 Playbook 中任务执行前的数据准备

实施任务控制

Ansible 中,"实施任务控制" 通常指的是对任务执行流程的控制,比如:

  • 条件执行(when)

  • 循环执行(with_items / loop)

  • 错误处理(block / rescue / ignore_errors)

  • 任务依赖(tags / handlers)

  • 异步执行(async / poll)

  • 任务注册与判断(register)

编写循环任务

复杂循环

 [yuxb@controller web 10:36:36]$ cat playbook.yml 
 # yaml格式起始行,一般不省略
 ---
 ​
 # Playbook中第一个play
 # play具有属性:name,hosts,become,tasks,缩进一致
 # name属性,用于简要描述play
 - name: Enable intranet services
   
   # hosts属性,用于定义要在哪个受管理节点执行
   hosts: node1
   
   # tasks属性,用于描述play中任务,属性是列表格式
   tasks:
     
     # 第一个任务
     # 任务具有属性:涵name和模块名等。
     # name属性,用于简要描述任务
     - name: latest version of httpd and firewalld installed
       
       # 指明模块名,也就是要执行的任务
       yum:
         
         # 执行要操作的rpm包名称
         name:
           # rpm包名称是-开头的列表格式,或者逗号分隔的列表格式
           - httpd
           - firewalld
         
         # 定义软件包的状态,lastet代表升级为最新版本
         state: latest
     
     # 第二个任务
     - name: test html page is installed
       # copy模块,用于将content属性值写入到目标文件
       copy:
         content: "Welcome Laoma WebSite!\n"
         dest: /var/www/html/index.html
     
     # 第三个任务
     - name: firewalld enabled and running
       # service模块,用于启用并启动firewalld服务
       service:
         name: "{{item}}"
         enabled: true
         state: started
       loop:
         - httpd
         - firewalld
     
     # 第四个任务
    # - name: firewalld permits access to httpd service
    #   # firewalld,用于放行http服务
    #   firewalld:
    #     service: http
    #     permanent: true
    #     state: enabled
    #     immediate: yes
     
     # 第五个任务
     - name: httpd enabled and running
       # service模块,用于启用并启动httpd服务
       service:
         name: httpd
         enabled: true
         state: started
 ​
 # Playbook中第二个play,-开头表示列表
 - name: Test intranet web server
   hosts: localhost
   become: no
   tasks:
     - name: connect to intranet web server
       # uri模块,用于测试网站是否可以访问
       uri:
         url: http://node1
         return_content: yes
         status_code: 200
 ​
 # yaml格式结束行,一般省略
 ...
 ​
 # 验证
 [yuxb@controller web 10:36:41]$ ansible-playbook playbook.yml 
 ​
 PLAY [Enable intranet services] *********************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [latest version of httpd and firewalld installed] **********************************************
 ok: [node1]
 ​
 TASK [test html page is installed] ******************************************************************
 changed: [node1]
 ​
 TASK [firewalld enabled and running] ****************************************************************
 ok: [node1] => (item=httpd)
 ok: [node1] => (item=firewalld)
 ​
 TASK [httpd enabled and running] ********************************************************************
 ok: [node1]
 ​
 PLAY [Test intranet web server] *********************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [localhost]
 ​
 TASK [connect to intranet web server] ***************************************************************
 ok: [localhost]
 ​
 PLAY RECAP ******************************************************************************************
 localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 node1                      : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 ​

简单循环

示例一
 [yuxb@controller web 10:40:57]$ vim playbook.yml
 ---
 - name: add several users
   hosts: node1
   gather_facts: no
   tasks:
     - name: add user jane
       user:
         name: "jane"
         groups: "wheel"
         state: present
     - name: add user joe
       user:
         name: "joe"
         state: present
         groups: "wheel"
 ...
 ​
 # 使用loop循环改写
 [yuxb@controller web 10:49:39]$ cat playbook.yml 
 ---
 - name: add several users
   hosts: node1
   gather_facts: no
   tasks:
     - name: add user jane
       user:
         name: "{{ item }}"
         groups: "wheel"
         state: present
       loop:
         - jane
         - joe  
 ...
 ​
 # 验证
 [yuxb@controller web 10:36:55]$ ansible-playbook playbook.yml 
 ​
 PLAY [add several users] ****************************************************************************
 ​
 TASK [add user jane] ********************************************************************************
 changed: [node1] => (item=jane)
 changed: [node1] => (item=joe)
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 ​
示例二
 [yuxb@controller web 10:58:12]$ cat playbook.yml 
 ---
 - name: add several users
   hosts: node1
   gather_facts: no
   tasks:
     - name: add user jane
       user:
         name: "{{ item.name }}"
         groups: "{{ item.groups }}"
         state: present
       loop:
         - name: jane
           groups: wheel
         - name: joe
           groups: root
 ...
 ​
 # 验证
 [yuxb@controller web 10:49:53]$ ansible-playbook playbook.yml 
 ​
 PLAY [add several users] ****************************************************************************
 ​
 TASK [add user jane] ********************************************************************************
 ok: [node1] => (item={u'name': u'jane', u'groups': u'wheel'})
 changed: [node1] => (item={u'name': u'joe', u'groups': u'root'})
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 ​

Do-Until Loops

Do-Until 循环 是一种 后测试循环结构,意思是:

  • 先执行一次循环体

  • 然后再判断条件

  • 直到条件为真时,循环才会终止

示例:测试 node2,什么时候可以ping通。

 [yuxb@controller web 10:58:14]$ vim playbook.yml 
 [yuxb@controller web 11:20:47]$ cat playbook.yml 
 ---
 - name: test loop
   hosts: node1
   gather_facts: no
   tasks:
     - shell: ping -c1 -w 2 node2
       register: result
       until: result.rc == 0
       retries: 20
       delay: 1
 ...
 ​
 # 关闭node2测试
 [yuxb@controller web 11:21:03]$ ansible-playbook playbook.yml 
 ​
 PLAY [test loop] ************************************************************************************
 ​
 TASK [shell] ****************************************************************************************
 FAILED - RETRYING: command (20 retries left).
 FAILED - RETRYING: command (19 retries left).
 FAILED - RETRYING: command (18 retries left).
 FAILED - RETRYING: command (17 retries left).
 ​
 # 测试过程中打开node2后
 [yuxb@controller web 11:21:03]$ ansible-playbook playbook.yml 
 ​
 PLAY [test loop] ************************************************************************************
 ​
 TASK [shell] ****************************************************************************************
 FAILED - RETRYING: command (20 retries left).
 FAILED - RETRYING: command (19 retries left).
 FAILED - RETRYING: command (18 retries left).
 FAILED - RETRYING: command (17 retries left).
 FAILED - RETRYING: command (16 retries left).
 FAILED - RETRYING: command (15 retries left).
 FAILED - RETRYING: command (14 retries left).
 FAILED - RETRYING: command (13 retries left).
 FAILED - RETRYING: command (12 retries left).
 FAILED - RETRYING: command (11 retries left).
 FAILED - RETRYING: command (10 retries left).
 FAILED - RETRYING: command (9 retries left).
 FAILED - RETRYING: command (8 retries left).
 FAILED - RETRYING: command (7 retries left).
 FAILED - RETRYING: command (6 retries left).
 FAILED - RETRYING: command (5 retries left).
 changed: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 ​

编写条件任务

在 Ansible 中,使用 when 子句实现条件判断,使任务根据特定条件是否满足来执行。

常见判断

在某些场景中,变量可能并不是每次都存在,如果直接使用未定义的变量,会导致任务报错。因此,判断变量是否已定义是一个最佳实践

变量是否定义判断

示例一

变量是否定义判断

 [yuxb@controller web 11:45:48]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   vars:
     username: yuxb
   tasks:
     - debug:
         msg: "var: username is defined"
       when: username is defined
 ...
 ​
 # 执行
 [yuxb@controller web 11:46:19]$ ansible-playbook playbook.yml 
 ​
 PLAY [node1] ****************************************************************************************
 ​
 TASK [debug] ****************************************************************************************
 ok: [node1] => {
     "msg": "var: username is defined"
 }
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 ​

示例2:判断受管主机是否具有相应设备。

 # 看有没有sdb硬盘
 [yuxb@controller web 11:49:01]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: yes
   vars:
     username: yuxb
   tasks:
     - debug:
         msg: "sdb is exist"
       when: ansible_devices.sdb is defined
 ...
 ​
 # 执行
 [yuxb@controller web 11:46:21]$ ansible-playbook playbook.yml 
 ​
 PLAY [node1] ****************************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [debug] ****************************************************************************************
 skipping: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0
 ​
 ​
 # 看有没有sr0盘
 [yuxb@controller web 11:49:05]$ vim playbook.yml 
 [yuxb@controller web 11:50:15]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: yes
   vars:
     username: yuxb
   tasks:
     - debug:
         msg: "sr0 is exist"
       when: ansible_devices.sr0 is defined
 ...
 ​
 # 执行
 [yuxb@controller web 11:49:13]$ ansible-playbook playbook.yml 
 ​
 PLAY [node1] ****************************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [debug] ****************************************************************************************
 ok: [node1] => {
     "msg": "sr0 is exist"
 }
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
in 和 not in 判断

用于判断某个值是否存在于一个 字符串列表字典键中

示例1:给用户添加组
 [yuxb@controller web 13:50:47]$ cat playbook.yml 
 ---
 - name: test 
   hosts: node1
   gather_facts: no
   vars:
     username: devops
     supergroup: wheel
   tasks:
     - name: gather user information
       shell: id {{ username }}
       register: result
     - name: Task run if user is in supergroups
       user:
         name: "{{ username }}"
         groups: "{{ supergroup }}"
         append: yes
       when: supergroup not in result.stdout
 ...
 ​
 [root@node1 ~ 13:47:57]# useradd devops
 [root@node1 ~ 13:48:12]# id devops
 uid=1091(devops) gid=1091(devops) 组=1091(devops)
 ​
 # 执行
 [yuxb@controller web 13:50:10]$ ansible-playbook playbook.yml 
 ​
 PLAY [test] *****************************************************************************************
 ​
 TASK [gather user information] **********************************************************************
 changed: [node1]
 ​
 TASK [Task run if user is in supergroups] ***********************************************************
 changed: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
示例二:根据主机所属组控制输出
 [yuxb@controller web 13:59:02]$ cat inventory 
 controller
 ​
 [nodes]
 node1
 node2
 node3
 node4
 ​
 [webs]
 node1 
 node2
 ​
 [dbs]
 node3
 node4
 ​
 [yuxb@controller web 13:58:56]$ cat playbook.yml 
 ---
 - name: test 
   hosts: node1 node3
   gather_facts: no
   tasks:
     - name: install httpd
       yum:
         name: httpd
         state: present
       when: inventory_hostname in groups.webs
     - name: install mariadb
       yum:
         name: mariadb       
         state: present    
       when: inventory_hostname in groups.dbs 
 ...
 ​
 # 执行
 [yuxb@controller web 14:02:01]$ ansible-playbook playbook.yml 
 ​
 PLAY [test] *****************************************************************************************
 ​
 TASK [install httpd] ********************************************************************************
 skipping: [node3]
 ok: [node1]
 ​
 TASK [install mariadb] ******************************************************************************
 skipping: [node1]
 changed: [node3]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
 node3                      : ok=1    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

loop 和 when 联合

  • loop:用于对一组元素逐个执行任务

  • when:用于对每一个循环项(item)进行条件判断

loopwhen 一起使用时,when 表达式会对 每个 item 单独判断

示例:当 / 文件系统可用空间大于300000000 安装 mariadb-server

解决方法:通过 ansible_facts 获取 / 文件系统可用空间

 [yuxb@controller web 14:27:02]$ cat playbook.yml 
 ---
 - name: Combining Loops and Conditional Play 
   hosts: node1
   tasks:
     - name: install mariadb-server if enough space on root
       yum:
         name: mariadb-server
         state: latest
       loop: "{{ ansible_mounts }}"
       when:
         - item.mount == "/"
         - item.size_available > 300000000
 ...
 ​
 # 执行
 [yuxb@controller web 14:25:52]$ ansible-playbook playbook.yml 
 ​
 PLAY [Combining Loops and Conditional Play] *********************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [install mariadb-server if enough space on root] ***********************************************
 skipping: [node1] => (item={u'block_used': 35554, u'uuid': u'b5c926d1-b582-436b-95a9-38664b23e619', u'size_total': 1063256064, u'block_total': 259584, u'mount': u'/boot', u'block_available': 224030, u'size_available': 917626880, u'fstype': u'xfs', u'inode_total': 524288, u'options': u'rw,relatime,attr2,inode64,noquota', u'device': u'/dev/sda1', u'inode_used': 326, u'block_size': 4096, u'inode_available': 523962}) 
 ok: [node1] => (item={u'block_used': 524536, u'uuid': u'db4466fc-68b8-4fd7-82b1-0b578187dafa', u'size_total': 53660876800, u'block_total': 13100800, u'mount': u'/', u'block_available': 12576264, u'size_available': 51512377344, u'fstype': u'xfs', u'inode_total': 26214400, u'options': u'rw,relatime,attr2,inode64,noquota', u'device': u'/dev/mapper/centos-root', u'inode_used': 34139, u'block_size': 4096, u'inode_available': 26180261})
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ansible Handlers

Handler(处理器) 是 Ansible 中的一种特殊任务,用于在任务发生变更(changed)时才触发执行的动作。

Ansible Handlers 功能

功能说明
  • Handlers 是为 “有变更时才执行的操作” 而设计的

  • 常用于在某些操作完成后执行额外的步骤,例如:

    • 修改配置文件后重启服务

    • 更新程序后重载守护进程

    • 改变权限后重新应用策略

工作原理
  1. 任务执行后发生变更(changed)

  2. 该任务中使用 notify 通知某个 handler

  3. Handler 在 playbook 的最后统一执行一次(除非用 meta: flush_handlers 强制提前执行)

示例 在远程主机 node1部署 Web 服务器

 [yuxb@controller web 14:40:17]$ cat playbook.yml 
 ---
 - name: deploy web server
   hosts: node1
   tasks:
     - name: install packages
       yum:
         name: httpd
         state: present
       notify:
         - enable and restart apache
 ​
     - name: install httpd-manual
       yum:
         name: httpd-manual
         state: present
       notify:
         - enable and restart apache
     
     - debug: 
         msg: last task in tasks
 ​
   handlers:
     - name: enable and restart apache
       service:
         name: httpd
         state: restarted
         enabled: yes
 ...
 ​
 # 执行
 [yuxb@controller web 14:27:08]$ ansible-playbook playbook.yml 
 ​
 PLAY [deploy web server] ****************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [install packages] *****************************************************************************
 changed: [node1]
 ​
 TASK [install httpd-manual] *************************************************************************
 changed: [node1]
 ​
 TASK [debug] ****************************************************************************************
 ok: [node1] => {
     "msg": "last task in tasks"
 }
 ​
 RUNNING HANDLER [enable and restart apache] *********************************************************
 changed: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

处理 Errors

Ansible评估各任务的返回代码,从而确定任务是成功还是失败。通常而言, 当某个主机执行任务失败时,Ansible将立即终止该主机继续执行play,其他主机可以继续执行play。

示例:

 # 环境
 [root@node1 ~ 14:37:44]# cp /etc/hosts /etc/myhost
 [root@node2 ~ 15:02:29]# rm -f /etc/myhost
 ​
 [yuxb@controller web 14:41:21]$ vim playbook.yml 
 [yuxb@controller web 15:03:54]$ cat playbook.yml 
 ---
 - name: test
   hosts: node1,node2
   tasks:
     - name: show /etc/myhosts
       shell: cat /etc/myhosts
     - name: echo end
       debug:
         msg: echo end
 ​
 # 执行
 [yuxb@controller web 15:01:27]$ ansible-playbook playbook.yml 
 ​
 PLAY [test] *****************************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node2]
 ok: [node1]
 ​
 TASK [show /etc/myhosts] ****************************************************************************
 fatal: [node2]: FAILED! => {"changed": true, "cmd": "cat /etc/myhosts", "delta": "0:00:00.003767", "end": "2025-08-14 15:02:49.212666", "msg": "non-zero return code", "rc": 1, "start": "2025-08-14 15:02:49.208899", "stderr": "cat: /etc/myhosts: 没有那个文件或目录", "stderr_lines": ["cat: /etc/myhosts: 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
 fatal: [node1]: FAILED! => {"changed": true, "cmd": "cat /etc/myhosts", "delta": "0:00:00.003663", "end": "2025-08-14 15:02:49.235356", "msg": "non-zero return code", "rc": 1, "start": "2025-08-14 15:02:49.231693", "stderr": "cat: /etc/myhosts: 没有那个文件或目录", "stderr_lines": ["cat: /etc/myhosts: 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
 node2                      : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

ignore_errors

您可能希望即使在任务失败时也继续执行play。例如,您或许预期特定任务有可能会失败,并且希望通过有条件地运行某项其他任务来恢复。

ignore_errors可以定义在以下位置:

  • 定义在 play 中,则play中所有任务忽略错误。

  • 定义在 task 中,则特定task忽略错误。

示例:

 [yuxb@controller web 15:03:57]$ vim playbook.yml 
 [yuxb@controller web 15:05:33]$ cat playbook.yml 
 ---
 - name: test
   hosts: node1
   tasks:
     - name: install a not exist package
       yum:
         name: notexitpackage
         state: present
       ignore_errors: yes
       register: result
     - name: debug install result
       debug:
         msg: notexitpackage is not exit
       when: result is failed
 ​
 # 执行
 [yuxb@controller web 15:05:16]$ ansible-playbook playbook.yml 
 ​
 PLAY [test] *****************************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [install a not exist package] ******************************************************************
 fatal: [node1]: FAILED! => {"changed": false, "msg": "No package matching 'notexitpackage' found available, installed or updated", "rc": 126, "results": ["No package matching 'notexitpackage' found available, installed or updated"]}
 ...ignoring
 ​
 TASK [debug install result] *************************************************************************
 ok: [node1] => {
     "msg": "notexitpackage is not exit"
 }
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1 

force_handlers

play中使用关键字 force_handlers,控制当play中某个任务执行失败后是否继续执行之前定义的notify,可用值yes或no(也可以是true或false),默认no(false)。

示例:

 [yuxb@controller web 15:05:39]$ vim playbook.yml 
 [yuxb@controller web 15:07:11]$ cat playbook.yml 
 ---
 - name: test
   hosts: node1
   force_handlers: yes
   tasks:
     - name: a task which always notifies its handler
       command: /bin/true
       notify: restart the sshd
     
     - name:  fails because the package doesn't exist
       yum:
         name: notexistpkg
         state: latest
 ​
   handlers:
     - name: restart the sshd
       service:
         name: sshd
         state: restarted
         
         
 [yuxb@controller web 15:05:45]$ ansible-playbook playbook.yml 
 ​
 PLAY [test] *****************************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [a task which always notifies its handler] *****************************************************
 changed: [node1]
 ​
 TASK [fails because the package doesn't exist] ******************************************************
 fatal: [node1]: FAILED! => {"changed": false, "msg": "No package matching 'notexistpkg' found available, installed or updated", "rc": 126, "results": ["No package matching 'notexistpkg' found available, installed or updated"]}
 ​
 RUNNING HANDLER [restart the sshd] ******************************************************************
 changed: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=3    changed=2    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0        

fail 模块

fail 模块,执行该任务,任务必定 failed。

示例:

 [yuxb@controller web 15:07:14]$ vim playbook.yml 
 [yuxb@controller web 15:09:33]$ cat playbook.yml 
 ---
 - name: test fail module
   hosts: node1
   gather_facts: no
   tasks:
     - debug:
         msg: task1
     - fail:
     - debug:
         msg: task3
         
         
 [yuxb@controller web 15:07:33]$ ansible-playbook playbook.yml 
 ​
 PLAY [test fail module] *****************************************************************************
 ​
 TASK [debug] ****************************************************************************************
 ok: [node1] => {
     "msg": "task1"
 }
 ​
 TASK [fail] *****************************************************************************************
 fatal: [node1]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0        

failed_when

指明什么条件下,判定任务执行失败。

示例:

 [yuxb@controller web 15:09:36]$ vim playbook.yml 
 [yuxb@controller web 15:11:21]$ cat playbook.yml 
 ---
 - name: test failed_when
   hosts: node1
   tasks:
     - shell: /root/adduser
       register: command_result
       failed_when: "'failed' in command_result.stdout"
 ​
 ​

环境准备:

 [root@node1 ~ 15:15:37]# vim /root/adduser
 [root@node1 ~ 15:16:06]# chmod +x /root/adduser
 [root@node1 ~ 15:16:11]# cat /root/adduser 
 #!/bin/bash
 useradd devops &> /dev/null
 if [ $? -eq 0 ];then
   echo add user devops success
 else
   echo add user devops failed
 fi
  • 当devops用户不存在时,shell模块跳过执行。

  • 当devops用户存在时,shell模块执行失败。

 [yuxb@controller web 15:09:49]$ ansible-playbook playbook.yml 
 ​
 PLAY [test failed_when] *****************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [shell] ****************************************************************************************
 fatal: [node1]: FAILED! => {"changed": true, "cmd": "/root/adduser", "delta": "0:00:00.005987", "end": "2025-08-14 15:16:36.903010", "failed_when_result": true, "rc": 0, "start": "2025-08-14 15:16:36.897023", "stderr": "", "stderr_lines": [], "stdout": "add user devops failed", "stdout_lines": ["add user devops failed"]}
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

changed_when

指明什么条件下,判定任务执行结果为changed。

示例1:

 [yuxb@controller web 15:11:24]$ vim playbook.yml 
 [yuxb@controller web 15:17:58]$ cat playbook.yml 
 ---
 - name: changed_when
   hosts: node1
   tasks:
     - name: upgrade-database
       shell: /usr/local/bin/upgrade-database
       register: result
       changed_when: "'Success' in result.stdout"
       notify:
         - restart_database
   handlers:
     - name: restart_database
       service:
         name: mariadb
         state: restarted

环境准备:

 [root@node1 ~ 15:18:25]# yum install -y mariadb-server
 已加载插件:fastestmirror
 Loading mirror speeds from cached hostfile
  * base: mirrors.aliyun.com
  * extras: mirrors.aliyun.com
  * updates: mirrors.aliyun.com
 软件包 1:mariadb-server-5.5.68-1.el7.x86_64 已安装并且是最新版本
 无须任何处理
 [root@node1 ~ 15:18:27]# systemctl enable mariadb --now
 ​
 [root@node1 ~ 15:18:27]# vim /usr/local/bin/upgrade-database
 [root@node1 ~ 15:18:49]# cat /usr/local/bin/upgrade-database
 #!/bin/bash
 mysql -e 'create user yuxb@"%" identified by "123";' && mysql -e 'GRANT ALL PRIVILEGES on *.* TO yuxb@"%";' && echo update database Success
 ​
 [root@node1 ~ 15:19:08]# chmod +x /usr/local/bin/upgrade-database
 ​
 ​
 [yuxb@controller web 15:20:07]$ ansible-playbook playbook.yml 
 ​
 PLAY [changed_when] *********************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node1]
 ​
 TASK [upgrade-database] *****************************************************************************
 fatal: [node1]: FAILED! => {"changed": false, "cmd": "/usr/local/bin/upgrade-database", "delta": "0:00:00.037942", "end": "2025-08-14 15:20:26.929733", "msg": "non-zero return code", "rc": 1, "start": "2025-08-14 15:20:26.891791", "stderr": "ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)", "stderr_lines": ["ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)"], "stdout": "", "stdout_lines": []}
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

对于command模块和shell模块,只要命令正常执行,结果状态通常都是changed。可以通过返回码和输出结果来判定它们是否做出更改。

关键字 changed_when: false ,让任务结果状态不为changed,只能报告为ok或failed。

示例2:

 [yuxb@controller web 15:18:01]$ vim playbook.yml 
 [yuxb@controller web 15:22:09]$ cat playbook.yml 
 ---
 - name: Test When
   hosts: node1
   gather_facts: no
   tasks:
   - name: test changed_when
     shell: cat /etc/redhat-release
     changed_when: false
     
     
 [yuxb@controller web 15:20:27]$ ansible-playbook playbook.yml 
 ​
 PLAY [Test When] ************************************************************************************
 ​
 TASK [test changed_when] ****************************************************************************
 ok: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

 [yuxb@controller web 15:22:20]$ vim playbook.yml 
 [yuxb@controller web 15:24:49]$ cat playbook.yml 
 ---
 - name: test
   hosts: node1,node2
   tasks:
     - name: show /etc/myhosts
       shell: cat /etc/hosts
       changed_when: false
     - name: echo end
       debug:
         msg: echo end
 ​
 [yuxb@controller web 15:22:13]$ ansible-playbook playbook.yml 
 ​
 PLAY [test] *****************************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node2]
 ok: [node1]
 ​
 TASK [show /etc/myhosts] ****************************************************************************
 ok: [node2]
 ok: [node1]
 ​
 TASK [echo end] *************************************************************************************
 ok: [node1] => {
     "msg": "echo end"
 }
 ok: [node2] => {
     "msg": "echo end"
 }
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 node2                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ansible block

多个任务作为block子条目,block作为多个任务整体。

Ansible 的 block 是一种语法结构,可以让你将多个任务组合在一起,并对这些任务统一地使用:

  • when 条件判断

  • become 权限提升

  • rescue 错误处理

  • always 最终操作(无论成功或失败都执行)

示例1:在所有受管节点上创建符合以下要求的逻辑卷:

  • 在research卷组中创建逻辑卷:

    • 逻辑卷名称为data

    • 逻辑卷大小为4000MiB

    • 使用ext4文件系统格式化逻辑卷

    • 将逻辑卷挂载到/data目录

    • 如果无法创建请求的逻辑卷大小,应显示错误信息:Could not create logical volume of that size 并且应改为使用大小800MiB。

  • 如果卷组research不存在,应显示错误信息:Volume does not exist

环境准备

 # node1:添加一块 20G sata 硬盘
 [root@node1 ~ 15:36:13]# vgcreate research /dev/sdb
   Physical volume "/dev/sdb" successfully created.
   Volume group "research" successfully created
 ​
 # node2:添加一块 20G sata 硬盘
 [root@node2 ~ 15:37:04]# parted /dev/sdb unit MiB mklabel msdos
 信息: You may need to update /etc/fstab.
 ​
 [root@node2 ~ 15:37:04]# parted /dev/sdb unit MiB mkpart primary 1 1025   
 信息: You may need to update /etc/fstab.
 ​
 [root@node2 ~ 15:37:11]# vgcreate research /dev/sdb1                      
   Physical volume "/dev/sdb1" successfully created.
   Volume group "research" successfully created
 ​

playbook.yaml内容如下:

 [yuxb@controller web 16:31:17]$ cat playbook.yml 
 ---
 - name: create logical volume
   hosts: all
   tasks:
     - name: create lv
       block:
         - name: Create a logical volume of 4000m
           lvol:
             vg: research
             lv: data
             size: 4000
       rescue:
         - name: Could not create logical volume of that size
           debug:
             msg: Could not create logical volume of that size
         - name: Create a logical volume of 800m
           lvol:
             vg: research
             lv: data
             size: 800
       always:
         - name: Create a ext4 filesystem
           filesystem:
             fstype: ext4
             dev: /dev/research/data
         - name: Create a directory
           file:
             path: /data
             state: directory
         - name: Mount up device
           mount:
             path: /data
             src: /dev/research/data
             fstype: ext4
             state: mounted
       when: ansible_lvm.vgs.research is defined
     - name: Volume does not exist
       debug:
         msg: Volume does not exist
       when: ansible_lvm.vgs.research is not defined
       
 [yuxb@controller web 16:30:01]$ ansible-playbook playbook.yml 
 ​
 PLAY [create logical volume] ************************************************************************
 ​
 TASK [Gathering Facts] ******************************************************************************
 ok: [node2]
 ok: [controller]
 ok: [node3]
 ok: [node1]
 ok: [node4]
 ​
 TASK [Create a logical volume of 4000m] *************************************************************
 skipping: [controller]
 skipping: [node3]
 skipping: [node4]
 [WARNING]: The value 4000 (type int) in a string field was converted to u'4000' (type string). If
 this does not look like what you expect, quote the entire value to ensure it does not change.
 fatal: [node2]: FAILED! => {"changed": false, "err": "  Volume group \"research\" has insufficient free space (255 extents): 1000 required.\n", "msg": "Creating logical volume 'data' failed", "rc": 5}
 changed: [node1]
 ​
 TASK [Could not create logical volume of that size] *************************************************
 ok: [node2] => {
     "msg": "Could not create logical volume of that size"
 }
 ​
 TASK [Create a logical volume of 800m] **************************************************************
 [WARNING]: The value 800 (type int) in a string field was converted to u'800' (type string). If this
 does not look like what you expect, quote the entire value to ensure it does not change.
 changed: [node2]
 ​
 TASK [Create a ext4 filesystem] *********************************************************************
 skipping: [controller]
 skipping: [node3]
 skipping: [node4]
 changed: [node2]
 changed: [node1]
 ​
 TASK [Create a directory] ***************************************************************************
 skipping: [controller]
 skipping: [node3]
 skipping: [node4]
 changed: [node2]
 changed: [node1]
 ​
 TASK [Mount up device] ******************************************************************************
 skipping: [controller]
 skipping: [node3]
 skipping: [node4]
 changed: [node2]
 changed: [node1]
 ​
 TASK [Volume does not exist] ************************************************************************
 ok: [controller] => {
     "msg": "Volume does not exist"
 }
 skipping: [node1]
 skipping: [node2]
 ok: [node3] => {
     "msg": "Volume does not exist"
 }
 ok: [node4] => {
     "msg": "Volume does not exist"
 }
 ​
 PLAY RECAP ******************************************************************************************
 controller                 : ok=2    changed=0    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0   
 node1                      : ok=5    changed=4    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
 node2                      : ok=6    changed=4    unreachable=0    failed=0    skipped=1    rescued=1    ignored=0   
 node3                      : ok=2    changed=0    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0   
 node4                      : ok=2    changed=0    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0
 # 验证
 [root@node1 ~ 16:33:32]# df /data/
 文件系统                    1K-块  已用    可用 已用% 挂载点
 /dev/mapper/research-data 3966144 15992 3728968    1% /data
 [root@node1 ~ 16:33:39]# df /data -h
 文件系统                   容量  已用  可用 已用% 挂载点
 /dev/mapper/research-data  3.8G   16M  3.6G    1% /data
 ​

部署文件到受管主机

修改文件并将其复制到主机

file 模块

示例1:创建文件或修改文件属性

 [yuxb@controller web 16:31:19]$ vim playbook.yml 
 [yuxb@controller web 17:08:29]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: Touch a file and set permissions
       file:
         path: /tmp/testfile
         owner: yuxb
         group: wheel
         mode: 0640
         state: touch
         
 [yuxb@controller web 16:31:07]$ ansible-playbook playbook.yml 
 ​
 PLAY [node1] ****************************************************************************************
 ​
 TASK [Touch a file and set permissions] *************************************************************
 changed: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
 ​
 [root@node1 ~ 17:13:27]# ls -l /tmp/testfile 
 -rw-r----- 1 yuxb wheel 0 8月  14 17:13 /tmp/testfile

示例2:删除文件

 state: absent 
 ​
 [yuxb@controller web 17:23:51]$ ansible-playbook playbook.yml
 ​
 [root@node1 ~ 17:13:48]# ls -l /tmp/testfile 
 ls: 无法访问/tmp/testfile: 没有那个文件或目录
 ​

lineinfile 模块

示例1:确保文件中存在特定行

 [yuxb@controller web 17:08:32]$ vim playbook.yml 
 [yuxb@controller web 18:44:27]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: add line
       lineinfile:
         path: /tmp/testfile
         line: 'Add this line to file'
         state: present
         create: yes
         
 [yuxb@controller web 17:24:19]$ ansible-playbook playbook.yml 
 ​
 PLAY [node1] ****************************************************************************************
 ​
 TASK [add line] *************************************************************************************
 changed: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
 ​
 [root@node1 ~ 17:24:26]# ls -l /tmp/testfile 
 -rw-r--r-- 1 root root 22 8月  14 18:44 /tmp/testfile
 [root@node1 ~ 18:45:06]# cat /tmp/testfile 
 Add this line to file

还可以在特定位置插入:

 [yuxb@controller web 18:47:41]$ vim playbook.yml 
 [yuxb@controller web 18:48:08]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: add line
       lineinfile:
         path: /tmp/testfile
         line: my
         insertbefore: file
         state: present
         create: yes
         
         
 [yuxb@controller web 18:44:52]$ ansible-playbook playbook.yml
 ​
 [root@node1 ~ 18:45:13]# cat /tmp/testfile 
 my
 Add this line to file

替换文本行:

 [yuxb@controller web 18:49:13]$ vim playbook.yml 
 [yuxb@controller web 18:49:42]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: add line
       lineinfile:
         path: /tmp/testfile
         line: my
         regexp: file
         state: present
         create: yes
         
 [yuxb@controller web 18:47:45]$ ansible-playbook playbook.yml 
 ​
 [root@node1 ~ 18:47:47]# cat /tmp/testfile 
 my
 my

replace 模块

 [yuxb@controller web 18:53:54]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: add line
       replace:
         path: /tmp/testfile
         replace: hello world
         regexp: my
         
         
 [yuxb@controller web 18:53:22]$ ansible-playbook playbook.yml
 ​
 [root@node1 ~ 18:49:49]# cat /tmp/testfile 
 hello world
 hello world
 ​

blockinfile 模块

 [yuxb@controller web 18:53:55]$ vim playbook.yml 
 [yuxb@controller web 18:59:17]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: add line
       blockinfile:
         path: /tmp/testfile
         block: |
            line 1 in file
            line 2 in file
         state: present
 ​
 ​
 [yuxb@controller web 18:53:58]$ ansible-playbook playbook.yml        
         
 [root@node1 ~ 18:54:14]# cat /tmp/testfile 
 hello world
 hello world
 # BEGIN ANSIBLE MANAGED BLOCK
 line 1 in file
 line 2 in file
 # END ANSIBLE MANAGED BLOCK
         

copy 模块

示例1:将控制节点上文件拷贝到受管理节点,类似于Linux中scp命令。

 [yuxb@controller web 18:59:18]$ vim playbook.yml 
 [yuxb@controller web 19:01:19]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: copy /tmp/testfile to remote node
       copy:
         src: /tmp/testfile
         dest: /tmp
 ​
 [yuxb@controller web 19:03:34]$ echo "hello from controller" > /tmp/testfile
 [yuxb@controller web 19:04:04]$ ansible-playbook playbook.yml 
 ​
 PLAY [node1] ****************************************************************************************
 ​
 TASK [copy /tmp/testfile to remote node] ************************************************************
 changed: [node1]
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 ​
 [root@node1 ~ 19:02:47]# cat /tmp/testfile 
 hello from controller
 ​

stat 模块

stat 模块检索文件的信息,类似于Linux stat命令。参数提供检索文件属性、确定文件校验和等功能。

stat 模块返回一个包含文件状态数据的值的散列字典,允许您使用单独的变量引用各条信息。

示例:

 [yuxb@controller web 19:01:20]$ vim playbook.yml 
 [yuxb@controller web 19:04:59]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - stat:
         path: /tmp/testfile
         checksum_algorithm: md5
       register: result
     - debug:
         msg: "/tmp/testfile md5 is {{ result.stat.checksum }}"
     - debug:
         var: result
 ​
 [yuxb@controller web 19:05:05]$ ansible-playbook playbook.yml
 ​
 PLAY [node1] ****************************************************************************************
 ​
 TASK [stat] *****************************************************************************************
 ok: [node1]
 ​
 TASK [debug] ****************************************************************************************
 ok: [node1] => {
     "msg": "/tmp/testfile md5 is 610c7f16071ca402c0cba45e4b2af437"
 }
 ​
 TASK [debug] ****************************************************************************************
 ok: [node1] => {
     "result": {
         "ansible_facts": {
             "discovered_interpreter_python": "/usr/bin/python"
         }, 
         "changed": false, 
         "failed": false, 
         "stat": {
             "atime": 1755169454.854485, 
             "attr_flags": "", 
             "attributes": [], 
             "block_size": 4096, 
             "blocks": 8, 
             "charset": "us-ascii", 
             "checksum": "610c7f16071ca402c0cba45e4b2af437", 
             "ctime": 1755169447.1454144, 
             "dev": 64768, 
             "device_type": 0, 
             "executable": false, 
             "exists": true, 
             "gid": 0, 
             "gr_name": "root", 
             "inode": 67588143, 
             "isblk": false, 
             "ischr": false, 
             "isdir": false, 
             "isfifo": false, 
             "isgid": false, 
             "islnk": false, 
             "isreg": true, 
             "issock": false, 
             "isuid": false, 
             "mimetype": "text/plain", 
             "mode": "0644", 
             "mtime": 1755169446.892412, 
             "nlink": 1, 
             "path": "/tmp/testfile", 
             "pw_name": "root", 
             "readable": true, 
             "rgrp": true, 
             "roth": true, 
             "rusr": true, 
             "size": 22, 
             "uid": 0, 
             "version": "1806842840", 
             "wgrp": false, 
             "woth": false, 
             "writeable": true, 
             "wusr": true, 
             "xgrp": false, 
             "xoth": false, 
             "xusr": false
         }
     }
 }
 ​
 PLAY RECAP ******************************************************************************************
 node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
 ​

fetch 模块

从受管节点检索文件,例如将被管理节点文件先取到控制节点,然后用于分发到其他节点。诸如SSH公钥之类的文件。

示例:

 [yuxb@controller web 19:05:02]$ vim playbook.yml 
 [yuxb@controller web 19:07:28]$ cat playbook.yml 
 ---
 - hosts: node1
   gather_facts: no
   tasks:
     - name: fetch file from remote node
       fetch:
         src: /tmp/testfile
         dest: /tmp
 [yuxb@controller web 19:05:06]$ ansible-playbook playbook.yml
 ​
 [yuxb@controller web 19:08:08]$ tree /tmp/node1/
 /tmp/node1/
 └── tmp
     └── testfile
 ​
 1 directory, 1 file
 ​


网站公告

今日签到

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