ansible使用笔记(三)Jinja2模板及playbook简介

ansible playbook编写 一、Jinja2模板简介
Jinja2是什么
Jinja2是基于Python的模板引擎,包含变量和表达式两部分,两者在模板求值时会被替换为值,模板中还有标签,控制模板的逻辑
为什么要学习Jinja2模板
因为playbook的模板使用Python的Jinja2模块来处理
Jinja2模版的基本语法

  • 模板的表达式都是包含在他要分隔符"{{}}"内的
  • 控制的语句都是包含在分隔符"{% %}"内的
  • 模板支持注释,都是包含在分隔符'{# #}'内,支持块注释
  • 调用变量
    {{varname}}
  • 计算
    {{2+2}}
  • 判断
    {{1 in {{1,2,3}}
  • Jinja2模版控制语句
{% if name == '诗仙' %} 李白 {% elif name == '诗圣' %} 杜甫 {% elif name == '诗魔' %} 白居易 {% else %} 李贺 {% endif %}

Jinja2过滤器
变量可以通过过滤器修改,过滤器与变量用管道符号( | )分割,也可以用圆括号传递可选参数,多个过滤器可以链式调用,前一个过滤器的输出会被作业后一个过滤器的输入
例如:
加密一个字符串:{{'astr'|password_hash('sha512')}}
二、playbook简介 playbook是什么
playbook是ansible用于配置,部署和管理托管主机剧本,通过playbook的详细描述,执行其中一系列tasks,可以让远端主机达到预期的状态
也可以说 playbook字面意思即剧本,现实中由演员按剧本表演,在ansible中由计算机进行安装,部署应用,提供对外服务,以及组织计算机处理各种各样的事情
为什么要使用palybook
  • 执行一些简单的任务,使用ad-hoc命令可以方便的解决问题,但有时一个设施过于复杂时,执行ad-hoc命令是不合适的,最好使用playbook
  • playbook可以反复使用编写的代码,可以放到不同机器上面,像函数一样,最大化的利用代码,在使用ansible的过程中,处理的大部分操作都是在编写playbook
playbook语法基础(1)
  • playbook由YAML语文编写,遵循YAML标准
  • 在同一行中,#之后的内容表示注释
  • 同一个列表中元素应该保持相同缩进
  • playbook由一个或多个play组成
  • play中host,variables,roles,tasks等对角表示方法都是键值中间以": "分隔表示
  • YAML还有一个小的怪癖,它的文件开始行都是以 ---,这是YAML格式的一部分,表明一个文件的开始
playbook构成
  • hosts: 定义交要执行playbook的远程主机级
  • vars: 定义playbook运行时需要使用的变量
  • tasks: 定义将要远程主机上执行的任务列表
  • handlers: 定义task执行完成以后需要调用的任务
playbook执行结果
使用ansible-playbook运行playbook文件,输出内容为JSON格式,由不同颜色组成便于识别
  • 绿色代表执行成功
  • 黄色 代表系统状态发生改变
  • 红色代表执行失败
ansible具有幂等性,幂等性能够保证我们重复的执行一项操作时,得到的结果是相同的,我们再来回顾一下幂等性的概念
"幂等性"是什么意思呢?举个例子,你想把一个文件拷贝到目标主机的某个目录上,但是你不确定此目录中是否已经存在此文件,当你使用ansible完成这项任务时,就非常简单了,因为如果目标主机的对应目录中已经存在此文件,那么ansible则不会进行任何操作,如果目标主机的对应目录中并不存在此文件,ansible就会将文件拷贝到对应目录中,说白了,ansible是"以结果为导向的",我们指定了一个"目标状态",ansible会自动判断,"当前状态"是否与"目标状态"一致,如果一致,则不进行任何操作,如果不一致,那么就将"当前状态"变成"目标状态",这就是"幂等性","幂等性"可以保证我们重复的执行同一项操作时,得到的结果是一样的。
有了幂等性的概念上面显示颜色代表的不同结果就比较好理解了
  • 绿色 代表执行成功 结果已经和指定"目标状态"一致不需要修改
  • 黄色 执行成功,系统状态发生改变,修改过"目标状态"
  • 红色代表执行失败
三、playbook编写 使用ping模块编写 第一个playbook
[root@ansible default]# cat ping.yml ---#第一行,表示开始 - hosts: all#远程主机,多个可以用逗号孙隔开 remote_user: root#远程执行的用户名 tasks: - name: ping test#任务名称,你也可以理解为备注信息,会在执行中显示,方便阅读 ping:#模块名ping[root@ansible default]# ansible-playbook ping.yml -f 5#-f 并发进程数量,默认是5PLAY [all] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [cache] ok: [web1] ok: [web2] ok: [db2] ok: [db1]TASK [ping test] ************************************************************************************ ok: [cache] ok: [web1] ok: [web2] ok: [db1] ok: [db2]PLAY RECAP ****************************************************************************************** cache: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0 db1: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0 db2: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0 web1: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0 web2: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0

playbook语法基础(2)
  • tasks
    命令的集合
    每一个play包含一个task列表(任务列表)
    一个task在其所对应的所有主机上(通过hosts pattern匹配的所有主机) 执行完毕之后,下一个task才执行
  • hosts
    主机的集合
    定义要执行的主机
案例1:
实现 给web主机添加用户james
设置默认密码123456,并把james用户添加到users组
[root@ansible default]# cat user.yml --- - hosts: web remote_user: root tasks: - name: Add the user 'james' user: name: james groups: users - name: set passwd shell: echo 123456|passwd --stdin james[root@ansible default]# ansible-playbook user.yml PLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web1] ok: [web2]TASK [Add the user 'james'] ************************************************************************* changed: [web1] changed: [web2]TASK [set passwd] *********************************************************************************** changed: [web2] changed: [web1]PLAY RECAP ****************************************************************************************** web1: ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=0 web2: ok=3changed=2unreachable=0failed=0skipped=0rescued=0

案例2:
要求:
1.所以web主机安装Apache
2.修改配置文件的监听端口为8080
3.设置默认页面hello world
4.启动服务
5.设置开机自启
1) 先在本面准备默认访问
[root@ansible default]# cat /root/index.htm hello world

2)编号playbook
[root@ansible default]# cat http.yml --- - hosts: web remote_user: root tasks: - name: install one specific version of Apache//tasks01.安装 yum: name: httpd state: installed - lineinfile://tasks02.修改监听端口 path: /etc/httpd/conf/httpd.conf regexp: '^Listen' line: 'Listen 8080' - copy://tasks03替换主页index.html src: /root/index.html dest: /var/www/html/index.html - service://tasks04.开启服务 name: httpd enabled: yes state: restarted [root@ansible default]# ansible-playbook http.yml PLAY [web] ***********************************************************************************************************************TASK [Gathering Facts] *********************************************************************************************************** ok: [web1] ok: [web2]TASK [install one specific version of Apache] ************************************************************************************changed: [web2] changed: [web1]TASK [lineinfile] **************************************************************************************************************** changed: [web1] changed: [web2]TASK [copy] ********************************************************************************************************************** changed: [web2] changed: [web1]TASK [service] ******************************************************************************************************************* changed: [web2] changed: [web1]PLAY RECAP *********************************************************************************************************************** web1: ok=5changed=4unreachable=0failed=0skipped=0rescued=0ignored=0 web2: ok=5changed=4unreachable=0failed=0skipped=0rescued=0ignored=0[root@ansible default]# curl web1:8080//测试 holle world

