黑帽联盟

标题: ansible笔记(37):include(二) [打印本页]

作者: yun    时间: 2019-9-10 14:51
标题: ansible笔记(37):include(二)
本帖最后由 yun 于 2019-9-11 17:17 编辑

ansible是一个系列文章,我们会尽量以通俗易懂的方式总结ansible的相关知识点。
ansible系列博文直达链接:ansible轻松入门系列
"ansible系列"中的每篇文章都建立在前文的基础之上,所以,请按照顺序阅读这些文章,否则有可能在阅读中遇到障碍。


在上一篇文章中我们总结了"include"的使用方法,而且我们提到,"include"的某些原始用法在之后的版本中可能会被弃用,在之后的版本中,会使用一些新的关键字代替这些原始用法,那么在这篇文章中,我们就来介绍一下这些与"include"有关的新的关键字。


include_tasks
先来介绍一下include_tasks模块,在理解了include以后,再来理解include_tasks,简直不要太轻松,我们知道,include模块可以用来包含一个任务列表,include_tasks模块的作用也是用来包含一个任务列表,在之后的版本中,如果我们想要包含一个任务列表,那么就可以使用"include_tasks"关键字代替"include"关键字,示例如下:
  1. # cat intest.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   tasks:
  7.   - debug:
  8.       msg: "test task1"
  9.   - include_tasks: in.yml
  10.   - debug:
  11.       msg: "test task2"

  12. # cat in.yml
  13. - debug:
  14.     msg: "task1 in in.yml"
  15. - debug:
  16.     msg: "task2 in in.yml"
复制代码
如上例所示,当我们需要包含一个任务列表时,"include_tasks"关键字的用法与"include"完全相同,那么我们执行一下上例的playbook,看看执行效果如何
1.png
如上图所示,当我们使用"include_tasks"时,"include_tasks"本身会被当做一个"task",这个"task"会把被include的文件的路径输出在控制台中,这就是"include_tasks模块"与"include模块"之间的区别,如果将上例中的"include_tasks"关键字替换成"include",控制台中则不会显示上图中标注的任务信息,由此可见"include"是透明的,"include_tasks"是可见的,"include_tasks"更像是一个任务,这个任务包含了其他的一些任务。

在ansible的2.7版本中,"include_tasks"模块中加入了新的参数,由于我当前使用的版本为2.6.2,所以此处先将ansible升级至2.7.0版本(当前使用yum源能够更新的最新版本为2.7.0),以便了解"include_tasks"模块的新参数,在之后的文章中,如果没有特殊说明,则使用的是2.7.0版本的ansible进行演示。

从2.7版本开始,"include_tasks"模块加入了file参数和apply参数,先来聊聊file参数,file参数可以用来指定要包含的任务列表文件,语法如下:
  1.   - include_tasks:
  2.       file: in.yml
复制代码
上例表示包含in.yml文件,in.yml文件中的任务将被引用,你可能会有疑问,上例的写法和如下写法的效果相同吗?
  1.   - include_tasks: in.yml
复制代码
没错,这两种写法的最终效果是完全相同的,只不过一个使用了"file"参数的方式,另一个使用了"free_form"的方式(free_form的意思前文中已经总结过,此处不再赘述),虽然语法上不同,但是本质上没有区别。

在前一篇文章中我们演示过,如果为include添加tags,那么tags是对include文件中的所有任务生效的,也就是说,如果调用include对应的tag,那么include文件中的所有任务都会执行,如果对"include_tasks"添加tags,tags是不是也会对include_tags中的所有任务生效呢?我们来试试,示例如下
  1. # cat intest.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   tasks:
  7.   - debug:
  8.       msg: "test task1"
  9.   - include_tasks:
  10.       file: in.yml
  11.     tags: t1
  12.   - debug:
  13.       msg: "test task2"
复制代码
如上例所示,我们为"include_tasks"任务添加了标签t1,那么,我们在执行此playbook时,指定使用t1标签,执行后输出如下
  1. # ansible-playbook intest.yml --tags t1

  2. PLAY [test70] *******************************************

  3. TASK [include_tasks] *************************************
  4. included: /testdir/ansible/in.yml for test70

  5. PLAY RECAP ********************************************
  6. test70                     : ok=1    changed=0    unreachable=0    failed=0
