Playbook Roles and Include Statements

介紹

當 playbook 檔案越來越大的時候(你可以跳出來去學習 playbooks 了),最後一定會有檔案重用時候,此刻就需要我們來重新組織 playbooks 了.

從最基本來講, task files 請允許我們拆分配置策略到多個小檔案. Task 可以從其它檔案中讀取 tasks. 因為 handler 也是tasks, 所以你也可以在在 ‘handlers:’ 區域引用 handler 檔案.

你可以查閱 Playbooks 來複習該章節內容.

Playbooks 也可以引用其它 playbooks 檔案中的命令條目.當所有檔案均讀取完畢後, 所有的命令條目將被插入到一個 playbook 中組合成一條長的命令列表.

當你開始思考 – tasks, handlers, variables等 – 開始形成大的想法概念,當你開始創造一些東西,而非模仿某些東西. It’s no longer “apply this handful of THINGS to these hosts” ,你決定 “這些 hosts 是 dbservers” 或者 這些 hosts 是 webservers”. 在程式語言中,我們稱之為”封裝”.舉個例子,你會開車但不需要知道發動機工作原理.

Roles 在 Ansible中起到的宗旨是把配置檔案整合到一起並最大程度保證其乾淨整潔,可重用 – 他允許我們把重點放在大局上只有在需要的時候才再深入瞭解.

瞭解 includes 的對深入 roles 有重要意義,但我們最終的目標是理解 roles – roles是非常偉大的產品,所以當我們寫 playbooks 時一定要使用 roles.

閱讀 ansible-examples <https://github.com/ansible/ansible-examples> 獲取更多例項. 你可以開啟單獨的頁面進行深入學習.

Task Include Files 和鼓勵重用機制

猜想你希望在不同 tasks 之間plays 和 playbooks 可以重複呼叫. include files可以達成目的. 系統通過使用 include task 來完美實現 role 定義. 記住, playbook 中的 play 最終目的是對映系統群到多 roles中. 我們來舉個例子吧...

只簡單包括 tasks 的 task 檔案如下示例:

---
# possibly saved as tasks/foo.yml

- name: placeholder foo
  command: /bin/foo

- name: placeholder bar
  command: /bin/bar

Include 指令類似如下,可以像普通 tasks 命令一樣在 playbook 中混合使用:

tasks:

  - include: tasks/foo.yml

你也可以傳輸變數到 includes 指令, 我們稱之為 ‘parameterized include’.

例如,如何分發多個 wordpress 例項,我可以包涵所有 wordpress 命令到一個 wordpress.yml 檔案,按如下方式使用:

tasks:
  - include: wordpress.yml wp_user=timmy
  - include: wordpress.yml wp_user=alice
  - include: wordpress.yml wp_user=bob

如果你使用的是 Ansible 1.4以上版本(包括1.4), include 語法簡化了匹配 roles, 同時允許傳遞參數列表和字典:

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

使用任意一種語法, 變數傳遞均可以在 included 檔案中被使用. 我們將在 Variables 詳細討論. 你可以這樣引用他們:

{{ wp_user }}

(除了明確聲明定義參數,所有 vars 區塊定義的變數在這裡同樣適用.)

從1.0開始, ansible 還支援另外一種變數傳參到 include files 的方式-結構化變數,方式如下:

tasks:

  - include: wordpress.yml
    vars:
        wp_user: timmy
        ssh_keys:
          - keys/one.txt
          - keys/two.txt

Playbooks 也同樣可以 include 引用其它 playbooks,但這部分內容將在另外章節介紹.

Note

截止1.0版本,task include 聲明可以在任意層級目錄使用.在這之前,變數只能同層級目錄引用,所以 task includes 不能引用其實包含有 task includes 引用的task檔案.

Includes 功能也可以被用在用 handlers 區域,例如,如果你希望定義如何重啟apache,你只需要定義一個playbook,只需要做一次.編輯類似如下樣例的 handers.yml:

---
# this might be in a file like handlers/handlers.yml
- name: restart apache
  service: name=apache state=restarted

然後像如下方式在 main playbook 的詢問引用play即可:

handlers:
  - include: handlers/handlers.yml