四、playbook进阶 4.1 变量
案例3:**
要求:添加用户 使用变量username代替
给web主机添加用户james2,设置默认密码123456
[root@ansible default]# cat user2.yml --- - hosts: web remote_user: root vars: username: james2 tasks: - name: Add the user"{{username}}" user: name: "{{username}}" - shell: echo 123456|passwd --stdin "{{username}}"[root@ansible default]# ansible-playbook user2.yml PLAY [web] ************************......

用户设密码
作完以上的实验你肯定已经发现为什么不用user模块直接设置密码,而且用shell模块单独设置,这是因为user模块是把Password字符串直接写入shadow,并没有改变,而Linux的shadow密码是经过加密的,所以明文密码不能直接使用
解决方案
-使用变量过滤器password_hash
{{'urpassword'|password_hash('sha512')}}
最后的playbook如下
[root@ansible default]# cat user3.yml --- - hosts: web remote_user: root vars: username: james2 tasks: - name: Add the user "{{username}}" user: name: "{{username}}" group: root password: "{{'123qqq...A'|password_hash('sha512')}}"

变量参数
-e 传递变量或文件参数
-e @文件
文件的格式必须是YAML或JSON格式
[root@ansible default]# ansible-playbook user3.yml -e username=james4PLAY [web] *******************************************************************************************TASK [Gathering Facts] ******************************************************************************* ok: [web2] ok: [web1]TASK [Add the user "james4"] ************************************************************************* changed: [web2] changed: [web1]PLAY RECAP ******************************************************************************************* web1: ok=2changed=1unreachable=0failed=0skipped=0rescued=0 web2: ok=2changed=1unreachable=0failed=0skipped=0rescued=0 [root@ansible default]# ssh web1 id james4 uid=1007(james4) gid=0(root) 组=0(root)[root@ansible default]# cat username.yml --- username: james5[root@ansible default]# ansible-playbook user3.yml -e @username.ymlPLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web1] ok: [web2]TASK [Add the user "james5"] ************************************************************************ changed: [web2] changed: [web1]PLAY RECAP ****************************************************************************************** web1: ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 web2: ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0[root@ansible default]# cat username.yml --- username: james5 [root@ansible default]# ssh web1 id james5 uid=1008(james5) gid=0(root) 组=0(root)