复制代码
从执行后的输出信息可以看出,当我们指定t1标签后,"include_tasks"这个任务本身被调用了,而"include_tasks"对应文件中的任务却没有被调用,所以我们可以得出结论,在使用tags时,"include_tasks"与"include"并不相同,标签只会对"include_tasks"任务本身生效,而不会对其中包含的任务生效。
如果我们想要tags对"include_tasks"中包含的所有任务都生效,该怎么实现呢?这时,我们就需要使用到"include_tasks"模块的apply参数了,示例如下:
  1. ---
  2. - hosts: test70
  3.   remote_user: root
  4.   gather_facts: no
  5.   tasks:
  6.   - include_tasks:
  7.       file: in.yml
  8.       apply:
  9.         tags:
  10.         - t1
复制代码
如上例所示,除了使用file参数指定包含的任务列表文件以外,还使用了apply参数,apply参数调用了tags关键字,tags关键字用于指定一个标签列表,列表中的标签将会被应用到in.yml文件中的所有任务上,换句话说就是,apply参数可以指定哪些标签会被应用到被包含的任务上,聪明如你,一定看懂了,但是,按照上述语法,就能够正常的将t1标签应用到in.yml中的所有任务吗?不如我们来实际测试一下,看看与我们预想的结果是否一致,上例playbook执行后结果如下
  1. # ansible-playbook intest.yml --tags t1

  2. PLAY [test70] *******************************************

  3. PLAY RECAP ********************************************
复制代码
如你所见,当我们执行上例playbook以后,并没有如我们预想的一样调用in.yml中的任务,就连"include_tasks"任务自身也没有被调用,我们该怎么办呢?如果想让上例playbook按照我们所想的那样运行,则需要使用如下方法:
  1. ---
  2. - hosts: test70
  3.   remote_user: root
  4.   gather_facts: no
  5.   tasks:
  6.   - include_tasks:
  7.       file: in.yml
  8.       apply:
  9.         tags:
  10.         - t1
  11.     tags: always
复制代码
如上例所示,在使用"include_tasks"时,不仅使用apply参数指定了tags,同时还使用tags关键字,对"include_tasks"本身添加了always标签(前文中已经总结过always标签的含义,如果你忘记了可以回顾前文),按照上例的语法,才能够按照我们所想的那样,将t1标签应用到in.yml中的所有任务上,效果如下:
  1. # ansible-playbook intest.yml --tags t1

  2. PLAY [test70] *******************************************

  3. TASK [include_tasks] *************************************
  4. included: /testdir/ansible/in.yml for test70

  5. TASK [debug] *******************************************
  6. ok: [test70] => {
  7.     "msg": "task1 in in.yml"
  8. }

  9. TASK [debug] *******************************************
  10. ok: [test70] => {
  11.     "msg": "task2 in in.yml"
  12. }

  13. PLAY RECAP *******************************************
  14. test70                     : ok=3    changed=0    unreachable=0    failed=0
复制代码
从上述执行结果可以看出,当我们调用t1标签时,in.yml中的所有任务都执行了,但是你可能会有疑问,如果对"include_tasks"自身添加了always标签,那么我们调用其他标签时,in.yml文件中的任务也会"always"执行吗?我们来测试一下,playbook如下:
  1. # cat intest.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   tasks:
  7.   - debug:
  8.       msg: "test task"
  9.     tags: t0
  10.   - include_tasks:
  11.       file: in.yml
  12.       apply:
  13.         tags:
  14.         - t1
  15.     tags: always
复制代码
执行上例playbook时,我们只调用t0标签,看看t1标签对应的in.yml中的任务会不会"always"执行,测试结果如下:
  1. # ansible-playbook intest.yml --tags t0

  2. PLAY [test70] *******************************************

  3. TASK [debug] *******************************************
  4. ok: [test70] => {
  5.     "msg": "test task"
  6. }

  7. TASK [include_tasks] *************************************
  8. included: /testdir/ansible/in.yml for test70

  9. PLAY RECAP ********************************************
  10. test70                     : ok=2    changed=0    unreachable=0    failed=0
