抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

18. Ansible Playbook Include

目录

说明

在前面的学习当中,我们一直使用一个playbook文件来组织所有的task任务。但是,当我们项目越来越大,task越来越多的时候,如果还将所有的task都写到一个playbook当中,可读性就会变差,这个时候我们就需要重新来组织playbook了。

我们可以将一个大的playbook拆成若干个小的playbook文件,在主配置文件中将这些零碎的小文件引入进来,而这种方式就叫做playbook的”include”。

include

playbook的include其实就是使用include关键字

tasks include

1. include简单示例

下面是两个playbook示例,分别用于安装lamp和lnmp环境:

# cat lamp.yml
- hosts: test
  gather_facts: no
  tasks:
    - package: 
        name: mysql
        state: present
    - package: 
        name: php-fpm
        state: present
    - package:
        name: httpd
        state: present

# cat lnmp.yml
- hosts: test
  gather_facts: no
  tasks:
    - package: 
        name: mysql
        state: present
    - package:
        name: php-fpm
        state: present
    - package:
        name: nginx
        state: present

在上面的示例当中,我们可以看到lamp和lnmp中mysql和php的安装都是一样的,所以我们可以将这两个任务提取出来,放到一个单独的task文件中,然后在lnmp和lamp中引入:

# cat install_mysql_php.yml
- package: 
    name: mysql
    state: present
- package:
    name: php-fpm
    state: present


# cat lamp.yml
- hosts: test
  gather_facts: no
  tasks:
    - include: install_mysql_php.yml
    - package:
        name: httpd
        state: php-fpm

# cat lnmp.yml
- hosts: test
  gather_facts: no
  tasks:
    - include: install_mysql_php.yml
    - package:
        name: nginx
        state: php-fpm

2. 在include时引入变量

也可以在include的时候,传入变量:

# cat test_include.yml
- hosts: test
  gather_facts: no
  tasks:
    - include: wordpress.yml user=timmy
    - include: wordpress.yml user=alice
    - include: wordpress.yml user=bob

# cat wordpress.yml
- debug:
    msg: "{{ user }}"

通过如下方式带入变量:

tasks:
 - { include: wordpress.yml, user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }

再给一个例子:

- hosts: test
  gather_facts: no
  tasks: 
    - include: in.yml
      vars:
        users:
          bob:
            gender: male
          lucy:
            gender: female

# cat in.yml
- debug:
    msg: "{{ item.key }} is {{ item.value.gender }}"
  loop: "{{users | dict2items }}""

3. 在include中使用tag

# cat test_include.yml
- hosts: test
  gather_facts: no
  tasks:
    - include: in1.yml
      tags: t1
    - include: in2.yml
      tags: t2

# cat in1.yml
- debug:
    msg: "task1 in in1.yml"
- debug:
    msg: "task2 in in1.yml"

# cat in2.yml
- debug:
    msg: "task1 in in2.yml"
- debug:
    msg: "task2 in in2.yml"

在上面的示例当中,两个Include分别对应两个tag,如果我们在执行test_include.yml时,指定tag为t2,那么in2.yml中的所有任务都会被执行。所以tag是针对include的所有任务生效。

4. 在include中使用条件判断

# cat test_include.yml

- hosts: test
  gather_facts: no
  tasks:
    - include: in.yml
      when: 2 > 1

# cat in.yml
- debug:
    msg: "task in in.yml"

5. 在include中使用循环

下面是一个简单的循环示例:

# cat test_include.yml

- hosts: test
  gather_facts: no
  tasks:
    - include: in.yml
      loop:
        - 1
        - 2

# cat in.yml
- debug:
    msg: "task1 in in.yml"
- debug:
    msg: "task2 in in.yml"

可以看到in.yml被循环执行了两次。

我们可以稍微修改in.yml示例如下:

# cat in.yml
- debug: 
    msg: "{{ item }} task1 in in.yml"
- debug:
    msg: "{{ item }} task2 in in.yml"

再次执行playbook的结果如下:

[root@workstation ansible]# ansible-playbook test_include.yml 

PLAY [servera] **********************************************************************************************************************************

TASK [include] **********************************************************************************************************************************
included: /etc/ansible/in.yml for servera => (item=1)
included: /etc/ansible/in.yml for servera => (item=2)

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "1 task1 in in.yml"
}

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "1 task2 in in.yml"
}

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "2 task1 in in.yml"
}

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "2 task2 in in.yml"
}