4.2 error 处理 ansible-playbook对错误的处理
因为playbook是顺序执行,当遇到错误就会停止往下执行,默认情况判断上一个任务$?,如果值不为0就停止执行
但某些情况我们需要忽略错误继续执行
案例4:
要求:
创建缓存目录,然后重启 apache
1.因为 /tmp/web 之前已经存在 shell模块 mkdir报错playbook停止继续执行,但其实这个错误是不影响下面的任务执行
[root@ansible default]# cat Restart.yml --- - hosts: web remote_user: root tasks: - shell: mkdir /tmp/web - name: ReStart service httpd service: name: httpd state: restarted[root@ansible default]# ansible-playbook Restart.yml PLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web2] ok: [web1]TASK [shell] **************************************************************************************** [WARNING]: Consider using the file module with state=directory rather than running 'mkdir'.If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. fatal: [web2]: FAILED! => {"changed": true, "cmd": "mkdir /tmp/web", "delta": "0:00:00.008765", "end": "2020-11-19 16:55:04.967697", "msg": "non-zero return code", "rc": 1, "start": "2020-11-19 16:55:04.958932", "stderr": "mkdir: 无法创建目录\"/tmp/web\": 文件已存在", "stderr_lines": ["mkdir: 无法创建目录\"/tmp/web\": 文件已存在"], "stdout": "", "stdout_lines": []} fatal: [web1]: FAILED! => {"changed": true, "cmd": "mkdir /tmp/web", "delta": "0:00:00.007319", "end": "2020-11-19 16:55:05.751981", "msg": "non-zero return code", "rc": 1, "start": "2020-11-19 16:55:05.744662", "stderr": "mkdir: 无法创建目录\"/tmp/web\": 文件已存在", "stderr_lines": ["mkdir: 无法创建目录\"/tmp/web\": 文件已存在"], "stdout": "", "stdout_lines": []}PLAY RECAP ****************************************************************************************** web1: ok=1changed=0unreachable=0failed=1skipped=0rescued=0ignored=0 web2: ok=1changed=0unreachable=0failed=1skipped=0rescued=0ignored=0