复制代码
从上述测试结果可以看出,in.yml中的任务并未"always"执行,而"include_tasks"任务本身却"always"执行了,所以,我们可以得出结论,上例中的always标签只是针对"include_tasks"任务自身而言的,之所以为"include_tasks"任务添加always标签,就是为了让apply参数中的t1标签能够针对包含的所有任务生效,always标签并不会对被包含的所有任务生效,如果想要被包含的任务也都"always"执行,该怎么办呢?你一定已经想到了,没错,在apply参数的tags列表中添加always标签即可,示例如下:
  1. ---
  2. - hosts: test70
  3.   remote_user: root
  4.   gather_facts: no
  5.   tasks:
  6.   - debug:
  7.       msg: "test task"
  8.     tags: t0
  9.   - include_tasks:
  10.       file: in.yml
  11.       apply:
  12.         tags: t1,always
  13.     tags: always
复制代码
综上所述就是,apply参数中的tags用于给in.yml中的任务统一打标签,"include_tasks"对应的tags用于给"include_tasks"任务自身打标签,同时,如果想要apply参数中的tags能够生效,"include_tasks"的标签中必须包含always标签,是不是觉得有点"绕",多动手试试就不绕了。

除了使用参数的方式能够指定tags,使用"free_form"的方式也可以指定tags,示例如下:
  1. ---
  2. - hosts: test70
  3.   remote_user: root
  4.   gather_facts: no
  5.   tasks:
  6.   - debug:
  7.       msg: "test task"
  8.     tags: t0
  9.   - include_tasks: in.yml
  10.     args:
  11.       apply:
  12.         tags: t1
  13.     tags: always
复制代码
我觉得我已经说清楚了,你肯定也已经明白了。

import_tasks
刚才已经了解了"include_tasks"的用法,现在聊聊另一个与它很像的模块:"import_tasks",如果想要包含引用一个任务列表,也可以使用"import_tasks"关键字,示例如下:
  1. # cat intest1.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   tasks:
  7.   - debug:
  8.       msg: "test task"
  9.   - import_tasks: in.yml

  10. # cat in.yml
  11. - debug:
  12.     msg: "task1 in in.yml"
  13. - debug:
  14.     msg: "task2 in in.yml"
复制代码
如上例所示,"import_tasks"模块使用了free-form的方式引用了in.yml文件,到目前的版本为止,"import_tasks"模块只能使用free-form的方式引用任务列表文件,没有其他方式可以使用,那么我们来执行一下上例playbook,执行后输出如下:
  1. # ansible-playbook intest1.yml

  2. PLAY [test70] *******************************************

  3. TASK [debug] *******************************************
  4. ok: [test70] => {
  5.     "msg": "test task"
  6. }

  7. TASK [debug] *******************************************
  8. ok: [test70] => {
  9.     "msg": "task1 in in.yml"
  10. }

  11. TASK [debug] *******************************************
  12. ok: [test70] => {
  13.     "msg": "task2 in in.yml"
  14. }

  15. PLAY RECAP ********************************************
  16. test70                     : ok=3    changed=0    unreachable=0    failed=0
复制代码
从输出的结果可以看出,"import_tasks"模块并不会像"include_tasks"模块那样,在控制台中输出相关的任务信息,"import_tasks"是相对透明的。

除了上述不同,"import_tasks"和"include_tasks"到底有什么不同之处呢?它们的不同之处在于,"import_tasks"是静态的,"include_tasks"是动态的。

那么动态和静态又是什么意思呢,它们有什么不同呢?动态和静态的主要区别在于被include的文件的加载时机不同。
"静态"的意思就是被include的文件在playbook被加载时就展开了(是预处理的)。
"动态"的意思就是被include的文件在playbook运行时才会被展开(是实时处理的)。

由于"include_tasks"是动态的,所以,被include的文件的文件名可以使用任何变量替换。
由于"import_tasks"是静态的,所以,被include的文件的文件名不能使用动态的变量替换。
这样说可能不容易理解,不如来看一个小示例,有时候,我们想要使用变量替换被包含文件的文件名,示例如下:
  1. # cat intest3.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   vars:
  7.     file_name: in.yml
  8.   tasks:
  9.   - import_tasks: "{{file_name}}"
  10.   - include_tasks: "{{file_name}}"
