角色

角色允许您根据已知的文件夹结构自动加载相关的变量、文件、任务、处理器和其他 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.yamlmain

  • 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.txtvars/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'

目录 defaultsvars 也可能包含 *嵌套目录*。如果您的变量文件是一个目录,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.yamlmain):

  • roles/x/tasks/

  • roles/x/handlers/

  • roles/x/vars/

  • roles/x/defaults/

  • roles/x/meta/

  • 任何复制、脚本、模板或包含任务(在角色中)都可以引用 roles/x/{files,templates,tasks}/ 中的文件(目录取决于任务),而无需相对或绝对地对其进行路径化。

注意

varsdefaults 也可以匹配到同名的目录,Ansible 将处理该目录中包含的所有文件。有关更多详细信息,请参阅 角色目录结构

注意

如果使用 include_role/import_role,则可以指定自定义文件名而不是 mainmeta 目录是一个例外,因为它不允许自定义。

在 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 文件来保持向后兼容性。

注意

当在已定义 依赖项 的角色上使用角色参数验证时,即使依赖角色的参数验证失败,这些依赖项的验证也将先于依赖角色运行。

注意

Ansible 使用 always 标记插入的角色参数验证任务。如果角色是 静态导入 的,则此任务将运行,除非您使用 --skip-tags 标志。

规范格式

角色参数规范必须在角色 meta/argument_specs.yml 文件中顶级 argument_specs 块中定义。所有字段都为小写。

entry-point-name:
  • 角色入口点的名称。

  • 在未指定入口点的情况下,这应该是 main

  • 这将是要执行的任务文件的基名称,没有 .yml.yaml 文件扩展名。

short_description:
  • 入口点的简短说明,一行。理想情况下,它是一个短语而不是句子。

  • short_descriptionansible-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:
  • 如果 requiredfalse/缺少,则可以指定 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_roleinclude_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 角色依赖于两个角色:tirebrake。然后 wheelmeta/main.yml 将包含以下内容

---
dependencies:
  - role: tire
  - role: brake

并且 tirebrakemeta/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 出现在 tirebrake 角色的 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 模板中使用这些过滤器。

共享角色:Ansible Galaxy

Ansible Galaxy 是一个免费的网站,用于查找、下载、评分和审查各种社区开发的 Ansible 角色,并且可以成为自动化项目快速启动的绝佳方式。

客户端 ansible-galaxy 包含在 Ansible 中。Galaxy 客户端允许您从 Ansible Galaxy 下载角色,并为创建您自己的角色提供了一个极好的默认框架。

阅读 Ansible Galaxy 文档 页面以获取更多信息。

另请参阅

Galaxy 用户指南

如何创建新角色、在 Galaxy 上共享角色、角色管理

YAML 语法

了解 YAML 语法

使用 playbook

回顾基本的 Playbook 语言功能

一般提示

playbook 的提示和技巧

使用变量

playbook 中的变量

条件语句

playbook 中的条件语句

循环

playbook 中的循环

标签

使用标签在长 playbook 中选择或跳过角色/任务

集合索引

浏览现有的集合、模块和插件

您是否应该开发一个模块?

通过编写您自己的模块扩展 Ansible

沟通

有问题?需要帮助?想分享你的想法?访问 Ansible 通信指南