Includes也可以在常規不包含 included 的tasks和handlers檔案中混合引用. Includes常被用作將一個playbook檔案中的命令匯入到另外一個playbook.這種方式允許我們定義由其它playbooks組成的頂層playbook(top-level playbook).

For example:

- 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

注意: 引用playbook到其它playbook時,變數替換功能將失效不可用.

Note

你不能有條件的指定位置的 include 檔案,就像在你使用 ‘vars_files’ 時一樣. 如果你發展你必須這麼做,那請重新規劃調整 playbook 的class/role 編排.這樣 說其實是想明確告訴你不要妄想 include 指定位置的file. 所有被包含在 play 中的主機都將執行相同的tasks.(‘when‘提供了一些指定條件來跳過tasks)

Roles

New in version 1.2.

Now that you have learned about tasks and handlers, what is the best way to organize your playbooks? The short answer is to use roles! Roles are ways of automatically loading certain vars_files, tasks, and handlers based on a known file structure. Grouping content by roles also allows easy sharing of roles with other users.

Roles are just automation around ‘include’ directives as described above, and really don’t contain much additional magic beyond some improvements to search path handling for referenced files. However, that can be a big thing!

Example project structure:

site.yml
webservers.yml
fooservers.yml
roles/
   common/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/
   webservers/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/

In a playbook, it would look like this:

---
- hosts: webservers
  roles:
     - common
     - webservers

This designates the following behaviors, for each role ‘x’:

  • If roles/x/tasks/main.yml exists, tasks listed therein will be added to the play
  • If roles/x/handlers/main.yml exists, handlers listed therein will be added to the play
  • If roles/x/vars/main.yml exists, variables listed therein will be added to the play
  • If roles/x/meta/main.yml exists, any role dependencies listed therein will be added to the list of roles (1.3 and later)
  • Any copy tasks can reference files in roles/x/files/ without having to path them relatively or absolutely
  • Any script tasks can reference scripts in roles/x/files/ without having to path them relatively or absolutely
  • Any template tasks can reference files in roles/x/templates/ without having to path them relatively or absolutely
  • Any include tasks can reference files in roles/x/tasks/ without having to path them relatively or absolutely

In Ansible 1.4 and later you can configure a roles_path to search for roles. Use this to check all of your common roles out to one location, and share them easily between multiple playbook projects. See Ansible的配置檔案 for details about how to set this up in ansible.cfg.

Note

Role dependencies are discussed below.

If any files are not present, they are just ignored. So it’s ok to not have a ‘vars/’ subdirectory for the role, for instance.

Note, you are still allowed to list tasks, vars_files, and handlers “loose” in playbooks without using roles, but roles are a good organizational feature and are highly recommended. If there are loose things in the playbook, the roles are evaluated first.

Also, should you wish to parameterize roles, by adding variables, you can do so, like this:

---

- hosts: webservers
  roles:
    - common
    - { role: foo_app_instance, dir: '/opt/a',  port: 5000 }
    - { role: foo_app_instance, dir: '/opt/b',  port: 5001 }

While it’s probably not something you should do often, you can also conditionally apply roles like so:

---

- hosts: webservers
  roles:
    - { role: some_role, when: "ansible_os_family == 'RedHat'" }

This works by applying the conditional to every task in the role. Conditionals are covered later on in the documentation.

Finally, you may wish to assign tags to the roles you specify. You can do so inline::

---

- hosts: webservers
  roles:
    - { role: foo, tags: ["bar", "baz"] }

If the play still has a ‘tasks’ section, those tasks are executed after roles are applied.

If you want to define certain tasks to happen before AND after roles are applied, you can do this:

---

- hosts: webservers

  pre_tasks:
    - shell: echo 'hello'

  roles:
    - { role: some_role }

  tasks:
    - shell: echo 'still busy'

  post_tasks:
    - shell: echo 'goodbye'

Note

If using tags with tasks (described later as a means of only running part of a playbook), be sure to also tag your pre_tasks and post_tasks and pass those along as well, especially if the pre and post tasks are used for monitoring outage window control or load balancing.

Role Default Variables

New in version 1.3.