复制代码
如上例所示,被包含的文件名并没有写死,而是使用变量替换成了对应的文件名,执行上例playbook,是完全可以正常执行的。
但是,如果我们不使用vars关键字定义变量,而是使用set_fact的方式定义变量,能不能正常执行呢?我们来试试,示例如下:
  1. # cat intest3.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   tasks:
  7.   - set_fact:
  8.       file_name: in.yml
  9.   - import_tasks: "{{file_name}}"
  10.   - include_tasks: "{{file_name}}"
复制代码
如上例所示,我们使用了set_fact的方式定义了file_name变量,尝试执行上例playbook,会发现如下报错:
  1. # ansible-playbook intest3.yml
  2. ERROR! Error when evaluating variable in import path: {{file_name}}.

  3. When using static imports, ensure that any variables used in their names are defined in vars/vars_files
  4. or extra-vars passed in from the command line. Static imports cannot use variables from facts or inventory
  5. sources like group or host vars.
复制代码
从报错信息可以看出,当使用静态的import时,请确保文件名中使用到的变量被定义在vars中、vars_files中、或者extra-vars中,静态的import不支持其他方式传入的变量。

除了上述不同之处,在使用"循环操作"和"条件判断"时,"include_tasks"和"import_tasks"也有很多不同点需要注意,注意点如下。

如果想要对包含的任务列表进行循环操作,则只能使用"include_tasks"关键字,不能使用"import_tasks"关键字,"import_tasks"并不支持循环操作,
也就是说,使用"loop"关键字或"with_items"关键字对include文件进行循环操作时,只能配合"include_tasks"才能正常运行。

我们知道,当使用when关键字对include文件添加了条件判断时,只有条件满足后,include文件中的任务列表才会被执行,其实,when关键字对"include_tasks"和"import_tasks"的实际操作有着本质区别,区别如下:
当对"include_tasks"使用when进行条件判断时,when对应的条件只会应用于"include_tasks"任务本身,当执行被包含的任务时,不会对这些被包含的任务重新进行条件判断。
当对"import_tasks"使用when进行条件判断时,when对应的条件会应用于被include的文件中的每一个任务,当执行被包含的任务时,会对每一个被包含的任务进行同样的条件判断。

这样说不容易理解,来看一个小示例,就容易理解多了,示例playbook如下:
  1. # cat intest4.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   tasks:
  7.   - name: '----------set testvar to 0'
  8.     set_fact:
  9.       testnum: 0
  10.   - debug:
  11.       msg: '-----include_tasks-----enter the in1.yml-----'
  12.   - include_tasks: in1.yml
  13.     when: testnum == 0

  14.   - name: '----------set testvar to 0'
  15.     set_fact:
  16.       testnum: 0
  17.   - debug:
  18.       msg: '-----import_tasks-----enter the in1.yml-----'
  19.   - import_tasks: in1.yml
  20.     when: testnum == 0

  21. # cat in1.yml
  22. - set_fact:
  23.     testnum: 1

  24. - debug:
  25.     msg: "task1 in in1.yml"
复制代码
如上例 所示,我们分别使用"include_tasks"和"import_tasks"两个关键字包含了in1.yml文件,并且,二者都有相同的条件判断,即当testnum变量的值为0时,才会调用in1.yml中的任务,无论是在测试"include_tasks"还是测试"import_tasks"之前,都会使用set_fact将testnum的值设置为0,以便条件成立后执行in.yml中的任务,在in.yml中 ,又使用set_fact将testnum的值设置为了1,为什么这样做呢?咱们稍后解释,同时,在in1.yml中输出了一条测试信息,那么你预想一下,如果执行上例的intest4.yml剧本,"task1 in in1.yml"这条测试信息会输出几次呢?请先在你的大脑里预演一遍上例的playbook,然后再查看实际的运行效果 ,实际的运行效果如下:
  1. PLAY [test70] *********************************************

  2. TASK [----------set testvar to 0] ******************************
  3. ok: [test70]

  4. TASK [debug] *********************************************
  5. ok: [test70] => {
  6.     "msg": "-----include_tasks-----enter the in1.yml-----"
  7. }

  8. TASK [include_tasks] ***************************************
  9. included: /testdir/ansible/in1.yml for test70

  10. TASK [set_fact] ********************************************
  11. ok: [test70]

  12. TASK [debug] **********************************************
  13. ok: [test70] => {
  14.     "msg": "task1 in in1.yml"
  15. }

  16. TASK [----------set testvar to 0] *******************************
  17. ok: [test70]

  18. TASK [debug] **********************************************
  19. ok: [test70] => {
  20.     "msg": "-----import_tasks-----enter the in1.yml-----"
  21. }

  22. TASK [set_fact] *********************************************
  23. ok: [test70]

  24. TASK [debug] **********************************************
  25. skipping: [test70]

  26. PLAY RECAP ***********************************************
  27. test70                     : ok=8    changed=0    unreachable=0    failed=0