PLAY RECAP **************************************************************************************************************************************
servera                    : ok=6    changed=0    unreachable=0    failed=0

可以看到item的值就来自test_include中的loop循环。那么这就引出了一个问题:如果正好in.yml当中也有循环时怎么办?

# cat in.yml
- debug: 
    msg: "{{ item }} task1 in in.yml"
  loop: ['a','b','c']

再次执行test_include,结果如下:

[root@workstation ansible]# ansible-playbook test_include.yml 

PLAY [servera] **********************************************************************************************************************************

TASK [include] **********************************************************************************************************************************
included: /etc/ansible/in.yml for servera => (item=1)
included: /etc/ansible/in.yml for servera => (item=2)

TASK [debug] ************************************************************************************************************************************
 [WARNING]: The loop variable 'item' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to
something else to avoid variable collisions and unexpected behavior.

ok: [servera] => (item=a) => {
    "msg": "a task1 in in.yml"
}
ok: [servera] => (item=b) => {
    "msg": "b task1 in in.yml"
}
ok: [servera] => (item=c) => {
    "msg": "c task1 in in.yml"
}

TASK [debug] ************************************************************************************************************************************
 [WARNING]: The loop variable 'item' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to
something else to avoid variable collisions and unexpected behavior.

ok: [servera] => (item=a) => {
    "msg": "a task1 in in.yml"
}
ok: [servera] => (item=b) => {
    "msg": "b task1 in in.yml"
}
ok: [servera] => (item=c) => {
    "msg": "c task1 in in.yml"
}

PLAY RECAP **************************************************************************************************************************************
servera                    : ok=4    changed=0    unreachable=0    failed=0

这个时候,可以看到最终item的值来自in.yml中的循环。那如果我就想要使用test_include中的循环的值怎么办? 我们再次修改test_include.yml以及in.yml如下:

# cat test_include.yml
- hosts: test
  gather_facts: no
  tasks:
    - include: in.yml
      loop:
        - 1
        - 2
      loop_control:
        loop_var: outer_item

# cat in.yml
- debug: 
    msg: "{{outer_item }} {{ item }} task1 in in.yml"
  loop: ['a','b','c']

再次查看结果:

PLAY [servera] **********************************************************************************************************************************

TASK [include] **********************************************************************************************************************************
included: /etc/ansible/in.yml for servera
included: /etc/ansible/in.yml for servera

TASK [debug] ************************************************************************************************************************************
ok: [servera] => (item=a) => {
    "msg": "1 a task1 in in.yml"
}
ok: [servera] => (item=b) => {
    "msg": "1 b task1 in in.yml"
}
ok: [servera] => (item=c) => {
    "msg": "1 c task1 in in.yml"
}

TASK [debug] ************************************************************************************************************************************
ok: [servera] => (item=a) => {
    "msg": "2 a task1 in in.yml"
}
ok: [servera] => (item=b) => {
    "msg": "2 b task1 in in.yml"
}
ok: [servera] => (item=c) => {
    "msg": "2 c task1 in in.yml"
}

PLAY RECAP **************************************************************************************************************************************
servera                    : ok=4    changed=0    unreachable=0    failed=0

可以看到,outer_item中的值正是外层循环中item的值。当出现这个双层循环时,可以在外层循环中使用loop_var选项指定一个变量,这个变量用于替代外层循环中的item变量,以便在内层循环中获取到外层循环的item的值,从而避免两层循环中item变量名的冲突。

handlers include

handlers include与tasks include大体类似,直接给例子:

# handlers1.yml内容如下:
# this might be in a file like handlers/handlers.yml
- name: restart apache
  service: 
     name: apache 
     state: restarted

# handlers.yml包含handlers1.yml示例:
handlers:
  - include: handlers/handlers.yml

playbook include

include也可以用于将一个playbook导入到另一个playbook中:

- name: this is a play at the top level of a file
  hosts: all
  remote_user: root
  tasks:
  - name: say hi
    tags: foo
    shell: echo "hi..."
- include: load_balancers.yml
- include: webservers.yml
- include: dbservers.yml

include_tasks

基本使用

在前面我们详细说了include的用法,然而事实上在后续的ansible版本当中,include语法可能会被弃用。而使用一些新的关键字来代替include的原始用法,include_tasks就是其中之一。

我们知道include可以用于包含tasks,handlers,playbooks等,而include_tasks则专门用于包含tasks:

# cat include_tasks_ex.yml
- hosts:
  gather_facts: no
  tasks:
    - debug:
        msg: "task1"
    - include_tasks: in.yml
    - debug:
        msg: "task2"

# cat in.yml
- debug: 
    msg: "{{ item }} task1 in in.yml"
- debug:
    msg: "{{ item }} task2 in in.yml"

执行结果如下:

PLAY [servera] **********************************************************************************************************************************

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "task1"
}

TASK [include_tasks] ****************************************************************************************************************************
included: /etc/ansible/in.yml for servera

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "task1 in in.yml"
}

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "task2 in in.yml"
}

TASK [debug] ************************************************************************************************************************************
ok: [servera] => {
    "msg": "task2"
}

PLAY RECAP **************************************************************************************************************************************
servera                    : ok=5    changed=0    unreachable=0    failed=0

可以看到,当我们使用include_tasks时,include_tasks本身会被当做一个task,这个task会把include的文件的路径输出在控制台中中, 这就是include_tasksinclude之间的区别。include是透明的,而include_tasks是可见的,include_tasks更像是一个任务,这个任务包含了其他的一些任务。

在ansible 2.7版本当中,include_tasks还加入了新的参数,下面是一个简单用法示例:

include_tasks:
  file: in.yml

当然这种使用方法与include_tasks: in.yml的效果完全相同。

在include_tasks中使用tags

在前面我们提到过,如果为include添加tags,那么tags是对include中所有任务生效的。也就是说,如果调用include对应的tag,那么include文件中的所有任务都会执行。

但是对include_tasks添加tags,则只会对include_tasks本身生效,include_tasks中所有的任务都不生效。示例如下:

# cat include_tasks_ex.yml 
- hosts: test
  gather_facts: no
  tasks:
    - debug:
        msg: "test task1"
    - include_tasks:
        file: in.yml
      tags: t1
    - debug:
        msg: "test task3"

# cat in.yml 
- debug:
    msg: "test task2"

执行结果如下:

# ansible-playbook  include_tasks_ex.yml  --tags t1

PLAY [test] ******************************************************************************************************

TASK [include_tasks] *********************************************************************************************
included: /etc/ansible/in.yml for 10.1.61.187

PLAY RECAP *******************************************************************************************************
10.1.61.187                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

如果想要tags对include_tasks中包含的所有任务生效,则需要使用include_tasks模块的apply参数并配合tags: always内置tag:

- hosts: test
  gather_facts: no
  tasks:
    - debug:
        msg: "test task1"
    - include_tasks:
        file: in.yml
        apply:
          tags: t1
      tags: always
    - debug:
        msg: "test task3"

执行结果:

# ansible-playbook include_tasks_ex.yml --tags t1

PLAY [test] ******************************************************************************************************

TASK [include_tasks] *********************************************************************************************
included: /etc/ansible/in.yml for 10.1.61.187

TASK [debug] *****************************************************************************************************
ok: [10.1.61.187] => {
    "msg": "test task2"
}

PLAY RECAP *******************************************************************************************************
10.1.61.187                : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

在上一篇我们讲到tags的时候说过,如果一个任务被打上了tags: always标签,则即使我们调用其他任务的标签,该任务也会被执行。

需要说明的是,在这里,tags: always标签只针对include_tasks本身生效,也就是说,如果其他任务的标签被调用,include_tasks本身会被调用,而其包含的任务不会被调用。如果要想其包含的任务也总是被调用,可修改配置如下:

- hosts: test
  gather_facts: no
  tasks:
    - debug:
        msg: "test task1"
    - include_tasks:
        file: in.yml
        apply:
          tags: t1,always
      tags: always
    - debug:
        msg: "test task3"

import_tasks

import_tasksinclude_tasks用法类似,都用于包含一个任务列表:

# cat import_tasks_ex.yml
- hosts: test
  gather_facts: no
  tasks
    - debug: 
        msg: "test task1"
    - import_tasks: in.yml

# cat in.yml 
- debug:
    msg: "test task2"

执行结果:

# ansible-playbook import_tasks_ex.yml 

PLAY [test] ******************************************************************************************************

TASK [debug] *****************************************************************************************************
ok: [10.1.61.187] => {
    "msg": "test task1"
}

TASK [debug] *****************************************************************************************************
ok: [10.1.61.187] => {
    "msg": "test task2"
}

PLAY RECAP *******************************************************************************************************
10.1.61.187                : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