错误处理方法:
添加错误选项:ignore_errors
True 表示忽略错误继续执行
False 表示遇到错误就停止执行(默认)
[root@ansible default]# cat Restart.yml --- - hosts: web remote_user: root tasks: - shell: mkdir /tmp/web ignore_errors: True - name: ReStart service httpd service: name: httpd state: restarted[root@ansible default]# ansible-playbook Restart.yml PLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web1] ok: [web2]TASK [shell] **************************************************************************************** [WARNING]: Consider using the file module with state=directory rather than running 'mkdir'.If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. fatal: [web2]: FAILED! => {"changed": true, "cmd": "mkdir /tmp/web", "delta": "0:00:00.007426", "end": "2020-11-19 17:01:57.077906", "msg": "non-zero return code", "rc": 1, "start": "2020-11-19 17:01:57.070480", "stderr": "mkdir: 无法创建目录\"/tmp/web\": 文件已存在", "stderr_lines": ["mkdir: 无法创建目录\"/tmp/web\": 文件已存在"], "stdout": "", "stdout_lines": []} ...ignoring fatal: [web1]: FAILED! => {"changed": true, "cmd": "mkdir /tmp/web", "delta": "0:00:00.014932", "end": "2020-11-19 17:01:57.853702", "msg": "non-zero return code", "rc": 1, "start": "2020-11-19 17:01:57.838770", "stderr": "mkdir: 无法创建目录\"/tmp/web\": 文件已存在", "stderr_lines": ["mkdir: 无法创建目录\"/tmp/web\": 文件已存在"], "stdout": "", "stdout_lines": []} ...ignoringTASK [ReStart service httpd] ************************************************************************ changed: [web2] changed: [web1]PLAY RECAP ****************************************************************************************** web1: ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=1 web2: ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=1

4.3 tags标签
tags:给指定的任务定义一个调用标识 有时把多个模块写在一个playbook中.但只想用到其中一个或其中几个模块
-使用方法在模块里添加tags标签
-playbook调用方式
-t TAGS, --tags=TAGS
案例5:
要求:调用任务 task03 创建文件
[root@ansible default]# cat tags2.yml --- - hosts: web remote_user: root tasks: - name: task01 file: path: /tmp/web/task01 state: touch tags: t1 - name: task02 file: path: /tmp/web/task02 state: touch tags: t2 - name: task03 file: path: /tmp/web/task03 state: touch tags: t3[root@ansible default]# ansible-playbook tags2.yml -t t3PLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web2] ok: [web1]TASK [task03] *************************************************************************************** changed: [web2] changed: [web1]PLAY RECAP ****************************************************************************************** web1: ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0 web2: ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0[root@ansible default]# ssh web1 ls /tmp/web/ task03

4.4 handlers
当关注的资源发生变化时采取的操作
notify这个action可用于每个play的最后触发,这样就可以避免有多次改变发生时每次都执行指定的操作,取而代之仅在所有的变化发生完成后一次性执行指定操作
在notify中列出的操作称为handler,即notify调用handler中定义的操作
我们还是拿上面的httpd服务举例
案例6:
要求:
修改httpd 默认index.html 然后重启httpd服务
[root@ansible default]# cat RestartHttp.yml --- - hosts: web remote_user: root tasks: - copy: src: /root/index.html dest: /var/www/html/index.html ignore_errors: True - name: ReStart service httpd service: name: httpd state: restarted[root@ansible default]# ansible-playbook RestartHttp.yml PLAY [web] *******************************************************************************************************************************************************************************************************TASK [Gathering Facts] ******************************************************************************************************************************************************************************************* ok: [web1] ok: [web2]TASK [copy] ****************************************************************************************************************************************************************************************************** changed: [web2] changed: [web1]TASK [ReStart service httpd] ************************************************************************************************************************************************************************************* changed: [web2] changed: [web1]PLAY RECAP ******************************************************************************************************************************************************************************************************* web1: ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=0 web2: ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=0

第二次执行相同操作
[root@ansible default]# ansible-playbook RestartHttp.yml