复制代码
从上述实际运行结果中可以看出,"task1 in in1.yml"这条测试信息只输出了一遍,这可能与我们预想的效果不太一样,那么上例具体是怎样执行的呢?我们来捋一遍,之前粗略的解释过,当对"include_tasks"使用when进行条件判断时,when对应的条件只会应用于"include_tasks"任务本身,所以,当testnum的值为0时,条件成立,in1.yml中的任务被执行,于是,debug模块输出了 "task1 in in1.yml",如我们所想,一切正常,然后继续执行之后的任务,我们重新将testnum的值设置为了0,所以"import_tasks"的条件" testnum == 0"也同样成立了 ,于是,开始执行in1.yml文件中的任务,之前粗略的介绍过,when对应的条件会应用于被include的文件中的每一个任务,当执行被包含的任务时,会对每一个被包含的任务进行同样的条件判断,所以,当执行in1.yml文件中的第一个任务(set_fact任务)时,此任务会再次对" testnum == 0"条件进行判断,如果"testnum == 0",则执行set_fact任务,此时,条件完全成立,于是,执行set_fact任务,将testnum的值设置为1,当执行到debug任务时,同样会对debug任务进行" testnum == 0"条件判断,如果条件成立,则执行debug任务,但是,由于上一个任务已经把testnum的值设置为1,所以此时条件不成立,于是,debug任务并没有被执行,这就是上例playbook运行的整个过程,你肯定已经明白了,由此可见,"when"对于"include_tasks"和"import_tasks"的操作在本质上存在差别。

说完了上述注意点,我们再来聊聊"tags"与"handlers"。

与"include_tasks"不同,当为"import_tasks"添加标签时,tags是针对被包含文件中的所有任务生效的,与"include"关键字的效果相同。

"include_tasks"与"import_tasks"都可以在handlers中使用,并没有什么不同,不过在当前2.7.0版本中,如果在handlers中使用"import_tasks"引用任务列表,会出现bug,bug的具体表现可以参考如下链接:
https://github.com/ansible/ansible/issues/47392

import_playbook
在上篇文章中我们还提到,使用"include"关键字除了能够引用任务列表,还能够引用整个playbook,在之后的版本中,如果想要引入整个playbook,则需要使用"import_playbook"模块代替"include"模块,因为在2.8版本以后,使用"include"关键字引用整个playbook的特性将会被弃用。

"import_playbook"的示例如下:
  1. # cat intest6.yml
  2. ---
  3. - hosts: test70
  4.   remote_user: root
  5.   gather_facts: no
  6.   tasks:
  7.   - debug:
  8.       msg: "test task in intest6.yml"

  9. - import_playbook: intest7.yml

  10. # cat intest7.yml
  11. ---
  12. - hosts: test70
  13.   remote_user: root
  14.   gather_facts: no
  15.   tasks:
  16.   - debug:
  17.       msg: "test task in intest7.yml"
复制代码
如上例所示, "import_playbook"的用法与"include"的用法并没有什么区别,此处不再赘述。

这篇文章中我们总结了一些新的模块,通过这些模块可以替代"include"模块的一些功能特性,文章就先总结到这里吧,希望能够对你有所帮助~





欢迎光临 黑帽联盟 (https://bbs.cnblackhat.com/) Powered by Discuz! X2.5