角色
角色允许您根据已知的文件夹结构自动加载相关的变量、文件、任务、处理器和其他 Ansible 工件。将内容分组到角色后,您可以轻松地重复使用它们并与其他用户共享。
角色目录结构
Ansible 角色具有定义的目录结构,其中包含七个主要的标准目录。您必须在每个角色中至少包含其中一个目录。您可以省略角色不使用的任何目录。例如
# playbooks
site.yml
webservers.yml
fooservers.yml
roles/
common/ # this hierarchy represents a "role"
tasks/ #
main.yml # <-- tasks file can include smaller files if warranted
handlers/ #
main.yml # <-- handlers file
templates/ # <-- files for use with the template resource
ntp.conf.j2 # <------- templates end in .j2
files/ #
bar.txt # <-- files for use with the copy resource
foo.sh # <-- script files for use with the script resource
vars/ #
main.yml # <-- variables associated with this role
defaults/ #
main.yml # <-- default lower priority variables for this role
meta/ #
main.yml # <-- role dependencies
library/ # roles can also include custom modules
module_utils/ # roles can also include custom module_utils
lookup_plugins/ # or other types of plugins, like lookup in this case
webtier/ # same kind of structure as "common" was above, done for the webtier role
monitoring/ # ""
fooapp/ # ""
默认情况下,Ansible 将在大多数角色目录中查找 main.yml
文件以获取相关内容(也包括 main.yaml
和 main
)
tasks/main.yml
- 角色提供给 playbook 以执行的任务列表。handlers/main.yml
- 导入到父 playbook 中的处理器,供角色或 playbook 中的其他角色和任务使用。defaults/main.yml
- 角色提供的变量的非常低的优先级值(有关更多信息,请参阅 使用变量)。角色自己的默认值将优先于其他角色的默认值,但任何/所有其他变量源都将覆盖此值。vars/main.yml
- 角色提供给 playbook 的高优先级变量(有关更多信息,请参阅 使用变量)。files/stuff.txt
- 可用于角色及其子级的一个或多个文件。templates/something.j2
- 用于角色或子角色的模板。meta/main.yml
- 角色的元数据,包括角色依赖项和可选的 Galaxy 元数据,例如支持的平台。这是作为独立角色上传到 Galaxy 所必需的,但对于在 playbook 中使用角色而言并非必需。
注意
以上文件都不是角色所必需的。例如,您只需提供
files/something.txt
或vars/for_import.yml
,它仍然是一个有效角色。在独立角色上,您还可以包含自定义模块和/或插件,例如
library/my_module.py
,它可以在此角色中使用(有关更多信息,请参阅 在角色中嵌入模块和插件)。“独立”角色是指不属于集合而是作为单独可安装内容的角色。
来自
vars/
和defaults/
的变量将导入到 playbook 范围,除非您通过import_role
/include_role
中的public
选项禁用它。
您可以在某些目录中添加其他 YAML 文件,但默认情况下不会使用它们。它们可以被直接包含/导入或在使用 include_role/import_role
时指定。例如,您可以将特定于平台的任务放在单独的文件中,并在 tasks/main.yml
文件中引用它们。
# roles/example/tasks/main.yml
- name: Install the correct web server for RHEL
import_tasks: redhat.yml
when: ansible_facts['os_family']|lower == 'redhat'
- name: Install the correct web server for Debian
import_tasks: debian.yml
when: ansible_facts['os_family']|lower == 'debian'
# roles/example/tasks/redhat.yml
- name: Install web server
ansible.builtin.yum:
name: "httpd"
state: present
# roles/example/tasks/debian.yml
- name: Install web server
ansible.builtin.apt:
name: "apache2"
state: present
或者在加载角色时直接调用这些任务,这将绕过 main.yml
文件。
- name: include apt tasks
include_role:
name: package_manager_bootstrap
tasks_from: apt.yml
when: ansible_facts['os_family'] == 'Debian'
目录 defaults
和 vars
也可能包含 *嵌套目录*。如果您的变量文件是一个目录,Ansible 会按字母顺序读取目录内部的所有变量文件和目录。如果嵌套目录包含变量文件和目录,Ansible 会先读取目录。以下是 vars/main
目录的一个示例。
roles/
common/ # this hierarchy represents a "role"
vars/
main/ # <-- variables associated with this role
first_nested_directory/
first_variables_file.yml
second_nested_directory/
second_variables_file.yml
third_variables_file.yml
存储和查找角色
默认情况下,Ansible 在以下位置查找角色:
在集合中,如果您正在使用它们。
在名为
roles/
的目录中,相对于 playbook 文件。在配置的 roles_path 中。默认搜索路径为
~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles
。在 playbook 文件所在的目录中。
如果将角色存储在其他位置,请设置 roles_path 配置选项,以便 Ansible 能够找到您的角色。将共享角色检入到单个位置可以更轻松地在多个 playbook 中使用它们。有关在 ansible.cfg
中管理设置的详细信息,请参阅 配置 Ansible。
或者,您可以使用完全限定路径调用角色。
---
- hosts: webservers
roles:
- role: '/path/to/my/roles/common'
使用角色
您可以通过以下方式使用角色:
在 playbook 级别使用
roles
选项:这是在 playbook 中使用角色的经典方法。在任务级别使用
include_role
:您可以使用include_role
在 playbook 的tasks
部分中的任何位置动态地重复使用角色。在任务级别使用
import_role
:您可以使用import_role
在 playbook 的tasks
部分中的任何位置静态地重复使用角色。作为另一个角色的依赖项(请参阅此页面中
meta/main.yml
中的dependencies
关键字)。
在 playbook 级别使用角色
使用角色的经典(原始)方法是使用给定 playbook 的 roles
选项。
---
- hosts: webservers
roles:
- common
- webservers
当您在 playbook 级别使用 roles
选项时,每个角色“x”都会在以下目录中查找 main.yml
(也包括 main.yaml
和 main
):
roles/x/tasks/
roles/x/handlers/
roles/x/vars/
roles/x/defaults/
roles/x/meta/
任何复制、脚本、模板或包含任务(在角色中)都可以引用 roles/x/{files,templates,tasks}/ 中的文件(目录取决于任务),而无需相对或绝对地对其进行路径化。
注意
vars
和 defaults
也可以匹配到同名的目录,Ansible 将处理该目录中包含的所有文件。有关更多详细信息,请参阅 角色目录结构。
注意
如果使用 include_role/import_role
,则可以指定自定义文件名而不是 main
。 meta
目录是一个例外,因为它不允许自定义。
在 playbook 级别使用 roles
选项时,Ansible 会将角色视为静态导入并在 playbook 解析期间处理它们。Ansible 按以下顺序执行每个 playbook
playbook 中定义的任何
pre_tasks
。由 pre_tasks 触发的任何处理器。
roles:
中列出的每个角色,按列出顺序。角色的meta/main.yml
中定义的任何角色依赖项将首先运行,受标签过滤和条件的约束。有关更多详细信息,请参阅 使用角色依赖项。playbook 中定义的任何
tasks
。由角色或任务触发的任何处理器。
playbook 中定义的任何
post_tasks
。由 post_tasks 触发的任何处理器。
注意
如果在角色中使用带有任务的标签,请确保也标记您的 pre_tasks、post_tasks 和角色依赖项,并将它们也传递,尤其是在 pre/post 任务和角色依赖项用于监控停机窗口控制或负载平衡时。有关添加和使用标签的详细信息,请参阅 标签。
您可以将其他关键字传递给 roles
选项
---
- hosts: webservers
roles:
- common
- role: foo_app_instance
vars:
dir: '/opt/a'
app_port: 5000
tags: typeA
- role: foo_app_instance
vars:
dir: '/opt/b'
app_port: 5001
tags: typeB
当您向 role
选项添加标签时,Ansible 会将该标签应用于角色中的所有任务。
注意
在 ansible-core
2.15 之前,playbook 的 roles:
部分中的 vars:
会添加到 playbook 变量中,使其在角色之前和之后可用于 playbook 中的所有任务。此行为可以通过 DEFAULT_PRIVATE_ROLE_VARS 更改。在较新的版本中,vars:
不会泄漏到 playbook 的变量作用域。
包含角色:动态重用
您可以使用 include_role
在 playbook 的 tasks
部分中的任何位置动态重用角色。虽然在 roles
部分中添加的角色在 playbook 中的任何其他任务之前运行,但包含的角色按定义顺序运行。如果在 include_role
任务之前还有其他任务,则其他任务将首先运行。
要包含角色
---
- hosts: webservers
tasks:
- name: Print a message
ansible.builtin.debug:
msg: "this task runs before the example role"
- name: Include the example role
include_role:
name: example
- name: Print a message
ansible.builtin.debug:
msg: "this task runs after the example role"
包含角色时,您可以传递其他关键字,包括变量和标签
---
- hosts: webservers
tasks:
- name: Include the foo_app_instance role
include_role:
name: foo_app_instance
vars:
dir: '/opt/a'
app_port: 5000
tags: typeA
...
当您向 include_role
任务添加 标签 时,Ansible 仅将该标签应用于包含本身。这意味着您可以传递 --tags
以仅运行角色中选定的任务,如果这些任务本身具有与 include 语句相同的标签。有关详细信息,请参阅 选择性运行可重用文件中的带标签的任务。
您可以有条件地包含角色
---
- hosts: webservers
tasks:
- name: Include the some_role role
include_role:
name: some_role
when: "ansible_facts['os_family'] == 'RedHat'"
导入角色:静态重用
您可以使用 import_role
在 playbook 的 tasks
部分中的任何位置静态重用角色。行为与使用 roles
关键字相同。例如
---
- hosts: webservers
tasks:
- name: Print a message
ansible.builtin.debug:
msg: "before we run our role"
- name: Import the example role
import_role:
name: example
- name: Print a message
ansible.builtin.debug:
msg: "after we ran our role"
导入角色时,您可以传递其他关键字,包括变量和标签
---
- hosts: webservers
tasks:
- name: Import the foo_app_instance role
import_role:
name: foo_app_instance
vars:
dir: '/opt/a'
app_port: 5000
...
当您向 import_role
语句添加标签时,Ansible 会将该标签应用于角色中的所有任务。有关详细信息,请参阅 标签继承:向多个任务添加标签。
角色参数验证
从 2.11 版开始,您可以选择根据参数规范启用角色参数验证。此规范在 meta/argument_specs.yml
文件(或使用 .yaml
文件扩展名)中定义。定义此参数规范后,将在角色执行开始时插入一个新任务,该任务将根据规范验证为角色提供的参数。如果参数验证失败,则角色将执行失败。
注意
Ansible 还支持在角色 meta/main.yml
文件中定义的角色规范。但是,在 2.11 以下版本上,任何在该文件中定义规范的角色都无法工作。因此,我们建议使用 meta/argument_specs.yml
文件来保持向后兼容性。
注意
当在已定义 依赖项 的角色上使用角色参数验证时,即使依赖角色的参数验证失败,这些依赖项的验证也将先于依赖角色运行。
规范格式
角色参数规范必须在角色 meta/argument_specs.yml
文件中顶级 argument_specs
块中定义。所有字段都为小写。
- entry-point-name:
角色入口点的名称。
在未指定入口点的情况下,这应该是
main
。这将是要执行的任务文件的基名称,没有
.yml
或.yaml
文件扩展名。
- short_description:
入口点的简短说明,一行。理想情况下,它是一个短语而不是句子。
short_description
由ansible-doc -t role -l
显示。它也成为文档中角色页面标题的一部分。
简短说明应始终为字符串,而不是列表,并且不应以句点结尾。
- description:
可以包含多行的较长描述。
- 这可以是单个字符串或字符串列表。如果这是一个字符串列表,则每个列表
元素都是一个新段落。
- version_added:
添加入口点的角色版本。
这是一个字符串,而不是浮点数,例如
version_added: '2.1'
。在集合中,这必须是添加入口点的集合版本。例如,
version_added: 1.0.0
。
- author:
入口点作者的姓名。
这可以是单个字符串或字符串列表。每个作者使用一个列表条目。如果只有一个作者,则使用字符串或一个元素的列表。
- options:
选项通常称为“参数”或“参数”。本节定义了这些选项。
对于每个角色选项(参数),您可以包含
- option-name:
选项/参数的名称。
- description:
对该选项的作用的详细解释。它应该用完整的句子写成。
这可以是单个字符串或字符串列表。如果这是一个字符串列表,则每个列表元素都是一个新段落。
- version_added:
仅当此选项在初始角色/入口点发布后添加时才需要。换句话说,这大于顶级
version_added
字段。这是一个字符串,而不是浮点数,例如
version_added: '2.1'
。在集合中,这必须是添加选项的集合版本。例如,
version_added: 1.0.0
。
- type:
选项的数据类型。有关
type
的允许值,请参阅 参数规范。默认值为str
。如果选项的类型为
list
,则应指定elements
。
- required:
仅当
true
时才需要。如果缺少,则该选项不是必需的。
- default:
如果
required
为false
/缺少,则可以指定default
(如果缺少则假定为null
)。确保文档中的默认值与代码中的默认值匹配。角色变量的实际默认值始终来自角色默认值(如 角色目录结构 中所定义)。
除非默认值需要其他信息或条件,否则不应将其列为描述的一部分。
如果选项是布尔值,则如果要与
ansible-lint
兼容,则应使用true
/false
。
- choices:
选项值列表。
如果为空,则应不存在。
- 元素:
当类型为
list
时,指定列表元素的数据类型。
- options:
如果此选项采用字典或字典列表,则可以在此处定义结构。
示例规范
# roles/myapp/meta/argument_specs.yml
---
argument_specs:
# roles/myapp/tasks/main.yml entry point
main:
short_description: Main entry point for the myapp role
description:
- This is the main entrypoint for the C(myapp) role.
- Here we can describe what this entrypoint does in lengthy words.
- Every new list item is a new paragraph. You can have multiple sentences
per paragraph.
author:
- Daniel Ziegenberg
options:
myapp_int:
type: "int"
required: false
default: 42
description:
- "The integer value, defaulting to 42."
- "This is a second paragraph."
myapp_str:
type: "str"
required: true
description: "The string value"
myapp_list:
type: "list"
elements: "str"
required: true
description: "A list of string values."
version_added: 1.3.0
myapp_list_with_dicts:
type: "list"
elements: "dict"
required: false
default:
- myapp_food_kind: "meat"
myapp_food_boiling_required: true
myapp_food_preparation_time: 60
- myapp_food_kind: "fruits"
myapp_food_preparation_time: 5
description: "A list of dicts with a defined structure and with default a value."
options:
myapp_food_kind:
type: "str"
choices:
- "vegetables"
- "fruits"
- "grains"
- "meat"
required: false
description: "A string value with a limited list of allowed choices."
myapp_food_boiling_required:
type: "bool"
required: false
default: false
description: "Whether the kind of food requires boiling before consumption."
myapp_food_preparation_time:
type: int
required: true
description: "Time to prepare a dish in minutes."
myapp_dict_with_suboptions:
type: "dict"
required: false
default:
myapp_host: "bar.foo"
myapp_exclude_host: true
myapp_path: "/etc/myapp"
description: "A dict with a defined structure and default values."
options:
myapp_host:
type: "str"
choices:
- "foo.bar"
- "bar.foo"
- "ansible.foo.bar"
required: true
description: "A string value with a limited list of allowed choices."
myapp_exclude_host:
type: "bool"
required: true
description: "A boolean value."
myapp_path:
type: "path"
required: true
description: "A path value."
original_name:
type: list
elements: "str"
required: false
description: "An optional list of string values."
# roles/myapp/tasks/alternate.yml entry point
alternate:
short_description: Alternate entry point for the myapp role
description:
- This is the alternate entrypoint for the C(myapp) role.
version_added: 1.2.0
options:
myapp_int:
type: "int"
required: false
default: 1024
description: "The integer value, defaulting to 1024."
在一个 playbook 中多次运行一个角色
Ansible 在一个 playbook 中只执行每个角色一次,即使您多次定义它,除非为每个定义定义的参数不同。例如,Ansible 只在如下所示的 playbook 中运行一次角色 foo
---
- hosts: webservers
roles:
- foo
- bar
- foo
您有两个选项可以强制 Ansible 多次运行一个角色。
传递不同的参数
如果您在每个角色定义中传递不同的参数,Ansible 将多次运行该角色。提供不同的变量值与传递不同的角色参数并不相同。您必须为此行为使用 roles
关键字,因为 import_role
和 include_role
不接受角色参数。
此 playbook 会运行两次 foo
角色
---
- hosts: webservers
roles:
- { role: foo, message: "first" }
- { role: foo, message: "second" }
此语法也会运行两次 foo
角色;
---
- hosts: webservers
roles:
- role: foo
message: "first"
- role: foo
message: "second"
在这些示例中,Ansible 运行了两次 foo
,因为每个角色定义都有不同的参数。
使用 allow_duplicates: true
将 allow_duplicates: true
添加到角色的 meta/main.yml
文件中
# playbook.yml
---
- hosts: webservers
roles:
- foo
- foo
# roles/foo/meta/main.yml
---
allow_duplicates: true
在此示例中,Ansible 运行了两次 foo
,因为我们已明确启用它。
使用角色依赖项
角色依赖项允许您在使用角色时自动引入其他角色。
角色依赖项是先决条件,而不是真正的依赖项。角色之间没有父子关系。Ansible 加载所有列出的角色,首先运行在 dependencies
下列出的角色,然后运行列出它们的 角色。play 对象是所有角色的父级,包括由 dependencies
列表调用的角色。
角色依赖项存储在角色目录中的 meta/main.yml
文件中。此文件应包含一个角色列表和在指定角色之前插入的参数。例如
# roles/myapp/meta/main.yml
---
dependencies:
- role: common
vars:
some_parameter: 3
- role: apache
vars:
apache_port: 80
- role: postgres
vars:
dbname: blarg
other_parameter: 12
Ansible 始终在列出它们的 角色之前执行在 dependencies
中列出的角色。当您使用 roles
关键字时,Ansible 会递归地执行此模式。例如,如果您在 roles:
下列出角色 foo
,角色 foo
在其 meta/main.yml 文件中 dependencies
下列出角色 bar
,并且角色 bar
在其 meta/main.yml 文件中 dependencies
下列出角色 baz
,则 Ansible 会先执行 baz
,然后执行 bar
,最后执行 foo
。
在一个 playbook 中多次运行角色依赖项
Ansible 将重复的角色依赖项视为在 roles:
下列出的重复角色:Ansible 只执行一次角色依赖项,即使定义多次,除非为每个定义定义的参数、标签或 when 子句不同。如果一个 playbook 中的两个角色都将第三个角色列为依赖项,Ansible 只运行一次该角色依赖项,除非您传递不同的参数、标签、when 子句,或在要多次运行的角色中使用 allow_duplicates: true
。有关更多详细信息,请参阅 Galaxy 角色依赖项。
注意
角色去重不会查询父角色的调用签名。此外,当使用 vars:
而不是角色参数时,会改变变量作用域的副作用。使用 vars:
会导致这些变量在 playbook 级别范围内。在下面的示例中,使用 vars:
会导致在整个 playbook 中(包括之前调用的角色)将 n
定义为 4
。
此外,用户应该意识到角色去重发生在变量评估之前。这意味着 延迟评估 可能使看似不同的角色调用等效于相同,从而阻止角色多次运行。
例如,一个名为 car
的角色依赖于一个名为 wheel
的角色,如下所示
---
dependencies:
- role: wheel
n: 1
- role: wheel
n: 2
- role: wheel
n: 3
- role: wheel
n: 4
并且 wheel
角色依赖于两个角色:tire
和 brake
。然后 wheel
的 meta/main.yml
将包含以下内容
---
dependencies:
- role: tire
- role: brake
并且 tire
和 brake
的 meta/main.yml
将包含以下内容
---
allow_duplicates: true
最终的执行顺序如下
tire(n=1)
brake(n=1)
wheel(n=1)
tire(n=2)
brake(n=2)
wheel(n=2)
...
car
要将 allow_duplicates: true
与角色依赖项一起使用,必须为在 dependencies
下列出的角色指定它,而不是为列出它的角色指定它。在上面的示例中,allow_duplicates: true
出现在 tire
和 brake
角色的 meta/main.yml
中。 wheel
角色不需要 allow_duplicates: true
,因为由 car
定义的每个实例都使用不同的参数值。
注意
有关 Ansible 如何在不同位置定义的变量值(变量继承和作用域)之间进行选择的详细信息,请参阅 使用变量。此外,去重仅在 playbook 级别发生,因此同一 playbook 中的多个 playbook 可能会重新运行角色。
在角色中嵌入模块和插件
注意
这仅适用于独立角色。集合中的角色不支持插件嵌入;它们必须使用集合的 plugins
结构来分发插件。
如果您编写了一个自定义模块(请参阅 您是否应该开发一个模块?)或一个插件(请参阅 开发插件),您可能希望将其作为角色的一部分进行分发。例如,如果您编写了一个模块来帮助配置您公司的内部软件,并且您希望组织中的其他人使用此模块,但又不想告诉每个人如何配置他们的 Ansible 库路径,则可以将该模块包含在您的 internal_config 角色中。
要将模块或插件添加到角色:在角色的“任务”和“处理程序”结构旁边,添加一个名为“库”的目录,然后将模块直接包含在“库”目录中。
假设您有以下内容
roles/
my_custom_modules/
library/
module1
module2
该模块可以在角色本身中使用,也可以在此角色之后调用的任何角色中使用,如下所示
---
- hosts: webservers
roles:
- my_custom_modules
- some_other_role_using_my_custom_modules
- yet_another_role_using_my_custom_modules
如有必要,您还可以将模块嵌入到角色中以修改 Ansible 核心发行版中的模块。例如,您可以在生产版本中发布特定模块的开发版本之前,通过复制模块并将副本嵌入到角色中来使用它。谨慎使用此方法,因为核心组件中的 API 签名可能会发生变化,并且此解决方法不能保证有效。
相同的机制可用于在角色中嵌入和分发插件,使用相同的模式。例如,对于过滤器插件
roles/
my_custom_filter/
filter_plugins
filter1
filter2
然后可以在“my_custom_filter”之后调用的任何角色的 Jinja 模板中使用这些过滤器。