黑帽联盟

 找回密码
 会员注册
查看: 886|回复: 0
打印 上一主题 下一主题

[基础服务] ansible笔记(37):include(二)

[复制链接]
yun 黑帽联盟官方人员 

920

主题

37

听众

1364

积分

超级版主

Rank: 8Rank: 8

  • TA的每日心情
    奋斗
    2019-10-18 11:20
  • 签到天数: 678 天

    [LV.9]以坛为家II

    本帖最后由 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"模块的一些功能特性,文章就先总结到这里吧,希望能够对你有所帮助~
    帖子永久地址: 

    黑帽联盟 - 论坛版权1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
    2、本站所有主题由该帖子作者发表,该帖子作者与黑帽联盟享有帖子相关版权
    3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和黑帽联盟的同意
    4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
    5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
    6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
    7、黑帽联盟管理员和版主有权不事先通知发贴者而删除本文

    您需要登录后才可以回帖 登录 | 会员注册

    发布主题 !fastreply! 收藏帖子 返回列表 搜索
    回顶部