可以看到,import_tasks模块并不会像include_tasks模块一样,在控制台输出自身的任务信息,其相对透明。

除此之外,import_tasksinclude_tasks还有如下不同:

  1. import_tasks是静态的,被import的文件在playbook被加载时就预处理了,而include_tasks是动态的,被include的文件在playbook被运行时候才开始处理。一个简单的例子:

    - hosts: test
      gather_facts: no
      vars: 
        file_name: in.yml
      tasks:
        - include_tasks: {{ file_name }}
        - import_tasks: {{ file_name }}
    

    在上面的示例中,include_tasksimport_tasks均会被执行。

    再看下面的例子:

    - hosts: test
      gather_facts: no
      tasks:
        - set_fact:
            file_name: in.yml
        - include_tasks: {{ file_name }}
        - import_tasks: {{ file_name }}
    

    此时,import_tasks就会出错:

    # ansible-playbook include_import_tasks_ex.yml
    ERROR! Error when evaluating variable in import path: {{ file_name }}.
    
    When using static imports, ensure that any variables used in their names are defined in vars/vars_files
    or extra-vars passed in from the command line. Static imports cannot use variables from facts or inventory
    sources like group or host vars.
    

    当使用静态的import时,请确保文件名中使用到的变量被定义在vars、vars_files或者extra-vars中,不支持其他的方式传入变量。

  2. 如果想要对包含的任务列表进行循环操作,则只能使用include_tasksimport_tasks不支持循环操作。也就是说,使用loop或者with_X对include文件进行循环操作时,只能配合include_tasks才能正常使用

  3. 当使用when对include文件添加条件判断时,include_tasksimport_tasks

有着本质的不同:

  • 当对include_tasks使用when时,when对应的条件只会应用于include_tasks任务本身,当执行被包含的任务时,不会对这些被包含的任务重新进行条件判断

  • 当对import_tasks使用when时,when对应的条件会被应用于被import的文件中的每一个任务,当执行被import的任务时,会对每一个被包含的任务进行同样的条件判断。

示例如下:

# cat include_import_tasks_ex2.yml              
- hosts: test
  gather_facts: no
  tasks:
    - name: set testvar to 0
      set_fact: 
         testnum: 0
    - debug:
        msg: 'include_tasks: in1.yml'
    - include_tasks: in1.yml
      when: testnum == 0

    - name: set testvar to 0 
      set_fact: 
        testnum: 0
    - debug:
        msg: 'import_tasks: in1.yml'
    - import_tasks: in1.yml
      when: testnum == 0

执行结果:

# ansible-playbook include_import_tasks_ex2.yml 

PLAY [test] ******************************************************************************************************

TASK [set testvar to 0] ******************************************************************************************
ok: [10.1.61.187]

TASK [debug] *****************************************************************************************************
ok: [10.1.61.187] => {
    "msg": "include_tasks: in1.yml"
}

TASK [include_tasks] *********************************************************************************************
included: /etc/ansible/in1.yml for 10.1.61.187

TASK [set_fact] **************************************************************************************************
ok: [10.1.61.187]

TASK [debug] *****************************************************************************************************
ok: [10.1.61.187] => {
    "msg": "test task2"
}

TASK [set testvar to 0] ******************************************************************************************
ok: [10.1.61.187]

TASK [debug] *****************************************************************************************************
ok: [10.1.61.187] => {
    "msg": "import_tasks: in1.yml"
}

TASK [set_fact] **************************************************************************************************
ok: [10.1.61.187]

TASK [debug] *****************************************************************************************************
skipping: [10.1.61.187]

PLAY RECAP *******************************************************************************************************
10.1.61.187                : ok=8    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

在handlers中使用include_tasks及import_tasks

我们知道,handlers中执行的其实也是任务,只不过是被触发才会运行,所以如果要在handlers中引入任务,也可直接使用include_tasksimport_tasks。没有include_handlers的说法。

import_playbook

我们在前面提到过,include除了可以引用任务列表,还可以引用整个playbook,在之后的版本中,如果想要引入playbook,则需要使用import_playbook模块。在2.8版本后,使用include引用整个playbook的特性会被弃用。

示例:

# cat import_playbook_ex.yml
- hosts: test
  gather_facts: no
  tasks:
    - debug:
        msg: "test task"
- import_playbook: inplay.yml

# cat inplay.yml
- hosts: test
  gather_facts: no
  tasks:
    - debug:
        msg: "test task in inplay.yml"

评论