Ansible的动态包含与静态包含
由于 Ansible Advent Calendar 2016 的第七天还未被填写,我稍晚地参与进来。
顺便说一下,不是指库存而是指包含的事物。
首先
在Ansible 2.0版本后,include被分为两种类型:动态引用和静态引用。实际上,在编写playbook时,可能会无意识地选择动态引用或静态引用。而且,根据使用的Ansible版本,对同一playbook的include解释也会有所不同,因此需要注意。
我想通过参考下面公式文件来阐述一下动态和静态的区别。
-
- docs.ansible.com: Dynamic versus Static Includes
- docs.ansible.com: include
动态包含和静态包含
简单的例子
动态包含和静态包含的区别在于,动态包含在运行时进行评估,而静态包含在运行之前进行评估。首先我们来看一个简单的例子,观察它们各自的行为。请注意,下文是在Ansible 2.2.0.0的环境中进行确认的。
Static Include的例子
在Ansible 2.1及更高版本中,如果以常规方式进行包含,则属于静态包含。下面是执行示例。很普通的例子。
---
- hosts: all
tasks:
- include: ping.yml
---
- name: ping
ping:
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
ping TAGS: []
在Ansible 1.9版本之前,总是被处理为静态包含(Static Include)。然而,仅限于Ansible 2.0版本,始终采用动态包含(Dynamic Include),因此需要注意。
动态包含的实例
Dynamic Include有三种情况。(从Ansible 2.1开始)
-
- static:noを宣言している場合
-
- includeのファイル名に変数を含む場合
- ループを使用したincludeを実行する場合
只要在ansible.cfg中设置了强制使用static选项,尝试执行Dynamic Include时将会出错,就像之前的Ansible 1.9版本以及没有Dynamic Include一样。
如果声明了static:no
如果在include时加上static: no,那就成为Dynamic Include。
---
- hosts: all
tasks:
- include: ping.yml
static: no
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 : ok=2 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
include TAGS: []
当您尝试执行时,发现在 Dynamic Include 中,ansible-playbook 在执行时会显示 [include] 的 TASK。
此外,当查看 ansible-playbook –list-tasks 的输出结果时,可以看到在 Static Include 的情况下会显示 ping,而在 Dynamic Include 的情况下会显示 include。
如果在include的文件名中包含变量的情况下
自Ansible 2.0起,可使用变量进行include。如果使用变量进行include,即使没有明确添加 static: no,也会被视为动态include。
---
- hosts: all
tasks:
- include: "{{ include_file }}.yml"
$ ansible-playbook -c local --extra-vars="include_file=ping" playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
include TAGS: []
如果使用循环执行include的话
使用循环的包括功能也可在Ansible 2.0及更高版本中实现。下面的示例中使用了with_items等语法,同样也可作为动态包括处理。
---
- hosts: all
tasks:
- include: ping.yml
with_items:
- host1
- host2
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
included: /home/heriet/work/ansible/example_include/example_dynamic/ping.yml for localhost
TASK [ping] ********************************************************************
ok: [localhost]
TASK [ping] ********************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
$ ansible-playbook --list-tasks playbook.yml
playbook: playbook.yml
play #1 (all): all TAGS: []
tasks:
include TAGS: []
在ansible.cfg中强行使用静态模式.
你还可以在Ansible 2.1及以上版本中的ansible.cfg文件中设置强制使用static选项。
task_includes_static すべてのタスクが静的であることを強制する
handler_includes_static すべてのハンドラが静的であることを強制する
当你尝试将task_includes_static设置为True,并在循环内执行include时,请看看会发生什么。
$ ansible-playbook -c local playbook.yml
ERROR! You cannot use 'static' on an include with a loop
The error appears to have been in '/home/heriet/work/ansible/example_include/example_dynamic/playbook.yml': line 5, column 5, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
tasks:
- include: ping.yml
^ here
错误!您不能在具有循环的包含中使用“static”一词,这引起了我的愤怒。
通常情况下,建议保持默认设置并禁用该选项,这样使用频率会更高。
动态包含与静态包含的区别
正如前面提到的,动态引用和静态引用的根本区别在于它们是在运行时还是在运行前进行评估,这会产生以下影响。
-
- Dynamic Includeされるhandlerにはnotifyできない
-
- Dynamic Include内にしか存在しないタグは –list-tags で出力されない
- Dynamic Include内のタスクは –list-tasks で出力されない
无法通知被动态包含的处理程序。
举个例子,假设我们想根据操作系统切换处理程序。我准备了下面这样的playbook。
---
- hosts: all
tasks:
- include: hello.yml
handlers:
- include: "ping_handler_{{ ansible_os_family }}.yml"
---
- name: hello
command: echo hello > hello
notify: ping handler
---
- name: ping handler
ping:
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [hello] *******************************************************************
ERROR! The requested handler 'ping handler' was not found in either the main handlers list nor in the listening handlers list
执行以上操作时,会报错说找不到’ping handler’。尽管在ping_handler_Debian.yml中有ping handler,但为什么会出现这种情况呢?
是的,由于在handlers的include中使用了变量,这部分就成为了动态包含。当然,在动态包含中,直到被处理之前是不会被评估的,所以在执行任务时,ping handler是不存在的。
如果您希望进行类似的事情,则可以考虑使用”when”的模式。
---
- hosts: all
tasks:
- include: hello.yml
handlers:
- include: ping_handler_Debian.yml
when: ansible_os_family == 'Debian'
在这种情况下,由于是静态包含,它会在执行之前识别ping handler。
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [hello] *******************************************************************
changed: [localhost]
RUNNING HANDLER [ping handler] *************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0
实际上,在每个操作系统中都包含处理程序可能并不常见,但是可以考虑把不熟悉处理程序的情况之一放在心里。
只有在Dynamic Include中存在的标签,才不会被通过 -list-tags 命令输出。
Dynamic Include中的任务不会在–list-tasks中输出。
正如上述简单示例所示,动态包含的部分不会识别其中的任务,只会输出被包含的信息。–list-tags也是同样的情况。顺便提一下,如果指定了–start-at-task或–tags,动态包含也会按照预期识别和执行内部的任务和标签。
意图将Dynamic Include应用到实例中。
在使用通用的playbook/role时,经常会根据操作系统及其版本进行包含并分离处理。例如,下面是一个例子:
---
- hosts: all
tasks:
- include: ping_RedHat.yml
when: ansible_os_family == 'RedHat'
- include: ping_Debian.yml
when: ansible_os_family == 'Debian'
---
- name: ping A
ping:
- name: ping B
ping:
- name: ping C
ping:
---
- name: ping D
ping:
- name: ping E
ping:
- name: ping F
ping:
在上述的例子中,我们只是给ping A~F起了一些名字,但实际上请将它们视为排列了许多特定操作系统的处理。由于不满足Dynamic Include的条件,上述内容将变为Static Include。执行此操作将得到以下结果。
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [ping A] ******************************************************************
skipping: [localhost]
TASK [ping B] ******************************************************************
skipping: [localhost]
TASK [ping C] ******************************************************************
skipping: [localhost]
TASK [ping D] ******************************************************************
ok: [localhost]
TASK [ping E] ******************************************************************
ok: [localhost]
TASK [ping F] ******************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
由于在Debian环境下执行,我们可以看到A〜C被跳过了,而D〜F被执行了。然而,在ping_RedHat.yml文件中,如果不仅仅是ping A〜C,而是实际上有很多任务排列在一起,那么跳过的结果将大量出现,并且可能会变得非常繁琐。
于是,我们尝试使用明确的动态包含。
---
- hosts: all
tasks:
- include: ping_RedHat.yml
when: ansible_os_family == 'RedHat'
static: no
- include: ping_Debian.yml
when: ansible_os_family == 'Debian'
static: no
$ ansible-playbook -c local playbook.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [localhost]
TASK [include] *****************************************************************
skipping: [localhost]
TASK [include] *****************************************************************
included: /home/heriet/work/ansible/example_include/example_os/ping_Debian.yml for localhost
TASK [ping D] ******************************************************************
ok: [localhost]
TASK [ping E] ******************************************************************
ok: [localhost]
TASK [ping F] ******************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
仅仅跳过了第一个include(ping_RedHat.yml的部分),视野变得更加清晰。如果大量任务的跳过令人困扰,并且没有必要使用静态包含,请将其更改为动态包含是一个好办法。
总结
我展示了根据Playbook的编写方式,可以实现动态包含或静态包含的例子。通常情况下,我们可能不会特别注意这种差异,但是在处理notify、task、tag等内容时,可能会略有细微差别,有时可能会掉入意想不到的陷阱。此外,如果不想显示内部任务的跳过信息,有意使用动态包含是有效的。