【ansible使用笔记(三)Jinja2模板及playbook简介】第二次和第一次执行的区别是没有在拷贝文件,因为第一次已经拷贝过了,但服务还是重启过两次,看上去好像没问题,但细想第二次文件没有改动过,和我们修改配置文件后重启服务的实际需求是有区别的,或者说第二次服务重次是完全没有必要的
这时就要用的notify调用handler中定义的操作
简单说就是当文件发生过改动后,才重启服务
修改配置后
[root@ansible default]# cat RestartHttp.yml --- - hosts: web remote_user: root tasks: - copy: src: /root/index.html dest: /var/www/html/index.html ignore_errors: True notify: - restart "ReStart service httpd"handlers: - name: ReStart service httpd service: name: httpd state: restarted[root@ansible default]# ansible-playbook RestartHttp.yml PLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web2] ok: [web1]TASK [copy] ***************************************************************************************** ok: [web1] ok: [web2]PLAY RECAP ****************************************************************************************** web1: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0 web2: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0

从执行结果来看 并没有重启httpd服务
4.5 when条件判断
有些时候需要在满足特定条件后再触发某一项操作,或在特定的条件下终止某个行为,这时候需要进行条件判断,when正是解决这个问题的最佳选择,行程中的系统变量facts作为when的条件,可以通过setup模块查看
案例7:
我们用debug模块做个测试 debug模块我们后面会讲到
通过两个when判断条件如果成立,则输出"System release is centos7"
[root@ansible default]# cat when.yml --- - hosts: cache remote_user: root tasks: - debug: msg: "System release is centos7" when: - ansible_distribution == "CentOS" - ansible_distribution_major_version == "7"[root@ansible default]# ansible-playbook when.yml PLAY [cache] ****************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [cache]TASK [debug] **************************************************************************************** ok: [cache] => { "msg": "System release is centos7" }PLAY RECAP ****************************************************************************************** cache: ok=2changed=0unreachable=0failed=0skipped=0rescued=0ignored=0

4.6 register返回值
有时候我们还需要更复杂的例子,如判断前一个命令的执行结果去处理后面的操作,这时候就需要register模块来保存前一个命令的返回状态,在后面进行调用
案例8:
要求:把系统负载太高的Apache服务停止
当系统负载超过0.7时,则关掉httpd
[root@web2 ~]# awk 'BEGIN{while(1){}}'//先用awk对web2提高系统负载,为测试创建条件[root@ansible default]# ssh web2 uptime//已经满足测试条件 11:10:11 up 24 days, 17:47,2 users,load average: 0.88, 0.30, 0.14root@all (5)[f:5]$ sys [root@ansible default]# ansible-console web//通过ansible-console 查看httpd状态 Welcome to the ansible console. Type help or ? to list commands.root@web (2)[f:5]$ systemctl status httpd web1 | CHANGED | rc=0 >> ● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled) Active: active (running) since 四 2020-11-19 17:58:04 CST; 17h ago ......web2 | CHANGED | rc=0 >> ● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled) Active: active (running) since 四 2020-11-19 17:58:03 CST; 17h ago ....root@web (2)[f:5]$ exit[root@ansible default]# cat when.yml --- - hosts: web remote_user: root tasks: - shell: uptime|awk '{printf("%.2f\n",$(NF-2))}' register: result//返回shell执行结果到变量result - name: stopservice service: name: httpd state: stopped when: result.stdout|float > 0.7//对result 结果进行判断[root@ansible default]# ansible-playbook when.yml PLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web1] ok: [web2]TASK [shell] **************************************************************************************** changed: [web1] changed: [web2]TASK [stopservice] ******************************************************************************** skipping: [web1] changed: [web2]PLAY RECAP ****************************************************************************************** web1: ok=2changed=1unreachable=0failed=0skipped=1rescued=0ignored=0 web2: ok=3changed=2unreachable=0failed=0skipped=0rescued=0ignored=0[root@ansible default]# ssh web2 systemctl status httpd ● httpd.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled) Active: inactive (dead) since 五 2020-11-20 11:24:23 CST; 41s ago//httpd服务已关闭 ......