Role default variables allow you to set default variables for included or dependent roles (see below). To create defaults, simply add a defaults/main.yml file in your role directory. These variables will have the lowest priority of any variables available, and can be easily overridden by any other variable, including inventory variables.

Role Dependencies

New in version 1.3.

Role dependencies allow you to automatically pull in other roles when using a role. Role dependencies are stored in the meta/main.yml file contained within the role directory. This file should contain a list of roles and parameters to insert before the specified role, such as the following in an example roles/myapp/meta/main.yml:

---
dependencies:
  - { role: common, some_parameter: 3 }
  - { role: apache, port: 80 }
  - { role: postgres, dbname: blarg, other_parameter: 12 }

Role dependencies can also be specified as a full path, just like top level roles:

---
dependencies:
   - { role: '/path/to/common/roles/foo', x: 1 }

Role dependencies can also be installed from source control repos or tar files (via galaxy) using comma separated format of path, an optional version (tag, commit, branch etc) and optional friendly role name (an attempt is made to derive a role name from the repo name or archive filename). Both through the command line or via a requirements.yml passed to ansible-galaxy.

Roles dependencies are always executed before the role that includes them, and are recursive. By default, roles can also only be added as a dependency once - if another role also lists it as a dependency it will not be run again. This behavior can be overridden by adding allow_duplicates: yes to the meta/main.yml file. For example, a role named ‘car’ could add a role named ‘wheel’ to its dependencies as follows:

---
dependencies:
- { role: wheel, n: 1 }
- { role: wheel, n: 2 }
- { role: wheel, n: 3 }
- { role: wheel, n: 4 }

And the meta/main.yml for wheel contained the following:

---
allow_duplicates: yes
dependencies:
- { role: tire }
- { role: brake }

The resulting order of execution would be as follows:

tire(n=1)
brake(n=1)
wheel(n=1)
tire(n=2)
brake(n=2)
wheel(n=2)
...
car

Note

Variable inheritance and scope are detailed in the Variables.

Embedding Modules In Roles

This is an advanced topic that should not be relevant for most users.

If you write a custom module (see Developing Modules) you may wish to distribute it as part of a role. Generally speaking, Ansible as a project is very interested in taking high-quality modules into ansible core for inclusion, so this shouldn’t be the norm, but it’s quite easy to do.

A good example for this is if you worked at a company called AcmeWidgets, and wrote an internal module that helped configure your internal software, and you wanted other people in your organization to easily use this module – but you didn’t want to tell everyone how to configure their Ansible library path.

Alongside the ‘tasks’ and ‘handlers’ structure of a role, add a directory named ‘library’. In this ‘library’ directory, then include the module directly inside of it.

Assuming you had this:

roles/
   my_custom_modules/
       library/
          module1
          module2

The module will be usable in the role itself, as well as any roles that are called after this role, as follows:

- hosts: webservers
  roles:
    - my_custom_modules
    - some_other_role_using_my_custom_modules
    - yet_another_role_using_my_custom_modules

This can also be used, with some limitations, to modify modules in Ansible’s core distribution, such as to use development versions of modules before they are released in production releases. This is not always advisable as API signatures may change in core components, however, and is not always guaranteed to work. It can be a handy way of carrying a patch against a core module, however, should you have good reason for this. Naturally the project prefers that contributions be directed back to github whenever possible via a pull request.

Ansible Galaxy

Ansible Galaxy is a free site for finding, downloading, rating, and reviewing all kinds of community developed Ansible roles and can be a great way to get a jumpstart on your automation projects.

You can sign up with social auth, and the download client ‘ansible-galaxy’ is included in Ansible 1.4.2 and later.

Read the “About” page on the Galaxy site for more information.

See also

Ansible Galaxy
How to share roles on galaxy, role management
YAML 語法
Learn about YAML syntax
Playbooks
Review the basic Playbook language features
最佳實踐
Various tips about managing playbooks in the real world
Variables
All about variables in playbooks
條件選擇
Conditionals in playbooks
迴圈
Loops in playbooks
模組相關
Learn about available modules
Developing Modules
Learn how to extend Ansible by writing your own modules
GitHub Ansible examples
Complete playbook files from the GitHub project source
Mailing List
Questions? Help? Ideas? Stop by the list on Google Groups