4.7 with_items 循环
with_items是playbook标准循环,可以用于迭代一个列表或字典,通过{{item}}获取每次迭代的值
案例9:
要求:一次性创建多个用户nb、aa、plj、lx
[root@ansible default]# cat adduser.yml --- - hosts: cache remote_user: root tasks: - name: add users with_items: ["nb","aa","plj","lx"] user: group=wheel password={{'123456'|password_hash('sha512')}} name={{item}} [root@ansible default]# ansible-playbook adduser.yml PLAY [cache] ****************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [cache]TASK [add users] ************************************************************************************ changed: [cache] => (item=nb) changed: [cache] => (item=aa) changed: [cache] => (item=plj) changed: [cache] => (item=lx)PLAY RECAP ****************************************************************************************** cache: ok=2changed=1unreachable=0failed=0skipped=0rescued=0ignored=0

4.8 include
在编写playbook的时候随随着项目越来越大,playbook越来越复杂,修改也很麻烦,这时可以把一些paly、task或handler放到其他文件中,通过include指令包含进来是一个不错的选择
包含项包括
  • vars:变量层
  • tasks:任务层
  • handlers:触发条件
  • files:文件
  • template:模板
  • default:默认,优先级最低
案例10:
把任务层独立出来
[root@ansible default]# cat include1.yml --- - hosts: cache remote_user: root tasks: - include: in3.yml when: 2 > 1[root@ansible default]# cat in3.yml - debug: msg: "task1 in in3.yml" - debug: msg: "task2 in in3.yml"[root@ansible default]# ansible-playbook include1.yml

4.9 debug调试
如果对于Python语法不熟悉的,playbook书写起来容易出错,且排错困难,这里介绍几种简单的排错调试方式
检测语法
  • ansible-playbook --syntax-check playbook.ymal
显示受到影响的主机
  • --list-hosts
    显示工作的task
  • --list-tasks
    显示将要运行的tag
  • --list-tags
测试运行
模拟运行结果,但如果在任务列表中,任务2 需要任务1执行的结果,那么任务2可能会报错,因为任务1没有实际执行
  • ansible-playbook -C playbook.yaml
还可以在运行时输出更为详细的信息,帮助排错 比如:变量的值
案例11:
以之前when.yml为例,查看result变量中的内容
[root@ansible default]# catwhen.yml --- - hosts: web remote_user: root tasks: - shell: uptime|awk '{printf("%.2f\n",$(NF-2))}' register: result - name: stopservice service: name: httpd state: stopped when: result.stdout|float > 0.7 - name: Show debug info debug: var: result[root@ansible default]# ansible-playbook when.yml PLAY [web] ******************************************************************************************TASK [Gathering Facts] ****************************************************************************** ok: [web1] ok: [web2]TASK [shell] **************************************************************************************** changed: [web1] changed: [web2]TASK [stopservice] ******************************************************************************** skipping: [web1] ok: [web2]TASK [Show debug info] ****************************************************************************** ok: [web1] => { "result": { "changed": true, "cmd": "uptime|awk '{printf(\"%.2f\\n\",$(NF-2))}'", "delta": "0:00:00.010118", "end": "2020-11-20 15:12:05.959884", "failed": false, "rc": 0, "start": "2020-11-20 15:12:05.949766", "stderr": "", "stderr_lines": [], "stdout": "0.04", "stdout_lines": [ "0.04" ] } } ok: [web2] => { "result": { "changed": true, "cmd": "uptime|awk '{printf(\"%.2f\\n\",$(NF-2))}'", "delta": "0:00:00.010133", "end": "2020-11-20 15:12:05.484865", "failed": false, "rc": 0, "start": "2020-11-20 15:12:05.474732", "stderr": "", "stderr_lines": [], "stdout": "2.04", "stdout_lines": [ "2.04" ] } }PLAY RECAP ****************************************************************************************** web1: ok=3changed=1unreachable=0failed=0skipped=1rescued=0ignored=0 web2: ok=4changed=1unreachable=0failed=0skipped=0rescued=

    推荐阅读